[
  {
    "path": ".devcontainer/README.md",
    "content": "# devcontainer\n\nThis directory provides a _devcontainer_ configuration that configures a\nreproducible development environment for this project.\n\nThe devcontainer configuration is maintained in the\n[linkerd/dev](https://github.com/linkerd/dev) repository.\n\n## Docker\n\nThis configuration currently uses the parent host's Docker daemon (rather than\nrunning a separate docker daemon within in the container). It creates\ndevcontainers on the host network so it's easy to use k3d clusters hosted in the\nparent host's docker daemon.\n\n## Customizing\n\nThis configuration is supposed to provide a minimal setup without catering to\nany one developer's personal tastes. Devcontainers can be extended with per-user\nconfiguration.\n\nTo add your own extensions to the devcontainer, configure default extensions in\nyour VS Code settings:\n\n```jsonc\n    \"remote.containers.defaultExtensions\": [\n        \"eamodio.gitlens\",\n        \"GitHub.copilot\",\n        \"GitHub.vscode-pull-request-github\",\n        \"mutantdino.resourcemonitor\",\n        \"stateful.edge\"\n    ],\n```\n\nFurthermore, you can configure a _dotfiles_ repository to perform customizations\nwith a configuration like:\n\n```jsonc\n    \"dotfiles.repository\": \"https://github.com/olix0r/dotfiles.git\",\n```\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n    \"name\": \"linkerd2\",\n    \"image\": \"ghcr.io/linkerd/dev:v48\",\n    // \"dockerFile\": \"./Dockerfile\",\n    // \"context\": \"..\",\n    \"features\": {\n        \"ghcr.io/devcontainers/features/github-cli:1\": {}\n    },\n    \"customizations\": {\n        \"vscode\": {\n            \"extensions\": [\n                \"DavidAnson.vscode-markdownlint\",\n                \"golang.go\",\n                \"kokakiwi.vscode-just\",\n                \"ms-kubernetes-tools.vscode-kubernetes-tools\",\n                \"NathanRidley.autotrim\",\n                \"rust-lang.rust-analyzer\",\n                \"samverschueren.final-newline\",\n                \"tamasfe.even-better-toml\",\n                \"zxh404.vscode-proto3\"\n            ],\n            \"settings\": {\n                \"go.lintTool\": \"golangci-lint\",\n                // TODO(ver) Find a way to enforce YAML formatting.\n                // See https://github.com/redhat-developer/vscode-yaml/discussions/839\n                \"yaml.format.enable\": false\n            }\n        }\n    },\n    \"runArgs\": [\n        \"--init\",\n        // Limit container memory usage.\n        \"--memory=12g\",\n        \"--memory-swap=12g\",\n        // Use the host network so we can access k3d, etc.\n        \"--net=host\",\n        // For lldb\n        \"--cap-add=SYS_PTRACE\",\n        \"--security-opt=seccomp=unconfined\"\n    ],\n    \"overrideCommand\": false,\n    \"remoteUser\": \"code\",\n    \"mounts\": [\n        \"source=/var/run/docker.sock,target=/var/run/docker-host.sock,type=bind\"\n    ]\n}\n"
  },
  {
    "path": ".dockerignore",
    "content": ".git\n.github\n**/.idea\n**/cmake-*\n**/CMakeLists.txt\n*.iml\n**/node_modules\nbin\n!bin/action-dev-check\n!bin/fetch-proxy\n!bin/install-deps\n!bin/scurl\n!bin/web\n**/Dockerfile*\nDockerfile*\n**/target\ntarget\n!target/docker-build\nweb/app/dist\nweb/app/yarn-error.log\nvendor\n"
  },
  {
    "path": ".editorconfig",
    "content": "# top-most EditorConfig file\nroot = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\ncharset = utf-8\nindent_style = space\nindent_size = 4\n\n# Go code uses tabs. Display with 4 space indentation in editors and on GitHub\n# (see https://github.com/isaacs/github/issues/170#issuecomment-150489692).\n[*.go]\nindent_style = tab\n\n[*.jsx]\nindent_size = 2\nindent_style = space\n\n[*.js]\nindent_size = 2\nindent_style = space\n\n[*.css]\nindent_size = 2\nindent_style = space\n\n[*.yml]\nindent_size = 2\n\n[*.yaml]\nindent_size = 2\n\n[*.proto]\nindent_size = 2\nindent_style = space\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Expand these files in PRs:\ngo.sum linguist-generated=false\n**/Cargo.lock linguist-generated=false\n# Collapse these files in PRs:\ncontroller/gen/**/*.pb.go linguist-generated=true\n**/*.golden linguist-generated=true\n**/*.golden.yml linguist-generated=true\ncontroller/proxy-injector/fake/data/* linguist-generated=true\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# By default, the maintainers group is responsible for everything.\n*   @linkerd/maintainers\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: \"🐛 Bug Report\"\ndescription: If you've found a reproducible bug\nlabels: [\"bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to file a bug report!\n\n        We're most likely to fix bugs that are easy to reproduce, so please try\n        to provide enough information for us to understand how to reproduce the\n        issue. Example Kubernetes manifests are encouraged!\n\n  - type: textarea\n    attributes:\n      label: What is the issue?\n      description: |\n        A clear and concise description of what Linkerd is doing and what you\n        would expect.\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: How can it be reproduced?\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Logs, error output, etc\n      description: |\n        If the output is long, please create a [gist](https://gist.github.com/)\n        and paste the link here.\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: output of `linkerd check -o short`\n      placeholder: |\n        ```text\n        your output here ...\n        ```\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Environment\n      placeholder: |\n        - Kubernetes Version:\n        - Cluster Environment: (GKE, AKS, kops, ...)\n        - Host OS:\n        - Linkerd version:\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Possible solution\n      description: \"If you have suggestions on a fix for the bug.\"\n\n  - type: textarea\n    attributes:\n      label: Additional context\n      description: \"Add any other context about the problem here. Or a screenshot if applicable.\"\n\n  - type: dropdown\n    attributes:\n      label: Would you like to work on fixing this bug?\n      description: We are more than happy to help you through the process.\n      options:\n        - \"yes\"\n        - \"no\"\n        - \"maybe\"\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n- name: 🙋🏾 Question\n  url: https://github.com/linkerd/linkerd2/discussions/new\n  about: If you need help debugging something or have a general question about Linkerd\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: \"💡 Feature Request\"\ndescription: If you have a suggestion for how to improve Linkerd\nlabels: [\"enhancement\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to suggest a new feature! Please fill out this form as completely as possible.\n\n        Please search to see if an issue already exists for the feature you are suggesting.\n\n  - type: textarea\n    attributes:\n      label: What problem are you trying to solve?\n      description: A concise description of what the problem is.\n      placeholder: I have an issue when [...]\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: How should the problem be solved?\n      description: What do you want to happen? Add any considered drawbacks.\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Any alternatives you've considered?\n      description: Is there another way to solve this problem that isn't as good a solution?\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: How would users interact with this feature?\n      description: If you can, explain how users will be able to use this. Maybe come sample CLI output?\n\n  - type: dropdown\n    attributes:\n      label: Would you like to work on this feature?\n      description: We are more than happy to help you through the process.\n      options:\n        - \"yes\"\n        - \"no\"\n        - \"maybe\"\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--  Thanks for sending a pull request!\n\nIf you already have a well-structured git commit message, chances are GitHub\nset the title and description of this PR to the git commit message subject and\nbody, respectively. If so, you may delete these instructions and submit your PR.\n\nIf this is your first time, please read our contributor guide:\nhttps://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md\n\nThe title and description of your Pull Request should match the git commit\nsubject and body, respectively. Git commit messages are structured as follows:\n\n```\nSubject\n\nProblem\n\nSolution\n\nValidation\n\nFixes #[GitHub issue ID]\n\nDCO Sign off\n```\n\nExample git commit message:\n\n```\nIntroduce Pull Request Template\n\nGitHub's community guidelines recommend a pull request template, the repo was\nlacking one.\n\nIntroduce a `PULL_REQUEST_TEMPLATE.md` file.\n\nOnce merged, the\n[Community profile checklist](https://github.com/linkerd/linkerd2/community)\nshould indicate the repo now provides a pull request template.\n\nFixes #3321\n\nSigned-off-by: Jane Smith <jane.smith@example.com>\n```\n\nNote the git commit message subject becomes the pull request title.\n\nFor more details around git commits, see the section on Committing in our\ncontributor guide:\nhttps://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md#committing\n-->\n"
  },
  {
    "path": ".github/actions/cli-setup/action.yaml",
    "content": "\nname: CLI Setup\ndescription: Fetches the Linkerd CLI from an artifact and installs it into the target.\n\ninputs:\n  artifact-id:\n    description: The ID of the artifact to download.\n    required: true\n  target:\n    description: The path to install the CLI into.\n    required: true\n\nruns:\n  using: composite\n  steps:\n    - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c\n      with:\n        artifact-ids: ${{ inputs.artifact-id }}\n        path: ${{ runner.temp }}/cli\n    - run: mv ${{ runner.temp }}/cli/linkerd2-cli-*-linux-amd64 ${{ inputs.target }} && chmod 755 ${{ inputs.target }}\n      shell: bash\n"
  },
  {
    "path": ".github/actions/docker-build/action.yml",
    "content": "name: Docker Build\ndescription: Builds linkerd's docker images\n\ninputs:\n  docker-registry:\n    description: The docker registry used to tag the images\n    required: false\n    default: cr.l5d.io/linkerd\n  docker-target:\n    description: The OS-arch the docker build will be targeted to\n    required: false\n    default: linux-amd64\n  docker-push:\n    description: Whether to push the built images to the registry\n    required: false\n    default: ''\n  component:\n    description: Component to build\n    required: true\n  tag:\n    description: Tag to use for the built image\n    required: true\n\nruns:\n  using: composite\n  steps:\n    # populate ACTIONS_CACHE_URL and ACTIONS_RUNTIME_TOKEN\n    - uses: crazy-max/ghaction-github-runtime@04d248b84655b509d8c44dc1d6f990c879747487\n    - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a\n    - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd\n      with:\n        driver-opts: network=host\n    - env:\n        DOCKER_REGISTRY: ${{ inputs.docker-registry }}\n        DOCKER_TARGET: ${{ inputs.docker-target }}\n        DOCKER_PUSH: ${{ inputs.docker-push }}\n        TAG: ${{ inputs.tag }}\n        RUNTIME_IMAGE: localhost:5000/linkerd/proxy-runtime:${{ inputs.tag }}\n        PUSH_RUNTIME_IMAGE: true\n      shell: bash\n      run: bin/docker-build-${{ inputs.component }}\n\n    - name: Output Image Digest\n      shell: bash\n      id: image_digest\n      run: |\n        image_digest=$(cat target/metadata-${{ inputs.component }}.json | jq -r '.\"containerimage.digest\"')\n        echo \"${image_digest}\"\n        echo \"DIGEST=${{ inputs.docker-registry }}/${{ inputs.component }}@${image_digest}\" >> \"$GITHUB_OUTPUT\"\n\noutputs:\n  image:\n    description: The image that was built\n    value: ${{ inputs.docker-registry }}/${{ inputs.component }}:${{ inputs.tag }}\n  digest:\n    description: The image digest\n    value: ${{ steps.image_digest.outputs.DIGEST }}\n"
  },
  {
    "path": ".github/actions/helm-publish/action.yml",
    "content": "name: Helm publish\ndescription: Helm chart creation and uploading\nruns:\n  using: composite\n  steps:\n  - name: Set up Cloud SDK\n    uses: 'google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db'\n  - uses: linkerd/dev/actions/setup-tools@v48\n  - shell: bash\n    run: |\n      mkdir -p target/helm\n      gsutil cp gs://helm.linkerd.io/edge/index.yaml target/helm/index-pre.yaml\n      bin/compute-edge-version update-charts\n      helm-docs\n      bin/helm-build package\n      cp charts/artifacthub-repo-edge.yml target/helm/artifacthub-repo.yml\n      gsutil rsync target/helm gs://helm.linkerd.io/edge\n"
  },
  {
    "path": ".github/dco.yml",
    "content": "require:\n  members: false\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# Dependabot are scheduled to avoid contention with normal workday CI usage. We\n# start running updates at 3AM UTC (7PM PST, 8AM IST) and stagger each\n# subsequent update by 30m.\n#\n# JS updates are run weekly.\nversion: 2\nupdates:\n  - package-ecosystem: gomod\n    directory: \"/\"\n    schedule:\n      interval: daily\n      time: \"03:00\"\n      timezone: Etc/UTC\n    ignore:\n      # TODO(ver): Remove this once we update our Gateway API dependencies.\n      - dependency-name: sigs.k8s.io/gateway-api\n        update-types: [version-update:semver-major, version-update:semver-minor]\n    groups:\n      kube:\n        patterns:\n          - k8s.io/*\n\n  - package-ecosystem: cargo\n    directory: \"/\"\n    schedule:\n      interval: daily\n      time: \"03:30\"\n      timezone: Etc/UTC\n    allow:\n      - dependency-type: all\n    ignore:\n      # These dependencies are for platforms that we don't support:\n      - dependency-name: redox*\n      - dependency-name: js-sys\n      - dependency-name: wasm-bindgen\n      - dependency-name: web-sys\n      # These dependencies are updated via kubert, so only accept patch updates.\n      - dependency-name: clap\n        update-types: [version-update:semver-major, version-update:semver-minor]\n      - dependency-name: clap_*\n        update-types: [version-update:semver-major, version-update:semver-minor]\n      - dependency-name: kube\n        update-types: [version-update:semver-major, version-update:semver-minor]\n      - dependency-name: kube-*\n        update-types: [version-update:semver-major, version-update:semver-minor]\n      - dependency-name: k8s-openapi\n        update-types: [version-update:semver-major, version-update:semver-minor]\n      # These dependencies are updated via linkerd2-proxy-api, so only accept patch updates.\n      - dependency-name: prost\n        update-types: [version-update:semver-major, version-update:semver-minor]\n      - dependency-name: prost-*\n        update-types: [version-update:semver-major, version-update:semver-minor]\n      - dependency-name: tonic\n        update-types: [version-update:semver-major, version-update:semver-minor]\n    groups:\n      clap:\n        patterns:\n          - clap\n          - clap_*\n        update-types: [minor, patch]\n      futures:\n        patterns:\n          - futures\n          - futures-*\n      kube-patch:\n        patterns:\n          - k8s-openapi\n          - kube\n          - kube-*\n          - gateway-api\n        update-types: [patch]\n      kube:\n        patterns:\n          - kubert\n          - kubert-*\n          - k8s-openapi\n          - kube\n          - kube-*\n          - gateway-api\n        update-types: [major, minor]\n      grpc:\n        patterns:\n          - prost\n          - prost-*\n          - tonic\n        update-types: [patch]\n      tracing:\n        patterns:\n          - tracing\n          - tracing-*\n      serde:\n        patterns:\n          - serde\n          - serde_*\n      pest:\n        patterns:\n          - pest\n          - pest_*\n\n  - package-ecosystem: \"github-actions\"\n    directories:\n    - \"/\"\n    - \".github/actions/*\"\n    schedule:\n      interval: \"daily\"\n      time: \"04:00\"\n      timezone: Etc/UTC\n\n  - package-ecosystem: \"npm\"\n    directory: \"/web/app\"\n    schedule:\n      # JS dependencies tend to be pretty noisy, so only check once per week.\n      interval: \"weekly\"\n      day: \"sunday\"\n    ignore:\n      # v6 is backwards-incompatible and requires lots of changes.\n      # A compatibility layer should come out at some point\n      # see https://reactrouter.com/docs/en/v6/upgrading/v5\n      - dependency-name: \"react-router-dom\"\n        update-types: [\"version-update:semver-major\"]\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 90\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 14\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - pinned \n  - area/security\n  - area/tls\n  - area/docs\n  - area/test\n  - good first issue\n  - benchmarking\n  - help wanted\n  - epic\n  - rfc\n  - roadmap \n# Label to use when marking an issue as stale\nstaleLabel: wontfix\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed in 14 days if no further activity occurs. Thank you\n  for your contributions.\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/actions.yml",
    "content": "name: Actions\n\non:\n  pull_request:\n    paths:\n      - .devcontainer/devcontainer.json\n      - .github/workflows/**\n\npermissions:\n  contents: read\n\njobs:\n  actionlint:\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    timeout-minutes: 10\n    steps:\n      - uses: linkerd/dev/actions/setup-tools@v48\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - run: just-dev lint-actions\n\n  devcontainer-versions:\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    steps:\n      - uses: linkerd/dev/actions/setup-tools@v48\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - run: just-dev check-action-images\n"
  },
  {
    "path": ".github/workflows/cli-build.yml",
    "content": "on:\n  workflow_call:\n    inputs:\n      version:\n        type: string\n        required: true\n      target:\n        type: string\n        required: false\n        default: 'multi-arch'\n    outputs:\n      artifact-id:\n        value: ${{ jobs.build.outputs.artifact-id }}\n\njobs:\n  build:\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    timeout-minutes: 20\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd\n      - uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294\n        id: build\n        with:\n          build-args: |\n            LINKERD_VERSION=${{ inputs.version }}\n          target: ${{ inputs.target }}\n          context: .\n          file: cli/Dockerfile\n          cache-from: type=gha,scope=${{ github.ref_name }}-cli\n          cache-to: type=gha,mode=max,scope=${{ github.ref_name }}-cli\n          push: false\n          load: false\n          outputs: type=local,dest=.\n      - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f\n        id: upload\n        with:\n          name: cli-bin-${{ inputs.version }}-${{ inputs.target }}\n          path: linkerd2-cli-${{ inputs.version }}-*\n    outputs:\n      artifact-id: ${{ steps.upload.outputs.artifact-id }}\n"
  },
  {
    "path": ".github/workflows/codecov.yml",
    "content": "name: Coverage\n\n# Run weekly on Sunday at midnight (UTC).\non:\n  schedule:\n    - cron: \"0 0 * * 0\"\n\npermissions:\n  contents: read\n\njobs:\n  go:\n    name: Go\n    timeout-minutes: 30\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    container:\n      image: golang:1.25\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - run: go install gotest.tools/gotestsum@v0.4.2\n      - run: gotestsum -- -cover -coverprofile=coverage.out -v -mod=readonly ./...\n      - uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad\n        with:\n          files: ./coverage.out\n          flags: unittests,golang\n\n  js:\n    name: JS\n    timeout-minutes: 30\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    container:\n      image: node:20-stretch\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - name: Yarn setup\n        shell: bash\n        run: bin/scurl -o- https://yarnpkg.com/install.sh | bash -s -- --version 1.21.1 --network-concurrency 1\n      - name: Unit tests\n        run: |\n          export PATH=\"$HOME/.yarn/bin:$PATH\"\n          export NODE_ENV=test\n          bin/web --frozen-lockfile\n          bin/web test --reporters=\"jest-progress-bar-reporter\" --reporters=\"./gh_ann_reporter.js\" --coverage\n      - uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad\n        with:\n          directory: ./web/app/coverage\n          flags: unittests,javascript\n\n  rust:\n    name: Rust\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    timeout-minutes: 15\n    container:\n      image: docker://rust:1.90.0\n      options: --security-opt seccomp=unconfined\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - shell: bash\n        run: mkdir -p target && cd target && bin/scurl -v https://github.com/xd009642/tarpaulin/releases/download/0.27.3/cargo-tarpaulin-x86_64-unknown-linux-musl.tar.gz | tar zxvf - && chmod 755 cargo-tarpaulin\n      - run: target/cargo-tarpaulin tarpaulin --workspace --out Xml\n      - uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad\n        with:\n          flags: unittests,rust\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "# See https://github.com/github/codeql-action/tree/v1 for more information.\n\nname: CodeQL\n\non:\n  push:\n    branches: [main, stable-*]\n    paths:\n      - .github/workflows/codeql.yml\n      - \"**/*.go\"\n      - \"**/*.js\"\n      - \"**/*.jsx\"\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [main, stable-*]\n    paths:\n      - .github/workflows/codeql.yml\n      - \"**/*.go\"\n      - \"**/*.js\"\n      - \"**/*.jsx\"\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language:\n          - go\n          - javascript\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417\n        with:\n          go-version-file: go.mod\n\n      - name: Initialize\n        # Unpinned action version so that we automatically get analyzer updates.\n        uses: github/codeql-action/init@v4\n        with:\n          languages: ${{ matrix.language }}\n\n      - name: Analyze\n        uses: github/codeql-action/analyze@v4\n"
  },
  {
    "path": ".github/workflows/devcontainer.yml",
    "content": "name: Devcontainer\n\n# When a pull request is opened that changes the Devcontainer configuration,\n# ensure that the container continues to build properly.\non:\n  pull_request:\n    paths:\n      - .devcontainer/devcontainer.json\n      - .github/workflows/devcontainer.yml\n      - rust-toolchain\n\npermissions:\n  contents: read\n\njobs:\n  rust-version:\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    container: ghcr.io/linkerd/dev:v48-rust\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - shell: bash\n        run: |\n          # Extract current Rust version from the toolchain file.\n          version_regex='channel = \"([0-9]+\\.[0-9]+\\.[0-9]+)\"'\n          toolchain=$(cat rust-toolchain.toml)\n          if [[ $toolchain =~ $version_regex ]]; then\n            tc=${BASH_REMATCH[1]}\n          else\n            echo \"::error file=rust-toolchain.toml::failed to parse rust-toolchain.toml\"\n            exit 1\n          fi\n\n          dev=$(cargo version | cut -d' ' -f2)\n          if [ \"$dev\" != \"$tc\" ]; then\n            echo \"::error file=rust-toolchain.toml,line=2::rust-toolchain ($tc) does not match devcontainer ($dev)\"\n            exit 1\n          fi\n\n  devcontainer-image:\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    steps:\n      - uses: linkerd/dev/actions/setup-tools@v48\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - run: just-dev pull-dev-image\n"
  },
  {
    "path": ".github/workflows/go.yml",
    "content": "name: Go\non: pull_request\n\npermissions:\n  contents: read\n\njobs:\n  meta:\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323\n        id: changed\n        with:\n          files: |\n            .github/workflows/go.yml\n            go.sum\n            **/*.go\n            **/*.golden\n            **/charts/**\n          files_ignore: |\n            **/Chart.yaml\n            **/README*\n    outputs:\n      changed: ${{ steps.changed.outputs.any_changed }}\n\n  go-lint:\n    needs: meta\n    if: needs.meta.outputs.changed == 'true'\n    timeout-minutes: 10\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    container: ghcr.io/linkerd/dev:v48-go\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417\n        with:\n          go-version-file: go.mod\n      - run: git config --global --add safe.directory \"$PWD\" # actions/runner#2033\n      - run: just go-lint --verbose --timeout=10m\n\n  go-format:\n    needs: meta\n    if: needs.meta.outputs.changed == 'true'\n    timeout-minutes: 10\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    container: ghcr.io/linkerd/dev:v48-go\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417\n        with:\n          go-version-file: go.mod\n      - run: git config --global --add safe.directory \"$PWD\" # actions/runner#2033\n      - run: just go-fmt\n\n  go-test:\n    needs: meta\n    if: needs.meta.outputs.changed == 'true'\n    timeout-minutes: 30\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    container: ghcr.io/linkerd/dev:v48-go\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417\n        with:\n          go-version-file: go.mod\n      - run: git config --global --add safe.directory \"$PWD\" # actions/runner#2033\n      - run: just go-fetch\n      - run: just go-test\n\n  # There's currently one flakey test we want to retry in particular:\n  # TestEndpointProfileTranslator/Handles_overflow\n  go-test-retry:\n    needs: go-test\n    if: failure() && fromJSON(github.run_attempt) < 3\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    permissions:\n      actions: write\n    env:\n      GH_REPO: ${{ github.repository }}\n      GH_TOKEN: ${{ github.token }}\n      GH_DEBUG: api\n      REF: ${{ github.head_ref }}\n    steps:\n      - run: gh workflow run rerun.yml -F 'run_id=${{ github.run_id }}' --ref \"$REF\"\n\n  go-ok:\n    needs: [go-lint, go-format, go-test]\n    if: always()\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    steps:\n      - name: Results\n        run: |\n          echo 'go-lint: ${{ needs.go-lint.result }}'\n          echo 'go-format: ${{ needs.go-format.result }}'\n          echo 'go-test: ${{ needs.go-test.result }}'\n\n      - name: Verify jobs\n        # All jobs must succeed or be skipped.\n        if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')\n        run: exit 1\n"
  },
  {
    "path": ".github/workflows/integration.yml",
    "content": "name: Integration tests\n\non: pull_request\n\npermissions:\n  contents: read\n\nenv:\n  CARGO_INCREMENTAL: 0\n  CARGO_NET_RETRY: 10\n  CARGO_NEXTEST_VERSION: 0.9.100\n  DOCKER_REGISTRY: ghcr.io/linkerd\n  GH_ANNOTATION: true\n  K3D_VERSION: v5.8.3\n  RUST_BACKTRACE: short\n  RUSTUP_MAX_RETRIES: 10\n  YQ_VERSION: v4.44.5\n  LINKERD2_PROXY_REPO: ${{ vars.LINKERD2_PROXY_REPO || 'linkerd/linkerd2-proxy' }}\n  LINKERD2_PROXY_RELEASE_PREFIX: ${{ vars.LINKERD2_PROXY_RELEASE_PREFIX || 'release/' }}\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.head_ref }}\n  cancel-in-progress: true\n\njobs:\n  meta:\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - id: tag\n        run: echo \"tag=$(CI_FORCE_CLEAN=1 bin/root-tag)\" >> \"$GITHUB_OUTPUT\"\n      - uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323\n        id: core\n        with:\n          files: |\n            .github/workflows/integration.yml\n            .proxy-version\n            go.sum\n            **/*.go\n            **/*.rs\n            **/Dockerfile*\n            charts/**\n            multicluster/charts/**\n            justfile\n            bin/fetch-proxy\n            bin/_test-helper.sh\n          files_ignore: |\n            .devcontainer/**\n            **/Chart.yaml\n            **/README*\n    outputs:\n      tag: ${{ steps.tag.outputs.tag }}\n      changed: ${{ steps.core.outputs.any_changed }}\n\n  info:\n    needs: meta\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    timeout-minutes: 2\n    steps:\n      - name: Info\n        run: |\n          echo \"tag=${{ needs.meta.outputs.tag }}\"\n          echo \"changed=${{ needs.meta.outputs.changed }}\"\n\n  build-cli:\n    needs: meta\n    if: needs.meta.outputs.changed == 'true'\n    uses: ./.github/workflows/cli-build.yml\n    with:\n      version: ${{ needs.meta.outputs.tag }}\n      target: linux-amd64\n\n  ##\n  ## Core: Test the core control plane\n  ##\n  ## TODO(ver) CNI configurations should be tested separately.\n  ##\n\n  build-core:\n    needs: meta\n    if: needs.meta.outputs.changed == 'true'\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    services:\n      registry:\n        image: registry:3\n        ports:\n          - 5000:5000\n    strategy:\n      matrix:\n        component:\n          - controller\n          - proxy\n    timeout-minutes: 20\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: ./.github/actions/docker-build\n        id: build\n        env:\n          LINKERD2_PROXY_GITHUB_TOKEN: ${{ secrets.LINKERD2_PROXY_GITHUB_TOKEN || github.token }}\n        with:\n          docker-registry: ${{ env.DOCKER_REGISTRY }}\n          docker-target: linux-amd64\n          component: ${{ matrix.component }}\n          tag: ${{ needs.meta.outputs.tag }}\n      - name: Run docker save\n        run: |\n          mkdir -p /home/runner/archives\n          docker save '${{ steps.build.outputs.image }}' >'/home/runner/archives/${{ matrix.component }}.tar'\n      - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f\n        with:\n          name: image-archives-${{ matrix.component }}\n          path: /home/runner/archives\n\n  test-core:\n    needs: [meta, build-cli, build-core]\n    if: needs.meta.outputs.changed == 'true'\n    strategy:\n      fail-fast: false\n      matrix:\n        test:\n          - cni-calico-deep\n          - deep\n          - deep-native-sidecar\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    timeout-minutes: 15\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417\n        with:\n          go-version-file: go.mod\n      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c\n        with:\n          pattern: image-archives-*\n          path: image-archives\n          merge-multiple: true\n      - run: find image-archives -ls\n      - uses: ./.github/actions/cli-setup\n        with:\n          artifact-id: ${{ needs.build-cli.outputs.artifact-id }}\n          target: ${{ runner.temp }}/linkerd\n      - run: bin/tests --images archive --cleanup-docker --name ${{ matrix.test }} ${{ runner.temp }}/linkerd\n        env:\n          LINKERD_DOCKER_REGISTRY: ${{ env.DOCKER_REGISTRY }}\n          TAG: ${{ needs.meta.outputs.tag }}\n\n  ##\n  ## Policy: Only run policy tests when the policy controller or proxy changes\n  ##\n\n  test-policy:\n    needs: [meta, build-cli, build-core]\n    if: needs.meta.outputs.changed == 'true'\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    timeout-minutes: 20\n    strategy:\n      fail-fast: false\n      matrix:\n        # Test the latest Kubernetes version with the latest Gateway API\n        # version, as well as the vendored Gateway API CRDs.\n        k8s:\n          - v1.34\n        gateway-api:\n          - version: linkerd\n            channel: experimental\n          - version: v1.4.0\n            channel: experimental\n        # Also test the Minimum Supported Kubernetes Version with the Minimum\n        # Supported Gateway API version.\n        include:\n          - k8s: v1.23\n            gateway-api:\n              version: v1.1.1\n              channel: standard\n    env:\n      GATEWAY_API_VERSION: ${{ matrix.gateway-api.version }}\n      GATEWAY_API_CHANNEL: ${{ matrix.gateway-api.channel }}\n    steps:\n      - uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - uses: olix0r/cargo-action-fmt/setup@9269f3aa1ff01775d95efc97037e2cbdb41d9684\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c\n        with:\n          pattern: image-archives-*\n          path: image-archives\n          merge-multiple: true\n      - run: find image-archives -ls\n      - uses: ./.github/actions/cli-setup\n        with:\n          artifact-id: ${{ needs.build-cli.outputs.artifact-id }}\n          target: ${{ runner.temp }}/linkerd\n      - name: Setup deps\n        shell: bash\n        run: |\n          rm -rf \"$HOME/.cargo\"\n          bin/scurl -v https://sh.rustup.rs | sh -s -- -y --default-toolchain \"$(./bin/rust-toolchain-version)\"\n          # shellcheck disable=SC1090\n          source ~/.cargo/env\n          echo \"PATH=$PATH\" >> \"$GITHUB_ENV\"\n          bin/scurl -v \"https://raw.githubusercontent.com/k3d-io/k3d/${K3D_VERSION}/install.sh\" | bash\n          bin/scurl -vo /usr/local/bin/yq \"https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64\" && chmod +x /usr/local/bin/yq\n          bin/scurl -v \"https://github.com/nextest-rs/nextest/releases/download/cargo-nextest-${CARGO_NEXTEST_VERSION}/cargo-nextest-${CARGO_NEXTEST_VERSION}-x86_64-unknown-linux-musl.tar.gz\" -o nextest.tar.gz\n          tar -xzf nextest.tar.gz\n          mv cargo-nextest ~/.cargo/bin/\n          chmod +x ~/.cargo/bin/cargo-nextest\n      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4\n      - run: just policy-test-build\n      - run: just k3d-k8s='${{ matrix.k8s }}' k3d-create\n      - run: docker load <image-archives/controller.tar\n      - run: docker load <image-archives/proxy.tar\n      - run: docker image ls\n      - run: just linkerd-tag='${{ needs.meta.outputs.tag }}' linkerd-exec=\"${{ runner.temp }}/linkerd\" linkerd-install\n      - name: Load images\n        run: |\n          # Image loading is flakey in CI, so retry!\n          for _ in {1..6} ; do\n            if just linkerd-tag='${{ needs.meta.outputs.tag }}' policy-test-deps-load ; then exit 0 ; fi\n            sleep 10\n            echo retrying...\n          done\n          exit 1\n      - run: just policy-test-run --jobs=1\n        env:\n          # https://nexte.st/book/retries.html\n          NEXTEST_RETRIES: 3\n\n  ##\n  ## Ext: Run tests that require non-core components.\n  ##\n\n  build-ext:\n    needs: meta\n    if: needs.meta.outputs.changed == 'true'\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    strategy:\n      matrix:\n        component:\n          - metrics-api\n          - tap\n          - web\n    timeout-minutes: 15\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: ./.github/actions/docker-build\n        id: build\n        with:\n          docker-registry: ${{ env.DOCKER_REGISTRY }}\n          docker-target: linux-amd64\n          component: ${{ matrix.component }}\n          tag: ${{ needs.meta.outputs.tag }}\n      - name: Run docker save\n        run: |\n          mkdir -p /home/runner/archives\n          docker save '${{ steps.build.outputs.image }}' >'/home/runner/archives/${{ matrix.component }}.tar'\n      - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f\n        with:\n          name: image-archives-${{ matrix.component }}\n          path: /home/runner/archives\n\n  # These tests exercise core functionality, but need the viz extension.\n  test-ext:\n    needs: [meta, build-cli, build-core, build-ext]\n    if: needs.meta.outputs.changed == 'true'\n    strategy:\n      fail-fast: false\n      matrix:\n        integration_test:\n          - cluster-domain\n          - default-policy-deny\n          - external\n          - rsa-ca\n          - tracing\n          - helm-upgrade\n          - uninstall\n          - upgrade-edge\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    timeout-minutes: 15\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417\n        with:\n          go-version-file: go.mod\n      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c\n        with:\n          pattern: image-archives-*\n          path: image-archives\n          merge-multiple: true\n      - run: find image-archives -ls\n      - uses: ./.github/actions/cli-setup\n        with:\n          artifact-id: ${{ needs.build-cli.outputs.artifact-id }}\n          target: ${{ runner.temp }}/linkerd\n      - run: bin/tests --images archive --cleanup-docker --name '${{ matrix.integration_test }}' ${{ runner.temp }}/linkerd\n        env:\n          LINKERD_DOCKER_REGISTRY: ${{ env.DOCKER_REGISTRY }}\n\n  ##\n  ## Viz: Run the (flakey) `viz` suite only when the `viz` extension is updated.\n  ##\n\n  test-viz:\n    needs: [meta, build-cli, build-core, build-ext]\n    if: needs.meta.outputs.changed == 'true'\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417\n        with:\n          go-version-file: go.mod\n      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c\n        with:\n          pattern: image-archives-*\n          path: image-archives\n          merge-multiple: true\n      - uses: ./.github/actions/cli-setup\n        with:\n          artifact-id: ${{ needs.build-cli.outputs.artifact-id }}\n          target: ${{ runner.temp }}/linkerd\n      - run: bin/tests --images archive --cleanup-docker --name viz ${{ runner.temp }}/linkerd\n        env:\n          LINKERD_DOCKER_REGISTRY: ${{ env.DOCKER_REGISTRY }}\n\n  ##\n  ## Multicluster: Run 'multicluster' suite only when the 'multicluster' extension is updated.\n  ##               Tests are run on min and max k8s versions\n  ##\n\n  test-multicluster:\n    needs: [meta, build-cli, build-core, build-ext]\n    if: needs.meta.outputs.changed == 'true'\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    timeout-minutes: 30\n    strategy:\n      fail-fast: false\n      matrix:\n        cases:\n        - {k8s: \"v1.23\", manage-controllers: false, install-gwapi: true}\n        # for v1.34 onwards, gwapi gets installed by Traefik on the target cluster\n        - {k8s: \"v1.34\", manage-controllers: false, install-gwapi: false}\n        - {k8s: \"v1.34\", manage-controllers: true, install-gwapi: false}\n    name: \"test-multicluster(${{ matrix.cases.k8s }}, ${{ matrix.cases.manage-controllers && 'managed-controllers' || 'unmanaged-controllers' }})\"\n    steps:\n      - uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417\n        with:\n          go-version-file: go.mod\n      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c\n        with:\n          pattern: image-archives-*\n          path: image-archives\n          merge-multiple: true\n      - run: find image-archives -ls\n      - uses: ./.github/actions/cli-setup\n        with:\n          artifact-id: ${{ needs.build-cli.outputs.artifact-id }}\n          target: ${{ runner.temp }}/linkerd\n      - name: Setup deps\n        shell: bash\n        run: |\n          echo \"PATH=$PATH\" >> \"$GITHUB_ENV\"\n          bin/scurl -v \"https://raw.githubusercontent.com/k3d-io/k3d/${K3D_VERSION}/install.sh\" | bash\n      - name: Load docker images\n        run: |\n          for img in controller proxy; do\n            docker load <\"image-archives/${img}.tar\"\n          done\n      - run: docker image ls\n      - run: just mc-test-build\n      - name: Run just mc-load\n        run: |\n          just linkerd-tag='${{ needs.meta.outputs.tag }}' \\\n              k3d-k8s='${{ matrix.cases.k8s }}' \\\n              mc-load\n      - run: just mc-install-gwapi\n      - name: Run just mc-target-load\n        run: |\n          just linkerd-tag='${{ needs.meta.outputs.tag }}' \\\n              k3d-k8s='${{ matrix.cases.k8s }}' \\\n              mc-target-load\n      - name: Run just mc-install-gwapi\n        if: ${{ matrix.cases.install-gwapi }}\n        run: just k3d-name='l5d-test-target' mc-install-gwapi\n      - run: just mc-flat-network-init\n      - name: Run just mc-test-run\n        run: |\n          just linkerd-tag='${{ needs.meta.outputs.tag }}' \\\n              k3d-k8s='${{ matrix.cases.k8s }}' \\\n              mc-test-run -multicluster-manage-controllers=${{ matrix.cases.manage-controllers }}\n\n  build-ok:\n    needs: [build-cli, build-core, build-ext]\n    if: always()\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    steps:\n      - name: Results\n        run: |\n          echo 'needs.build-cli.result: ${{ needs.build-cli.result }}'\n          echo 'needs.build-core.result: ${{ needs.build-core.result }}'\n          echo 'needs.build-ext.result: ${{ needs.build-ext.result }}'\n      - name: Verify jobs\n        # All jobs must succeed or be skipped.\n        if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')\n        run: exit 1\n\n  # Try to re-run the integration tests if they fail, but only up to 3 times.\n  integrations-retry:\n    needs:\n      [build-ok, test-core, test-policy, test-ext, test-viz, test-multicluster]\n    if: failure() && fromJSON(github.run_attempt) < 3 && needs.build-ok.result == 'success'\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    permissions:\n      actions: write\n    env:\n      GH_REPO: ${{ github.repository }}\n      GH_TOKEN: ${{ github.token }}\n      GH_DEBUG: api\n      REF: ${{ github.head_ref }}\n    steps:\n      - run: gh workflow run rerun.yml -F 'run_id=${{ github.run_id }}' --ref \"$REF\"\n\n  integrations-ok:\n    needs:\n      [build-ok, test-core, test-policy, test-ext, test-viz, test-multicluster]\n    if: always()\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    steps:\n      - name: Results\n        run: |\n          echo 'needs.build-ok.result: ${{ needs.build-ok.result }}'\n          echo 'needs.test-core.result: ${{ needs.test-core.result }}'\n          echo 'needs.test-policy.result: ${{ needs.test-policy.result }}'\n          echo 'needs.test-ext.result: ${{ needs.test-ext.result }}'\n          echo 'needs.test-viz.result: ${{ needs.test-viz.result }}'\n          echo 'needs.test-multicluster.result: ${{ needs.test-multicluster.result }}'\n      - name: Verify jobs\n        # All jobs must succeed or be skipped.\n        if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')\n        run: exit 1\n"
  },
  {
    "path": ".github/workflows/js.yml",
    "content": "name: JS\n\non:\n  pull_request:\n    paths:\n      - .github/workflows/js.yml\n      - bin/web*\n      - web/app/**\n\npermissions:\n  contents: read\n\njobs:\n  js-web-test:\n    timeout-minutes: 30\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    container:\n      image: node:20-bookworm\n    env:\n      NODE_ENV: test\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - name: Yarn setup\n        shell: bash\n        run: |\n          bin/scurl --retry 2 https://yarnpkg.com/install.sh | bash -s -- --version 1.21.1 --network-concurrency 1\n          echo PATH=\"$HOME/.yarn/bin:$PATH\" >> \"$GITHUB_ENV\"\n      - run: bin/web --frozen-lockfile\n      - run: bin/web test --reporters=\"jest-progress-bar-reporter\" --reporters=\"./gh_ann_reporter.js\"\n"
  },
  {
    "path": ".github/workflows/lock.yml",
    "content": "name: \"Lock Threads\"\n\non:\n  schedule:\n    - cron: \"0 1 * * *\"\n\npermissions:\n  issues: write\n\njobs:\n  action:\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    steps:\n      - uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7\n        with:\n          issue-inactive-days: \"30\"\n          process-only: \"issues\"\n"
  },
  {
    "path": ".github/workflows/markdown.yml",
    "content": "name: markdown\n\npermissions:\n  contents: read\n\non:\n  pull_request:\n    paths:\n      - .github/workflows/markdown.yml\n      - \"**/*.md\"\n\njobs:\n  markdownlint:\n    timeout-minutes: 5\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: DavidAnson/markdownlint-cli2-action@07035fd053f7be764496c0f8d8f9f41f98305101\n        with:\n          globs: |\n            **/*.md\n            !**/node_modules/**\n            !target/**\n"
  },
  {
    "path": ".github/workflows/proto.yml",
    "content": "name: Proto\n\non:\n  pull_request:\n    paths:\n      - .github/workflows/proto.yml\n      - bin/protoc*\n      - \"**/*.proto\"\n      - \"**/gen/**/*.go\"\n\npermissions:\n  contents: read\n\njobs:\n  proto-diff:\n    timeout-minutes: 10\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    container: ghcr.io/linkerd/dev:v48-go\n    steps:\n      - run: apt update && apt install -y unzip\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - run: git config --global --add safe.directory \"$PWD\" # actions/runner#2033\n      - run: bin/protoc-go.sh\n      - run: git diff --exit-code\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - \"edge-*\"\n\npermissions:\n  contents: read\n\nenv:\n  GH_ANNOTATION: true\n  DOCKER_REGISTRY: ghcr.io/linkerd\n  K3D_VERSION: v5.8.3\n  LINKERD2_PROXY_REPO: ${{ vars.LINKERD2_PROXY_REPO }}\n\njobs:\n  # TODO(ver) We should stop relying so heavily on the environment,\n  # especially the TAG variable. And it would be great to stop relying\n  # on the root-tag script altogether.\n  tag:\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - run: echo \"tag=$(CI_FORCE_CLEAN=1 bin/root-tag)\" >> \"$GITHUB_OUTPUT\"\n        id: tag\n      - name: Validate edge version\n        run: bin/compute-edge-version\n    outputs:\n      tag: ${{ steps.tag.outputs.tag }}\n\n  docker_build:\n    name: Docker build\n    needs: [tag]\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    permissions:\n      contents: read\n      packages: write # for docker/login-action\n      id-token: write # for cosign\n    services:\n      registry:\n        image: registry:3\n        ports:\n          - 5000:5000\n    strategy:\n      matrix:\n        component:\n          - controller\n          - debug\n          - metrics-api\n          - proxy\n          - tap\n          - web\n    timeout-minutes: 45\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - name: Set tag\n        run: echo 'TAG=${{ needs.tag.outputs.tag }}' >> \"$GITHUB_ENV\"\n      - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2\n        with:\n          registry: ghcr.io\n          username: ${{ secrets.DOCKER_GHCR_USERNAME }}\n          password: ${{ secrets.DOCKER_GHCR_PAT }}\n      - uses: ./.github/actions/docker-build\n        id: build\n        with:\n          docker-registry: ${{ env.DOCKER_REGISTRY }}\n          docker-target: multi-arch\n          docker-push: 1\n          component: ${{ matrix.component }}\n          tag: ${{ needs.tag.outputs.tag }}\n        env:\n          LINKERD2_PROXY_GITHUB_TOKEN: ${{ secrets.LINKERD2_PROXY_GITHUB_TOKEN }}\n      - uses: sigstore/cosign-installer@v3\n      - run: cosign sign '${{ steps.build.outputs.digest }}'\n        env:\n          COSIGN_YES: true\n\n  cli:\n    needs: tag\n    uses: ./.github/workflows/cli-build.yml\n    with:\n      version: ${{ needs.tag.outputs.tag }}\n\n  integration_tests:\n    name: Integration tests\n    needs: [tag, cli, docker_build]\n    strategy:\n      matrix:\n        integration_test:\n          - cluster-domain\n          - cni-calico-deep\n          - deep\n          - viz\n          - tracing\n          - default-policy-deny\n          - external\n          - rsa-ca\n          - helm-upgrade\n          - uninstall\n          - upgrade-edge\n    timeout-minutes: 60\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417\n        with:\n          go-version-file: go.mod\n      - uses: ./.github/actions/cli-setup\n        with:\n          artifact-id: ${{ needs.cli.outputs.artifact-id }}\n          target: ${{ runner.temp }}/linkerd\n      - run: echo TAG=\"${{ needs.tag.outputs.tag }}\" >> \"$GITHUB_ENV\"\n      - name: Validate the CLI version matches the current build tag.\n        run: |\n          [[ \"$TAG\" == \"$(${{ runner.temp }}/linkerd version --short --client)\" ]]\n      - run: bin/tests --images preload --name ${{ matrix.integration_test }} \"${{ runner.temp }}/linkerd\"\n        env:\n          LINKERD_DOCKER_REGISTRY: ${{ env.DOCKER_REGISTRY }}\n\n  gh_release:\n    name: Create GH release\n    needs:\n      - tag\n      - cli\n      - integration_tests\n    timeout-minutes: 30\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    permissions:\n      contents: write\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c\n        with:\n          artifact-ids: ${{ needs.cli.outputs.artifact-id }}\n          path: cli\n      - name: Create release\n        id: create_release\n        uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe\n        with:\n          name: \"${{ needs.tag.outputs.tag }}\"\n          generate_release_notes: true\n          draft: false\n          prerelease: false\n          files: |\n            ./cli/linkerd2-cli-*\n\n  website_publish:\n    name: Linkerd website publish\n    needs: [chart_deploy]\n    if: startsWith(github.ref, 'refs/tags/stable') || startsWith(github.ref, 'refs/tags/edge')\n    timeout-minutes: 30\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    permissions:\n      contents: write\n    steps:\n      - name: Create linkerd/website repository dispatch event\n        uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697\n        with:\n          token: ${{ secrets.RELEASE_TOKEN }}\n          repository: linkerd/website\n          event-type: release\n\n  website_publish_check:\n    name: Linkerd website publish check\n    needs: [tag, website_publish]\n    timeout-minutes: 30\n    if: startsWith(github.ref, 'refs/tags/stable') || startsWith(github.ref, 'refs/tags/edge')\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - name: Set install target for stable\n        if: startsWith(github.ref, 'refs/tags/stable')\n        run: echo \"INSTALL=install\" >> \"$GITHUB_ENV\"\n      - name: Set install target for edge\n        if: startsWith(github.ref, 'refs/tags/edge')\n        run: echo \"INSTALL=install-edge\" >> \"$GITHUB_ENV\"\n      - name: Check published version\n        shell: bash\n        run: |\n          TAG='${{ needs.tag.outputs.tag }}'\n          until RES=$(bin/scurl \"https://run.linkerd.io/$INSTALL\" | grep \"LINKERD2_VERSION=\\${LINKERD2_VERSION:-$TAG}\") \\\n            || (( count++ >= 10 ))\n          do\n            sleep 30\n          done\n          if [[ -z \"$RES\" ]]; then\n            echo \"::error::The version '$TAG' was NOT found published in the website\"\n            exit 1\n          fi\n\n  chart_deploy:\n    name: Helm chart deploy\n    needs: [gh_release]\n    timeout-minutes: 30\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - name: Log into GCP\n        uses: \"google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093\"\n        with:\n          credentials_json: ${{ secrets.LINKERD_SITE_TOKEN }}\n      - name: Edge Helm chart creation and upload\n        uses: ./.github/actions/helm-publish\n"
  },
  {
    "path": ".github/workflows/rerun.yml",
    "content": "# This can be invoked from any other workflow to re-run itself on failure.\n# From https://github.com/orgs/community/discussions/67654#discussioncomment-8038649\nname: Re-run failed workflow\n\non:\n  workflow_dispatch:\n    inputs:\n      run_id:\n        description: \"The ID of the run to rerun\"\n        required: true\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ inputs.run_id }}\n  cancel-in-progress: true\n\njobs:\n  rerun:\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    timeout-minutes: 5\n    permissions:\n      actions: write\n    env:\n      GH_REPO: ${{ github.repository }}\n      GH_TOKEN: ${{ github.token }}\n      GH_DEBUG: api\n    steps:\n      - run: gh run watch ${{ inputs.run_id }}\n      - run: gh run rerun ${{ inputs.run_id }} --failed\n"
  },
  {
    "path": ".github/workflows/rust.yml",
    "content": "name: Rust\n\non:\n  pull_request:\n    paths:\n      - .github/workflows/rust.yml\n      - Cargo.lock\n      - \"**/Cargo.toml\"\n      - justfile\n      - deny.toml\n      - \"**/*.rs\"\n      - policy-*/Dockerfile\n      - rust-toolchain.toml\n      - bin/rust-toolchain-version\n\npermissions:\n  contents: read\n\nenv:\n  CARGO_INCREMENTAL: 0\n  CARGO_NET_RETRY: 10\n  PROTOC_NO_VENDOR: 1\n  RUST_BACKTRACE: short\n  RUSTUP_MAX_RETRIES: 10\n\njobs:\n  audit:\n    timeout-minutes: 10\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    container: ghcr.io/linkerd/dev:v48-rust\n    strategy:\n      matrix:\n        checks:\n          - advisories\n          - bans licenses sources\n    # Prevent sudden announcement of a new advisory from failing Ci.\n    continue-on-error: ${{ matrix.checks == 'advisories' }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - run: git config --global --add safe.directory \"$PWD\" # actions/runner#2033\n      - run: cargo deny --all-features check ${{ matrix.checks }}\n\n  fmt:\n    timeout-minutes: 5\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    container: ghcr.io/linkerd/dev:v48-rust\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - run: git config --global --add safe.directory \"$PWD\" # actions/runner#2033\n      - run: just rs-check-fmt\n\n  clippy:\n    timeout-minutes: 20\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    container: ghcr.io/linkerd/dev:v48-rust\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - run: git config --global --add safe.directory \"$PWD\" # actions/runner#2033\n      - run: just rs-fetch\n      - run: just rs-clippy\n      - run: just rs-doc --no-deps\n\n  check:\n    timeout-minutes: 20\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    container: ghcr.io/linkerd/dev:v48-rust\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - run: git config --global --add safe.directory \"$PWD\" # actions/runner#2033\n      - run: just rs-fetch\n      - run: just rs-check-dirs\n\n  test:\n    name: test\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    timeout-minutes: 15\n    container: ghcr.io/linkerd/dev:v48-rust\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - run: git config --global --add safe.directory \"$PWD\" # actions/runner#2033\n      - run: just rs-fetch\n      - run: just rs-test-build\n      - run: just rs-test\n\n  rust-toolchain:\n    name: rust toolchain\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    timeout-minutes: 2\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - shell: bash\n        run: |\n          toolchain_version=\"$(./bin/rust-toolchain-version)\"\n\n          ex=0\n          # Check this workflow against the version in rust-toolchain.\n          versions=$(sed -nE 's|.*docker://(.*/)?rust:([^ #]+).*|\\2|p' .github/workflows/*)\n          for mismatch in $(echo \"$versions\" | grep -vF \"$toolchain_version\" || true) ; do\n            echo \"::error file=.github/workflows/rust.yml::Workflow uses incorrect rust version(s): $mismatch (expected '$toolchain_version)')\"\n            ex=$((ex + 1))\n          done\n\n          exit $ex\n"
  },
  {
    "path": ".github/workflows/shell.yml",
    "content": "name: Shell\n\non:\n  pull_request:\n    paths:\n      - .github/workflows/shell.yml\n      - \"**/*.sh\"\n\npermissions:\n  contents: read\n\njobs:\n  # For more information on shellcheck failures:\n  # https://github.com/koalaman/shellcheck/wiki/Checks\n  shellcheck:\n    timeout-minutes: 10\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    steps:\n      - uses: linkerd/dev/actions/setup-tools@v48\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n      - run: just sh-lint\n"
  },
  {
    "path": ".github/workflows/sync-proxy.yml",
    "content": "name: Sync proxy\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: \"The version of the proxy to sync\"\n        required: true\n        default: \"latest\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.head_ref }}\n  cancel-in-progress: true\n\njobs:\n  meta:\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    timeout-minutes: 5\n    env:\n      GH_REPO: ${{ vars.LINKERD2_PROXY_REPO || 'linkerd/linkerd2-proxy' }}\n      GH_TOKEN: ${{ secrets.LINKERD2_PROXY_GITHUB_TOKEN || github.token }}\n    steps:\n      - if: inputs.version == 'latest' || inputs.version == ''\n        id: latest\n        run: gh release view --json tagName,name,url | jq -r 'to_entries[] | (.key + \"=\" + .value)' >> \"$GITHUB_OUTPUT\"\n      - name: steps.latest.outputs.tagName=${{ steps.latest.outputs.tagName }}\n        run: \"true\"\n      - name: steps.latest.outputs.name=${{ steps.latest.outputs.name }}\n        run: \"true\"\n      - name: steps.latest.outputs.url=${{ steps.latest.outputs.url }}\n        run: \"true\"\n\n      - if: inputs.version != 'latest' && inputs.version != ''\n        id: known\n        env:\n          VERSION: ${{ inputs.version }}\n        run: gh release view \"$VERSION\" --json tagName,name,url | jq -r 'to_entries[] | (.key + \"=\" + .value)' >> \"$GITHUB_OUTPUT\"\n      - name: steps.known.outputs.tagName=${{ steps.known.outputs.tagName }}\n        run: \"true\"\n      - name: steps.known.outputs.name=${{ steps.known.outputs.name }}\n        run: \"true\"\n      - name: steps.known.outputs.url=${{ steps.known.outputs.url }}\n        run: \"true\"\n\n      - name: Verify tagName\n        if: steps.latest.outputs.tagName == '' && steps.known.outputs.tagName == ''\n        run: exit 1\n      - name: Verify name\n        if: steps.latest.outputs.name == '' && steps.known.outputs.name == ''\n        run: exit 1\n      - name: Verify url\n        if: steps.latest.outputs.url == '' && steps.known.outputs.url == ''\n        run: exit 1\n\n    outputs:\n      tagName: ${{ steps.latest.outputs.tagName || steps.known.outputs.tagName }}\n      name: ${{ steps.latest.outputs.name || steps.known.outputs.name }}\n      url: ${{ steps.latest.outputs.url || steps.known.outputs.url }}\n\n  changed:\n    needs: meta\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    timeout-minutes: 5\n    env:\n      VERSION: ${{ needs.meta.outputs.name }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          token: ${{ secrets.LINKERD2_PROXY_GITHUB_TOKEN || github.token }}\n      - name: Check if proxy version has changed\n        id: changed\n        run: |\n          if [ \"$(cat .proxy-version)\" != \"$VERSION\" ]; then\n            echo changed=true >> \"$GITHUB_OUTPUT\"\n          fi\n      - name: steps.changed.outputs.changed=${{ steps.changed.outputs.changed == 'true' }}\n        run: \"true\"\n    outputs:\n      changed: ${{ steps.changed.outputs.changed == 'true' }}\n\n  sync-proxy:\n    needs: [meta, changed]\n    if: needs.changed.outputs.changed == 'true'\n    runs-on: ${{ vars.LINKERD2_RUNNER || 'ubuntu-24.04' }}\n    timeout-minutes: 5\n    permissions:\n      checks: read\n      contents: write\n      pull-requests: write\n    env:\n      VERSION: ${{ needs.meta.outputs.name }}\n      URL: ${{ needs.meta.outputs.url }}\n    steps:\n      - name: Configure git\n        env:\n          GITHUB_USERNAME: ${{ vars.LINKERD2_PROXY_GITHUB_USERNAME || 'github-actions[bot]' }}\n        run: |\n          git config --global --add safe.directory \"$PWD\" # actions/runner#2033\n          git config --global user.name \"$GITHUB_USERNAME\"\n          git config --global user.email \"$GITHUB_USERNAME\"@users.noreply.github.com\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          token: ${{ secrets.LINKERD2_PROXY_GITHUB_TOKEN || github.token }}\n      - name: Commit proxy version\n        run: |\n          set -eu\n          git fetch origin bot/sync-proxy/\"$VERSION\" || true\n          git switch -c bot/sync-proxy/\"$VERSION\"\n          echo \"$VERSION\" > .proxy-version\n          git add .proxy-version\n          (\n            echo \"proxy: $VERSION\"\n            echo\n            echo \"Release notes: $URL\"\n          ) >\"$RUNNER_TEMP\"/commit.txt\n          git commit --signoff -F \"$RUNNER_TEMP\"/commit.txt\n          git push origin bot/sync-proxy/\"$VERSION\"\n      - env:\n          GH_REPO: ${{ github.repository }}\n          GH_TOKEN: ${{ secrets.LINKERD2_PROXY_GITHUB_TOKEN || github.token }}\n        run: |\n          gh pr create --title \"proxy: $VERSION\" --body \"Release notes: $URL\" --label bot/sync-proxy\n"
  },
  {
    "path": ".gitignore",
    "content": "**/disco\ntarget\ntmp.discovery\n**/.idea\n**/cmake-*\n**/CMakeLists.txt\n*.iml\n**/node_modules\nweb/web\nweb/app/dist\nweb/app/js/locales/_build\nweb/app/js/locales/**/*.js\nweb/app/yarn-error.log\nvendor\n**/*.swp\n**/charts/**/charts\npackage-lock.json\n.vscode\n**/coverage*\n"
  },
  {
    "path": ".golangci.yml",
    "content": "# This file specifies which linters golangci-lint should run.\n#\n# For descriptions of all available linters, run:\n# ./.golangci-lint-1.17.1 linters\n# or browse to:\n# https://github.com/golangci/golangci-lint#supported-linters\n\nrun:\n  deadline: 5m\n  exclude-dirs:\n  - controller/gen\n\nlinters:\n  enable:\n  - bodyclose\n  #TODO - copyloopvar\n  - errcheck\n  - errorlint\n  - gocritic\n  - gosec\n  - gosimple\n  - govet\n  - ineffassign\n  - misspell\n  - nakedret\n  - revive\n  - staticcheck\n  - stylecheck\n  - typecheck\n  - unconvert\n  - unparam\n  - unused\n  # TODO: enable more linters!\n  # - depguard\n  # - dupl\n  # - gochecknoglobals\n  # - gochecknoinits\n  # - gocyclo\n  # - interfacer\n  # - lll\n  # - maligned\n  # - prealloc\n\n  disable: []\n\nlinters-settings:\n  revive:\n    rules:\n    - name: package-comments\n      disabled: true\n  stylecheck:\n    checks: [\"ST1019\"]\n  errcheck:\n    exclude-functions:\n      - fmt.Fprint\n      - fmt.Fprintf\n      - fmt.Fprintln\n  gosec:\n    excludes:\n      - G115 # Potential integer overflow when converting between integer types\n\nissues:\n  exclude-use-default: false\n  exclude-rules:\n  # Ignore errors when performing the following file operations. If these are\n  # not handled separately already, they tend to be insignificant.\n  - linters:\n    - errcheck\n    text: Error return value of `.*\\.(Copy|Flush|Write|WriteTo)` is not checked\n\n  # Ignore error values when closing file or HTTP response bodies. These\n  # generally happen as cleanup and are part of defer statements.\n  - linters:\n    - errcheck\n    text: Error return value of `.*\\.Close` is not checked\n\n  # Ignore error values when closing file or HTTP response bodies. These\n  # generally happen as cleanup and are part of defer statements.\n  - linters:\n    - gosec\n    text: Deferring unsafe method \"Close\" on type\n\n  # Ignore error checks for CLI output.\n  - linters:\n    - errcheck\n    text: Error return value of `(plugin|spin|termbox)\\.(Clear|Color|Flush|Run)` is not checked\n\n  # The errcheck linter catches these instances and we exclude them with the\n  # rule above; therefore we'll ignore redundant warnings through gosec.\n  - linters:\n    - gosec\n    text: \"G104: Errors unhandled\"\n\n  # This gives false negatives if a variable name is too close to the pattern\n  # used to determine if a variable is a credential.\n  - linters:\n    - gosec\n    text: \"G101: Potential hardcoded credentials\"\n\n  # Temporarily disable this check until the next golang-ci upgrade (greater\n  # than v1.50.1) which upgrades gosec from v2.13.1 to v2.14.0. The fix is in\n  # this commit, that refers to G404 but it seems it also affects G402:\n  # https://github.com/securego/gosec/commit/dfde579243e1bfe0856ddafc5fc6aebb29c0edf6\n  - linters:\n    - gosec\n    text: \"G402: TLS MinVersion too low\"\n\n  # Flag operations are fallible if the flag does not exist. We assume these\n  # exist as they are generally flags we are deprecating or use only for\n  # development.\n  - linters:\n    - errcheck\n    text: Error return value of `(.*)\\.(MarkDeprecated|MarkHidden|Set)` is not checked\n\n  # Flag completion is not critical to the CLI and errors are ignored if\n  # registration fails.\n  - linters:\n    - errcheck\n    text: Error return value of `.*\\.RegisterFlagCompletionFunc` is not checked\n\n  # Errors that occur when gracefully shutting down control plane components\n  # are insignificant.\n  - linters:\n    - errcheck\n    text: Error return value of `(adminServer|apiServer|server)\\.Shutdown` is not checked\n\n  # Append should be able to assign to a different var/slice.\n  - linters:\n    - gocritic\n    text: \"appendAssign: append result not assigned to the same slice\"\n\n  # This does not always result in more readable code.\n  - linters:\n    - gocritic\n    text: \"singleCaseSwitch: should rewrite switch statement to if statement\"\n\n  # This does not always result in more readable code.\n  - linters:\n    - gocritic\n    text: \"ifElseChain: rewrite if-else to switch statement\"\n\n  # Test/fuzzing do not need to be tested for security issues.\n  - linters:\n    - gosec\n    path: .*(test|fuzzer).*\\.go\n\n  # In tests/fuzzing we are usually mocking components or have a good idea\n  # about the errors that we expect. For this reason, we ignore unchecked\n  # errors in all test files.\n  - path: .*(test|fuzzer).*\\.go\n    text: Error return value of `.*` is not checked\n\n  # In tests we'll ignore unchecked filename operations because the values\n  # are not dynamic.\n  - path: (.*test.*\\.go|fake)\n    text: \"G304: Potential file inclusion via variable\"\n\n  # This ignores the errors returned from AddToScheme operations.\n  - path: pkg/k8s/fake.go\n    text: Error return value is not checked\n\n  # Ignore NewSimpleClientset, Endpoints, EndpointSubset, and NewGone\n  # deprecation warnings for now.\n  - linters:\n    - staticcheck\n    text: \"corev1.Endpoints is deprecated: This API is deprecated in v1.33+\"\n  - linters:\n    - staticcheck\n    text: \"corev1.EndpointSubset is deprecated: This API is deprecated in v1.33+\"\n  - linters:\n    - staticcheck\n    text: \"fake.NewSimpleClientset is deprecated\"\n  - linters:\n    - staticcheck\n    text: \"k8sError.NewGone is deprecated\"\n"
  },
  {
    "path": ".helmdocsignore",
    "content": "# Add potential chart ignores here\n"
  },
  {
    "path": ".markdownlint.yaml",
    "content": "default: true\nline_length: \n  code_blocks: false\n  tables: false\nheadings:\n  siblings_only: true\n"
  },
  {
    "path": ".proxy-version",
    "content": "v2.344.0\n"
  },
  {
    "path": "ADOPTERS.md",
    "content": "# Linkerd 2.x adopters\n\n- [Ada](http://www.ada.cx)\n- [Adidas](https://www.adidas-group.com)\n- [Advance Latam](http://www.advlatam.com)\n- [Allotrac](https://allotrac.com.au)\n- [AlphaSights](https://www.alphasights.com)\n- [Altinn](https://www.altinn.no/en/)\n- [ANNA Money](https://anna.money)\n- [Apester](https://apester.com)\n- [AppddictionStudio](https://appddicitonstudio.com)\n- [Applause](https://www.applause.com)\n- [AT&T](<https://il.att.com/>)\n- [Attest](https://www.askattest.com)\n- [Bede Gaming](https://bedegaming.com)\n- [Bink](https://bink.com)\n- [Bitfactory](https://www.bitfactory.nl/)\n- [Boomin](https://www.boomin.com)\n- [Bosch Thermotecnology](https://www.bosch-thermotechnology.com)\n- [Buoyant](https://buoyant.io)\n- [Cabify](https://cabify.com)\n- [Candide](https://candidegardening.com)\n- [Celonis](https://celonis.com)\n- [CloverHealth](https://www.cloverhealth.com/)\n- [Cohere](https://cohere.ai/)\n- [Commonbond](https://www.commonbond.co/)\n- [Crayon](https://crayon.com)\n- [Docker](https://docker.com)\n- [Doji](https://www.doji.cn/)\n- [Dukaan](https://mydukaan.io/)\n- [Expedia](https://www.expedia.com)\n- [Facelift](https://www.facelift-bbt.com/en)\n- [Favrit](https://www.favrit.com)\n- [Figure](https://www.figure.com)\n- [finleap connect](https://connect.finleap.com/)\n- [Giant Swarm](https://www.giantswarm.io)\n- [Gotin](https://www.gotin.online)\n- [HomeChoice](https://www.homechoice.co.za/)\n- [HP Inc](https://www8.hp.com/us/en/home.html)\n- [In Loco](https://inloco.com.br/en/)\n- [Info Support](https://infosupport.com/)\n- [Ingrid](https://ingrid.com/)\n- [Inscripta](https://inscripta.io)\n- [Jimdo](https://www.jimdo.com/)\n- [Just Football](https://justfootball.io)\n- [K3 Business Technologies](https://www.k3btg.com)\n- [Kairos](https://kairos.com)\n- [Kurio](https://kurio.id)\n- [LeadIQ](https://leadiq.com)\n- [LeanNet](https://leannet.eu/)\n- [Lendico](https://www.lendico.de/)\n- [Livspace](https://www.livspace.com/)\n- [Lumoa](https://www.lumoa.me/)\n- [M1 Finance](https://www.m1finance.com/)\n- [manager.cl](https://www.manager.cl/)\n- [MattePaint](https://www.mattepaint.com/)\n- [Mentum](https://mentumqr.com/)\n- [MercedesBenz.io](https://www.mercedes-benz.io/)\n- [Microsoft](https://www.microsoft.com/)\n- [Mulligan Funding](https://www.mulliganfunding.com/)\n- [Mythical Games](https://mythicalgames.com/)\n- [National Information Solutions Cooperative (NISC)](https://nisc.coop/)\n- [NAV](https://nav.no/)\n- [Nordstrom](https://nordstrom.com/)\n- [Novolabs](https://novolabs.com)\n- [OLX Brasil](https://www.olx.com.br)\n- [p3r](https://www.p3r.one/)\n- [Pangea](https://pangea.cloud)\n- [Parklab](https://parklab.app/)\n- [Paybase](https://paybase.io/)\n- [Pento](https://pento.io/)\n- [Personio](https://www.personio.com/)\n- [PITS Global Data Recovery Services](https://www.pitsdatarecovery.net/)\n- [PlayStudios Asia](https://www.playstudios.asia)\n- [PlexTrac](https://plextrac.com)\n- [PriceKinetics (GVC Australia)](https://www.pricekinetics.com.au/)\n- [Projector](https://projector.com)\n- [Purdue University Global](https://www.purdueglobal.edu/)\n- [reDock](https://www.redock.com/)\n- [ReliMail](https://relimail.com/)\n- [S&P Global Platts](https://www.spglobal.com/platts/en)\n- [Salt Security](https://salt.security/)\n- [Samarkand Global](https://www.samarkand.global/)\n- [SCA](https://sca.com.au)\n- [Search365](https://search365.ai/)\n- [Skit](https://skit.ai/)\n- [Sophotech](https://sopho.tech)\n- [Split](https://www.split.io/)\n- [StackPulse](https://stackpulse.com)\n- [Studyo](https://studyo.co)\n- [Sue](https://sue.nl)\n- [The Zebra](https://www.thezebra.com)\n- [Timescale](https://www.timescale.com)\n- [Tradeshift](https://tradeshift.com/)\n- [Transit](https://transit.app)\n- [True Tickets](https://true-tickets.com)\n- [Web Summit](https://websummit.com)\n- [Winuall](https://www.winuall.com)\n- [Wiz Security](https://www.wiz.io/)\n- [xCloud](https://www.xbox.com/en-US/xbox-game-streaming/project-xcloud)\n- [YouMail](https://www.youmail.com)\n- [ZeroFlucs](https://zeroflucs.io/)\n- [Zimpler](https://www.zimpler.com/)\n\nIf you're using Linkerd 2.x and aren't on this list, please [submit a pull\nrequest](https://github.com/linkerd/linkerd2/edit/main/ADOPTERS.md)!\n"
  },
  {
    "path": "AMBASSADORS.md",
    "content": "# Linkerd Ambassadors\n\nThe Linkerd Ambassador badge is a distinction awarded to those community\nmembers who are experts in their field and who demonstrate expertise, passion,\nengagement, and a commitment to sharing their Linkerd experience with the\nbroader community. Linkerd Ambassadors are hand-picked by the Linkerd\nmaintainers.\n\nInterested in becoming a Linkerd Ambassador? Learn more at\n<https://linkerd.io/community/ambassadors/>.\n\n## Current Ambassadors\n\n- 🇺🇸 Chris Campbell, @campbel\n- 🇩🇪 Christian Hüning, @christianhuening\n- 🇭🇺 Dominik Táskai, @dtaskai\n- 🇮🇱 Eli Goldberg, @Eli-Goldberg\n- 🇬🇧 Mahendran Selvakumar, @skmahe1077\n- 🇬🇹 Sergio Méndez, @sergioarmgpl\n- 🇳🇱 William Rizzo, @wrkode\n\n## Ambassadors Emeriti\n\n- 🇺🇸 Charles Pretzer, @cpretzer\n- 🇳🇴 Fredrik Klingenberg, @fredrkl\n- 🇩🇰 Kasper Nissen, @kaspernissen\n- 🇵🇹 María Teresa Rojas, @mtrojas\n- 🇦🇺 Steve Gray\n"
  },
  {
    "path": "BUILD.md",
    "content": "<!-- markdownlint-disable-file code-block-style -->\n# Linkerd2 Development Guide\n\n:balloon: Welcome to the Linkerd2 development guide! :wave:\n\nThis document will help you build and run Linkerd2 from source. More information\nabout testing from source can be found in the [TEST.md](TEST.md) guide.\n\n## Table of contents\n\n- [Repo Layout](#repo-layout)\n  - [Control Plane (Go/React)](#control-plane-goreact)\n  - [Data Plane (Rust)](#data-plane-rust)\n- [Components](#components)\n- [Development configurations](#development-configurations)\n  - [Comprehensive](#comprehensive)\n    - [Deploying Control Plane components with Tracing](#deploying-control-plane-components-with-tracing)\n  - [Publishing Images](#publishing-images)\n  - [Go](#go)\n    - [A note about Go run](#a-note-about-go-run)\n    - [Lint](#lint)\n    - [Formatting](#formatting)\n    - [Building the CLI for development](#building-the-cli-for-development)\n    - [Running the control plane for development](#running-the-control-plane-for-development)\n    - [Running the Tap APIService for development](#debugging-the-tap-apiservice-for-development)\n    - [Generating CLI docs](#generating-cli-docs)\n  - [Web](#web)\n    - [First time setup](#first-time-setup)\n    - [Run web standalone](#run-web-standalone)\n    - [Webpack dev server](#webpack-dev-server)\n    - [JavaScript dependencies](#javascript-dependencies)\n    - [Translations](#translations)\n  - [Rust](#rust)\n    - [Docker](#docker)\n- [Dependencies](#dependencies)\n  - [Updating protobuf dependencies](#updating-protobuf-dependencies)\n  - [Updating ServiceProfile generated\n    code](#updating-serviceprofile-generated-code)\n- [Linkerd Helm Chart](#linkerd-helm-chart)\n  - [Extensions Helm charts](#extensions-helm-charts)\n  - [Making changes to the chart templates](#making-changes-to-the-chart-templates)\n  - [Annotating values.yaml](#annotating-valuesyaml)\n  - [Markdown templates](#markdown-templates)\n\n## Repo layout\n\nLinkerd2 is primarily written in Rust, Go, and React. At its core is a\nhigh-performance data plane written in Rust. The control plane components and\nits extensions are written in Go. The dashboard UI is a React application.\n\n### Control Plane (Go/React)\n\n- [`cli`](cli): Command-line `linkerd` utility, view and drive the control\n  plane.\n- [`controller`](controller)\n  - [`destination`](controller/api/destination): Accepts requests from `proxy`\n    instances and serves service discovery information.\n  - [`proxy-injector`](controller/proxy-injector): Mutating webhook triggered by\n    pods creation, that injects the proxy container as a sidecar.\n  - [`identity`](controller/identity): Provides a CA to distribute certificates\n    to proxies for them to establish mTLS connections between them.\n- [`viz extension`](viz)\n  - [`metrics-api`](viz/metrics-api): Accepts requests from API clients such as\n    cli and web, serving metrics from the proxies in the cluster through\n    Prometheus queries.\n  - [`tap`](viz/tap/api): Provides a live pipeline of requests.\n  - [`tap-injector`](viz/tap/injector): Mutating webhook triggered by pods\n    creation, that injects metadata into the proxy container in order to enable\n    tap.\n  - [`web`](web): Provides a UI dashboard to view and drive the control plane.\n- [`multicluster extension`](multicluster)\n  - [`linkerd-gateway`]: Accepts requests from other clusters and forwards them\n    to the appropriate destination in the local cluster.\n  - [`linkerd-service-mirror-xxx`](multicluster/service-mirror): Controller\n    observing the labeling of exported services in the target cluster, each one\n    for which it will create a mirrored service in the local cluster.\n\n### Data Plane (Rust)\n\n- [`linkerd2-proxy`](https://github.com/linkerd/linkerd2-proxy): Rust source\n  code for the proxy lives in the linkerd2-proxy repo.\n- [`linkerd2-proxy-api`](https://github.com/linkerd/linkerd2-proxy-api):\n  Protobuf definitions for the data plane APIs live in the linkerd2-proxy-api\n  repo.\n\n## Components\n\n![Linkerd2 Components](https://g.gravizo.com/source/svg/linkerd2_components?https%3A%2F%2Fraw.githubusercontent.com%2Flinkerd%2Flinkerd2%2Fmain%2FBUILD.md)\n\n<!-- markdownlint-disable no-inline-html -->\n<details>\n<summary></summary>\nlinkerd2_components\n  digraph G {\n    rankdir=LR;\n\n    node [style=filled, shape=rect];\n\n    \"cli\" [color=lightblue];\n    \"destination\" [color=lightblue];\n    \"identity\" [color=lightblue];\n    \"metrics-api\" [color=lightblue];\n    \"tap\" [color=lightblue];\n    \"web\" [color=lightblue];\n\n    \"proxy\" [color=orange];\n\n    \"cli\" -> \"metrics-api\";\n    \"cli\" -> \"tap\";\n\n    \"web\" -> \"metrics-api\";\n    \"web\" -> \"tap\";\n    \"web\" -> \"grafana\";\n\n    \"metrics-api\" -> \"prometheus\";\n\n    \"tap\" -> \"proxy\";\n\n    \"proxy\" -> \"destination\";\n    \"proxy\" -> \"identity\";\n\n    \"identity\" -> \"kubernetes api\"\n\n    \"destination\" -> \"kubernetes api\";\n\n    \"grafana\" -> \"prometheus\";\n    \"prometheus\" -> \"proxy\";\n  }\nlinkerd2_components\n</details>\n<!-- markdownlint-enable no-inline-html -->\n\n## Development configurations\n\nDepending on use case, there are several configurations with which to develop\nand run Linkerd2:\n\n- [Comprehensive](#comprehensive): Integrated configuration using k3d, most\n  closely matches release.\n- [Web](#web): Development of the Linkerd2 Dashboard.\n\n### Comprehensive\n\nThis configuration builds all Linkerd2 components in Docker images, and deploys\nthem onto a k3d cluster. This setup most closely parallels our recommended\nproduction installation, documented in [Getting\nStarted](https://linkerd.io/2/getting-started/).\n\nNote that you need to have first installed docker buildx, as explained\n[buildx docs](https://github.com/docker/buildx).\n\n```bash\n# create the k3d cluster\nbin/k3d cluster create\n\n# build all docker images\nbin/docker-build\n\n# load all the images into k3d\nbin/image-load --k3d\n\n# install linkerd\nbin/linkerd install --crds | kubectl apply -f -\nbin/linkerd install | kubectl apply -f -\n\n# wait for the core components to be ready, then install linkerd-viz\nbin/linkerd viz install | kubectl apply -f -\n\n# in order to use `linkerd viz tap` against control plane components, you need\n# to restart them (so that the tap-injector enables tap on their proxies)\nkubectl -n linkerd rollout restart deploy\n\n# verify cli and server versions\nbin/linkerd version\n\n# validate installation\nbin/linkerd check --expected-version $(bin/root-tag)\n\n# view linkerd dashboard\nbin/linkerd viz dashboard\n\n# install the demo app\ncurl https://run.linkerd.io/emojivoto.yml | bin/linkerd inject - | kubectl apply -f -\n\n# port-forward the demo app's frontend to see it at http://localhost:8080\nkubectl -n emojivoto port-forward svc/web-svc 8080:80\n\n# view details per deployment\nbin/linkerd viz -n emojivoto stat deployments\n\n# view a live pipeline of requests\nbin/linkerd viz -n emojivoto tap deploy voting\n```\n\n#### Deploying Control Plane components with Tracing\n\nControl Plane components have the `trace-collector` flag used to enable\n[Distributed Tracing](https://opentracing.io/docs/overview/what-is-tracing/)\nfor development purposes. It can be enabled globally i.e Control plane\ncomponents and their proxies by using the\n`--set controller.tracing.enable=true` installation flag.\n\nThis will configure all the components to send the traces to the collector you\nhave configured for your cluster.\n\n```bash\n\n# install Linkerd with tracing\nlinkerd install \\\n  --set controller.tracing.enable=true \\\n  --set controller.tracing.collector.endpoint=<your trace collector endpoint> \\\n  | kubectl apply -f -\n```\n\n### Publishing images\n\nThe example above builds and loads the docker images into k3d. For testing your\nbuilt images outside your local environment, you need to publish your images so\nthey become accessible in those external environments.\n\nTo signal `bin/docker-build` or any of the more specific scripts\n`bin/docker-build-*` what registry to use, just set the environment variable\n`DOCKER_REGISTRY` (which defaults to the official registry `cr.l5d.io/linkerd`).\nAfter having pushed those images through the usual means (`docker push`) you'll\nhave to pass the `--registry` flag to `linkerd install` with a value  matching\nyour registry. Extensions don't have that flag and instead you need to use the\nequivalent Helm value; e.g. for Viz `linkerd viz install --set\ndefaultRegistry=...`.\n\n### Go\n\n#### A note about Go run\n\nOur instructions use a [`bin/go-run`](bin/go-run) script in lieu `go run`. This\nis a convenience script that leverages caching via `go build` to make your\nbuild/run/debug loop faster.\n\nIn general, replace commands like this:\n\n```bash\ngo run cli/main.go check\n```\n\nwith this:\n\n```bash\nbin/go-run cli check\n```\n\nThat is equivalent to running `linkerd check` using the code on your branch.\n\n#### Lint\n\nTo analyze and lint the Go code using golangci-lint, run:\n\n```bash\ngolangci-lint run\n```\n\n#### Formatting\n\nAll Go source code is formatted with `goimports`. The version of `goimports`\nused by this project is specified in `go.mod`. To ensure you have the same\nversion installed, run `go install -mod=readonly\ngolang.org/x/tools/cmd/goimports`. It's recommended that you set your IDE or\nother development tools to use `goimports`. Formatting is checked during CI by\nthe `bin/fmt` script.\n\n#### Building the CLI for development\n\nThe script for building the CLI binaries using docker is\n`bin/docker-build-cli-bin`. This will also be called indirectly when calling\n`bin/docker-build`. By default it creates binaries for your current host's\nOS/arch.\n\nTo cross-build targeting a different OS or architecture, set the environment\nvariable `DOCKER_TARGET` according to any of the final stages available in\n[cli/Dockerfile](cli/Dockerfile).\n\nFor local development and a faster edit-build-test cycle you can build directly\nwithout going through a docker container by calling `bin/build-cli-bin`.\n\nIf you set the environment variable `LINKERD_LOCAL_BUILD_CLI=1` then\n`bin/docker-build` will use this last method for the step that builds the CLI.\n\n#### Running the control plane for development\n\nLinkerd2's control plane is composed of several Go microservices. You can run\nthese components in a Kubernetes cluster, or even locally.\n\nTo run an individual component locally, you can use the `go-run` command, and\npass in valid Kubernetes credentials via the `-kubeconfig` flag. For instance,\nto run the destination service locally, run:\n\n```bash\nbin/go-run controller/cmd destination -kubeconfig ~/.kube/config -log-level debug\n```\n\nYou can send test requests to the destination service using the\n`destination-client` in the `controller/script` directory. For instance:\n\n```bash\nbin/go-run controller/script/destination-client -path hello.default.svc.cluster.local:80\n```\n\n##### Debugging the Tap APIService for development\n\nThe Tap APIService is a Kubernetes extension API server, so it can be\nchallenging to run outside the cluster. The most straightforward workflow is to\nsimply test changes by building and loading the container image as explained in\nthe [comprehensive configuration](#comprehensive) section above (in order to\njust build this component use `bin/docker-build-tap`).\n\n#### Generating CLI docs\n\nThe [documentation](https://linkerd.io/2/cli/) for the CLI tool is partially\ngenerated from YAML. This can be generated by running the `linkerd doc` command.\n\n### Web\n\nThis is a React app fronting a Go process. It uses webpack to bundle assets, and\npostcss to transform css.\n\nThese commands assume working [Go](https://golang.org) and\n[Yarn](https://yarnpkg.com) environments.\n\n#### First time setup\n\n1. Install [Yarn](https://yarnpkg.com) and use it to install JS dependencies:\n\n    ```bash\n    brew install yarn\n    bin/web setup\n    ```\n\n2. Install Linkerd on a Kubernetes cluster.\n\n#### Run web standalone\n\n```bash\nbin/web run\n```\n\nThe web server will be running on `localhost:7777`.\n\n#### Webpack dev server\n\nTo develop with a webpack dev server:\n\n1. Start the development server.\n\n    ```bash\n    bin/web dev\n    ```\n\n    Note: this will start up:\n\n    - `web` on :7777. This is the golang process that serves the dashboard.\n    - `webpack-dev-server` on :8080 to manage rebuilding/reloading of the\n      javascript.\n    - `metrics-api` is port-forwarded from the Kubernetes cluster via `kubectl`\n      on :8085\n\n2. Go to [http://localhost:7777](http://localhost:7777) to see everything\n   running.\n\n#### JavaScript dependencies\n\nTo add a JS dependency:\n\n```bash\ncd web/app\nyarn add [dep]\n```\n\n#### Translations\n\nTo add a locale:\n\n```bash\ncd web/app\nyarn lingui add-locale [locales...] # will create a messages.json file for new locale(s)\n```\n\nTo extract message keys from existing components:\n\n```bash\ncd web/app\nyarn lingui extract\n...\nyarn lingui compile # done automatically in bin/web run\n```\n\nFinally, make sure the new locale is also referred in the following places:\n\n- Under the `lingui` section in `package.json`\n- In the `make-plural/plurals` import in `index.js`\n- In the `langOptions` object in `index.js`\n\n### Rust\n\nAll Rust development happens in the\n[`linkerd2-proxy`](https://github.com/linkerd/linkerd2-proxy) repo.\n\n#### Docker\n\nThe `bin/docker-build-proxy` script builds the proxy by pulling a pre-published\nproxy binary:\n\n```bash\nbin/docker-build-proxy\n```\n\n#### Locally built proxy\n\nIf you want to deploy a locally built proxy, you can build it in the\n[`linkerd2-proxy`](https://github.com/linkerd/linkerd2-proxy) repo by running:\n\n```bash\nDOCKER_TAG=cr.l5d.io/linkerd/proxy:dev make docker\n```\n\nThen, in this repo, run:\n\n```bash\n./bin/k3d image import cr.l5d.io/linkerd/proxy:dev\n```\n\nNow, to make a pod use your image, add the following annotations to it:\n\n```yaml\nconfig.linkerd.io/proxy-version: dev\n```\n\n## Dependencies\n\n### Updating protobuf dependencies\n\n If you make Protobuf changes, run:\n\n ```bash\nbin/protoc-go.sh\n```\n\n### Updating ServiceProfile generated code\n\nThe [ServiceProfile client code](./controller/gen/client) is generated by\n[`bin/update-codegen.sh`](bin/update-codegen.sh), which depends on [K8s\ncode-generator](https://github.com/kubernetes/code-generator), which does not\nyet support Go Modules. To re-generate this code, check out this repo into your\n`GOPATH`:\n\n```bash\ngo get -u github.com/linkerd/linkerd2\ncd $GOPATH/src/github.com/linkerd/linkerd2\nbin/update-codegen.sh\n```\n\n## Linkerd Helm chart\n\nThe Linkerd control plane chart is located in the\n[`charts/linkerd2`](charts/linkerd2) folder. The [`charts/patch`](charts/patch)\nchart consists of the Linkerd proxy specification, which is used by the proxy\ninjector to inject the proxy container. Both charts depend on the partials\nsubchart which can be found in the [`charts/partials`](charts/partials) folder.\n\nNote that the `charts/linkerd2/values.yaml` file contains a placeholder\n`linkerdVersionValue` that you need to replace with an appropriate string (like\n`edge-20.2.2`) before proceeding.\n\nDuring development, please use the [`bin/helm`](bin/helm) wrapper script to\ninvoke the Helm commands. For example,\n\n```bash\nbin/helm install linkerd2 charts/linkerd2\n```\n\nThis ensures that you use the same Helm version as that of the Linkerd CI\nsystem.\n\nFor general instructions on how to install the charts check out the\n[docs](https://linkerd.io/2/tasks/install-helm/). You also need to supply or\ngenerate your own certificates to use the chart, as explained in the\n[Generate Certificates task](https://linkerd.io/2/tasks/generate-certificates/).\n\n### Extensions Helm charts\n\nExtensions provide each their own chart:\n\n- Viz: [`viz/charts/linkerd-viz`](viz/charts/linkerd-viz)\n- Multicluster:\n  [`multicluster/charts/linkerd-multicluster`](multicluster/charts/linkerd-multicluster)\n\n### Making changes to the chart templates\n\nWhenever you make changes to the files under\n[`charts/linkerd2/templates`](charts/linkerd2/templates) or its dependency\n[`charts/partials`](charts/partials), make sure to run\n[`bin/helm-build`](bin/helm-build) which will refresh the dependencies and lint\nthe templates.\n\n### Annotating values.yaml\n\nTo allow helm-docs to properly document the values in `values.yaml` a descriptive\ncomment is required. This can be done in two ways.\nEither comment the value directly above with\n`# -- This is a really nice value` where the double dashes automatically\nannotates the value. Another explicit usage is to type out the value name.\n`# global.MyNiceValue -- I really like this value`\n\n### Markdown templates\n\nIn order to accommodate for extra data that might not have a proper place in the\n´values.yaml´ file the corresponding ´README.md.gotmpl´ can be modified for each\nchart. This template allows the standard markdown syntax as well as the go\ntemplating functions. Checkout\n[helm-docs](https://github.com/norwoodj/helm-docs) for more info.\n"
  },
  {
    "path": "CHANGES.md",
    "content": "<!-- markdownlint-disable-file MD059 -->\n# Changes\n\nPlease visit Linkerd's [Release page][gh-releases] for for the latest release\nnotes moving forward!\n\n[gh-releases]: https://github.com/linkerd/linkerd2/releases\n\n## edge-24.2.5\n\n* Migrated edge release change notes to use GitHub's automated release notes\n  feature.\n\n## edge-24.2.4\n\n* Updated the ExternalWorkload CRD to v1beta1, renaming the meshTls field to\n  meshTLS ([#12098])\n* Updated the proxy to address some logging and metrics inconsistencies\n  ([#12099])\n\n[#12098]: https://github.com/linkerd/linkerd2/pull/12098\n[#12099]: https://github.com/linkerd/linkerd2/pull/12099\n\n## edge-24.2.3\n\n* Allowed the `MutatingWebhookConfig` timeout value to be configured ([#12028])\n  (thanks @mikebell90)\n* Added a counter for items dropped from destination controller workqueue\n  ([#12079])\n* Fixed a spurious `linkerd check` error when using container images with\n  digests ([#12059])\n* Fixed an issue where inbound policy could be incorrect after certain policy\n  resources are deleted ([#12088])\n\n[#12028]: https://github.com/linkerd/linkerd2/pull/12028\n[#12079]: https://github.com/linkerd/linkerd2/pull/12079\n[#12059]: https://github.com/linkerd/linkerd2/pull/12059\n[#12088]: https://github.com/linkerd/linkerd2/pull/12088\n\n## edge-24.2.2\n\nThis release addresses some issues in the destination service that could cause\nit to behave unexpectedly when processing updates.\n\n* Fixed a race condition in the destination service that could cause panics\n  under very specific conditions ([#12022]; fixes [#12010])\n* Changed how updates to a `Server` selector are handled in the destination\n  service. When a `Server` that marks a port as opaque no longer selects a\n  resource, the resource's opaqueness will reverted to default settings\n  ([#12031]; fixes [#11995])\n* Introduced Helm configuration values for liveness and readiness probe\n  timeouts and delays ([#11458]; fixes [#11453]) (thanks @jan-kantert!)\n\n[#12010]: https://github.com/linkerd/linkerd2/issues/12010\n[#12022]: https://github.com/linkerd/linkerd2/pull/12022\n[#11995]: https://github.com/linkerd/linkerd2/issues/11995\n[#12031]: https://github.com/linkerd/linkerd2/pull/12031\n[#11453]: https://github.com/linkerd/linkerd2/issues/11453\n[#11458]: https://github.com/linkerd/linkerd2/pull/11458\n\n## edge-24.2.1\n\nThis edge release contains performance and stability improvements to the\nDestination controller, and continues stabilizing support for ExternalWorkloads.\n\n* Reduced the load on the Destination controller by only processing Server\n  updates on workloads affected by the Server ([#12017])\n* Changed how the Destination controller reacts to target clusters (in\n  multicluster pod-to-pod mode) whose Server CRD is outdated: skip them and log\n  an error instead of panicking ([#12008])\n* Improved the leader election of the ExternalWorkloads Endpoints controller to\n  avoid missing events ([#12021])\n* Improved naming of EndpointSlices generated by ExternWorkloads ([#12016])\n* Restriced the number of IPs an ExternalWorkload can have ([#12026])\n\n[#12017]: https://github.com/linkerd/linkerd2/pull/12017\n[#12008]: https://github.com/linkerd/linkerd2/pull/12008\n[#12021]: https://github.com/linkerd/linkerd2/pull/12021\n[#12016]: https://github.com/linkerd/linkerd2/pull/12016\n[#12026]: https://github.com/linkerd/linkerd2/pull/12026\n\n## edge-24.1.3\n\nThis release continues support for ExternalWorkload resources throughout the\ncontrol and data planes.\n\n* Updated the proxy to use SPIRE to instrument identity outside of Kubernetes.\n* Updated the Destination controller to return `INVALID_ARGUMENT` status codes\n  properly when a `ServiceProfile` is requested for a service that does not\n  exist. (#11980)\n* An ExternalWorkload EndpointSlice controller has been added to the\n  Destination controller.\n* Added a `createNamespaceMetadataJob` Helm value to control whether the\n  namespace-metadata job is run during install (#11782)\n\n## edge-24.1.2\n\nThis edge release incrementally improves support for ExternalWorkload resources\nthroughout the control plane.\n\n## edge-24.1.1\n\nThis edge release introduces a number of different fixes and improvements. More\nnotably, it introduces a new `cni-repair-controller` binary to the CNI plugin\nimage. The controller will automatically restart pods that have not received\ntheir iptables configuration.\n\n* Removed shortnames from Tap API resources to avoid colliding with existing\n  Kubernetes resources ([#11816]; fixes [#11784])\n* Introduced a new ExternalWorkload CRD to support upcoming mesh expansion\n  feature ([#11805])\n* Changed `MeshTLSAuthentication` resource validation to allow SPIFFE URI\n  identities ([#11882])\n* Introduced a new `cni-repair-controller` to the `linkerd-cni` DaemonSet to\n  automatically restart misconfigured pods that are missing iptables rules\n  ([#11699]; fixes [#11073])\n* Fixed a `\"duplicate metrics\"` warning in the multicluster service-mirror\n  component ([#11875]; fixes [#11839])\n* Added metric labels and weights to `linkerd diagnostics endpoints` json\n  output ([#11889])\n* Changed how `Server` updates are handled in the destination service. The\n  change will ensure that during a cluster resync, consumers won't be\n  overloaded by redundant updates ([#11907])\n* Changed `linkerd install` error output to add a newline when a Kubernetes\n  client cannot be successfully initialised ([#11917])\n\n[#11816]: https://github.com/linkerd/linkerd2/pull/11816\n[#11784]: https://github.com/linkerd/linkerd2/issues/11784\n[#11805]: https://github.com/linkerd/linkerd2/pull/11805\n[#11882]: https://github.com/linkerd/linkerd2/pull/11882\n[#11699]: https://github.com/linkerd/linkerd2/pull/11699\n[#11073]: https://github.com/linkerd/linkerd2/issues/11073\n[#11875]: https://github.com/linkerd/linkerd2/pull/11875\n[#11839]: https://github.com/linkerd/linkerd2/issues/11839\n[#11889]: https://github.com/linkerd/linkerd2/pull/11889\n[#11907]: https://github.com/linkerd/linkerd2/pull/11907\n[#11917]: https://github.com/linkerd/linkerd2/pull/11917\n\n## edge-23.12.4\n\nThis edge release includes fixes and improvements to the destination\ncontroller's endpoint resolution API.\n\n* Fixed an issue in the control plane where discovery for pod IP addresses could\n  hang indefinitely ([#11815])\n* Updated the proxy to enforce time limits on control plane response streams so\n  that proxies more naturally distribute load over control plane replicas\n  ([#11837])\n* Fixed the policy's controller service metadata responses so that proxy logs\n  and metrics have informative values ([#11842])\n\n[#11842]: https://github.com/linkerd/linkerd2/pull/11842\n[#11837]: https://github.com/linkerd/linkerd2/pull/11837\n[#11815]: https://github.com/linkerd/linkerd2/pull/11815\n\n## edge-23.12.3\n\nThis edge release contains improvements to the logging and diagnostics of the\ndestination controller.\n\n* Added a control plane metric to count errors talking to the Kubernetes API\n  ([#11774])\n* Fixed an issue causing spurious destination controller error messages for\n  profile lookups on unmeshed pods with port in default opaque list ([#11550])\n\n[#11774]: https://github.com/linkerd/linkerd2/pull/11774\n[#11550]: https://github.com/linkerd/linkerd2/pull/11550\n\n## edge-23.12.2\n\nThis edge release includes a restructuring of the proxy's balancer along with\naccompanying new metrics. The new minimum supported Kubernetes version is 1.22.\n\n* Restructured the proxy's balancer ([#11750]): balancer changes may now occur\n  independently of request processing. Fail-fast circuit breaking is enforced on\n  the balancer's queue so that requests can't get stuck in a queue indefinitely.\n  This new balancer is instrumented with new metrics: request (in-queue) latency\n  histograms, failfast states, discovery updates counts, and balancer endpoint\n  pool sizes.\n* Changed how the policy controller updates HTTPRoute status so that it doesn't\n  affect statuses from other non-linkerd controllers ([#11705]; fixes [#11659])\n\n[#11750]: https://github.com/linkerd/linkerd2/pull/11750\n[#11705]: https://github.com/linkerd/linkerd2/pull/11705\n[#11659]: https://github.com/linkerd/linkerd2/pull/11659\n\n## edge-23.12.1\n\nThis edge release introduces new configuration values in the identity\ncontroller for client-go's `QPS` and `Burst` settings. Default values for these\nsettings have also been raised from `5` (QPS) and `10` (Burst) to `100` and\n`200` respectively.\n\n* Added `namespaceSelector` fields for the tap-injector and jaeger-injector\n  webhooks. The webhooks are now configured to skip `kube-system` by default\n  ([#11649]; fixes [#11647]) (thanks @mikutas!)\n* Added the ability to configure client-go's `QPS` and `Burst` settings in the\n  identity controller ([#11644])\n* Improved client-go logging visibility throughout the control plane's\n  components ([#11632])\n* Introduced `PodDisruptionBudgets` in the linkerd-viz Helm chart for tap and\n  tap-injector ([#11628]; fixes [#11248]) (thanks @mcharriere!)\n\n[#11649]: https://github.com/linkerd/linkerd2/pull/11649\n[#11647]: https://github.com/linkerd/linkerd2/issues/11647\n[#11644]: https://github.com/linkerd/linkerd2/pull/11644\n[#11632]: https://github.com/linkerd/linkerd2/pull/11632\n[#11628]: https://github.com/linkerd/linkerd2/pull/11628\n[#11248]: https://github.com/linkerd/linkerd2/issues/11248\n\n## edge-23.11.4\n\nThis edge release introduces support for the native sidecar containers entering\nbeta support in Kubernetes 1.29. This improves the startup and shutdown ordering\nfor the proxy relative to other containers, fixing the long-standing\nshutdown issue with injected `Job`s. Furthermore, traffic from other\n`initContainer`s can now be proxied by Linkerd.\n\nIn addition, this edge release includes Helm chart improvements, and improvements\nto the multicluster extension.\n\n* Added a new `config.alpha.linkerd.io/proxy-enable-native-sidecar` annotation\n  and `Proxy.NativeSidecar` Helm option that causes the proxy container to run\n  as an init-container (thanks @teejaded!) ([#11465]; fixes [#11461])\n* Fixed broken affinity rules for the multicluster `service-mirror` when running\n  in HA mode ([#11609]; fixes [#11603])\n* Added a new check to `linkerd check` that ensures all extension namespaces are\n  configured properly ([#11629]; fixes [#11509])\n* Updated the Prometheus Docker image used by the `linkerd-viz` extension to\n  v2.48.0, resolving a number of CVEs in older Prometheus versions ([#11633])\n* Added `nodeAffinity` to `deployment` templates in the `linkerd-viz` and\n  `linkerd-jaeger` Helm charts (thanks @naing2victor!) ([#11464]; fixes\n  [#10680])\n\n[#11465]: https://github.com/linkerd/linkerd2/pull/11465\n[#11461]: https://github.com/linkerd/linkerd2/issues/11461\n[#11609]: https://github.com/linkerd/linkerd2/pull/11609\n[#11603]: https://github.com/linkerd/linkerd2/issues/11603\n[#11629]: https://github.com/linkerd/linkerd2/pull/11629\n[#11509]: https://github.com/linkerd/linkerd2/issues/11509\n[#11633]: https://github.com/linkerd/linkerd2/pull/11633\n[#11464]: https://github.com/linkerd/linkerd2/pull/11464\n[#10680]: https://github.com/linkerd/linkerd2/issues/10680\n\n## edge-23.11.3\n\nThis edge release fixes a bug where Linkerd could cause EOF errors during bursts\nof TCP connections.\n\n* Fixed a bug where the `linkerd multicluster link` command's\n  `--gateway-addresses` flag was not respected when a remote gateway exists\n  ([#11564])\n* proxy: Increased DEFAULT_OUTBOUND_TCP_QUEUE_CAPACITY to prevent EOF errors\n  during bursts of TCP connections\n\n[#11564]: https://github.com/linkerd/linkerd2/pull/11564\n\n## edge-23.11.2\n\nThis edge release contains observability improvements and bug fixes to the\nDestination controller, and a refinement to the multicluster gateway resolution\nlogic.\n\n* Fixed an issue where the Destination controller could stop processing service\n  profile updates, if a proxy subscribed to those updates stops reading them;\n  this is a followup to the issue [#11491] fixed in [edge-23.10.3] ([#11546])\n* In the Destination controller, added informer lag histogram metrics to track\n  whenever the Kubernetes objects watched by the controller are falling behind\n  the state in the kube-apiserver ([#11534])\n* In the multicluster service mirror, extended the target gateway resolution\n  logic to take into account all the possible IPs a hostname might resolve to,\n  rather than just the first one (thanks @MrFreezeex!) ([#11499])\n* Added probes to the debug container to appease environments requiring probes\n  for all containers ([#11308])\n\n[edge-23.10.3]: https://github.com/linkerd/linkerd2/releases/tag/edge-23.10.3\n[#11546]: https://github.com/linkerd/linkerd2/pull/11546\n[#11534]: https://github.com/linkerd/linkerd2/pull/11534\n[#11499]: https://github.com/linkerd/linkerd2/pull/11499\n[#11308]: https://github.com/linkerd/linkerd2/pull/11308\n\n## edge-23.11.1\n\nThis edge release fixes two bugs in the Destination controller that could cause\noutbound connections to hang indefinitely.\n\n* helm: Introduce configurable values for protocol detection ([#11536])\n* destination: Fix GetProfiles error when address is opaque and unmeshed ([#11556])\n* destination: Return NotFound for unknown pod names ([#11540])\n* proxy: Log controller errors at WARN\n* proxy: Fix grpc_status metric labels for inbound traffic\n\n[#11536]: https://github.com/linkerd/linkerd2/pull/11536\n[#11556]: https://github.com/linkerd/linkerd2/pull/11556\n[#11540]: https://github.com/linkerd/linkerd2/pull/11540\n\n## edge-23.10.4\n\nThis edge release includes a fix for the `ServiceProfile` CRD resource schema.\nThe schema incorrectly required `not` response matches to be arrays, while the\nin-cluster validator parsed `not` response matches as objects. In addition, an\nissues has been fixed in `linkerd profile`. When used with the `--open-api`\nflag, it would not strip trailing slashes when generating a resource from\nswagger specifications.\n\n* Fixed an issue where trailing slashes wouldn't be stripped when generating\n  `ServiceProfile` resources through `linkerd profile --open-api` ([#11519])\n* Fixed an issue in the `ServiceProfile` CRD schema. The schema incorrectly\n  required that a `not` response match should be an array, which the service\n  profile validator rejected since it expected an object. The schema has been\n  updated to properly indicate that `not` values should be an object ([#11510];\n  fixes [#11483])\n* Improved logging in the destination controller by adding the client pod's\n  name to the logging context. This will improve visibility into the messages\n  sent and received by the control plane from a specific proxy ([#11532])\n* Fixed an issue in the destination controller where the metadata API would not\n  initialize a `Job` informer. The destination controller uses the metadata API\n  to retrieve `Job` metadata, and relies mostly on informers. Without an\n  initialized informer, an error message would be logged, and the controller\n  relied on direct API calls ([#11541]; fixes [#11531])\n\n[#11541]: https://github.com/linkerd/linkerd2/pull/11541\n[#11532]: https://github.com/linkerd/linkerd2/pull/11532\n[#11531]: https://github.com/linkerd/linkerd2/issues/11531\n[#11519]: https://github.com/linkerd/linkerd2/pull/11519\n[#11510]: https://github.com/linkerd/linkerd2/pull/11510\n[#11483]: https://github.com/linkerd/linkerd2/issues/11483\n\n## edge-23.10.3\n\nThis edge release fixes issues in the proxy and Destination controller which can\nresult in Linkerd proxies sending traffic to stale endpoints. In addition, it\ncontains other bugfixes and updates dependencies to include patches for the\nsecurity advisories [CVE-2023-44487]/GHSA-qppj-fm5r-hxr3 and GHSA-c827-hfw6-qwvm.\n\n* Fixed an issue where the Destination controller could stop processing\n  changes in the endpoints of a destination, if a proxy subscribed to that\n  destination stops reading service discovery updates. This issue results in\n  proxies attempting to send traffic for that destination to stale endpoints\n  ([#11491], fixes [#11480], [#11279], and [#10590])\n* Fixed a regression introduced in stable-2.13.0 where proxies would not\n  terminate unused service discovery watches, exerting backpressure on the\n  Destination controller which could cause it to become stuck\n  ([linkerd2-proxy#2484] and [linkerd2-proxy#2486])\n* Added `INFO`-level logging to the proxy when endpoints are added or removed\n  from a load balancer. These logs are enabled by default, and can be disabled\n  by [setting the proxy log level][proxy-log-level] to\n  `warn,linkerd=info,linkerd_proxy_balance=warn` or similar\n  ([linkerd2-proxy#2486])\n* Fixed a regression where the proxy rendered `grpc_status` metric labels as a\n  string rather than as the numeric status code ([linkerd2-proxy#2480]; fixes\n  [#11449])\n* Extended `linkerd-jaeger`'s `imagePullSecrets` Helm value to also apply to\nthe `namespace-metadata` ServiceAccount ([#11504])\n* Updated the control plane's dependency on the `golang.google.org/grpc` Go\n  package to include patches for [CVE-2023-44487]/GHSA-qppj-fm5r-hxr3 ([#11496])\n* Updated dependencies on `rustix` to include patches for GHSA-c827-hfw6-qwvm\n  ([linkerd2-proxy#2488] and [#11512]).\n\n[#10590]: https://github.com/linkerd/linkerd2/issues/10590\n[#11279]: https://github.com/linkerd/linkerd2/issues/11279\n[#11491]: https://github.com/linkerd/linkerd2/pull/11491\n[#11449]: https://github.com/linkerd/linkerd2/issues/11449\n[#11480]: https://github.com/linkerd/linkerd2/issues/11480\n[#11504]: https://github.com/linkerd/linkerd2/issues/11504\n[#11512]: https://github.com/linkerd/linkerd2/issues/11512\n[linkerd2-proxy#2480]: https://github.com/linkerd/linkerd2-proxy/pull/2480\n[linkerd2-proxy#2484]: https://github.com/linkerd/linkerd2-proxy/pull/2484\n[linkerd2-proxy#2486]: https://github.com/linkerd/linkerd2-proxy/pull/2486\n[linkerd2-proxy#2488]: https://github.com/linkerd/linkerd2-proxy/pull/2488\n[proxy-log-level]: https://linkerd.io/2.14/tasks/modifying-proxy-log-level/\n[CVE-2023-44487]: https://github.com/advisories/GHSA-qppj-fm5r-hxr3\n\n## edge-23.10.2\n\nThis edge release includes a fix addressing an issue during upgrades for\ninstances not relying on automated webhook certificate management (like\ncert-manager provides).\n\n* Added a `checksum/config` annotation to the destination and proxy injector\n  deployment manifests, to force restarting those workloads whenever their\n  webhook secrets change during upgrade (thanks @iAnomaly!) ([#11440])\n* Fixed policy controller error when deleting a Gateway API HTTPRoute resource\n  ([#11471])\n\n[#11440]: https://github.com/linkerd/linkerd2/pull/11440\n[#11471]: https://github.com/linkerd/linkerd2/pull/11471\n\n## edge-23.10.1\n\nThis edge release adds additional configurability to Linkerd's viz and\nmulticluster extensions.\n\n* Added a `podAnnotations` Helm value to allow adding additional annotations to\n  the Linkerd-Viz Prometheus Deployment ([#11365]) (thanks @cemenson)\n* Added `imagePullSecrets` Helm values to the multicluster chart so that it can\n  be installed in an air-gapped environment. ([#11285]) (thanks @lhaussknecht)\n\n[#11365]: https://github.com/linkerd/linkerd2/issues/11365\n[#11285]: https://github.com/linkerd/linkerd2/issues/11285\n\n## edge-23.9.4\n\nThis edge release makes Linkerd even better.\n\n* Added a controlPlaneVersion override to the `linkerd-control-plane` Helm chart\n  to support including SHA256 image digests in Linkerd manifests (thanks\n  @cromulentbanana!) ([#11406])\n* Improved `linkerd viz check` to attempt to validate that the Prometheus scrape\n  interval will work well with the CLI and Web query parameters ([#11376])\n* Improved CLI error handling to print differentiated error information when\n  versioncheck.linkerd.io cannot be resolved (thanks @dtaskai) ([#11377])\n* Fixed an issue where the destination controller would not update pod metadata\n  for profile resolutions for a pod accessed via the host network (e.g.\n  HostPort endpoints) ([#11334]).\n* Added a validating webhook config for httproutes.gateway.networking.k8s.io\n  resources (thanks @mikutas!) ([#11150])\n* Introduced a new `multicluster check --timeout` flag to limit the time\n  allowed for Kubernetes API calls (thanks @moki1202) ([#11420])\n\n[#11150]: https://github.com/linkerd/linkerd2/pull/11150\n[#11334]: https://github.com/linkerd/linkerd2/pull/11334\n[#11376]: https://github.com/linkerd/linkerd2/pull/11376\n[#11377]: https://github.com/linkerd/linkerd2/pull/11377\n[#11406]: https://github.com/linkerd/linkerd2/pull/11406\n[#11420]: https://github.com/linkerd/linkerd2/pull/11420\n\n## edge-23.9.3\n\nThis edge release updates the proxy's dependency on the `rustls` library to\npatch security vulnerability [RUSTSEC-2023-0052][RUSTSEC-2023-0052-0]\n(GHSA-8qv2-5vq6-g2g7), a potential CPU usage denial-of-service attack when\nacceting a TLS handshake from an untrusted peer with a maliciously-crafted\ncertificate. Furthermore, this edge release contains a few improvements to the\ncontrol plane and jaeger extension Helm charts.\n\n* Addressed security vulnerability [RUSTSEC-2023-0052][RUSTSEC-2023-0052-0] in\n  the proxy by updating its dependency on the `rustls` library\n* Added a `prometheusUrl` field for the heartbeat job in the control plane Helm\n  chart (thanks @david972!) ([#11343]; fixes [#11342])\n* Introduced support for arbitrary labels in the `podMonitors` field in the\n  control plane Helm chart (thanks @jseiser!) ([#11222]; fixes [#11175])\n* Added support for config merge and Deployment environment to\n  `opentelemetry-collector` in the jaeger extension (thanks @iAnomaly!)\n  ([#11283])\n\n[#11283]: https://github.com/linkerd/linkerd2/pull/11283\n[#11222]: https://github.com/linkerd/linkerd2/pull/11222\n[#11175]: https://github.com/linkerd/linkerd2/issues/11175\n[#11343]: https://github.com/linkerd/linkerd2/pull/11343\n[#11342]: https://github.com/linkerd/linkerd2/issues/11342\n[RUSTSEC-2023-0052-0]: https://rustsec.org/advisories/RUSTSEC-2023-0052.html\n\n## edge-23.9.2\n\nThis edge release updates the proxy's dependency on the `webpki` library to\npatch security vulnerability [RUSTSEC-2023-0052] (GHSA-8qv2-5vq6-g2g7), a\npotential CPU usage denial-of-service attack when accepting a TLS handshake from\nan untrusted peer with a maliciously-crafted certificate.\n\n* Addressed security vulnerability [RUSTSEC-2023-0052] in the proxy ([#11361])\n* Fixed `linkerd check --proxy` incorrectly checking the proxy version of pods\n  in the `completed` state (thanks @mikutas!) ([#11295]; fixes [#11280])\n* Removed unnecessary `linkerd.io/helm-release-version` annotation from the\n  `linkerd-control-plane` Helm chart (thanks @mikutas!) ([#11329]; fixes\n  [#10778])\n\n[RUSTSEC-2023-0052]: https://rustsec.org/advisories/RUSTSEC-2023-0052.html\n[#11295]: https://github.com/linkerd/linkerd2/pull/11295\n[#11280]: https://github.com/linkerd/linkerd2/issues/11280\n[#11361]: https://github.com/linkerd/linkerd2/pull/11361\n[#11329]: https://github.com/linkerd/linkerd2/pull/11329\n[#10778]: https://github.com/linkerd/linkerd2/issues/10778\n\n## edge-23.9.1\n\nThis edge release introduces a fix for service discovery on endpoints that use\nhostPorts. Previously, the destination service would return the pod IP for the\ndiscovery request which could break connectivity on pod restart. To fix this,\ndirect pod communication for a pod bound on a hostPort will always return the\nhostIP. In addition, this release fixes a security vulnerability (CVE-2023-2603)\ndetected in the CNI plugin and proxy-init images, and includes a number of other\nfixes and small improvements.\n\n* Addressed security vulnerability CVE-2023-2603 in proxy-init and CNI plugin\n  ([#11296])\n* Introduced resource requests/limits for the policy controller resource in the\n  control plane helm chart ([#11301])\n* Fixed an issue where an empty `remoteDiscoverySelector` field in a\n  multicluster link would cause all services to be mirrored ([#11309])\n* Removed time out from `linkerd multicluster gateways` command; when no\n  metrics exist the command will return instantly ([#11265])\n* Improved help messaging for `linkerd multicluster link` ([#11265])\n* Changed how hostPort lookups are handled in the destination service.\n  Previously, when doing service discovery for an endpoint bound on a hostPort,\n  the destination service would return the corresponding pod IP. On pod\n  restart, this could lead to loss of connectivity on the client's side. The\n  destination service now always returns host IPs for service discovery on an\n  endpoint that uses hostPorts ([#11328])\n* Updated HTTPRoute webhook rule to validate all apiVersions of the resource\n  (thanks @mikutas!) ([#11149])\n* Fixed erroneous `skipped` messages when injecting namespaces with `linkerd\n  inject` (thanks @mikutas!) ([#10231])\n\n[#11309]: https://github.com/linkerd/linkerd2/issues/11309\n[#11296]: https://github.com/linkerd/linkerd2/discussions/11296\n[#11328]: https://github.com/linkerd/linkerd2/pull/11328\n[#11301]: https://github.com/linkerd/linkerd2/issues/11301\n[#11265]: https://github.com/linkerd/linkerd2/pull/11265\n[#11149]: https://github.com/linkerd/linkerd2/pull/11149\n[#10231]: https://github.com/linkerd/linkerd2/issues/10231\n\n## stable-2.14.0\n\nThis release introduces direct pod-to-pod multicluster service mirroring. When\nclusters are deployed on a flat network, Linkerd can export multicluster\nservices in a way where cross-cluster traffic does not need to go through the\ngateway. This enhances multicluster authentication and can reduce the need for\nprovisioning public load balancers.\n\nIn addition, this release adds support for the\n[Gateway API](https://gateway-api.sigs.k8s.io/) HTTPRoute resource (in the\n`gateway.networking.k8s.io` api group). This improves compatibility with other\ntools that use these resources such as [Flagger](https://flagger.app/) and\n[Argo Rollouts](https://argoproj.github.io/rollouts/). The release also includes\na large number of features and improvements to HTTPRoute including the ability\nto set timeouts and the ability to define consumer-namespace HTTPRoutes.\n\nFinally, this release includes a number of bugfixes, performance improvements,\nand other smaller additions.\n\n**Upgrade notes**: Please see the\n[upgrade instructions](https://linkerd.io/2/tasks/upgrade/#upgrade-notice-stable-2140).\n\n* Multicluster\n  * Remove namespace field from cluster scoped resources to fix pruning\n  * Added -o json flag for the `linkerd multicluster gateways` command (thanks\n    @hiteshwani29)\n  * Introduced `logFormat` value to the multicluster `Link` Helm Chart (thanks\n    @bunnybilou!)\n  * Added leader-election capabilities to the service-mirror controller\n  * Added high-availability (HA) mode for the multicluster service-mirror\n  * Added a new `remoteDiscoverySelector` field to the multicluster `Link` CRD,\n    which enables a service mirroring mode where the control plane\n    performs discovery for the mirrored service from the remote cluster, rather\n    than creating Endpoints for the mirrored service in the source cluster\n* HTTPRoute\n  * Fixed `linkerd uninstall` issue for HTTPRoute\n  * Added support for `gateway.networking.k8s.io` HTTPRoutes in the policy\n    controller\n  * Added support for RequestHeaderModifier and RequestRedirect HTTP filters in\n    outbound policy; filters may be added at the route or backend level\n  * Added support for the `ResponseHeaderModifier` HTTPRoute filter\n  * Added support for HTTPRoutes defined in the consumer namespace\n  * Added support for HTTPRoute `parent_refs` that do not specify a port\n* CRDs\n  * Patched the MeshTLSAuthentication CRD to force providing at least one\n    identity/identityRef\n* Control Plane\n  * Send Opaque protocol hint for opaque ports in destination controller\n  * Replaced deprecated `failure-domain.beta.kubernetes.io/zone` labels in Helm\n    charts  with `topology.kubernetes.io/zone` labels (thanks @piyushsingariya!)\n  * Replaced `server_port_subscribers` Destination controller gauge metric with\n    `server_port_subscribes` and `server_port_unsubscribes` counter metrics\n* Proxy\n  * Handle Opaque protocol hints on endpoints\n  * Added `outbound_http_balancer_endpoints` metric\n  * Fixed missing route_ metrics for requests with ServiceProfiles\n  * Fixed proxy startup failure when using the `config.linkerd.io/admin-port`\n    annotation (thanks @jclegras!)\n  * Added distinguishable version information to proxy logs and metrics\n* CLI\n  * The `linkerd diagnostics policy` command now displays outbound policy when\n    the target resource is a Service\n  * A fix for HA validation checks when Linkerd is installed with Helm. Thanks\n    @mikutas!!\n* Viz\n  * Add the `kubelet` NetworkAuthentication back since it is used by the\n    `linkerd viz allow-scrapes` subcommand.\n  * Fixed the `linkerd viz check` command so that it will wait until the viz\n    extension becomes ready\n  * Fixed an issue where specifying a `remote_write` config would cause the\n    Prometheus config to be invalid (thanks @hiteshwani29)\n  * Improved validation of the `--to` and `--from` flags for the `linkerd viz stat`\n    command (thanks @pranoyk)\n  * Added `-o jsonpath` flag to `linkerd viz tap` to allow filtering output fields\n    (thanks @hiteshwani29!)\n  * Fixed a Grafana error caused by an incorrect datasource (thanks @albundy83!)\n  * Fixed missing \"Services\" menu item in the Spanish localization for the\n  `linkerd-viz` web dashboard (thanks @mclavel!)\n* Extensions\n  * Added missing label `linkerd.io/extension` to certain resources to ensure they\n    pruned when appropriate (thanks @ClementRepo)\n  * Added tolerations and nodeSelector support in extensions `namespace-metadata`\n    Jobs (thanks @pssalman!)\n* Init Containers\n  * Added an option for disabling the network validator's security context for\n    environments that provide their own\n* CNI\n  * Added --set flag to install-cni plugin (thanks @amit-62!)\n  * Fixed missing resource-cni labels on linkerd-cni, this blocked the\n    linkerd-cni pods from coming up when the injector was broken (thanks\n    @migueleliasweb!)\n* Build\n  * Build improvements for multi-arch build artifacts. Thanks @MarkSRobinson!!\n\nThis release includes changes from a massive list of contributors! A special\nthank-you to everyone who helped make this release possible:\n\n* Amir Karimi @AMK9978\n* Amit Kumar @amit-62\n* Andre Marcelo-Tanner @kzap\n* Andrew @andrew-gropyus\n* Arnaud Beun @bunnybilou\n* Clement @proxfly\n* Dima @krabradosty\n* Grégoire Bellon-Gervais @albundy83\n* Harsh Soni @harsh020\n* Jean-Charles Legras @jclegras\n* Loong Dai @daixiang0\n* Mark Robinson @MarkSRobinson\n* Miguel Elias dos Santos @migueleliasweb\n* Pranoy Kumar Kundu @pranoyk\n* Ryan Hristovski @ryanhristovski\n* Takumi Sue @mikutas\n* Zakhar Bessarab @zekker6\n* hiteshwani29 @hiteshwani29\n* pheianox\n* pssalman @pssalman\n\n## edge-23.8.3\n\nThis is a release candidate for stable-2.14.0; we encourage you to help trying\nit out!\n\nThis edge release contains a number of improvements over the multi-cluster\nfeatures introduced in the last edge release supporting flat networks. It also\nhardens the containers security stance by removing write access to the root\nfilesystem.\n\n* Enhanced `linkerd multicluster link` to allow clusters to be linked without a\n  gateway ([#11226])\n* Added cluster store size gauge metric ([#11256])\n* Disabled local traffic policy for remote discovery ([#11257])\n* Fixed various innocuous multi-cluster warnings ([#11251], [#11246], [#11253])\n* Set `readOnlyRootFilesystem: true` in all the containers, as they don't\n  require write permissions ([#11221]; fixes [#11142]) (thanks @mikutas!)\n\n[#11226]: https://github.com/linkerd/linkerd2/pull/11226\n[#11256]: https://github.com/linkerd/linkerd2/pull/11256\n[#11257]: https://github.com/linkerd/linkerd2/pull/11257\n[#11251]: https://github.com/linkerd/linkerd2/pull/11251\n[#11246]: https://github.com/linkerd/linkerd2/pull/11246\n[#11253]: https://github.com/linkerd/linkerd2/pull/11253\n[#11221]: https://github.com/linkerd/linkerd2/pull/11221\n[#11142]: https://github.com/linkerd/linkerd2/issues/11142\n\n## edge-23.8.2\n\nThis edge release adds improvements to Linkerd's multi-cluster features as part\nof the [flat network support] planned for Linkerd stable-2.14.0. In addition, it\nfixes an issue ([#10764]) where warnings about an invalid metric were logged\nfrequently by the Destination controller.\n\n* Added a new `remoteDiscoverySelector` field to the multicluster `Link` CRD,\n  which enables a service mirroring mode where the control plane\n  performs discovery for the mirrored service from the remote cluster, rather\n  than creating Endpoints for the mirrored service in the source cluster\n  ([#11190], [#11201], [#11220], and [#11224])\n* Fixed missing \"Services\" menu item in the Spanish localization for the\n  `linkerd-viz` web dashboard ([#11229]) (thanks @mclavel!)\n* Replaced `server_port_subscribers` Destination controller gauge metric with\n  `server_port_subscribes` and `server_port_unsubscribes` counter metrics\n  ([#11206]; fixes [#10764])\n* Replaced deprecated `failure-domain.beta.kubernetes.io/zone` labels in Helm\n  charts  with `topology.kubernetes.io/zone` labels ([#11148]; fixes [#11114])\n  (thanks @piyushsingariya!)\n\n[#10764]: https://github.com/linkerd/linkerd2/issues/10764\n[#11114]: https://github.com/linkerd/linkerd2/issues/11114\n[#11148]: https://github.com/linkerd/linkerd2/issues/11148\n[#11190]: https://github.com/linkerd/linkerd2/issues/11190\n[#11201]: https://github.com/linkerd/linkerd2/issues/11201\n[#11206]: https://github.com/linkerd/linkerd2/issues/11206\n[#11220]: https://github.com/linkerd/linkerd2/issues/11220\n[#11224]: https://github.com/linkerd/linkerd2/issues/11224\n[#11229]: https://github.com/linkerd/linkerd2/issues/11229\n[flat network support]: https://linkerd.io/2023/07/20/enterprise-multi-cluster-at-scale-supporting-flat-networks-in-linkerd/\n\n## edge-23.8.1\n\nThis edge release restores a proxy setting for it to shed load less aggressively\nwhile under high load, which should result in lower error rates (see #11055). It\nalso removes the usage of host networking in the linkerd-cni extension.\n\n* Changed the default HTTP request queue capacities for the inbound and outbound\n  proxies back to 10,000 requests (see #11055 and #11198)\n* Lifted need of using host networking in the linkerd-cni Daemonset (#11141)\n  (thanks @abhijeetgauravm!)\n\n## edge-23.7.3\n\nThis edge release improves Linkerd's support for HttpRoute by allowing\n`parent_ref` ports to be optional, allowing HttpRoutes to be defined in a\nconsumer's namespace, and adding support for the `ResponseHeaderModifier` filter.\nIt also fixes a panic in the destination controller.\n\n* Added an option for disabling the network validator's security context for\n  environments that provide their own\n* Added high-availability (HA) mode for the multicluster service-mirror\n* Added support for HttpRoute `parent_refs` that do not specify a port\n* Fixed a Grafana error caused by an incorrect datasource (thanks @albundy83!)\n* Added support for HttpRoutes defined in the consumer namespace\n* Improved the granularity of logging levels in the control plane\n* Fixed a race condition in the destination controller that could cause it to\n  panic\n* Added support for the `ResponseHeaderModifier` HttpRoute filter\n* Updated extension CLI commands to prefer the `--register` flag over the\n  `LINKERD_DOCKER_REGISTRY` environment variable, making the precedence more\n  consistent (thanks @harsh020!)\n\n## edge-23.7.2\n\nThis edge release introduces support for HTTP filters configured through both\n`policy.linkerd.io` and `gateway.networking.k8s.io` HTTPRoute resources.\nCurrently, RequestHeaderModifier and RequestRedirect HTTP filters are\nsupported. Additionally, this release fixes an issue with the linkerd-cni\nchart.\n\n* Added support for RequestHeaderModifier and RequestRedirect HTTP filters in\n  outbound policy; filters may be added at the route or backend level\n* Fixed missing resource-cni labels on linkerd-cni, this blocked the\n  linkerd-cni pods from coming up when the injector was broken (thanks\n  @migueleliasweb!)\n\n## edge-23.7.1\n\nThis edge release adds support for the upstream `gateway.networking.k8s.io`\nHTTPRoute resource (in addition to the `policy.linkerd.io` CRD installed by\nLinkerd). Furthermore, it fixes a bug where the ingress-mode proxy would fail to\nfall back to ServiceProfiles for destinations without HTTPRoutes.\n\n* Added support for `gateway.networking.k8s.io` HTTPRoutes in the policy\n  controller\n* Added distinguishable version information to proxy logs and metrics\n* Fixed incorrect handling of `NotFound` client policies in ingress-mode proxies\n\n## edge-23.6.3\n\nThis edge release adds leader-election capabilities to the service-mirror\ncontroller under the hood, as a precursor to HA mode in an upcoming release. It\nalso includes a `linkerd viz tap` improvement and a proxy startup bugfix, both\ncontributed by the community!\n\n* Added leader-election capabilities to the service-mirror controller\n* Added `-o jsonpath` flag to `linkerd viz tap` to allow filtering output fields\n  (thanks @hiteshwani29!)\n* Fixed proxy startup failure when using the `config.linkerd.io/admin-port`\n  annotation (thanks @jclegras!)\n\n## edge-23.6.2\n\nThis edge release introduces timeout capabilities for HTTPRoutes in a manner\ncompatible with the proposed changes to HTTPRoute in\n[kubernetes-sigs/gateway-api#1997](https://github.com/kubernetes-sigs/gateway-api/pull/1997).\n\nThis release also includes several small improvements and fixes:\n\n* A fix for HA validation checks when Linkerd is installed with Helm. Thanks\n@mikutas!!\n* Build improvements for multi-arch build artifacts. Thanks @MarkSRobinson!!\n\n## edge-23.6.1\n\nThis edge release changes the behavior of the CNI plugin to run exclusively in\n\"chained mode\". Instead of creating its own configuration file, the CNI plugin\nwill now wait until a `conf` file exists before appending its configuration.\nAdditionally, this change includes a bug fix for topology aware service\nrouting.\n\n* Changed the CNI plugin installer to always run in 'chained' mode; the plugin will\n  now wait until another CNI plugin is installed before appending its\n  configuration\n* Fixed bug where topology routing would not disable while service was under\n  load (thanks @MarkSRobinson!)\n* Introduced `logFormat` value to the multicluster `Link` Helm Chart (thanks\n  @bunnybilou!)\n\n## edge-23.5.3\n\nThis edge release includes fixes for several bugs related to HTTPRoute handling.\n\n* Fixed an issue where the `namespace` field on HTTPRoute `backendRef`s was\n  ignored, and the backend Service would always be assumed to be in the\n  namespace as the parent Service\n* Fixed an issue where default authorizations generated for readiness and\n  liveness probes would fail if the probe path included URI query parameters\n* Fixed the proxy not using gRPC response classification for gRPC requests to\n  destinations without ServiceProfiles\n\n## edge-23.5.2\n\nThis edge release adds some minor improvements in the MeshTLSAuthentication CRD\nand the extensions charts, and fixes an issue with `linkerd multicluster check`.\n\n* Added tolerations and nodeSelector support in extensions `namespace-metadata`\n  Jobs (thanks @pssalman!)\n* Patched the MeshTLSAuthentication CRD to force providing at least one\n  identity/identityRef\n* Fixed the `linkerd multicluster check` command failing in the presence of lots\n  of mirrored services\n\n## edge-23.5.1\n\nThis edge release introduces the ability to configure the proxy's discovery cache\ntimeouts via annotations. While most users will not need to do this, it can be\nuseful to improve the mesh's resilience to control plane failures. This release\nalso includes a number of other important improvements and bug fixes.\n\n* Added -o json flag for the `linkerd multicluster gateways` command (thanks\n  @hiteshwani29)\n* Added missing label `linkerd.io/extension` to certain resources to ensure they\n  pruned when appropriate (thanks @ClementRepo)\n* Fixed a memory leak in the service mirror controller\n* Improved validation of the `--to` and `--from` flags for the `linkerd viz stat`\n  command (thanks @pranoyk)\n* Fixed an issue with W3C trace context propagation which caused proxy spans to\n  be siblings rather than children of their original parent (thanks\n  @whiskeysierra)\n* Updated the Linkerd CNI plugin base docker image from Debian to Alpine\n* Fixed an issue where specifying a `remote_write` config would cause the\n  Prometheus config to be invalid (thanks @hiteshwani29)\n* Added the ability to configure the proxy's discovery cache timeouts with the\n  `config.linkerd.io/proxy-outbound-discovery-cache-unused-timeout` and\n  `config.linkerd.io/proxy-inbound-discovery-cache-unused-timeout` annotations\n* Fixed the `linkerd viz check` command so that it will wait until the viz\n  extension becomes ready\n* Fixed an issue where meshed pods could not communicate with themselves through\n  a ClusterIP Service\n\n## edge-23.4.3\n\nThis edge release improves compatibility with ArgoCD by changing the Linkerd\ncontrol plane to create Lease resources at runtime rather than including them\nin the Helm chart. It also addresses a CVE by upgrading an underlying\ndependency.\n\n* Upgraded `h2` dependency to address CVE-2023-26964\n* Fixed an issue where `server_port_subscribers` metric in the Destination\n  controller was sometimes absent\n* Removed the policy-controller-write Lease from the control plane Helm chart in\n  favor of creating it at runtime\n* Updated the proxy-injector to pass opaque port lists to the proxy as ranges\n  rather than individually, greatly reducing the size of proxy manifests when\n  large opaque port ranges are set\n* Fixed an issue where the proxy was performing protocol detection on ports\n  marked as opaque\n* Improved backwards compatibility between 2.13 proxies and 2.12 control planes\n\n## edge-23.4.2\n\nThis edge release contains a number of bug fixes.\n\n* CLI\n  * Fixed `linkerd uninstall` issue for HttpRoute\n  * The `linkerd diagnostics policy` command now displays outbound policy when\n    the target resource is a Service\n\n* CNI\n  * Fixed incompatibility issue with AWS CNI addon in EKS, that was\n    forbidding pods to acquire networking after scaling up nodes.\n    (thanks @frimik!)\n  * Added --set flag to install-cni plugin (thanks @amit-62!)\n\n* Control Plane\n  * Fixed an issue where the policy controller always used the default\n    `cluster.local` domain\n  * Send Opaque protocol hint for opaque ports in destination controller\n\n* Helm\n  * Fixed an issue in the viz Helm chart where the namespace metadata template\n    would throw `unexpected argument found` errors\n  * Fixed Jaeger chart installation failure\n\n* Multicluster\n  * Remove namespace field from cluster scoped resources to fix pruning\n\n* Proxy\n  * Updated `h2` dependency to include a patch for a theoretical\n    denial-of-service vulnerability discovered in CVE-2023-26964\n  * Handle Opaque protocol hints on endpoints\n  * Changed the proxy's default log level to silence warnings from\n    `trust_dns_proto` that are generally spurious.\n  * Added `outbound_http_balancer_endpoints` metric\n  * Fixed missing route_ metrics for requests with ServiceProfiles\n\n* Viz\n  * Bump prometheus image to v2.43.0\n  * Add the `kubelet` NetworkAuthentication back since it is used by the\n`linkerd viz allow-scrapes` subcommand.\n\n## stable-2.13.1\n\nThis stable release fixes an issue in the policy controller where a non-default\ncluster domain would return incorrect authorities in the outbound policy API.\nAdditionally, this release updates a proxy dependency to fix CVE-2023-2694.\n\n* Proxy\n  * Updated `h2` dependency to include a patch for a theoretical\n    denial-of-service vulnerability discovered in CVE-2023-26964\n\n* Control Plane\n  * Fixed an issue where the policy controller always used the default\n    `cluster.local` domain\n\n* Helm\n  * Fixed an issue in the viz Helm chart where the namespace metadata template\n    would throw `unexpected argument found` errors\n\n## stable-2.13.0\n\nThis release introduces client-side policy to Linkerd, including dynamic routing\nand circuit breaking. [Gateway API](https://gateway-api.sigs.k8s.io/) HTTPRoutes\ncan now be used to configure policy for outbound (client) proxies as well as\ninbound (server) proxies, by creating HTTPRoutes with Service resources as their\n`parentRef`. See the Linkerd documentation for tutorials on [dynamic request\nrouting] and [circuit breaking]. New functionality for debugging HTTPRoute-based\npolicy is also included in this release, including [new proxy metrics] and the\nability to display outbound policies in the `linkerd diagnostics policy` CLI\ncommand.\n\nIn addition, this release adds `network-validator`, a new init container to be\nused when CNI is enabled. `network-validator` ensures that local iptables rules\nare working as expected. It will validate this before linkerd-proxy starts.\n`network-validator` replaces the `noop` container, runs as `nobody`, and drops\nall capabilities before starting.\n\nFinally, this release includes a number of bugfixes, performance improvements,\nand other smaller additions.\n\n**Upgrade notes**: Please see the [upgrade instructions][upgrade-2130].\n\n* CRDs\n  * HTTPRoutes may now have Service parents, to configure outbound policy\n  * Updated HTTPRoute version from `v1alpha1` to `v1beta2`\n\n* CLI\n  * Added a new `linkerd prune` command to the CLI (including most extensions) to\n    remove resources which are no longer part of Linkerd's manifests\n  * Added additional shortnames for Linkerd policy resources (thanks @javaducky!)\n  * The `linkerd diagnostics policy` command now displays outbound policy when\n    the target resource is a Service\n\n* Control Plane\n  * The policy controller now discovers outbound policy configurations from\n    HTTPRoutes that target Services.\n  * Added OutboundPolicies API, for use by `linkerd-proxy` to route\n    outbound traffic\n  * Added Prometheus `/metrics` endpoint to the admin server, with process\n    metrics\n  * Fixed QueryParamMatch parsing for HTTPRoutes\n  * Added the policy status controller which writes the `status` field to\n    HTTPRoutes when a parent reference Server accepts or rejects it\n  * Added KubeAPI server ports to `ignoreOutboundPorts` of `proxy-injector`\n  * No longer apply `waitBeforeExitSeconds` to control plane, viz and jaeger\n    extension pods\n  * Added support for the `internalTrafficPolicy` of a service (thanks @yc185050!)\n  * Added block chomping to strip trailing new lines in ConfigMap (thanks @avdicl!)\n  * Added protection against nil dereference in resources helm template\n  * Added support for Pod Security Admission (Pod Security Policy resources are\n    still supported but disabled by default)\n  * Lowered non-actionable error messages in the Destination log to debug-level\n    entries to avoid triggering false alarms (thanks @siddharthshubhampal!)\n  * Fixed an issue with EndpointSlice endpoint reconciliation on slice deletion;\n    when using more than one slice, a `NoEndpoints` event would be sent to the\n    proxy regardless of the amount of endpoints that were still available\n    (thanks @utay!)\n  * Improved diagnostic log messages\n  * Fixed sending of spurious profile updates\n  * Removed unnecessary Namespaces access from the destination controller RBAC\n  * Added the server_port_subscribers metric to track the number of subscribers\n    to Server changes associated with a pod's port\n  * Added the service_subscribers metric to track the number of subscribers to\n    Service changes\n  * Fixed a small memory leak in the opaque ports watcher\n\n* Proxy\n  * Use the new OutboundPolicies API, supporting Gateway API-style routes\n    in the outbound proxy\n  * Added support for dynamic request routing based on HTTPRoutes\n  * Added HTTP circuit breaking\n  * Added `outbound_route_backend_http_requests_total`,\n    `outbound_route_backend_grpc_requests_total`, and\n    `outbound_http_balancer_endpoints` metrics\n  * Changed the proxy's behavior when traffic splitting so that only services\n    that are not in failfast are used. This will enable the proxy to manage\n    failover without external coordination\n  * Updated tokio (async runtime) in the proxy which should reduce CPU usage,\n    especially for proxy's pod local (i.e in the same network namespace)\n    communication\n\n* linkerd-proxy-init\n  * Changed `proxy-init` iptables rules to be idempotent upon init pod\n    restart (thanks @jim-minter!)\n  * Improved logging in `proxy-init` and `linkerd-cni`\n  * Added a `proxyInit.privileged` setting to control whether the `proxy-init`\n    initContainer runs as a privileged process\n\n* CNI\n  * Added static and dynamic port overrides for CNI eBPF to work with socket-level\n    load balancing\n  * Added `network-validator` init container to ensure that iptables rules are\n    working as expected\n  * Added a `resources` field in the linkerd-cni chart (thanks @jcogilvie!)\n\n* Viz\n  * Added `tap.ignoredHeaders` Helm value to the linkerd-viz chart. This value\n    allows users to specify a comma-separated list of header names which will be\n    ignored by Linkerd Tap (thanks @ryanhristovski!)\n  * Removed duplicate SecurityContext in Prometheus manifest\n  * Added new flag `--viz-namespace` which avoids requiring permissions for\n    listing all namespaces in `linkerd viz` subcommands (thanks @danibaeyens!)\n  * Removed the TrafficSplit page from the Linkerd viz dashboard (thanks\n    @h-dav!)\n  * Introduced new values in the `viz` chart to allow for arbitrary annotations\n    on the `Service` objects (thanks @sgrzemski!)\n  * Added an optional AuthorizationPolicy to authorize Grafana to Prometheus\n    in the Viz extension\n\n* Multicluster\n  * Removed duplicate AuthorizationPolicy for probes from the multicluster\n    gateway Helm chart\n  * Updated wording for linkerd-multicluster cluster when it fails to probe a\n    remote gateway mirror\n  * Added multicluster gateway `nodeSelector` and `tolerations` helm parameters\n  * Added new configuration options for the multicluster gateway:\n    * `gateway.deploymentAnnotations`\n    * `gateway.terminationGracePeriodSeconds` (thanks @bunnybilou!)\n    * `gateway.loadBalancerSourceRanges` (thanks @Tyrion85!)\n\n* Extensions\n  * Removed dependency on the `curlimages/curl` 3rd-party image used to initialize\n    extensions namespaces metadata (so they are visible by `linkerd check`),\n    replaced by the new `extension-init` image\n  * Converted `ServerAuthorization` resources to `AuthorizationPolicy` resources\n    in Linkerd extensions\n  * Removed policy resources bound to admin servers in extensions (previously\n    these resources were used to authorize probes but now are authorized by\n    default)\n  * Fixed the link to the Jaeger dashboard the in viz dashboard (thanks\n    @eugenegoncharuk!)\n  * Updated linkerd-jaeger's collector to expose port 4318 in order support HTTP\n    alongside gRPC (thanks @uralsemih!)\n\n* Among other dependency updates, the no-longer maintained ghodss/yaml library\n  was replaced with sigs.k8s.io/yaml (thanks @Juneezee!)\n\nThis release includes changes from a massive list of contributors! A special\nthank-you to everyone who helped make this release possible:\n\n* Andrew Pinkham [@jambonrose](https://github.com/jambonrose)\n* Arnaud Beun [@bunnybilou](https://github.com/bunnybilou)\n* Carlos Tadeu Panato Junior [@cpanato](https://github.com/cpanato)\n* Christian Segundo [@someone-stole-my-name](https://github.com/someone-stole-my-name)\n* Dani Baeyens [@danibaeyens](https://github.com/danibaeyens)\n* Duc Tran [@ductnn](https://github.com/ductnn)\n* Eng Zer Jun [@Juneezee](https://github.com/Juneezee)\n* Ivan Ivic [@Tyrion85](https://github.com/Tyrion85)\n* Joe Bowbeer [@joebowbeer](https://github.com/joebowbeer)\n* Jonathan Ogilvie [@jcogilvie](https://github.com/jcogilvie)\n* Jun [@junnplus](https://github.com/junnplus)\n* Loong Dai [@daixiang0](https://github.com/daixiang0)\n* María Teresa Rojas [@mtrojas](https://github.com/mtrojas)\n* Mo Sattler [@MoSattler](https://github.com/MoSattler)\n* Oleg Vorobev [@olegy2008](https://github.com/olegy2008)\n* Paul Balogh [@javaducky](https://github.com/javaducky)\n* Peter Smit [@psmit](https://github.com/psmit)\n* Ryan Hristovski [@ryanhristovski](https://github.com/ryanhristovski)\n* Semih Ural [@uralsemih](https://github.com/uralsemih)\n* Shubhodeep Mukherjee [@shubhodeep9](https://github.com/shubhodeep9)\n* Siddharth S Pal [@siddharthshubhampal](https://github.com/siddharthshubhampal)\n* Subhash Choudhary [@subhashchy](https://github.com/subhashchy)\n* Szymon Grzemski [@sgrzemski](https://github.com/sgrzemski)\n* Takumi Sue [@mikutas](https://github.com/mikutas)\n* Yannick Utard [@utay](https://github.com/utay)\n* Yu Cao [@yc185050](https://github.com/yc185050)\n* anoxape [@anoxape](https://github.com/anoxape)\n* bastienbosser [@bastienbosser](https://github.com/bastienbosser)\n* bitfactory-sem-denbroeder [@bitfactory-sem-denbroeder](https://github.com/bitfactory-sem-denbroeder)\n* cui fliter [@cuishuang](https://github.com/cuishuang)\n* eugenegoncharuk [@eugenegoncharuk](https://github.com/eugenegoncharuk)\n* h-dav @[h-dav](https://github.com/h-dav)\n* martinkubrak [@martinkubra](https://github.com/martinkubra)\n* verbotenj [@verbotenj](https://github.com/verbotenj)\n* ziollek [@ziollek](https://github.com/ziollek)\n\n[dynamic request routing]: https://linkerd.io/2.13/tasks/configuring-dynamic-request-routing\n[circuit breaking]: https://linkerd.io/2.13/tasks/circuit-breakers\n[new proxy metrics]: https://linkerd.io/2.13/reference/proxy-metrics/#outbound-xroute-metrics\n[upgrade-2130]: https://linkerd.io/2/tasks/upgrade/#upgrade-notice-stable-2130\n\n## edge-23.4.1\n\nThis is a release candidate for stable-2.13.0 &mdash; we encourage you to help\ntry it out!\n\nThis edge release introduces request-level HTTP circuit-breaking\nusing a consecutive failures failure accrual policy. Circuit breaking can be\nconfigured by adding failure accrual annotations to a Service. In addition, this\nrelease adds new `outbound_route_backend_http_requests_total` and\n`outbound_route_backend_grpc_requests_total` proxy metrics, which can be\nused to track how routing rules and backend distributions apply to\nrequests. These metrics contain labels describing the route's parent\n(i.e. a Service), the route resource being used, and the backend\nresource being used by each request.\n\n* Proxy\n  * Added discovery of failure accrual policies from the OutboundPolicy API\n  * Implemented consecutive failures failure accrual policy\n  * Added INFO-level logging on failure accrual changes\n  * Added `outbound_route_backend_http_requests_total` and\n    `outbound_route_backend_grpc_requests_total` metrics\n\n* Policy Controller\n  * Added failure accrual configuration to the OutboundPolicy API\n  * Added Prometheus `/metrics` endpoint to the admin server, with process\n    metrics\n  * Changed the policy controller to only accept HTTPRoutes when the parentRef\n    is a ClusterIP Service\n  * Added ports to service references in the OutboundPolicy API\n\n* Viz\n  * Added `tap.ignoredHeaders` Helm value to the linkerd-viz chart. This value\n    allows users to specify a comma-separated list of header names which will be\n    ignored by Linkerd Tap (thanks @ryanhristovski!)\n  * Removed duplicate SecurityContext in Prometheus manifest\n\n* Multicluster\n  * Removed duplicate AuthorizationPolicy for probes from the multicluster\n    gateway Helm chart\n\n## edge-23.3.4\n\nThis edge release further enhances the OutboundPolicies API used by the proxy to\nroute outbound traffic, and continues extending the HTTPRoute resource's Status\nfield. It also starts integrating circuit-breaking functionality into the proxy,\nwhich will be configurable in a subsequent iteration.\n\n* Continued iterating on the HTTPRoute's Status field, by extending support for\n  routes parented to Services, and adding a ResolvedRefs condition reflecting\n  the status of BackendRefs\n* Updated the OutboundPolicies API such that only HTTPRoutes with an Accepted\n  status of `true` are considered when routing outbound requests\n* Improved handling of invalid backends, allowing the configuration of error\n  responses\n* Added new flag `--viz-namespace` which avoids requiring permissions for\n  listing all namespaces in `linkerd viz` subcommands (thanks @danibaeyens!)\n* Among other dependency updates, the no-longer maintained ghodss/yaml library\n  was replaced with sigs.k8s.io/yaml (thanks @Juneezee!)\n\n## edge-23.3.3\n\nThis edge release removes TrafficSplits from the Linkerd dashboard as well as\nfixing a number of issues in the policy controller.\n\n* Removed the TrafficSplit page from the Linkerd viz dashboard\n* Fixed an issue where the policy controller was not returning the correct\n  status for non-Service authorities\n* Fixed an issue where the policy controller could use large amounts of CPU\n  when lease API calls failed\n\n## edge-23.3.2\n\nThis edge release continues to improve dynamic Policy statuses and\nintroduces support for header-based routing.\n\n* Destination Controller\n  * Added OutboundPolicies API, for use by `linkerd-proxy` to route\n    outbound traffic\n  * Improved diagnostic log messages\n  * Fixed sending of spurious profile updates\n\n* Proxy\n  * Use the new OutboundPolicies API, supporting Gateway API-style routes\n    in the outbound proxy\n\n* Policy Controller\n  * Support highly available Policy Controller by utilizing\n   `policy-controller-write` Lease when patching HTTPRoutes\n  * Consider the `status` field and filter out HTTPRoutes which have not\n    been accepted\n\n* Added KubeAPI server ports to `ignoreOutboundPorts` of `proxy-injector`\n* Updated HTTPRoute version from `v1alpha1` to `v1beta2`\n* Updated `network-validator` helm charts to use `proxy-init` resources\n* Fixed Grafana regular expression, enabling monitoring of filesystem\n  usage (thanks @h-dav!)\n\n## edge-23.3.1\n\nThis edge release continues to build support under the hood for the upcoming\nfeatures in 2.13. Also included are several dependency updates and less verbose\nlogging.\n\n* Removed dependency on the `curlimages/curl` 3rd-party image used to initialize\n  extensions namespaces metadata (so they are visible by `linkerd check`),\n  replaced by the new `extension-init` image\n* Lowered non-actionable error messages in the Destination log to debug-level\n  entries to avoid triggering false alarms (thanks @siddharthshubhampal!)\n\n## edge-23.2.3\n\nThis edge release includes a number of fixes and introduces a new CLI command,\n`linkerd prune`. The new `prune` command should be used to remove resources\nwhich are no longer part of the Linkerd manifest when doing an upgrade.\nPreviously, the recommendation was to use `linkerd upgrade` in conjunction with\n`kubectl apply --prune`, however, that will not remove resources which are not\npart of the input manifest, and it will not detect cluster scoped resources,\n`linkerd prune` (included in all core extensions) should be preferred over it.\n\nAdditionally, this change contains a few fixes from our external contributors,\nand a change to the `viz` Helm chart which allows for arbitrary annotations on\n`Service` objects. Last but not least, the release contains a few proxy\ninternal changes to prepare for the new client policy API.\n\n* Added a new `linkerd prune` command to the CLI (including extensions) to\n  remove resources which are no longer part of Linkerd's manifests\n* Introduced new values in the `viz` chart to allow for arbitrary annotations\n  on the `Service` objects (thanks @sgrzemski!)\n* Fixed up a comment in k8s API wrapper (thanks @ductnn!)\n* Fixed an issue with EndpointSlice endpoint reconciliation on slice deletion;\n  when using more than one slice, a `NoEndpoints` event would be sent to the\n  proxy regardless of the amount of endpoints that were still available (thanks\n  @utay!)\n\n## edge-23.2.2\n\nThis edge release adds the policy status controller which writes the `status`\nfield to HTTPRoutes when a parent reference Server accepts or rejects the\nHTTPRoute. This field is currently not consumed by the policy controller, but\nacts as the first step for considering HTTPRoute `status` when serving policy.\n\nAdditionally, the destination controller now uses the Kubernetes metadata API\nfor resources which it only needs to track the metadata for — Nodes and\nReplicaSets. For all other resources it tracks, it uses additional information\nso continues to use the API as before.\n\n* Fixed error message to include the colliding Server in the policy controller's\n  admission webhook validation\n* Updated wording for linkerd-multicluster cluster when it fails to probe a\n  remote gateway mirror\n* Removed unnecessary Namespaces access from the destination controller RBAC\n* Added Kubernetes metadata API in the destination controller for watching Nodes\n  and ReplicaSets\n* Fixed QueryParamMatch parsing for HTTPRoutes\n* Added the policy status controller which writes the `status` field to\n  HTTPRoutes when a parent reference Server accepts or rejects it\n\n## edge-23.2.1\n\nThis edge release sees the `linkerd-cni` plugin moved to\n`linkerd2-proxy-init` and released from that repository. An iptables\nimprovement to `linkerd-cni` and `proxy-init` is the main focus. Other\nminor fixes are also included.\n\n* Changed `proxy-init` iptables rules to be idempotent upon init pod\n  restart (thanks @jim-minter!)\n* Improved logging in `proxy-init` and `linkerd-cni`\n* Added the server_port_subscribers metric to track the number of subscribers\n  to Server changes associated with a pod's port\n* Added the service_subscribers metric to track the number of subscribers to\n  Service changes\n* Fixed a small memory leak in the opaque ports watcher\n* No longer apply `waitBeforeExitSeconds` to control plane, viz and jaeger\n  extension pods\n* Added support for the `internalTrafficPolicy` of a service (thanks @yc185050!)\n* Added `limits` and `requests` to network-validator for ResourceQuota interop\n* Added block chomping to strip trailing new lines in ConfigMap (thanks @avdicl!)\n* Added multicluster gateway `nodeSelector` and `tolerations` helm parameters\n* Added protection against nil dereference in resources helm template\n\n## edge-23.1.2\n\nThis edge release fixes a memory leak in the Linkerd control plane that could\noccur when many many pods were created. It also adds a number of new\nconfiguration options Multicluster extension's gateway.\n\n* Added additional shortnames for Linkerd policy resources (thanks @javaducky!)\n* Added new configuration options for the multicluster gateway:\n  * `gateway.deploymentAnnotations`\n  * `gateway.terminationGracePeriodSeconds` (thanks @bunnybilou!)\n  * `gateway.loadBalancerSourceRanges` (thanks @Tyrion85!)\n* Added an optional AuthorizationPolicy to authorize Grafana to Prometheus\n  in the Viz extension\n* Fixed the link to the Jaeger dashboard the in viz dashboard (thanks @eugenegoncharuk!)\n* Fixed an issue where control plane components could fail to start on large\n  clusters because of failing readiness probes while caches were being\n  initialized\n* Fixed a memory leak in the Destination controller\n* Fixed an issue where PodSecurityPolicies could reject Linkerd control plane\n  components due to the `seccompProfile`\n\n## edge-23.1.1\n\nThis edge release fixes a caching issue in the destination controller, converts\ndeprecated policy resources, and introduces several changes to how the proxy\nworks.\n\nA bug in the destination controller that could potentially lead to stale pods\nbeing considered in the load balancer has been fixed.\n\nSeveral Linkerd extensions were still using the now deprecated\nServerAuthorization resource. These instances have now been converted to using\nAuthorizationPolicy. Additionally, removed several policy resources that\nauthenticated probes, since probes are now authenticated by default.\n\nAs part of ongoing policy work, there are several changes with how the proxy\nworks. Routes are now lazily initialized so that service profile routes will\nnot show up in metrics until the route is used. Furthermore, the proxy’s\ntraffic splitting behavior has changed so that only available resources are\nused, resulting in less failfast errors.\n\nFinally, this edge release contains a number of fixes and improvements from our\ncontributors.\n\n* Converted `ServerAuthorization` resources to `AuthorizationPolicy` resources\n  in Linkerd extensions\n* Removed policy resources bound to admin servers in extensions (previously\n  these resources were used to authorize probes but now are authorized by\n  default)\n* Added a `resources` field in the linkerd-cni chart (thanks @jcogilvie!)\n* Fixed an issue in the CLI where `--identity-external-ca` would set an\n  incorrect field (thanks @anoxape!)\n* Fixed an issue in the destination controller's cache that could result in\n  stale endpoints when using EndpointSlice objects\n* Added namespace to namespace-metadata resources in Helm (thanks @joebowbeer!)\n* Added support for Pod Security Admission (Pod Security Policy resources are\n  still supported but disabled by default)\n* Changed routes to be initialized lazily. Service Profile routes will no\n  longer show up in metrics until the route is used (default routes are always\n  available when no Service Profile is defined for a service)\n* Changed the proxy's behavior when traffic splitting so that only services\n  that are not in failfast are used. This will enable the proxy to manage\n  failover without external coordination\n* Updated tokio (async runtime) in the proxy which should reduce CPU usage,\n  especially for proxy's pod local (i.e in the same network namespace)\n  communication\n* Fixed an issue where `linkerd viz tap` would display wrong latency/duration\n  value (thanks @olegy2008!)\n\n## edge-22.12.1\n\nThis edge release introduces static and dynamic port overrides for CNI eBPF\nsocket-level load balancing. In certain installations when CNI plugins run in\neBPF mode, socket-level load balancing rewrites packet destinations to port\n6443; as with 443 already, this port is now skipped as well on control plane\ncomponents so that they can communicate with the Kubernetes API before their\nproxies are running.\n\nAdditionally, a potential panic and false warning have been fixed in the\ndestination controller.\n\n* Updated linkerd-jaeger's collector to expose port 4318 in order support HTTP\n  alongside gRPC (thanks @uralsemih!)\n* Added a `proxyInit.privileged` setting to control whether the `proxy-init`\n  initContainer runs as a privileged process\n* Fixed a potential panic in the destination controller caused by concurrent\n  writes when dealing with Endpoint updates\n* Fixed false warning when looking up HostPort mappings on Pods\n* Added static and dynamic port overrides for CNI eBPF to work with socket-level\n  load balancing\n\n## edge-22.11.3\n\nThis edge release fixes connection errors to pods that use `hostPort`\nconfigurations. The CNI `network-validator` init container features\nimproved error logging, and the default `linkerd-cni` DaemonSet\nconfiguration is updated to tolerate all node taints so that the CNI\nruns on all nodes in a cluster.\n\n* Fixed `destination` service to properly discover targets using a `hostPort`\n  different than their `containerPort`, which was causing 502 errors\n* Upgraded the `network-validator` with better logging allowing users to\n  determine whether failures occur as a result of their environment or the tool\n  itself\n* Added default `Exists` toleration to the `linkerd-cni` DaemonSet, allowing it\n  to be deployed in all nodes by default, regardless of taints\n\n## edge-22.11.2\n\nThis edge release introduces the use of the Kubernetes metadata API in the\nproxy-injector and tap-injector components. This can reduce the IO and memory\nfootprint for those components as they now only need to track the metadata for\ncertain resources, rather than the entire resource itself. Similar changes will\nbe made for the destination component in an upcoming release.\n\n* Bumped HTTP dependencies to fix a potential deadlock in HTTP/2 clients\n* Changed the proxy-injector and tap-injector components to use the metadata API\n  which should result in less memory consumption\n\n## edge-22.11.1\n\nThis edge releases ships a few fixes in Linkerd's dashboard, and the\nmulticluster extension. Additionally, a regression has been fixed in the CLI\nthat blocked upgrades from versions older than 2.12.0, due to missing CRDs\n(even if the CRDs were present in-cluster). Finally, the release includes\nchanges to the helm charts to allow for arbitrary (user-provided) labels on\nLinkerd workloads.\n\n* Fixed an issue in the CLI where upgrades from any version prior to\n  stable-2.12.0 would fail when using the `--from-manifest` flag\n* Removed un-injectable namespaces, such as kube-system from unmeshed resource\n  notification in the dashboard (thanks @MoSattler!)\n* Fixed an issue where the dashboard would respond to requests with 404 due to\n  wrong root paths in the HTML script (thanks @junnplus!)\n* Removed the proxyProtocol field in the multicluster gateway policy; this has\n  the effect of changing the protocol from 'HTTP/1.1' to 'unknown' (thanks\n  @psmit!)\n* Fixed the multicluster gateway UID when installing through the CLI, prior to\n  this change the 'runAsUser' field would be empty\n* Changed the helm chart for the control plane and all extensions to support\n  arbitrary labels on resources (thanks @bastienbosser!)\n\n## edge-22.10.3\n\nThis edge release adds `network-validator`, a new init container to be used when\nCNI is enabled. `network-validator` ensures that local iptables rules are\nworking as expected. It will validate this before linkerd-proxy starts.\n`network-validator` replaces the `noop` container, runs as `nobody`, and drops\nall capabilities before starting.\n\n* Validate CNI `iptables` configuration during pod startup\n* Fix \"cluster networks contains all services\" fails with services with no\n  ClusterIP\n* Remove kubectl version check from `linkerd check` (thanks @ziollek!)\n* Set `readOnlyRootFilesystem: true` in viz chart (thanks @mikutas!)\n* Fix `linkerd multicluster install` by re-adding `pause` container image\n  in chart\n* linkerd-viz have hardcoded image value in namespace-metadata.yml template\n  bug correction (thanks @bastienbosser!)\n\n## edge-22.10.2\n\nThis edge release fixes an issue with CNI chaining that was preventing the\nLinkerd CNI plugin from working with other CNI plugins such as Cilium. It also\nincludes several other fixes.\n\n* Updated Grafana dashboards to use variable duration parameter so that they can\n  be used when Prometheus has a longer scrape interval (thanks @TarekAS)\n* Fixed handling of .conf files in the CNI plugin so that the Linkerd CNI plugin\n  can be used alongside other CNI plugins such as Cilium\n* Added a `linkerd diagnostics policy` command to inspect Linkerd policy state\n* Added a check that ClusterIP services are in the cluster networks\n* Added a noop init container to injected pods when the CNI plugin is enabled\n  to prevent certain scenarios where a pod can get stuck without an IP address\n* Fixed a bug where the`config.linkerd.io/proxy-version` annotation could be empty\n\n## edge-22.10.1\n\nThis edge release fixes some sections of the Viz dashboard appearing blank, and\nadds an optional PodMonitor resource to the Helm chart to enable easier\nintegration with the Prometheus Operator. It also includes many fixes submitted\nby our contributors.\n\n* Fixed the dashboard sections Tap, Top, and Routes appearing blank (thanks\n  @MoSattler!)\n* Added an optional PodMonitor resource to the main Helm chart (thanks\n  @jaygridley!)\n* Fixed the CLI ignoring the `--api-addr` flag (thanks @mikutas!)\n* Expanded the `linkerd authz` command to display AuthorizationPolicy resources\n  that target namespaces (thanks @aatarasoff!)\n* Fixed the `NotIn` label selector operator in the policy resources, being\n  erroneously treated as `In`.\n* Fixed warning logic around the \"linkerd-viz ClusterRoles exist\" and\n  \"linkerd-viz ClusterRoleBindings exist\" checks in `linkerd viz check`\n* Fixed proxies emitting some duplicate inbound metrics\n\n## stable-2.12.1\n\nThis release includes several control plane and proxy fixes for `stable-2.12.0`.\nIn particular, it fixes issues related to control plane HTTP servers' header\nread timeouts resulting in decreased controller success rates, lowers the\ninbound connection pool idle timeout in the proxy, and fixes an issue where the\njaeger injector would put pods into an error state when upgrading from\nstable-2.11.x.\n\nAdditionally, this release adds the `linkerd.io/trust-root-sha256` annotation to\nall injected workloads allowing predictable comparison of all workloads' trust\nanchors via the Kubernetes API.\n\nFor Windows users, note that the Linkerd CLI's `nupkg` file for Chocolatey is\nonce again included in the release assets (it was previously removed in\nstable-2.10.0).\n\n* Proxy\n  * Lowered inbound connection pool idle timeout to 3s\n\n* Control Plane\n  * Updated AdmissionRegistration API version usage to v1\n  * Added `linkerd.io/trust-root-sha256` annotation on all injected workloads\n    to indicate certifcate bundle\n  * Updated fields in `AuthorizationPolicy` and `MeshTLSAuthentication` to\n    conform to specification (thanks @aatarasoff!)\n  * Updated the identity controller to not require a `ClusterRoleBinding`\n    to read all deployment resources\n  * Increased servers' header read timeouts so they no longer match default\n    probe and Prometheus scrape intervals\n\n* Helm\n  * Restored `namespace` field in Linkerd helm charts\n  * Updated `PodDisruptionBudget` `apiVersion` from `policy/v1beta1` to\n    `policy/v1` (thanks @Vrx555!)\n\n* Extensions\n  * Fixed jaeger injector interfering with upgrades to 2.12.x\n\n## edge-22.9.2\n\nThis release fixes an issue where the jaeger injector would put pods into an\nerror state when upgrading from stable-2.11.x.\n\n* Updated AdmissionRegistration API version usage to v1\n* Fixed jaeger injector interfering with upgrades to 2.12.x\n\n## edge-22.9.1\n\nThis release adds the `linkerd.io/trust-root-sha256` annotation to all injected\nworkloads allowing predictable comparison of all workloads' trust anchors via\nthe Kubernetes API.\n\nAdditionally, this release lowers the inbound connection pool idle timeout to\n3s. This should help avoid socket errors, especially for Kubernetes probes.\n\n* Added `linkerd.io/trust-root-sha256` annotation on all injected workloads\n  to indicate certifcate bundle\n* Lowered inbound connection pool idle timeout to 3s\n* Restored `namespace` field in Linkerd helm charts\n* Updated fields in `AuthorizationPolicy` and `MeshTLSAuthentication` to\n  conform to specification (thanks @aatarasoff!)\n* Updated the identity controller to not require a `ClusterRoleBinding`\n  to read all deployment resources.\n\n## edge-22.8.3\n\nIncreased control plane HTTP servers' read timeouts so that they no longer\nmatch the default probe intervals. This was leading to closed connections\nand decreased controller success rate.\n\n## stable-2.12.0\n\nThis release introduces route-based policy to Linkerd, allowing users to define\nand enforce authorization policies based on HTTP routes in a fully zero-trust\nway. These policies are built on Linkerd's strong workload identities, secured\nby mutual TLS, and configured using types from the Kubernetes [Gateway\nAPI](https://gateway-api.sigs.k8s.io/).\n\nThe 2.12 release also introduces optional request logging (\"access logging\"\nafter its name in webservers), optional support for `iptables-nft`, and a host\nof other improvements and performance enhancements.\n\nAdditionally, the `linkerd-smi` extension is now required to use TrafficSplit,\nand the installation process has been updated to separate management of the\nLinkerd CRDs from the main installation process. With the CLI, you'll need to\n`linkerd install --crds` before running `linkerd install`; with Helm, you'll\ninstall the new `linkerd-crds` chart, then the `linkerd-control-plane` chart.\nThese charts are now versioned using [SemVer](https://semver.org) independently\nof Linkerd releases. For more information, see the [upgrade\nnotes][upgrade-2120].\n\n**Upgrade notes**: Please see the [upgrade instructions][upgrade-2120].\n\n* Proxy\n  * Added a `config.linkerd.io/shutdown-grace-period` annotation to limit the\n    duration that the proxy may wait for graceful shutdown\n  * Added a `config.linkerd.io/access-log` annotation to enable logging of\n    workload requests\n  * Added a new `iptables-nft` mode for the `proxy-init` initContainer\n  * Added support for non-HTTP traffic forwarding within the mesh in `ingress`\n    mode\n  * Added the `/env.json` log diagnostic endpoint\n  * Added a new `process_uptime_seconds_total` metric to track proxy uptime in\n    seconds\n  * Added support for dynamically discovering policies for ports that are not\n    documented in a pod's `containerPorts`\n  * Added support for route-based inbound HTTP metrics\n    (`route_group`/`route_kind`/`route_name`)\n  * Added a new annotation to configure skipping subnets in the init container\n    (`config.linkerd.io/skip-subnets`), needed e.g. in Docker-in-Docker\n    workloads (thanks @michaellzc!)\n\n* Control Plane\n  * Added support for per-route policy by supporting AuthorizationPolicy\n    resources which can target HttpRoute or Server resources\n  * Added support for bound service account token volumes for the control plane\n    and injected workloads\n  * Removed kube-system exclusions from watchers to fix service discovery for\n    workloads in the kube-system namespace (thanks @JacobHenner!)\n  * Updated healthcheck to ignore `Terminated` state for pods (thanks\n    @AgrimPrasad!)\n  * Updated the default policy controller log level to `info`; the controller\n    will now emit INFO level logs for some of its dependencies\n  * Added probe authorization by default, allowing clusters that use a default\n    `deny` policy to not explicitly need to authorize probes\n  * Fixed an issue where the proxy-injector would break when using\n    `nodeAffinity` values for the control plane\n  * Fixed an issue where certain control plane components were not restarting as\n    necessary after a trust root rotation\n  * Removed SMI functionality in the default Linkerd installation; this is now\n    part of the `linkerd-smi` extension\n\n* CLI\n  * Fixed the `linkerd check` command crashing when unexpected pods are found in\n    a Linkerd namespace\n  * Updated the `linkerd authz` command to support AuthorizationPolicy and\n    HttpRoute resources\n  * Updated `linkerd check` to allow RSA signed trust anchors (thanks\n    @danibaeyens!)\n  * `linkerd install --crds` must be run before `linkerd install`\n  * `linkerd upgrade --crds` must be run before `linkerd upgrade`\n  * Fixed invalid yaml syntax in the viz extension's tap-injector template\n    (thanks @wc-s!)\n  * Fixed an issue where the `--default-inbound-policy` setting was not being\n    respected\n  * Added support for AuthorizationPolicy and HttpRoute to `viz authz` command\n  * Added support for AuthorizationPolicy and HttpRoute to `viz stat` command\n  * Added support for policy metadata in `linkerd viz tap`\n\n* Helm\n  * Split the `linkerd2` chart into `linkerd-crds` and `linkerd-control-plane`\n  * Charts are now versioned using [SemVer](https://semver.org) independently of\n    Linkerd releases\n  * Added missing port in the Linkerd viz chart documentation (thanks @haswalt!)\n  * Changed the `proxy.await` Helm value so that users can now disable\n    `linkerd-await` on control plane components\n  * Added the `policyController.probeNetworks` Helm value for configuring the\n    networks that probes are expected to be performed from\n\n* Extensions\n  * Added annotations to allow Linkerd extension deployments to be evicted by\n    the autoscaler when necessary\n  * Added ability to run the Linkerd CNI plugin in non-chained (stand-alone)\n    mode\n  * Added a ServiceAccount token Secret to the multicluster extension to support\n    Kubernetes versions >= v1.24\n\nThis release includes changes from a massive list of contributors, including\nengineers from Adidas, Intel, Red Hat, Shopify, Sourcegraph, Timescale, and\nothers. A special thank-you to everyone who helped make this release possible:\n\nAgrim Prasad [@AgrimPrasad](https://github.com/AgrimPrasad)\nAhmed Al-Hulaibi [@ahmedalhulaibi](https://github.com/ahmedalhulaibi)\nAleksandr Tarasov [@aatarasoff](https://github.com/aatarasoff)\nAlexander Berger [@alex-berger](https://github.com/alex-berger)\nAo Chen [@chenaoxd](https://github.com/chenaoxd)\nBadis Merabet [@badis](https://github.com/badis)\nBjørn [@Crevil](https://github.com/Crevil)\nBrian Dunnigan [@bdun1013](https://github.com/bdun1013)\nChristian Schlotter [@chrischdi](https://github.com/chrischdi)\nDani Baeyens [@danibaeyens](https://github.com/danibaeyens)\nDavid Symons [@multimac](https://github.com/multimac)\nDmitrii Ermakov [@ErmakovDmitriy](https://github.com/ErmakovDmitriy)\nElvin Efendi [@ElvinEfendi](https://github.com/ElvinEfendi)\nEvan Hines [@evan-hines-firebolt](https://github.com/evan-hines-firebolt)\nEng Zer Jun [@Juneezee](https://github.com/Juneezee)\nGustavo Fernandes de Carvalho [@gusfcarvalho](https://github.com/gusfcarvalho)\nHarry Walter [@haswalt](https://github.com/haswalt)\nIsrael Miller [@imiller31](https://github.com/imiller31)\nJack Gill [@jackgill](https://github.com/jackgill)\nJacob Henner [@JacobHenner](https://github.com/JacobHenner)\nJacob Lorenzen [@Jaxwood](https://github.com/Jaxwood)\nJoakim Roubert [@joakimr-axis](https://github.com/joakimr-axis)\nJosh Ault [@jault-figure](https://github.com/jault-figure)\nJoão Soares [@jasoares](https://github.com/jasoares)\njtcarnes [@jtcarnes](https://github.com/jtcarnes)\nKim Christensen [@kichristensen](https://github.com/kichristensen)\nKrzysztof Dryś [@krzysztofdrys](https://github.com/krzysztofdrys)\nLior Yantovski [@lioryantov](https://github.com/lioryantov)\nMartin Anker Have [@mahlunar](https://github.com/mahlunar)\nMichael Lin [@michaellzc](https://github.com/michaellzc)\nMichał Romanowski [@michalrom089](https://github.com/michalrom089)\nNaveen Nalam [@nnalam](https://github.com/nnalam)\nNick Calibey [@ncalibey](https://github.com/ncalibey)\nNikola Brdaroski [@nikolabrdaroski](https://github.com/nikolabrdaroski)\nOr Shachar [@or-shachar](https://github.com/or-shachar)\nPål-Magnus Slåtto [@dev-slatto](https://github.com/dev-slatto)\nRaman Gupta [@rocketraman](https://github.com/rocketraman)\nRicardo Gândara Pinto [@rmgpinto](https://github.com/rmgpinto)\nRoberth Strand [@roberthstrand](https://github.com/roberthstrand)\nSankalp Rangare [@sankalp-r](https://github.com/sankalp-r)\nSascha Grunert [@saschagrunert](https://github.com/saschagrunert)\nSteve Gray [@steve-gray](https://github.com/steve-gray)\nSteve Zhang [@zhlsunshine](https://github.com/zhlsunshine)\nTakumi Sue [@mikutas](https://github.com/mikutas)\nTanmay Bhat [@tanmay-bhat](https://github.com/tanmay-bhat)\nTáskai Dominik [@dtaskai](https://github.com/dtaskai)\nUjjwal Goyal [@importhuman](https://github.com/importhuman)\nWeichung Shaw [@wc-s](https://github.com/wc-s)\nWim de Groot [@wim-de-groot](https://github.com/wim-de-groot)\nYannick Utard [@utay](https://github.com/utay)\nYurii Dzobak [@yuriydzobak](https://github.com/yuriydzobak)\n罗泽轩 [@spacewander](https://github.com/spacewander)\n\n[upgrade-2120]: https://linkerd.io/2/tasks/upgrade/#upgrade-notice-stable-2120\n\n## stable-2.12.0-rc2\n\nThis release is the second release candidate for stable-2.12.0.\n\nAt this point the Helm charts can be retrieved from the stable repo:\n\n```sh\nhelm repo add linkerd https://helm.linkerd.io/stable\nhelm repo up\nhelm install linkerd-crds -n linkerd --create-namespace linkerd/linkerd-crds\nhelm install linkerd-control-plane \\\n  -n linkerd \\\n  --set-file identityTrustAnchorsPEM=ca.crt \\\n  --set-file identity.issuer.tls.crtPEM=issuer.crt \\\n  --set-file identity.issuer.tls.keyPEM=issuer.key \\\n  linkerd/linkerd-control-plane\n```\n\nThe following lists all the changes since edge-22.8.2:\n\n* Fixed inheritance of the `linkerd.io/inject` annotation from Namespace to\n  Workloads when its value is `ingress`\n* Added the `config.linkerd.io/default-inbound-policy: all-authenticated`\n  annotation to linkerd-multicluster’s Gateway deployment so that all clients\n  are required to be authenticated\n* Added a `ReadHeaderTimeout` of 10s to all the go `http.Server` instances, to\n  avoid being vulnerable to \"slowrolis\" attacks\n* Added check in `linkerd viz check --proxy` to warn in case namespace have the\n  `config.linkerd.io/default-inbound-policy: deny` annotation, which would not\n  authorize scrapes coming from the linkerd-viz Prometheus instance\n* Added validation for accepted values for the `--default-inbound-policy` flag\n* Fixed invalid URL in the `linkerd install --help` output\n* Added `--destination-pod` flag to `linkerd diagnostics endpoints` subcommand\n* Added `proxyInit.runAsUser` in `values.yaml` defaulting to non-zero, to\n  complement the new default `proxyInit.runAsRoot: false` that was rencently\n  changed\n\n## edge-22.8.2\n\nThis release is considered a release candidate for stable-2.12.0 and we\nencourage you to try it out! It includes an update to the multicluster extension\nwhich adds support for Kubernetes v1.24 and also updates many CLI commands to\nsupport the new policy resources: ServerAuthorization and HTTPRoute.\n\n* Updated linkerd check to allow RSA signed trust anchors (thanks @danibaeyens!)\n* Fixed some invalid yaml in the viz extension's tap-injector template (thanks @wc-s!)\n* Added support for AuthorizationPolicy and HttpRoute to viz authz command\n* Added support for AuthorizationPolicy and HttpRoute to viz stat\n* Added support for policy metadata in linkerd tap\n* Fixed an issue where certain control plane components were not restarting as\n  necessary after a trust root rotation\n* Added a ServiceAccount token Secret to the multicluster extension to support\n  Kubernetes versions >= v1.24\n* Fixed an issue where the --default-inbound-policy setting was not being\n  respected\n\n## edge-22.8.1\n\nThis releases introduces default probe authorization. This means that on\nclusters that use a default `deny` policy, probes do not have to be explicitly\nauthorized using policy resources. Additionally, the\n`policyController.probeNetworks` Helm value has been added, which allows users\nto configure the networks that probes are expected to be performed from.\n\nAdditionally, the `linkerd authz` command has been updated to support the policy\nresources AuthorizationPolicy and HttpRoute.\n\nFinally, some smaller changes include allowing to disable `linkerd-await` on\ncontrol plane components (using the existing `proxy.await` configuration) and\nchanging the default iptables mode back to `legacy` to support more cluster\nenvironments by default.\n\n* Updated the `linkerd authz` command to support AuthorizationPolicy and\n  HttpRoute resources\n* Changed the `proxy.await` Helm value so that users can now disable\n  `linkerd-await` on control plane components\n* Added probe authorization by default allowing clusters that use a default\n  `deny` policy to not explicitly need to authorize probes\n* Added ability to run the Linkerd CNI plugin in non-chained (stand-alone) mode\n* Added the `policyController.probeNetworks` Helm value for configuring the\n  networks that probes are expected to be performed from\n* Changed the default iptables mode to `legacy`\n\n## edge-22.7.3\n\nThis release adds a new `nft` iptables mode, used by default in proxy-init.\nWhen used, firewall configuration will be set-up through the `iptables-nft`\nbinary; this should allow hosts that do not support `iptables-legacy` (such as\nRHEL based environments) to make use of the init container. The older\n`iptables-legacy` mode is still supported, but it must be explictly turned on.\nMoreover, this release also replaces the `HTTPRoute` CRD with Linkerd's own\nversion, and includes a number of fixes and improvements.\n\n* Added a new `iptables-nft` mode for proxy-init. When running in this mode,\n  the firewall will be configured with `nft` kernel API; this should allow\n  users to run the init container on RHEL-family hosts\n* Fixed an issue where the proxy-injector would break when using `nodeAffinity`\n  values for the control plane\n* Updated healthcheck to ignore `Terminated` state for pods (thanks\n  @AgrimPrasad!)\n* Replaced `HTTRoute` CRD version from `gateway.networking.k8s.io` with a\n  similar version from the `policy.linkerd.io` API group. While the CRD is\n  similar, it does not support the `Gateway` type, does not contain the\n  `backendRefs` fields, and does not support `RequestMirror` and `ExtensionRef`\n  filter types.\n* Updated the default policy controller log level to `info`; the controller\n  will now emit INFO level logs for some of its dependencies\n* Added validation to ensure `HTTPRoute` paths are absolute; relative paths are\n  not supported by the proxy and the policy controller admission server will\n  reject any routes that use paths which do not start with `/`\n\n## edge-22.7.2\n\nThis release adds support for per-route authorization policy using the\nAuthorizationPolicy and HttpRoute resources. It also adds a configurable\nshutdown grace period to the proxy which can be used to ensure that proxy\ngraceful shutdown completes within a certain time, even if there are outstanding\nopen connections.\n\n* Removed kube-system exclusions from watchers to fix service discovery for\n  workloads in the kube-system namespace (thanks @JacobHenner!)\n* Added annotations to allow Linkerd extension deployments to be evicted by the\n  autoscaler when necessary\n* Added missing port in the Linkerd viz chart documentation (thanks @haswalt!)\n* Added support for per-route policy by supporting AuthorizationPolicy resources\n  which target HttpRoute resources\n* Fixed the `linkerd check` command crashing when unexpected pods are found in\n  a Linkerd namespace\n* Added a `config.linkerd.io/shutdown-grace-period` annotation to configure the\n  proxy's maximum grace period for graceful shutdown\n\n## edge-22.7.1\n\nThis release includes a security improvement. When a user manually specified the\n`policyValidator.keyPEM` setting, the value was incorrectly included in the\n`linkerd-config` configmap. This means that this private key was erroneously\nexposed to service accounts with read access to this configmap. Practically,\nthis means that the Linkerd `proxy-injector`, `identity`, and `heartbeat` pods\ncould read this value. This should **not** have exposed this private key to\nother unauthorized users unless additional role bindings were added outside of\nLinkerd. Nevertheless, we recommend that users who manually set control plane\ncertificates update the credentials for the policy validator after upgrading\nLinkerd.\n\nAdditionally, the linkerd-multicluster extensions has several fixes related to\nfail fast errors during link watch restarts, improper label matching for\nmirrored services, and properly cleaning up mirrored endpoints in certain\nsituations.\n\nLastly, the proxy can now retry gRPC requests that have responses with a\nTRAILERS frame. A fix to reduce redundant load balancer updates should also\nresult in less connection churn.\n\n* Changed unit tests to use newly introduced `prommatch` package for asserting\n  expected metrics (thanks @krzysztofdrys!)\n* Fixed Docker container runtime check to only during `linkerd install` rather\n  than `linkerd check --pre`\n* Changed linkerd-multicluster's remote cluster watcher to assume the gateway is\n  alive when starting—fixing fail fast errors from occurring during restarts\n  (thanks @chenaoxd!)\n* Added `matchLabels` and `matchExpressions` to linkerd-multicluster's Link CRD\n* Fixed linkerd-multicluster's label selector to properly select resources that\n  match the expected label value, rather than just the presence of the label\n* Fixed linkerd-multicluster's cluster watcher to properly clean up endpoints\n  belonging to remote headless services that are no longer mirrored\n* Added the HttpRoute CRD which will be used by future policy features\n* Fixed CNI plugin event processing where file updates could sometimes be\n  skipped leading to the update not being acknowledged\n* Fixed redundant load balancer updates in the proxy that could cause\n  unnecessary connection churn\n* Fixed gRPC request retries for responses that contain a TRAILERS frame\n* Fixed the dashboard's `linkerd check` due to missing RBAC for listing pods in\n  the cluster\n* Fixed API check that ensures access to the Server CRD (thanks @aatarasoff!)\n* Changed `linkerd authz` to match the labels of pre-fetched Pods rather than\n  the multiple API calls it was doing—resulting in significant speed-up (thanks\n  @aatarasoff!)\n* Unset `policyValidtor.keyPEM` in `linkerd-config` ConfigMap\n\n## edge-22.6.2\n\nThis edge release bumps the minimum supported Kubernetes version from `v1.20`\nto `v1.21`, introduces some new changes, and includes a few bug fixes. Most\nnotably, a bug has been fixed in the proxy's outbound load balancer that could\ncause panics, especially when the balancer would process many service discovery\nupdates in a short period of time. This release also fixes a panic in the\nproxy-injector, and introduces a change that will include HTTP probe ports in\nthe proxy's inbound ports configuration, to be used for policy discovery.\n\n* Fixed a bug in the proxy's outbound load balancer that could cause panics\n  when many discovery updates were processed in short time periods\n* Added `runtimeClassName` options to Linkerd's Helm chart (thanks @jtcarnes!)\n* Introduced a change in the proxy-injector that will configure the inbound\n  ports proxy configuration with the pod's probe ports (HTTPGet)\n* Added godoc links in the project README file (thanks @spacewander!)\n* Increased minimum supported Kubernetes version to `v1.21` from `v1.20`\n* Fixed an issue where the proxy-injector would not emit events for resources\n  that receive annotation patches but are skipped for injection\n* Refactored `PublicIPToString` to handle both IPv4 and IPv6 addresses in a\n  similar behavior (thanks @zhlsunshine!)\n* Replaced the usage of branch with tags, and pinned `cosign-installer` action\n  to `v1` (thanks @saschagrunert!)\n* Fixed an issue where the proxy-injector would panic if resources have an\n  unsupported owner kind\n\n## edge-22.6.1\n\nThis edge release fixes an issue where Linkerd injected pods could not be\nevicted by Cluster Autoscaler. It also adds the `--crds` flag to `linkerd check`\nwhich validates that the Linkerd CRDs have been installed with the proper\nversions.\n\nThe previously noisy \"cluster networks can be verified\" check has been replaced\nwith one that now verifies each running Pod IP is contained within the current\n`clusterNetworks` configuration value.\n\nAdditionally, linkerd-viz is no longer required for linkerd-multicluster's\n`gateways` command — allowing the `Gateways` API to marked as deprecated for\n2.12.\n\nFinally, several security issues have been patched in the Docker images now that\nthe builds are pinned only to minor — rather than patch — versions.\n\n* Replaced manual IP address parsing with functions available in the Go standard\n  library (thanks @zhlsunshine!)\n* Removed linkerd-multicluster's `gateway` command dependency on the linkerd-viz\n  extension\n* Fixed issue where Linkerd injected pods were prevented from being evicted by\n  Cluster Autoscaler\n* Added the `dst_target_cluster` metric to linkerd-multicluster's service-mirror\n  controller probe traffic\n* Added the `--crds` flag to `linkerd check` which validates that the Linkerd\n  CRDs have been installed\n* Removed the Docker image's hardcoded patch versions so that builds pick up\n  patch releases without manual intervention\n* Replaced the \"cluster networks can be verified check\" check with a \"cluster\n  networks contains all pods\" check which ensures that all currently running Pod\n  IPs are contained by the current `clusterNetworks` configuration\n* Added IPv6 compatible IP address generation in certain control plane\n  components that were only generating IPv4 (thanks @zhlsunshine!)\n* Deprecated linkerd-viz's `Gateways` API which is no longer used by\n  linkerd-multicluster\n* Added the `promm` package for making programatic Prometheus assertions in\n  tests (thanks @krzysztofdrys!)\n* Added the `runAsUser` configuration to extensions to fix a PodSecurityPolicy\n  violation when CNI is enabled\n\n## edge-22.5.3\n\nThis edge release fixes a few proxy issues, improves the upgrade process, and\nintroduces proto retries to Service Profiles. Also included are updates to the\nbash scripts to ensure that they follow best practices.\n\n* Polished the shell scripts (thanks @joakimr-axis)\n* Introduced retries to Service Profiles based on the idempotency option of the\n  method by adding an isRetryable function to the proto definition\n (thanks @mahlunar)\n* Fixed proxy responses to CONNECT requests by removing the content-length\n  and/or transfer-encoding headers from the response\n* Fixed DNS lookups in the proxy to consistently use A records when SRV records\n  cannot be resolved\n* Added dynamic policy discovery to the proxy by evaluating traffic on ports\n  not included in the LINKERD2_PROXY_INBOUND_PORTS environment variable\n* Added logic to require that the linkerd CRDs are installed when running\n  the `linkerd upgrade` command\n\n## edge-22.5.2\n\nThis edge release ships a few changes to the chart values, a fix for\nmulticluster headless services, and notable proxy features. HA functionality,\nsuch as PDBs, deployment strategies, and pod anti-affinity, have been split\nfrom the HA values and are now configurable for the control plane. On the proxy\nside, non-HTTP traffic will now be forwarded on the outbound side within the\ncluster when the proxy runs in ingress mode.\n\n* Updated `ingress-mode` proxies to forward non-HTTP traffic within the cluster\n  (protocol detection will always be attempted for outbound connections)\n* Added a new proxy metric `process_uptime_seconds_total` to keep track of the\n  number of seconds since the proxy started\n* Fixed an issue with multicluster headless service mirroring, where exported\n  endpoints would be mirrored with a delay, or when changes to the export label\n  would be ignored\n* Split HA functionality, such as PodDisruptionBudgets, into multiple\n  configurable values (thanks @evan-hines-firebolt for the initial work)\n\n## edge-22.5.1\n\nThis edge release adds more flexibility to the MeshTLSAuthentication and\nAuthorizationPolicy policy resources by allowing them to target entire\nnamespaces. It also fixes a race condition when multiple CNI plugins are\ninstalled together as well as a number of other bug fixes.\n\n* Added support for MeshTLSAuthentication resources to target an entire\n  namespace, authenticating all ServiceAccounts in that namespace\n* Fixed a panic in `linkerd install` when the `--ignore-cluster` flag is passed\n* Fixed issue where pods would fail to start when `enablePSP` and\n  `proxyInit.runAsRoot` are set\n* Added support for AuthorizationPolicy resources to target namespaces, applying\n  to all Servers in that namespace\n* Fixed a race condition where the Linkerd CNI configuration could be\n  overwritten when multiple CNI plugins are installed\n* Added test for opaque ports using Service and Pod IPs (thanks @krzysztofdrys!)\n* Fixed an error in the linkerd-viz Helm chart in HA mode\n\n## edge-22.4.1\n\nIn order to support having custom resources in the default Linkerd installation,\nthe CLI install flow is now always a 2-step process where `linkerd install\n--crds` must be run first to install CRDs only and then `linkerd install` is run\nto install everything else. This more closely aligns the CLI install flow with\nthe Helm install flow where the CRDs are a separate chart. This also applies to\n`linkerd upgrade`. Also, the `config` and `control-plane` sub-commands have been\nremoved from both `linkerd install` and `linkerd upgrade`.\n\nOn the proxy side, this release fixes an issue where proxies would not honor the\ncluster's opaqueness settings for non-pod/service addresses. This could cause\nprotocol detection to be peformed, for instance, when using off-cluster\ndatabases.\n\nThis release also disables the use of regexes in Linkerd log filters (i.e., as\nset by `LINKERD2_PROXY_LOG`). Malformed log directives could, in theory, cause a\nproxy to stop responding.\n\nThe `helm.sh/chart` label in some of the CRDs had its formatting fixed, which\navoids issues when installing/upgrading through external tools that make use of\nit, such as recent versions of Flux.\n\n* Added `--crds` flag to install/upgrade and remove config/control-plane stages\n* Allowed the `AuthorizationPolicy` CRD to have an empty\n  `requiredAuthenticationRefs` entry that allows all traffic\n* Introduced `nodeAffinity` config in all the charts for enhanced control on the\n  pods scheduling (thanks @michalrom089!)\n* Introduced `resources`, `nodeSelector` and `tolerations` configs in the\n  `linkerd-multicluster-link` chart for enhanced control on the service mirror\n  deployment (thanks @utay!)\n* Fixed formatting of the `helm.sh/chart` label in CRDs\n* Updated container base images from buster to bullseye\n* Added support for spaces in the `config.linkerd.io/opaque-ports` annotation\n\n## edge-22.3.5\n\nThis edge release introduces new policy CRDs that allow for more generalized\nauthorization policies.\n\nThe `AuthorizationPolicy` CRD authorizes clients that satisfy all the required\nauthentications to communicate with the Linkerd `Server` that it targets.\nRequired authentications are specified through the new `MeshTLSAuthentication`\nand `NetworkAuthentication` CRDs.\n\nA `MeshTLSAuthentication` defines a list of authenticated client IDs—specified\ndirectly by proxy identity strings or referencing resources such as\n`ServiceAccount`s.\n\nA `NetworkAuthentication` defines a list of client networks that will be\nauthenticated.\n\nAdditionally, to support the new CRDs, policy-related labels have been changed\nto better categorize policy metrics. A `srv_kind` label has been introduced\nwhich splits the current `srv_name` value—formatted as `kind:name`—into separate\nlabels. The `saz_name` label has been removed and is replaced by the new\n`authz_kind` and `authz_name` labels.\n\n* Introduced the `srv_kind` label which allowed splitting the value of the\n  current `srv_name` label\n* Removed the `saz_name` label and replaced it with the new `authz_kind` and\n  `authz_name` labels\n* Fixed an issue in the destination controller where an update would not be sent\n  after an endpoint was discovered for a currently empty service\n* Introduced the following custom resource types to support generalized\n  authorization policies: `AuthorizationPolicy`, `MeshTLSAuthentication`,\n  `NetworkAuthentication`\n* Deprecated the `--proxy-version` flag (thanks @importhuman!)\n* Updated linkerd-viz to use new policy CRDs\n\n## edge-22.3.4\n\n* Disabled pprof endpoints on Linkerd control plane components by default\n* Fixed an issue where mirror service endpoints of headless services were always\n  ready regardless of gateway liveness\n* Added server side validation for ServerAuthorization resources\n* Fixed an \"origin not allowed\" issue when using the latest Grafana with the\n  Linkerd Viz extension\n\n## edge-22.3.3\n\nThis edge release ensures that in multicluster installations, mirror service\nendpoints have their readiness tied to gateway liveness. When the gateway for a\ntarget cluster is not alive, the endpoints that point to it on a source cluster\nwill properly indicate that they are not ready.\n\n* Fixed tap controller logging errors that were succeptible to log forgery by\n  ensuring special characters are escaped\n* Fixed issue where mirror service endpoints were always ready regardless of\n  gateway liveness\n* Removed unused `namespace` entry in `linkerd-control-plane` chart\n\n## edge-22.3.2\n\nThis edge release includes a few fixes and quality of life improvements. An\nissue has been fixed in the proxy allowing HTTP Upgrade requests to work\nthrough multi-cluster gateways, and the init container's resource limits and\nrequests have been revised. Additionally, more Go linters have been enabled and\nimprovements have been made to the devcontainer.\n\n* Changed `linkerd-init` resource (CPU/memory) limits and requests to ensure by\n  default the init container does not break a pod's `Guaranteed` QOS class\n* Added a new check condition to skip pods whose status is `NodeShutdown`\n  during validation as they will not have a proxy container\n* Fixed an issue that would prevent proxies from sending HTTP Upgrade requests\n  (used in websockets) through multi-cluster gateways\n\n## edge-22.3.1\n\nThis edge release includes updates to dependencies, CI, and rust 1.59.0. It also\nincludes changes to the `linkerd-jaeger` chart to ensure that namespace labels\nare preserved and adds support for `imagePullSecrets`, along with improvements\nto the multicluster and policy functionality.\n\n* Added note to `multicluster link` command to clarify that the link is\n  one-direction\n* Introduced `imagePullSecrets` to Jaeger Helm chart\n* Updated Rust to v1.59.0\n* Fixed a bug where labels can be overwritten in the `linkerd-jaeger` chart\n* Fix broken mirrored headles services after `repairEndpoints` runs\n* Updated `Server` CRD to handle an empty `PodSelector`\n\n## edge-22.2.4\n\nThis edge release continues to address several security related lints and\nensures they are checked by CI.\n\n* Add `linkerd check` warning for clusters that cannot verify their\n  `clusterNetworks` due to Nodes missing the `podCIDR` field\n* Changed `Server` CRD to allow having an empty `PodSelector`\n* Modified `linkerd inject` to only support `https` URLs to mitigate security\n  risks\n* Fixed potential goroutine leak in the port forwarding used by several CLI\n  commands and control plane components\n* Fixed timeouts in the policiy validator which could lead to failures if\n  `failurePolicy` was set to `Fail`\n\n## edge-22.2.3\n\nThis edge release fixes some `Instant`-related proxy panics that occur on Amazon\nLinux. It also includes many behind the scenes improvements to the project's\nCI and linting.\n\n* Removed the `--controller-image-version` install flag to simplify the way that\n  image versions are handled. The controller image version can be set using the\n  `--set linkerdVersion` flag or Helm value\n* Lowercased logs and removed redundant lines from the Linkerd2 proxy init\n  container\n* Prevented the proxy from logging spurious errors when its pod does not define\n  any container ports\n* Added workarounds to reduce the likelihood of `Instant`-related proxy panics\n  that occur on Amazon Linux\n\n## edge-22.2.2\n\nThis edge release updates the jaeger extension to be available in ARM\narchitectures and applies some security-oriented amendments.\n\n* Upgraded jaeger and the opentelemetry-collector to their latest versions,\n  which now support ARM architectures\n* Fixed `linkerd multicluster check` which was reporting false warnings\n* Started enforcing TLS v1.2 as a minimum in the webhook servers\n* Had the identity controller emit SHA256 certificate fingerprints in its\n  logs/events, instead of MD5\n\n## edge-22.2.1\n\nThis edge release removed the `disableIdentity` configuration now that the proxy\nno longer supports running without identity.\n\n* Added a `privileged` configuration to linkerd-cni which is required by some\n  environments\n* Fixed an issue where the TLS credentials used by the policy validator were not\n  updated when the credentials were rotated\n* Removed the `disableIdentity` configurations now that the proxy no longer\n  supports running without identity\n* Fixed an issue where `linkerd jaeger check` would needlessly fail for BYO\n  Jaeger or collector installations\n* Fixed a Helm HA installation race condition introduced by the stoppage of\n  namespace creation\n\n## edge-22.1.5\n\nThis edge release adds support for per-request Access Logging for HTTP inbound\nrequests in Linkerd. A new annotation i.e. `config.linkerd.io/access-log` is added,\nwhich configures the proxies to emit access logs to stderr. `apache` and `json`\nare the supported configuration options, emitting access logs in Apache Common\nLog Format and JSON respectively.\n\nSpecial thanks to @tustvold for all the initial work around this!\n\n* Updated injector to support the new `config.linkerd.io/access-log` annotation\n* Added a new `LINKERD2_PROXY_ACCESS_LOG` proxy environment variable to configure\n  the access log format (thanks @tustvold)\n* Updated service mirror controller to emit relevant events when\n  mirroring is skipped for a service\n* Updated various dependencies across the project (thanks @dependabot)\n\n## edge-22.1.4\n\nThis edge release features a new configuration annotation, support for\nexternally hosted Grafana instances, and other improvements in the CLI,\ndashboard and Helm charts. To learn more about using an external Grafana\ninstance with Linkerd, you can refer to our\n[docs](https://github.com/linkerd/website/blob/0c3c5cd5ae329cd7dbcca18534f3bc8ec7d57859/linkerd.io/content/2.12/tasks/grafana.md).\n\n* Added a new annotation to configure skipping subnets in the init container\n  (`config.linkerd.io/skip-subnets`). This configuration option is ideal for\n  Docker-in-Docker (dind) workloads (thanks @michaellzc!)\n* Added support in the dashboard for externally hosted Grafana instances\n  (thanks @jackgill!)\n* Introduced resource block to `linkerd-jaeger` Helm chart (thanks\n  @yuriydzobak!)\n* Introduced parametrized datasource (`DS_PROMETHEUS`) in all Grafana\n  dashboards. This allows pointing to the right Prometheus datasource when\n  importing a dashboard\n* Introduced a consistent `--ignore-cluster` flag in the CLI for the base\n  installation and extensions; manifests will now be rendered even if there is\n  an existing installation in the current Kubernetes context (thanks\n  @krzysztofdrys!)\n* Updated the service mirror controller to skip mirroring services whose\n  namespaces do not yet exist in the source cluster; previously, the service\n  mirror would create the namespace itself.\n\n## edge-22.1.3\n\nThis release removes the Grafana component in the linkerd-viz extension.\nUsers can now import linkerd dashboards into Grafana from the [Linkerd org](https://grafana.com/orgs/linkerd)\nin Grafana. Users can also follow the instructions in the [docs](https://github.com/linkerd/website/blob/f687a04ee43c90bd804b04af287bc80c9366db98/linkerd.io/content/2.12/tasks/grafana.md)\nto install a separate Grafana that can be integrated with the Linkerd Dashboard.\n\n* Stopped shipping grafana-based image in the linkerd-viz extension\n* Removed `repair` sub-command in the CLI\n* Updated various dependencies across the project (thanks @dependabot)\n\n## edge-22.1.2\n\nThis release sets the version of the extension Helm charts to 30.0.0-edge to\nensure that previous versions of these charts can be upgraded properly.\n\n* Reset extensions Helm chart versions at 30.0.0-edge\n* Pin multicluster extension pause container version to 3.2 so that it will work\n  on Arm architectures\n* Create a unique PSP `RoleBinding` for each multicluster link to prevent\n  conflicts when PSP is enabled\n\n## edge-22.1.1\n\nThis release adds support for using the cert-manager CA Injector to configure\nLinkerd's webhooks.\n\n* Fixed a rare issue when a Service's opaque ports annotation does not match\n  that of the pods in the service\n* Disallowed privilege escalation in control plane containers (thanks @kichristensen!)\n* Updated the multicluster extension's service mirror controller to make mirror\n  services empty when the exported service is empty\n* Added support for injecting Webhook CA bundles with cert-manager CA Injector\n  (thanks @bdun1013!)\n\n## edge-21.12.4\n\nThis release adds support for custom HTTP methods in the viz stats\n(i.e CLI and Dashboard). Additionally, it also includes various\nsmaller improvements.\n\n* Added support for custom HTTP methods in the `linkerd-viz` stats\n* Updated the health checker to pull trust root from the `linkerd-identity-trust-roots`\n  configmap to support cases where they are generated externally (thanks @wim-de-groot)\n* Removed unnecessary `installNamespace` bool flag from the\n  `linkerd-control-plane` chart (thanks @mikutas)\n* Updated the `install` command to error if container runtime check fails\n* Updated various dependencies across the project (thanks @dependabot)\n\n## edge-21.12.3\n\nThis edge release contains a few improvements to the CLI commands and a major\nchange around Helm charts.\n\n* **Breaking change**\n\nThe `linkerd2` chart has been deprecated in favor of the `linkerd-crds` and\n`linkerd-control-plane` charts. The former takes care of installing all the\nrequired CRDs and the latter everything else. Of important note is that, as per\nHelm best practice, we're no longer creating the linkerd namespace. Users\nrequire to do that manually, or have the Helm tool do it explicitly. So the\ninstall procedure would look something like this:\n\n```bash\nhelm install linkerd-crds -n linkerd --create-namespace linkerd/linkerd-crds\n\nhelm install linkerd-control-plane -n linkerd \\\n  --set-file identityTrustAnchorsPEM=ca.crt \\\n  --set-file identity.issuer.tls.crtPEM=issuer.crt \\\n  --set-file identity.issuer.tls.keyPEM=issuer.key \\\n  linkerd/linkerd-control-plane\n```\n\nIn order to upgrade, please delete your previously installed `linkerd2` chart\nand install the new charts as explained above.\n\nAlthough the charts for the main extensions (viz, multicluster, jaeger,\nlinkerd2-cni) were not deprecated, they also stopped creating their namespace\nand users are required to uninstall and reinstall them anew, e.g:\n\n```bash\nhelm install linkerd-viz -n linkerd-viz --create-namespace linkerd/linkerd-viz\n```\n\n* Added a new `--obfuscate` flag to `linkerd diagnostics proxy-metrics` to\n  obfuscate potentially private information in the output (thanks\n  @ahmedalhulaibi!)\n* Fixed formatting of the recommended value for `--set clusterNetworks` in the\n  `linkerd check` output when that parameter doesn't contain all the node\n  podCIDRs (thanks @ElvinEfendi!)\n* Skipped evicted pods in `linkerd viz check` and `linkerd jaeger check`, to\n  avoid the checks fail unnecessarily\n* Removed some no longer used environment variables from the proxy's manifest\n\n## edge-21.12.2\n\nThis edge removes the default SMI functionality that is included in\ninstallations now that the linkerd-smi extension provides these resources. It\nalso relaxes the `proxy-init`'s `privileged` value to only be set to `true` when\nneeded by certain installation configurations.\n\nAlong with some bug fixes, the repository's issue and feature request templates\nhave been updated to forms; check them when opening a [new\nissue](https://github.com/linkerd/linkerd2/issues/new/choose)! (thanks\n@mikutas).\n\n* Removed SMI functionality in the default Linkerd installation; this is now\n  part of the linkerd-smi extension\n* Fixed autocompletion of the `--context` flag (thanks @mikutas!)\n* Added support for conditionally setting `proxy-init`'s `privileged: true` only\n  when needed (thanks @alex-berger!)\n* Added support for controlling opaque ports through the Server resource\n* Fixed an issue where `linkerd check` would compare proxy versions of\n  uninjected pods leading to incorrect errors\n* Relaxed extension checks so that the CLI still works when not all extension\n  proxies are healthy\n* Added the `--default-inbound-policy` flag to `linkerd inject` for setting a\n  non-default inbound policy on injected workloads (thanks @ahmedalhulaibi!)\n\n## edge-21.12.1\n\nThis edge release enables by default `EndpointSlices` in the destination\ncontroller, which unblocks any functionality that is specific to\n`EndpointSlices` such as as topology-aware hints. It also contains a couple of\ninternal cleanups and upgrades, by our external contributors!\n\n* Added new check to `linkerd check` verifying the nodes aren't running the old\n  Docker container runtime and attempting to run proxy-init as root at the same\n  time, which doesn't work (thanks @alex-berger!)\n* Enabled `EndpointSlices` in the destination controller by default\n* Removed extraneous empty lines and fixed the formatting of warnings in the\n  output of `linkerd check -o short`\n* Upgraded to go 1.17 (thanks @Juneezee!)\n* Removed old protobuf definitions from the codebase (thanks @krzysztofdrys!)\n\n## edge-21.11.4\n\nThis edge release introduces a change in the destination service to honor\nopaque ports set in the `proxyProtocol` field of `Server` resources. This\nchange makes it possible to set opaque ports directly in `Server` resources\nwithout needing the opaque ports annotation on pods. The release also features\na number of fixes and improvements, a big thank you to our external\ncontributors for their continued support and involvement.\n\n* Added support in the destination service for honoring opaque ports marked in\n  `Server` resources; ports can now be marked as opaque directly in `Server`\n  resources through the `proxyProtocol` field.\n* Added support to override default behavior and run `proxyInit` as root\n  (thanks @alex-berger!)\n* Added multicluster `Link` CRD to code generation script; consumers of the\n  multicluster API can now use a typed API to interact with multicluster links\n  (thanks @zaharidichev!)\n* Added a multicluster integration test for exported headless services (thanks\n  @importhuman!)\n* Deprecated `v1alpha1` version of the policy APIs\n* Removed newline from `linkerd check` header text (thanks @mikutas!)\n* Replaced deprecated `beta.kubernetes.io/os` label with `kubernetes.io/os`\n\n## edge-21.11.3\n\nThis edge releases fixes a compatibility issue that prevented the policy\ncontroller from starting in some Kubernetes distributions. This release also\nincludes a new High Availability mode for the gateway component in multicluster\nextension. Various dependencies across the CNI plugin, Policy Controller and\ndashboard have also been upgraded. In the proxy, error logging when the proxy\nfails to accept a connection due to a system error has been improved.\n\n* Updated policy controller to use `openssl` instead of `rustls` to fix\n  compatibility issues with some Kubernetes distributions\n* Added HA mode to multicluster gateway that adds a PodDisruptionBudget,\n  additional replicas and anti-affinity to the deployment (thanks @Crevil)\n* Improved TCP server error messages in the proxy\n* Fixed broken Grafana links in the dashboard\n* Upgraded CNI pkg to v0.8.1 in `linkerd-cni` to support latest CNI\n  versions\n* Updated various dependencies in the dashboard, policy controller\n  (thanks @dependabot)\n\n## edge-21.11.2\n\nThis edge release introduces a new Services page in the web dashboard that shows\nlive calls and route metrics for meshed services. Additionally, the `proxy-init`\ncontainer is no longer enforced to run as root. Lastly, the proxy can now retry\nrequests with a `content-length` header—permitting requests emitted by grpc-go\nto be retried.\n\n* Removed hardcoding that enforced the `proxy-init` container to run as root\n* Added support for retrying requests without a `content-length` header\n* Changed service discovery logs from `TRACE` to `DEBUG`\n* Fixed issue with policy controller where it assumed `linkerd` was the name of\n  the control plane namespace, leading to issues with installations that use a\n  non-default namespace name\n* Added support for ephemeral storage requests and limits configured either\n  through the CLI or annotations (thanks @michaellzc!)\n* Deprecated support for topology keys and added support for topology aware\n  hints\n* Added `logFormat` and `logLevel` configuration values for the `proxy-init`\n  container (thanks @gusfcarvalho!)\n* Added services to the web dashboard (thanks @krzysztofdrys!)\n* Updated example commands in the web dashboard to use the `viz` subcommand when\n  necessary (thanks @mikutas!)\n* Removed references to `linkerd-sp-validator` service account in the\n  `linkerd-psp` role binding (thanks @multimac!)\n\n## edge-21.11.1\n\nIn this edge, we're very excited to introduce Service Account Token Volume\nProjections, used to set up the pods' identities. These tokens are bounded\nspecifically for this use case and are rotated daily, replacing the usage of the\ndefault tokens injected by Kubernetes which are overly permissive.\n\nNote that this edge release updates the minimum supported kubernetes version to\n1.20.\n\n* Updated the minimum supported kubernetes version to 1.20\n* Use Service Account Token Volume Projections to set up the pods' identities;\n  now injection also works on pods with `automountServiceAccountToken` set to\n  `false`\n* Updated proxy-init's Alpine base image to fix some CVEs (not affecting\n  Linkerd)\n* Updated the Prometheus image in linkerd-viz to 2.30.3\n* Changed the proxy and policy controller to use jemalloc on x86_64 gnu/linux to\n  reduce memory usage\n* Fixed output for `linkerd check -o json`\n* Added ability to configure ephemeral-storage resources for each component\n  (thanks @michaellzc!)\n\n## edge-21.10.3\n\nThis edge release fixes a bug in the proxy that could cause it to be killed in\ncertain situations. It also uses a more relaxed policy for the identity\ncontroller that allows it to work in environments where health checks come from\noutside of the pod network.\n\n* Skipped Prometheus scrapes on policy's `admin` server so that it no longer\n  incorrectly appears as \"DOWN\" in the Prometheus UI\n* Updated the identity controller to use the 'all-unauthenticated' policy so\n  that it can accept health checks from the node IPs\n* Fixed an infinite loop in the proxy that could cause it to be killed\n* Added tests for the multicluster install command (thanks @crevil!)\n* Fixed a bug where `authz` CLI commands would fail when policy resources had\n  an empty selector\n\n## edge-21.10.2\n\nThis edge release fixes linkerd check and the helm charts to explicitly\nindicate that the minimum Kubernetes version is 1.17.0. Prior to this change,\nthere was no validation or enforcement from linkerd check or helm to meet this\nminimum requirement.\n\nThis edge also improves `check` functionality for extensions by adding the\n`-oshort` flag, and prevents duplicate policy resources from being created for\nlinked multicluster services.\n\n* Moved service mirror policy into multicluster base chart\n* Added `-oshort` flag for extension `check` commands\n* Updated minimum kubernetes version to 1.17.0\n* Removed unused `crtExpiry` template parameter from helm charts\n* Fixed multicluster gateway name for ServerAuthorization\n* Added `priorityClassName` to the helm charts to configure control plane\n  components\n\n## edge-21.10.1\n\nThis release includes some fixes in the `linkerd check`, along with a\nbunch of dependency updates across the dashboard, Go components, and\nothers. On the proxy side, Support for `TLSv1.2` has been dropped\n(Only `TLSv1.3` cipher suite will be used), `h2` crate has been updated\nto support HTTP/2 messages with larger header values.\n\n* Updated `linkerd check` to avoid multiline errors with retryable checks\n* Fixed incorrect opaque ports warning in `linkerd check --proxy` with\n  un-named ports\n* Bumped proxy-init to `1.4.1` which adds support for `--log-level`\n  and `--log-format` flags (thanks @gusfcarvalho)\n* Removed the use of `TLSv1.2` in the proxy\n* Updated the `h2` crate in the proxy to support HTTP/2 messages with\n  larger header values.\n* Updated various dependencies across the dashboard, policy-controller, etc\n  (thanks @dependabot!)\n\n## stable-2.11.0\n\nThis release introduces access control policies. Default policies may be\nconfigured at the cluster- and workspace-levels; and fine grained policies may\nbe instrumented via the new `policy.linkerd.io/v1beta1` CRDs: `Server` and\n`ServerAuthorization`. These resources may be created to define how individual\nports accept connections; and the `Server` resource will be a building block for\nfuture features that configure inbound proxy behavior.\n\nFurthermore, `ServiceProfile` retry configurations can now instrument retries\nfor requests with bodies. This unlocks retry behavior for gRPC services.\n\n**Upgrade notes**: Please see the [upgrade instructions][upgrade-2110].\n\n* Proxy\n  * Reduced CPU & Memory usage by up to 30% in some load tests\n  * Updated retries to support requests with bodies up to 64KB. ServiceProfiles\n    may now configure retries for gRPC services\n  * The proxy's container image is now based on `gcr.io/distroless/cc` to\n    contain a minimal OS footprint that should not trigger unnecessary alerts in\n    security scanners\n  * Added the `inbound_http_errors_total` and `outbound_http_errors_total`\n    metrics to reflect errors that caused the proxy to respond with errors\n  * Added an `l5d-proxy-error` header that is included on responses on trusted\n    connections for debugging purposes\n  * Added a `l5d-client-id` header on mutually-authenticated inbound requests so\n    that applications can discover the client's identity\n  * Added metrics to reflect TCP and HTTP authorization decisions\n  * Added `srv_name` and `saz_name` labels to inbound HTTP metrics\n  * Fixed an issue that could cause the proxy to continually reconnect to\n    defunct service endpoints\n  * Dropped support for non-HTTP outbound services when `linkerd.io/inject:\n    ingress` is used\n  * Instrumented fuzz testing to help guard against unexpected panics\n\n* Control Plane\n  * Added a new `policy-controller` container to the `linkerd-destination`\n    pod--the first control plane component implemented in Rust\n  * Added a new admission controller to validate that multiple `Server`\n    resources do not reference the same port\n  * Added a `linkerd-identity-trust-roots` ConfigMap which configures the trust\n    root bundle for all pods in the core control plane namespace\n  * Eliminated the `linkerd-controller` deployment so that Linkerd's core\n    control plane now consists of only 3 deployments\n  * Updated the proxy injector to configure the `proxy-init` container with\n    `NET_RAW` and `NET_ADMIN` capabilities so that the container does not fail\n    when the pod drops these capabilities\n\n* CLI\n  * Enhanced `linkerd completion` to expand Kubernetes resources from the current\n    kubectl context\n  * Added an `authz` subcommand to display the authorization policies that\n    impact a workload\n  * Added a _short_ output mode for `linkerd check` that only prints failed\n    checks\n  * Added support for `ReplicaSets` to `linkerd stat` so that pods created by\n    Argo `Rollout` resources can be inspected\n\n* Helm: please see the [upgrade instructions][upgrade-2110].\n\n* Extensions:\n  * Introduced a new (optional) SMI extension responsible for reading\n    `specs.smi-spec.io` resources and converting them to Linkerd resources\n  * In `stable-2.12`, this extension will be required to use `TrafficSplit`\n    resources with Linkerd\n  * Added an extensions page to the Linkerd Web UI\n\n  * Viz\n    * Added `Server` and `ServerAuthorization` resources for all ports\n    * Added JSON log formatting\n\n  * Jaeger\n    * Added OpenTelemetry collector instead of OpenCensus\n\n  * Multicluster\n    * Added experimental support for `StatefulSet` workloads\n\nThis release includes changes from a massive list of contributors. A special\nthank-you to everyone who helped make this release possible:\n\nGustavo Fernandes de Carvalho @gusfcarvalho\nOleg Vorobev @olegy2008\nBart Peeters @bartpeeters\nStepan Rabotkin @EpicStep\nLiuDui @xichengliudui\nAndrew Hemming @drewhemm\nUjjwal Goyal @importhuman\nKnut Götz @knutgoetz\nSanni Michael @sannimichaelse\nBrandon Sorgdrager @bsord\nGerald Pape @ubergesundheit\nAlexey Kostin @rumanzo\nrdileep13 @rdileep13\nTakumi Sue @mikutas\nAkshit Grover @akshitgrover\nSanskar Jaiswal @aryan9600\nAleksandr Tarasov @aatarasoff\nTaylor @skinn\nMiguel Ángel Pastor Olivar @migue\nwangchenglong01 @wangchenglong01\nJosh Soref @jsoref\nCarol Chen @kipply\nPeter Smit @psmit\nTarvi Pillessaar @tarvip\nJames Roper @jroper\nDominik Münch @muenchdo\nSzymon Gibała @Szymongib\nMitch Hulscher @mhulscher\n\n[upgrade-2110]: https://linkerd.io/2/tasks/upgrade/#upgrade-notice-stable-2110\n\n## edge-21.9.5\n\nThis edge is a release candidate for `stable-2.11.0`, containing a couple of\nimprovements to `linkerd check`, some final tweaks before the stable release,\nand a couple of contributions from the community.\n\n* Had `linkerd check --proxy` stop failing on pods that are in Shutdown status\n  (thanks @olegy2008!)\n* Lowered from error to warning a failed check on misconfigured opaque ports\n  annotations, given that doesn't imply the installation is broken\n* Added log level and format settings to all the viz components (thanks\n  @gusfcarvalho!)\n* Removed label from the multicluster gateway and service-mirror pods to allow\n  them to be properly rolled out when upgrading\n\n## edge-21.9.4\n\nThis edge is a release candidate for `stable-2.11.0`! It introduces a new\n`linkerd viz auth` command which shows metrics for server authorizations broken\ndown by server for a given resource. It also shows the rate of unauthorized\nrequests to each server.  This is helpful for seeing a breakdown of which\nauthorizations are being used and what proportion of traffic is being rejected.\n\nIt also fixes an issue in the proxy where  HTTP load balancers could continue\ntrying to establish connections to endpoints that were removed from service\ndiscovery. In addition it improves the proxy's error handling so that it can\nsignal to an inbound proxy when its peers outbound connections should be torn\ndown.\n\n* Changed destination watch updates from `info` to `debug` to reduce the amount\n  of logs (thanks @bartpeeters!)\n* Added the `linkerd viz auth` command which shows metrics for server\n  authorizations broken down by server for a given resource\n* Fixed an issue where the policy controller's validating admission webhook\n  attempted to validate ServerAuthorizations when it should only be validating\n  Servers\n* Removed `omitWebhookSideEffects` setting now that we no longer support\n  Kubernetes 1.12\n* Improved proxy error handling so that it can signal to its peers that their\n  outbound connections should be torn down\n* Fixed an issue where after upgrades there would be a mismatch in certs used by\n  the policy controller validator; the destination pod is now restarted similar\n  to the injector\n* Fixed a field reference in the Helm template to properly refer to\n  `profileValidator.namespaceSelector`\n* Updated policy CRD versions to `v1beta1`\n* Added support for `stat`'s `-o json` option to Server resources\n* Fixed an issue in the proxy where HTTP load balancers could continue trying to\n  establish connections to endpoints that were removed from service discovery\n* Added JSON output format to `linkerd viz authz` command\n\n## edge-21.9.3\n\nThis edge is a release candidate for `stable-2.11.0`! It features a new `linkerd\nauthz` CLI command to list servers and authorizations for a workload, as well as\npolicy resources support for `linkerd viz stat`. Furthermore, this edge release\nadds support for JSON log formatting, enables TLS detection on port 443\n(previously marked as opaque), and further improves policy features.\n\n* Removed port 443 from the default list of opaque ports, this will allow the\n  proxy to report metadata (such as the connection's SNI value) on TLS\n  connections to port 443\n* Added default policies for core Linkerd extensions\n* Added support for JSON log formatting to the policy controller\n* Added support for new policy resources to `viz stat` command\n* Added default policy annotation to `linkerd-identity`\n* Added a new `linkerd authz` command to the CLI to list all server and\n  authorization resources that apply to a specific resource\n* Added TLS labels (including client identity) to authorization metrics in the\n  proxy\n* Changed the opaque ports CLI check to consider service and pod ports when\n  checking annotation values; previously, the check would naively issue warnings\n  when the service annotation values were different from the pod it selected\n* Changed how the proxy forwards inbound connections to a pod locally; the proxy\n  now targets the original address instead of a port bound on localhost to\n  protect services that are only bound on loopback from being exposed to other\n  pods\n* Improved memory utilization in the proxy, especially for TCP forwarding, where\n  the memory allocated was reduced from 128KB to 16KB\n* Updated the inbound policy system for the proxies to always allow connections\n  from localhost\n* Fixed an issue where the policy controller would not detect changes to the\n  `proxyProtocol` field of `Server` resources\n* Fixed an issue where the policy admission controller would log a `WARN`\n  message when deserializing `Server` structs\n\n## edge-21.9.2\n\nThis edge release gets us closer to 2.11 by further polishing the policy\nfeature. Also the proxy received a noticeable resource consumption improvement.\n\n* Stopped creating the default authorizations for the kubelet\n* Added missing ports to the destination controller's default list of ports, to\n  allow the sp-validator to start properly when using a default-deny policy\n* Set the destination and proxy-injector pods default policy to\n  `all-unauthenticated` to allow the webhooks to be called from the kube-api\n  when using a default-deny policy\n* Extended inbound policies to cover the proxy's admin server\n* Improved the proxy's error handling so that HTTP metrics include 5XX responses\n  for common errors\n* The proxy's outbound tap has been fixed to include route labels when service\n  profiles are configured\n* Enabled link-time optimizations in the Rust components (proxy and policy\n  controller), resulting in noticeable RSS and CPU consumption improvements\n* Made the admin servers in the control plane components properly shut down\n  (thanks @EpicStep!)\n* Updated linkerd-await, suppressing the error emitted when linkerd-await was\n  disabled\n\n## edge-21.9.1\n\nThis release includes various improvements and feature additions across the policy\nfeature i.e, New validating webhook for policy resources. This also includes changes\nin the proxy i.e, terminating TCP connections when a authorization is revoked, improvements\nin the proxy authorization metrics. In addition, proxy injector has also been updated\nto set the right `opaque-ports` annotation on services with default opaque ports.\n\n* Added a new validating admission controller to validate the policy resources\n* Updated the proxy-init to remove a rule which caused the packets from the proxy\n  with destination != 127.0.0.1 on localhost to be sent to the inbound proxy\n* Updated inbound policy enforcement to interrupt TCP forwarding if a previously\n  established authorization is revoked\n* Added new proxy metrics to expose authorization decisions\n* Updated inbound TCP metrics to only include a `srv_name` label\n* Updated the proxy to export route-oriented metrics only when a ServiceProfile\n  is enabled\n* Updated the proxy's release build configuration to improve CPU and memory\n  utilization\n* Added DNS name validation to the `proxy-identity` binary which creates the\n  read-only private key required by the proxy (thanks @yorkijr!)\n* Updated the identity controller's default policy to be `cluster-unauthenticated`\n* Updated the proxy injector to include the correct default ports as opaque with\n  services\n* Deprecated the usage of `vis stat ts` and print a warning about the SMI extension\n* Updated various dependencies across the dashboard, policy-controller\n  (thanks @dependabot!)\n\n## edge-21.8.4\n\nThis edge release continues to build on the policy feature by adding support for\ncluster-scoped default policies and exposing policy labels on various prometheus\nmetrics. The proxy has been updated to return HTTP-level authorization errors\nat the time that the request is processed, instead of when the connection is\nestablished.\n\nIn addition, the proxy-injector has been updated to set the `opaque-ports`\nannotation on a workload to make sure that controllers can discover how the\nworkload was configured. Also, the `sleep` binary has been added to the proxy\nimage in order to restore the functionality required for `waitBeforeExitSeconds`\nto work.\n\n* Added `default-inbound-policy` annotation to the proxy-injector\n* Updated the proxy-injector to always add the `opaque-ports` annotation\n* Added `sleep` binary to proxy image\n* Updated inbound traffic metrics to include server and authorization labels\n* Updated the policy-controller to honor pod level port annotations when a\n  `Server` resource definition does not match the ports defined for the workload\n* Updated the point at which the proxy returns HTTP-level authorization errors\n* Exposed permit and policy labels on HTTP metrics\n* Added support for cluster-scoped default policies\n* Dropped `nonroot` variant from the policy-controller's distroless base image\n  to avoid erroring in some environments.\n\n## edge-21.8.3\n\nThis release adds support for dynamic inbound policies. The proxy now discovers\npolicies from the policy-controller API for all application ports documented in\na pod spec. Rejected connections are logged. Policies are not yet reflected in\nthe proxy's metrics.\n\nThese policies also allow the proxy to skip protocol detection when a server is\nexplicitly annotated as HTTP/2 or when the server is documented to be opaque or\napplication-terminated TLS.\n\n* Added a new section to linkerd-viz's dashboard that lists installed extensions\n  (thanks @sannimichaelse!)\n* Added the `enableHeadlessServices` Helm flag to the `linkerd multicluster\n  link` command for enabling headless service mirroring (thanks @knutgoetz!)\n* Removed some unused and duplicate constants in the codebase (thanks\n  @xichengliudui!)\n* Added support for exposing service metadata from exported to mirrored services\n  in multicluster installations (thanks @importhuman!)\n* Fixed an issue where the policy controller's liveness checks would fail after\n  the controller was disconnected but had successfully resumed its watches\n* Fixed the `linkerd-policy` service selector to properly select `destination`\n  control plane components\n* Added additional environment variables to the proxy container to allow support\n  for dynamic policy configuration\n\n## edge-21.8.2\n\nThis edge release continues the policy work by adding a new controller, written\nin Rust, to expose a discovery API for inbound server policies. Apart from\nthat, this release includes a number of changes from external contributors; the\n`linkerd-jaeger` helm chart now supports passing arguments to the Jaeger\ncontainer through the chart's values file. A number of unused functions and\nvariables have been also removed to improve the quality of the codebase.\nFinally, this release also comes with changes to the proxy's outbound behavior,\na new extensions page on the dashboard, and support for querying service\nmetrics using the `authority` label in `linkerd viz stat`.\n\n* Introduced new `linkerd-policy-controller`; the new controller is written in\n  Rust and implements discovery APIs for inbound server policies, the container\n  has been added to the `linkerd-destination` pod\n* Updated `linkerd-jaeger` helm chart to support passing arguments to the\n  Jaeger container (thanks @bsord!)\n* Added support for querying service metrics using the `authority` label in\n  `linkerd viz stat`\n* Improved code hygiene by removing unused constants and functions throughout\n  the codebase (thanks @xichengliudui!)\n* Added a new extensions page to the dashboard to list all known built-in and\n  third party extensions that can be used with Linkerd\n* Changed outbound behavior in the proxy to tear down server-side connections\n  when the remote proxy returns responses that indicate proxy errors; the\n  connection in this case will be reset to allow clients to connect to a new\n  endpoint\n\n## edge-21.8.1\n\nThis releases includes initial changes w.r.t addition of Authorization into\nLinkerd. It includes adding the new `policy.linkerd.io` CRDs to the core install.\nThis also includes numerous dependency updates both in the web and dashboard.\n\n* Added `servers.policy.linkerd.io` and `serverauthorizations.policy.linkerd.io`\n  CRDs into the default Linkerd installation to support configuration and\n  discovery of inbound policies\n* Modified the proxy to support upcoming policy features\n* Updated several dashboard dependencies to latest versions\n* Updated several proxy dependencies to latest versions\n\n## edge-21.7.5\n\nThis release updates Linkerd to store the identity trust root in a ConfigMap to\nmake it easier to manage and rotate the trust root.  The release also lays the\ngroundwork for StatefulSet support in the multicluster extension and removes\ndeprecated PSP resources by default.\n\n* Added a `linkerd-identity-trust-roots` ConfigMap which contains the configured\n  trust root bundle\n* Introduced support for StatefulSets across multicluster (disabled by default)\n* Stopped installing PSP resources by default since these are deprecated as\n  of Kubernetes v1.21\n\n## edge-21.7.4\n\nThis release continues to focus on dependency updates. It also adds the\n`l5d-proxy-error` information header to distinguish proxy generated errors\nproxy generated errors from application generated errors.\n\n* Updated several project dependencies\n* Added a new `l5d-proxy-error` on responses that allows proxy-generated error\n  responses to be distinguished from application-generated error responses.\n* Removed support for configuring HTTP/2 keepalives via the proxy.\n  Configuring this setting would sometimes cause conflicts with Go gRPC servers\n  and clients\n* Added a new `target_addr` label to `*_tcp_accept_errors` metrics to improve\n  diagnostics, especially for TLS detection timeouts\n\n## edge-21.7.3\n\nThis edge release introduces several changes around metrics. ReplicaSets are now\na supported resource and metrics can be associated with them. A new metric has\nbeen added which counts proxy errors encountered before a protocol can be\ndetected. Finally, the request errors metric has been split into separate\ninbound and outbound directions.\n\n* Fixed printing `check --pre` command usage if it fails after being unable to\n  connect to Kubernetes (thanks @rdileep13!)\n* Updated the default skip and opaque ports to match that which is listed in the\n  [documentation](https://linkerd.io/2.10/features/protocol-detection/#configuring-protocol-detection)\n* Added the `LINKERD2_PROXY_INBOUND_PORTS` environment variable during proxy\n  injection which will be used by ongoing policy changes\n* Added client-go cache size metrics to the `diagnostics controller-metrics`\n  command\n* Added validation that the certificate provided by an external issuer is a CA\n  (thanks @rumanzo!)\n* Added metrics support for ReplicaSets\n* Replaced the `request_errors_total` metric with two new metrics:\n  `inbound_http_errors_total` and `outbound_http_errors_total`\n* Introduced the `inbound_tcp_accept_errors_total` and\n  `outbound_tcp_accept_errors_total` metrics which count proxy errors\n  encountered before a protocol can be detected\n\n## edge-21.7.2\n\nThis edge release focuses on dependency updates and has a couple of functional\nchanges. First, the Dockerfile used to build the proxy has been updated to use\nthe default `distroless` image, rather than the non-root variant. This change\nis safe because the proxy already runs as non-root within the container. Second,\nthe `ignoreInboundPorts` parameter has been added in the linkerd2-cni helm\ncharts in order to enable tap support.\n\n* Updated several project dependencies\n* Updated the Dockerfile-proxy to use the default distroless image, because\n  the proxy already runs as non-root within the container\n* Added `ignoreInboundPorts` parameter to the linkerd2-cni plugin helm chart\n\n## edge-21.7.1\n\nThis edge release adds support for emitting Kubernetes events in the identity\ncontroller when issuing leaf certificates. The event includes the identity,\nexpiry date, and a hash of the certificate. Additionally, this release contains\nmany dependency updates for the control plane's components, and it includes a\nfix for an issue with the clusterNetworks healthcheck.\n\n* Updated the identity controller to emit Kubernetes events when successfully\n  issuing leaf certificates to injected pods.\n* Fixed an issue in `linkerd check` where the clusterNetworks healthcheck\n  would fail if the `podCIDR` field is omitted from a node's spec.\n* Removed unnecessary controller port-forward logic from the `bin/web` script.\n\n## edge-21.6.5\n\nThis release contains a few improvements, from many contributors!  Also under\nthe hood, the destination service has received updates in preparation to the\nupcoming support for StatefulSets across multicluster.\n\n* Improved the `linkerd check --proxy` command to avoid hitting a timeout when\n  dealing with large clusters\n* Fixed the web component permissions in order to properly run the podCIDR check\n  (thanks @aryan9600!)\n* Avoid having the proxy-init container fail when the main container is\n  configured to drop either the NET_RAW or NET_ADMIN capabilities (thanks\n  @aryan9600!)\n* Upgraded the proxy-init image to improve the output in \"simulate\" mode (thanks\n  @liuerfire!) and to log to stdout instead of stderr (thanks @mo4islona!)\n* Added test-coverage reports to PRs (thanks @akshitgrover!)\n\n## edge-21.6.3\n\nThis release moves the Linkerd proxy to a more minimal Docker base image,\nadds a check for detecting certain network misconfigurations, and replaces\nthe deprecated OpenCensus collector with the OpenTelemetry collector in the\njaeger extension.\n\n* Switched the Linkerd proxy's base docker image from Debian to a minimal\n  distroless base image (thanks @tskinn!)\n* Added a check to verify that Linkerd's clusterNetworks settings match the\n  cluster's pod CIDR networks (thanks @aryan9600!)\n* Replaced the deprecated OpenCensus collector with the OpenTelemetry\n  collector in the jaeger extension (thanks @aatarasoff!)\n\n## edge-21.6.2\n\nThis release fixes a problem with the HTTP body buffering that was added\nto support gRPC retries. Now, only requests with a retry configuration\nare buffered (and only when their bodies are less than 64KB).\n\nAdditionally, an issue with the outbound ingress-mode proxy where forwarded\nHTTP clients could fail to detect when the target pod was deleted, causing\nconnections to retry forever has been fixed. This only impacted traffic\nforwarded directly to pod IPs and not load balanced services.\n\nFinally, this release also includes some fixes in the CLI and dashboard.\n\n* Added a new check that verifies if the opaque ports annotation is\n  misconfigured on services or pods (thanks @migue!)\n* Added support for resource aware completion for core linkerd command\n* Fixed an issue where `namespace` resource was erroneously being shown\n  in the dashboard's topology graph\n* Added uninstall command support for legacy extension installs\n* Updated the proxy to only buffer request bodies when a request can be retried\n* Updated the proxy to prevent buffering indefinitely on requests\n  when endpoints are updated in ingress mode\n* Fixed spelling mistakes across various files in the project\n  (thanks @jsoref!)\n\n## edge-21.6.1\n\nThis release adds support for retrying HTTP/2 requests with small (<64KB)\nmessage bodies, allowing the proxy to properly buffer message bodies when\nresponses are classified as a failure. Documentation on how to configure\nretries can be found [here](https://linkerd.io/2.10/tasks/configuring-retries/).\n\nThis release also modifies the proxy's identity subsystem to instantiate a\nclient on-demand so client connections are not retained continually. Also\nincluded in this release are various bug fixes and improvements as well as\nexpanding support for resource-aware tab completion in the jaeger and\nmulticluster CLI extensions.\n\n* Added support for specifying a `gateway-port` flag for the `multicluster link`\n  command (thanks @psmit!)\n* Added support for Kubernetes resource aware tab completion for `jaeger` and\n  `multicluster` commands\n* Fixed an issue where `viz`, `jaeger` and `multicluster` extensions could not\n  be installed on `PodSecurityPolicy`-enabled clusters\n* Fixed an issue where `linkerd check --proxy` could incorrectly report\n  out-of-date proxy versions caused by incorrect regex (thanks @aryan9600!)\n* Added support for the proxy to retry HTTP/2 requests with message bodies\n  <= 64KB\n* Modified the proxy's controller stack to create new client connections\n  on-demand\n* Fixed Viz's `uninstall` command to remove viz installations that used the\n  legacy `linkerd.io/extension: linkerd-viz` label (thanks @jsoref!)\n* Expanded the \"linkerd-existence\" health check to also check for the\n  destination pod readiness\n\n## edge-21.5.3\n\nThis edge release contains various improvements to the Viz and Jaeger install\ncharts, along with bug fixes in the CLI, and destination. This release also\nadds kubernetes aware autocompletion to all viz commands, along with\nServiceProfiles to be part of the default `viz install`.\n\nFinally, the proxy has been updated to continue supporting requests without\n`l5d-dst-override` in ingress-mode proxies, to no longer include query parameters\nin the OpenCensus trace spans, and to prevent timeouts with controller clients\nof components with more than one replica.\n\n* Separated protocol hint setting from H2 upgrades in destination profile\n  response, thus preventing `hint.OpaqueTransport` field from not being set when\n  H2 upgrades are disabled\n* Updated OpenCensus trace spans for HTTP requests to no longer include query\n  parameters (thanks @aatarasoff!)\n* Reverted [linkerd/linkerd2-proxy#992](https://github.com/linkerd/linkerd2-proxy/pull/992)\n  to support requests without `l5d-dst-override` in ingress-mode proxies\n* Fixed an issue in the proxy to prevent timeouts with controller clients\n  of components with more than one replica\n* Fixed `linkerd check --proxy` failure with pods that are part of Jobs\n* Updated `viz install` to also include ServiceProfiles of its components.\n  As a side-effect, `linkerd diagnostics install-sp` cmd has been removed\n* Added support for Kubernetes resource aware tab completion for all\n  viz commands\n* Updated destination to prefer `ServiceProfile.dstOverrides` over\n  `TrafficSplit` when both are present for a service\n* Added toggle flags for `collector` and `jaeger` components in the\n  jaeger extension (thanks @tarvip!)\n* Added support for setting `nodeselector`, `toleration` fields for components\n  in the Viz extension (thanks @aatarasoff!)\n* Fixed a templating issue in Viz, making `podAnnotations` field\n  work with prometheus\n* Updated Golang version to 1.16.4\n* Removed unnecessary `--addon-overwrite` flag in `linkerd upgrade`\n\n## edge-21.5.2\n\nThis edge release updates the proxy-init container to check whether the iptables\nrules have already been added, which prevents errors if the proxy-init container\nis restarted. Also, the `viz stat` command now has tab completion for Kubernetes\nresources, saving you precious keystrokes! Finally, the proxy has been updated\nwith several fixes and improvements.\n\n* Added instructions to `build.md` for using a locally built proxy\n  (thanks @jroper!)\n* Added support for Kubernetes resource aware tab completion to the `viz stat`\n  command\n* Updated `proxy-init` to skip configuring firewall if rules exists\n* Fixed `viz uninstall` to delete all RBAC objects (thanks @aryan9600!)\n* Improved diagnostics for rejected profile discovery\n* Added the `l5d-client-id` header on mutually-authenticated inbound requests so\n  that applications can discover the client's identity.\n* Reduced proxy resource usage when there are no profiles\n* Changed the admin server to assume all meshed connections are HTTP/2 and fail\n  connections when that is not the case\n* Updated the proxy to require the `l5d-dst-override` header on outbound\n  requests when the proxy is in ingress-mode\n* Removed support for TCP-forwarding in ingress-mode\n\n## edge-21.5.1\n\nThis edge release adds support for versioned hint URLs in `linkerd check` and\nsupport for traffic splitting through ServiceProfiles, among other fixes and\nimprovements. Additionally, more options have been added to the\nlinkerd-multicluster and linkerd-jaeger helm charts.\n\n* Added support for traffic splitting through a ServiceProfile's `dstOverrides`\n  field.\n* Added `nodePorts` option to the multicluster helm chart (thanks @psmit!).\n* Added `nodeSelector` and toleration options to the linkerd-jaeger helm chart\n  (thanks @aatarasoff!).\n* Added versioned hint URLs to the CLI `check` command when encountering an\n  error; each major CLI version will now point to that version's relevant\n  section in the Linkerd troubleshooting page.\n* Fixed an issue in the CLI `check` command where error messages for\n  healthchecks that were being retried would be outputted repeatedly instead of\n  just once.\n* Fixed an issue in the proxy injector where a namespace annotated with opaque\n  ports would overwrite all service annotations.\n* Fixed a regression in the proxy that caused all logs to be output with ANSI\n  control characters, by default logs are output in plaintext now.\n* Simplified proxy internals in order to distinguish endpoint-forwarding logic\n  from the handling of load balanced services.\n* Simplified the ingress-mode outbound proxy by requiring the\n  `l5d-dst-override` header and by failing non-HTTP communication. Proxies\n  running in ingress-mode will not unexpectedly revert to insecure\n  communication as a result.\n\n## edge-21.4.5\n\nThis edge release adds a new output format `short` for `linkerd check` to show a\nsummary of the check output. This release also includes various proxy bug fixes\nand improvements.\n\n* Proxy\n  * Fixed a task leak that would be triggered when clients disconnect a\n    service in failfast.\n  * Improved admin server protocol detection so that error messages are\n    more descriptive about the underlying problem.\n  * Fixed panics found in fuzz testing. These panics were extremely\n    unlikely to occur in practice and would require very specific\n    configuration overrides to be triggered.\n* CLI\n  * Added support for a new `short` format for the `--output` flag of the `check`\n    command to show a summary of check results\n\n## edge-21.4.4\n\nThis edge release further consolidates the control plane by removing the\nlinkerd-controller deployment and moving the sp-validator container into the\ndestination deployment.\n\nAnnotation inheritance has been added so that all Linkerd annotations\non a namespace resource will be inherited by pods within that namespace.\nIn addition, the `config.linkerd.io/proxy-await` annotation has been added which\nenables the [linkerd-await](https://github.com/linkerd/linkerd-await)\nfunctionality by default, simplifying the implementation of the await behavior.\nSetting the annotation value to disabled will prevent this behavior.\n\nSome of the `linkerd check` functionality has been updated. The command\nensures that annotations and labels are properly located in the YAML and adds\nproxy checks for the control plane and extension pods.\n\nFinally, the nginx container has been removed from the Multicluster gateway pod,\nwhich will impact upgrades. Please see the note below.\n\n**Upgrade note:** When the Multicluster extension is updated in both of the\nsource and target clusters there won't be any downtime because this change only\naffects the readiness probe. The multicluster links must be re-generated with\nthe `linkerd mc link` command and the `linkerd mc gateways` will show\nthe target cluster as not alive until the `linkerd mc link` command is re-run,\nhowever that shouldn't affect existing endpoints pointing to the target cluster.\n\n* Added proxy checks for core control plane and extension pods\n* Added support for awaiting proxy readiness using an annotation\n* Added namespace annotation inheritance to pods\n* Removed the linkerd-controller pod\n* Moved sp-validator container into the destination deployment\n* Added check verifying that labels and annotations are not mixed up\n  (thanks @szymongib)\n* Enabled support for extra initContainers to the linkerd-cni daemonset\n  (thanks @mhulscher!)\n* Removed nginx container from multicluster gateway pod\n* Added an error message when there is nothing to uninstall\n\n## stable-2.10.1\n\nThis stable release adds CLI support for Apple Silicon M1 chips and support for\nSMI's TrafficSplit `v1alpha2`.\n\nThere are several proxy fixes: handling `FailedPrecondition` errors gracefully,\ninbound TLS detection from non-meshed workloads, and using the correct cached\nclient when the proxy is in ingress mode. The logging infrastructure has also\nbeen improved to reduce memory pressure in high-connection environments.\n\nOn the control-plane side, there have been several improvements to the\ndestination service such as support for Host IP lookups and ignoring pods\nin \"Terminating\" state. It also updates the proxy-injector to add opaque ports\nannotation to pods if their namespace has it set.\n\nOn the CLI side, `linkerd repair` has been updated to be aware about the control-plane\nversion and suggest the relevant version to generate the right config. Various\nbugs have been fixed around `linkerd identity`, etc.\n\n**Upgrade notes**: Please refer [2.10 upgrade instructions](https://linkerd.io/2/tasks/upgrade/#upgrade-notice-stable-2100)\nif you are upgrading from `2.9.x` or below versions.\n\n* Proxy:\n  * Fixed an issue where proxies could infinitely retry failed requests to the\n    `destination` controller when it returned a `FailedPrecondition`\n  * The proxy's logging infrastructure has been updated to reduce memory pressure\n    in high-connection environments.\n  * Fixed a caching issue in the outbound proxy that would cause it to\n    forward traffic to the wrong pod when running in ingress mode.\n  * Fixed an issue where inbound TLS detection from non-meshed workloads\n    could break\n  * Fixed an issue where the admin server's HTTP detection would fail and\n    not recover; these are now handled gracefully and without logging warnings\n  * Control plane proxies no longer emit warnings about the resolution stream ending.\n    This error was innocuous.\n  * Bumped the proxy-init image to v1.3.11 which updates the go version to be 1.16.2\n\n* Control Plane:\n  * Fixed an issue where the destination service would respond with too big of a\n    header and result in http2 protocol errors\n  * Fixed an issue where the destination control plane component sometimes returned\n    endpoint addresses with a 0 port number while pods were undergoing a rollout\n    (thanks @riccardofreixo!)\n  * Fixed an issue where pod lookups by host IP and host port fail even though\n    the cluster has a matching pod\n  * Updated the IP Watcher in destination to ignore pods in \"Terminating\" state\n    (thanks @Wenliang-CHEN!)\n  * Modified the proxy-injector to add the opaque ports annotation to pods\n    if their namespace has it set\n  * Added Support for TrafficSplit `v1alpha2`\n  * Updated all the control-plane components to use go `1.16.2`.\n\n* CLI:\n  * Fixed an issue where the linkerd identity command returned the root\n    certificate of a pod instead of its leaf certificates\n  * Fixed an issue where the destination service would respond with too\n    big of a header and result in http2 protocol errors\n  * Updated the release process to build Linkerd CLI binaries for Apple\n    Silicon M1 chips\n  * Improved error messaging when trying to install Linkerd on a cluster\n    that already had Linkerd installed\n  * Added a loading spinner to the linkerd check command when running\n    extension checks\n  * Added installNamespace toggle in the jaeger extension's install.\n    (thanks @jijeesh!)\n  * Updated healthcheck pkg to have hintBaseURL configurable, useful\n    for external extensions using that pkg\n  * Fixed TCP read and write bytes/sec calculations to group by label\n    based off inbound or outbound traffic\n  * Fixed an issue in linkerd inject where the wrong annotation would\n    be added when using --ingress flag\n  * Updated `linkerd repair` to be aware of the client and server versions\n  * Updated `linkerd uninstall` to print error message when there are no\n    resources to uninstall.\n\n* Helm:\n  * Aligned the Helm installation heartbeat schedule to match that of the CLI\n\n* Viz:\n  * Fixed an issue where the topology graph in the dashboard was no\n    longer draggable.\n  * Updated dashboard build to use webpack v5\n  * Added CA certs to the Viz extension's metrics-api container so\n    that it can validate the certificate of an external Prometheus\n  * Removed components from the control plane dashboard that now\n    are part of the Viz extension\n  * Changed web's base image from debian to scratch\n\n* Multicluster:\n  * Fixed an issue with Multicluster's service mirror where its endpoint\n    repair retries were not properly rate limited\n\n* Jaeger:\n  * Fixed components in the Jaeger extension to set the correct Prometheus\n    scrape values\n\n## edge-21.4.3\n\nThis edge supersedes `edge-21.4.2` as a release candidate for `stable-2.10.1`!\n\nThis release adds support for TrafficSplit `v1alpha2`. Additionally, It includes\nimprovements to the web and `proxy-init` images.\n\n* Added Support for TrafficSplit `v1alpha2`\n* Changed web base image from debian to scratch\n* Bumped the `proxy-init` image to `v1.3.11` which updates\n  the go version to be `1.16.2`\n\n## edge-21.4.2\n\nThis edge release is another candidate for `stable-2.10.1`!\n\nIt includes some CLI fixes and addresses an issue where the outbound proxy\nwould forward traffic to the wrong pod when running in ingress mode.\n\nThank you to all of our users that have helped test and identify issues in 2.10!\n\n* Fixed an issue in `linkerd inject` where the wrong annotation would be\n  added when using `--ingress` flag\n* Fixed a nil pointer dereference in `linkerd repair` caused by a mismatch\n  between CLI and server versions\n* Removed an unnecessary error handling condition in multicluster check\n  (thanks @wangchenglong01!)\n* Fixed a caching issue in the outbound proxy that would cause it to\n  forward traffic to the wrong pod when running in ingress mode.\n* Removed unsupported `matches` field from TrafficSplit CRD\n\n## edge-21.4.1\n\nThis is a release candidate for `stable-2.10.1`!\n\nThis includes several fixes for the core installation as well the Multicluster,\nJaeger, and Viz extensions. There are two significant proxy fixes that address\nTLS detection and admin server failures.\n\nThanks to all our 2.10 users who helped discover these issues!\n\n* Fixed TCP read and write bytes/sec calculations to group by label based off\n  inbound or outbound traffic\n* Updated dashboard build to use webpack v5\n* Modified the proxy-injector to add the opaque ports annotation to pods if\n  their namespace has it set\n* Added CA certs to the Viz extension's `metrics-api` container so that it can\n  validate the certificate of an external Prometheus\n* Fixed an issue where inbound TLS detection from non-meshed workloads could\n  break\n* Fixed an issue where the admin server's HTTP detection would fail and not\n  recover; these are now handled gracefully and without logging warnings\n* Aligned the Helm installation heartbeat schedule to match that of the CLI\n* Fixed an issue with Multicluster's service mirror where it's endpoint repair\n  retries were not properly rate limited\n* Removed components from the control plane dashboard that now are part of the\n  Viz extension\n* Fixed components in the Jaeger extension to set the correct Prometheus scrape\n  values\n\n## edge-21.3.4\n\nThis release fixes some issues around publishing of CLI binary\nfor Apple Silicon M1 Chips. This release also includes some fixes and\nimprovements to the dashboard, destination, and the CLI.\n\n* Fixed an issue where the topology graph in the dashboard was no longer\n  draggable\n* Updated the IP Watcher in destination to ignore pods in \"Terminating\" state\n  (thanks @Wenliang-CHEN!)\n* Added `installNamespace` toggle in the jaeger extension's install.\n  (thanks @jijeesh!)\n* Updated `healthcheck` pkg to have `hintBaseURL` configurable, useful\n  for external extensions using that pkg\n* Added multi-arch support for RabbitMQ integration tests (thanks @barkardk!)\n\n## edge-21.3.3\n\nThis release includes various bug fixes and improvements to the CLI, the\nidentity and destination control plane components as well as the proxy. This\nrelease also ships with a new CLI binary for Apple Silicon M1 chips.\n\n* Added new RabbitMQ integration tests (thanks @barkardk!)\n* Updated the Go version to 1.16.2\n* Fixed an issue where the `linkerd identity` command returned the root\n  certificate of a pod instead of its leaf certificate\n* Fixed an issue where the destination service would respond with too big of a\n  header and result in http2 protocol errors\n* Updated the release process to build Linkerd CLI binaries for Apple Silicon\n  M1 chips\n* Improved error messaging when trying to install Linkerd on a cluster that\n  already had Linkerd installed\n* Fixed an issue where the `destination` control plane component sometimes\n  returned endpoint addresses with a `0` port number while pods were\n  undergoing a rollout (thanks @riccardofreixo!)\n* Added a loading spinner to the `linkerd check` command when running extension\n  checks\n* Fixed an issue where pod lookups by host IP and host port fail even though\n  the cluster has a matching pod\n* Control plane proxies no longer emit warnings about the resolution stream\n  ending. This error was innocuous.\n* Fixed an issue where proxies could infinitely retry failed requests to the\n  `destination` controller when it returned a `FailedPrecondition`\n* The proxy's logging infrastructure has been updated to reduce memory pressure\n  in high-connection environments.\n\n## stable-2.10.0\n\nThis release introduces Linkerd extensions. The default control plane no longer\nincludes Prometheus, Grafana, the dashboard, or several other components that\npreviously shipped by default.  This results in a much smaller and simpler set\nof core functionalities.  Visibility and metrics functionality is now available\nin the Viz extension under the `linkerd viz` command.  Cross-cluster\ncommunication functionality is now available in the Multicluster extension\nunder the `linkerd multicluster` command.  Distributed tracing functionality is\nnow available in the Jaeger extension under the `linkerd jaeger` command.\n\nThis release also introduces the ability to mark certain ports as \"opaque\",\nindicating that the proxy should treat the traffic as opaque TCP instead of\nattempting protocol detection.  This allows the proxy to provide TCP metrics\nand mTLS for server-speaks-first protocols.  It also enables support for\nTCP traffic in the Multicluster extension.\n\n**Upgrade notes**: Please see the [upgrade\ninstructions](https://linkerd.io/2/tasks/upgrade/#upgrade-notice-stable-2100).\n\n* Proxy\n  * Updated the proxy to use TLS version 1.3; support for TLS 1.2 remains\n    enabled for compatibility with prior proxy versions\n  * Improved support for server-speaks-first protocols by allowing ports to be\n    marked as opaque, causing the proxy to skip protocol detection.  Ports can\n    be marked as opaque by setting the `config.linkerd.io/opaque-ports`\n    annotation on the Pod and Service or by using the `--opaque-ports` flag with\n    `linkerd inject`\n  * Ports `25,443,587,3306,5432,11211` have been removed from the default skip\n    ports; all traffic through those ports is now proxied and handled opaquely\n    by default\n  * Fixed an issue that could cause proxies in \"ingress mode\"\n    (`linkerd.io/inject: ingress`) to use an excessive amount of memory\n  * Improved diagnostic logging around \"fail fast\" and \"max-concurrency\n    exhausted\" error messages\n  * Added a new `/shutdown` admin endpoint that may only be accessed over the\n    loopback network allowing batch jobs to gracefully terminate the proxy on\n    completion\n\n* Control Plane\n  * Removed all components and functionality related to visibility, tracing,\n    or multicluster.  These have been moved into extensions\n  * Changed the identity controller to receive the trust anchor via environment\n    variable instead of by flag; this allows the certificate to be loaded from a\n    config map or secret (thanks @mgoltzsche!)\n  * Added PodDisruptionBudgets to the control plane components so that they\n    cannot be all terminated at the same time during disruptions\n    (thanks @tustvold!)\n\n* CLI\n  * Changed the `check` command to include each installed extension's `check`\n    output; this allows users to check for proper configuration and installation\n    of Linkerd without running a command for each extension\n  * Moved the `metrics`, `endpoints`, and `install-sp` commands into subcommands\n    under the `diagnostics` command\n  * Added an `--opaque-ports` flag to `linkerd inject` to easily mark ports\n    as opaque.\n  * Added the `repair` command which will repopulate resources needed for\n    properly upgrading a Linkerd installation\n  * Added Helm-style `set`, `set-string`, `values`, `set-files` customization\n    flags for the `linkerd install` and `linkerd upgrade` commands\n  * Introduced the `linkerd identity` command, used to fetch the TLS certificates\n    for injected pods (thanks @jimil749)\n  * Removed the `get` and `logs` command from the CLI\n\n* Helm\n  * Changed many Helm values, please see the upgrade notes\n\n* Viz\n  * Introduced the `linkerd viz` subcommand which contains commands for\n    installing the viz extension and all visibility commands\n  * Updated the Web UI to only display the \"Gateway\" sidebar link when the\n    multicluster extension is active\n  * Added a `linkerd viz list` command to list pods with tap enabled\n  * Fixed an issue where the `tap` APIServer would not refresh its certs\n    automatically when provided externally—like through cert-manager\n\n* Multicluster\n  * Introduced the `linkerd multicluster` subcommand which contains commands for\n    installing the multicluster extension and all multicluster commands\n  * Added support for cross-cluster TCP traffic\n  * Updated the service mirror controller to copy the\n    `config.linkerd.io/opaque-ports` annotation when mirroring services so that\n    cross-cluster traffic can be correctly handled as opaque\n  * Added support for multicluster gateways of types other than LoadBalancer\n    (thanks @DaspawnW!)\n\n* Jaeger\n  * Introduced the `linkerd jaeger` subcommand which contains commands for\n    installing the jaeger extension and all tracing commands\n  * Added a `linkerd jaeger list` command to list pods with tracing enabled\n\nThis release includes changes from a massive list of contributors. A special\nthank-you to everyone who helped make this release possible:\n[Lutz Behnke](https://github.com/cypherfox)\n[Björn Wenzel](https://github.com/DaspawnW)\n[Filip Petkovski](https://github.com/fpetkovski)\n[Simon Weald](https://github.com/glitchcrab)\n[GMarkfjard](https://github.com/GMarkfjard)\n[hodbn](https://github.com/hodbn)\n[Hu Shuai](https://github.com/hs0210)\n[Jimil Desai](https://github.com/jimil749)\n[jiraguha](https://github.com/jiraguha)\n[Joakim Roubert](https://github.com/joakimr-axis)\n[Josh Soref](https://github.com/jsoref)\n[Kelly Campbell](https://github.com/kellycampbell)\n[Matei David](https://github.com/mateiidavid)\n[Mayank Shah](https://github.com/mayankshah1607)\n[Max Goltzsche](https://github.com/mgoltzsche)\n[Mitch Hulscher](https://github.com/mhulscher)\n[Eugene Formanenko](https://github.com/mo4islona)\n[Nathan J Mehl](https://github.com/n-oden)\n[Nicolas Lamirault](https://github.com/nlamirault)\n[Oleh Ozimok](https://github.com/oleh-ozimok)\n[Piyush Singariya](https://github.com/piyushsingariya)\n[Naga Venkata Pradeep Namburi](https://github.com/pradeepnnv)\n[rish-onesignal](https://github.com/rish-onesignal)\n[Shai Katz](https://github.com/shaikatz)\n[Takumi Sue](https://github.com/tkms0106)\n[Raphael Taylor-Davies](https://github.com/tustvold)\n[Yashvardhan Kukreja](https://github.com/yashvardhan-kukreja)\n\n## edge-21.3.2\n\nThis edge release is another release candidate for stable 2.10 and fixes some\nfinal bugs found in testing. A big thank you to users who have helped us\nidentity these issues!\n\n* Fixed an issue with the service profile validating webhook that prevented\n  service profiles from being added or updated\n* Updated the `check` command output hint anchors to match Linkerd component\n  names\n* Fixed a permission issue with the Viz extension's tap admin cluster role by\n  adding namespace listing to the allowed actions\n* Fixed an issue with the proxy where connections would not be torn down when\n  communicating with a defunct endpoint\n* Improved diagnostic logging in the proxy\n* Fixed an issue with the Viz extension's Prometheus template that prevented\n  users from specifying a log level flag for that component (thanks @n-oden!)\n* Fixed a template parsing issue that prevented users from specifying additional\n  ignored inbound parts through Helm's `--set` flag\n* Fixed an issue with the proxy where non-HTTP streams could sometimes hang due\n  to TLS buffering\n\n## edge-21.3.1\n\nThis edge release is another release candidate, bringing us closer to\n`stable-2.10.0`! It fixes the Helm install/upgrade procedure and ships some new\nCLI commands, among other improvements.\n\n* Fixed Helm install/upgrade, which was failing when not explicitly setting\n  `proxy.image.version`\n* Added a warning in the dashboard when viewing tap streams from resources that\n  don't have tap enabled\n* Added the command `linkerd viz list` to list meshed pods and indicate which can\n  be tapped, which need to be restarted before they can be tapped, and which\n  have tap disabled\n* Similarly, added the command `linkerd jaeger list` to list meshed pods and\n  indicate which will participate in tracing\n* Added the `--opaque-ports` flag to `linkerd inject` to specify the list of\n  opaque ports when injecting pods (and services)\n* Simplified the output of `linkerd jaeger check`, combining the checks for the\n  status of each component into a single check\n* Changed the destination component to receive the list of default opaque ports\n  set during install so that it's properly reflected during discovery\n* Moved the level of the proxy server's I/O-related \"Connection closed\" messages\n  from info to debug, which were not providing actionable information\n\n## edge-21.2.4\n\nThis edge is a release candidate for `stable-2.10.0`! It wraps up the functional\nchanges planned for the upcoming stable release. We hope you can help us test\nthis in your staging clusters so that we can address anything unexpected before\nan official stable.\n\nThis release introduces support for CLI extensions. The Linkerd `check` command\nwill now invoke each extension's `check` command so that users can check the\nhealth of their Linkerd installation and extensions with one command. Additional\ndocumentation will follow for developers interested in creating extensions.\n\nAdditionally, there is no longer a default list of ports skipped by the proxy.\nThese ports have been moved to opaque ports, meaning protocols like MySQL will\nbe encrypted by default and without user input.\n\n* Cleaned up entries in `values.yaml` by removing `do not edit` entries; they\n  are now hardcoded in the templates\n* Added the count of service profiles installed in a cluster to the Heartbeat\n  metrics\n* Fixed CLI commands which would unnecessarily print usage instructions after\n  encountering API errors (thanks @piyushsingariya!)\n* Fixed the `install` command so that it errors after detecting there is an\n  existing Linkerd installation in the cluster\n* Changed the identity controller to receive the trust anchor via environment\n  variable instead of by flag; this allows the certificate to be loaded from a\n  config map or secret (thanks @mgoltzsche!)\n* Updated the proxy to use TLS version 1.3; support for TLS 1.2 remains enabled\n  for compatibility with prior proxy versions\n* The opaque ports annotation is now supported on services and enables users to\n  use this annotation on mirrored services in multicluster installations\n* Reverted the renaming of the `mirror.linkerd.io` label\n* Ports `25,443,587,3306,5432,11211` have been removed from the default skip\n  ports; all traffic through those ports is now proxied and handled opaquely by\n  default\n* Errors configuring the firewall in CNI are propagated so that they can be\n  handled by the user\n* Removed Viz extension warnings from the `check --proxy` command when tap is\n  not configured for pods; this is now handled by the `viz tap` command\n* Added support for CLI extensions as well as ensuring their `check` commands\n  are invoked by Linkerd's `check` command\n* Moved the `metrics`, `endpoints`, and `install-sp` commands into subcommands\n  under the `diagnostics` command.\n* Removed the `linkerd-` prefix from non-cluster scoped resources in the Viz and\n  Jaeger extensions\n* Added the linkerd-await helper to all Linkerd containers so that the proxy can\n  initialize before the components start making outbound connections\n* Removed the `tcp_connection_duration_ms` histogram from the metrics export to\n  fix high cardinality issues that surfaced through high memory usage\n\n## edge-21.2.3\n\nThis release wraps up most of the functional changes planned for the upcoming\n`stable-2.10.0` release. Try this edge release in your staging cluster and\nlet us know if you see anything unexpected!\n\n* **Breaking change**: Changed the multicluster `Service`-export annotation\n  from `mirror.linkerd.io/exported` to `multicluster.linkerd.io/export`\n* Updated the proxy-injector to to set the `config.linkerd.io/opaque-ports`\n  annotation on newly-created `Service` objects when the annotation is set on\n  its parent `Namespace`\n* Updated the proxy-injector to ignore pods that have disabled\n  `automountServiceAccountToken` (thanks @jimil749)\n* Updated the proxy to log warnings when control plane components are\n  unresolveable\n* Updated the Destination controller to cache node topology metadata (thanks\n  @fpetkovski)\n* Updated the CLI to handle API errors without printing the CLI usage (thanks\n  @piyushsingariya)\n* Updated the Web UI to only display the \"Gateway\" sidebar link when the\n  multicluster extension is active\n* Fixed the Web UI on Chrome v88 (thanks @kellycampbell)\n* Improved `install` and `uninstall` behavior for extensions to prevent\n  control-plane components from being left in a broken state\n* Docker images are now hosted on the `cr.l5d.io` registry\n* Updated base docker images to buster-20210208-slim\n* Updated the Go version to 1.14.15\n* Updated the proxy to prevent outbound connections to localhost to protect\n  against traffic loops\n\n## edge-21.2.2\n\nThis edge release introduces support for multicluster TCP!\n\nThe `repair` command was added which will repopulate resources needed for\nupgrading from a `2.9.x` installation. There will be an error message during the\nupgrade process indicating that this command should be run so that users do not\nneed to guess.\n\nLastly, it contains a breaking change for Helm users. The `global` field has\nbeen removed from the Helm chart now that it is no longer needed. Users will\nneed to pass in the identity certificates again—along with any other\ncustomizations, no longer rooted at `global`.\n\n* **Breaking change**: Removed the `Global` field from the Linkerd Helm chart\n  now that it is unused because of the extension model\n* Added the `repair` command which will repopulate resources needed for properly\n  upgrading a Linkerd installation\n* Fixed the spelling of the `sidecarContainers` key in the Viz extension Helm\n  chart to match that of the template (thanks @n-oden!)\n* Added the `tapInjector.logLevel` key to the Viz extension helm chart so that\n  the log level of the component can be configured\n* Removed the `--disable-tap` flag from the `inject` command now that tap is no\n  longer part of the core installation (thanks @mayankshah1607!)\n* Changed proxy configuration to use fully-qualified DNS names to avoid extra\n  search paths in DNS resolutions\n* Changed the `check` command to include each installed extension's `check`\n  output; this allows users to check for proper configuration and installation\n  of Linkerd without running a command for each extension\n* Added proxy support for TCP traffic to the multicluster gateways\n\n## edge-21.2.1\n\nThis edge release continues improving the proxy's diagnostics and also avoids\ntiming out when the HTTP protocol detection fails. Additionally, old resource\nversions were upgraded to avoid warnings in k8s v1.19. Finally, it comes with\nlots of CLI improvements detailed below.\n\n* Improved the proxy's diagnostic metrics to help us get better insights into\n  services that are in fail-fast\n* Improved the proxy's HTTP protocol detection to prevent timeout errors\n* Upgraded CRD and webhook config resources to get rid of warnings in k8s v1.19\n  (thanks @mateiidavid!)\n* Added viz components into the Linkerd Health Grafana charts\n* Had the tap injector add a `viz.linkerd.io/tap-enabled` annotation when\n  injecting a pod, which allowed providing clearer feedback for the `linkerd\n  tap` command\n* Had the jaeger injector add a `jaeger.linkerd.io/tracing-enabled` annotation\n  when injecting a pod, which also allowed providing better feedback for the\n  `linkerd jaeger check` command\n* Improved the `linkerd uninstall` command so it fails gracefully when there\n  still are injected resources in the cluster (a `--force` flag was provided\n  too)\n* Moved the `linkerd profile --tap` functionality into a new command `linkerd\n  viz profile --tap`, given tap now belongs to the viz extension\n* Expanded the `linkerd viz check` command to include data-plane checks\n* Cleaned-up YAML in templates that was incompatible with SOPS (thanks\n  @tkms0106!)\n\n## edge-21.1.4\n\nThis edge release continues to polish the Linkerd extension model and improves\nthe robustness of the opaque transport.\n\n* Improved the consistency of behavior of the `check` commands between\n  Linkerd extensions\n* Fixed an issue where Linkerd extension commands could be run before the\n  extension was fully installed\n* Renamed some extension Helm charts for consistency:\n  * jaeger -> linkerd-jaeger\n  * linkerd2-multicluster -> linkerd-multicluster\n  * linkerd2-multicluster-link -> linkerd-multicluster-link\n* Fixed an issue that could cause the inbound proxy to fail meshed HTTP/1\n  requests from older proxies (from the stable-2.8.x vintage)\n* Changed opaque-port transport to be advertised via ALPN so that new proxies\n  will not initiate opaque-transport connections to proxies from prior edge\n  releases\n* Added inbound proxy transport metrics with `tls=\"passthru\"` when forwarding\n  non-mesh TLS connections\n* Thanks to @hs0210 for adding new unit tests!\n\n## edge-21.1.3\n\nThis edge release improves proxy diagnostics and recovery in situations where\nthe proxy is temporarily unable to route requests. Additionally, the `viz` and\n`multicluster` CLI sub-commands have been updated for consistency.\n\nFull release notes:\n\n* Added Helm-style `set`, `set-string`, `values`, `set-files` customization\n  flags for the `linkerd install` and `linkerd multicluster install` commands\n* Fixed an issue where `linkerd metrics` could return metrics for the incorrect\n  set of pods when there are overlapping label selectors\n* Added tap-injector to linkerd-viz which is responsible for adding the tap\n  service name environment variable to the Linkerd proxy container\n* Improved diagnostics when the proxy is temporarily unable to route requests\n* Made proxy recovery for a service more robust when the proxy is unable to\n  route requests, even when new requests are being received\n* Added `client` and `server` prefixes in the proxy logs for socket-level errors\n  to indicate which side of the proxy encountered the error\n* Improved jaeger-injector reliability in environments with many resources by\n  adding watch RBAC permissions\n* Added check to confirm whether the jaeger-injector pod is in running state\n  (thanks @yashvardhan-kukreja!)\n* Fixed a crash in the destination controller when EndpointSlices are enabled\n  (thanks @oleh-ozimok!)\n* Added a `linkerd viz check` sub-command to verify the states of the\n  `linkerd-viz` components\n* Added a `log-format` flag to optionally output the control plane component log\n  output as JSON (thanks @mo4islona!)\n* Updated the logic in the `metrics` and `profile` subcommands to use the\n  `namespace` specified by the `current-context` of the KUBECONFIG so that it is\n  no longer necessary to use the `--namespace` flag to query resources in the\n  current namespace. Queries for resources in namespaces other than the\n  current namespace still require the `--namespace` flag\n* Added new pod 'linkerd-metrics-api' set up by `linkerd viz install` that\n  manages all functionality dependent on Prometheus, thus removing most of the\n  dependencies on Prometheus from the linkerd core installation\n* Removed need to have linkerd-viz installed for the\n  `linkerd multicluster check` command to properly work.\n\n## edge-21.1.2\n\nThis edge release continues the work on decoupling non-core Linkerd components.\nCommands that use the viz extension i.e, `dashboard`, `edges`, `routes`,\n`stat`, `tap` and `top` are moved to the `viz` sub-command. These commands are still\navailable under root but are marked as deprecated and will be removed in a\nlater stable release.\n\nThis release also upgrades the proxy's dependencies to the Tokio v1 ecosystem.\n\n* Moved sub-commands that use the viz extension under `viz`\n* Started ignoring pods with `Succeeded` status when watching IP addresses\n  in destination. This allows the re-use of IPs of terminated pods\n* Support Bring your own Jaeger use-case by adding `collector.jaegerAddr` in\n  the Jaeger extension.\n* Fixed an issue with the generation of working manifests in the\n  `podAntiAffinity` use-case\n* Added support for the modification of proxy resources in the viz\n  extension through `values.yaml` in Helm and flags in CLI.\n* Improved error reporting for port-forward logic with namespace\n  and pod data, used across dashboard, checks, etc\n  (thanks @piyushsingariya)\n* Added support to disable the rendering of `linkerd-viz` namespace\n  resource in the viz extension (thanks @nlamirault)\n* Made service-profile generation work offline with `--ignore-cluster`\n  flag (thanks @piyushsingariya)\n* Upgraded the proxy's dependencies to the Tokio v1 ecosystem\n\n## edge-21.1.1\n\nThis edge release introduces a new \"opaque transport\" feature that allows the\nproxy to securely transport server-speaks-first and otherwise opaque TCP\ntraffic. Using the `config.linkerd.io/opaque-ports` annotation on pods and\nnamespaces, users can configure ports that should skip the proxy's protocol\ndetection.\n\nAdditionally, a new `linkerd-viz` extension has been introduced that separates\nthe installation of the Grafana, Prometheus, web, and tap components. This\nextension closely follows the Jaeger and multicluster extensions; users can\n`install` and `uninstall` with the `linkerd viz ..` command as well as configure\nfor HA with the `--ha` flag.\n\nThe `linkerd viz install` command does not have any cli flags to customize the\ninstall directly, but instead follows the Helm way of customization by using\nflags such as `set`, `set-string`, `values`, `set-files`.\n\nFinally, a new `/shutdown` admin endpoint that may only be accessed over the\nloopback network has been added. This allows batch jobs to gracefully terminate\nthe proxy on completion. The `linkerd-await` utility can be used to automate\nthis.\n\n* Added a new `linkerd multicluster check` command to validate that the\n  `linkerd-multicluster` extension is working correctly\n* Fixed description in the `linkerd edges` command (thanks @jsoref!)\n* Moved the Grafana, Prometheus, web, and tap components into a new Viz chart,\n  following the same extension model that multicluster and Jaeger follow\n* Introduced a new \"opaque transport\" feature that allows the proxy to securely\n  transport server-speaks-first and otherwise opaque TCP traffic\n* Removed the check comparing the `ca.crt` field in the identity issuer secret\n  and the trust anchors in the Linkerd config; these values being different is\n  not a failure case for the `linkerd check` command (thanks @cypherfox!)\n* Removed the Prometheus check from the `linkerd check` command since it now\n  depends on a component that is installed with the Viz extension\n* Fixed error messages thrown by the cert checks in `linkerd check` (thanks\n  @pradeepnnv!)\n* Added PodDisruptionBudgets to the control plane components so that they cannot\n  be all terminated at the same time during disruptions (thanks @tustvold!)\n* Fixed an issue that displayed the wrong `linkerd.io/proxy-version` when it is\n  overridden by annotations (thanks @mateiidavid!)\n* Added support for custom registries in the `linkerd-viz` helm chart (thanks\n  @jimil749!)\n* Renamed `proxy-mutator` to `jaeger-injector` in the `linkerd-jaeger` extension\n* Added a new `/shutdown` admin endpoint that may only be accessed over the\n  loopback network allowing batch jobs to gracefully terminate the proxy on\n  completion\n* Introduced the `linkerd identity` command, used to fetch the TLS certificates\n  for injected pods (thanks @jimil749)\n* Fixed an issue with the CNI plugin where it was incorrectly terminating and\n  emitting error events (thanks @mhulscher!)\n* Re-added support for non-LoadBalancer service types in the\n  `linkerd-multicluster` extension\n\n## edge-20.12.4\n\nThis edge release adds support for the `config.linkerd.io/opaque-ports`\nannotation on pods and namespaces, to configure ports that should skip the\nproxy's protocol detection. In addition, it adds new CLI commands related to the\n`linkerd-jaeger` extension, fixes bugs in the CLI `install` and `upgrade`\ncommands and Helm charts, and fixes a potential false positive in the proxy's\nHTTP protocol detection. Finally, it includes improvements in proxy performance\nand memory usage, including an upgrade for the proxy's dependency on the Tokio\nasync runtime.\n\n* Added support for the `config.linkerd.io/opaque-ports` annotation on pods and\n  namespaces, to indicate to the proxy that some ports should skip protocol\n  detection\n* Fixed an issue where `linkerd install --ha` failed to honor flags\n* Fixed an issue where `linkerd upgrade --ha` can override existing configs\n* Added missing label to the `linkerd-config-overrides` secret to avoid breaking\n  upgrades performed with the help of `kubectl apply --prune`\n* Added a missing icon to Jaeger Helm chart\n* Added new `linkerd jaeger check` CLI command to validate that the\n  `linkerd-jaeger` extension is working correctly\n* Added new `linkerd jaeger uninstall` CLI command to print the `linkerd-jaeger`\n  extension's resources so that they can be piped into `kubectl delete`\n* Fixed an issue where the `linkerd-cni` daemonset may not be installed on all\n  intended nodes, due to missing tolerations to the `linkerd-cni` Helm chart\n  (thanks @rish-onesignal!)\n* Fixed an issue where the `tap` APIServer would not refresh its certs\n  automatically when provided externally—like through cert-manager\n* Changed the proxy's cache eviction strategy to reduce memory consumption,\n  especially for busy HTTP/1.1 clients\n* Fixed an issue in the proxy's HTTP protocol detection which could cause false\n  positives for non-HTTP traffic\n* Increased the proxy's default dispatch timeout to 5 seconds to accommodate\n  connection pools which might open connections without immediately making a\n  request\n* Updated the proxy's Tokio dependency to v0.3\n\n## edge-20.12.3\n\nThis edge release is functionally the same as `edge-20.12.2`. It fixes an issue\nthat prevented the release build from occurring.\n\n## edge-20.12.2\n\n* Fixed an issue where the `proxy-injector` and `sp-validator` did not refresh\n  their certs automatically when provided externally—like through cert-manager\n* Added support for overrides flags to the `jaeger install` command to allow\n  setting Helm values when installing the Linkerd-jaeger extension\n* Added missing Helm values to the multicluster chart (thanks @DaspawnW!)\n* Moved tracing functionality to the `linkerd-jaeger` extension\n* Fixed various issues in developer shell scripts (thanks @joakimr-axis!)\n* Fixed an issue where `install --ha` was only partially applying the high\n  availability config\n* Updated RBAC API versions in the CNI chart (thanks @glitchcrab!)\n* Fixed an issue where TLS credentials are changed during upgrades, but the\n  Linkerd webhooks would not restart, leaving them to use older credentials and\n  fail requests\n* Stopped publishing the multicluster link chart as its primary use case is in\n  the `multicluster link` command and not being installed through Helm\n* Added service mirror error logs for when the multicluster gateway's hostname\n  cannot be resolved.\n\n## edge-20.12.1\n\nThis edge release continues the work of decoupling non-core Linkerd components\nby moving more tracing related functionality into the Linkerd-jaeger extension.\n\n* Continued work on moving tracing functionality from the main control plane\n  into the `linkerd-jaeger` extension\n* Fixed a potential panic in the proxy when looking up a socket's peer address\n  while under high load\n* Added automatic readme generation for charts (thanks @GMarkfjard!)\n* Fixed zsh completion for the CLI (thanks @jiraguha!)\n* Added support for multicluster gateways of types other than LoadBalancer\n  (thanks @DaspawnW!)\n\n## edge-20.11.5\n\nThis edge release improves the proxy's support high-traffic workloads. It also\ncontains the first steps towards decoupling non-core Linkerd components, the\nfirst iteration being a new `linkerd jaeger` sub-command for installing tracing.\nPlease note this is still a work in progress.\n\n* Addressed some issues reported around clients seeing max-concurrency errors by\n  increasing the default in-flight request limit to 100K pending requests\n* Have the proxy appropriately set `content-type` when synthesizing gRPC error\n  responses\n* Bumped the `proxy-init` image to `v1.3.8` which is based off of\n  `buster-20201117-slim` to reduce potential security vulnerabilities\n* No longer panic in rare cases when `linkerd-config` doesn't have an entry for\n  `Global` configs (thanks @hodbn!)\n* Work in progress: the `/jaeger` directory now contains the charts and commands\n  for installing the tracing component.\n\n## edge-20.11.4\n\n* Fixed an issue in the destination service where endpoints always included a\n  protocol hint, regardless of the controller label being present or not\n\n## edge-20.11.3\n\nThis edge release improves support for CNI by properly handling parameters\npassed to the `nsenter` command, relaxes checks on root and intermediate\ncertificates (following X509 best practices), and fixes two issues: one that\nprevented installation of the control plane into a custom namespace and one\nwhich failed to update endpoint information when a headless service is modified.\nThis release also improves linkerd proxy performance by eliminating unnecessary\nendpoint resolutions for TCP traffic and properly tearing down serverside\nconnections when errors occur.\n\n* Added HTTP/2 keepalive PING frames\n* Removed logic to avoid redundant TCP endpoint resolution\n* Fixed an issue where serverside connections were not torn down when an error\n  occurs\n* Updated `linkerd check` so that it doesn't attempt to validate the subject\n  alternative name (SAN) on root and intermediate certificates. SANs for leaf\n  certificates will continue to be validated\n* Fixed a CLI issue where the `linkerd-namespace` flag is not honored when\n  passed to the `install` and `upgrade` commands\n* Fixed an issue where the proxy does not receive updated endpoint information\n  when a headless service is modified\n* Updated the control plane Docker images to use `buster-20201117-slim` to\n  reduce potential security vulnerabilities\n* Updated the proxy-init container to `v1.3.7` which fixes CNI issues in certain\n  environments by properly parsing `nsenter` args\n\n## edge-20.11.2\n\nThis edge release reduces memory consumption of Linkerd proxies which maintain\nmany idle connections (such as Prometheus).  It also removes some obsolete\ncommands from the CLI and allows setting custom annotations on multicluster\ngateways.\n\n* Reduced the default idle connection timeout to 5s for outbound clients and\n  20s for inbound clients to reduce the proxy's memory footprint, especially on\n  Prometheus instances\n* Added support for setting annotations on the multicluster gateway in Helm\n  which allows setting the load balancer as internal (thanks @shaikatz!)\n* Removed the `get` and `logs` command from the CLI\n\n## stable-2.9.0\n\nThis release extends Linkerd's zero-config mutual TLS (mTLS) support to all TCP\nconnections, allowing Linkerd to transparently encrypt and authenticate all TCP\nconnections in the cluster the moment it's installed. It also adds ARM support,\nintroduces a new multi-core proxy runtime for higher throughput, adds support\nfor Kubernetes service topologies, and lots, lots more, as described below:\n\n* Proxy\n  * Performed internal improvements for lower latencies under high concurrency\n  * Reduced performance impact of logging, especially when the `debug` or\n    `trace` log levels are disabled\n  * Improved error handling for DNS errors encountered when discovering control\n    plane addresses; this can be common during installation before all\n    components have been started, allowing linkerd to continue to operate\n    normally in HA during node outages\n\n* Control Plane\n  * Added support for [topology-aware service\n    routing](https://kubernetes.io/docs/concepts/services-networking/service-topology/)\n    to the Destination controller; when providing service discovery updates to\n    proxies the Destination controller will now filter endpoints based on the\n    service's topology preferences\n  * Added support for the new Kubernetes\n    [EndpointSlice](https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/)\n    resource to the Destination controller; Linkerd can be installed with\n    `--enable-endpoint-slices` flag to use this resource rather than the\n    Endpoints API in clusters where this new API is supported\n\n* Dashboard\n  * Added new Spanish translations (please help us translate into your\n    language!)\n  * Added new section for exposing multicluster gateway metrics\n\n* CLI\n  * Renamed the `--addon-config` flag to `--config` to clarify this flag can be\n    used to set any Helm value\n  * Added fish shell completions to the `linkerd` command\n\n* Multicluster\n  * Replaced the single `service-mirror` controller with separate controllers\n    that will be installed per target cluster through `linkerd multicluster\n    link`\n  * Changed the mechanism for mirroring services: instead of relying on\n    annotations on the target services, now the source cluster should specify\n    which services from the target cluster should be exported by using a label\n    selector\n  * Added support for creating multiple service accounts when installing\n    multicluster with Helm to allow more granular revocation\n  * Added a multicluster `unlink` command for removing multicluster links\n\n* Prometheus\n  * Moved Linkerd's bundled Prometheus into an add-on (enabled by default); this\n    makes the Linkerd Prometheus more configurable, gives it a separate upgrade\n    lifecycle from the rest of the control plane, and allows users to\n    disable the bundled Prometheus instance\n  * The long-awaited Bring-Your-Own-Prometheus case has been finally addressed:\n    added `global.prometheusUrl` to the Helm config to have linkerd use an\n    external Prometheus instance instead of the one provided by default\n  * Added an option to persist data to a volume instead of memory, so that\n    historical metrics are available when Prometheus is restarted\n  * The helm chart can now configure persistent storage and limits\n\n* Other\n  * Added a new `linkerd.io/inject: ingress` annotation and accompanying\n    `--ingress` flag to the `inject` command, to configure the proxy to support\n    service profiles and enable per-route metrics and traffic splits for HTTP\n    ingress controllers\n  * Changed the type of the injector and tap API secrets to `kubernetes.io/tls`\n    so they can be provisioned by cert-manager\n  * Changed default docker image repository to `ghcr.io` from `gcr.io`; **Users\n    who pull the images into private repositories should take note of this\n    change**\n  * Introduced support for authenticated docker registries\n  * Simplified the way that Linkerd stores its configuration; configuration is\n    now stored as Helm values in the `linkerd-config` ConfigMap\n  * Added support for Helm configuration of per-component proxy resources\n    requests\n\nThis release includes changes from a massive list of contributors. A special\nthank-you to everyone who helped make this release possible: [Abereham G\nWodajie](https://github.com/Abrishges), [Alexander\nBerger](https://github.com/alex-berger), [Ali\nAriff](https://github.com/aliariff), [Arthur Silva\nSens](https://github.com/ArthurSens), [Chris\nCampbell](https://github.com/campbel), [Daniel\nLang](https://github.com/mavrick), [David Tyler](https://github.com/DaveTCode),\n[Desmond Ho](https://github.com/DesmondH0), [Dominik\nMünch](https://github.com/muenchdo), [George\nGarces](https://github.com/jgarces21), [Herrmann\nHinz](https://github.com/HerrmannHinz), [Hu Shuai](https://github.com/hs0210),\n[Jeffrey N. Davis](https://github.com/penland365), [Joakim\nRoubert](https://github.com/joakimr-axis), [Josh\nSoref](https://github.com/jsoref), [Lutz Behnke](https://github.com/cypherfox),\n[MaT1g3R](https://github.com/MaT1g3R), [Marcus Vaal](https://github.com/mvaal),\n[Markus](https://github.com/mbettsteller), [Matei\nDavid](https://github.com/mateiidavid), [Matt\nMiller](https://github.com/mmiller1), [Mayank\nShah](https://github.com/mayankshah1607),\n[Naseem](https://github.com/naseemkullah), [Nil](https://github.com/c-n-c),\n[OlivierB](https://github.com/olivierboudet), [Olukayode\nBankole](https://github.com/rbankole), [Paul\nBalogh](https://github.com/javaducky), [Rajat\nJindal](https://github.com/rajatjindal), [Raphael\nTaylor-Davies](https://github.com/tustvold), [Simon\nWeald](https://github.com/glitchcrab), [Steve\nGray](https://github.com/steve-gray), [Suraj\nDeshmukh](https://github.com/surajssd), [Tharun\nRajendran](https://github.com/tharun208), [Wei Lun](https://github.com/WLun001),\n[Zhou Hao](https://github.com/zhouhao3), [ZouYu](https://github.com/Hellcatlk),\n[aimbot31](https://github.com/aimbot31),\n[iohenkies](https://github.com/iohenkies), [memory](https://github.com/memory),\nand [tbsoares](https://github.com/tbsoares)\n\n## edge-20.11.1\n\nThis edge supersedes edge-20.10.6 as a release candidate for stable-2.9.0.\n\n* Fixed issue where the `check` command would error when there is no Prometheus\n  configured\n* Fixed recent regression that caused multicluster on EKS to not work properly\n* Changed the `check` command to warn instead of error when webhook certificates\n  are near expiry\n* Added the `--ingress` flag to the `inject` command which adds the recently\n  introduced `linkerd.io/inject: ingress` annotation\n* Fixed issue with upgrades where external certs would be fetched and stored\n  even though this does not happen on fresh installs with externally created\n  certs\n* Fixed issue with upgrades where the issuer cert expiration was being reset\n* Removed the `--registry` flag from the `multicluster install` command\n* Removed default CPU limits for the proxy and control plane components in HA\n  mode\n\n## edge-20.10.6\n\nThis edge supersedes edge-20.10.5 as a release candidate for stable-2.9.0. It\nadds a new `linkerd.io/inject: ingress` annotation to support service profiles\nand enable per-route metrics and traffic splits for HTTP ingress controllers\n\n* Added a new `linkerd.io/inject: ingress` annotation to configure the\n  proxy to support service profiles and enable per-route metrics and traffic\n  splits for HTTP ingress controllers\n* Reduced performance impact of logging in the proxy, especially when the\n  `debug` or `trace` log levels are disabled\n* Fixed spurious warnings logged by the `linkerd profile` CLI command\n\n## edge-20.10.5\n\nThis edge supersedes edge-20.10.4 as a release candidate for stable-2.9.0. It\nadds a fix for updating the destination service when there are no endpoints\n\n* Added a fix to clear the EndpointTranslator state when it gets a\n  `NoEndpoints` message. This ensures that the clients get the correct set of\n  endpoints during an update.\n\n## edge-20.10.4\n\nThis edge release is a release candidate for stable-2.9.0. For the proxy, there\nhave been changes to improve performance, remove unused code, and configure\nports that can be ignored by default. Also, this edge release adds enhancements\nto the multicluster configuration and observability, adds more translations to\nthe dashboard, and addresses a bug in the CLI.\n\n* Added more Spanish translations to the dashboard and more labels that can be\n  translated\n* Added support for creating multiple service accounts when installing\n  multicluster with Helm to allow more granular revocation\n* Renamed `global.proxy.destinationGetNetworks` to `global.clusterNetworks`.\n  This is a cluster-wide setting and can no longer be overridden per-pod\n* Fixed an empty multicluster Grafana graph which used a deprecated label\n* Added the control plane tracing ServiceAccounts to the linkerd-psp\n  RoleBinding so that it can be used in environments where PodSecurityPolicy\n  is enabled\n* Enhanced EKS support by adding `100.64.0.0/10` to the set of discoverable\n  networks\n* Fixed a bug in the way that the `--all-namespaces` flag is handled by the\n  `linkerd edges` command\n* Added a default set of ports to bypass the proxy for server-first, https,\n  and memcached traffic\n\n## edge-20.10.3\n\nThis edge release is a release candidate for stable-2.9.0.  It overhauls the\ndiscovery and routing logic implemented by the proxy, simplifies the way that\nLinkerd stores configuration, and adds new Helm values to configure additional\nlabels, annotations, and namespace selectors for webhooks.\n\n* Added podLabels and podAnnotations Helm values to allow adding additional\n  labels or annotations to Linkerd control plane pods (thanks @tustvold!)\n* Added namespaceSelector Helm value for configuring the namespace selector\n  used by admission webhooks (thanks @tustvold!)\n* Expanded the 'linkerd edges' command to show TCP connections\n* Overhauled the discovery and routing logic implemented by the proxy:\n  * The `l5d-dst-override` header is no longer honored\n  * When the application attempts to connect to a pod IP, the proxy no\n    longer load balances these requests among all pods in the service.\n    The proxy will now honor session-stickiness as selected by an\n    application-level load balancer\n  * `TrafficSplits` are only applied when a client targets a service's IP\n  * The proxy no longer performs DNS \"canonicalization\" to translate\n    relative host header names to a fully-qualified form\n* Simplified the way that Linkerd stores its configuration.  Configuration is\n  now stored as Helm values in the linkerd-config ConfigMap\n* Renamed the --addon-config flag to --config to clarify this flag can be used\n  to set any Helm value\n\n## edge-20.10.2\n\nThis edge release adds more improvements for mTLS for all TCP traffic.\nIt also includes significant internal improvements to the way Linkerd\nconfiguration is stored within the cluster.\n\n* Changed TCP metrics exported by the proxy to ensure that peer\n  identities are encoded via the `client_id` and `server_id` labels.\n* Removed the dependency of control plane components on `linkerd-config`\n* Updated the data structure `proxy-injector` uses to derive the configuration\n  used when injecting workloads\n\n## edge-20.10.1\n\nThis edge release includes a couple of external contributions towards\nimproved cert-manager support and Grafana charts fixes, among other\nenhancements.\n\n* Changed the type of the injector and tap API secrets to `kubernetes.io/tls`,\n  so they can be provisioned by cert-manager (thanks @cypherfox!)\n* Fixed the \"Kubernetes cluster monitoring\" Grafana dashboard that had a few\n  charts with incomplete data (thanks @aimbot31!)\n* Fixed the `service-mirror` multicluster component so that it retries\n  connections to the target cluster's Kubernetes API when it's not reachable,\n  instead of blocking\n* Increased the proxy's default timeout for DNS resolution to 500ms, as there\n  were reports that 100ms was too restrictive\n\n## edge-20.9.4\n\nThis edge release introduces support for authenticated docker registries and\nfixes a recent multicluster regression.\n\n* Fixed a regression in multicluster gateway configurations that would forbid\n  inbound gateway traffic\n* Upgraded bundled Grafana to v7.1.5\n* Enabled Jaeger receiver in collector configuration in Helm chart (thanks\n  @olivierboudet!)\n* Fixed skip port configuration being skipped in CNI plugin\n* Introduced support for authenticated docker registries (thanks @c-n-c!)\n\n## edge-20.9.3\n\nThis edge release includes fixes and updates for the control plane and CLI.\n\n* Added `--dest-cni-bin-dir` flag to the `linkerd install-cni` command, to\n  configure the directory on the host where the CNI binary will be placed\n* Removed `collector.name` and `jaeger.name` config fields from the tracing\n  addon\n* Updated Jaeger to 1.19.2\n* Fixed a warning about deprecated Go packages in controller container logs\n\n## edge-20.9.2\n\nThis edge release continues the work of adding support for mTLS for all TCP\ntraffic and changes the default container registry to `ghcr.io` from `gcr.io`.\n\nIf you are upgrading from `stable-2.8.x` with the Linkerd CLI using the\n`linkerd upgrade` command, you must add the `--addon-overwrite` flag to ensure\nthat the grafana image is properly set.\n\n* Removed the default timeout for ServiceProfiles so that ServiceProfile routes\n  behave the same as when there is no ServiceProfile definition\n* Changed default docker image repository to ghcr.io from gcr.io. **Users who\n  pull the images into private repositories should take note of this change**\n* Added endpoint labels to outbound TCP metrics to provide more context and\n  detail for the metrics, add load balancing to TCP connections\n  (bypassing kube-proxy), and secure the connection with mTLS when both\n  endpoints are meshed\n* Made unnamed ServiceProfile discovery configurable using the\n  `proxy.destinationGetNetworks` variable to set the\n  `LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS` variable in the proxy chart\n  template\n* Added TLS certificate validation for the Injector, SP Validator, and Tap\n  webhooks to the `linkerd check` command\n\n## edge-20.9.1\n\nThis edge release contains an important proxy update that allows linkerd to\ncontinue to operate normally in HA during node outages. We're also adding full\nKubernetes 1.19 support!\n\n* Improved the proxy's error handling for DNS errors encountered when\n  discovering control plane addresses, which can be common during installation,\n  before all components have been started\n* The destination and identity services had to be made headless in order to\n  support that new controller discovery (which now can leverage SRV records)\n* Use SAN fields when generating the linkerd webhook configs; this completes the\n  Kubernetes 1.19 support which enforces them\n* Fixed `linkerd check` for multicluster that was spuriously claiming the\n  absence of some resources\n* Improved the injection test cleanup (thanks @zhouhao3!)\n* Added ability to run the integration test suite using a cluster in an ARM\n  architecture (thanks @aliariff!)\n\n## edge-20.8.4\n\n* Fixed a problem causing the `enable-endpoint-slices` flag to not be persisted\n  when set via `linkerd upgrade` (thanks @Matei207!)\n* Removed SMI-Metrics templates and experimental sub-commands\n* Use `--frozen-lockfile` to avoid accidental update of dashboard JS\n  dependencies in CI (thanks @tharun208!)\n\n## edge-20.8.3\n\nThis edge release adds support for [topology-aware service routing][topology] to\nthe Destination controller. When providing service discovery updates to proxies,\nthe Destination controller will now filter endpoints based on the service's\ntopology preferences. Additionally, this release includes bug fixes for the\n`linkerd check` CLI command and web dashboard.\n\n* CLI\n  * `linkerd check` will no longer warn about a looser webhook failure policy in\n    HA mode\n* Controller\n  * Added support for [topology-aware service routing][topology] to the Destination\n    controller (thanks @Matei207)\n  * Changed the Destination controller to always return destination overrides\n    for service profiles when no traffic split is present\n* Web UI\n  * Fixed Tap `Authority` dropdown not being populated (thanks to @tharun208!)\n\n[topology]: https://kubernetes.io/docs/concepts/services-networking/service-topology/\n\n## edge-20.8.2\n\nThis edge release adds an internationalization framework to the dashboard,\nSpanish translations to the dashboard UI, and a `linkerd multicluster uninstall`\ncommand for graceful removal of the multicluster components.\n\n* Web UI\n  * Added Spanish translations to the dashboard\n  * Added a framework and documentation to simplify creation of new\n    translations\n* Multicluster\n  * Added a multicluster uninstall command\n  * Added a warning from `linkerd check --multicluster` if the multicluster\n    support is not installed\n\n## edge-20.8.1\n\nThis edge adds multi-arch support to Linkerd! Our docker images and CLI now\nsupport the amd64, arm64, and arm architectures.\n\n* Multicluster\n  * Added a multicluster unlink command for removing multicluster links\n  * Improved multicluster checks to be more informative when the remote API is\n    not reachable\n* Proxy\n  * Enabled a multi-threaded runtime to substantially improve latency especially\n    when the proxy is serving requests for many concurrent connections\n* Other\n  * Fixed an issue where the debug sidecar image was missing during upgrades\n    (thanks @javaducky!)\n  * Updated all control plane plane and proxy container images to be multi-arch\n    to support amd64, arm64, and arm (thanks @aliariff!)\n  * Fixed an issue where check was failing when DisableHeartBeat was set to true\n    (thanks @mvaal!)\n\n## edge-20.7.5\n\nThis edge brings a new approach to multicluster service mirror controllers and\nthe way services in target clusters are selected for mirroring.\n\nThe long-awaited Bring-Your-Own-Prometheus case has been finally addressed.\n\nMany other improvements from our great contributors are described below. Also\nnote progress is still being made under the covers for future support for Service\nTopologies (by @Matei207) and delivering image builds in multiple platforms (by\n@aliariff).\n\n* Multicluster\n  * Replaced the single `service-mirror` controller, with separate controllers\n    that will be installed per target cluster through `linkerd multicluster\n    link`. More info [here](https://github.com/linkerd/linkerd2/pull/4710).\n  * Changed the mechanism for mirroring services: instead of relying on\n    annotations on the target services, now the source cluster should specify\n    which services from the target cluster should be exported by using a label\n    selector. More info [here](https://github.com/linkerd/linkerd2/pull/4795).\n  * Added new section in the dashboard for exposing multicluster gateway metrics\n    (thanks @tharun208!)\n* Prometheus\n  * Added `global.prometheusUrl` to the Helm config to have linkerd use an\n    external Prometheus instance instead of the one provided by default.\n  * Added ability to declare sidecar containers in the Prometheus Helm config.\n    This allows adding components for cases like exporting logs to services\n    such as Cloudwatch, Stackdriver, Datadog, etc. (thanks @memory!)\n  * Upgraded Prometheus to the latest version (v2.19.3), which should consume\n    substantially less memory, among other benefits.\n* Other\n  * Fixed bug in `linkerd check` that was failing to wait for Prometheus to be\n    available right after having installed linkerd.\n  * Added ability to set `priorityClassName` for CNI DaemonSet pods, and to\n    install CNI in an existing namespace (both options provided through the CLI\n    and as Helm configs) (thanks @alex-berger!)\n  * Added support for overriding the proxy's inbound and outbound TCP connection\n    timeouts (thanks @mmiller1!)\n  * Added library support for dashboard i18n. Strings still need to be tagged\n    and translations to be added. More info\n    [here](https://github.com/linkerd/linkerd2/pull/4803).\n  * In some Helm charts, replaced the non-standard\n    `linkerd.io/helm-release-version` annotation with `checksum/config` for\n    forcing restarting the component during upgrades (thanks @naseemkullah!)\n  * Upgraded the proxy init-container to v1.3.4, which comes with an updated\n    debian-buster distro and will provide cleaner logs listing the iptables\n    rules applied.\n\n## edge-20.7.4\n\nThis edge release adds support for the new Kubernetes\n[EndpointSlice](https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/)\nresource to the Destination controller. Using the EndpointSlice API is more\nefficient for the Kubernetes control plane than using the Endpoints API. If\nthe cluster supports EndpointSlices (a beta feature in Kubernetes 1.17),\nLinkerd can be installed with `--enable-endpoint-slices` flag to use this\nresource rather than the Endpoints API.\n\n* Added fish shell completions to the `linkerd` command (thanks @WLun001!)\n* Enabled the support for EndpointSlices (thanks @Matei207!)\n* Separated Prometheus checks and made them runnable only when the add-on\n  is enabled\n\n## edge-20.7.3\n\n* Add preliminary support for EndpointSlices which will be usable in future\n  releases (thanks @Matei207!)\n* Internal improvements to the CI process for testing Helm installations\n\n## edge-20.7.2\n\nThis edge release moves Linkerd's bundled Prometheus into an add-on. This makes\nthe Linkerd Prometheus more configurable, gives it a separate upgrade lifecycle\nfrom the rest of the control plane, and will allow users to disable the bundled\nPrometheus instance. In addition, this release includes fixes for several\nissues, including a regression where the proxy would fail to report OpenCensus\nspans.\n\n* Prometheus is now an optional add-on, enabled by default\n* Custom tolerations can now be specified for control plane resources when\n  installing with Helm (thanks @DesmondH0!)\n* Evicted data plane pods are no longer considered to be failed by `linkerd\n  check --proxy`, fixing an issue where the check would be retried indefinitely\n  as long as evicted pods are present\n* Fixed a regression where proxy spans were not reported to OpenCensus\n* Fixed a bug where the proxy injector would fail to render skipped port lists\n  when installed with Helm\n* Internal improvements to the proxy for lower latencies under high concurrency\n* Thanks to @Hellcatlk and @surajssd for adding new unit tests and spelling\n  fixes!\n\n## edge-20.7.1\n\nThis edge release features the option to persist prometheus data to a volume\ninstead of memory, so that historical metrics are available when prometheus is\nrestarted. Additional changes are outlined in the bullet points below.\n\n* Some commands like `linkerd stat` would fail if any control plane components\n  were unhealthy, even when other replicas are healthy. The check conditions\n  for these commands have been improved\n* The helm chart can now configure persistent storage for Prometheus\n  (thanks @naseemkullah!)\n* The proxy log output format can now be configured to `plain` or `json` using\n  the `config.linkerd.io/proxy-log-format` annotation or the\n  `global.proxy.logFormat` value in the helm chart\n  (thanks again @naseemkullah!)\n* `linkerd install --addon-config=` now supports URLs in addition to local\n  files\n* The CNI Helm chart used the incorrect variable name to determine the `createdBy`\n  version tag. This is now controlled by `cniPluginVersion` in the helm chart\n* The proxy's default buffer size has been increased, which reduces latency when\n  the proxy has many concurrent clients\n\n## edge-20.6.4\n\nThis edge release moves the proxy onto a new version of the Tokio runtime. This\nallows us to more easily integrate with the ecosystem and may yield performance\nbenefits as well.\n\n* Upgraded the proxy's underlying Tokio runtime and its related libraries\n* Added support for PKCS8 formatted ECDSA private keys\n* Added support for Helm configuration of per-component proxy resources requests\n  and limits (thanks @cypherfox!)\n* Updated the `linkerd inject` command to throw an error while injecting\n  non-compliant pods (thanks @mayankshah1607)\n\n## stable-2.8.1\n\nThis release fixes multicluster gateways support on EKS.\n\n* The multicluster service-mirror has been extended to resolve DNS names for\n  target clusters when an IP address is not known.\n* Linkerd checks could fail when run from the dashboard. Thanks to @alex-berger\n  for providing a fix!\n* Have the service mirror controller check in `linkerd check` retry on failures.\n* As of this version we're including a Chocolatey package (Windows) next to the\n  other binaries in the release assets in GitHub.\n* Base images have been updated:\n  * debian:buster-20200514-slim\n  * grafana/grafana:7.0.3\n* The shell scripts under `bin` continued to be improved, thanks to @joakimr-axis!\n\n## edge-20.6.3\n\nThis edge release is a release candidate for stable-2.8.1. It includes a fix\nto support multicluster gateways on EKS.\n\n* The `config.linkerd.io/proxy-destination-get-networks` annotation configures\n  the networks for which a proxy can discover metadata. This is an advanced\n  configuration option that has security implications.\n* The multicluster service-mirror has been extended to resolve DNS names for\n  target clusters when an IP address it not known.\n* Linkerd checks could fail when run from the dashboard. Thanks to @alex-berger\n  for providing a fix!\n* The CLI will be published for Chocolatey (Windows) on future stable releases.\n* Base images have been updated:\n  * debian:buster-20200514-slim\n  * grafana/grafana:7.0.3\n\n## stable-2.8.0\n\nThis release introduces new a multi-cluster extension to Linkerd, allowing it\nto establish connections across Kubernetes clusters that are secure,\ntransparent to the application, and work with any network topology.\n\n* The CLI has a new set of `linkerd multicluster` sub-commands that provide\n  tooling to create the resources needed to discover services across\n  Kubernetes clusters.\n* The `linkerd multicluster gateways` command exposes gateway-specific\n  telemetry to supplement the existing `stat` and `tap` commands.\n* The Linkerd-provided Grafana instance remains enabled by default, but it can\n  now be disabled. When it is disabled, the Linkerd dashboard can be\n  configured to link to an alternate, externally-managed Grafana instance.\n* Jaeger & OpenCensus are configurable as an [add-on][addon-2.8.0]; and the\n  proxy has been improved to emit spans with labels that reflect its pod's\n  metadata.\n* The `linkerd-cni` component has been promoted from _experimental_ to\n  _stable_.\n* `linkerd profile --open-api` now honors the `x-linkerd-retryable` and\n  `x-linkerd-timeout` OpenAPI annotations.\n* The Helm chart continues to become more flexible and modular, with new\n  Prometheus configuration options. More information is available in the\n  [Helm chart README][helm-2.8.0].\n* gRPC stream error handling has been improved so that transport errors\n  are indicated to the client with a `grpc-status: UNAVAILABLE` trailer.\n* The proxy's memory footprint could grow significantly when\n  server-speaks-first-protocol connections hit the proxy. Now, a timeout is\n  in place to prevent these connections from consuming resources.\n* After benchmarking the proxy in high-concurrency situations, the inbound\n  proxy has been improved to reduce contention, improving latency and\n  reducing spurious timeouts.\n* The proxy could fail requests to services that had only 1 request every 60\n  seconds. This race condition has been eliminated.\n* Finally, users reported that ingress misconfigurations could cause the proxy\n  to consume an entire CPU which could lead to timeouts. The proxy now\n  attempts to prevent the most common traffic-loop scenarios to protect against\n  this.\n\n_**NOTE**_: Linkerd's `multicluster` extension does not yet work on Amazon\nEKS. We expect to follow this release with a stable-2.8.1 to address this\nissue. Follow [#4582](https://github.com/linkerd/linkerd2/pull/4582) for updates.\n\nThis release includes changes from a massive list of contributors. A special\nthank-you to everyone who helped make this release possible: @aliariff,\n@amariampolskiy, @arminbuerkle, @arthursens, @christianhuening,\n@christyjacob4, @cypherfox, @daxmc99, @dr0pdb, @drholmie, @hydeenoble,\n@joakimr-axis, @jpresky, @kohsheen1234, @lewiscowper, @lundbird, @matei207,\n@mayankshah1607, @mmiller1, @naseemkullah, @sannimichaelse, & @supra08.\n\n[addon-2.8.0]: https://github.com/linkerd/linkerd2/blob/4219955bdb5441c5fce192328d3760da13fb7ba1/charts/linkerd2/README.md#add-ons-configuration\n[helm-2.8.0]: https://github.com/linkerd/linkerd2/blob/4219955bdb5441c5fce192328d3760da13fb7ba1/charts/linkerd2/README.md\n\n## edge-20.6.2\n\nThis edge release is our second release candidate for `stable-2.8`, including\nvarious fixes and improvements around multicluster support.\n\n* CLI\n  * Fixed bad output in the `linkerd multicluster gateways` command\n  * Improved the error returned when running the CLI with no KUBECONFIG path set\n    (thanks @Matei207!)\n* Controller\n  * Fixed issue where mirror service wasn't created when paired to a gateway\n    whose external IP wasn't yet provided\n  * Fixed issue where updating the gateway identity annotation wasn't propagated\n    back into the mirror gateway endpoints object\n  * Fixed issue where updating the gateway ports wasn't reflected in the gateway\n    mirror service\n  * Increased the log level for some of the service mirror events\n  * Changed the nginx gateway config so that it runs as non-root and denies all\n    requests to locations other than the probe path\n* Web UI\n  * Fixed multicluster Grafana dashboard\n* Internal\n  * Added flag in integration tests to dump fixture diffs into a separate\n    directory (thanks @cypherfox!)\n\n## edge-20.6.1\n\nThis edge release is a release candidate for `stable-2.8`! It introduces several\nimprovements and fixes for multicluster support.\n\n* CLI\n  * Added multicluster daisy chain checks to `linkerd check`\n  * Added list of successful gateways in multicluster checks section of `linkerd\n    check`\n* Controller\n  * Renamed `nginx-configuration` ConfigMap to `linkerd-gateway-config` (please\n    manually remove the former if upgrading from an earlier multicluster\n    install, thanks @mayankshah1607!)\n  * Renamed multicluster gateway ports to `mc-gateway` and `mc-probe`\n  * Fixed Service Profiles routes for `linkerd-prometheus`\n* Internal\n  * Fixed shellcheck errors in all `bin/` scripts (thanks @joakimr-axis!)\n* Helm\n  * Added support for `linkerd mc allow`\n  * Added ability to disable secret resources for self-signed certs (thanks\n    @cypherfox!)\n* Proxy\n  * Modified the `linkerd-gateway` component to use the inbound proxy, rather\n    than nginx, for gateway; this allows Linkerd to detect loops and propagate\n    identity\n\n## edge-20.5.5\n\nThis edge release adds refinements to the Linkerd multicluster implementation,\nadds new health checks for the tracing add-on, and addresses an issue in which\noutbound requests from the proxy result in looping behavior.\n\n* CLI\n  * Added the `multicluster` command along with subcommands to configure and\n    deploy Linkerd workloads which enable services to be mirrored across\n    clusters\n  * Added health-checks for tracing add-on\n* Proxy\n  * Added logic to prevent loops in outbound requests\n\n## edge-20.5.4\n\n* CLI\n  * Fixed the display of the meshed pod column for non-selector services in\n    `linkerd stat` output\n  * Added an `addon-overwrite` upgrade flag which allows users to overwrite the\n    existing addon config rather than merging into it\n  * Added a `--close-wait-timeout` inject flag which sets the\n    `nf_conntrack_tcp_timeout_close_wait` property which can be used to mitigate\n    connection issues with application that hold half-closed sockets\n* Controller\n  * Restricted the service-mirror's RBAC permissions so that it no longer is\n    able to read secrets in all namespaces\n  * Moved many multicluster components into the `linkerd-multicluster` namespace\n    by default\n  * Added multicluster gateway mirror services to allow multicluster liveness\n    probes to work in private networks\n  * Fixed an issue where multicluster gateway mirror services could be\n    incorrectly deleted during a resync\n* Internal\n  * Fixed many style issues in build scripts (thanks @joakimr-axis!)\n* Helm\n  * Added `global.grafanaUrl` variable to allow using an existing Grafana\n    installation\n\n## edge-20.5.3\n\n* Controller\n  * Added a Grafana dashboard for tracking multi-cluster traffic metrics\n  * Added health checks for the Grafana add-on, under a separate section\n  * Fixed issues when updating a remote multi-cluster gateway\n\n* Proxy\n  * Added special special handling for I/O errors in HTTP responses so that an\n    `errno` label is included to describe the underlying errors in the proxy's\n    metrics\n\n* Internal\n  * Started gathering stats of CI runs for aggregating CI health metrics\n\n## edge-20.5.2\n\nThis edge release contains everything required to get up and running with\nmulticluster. For a tutorial on how to do that, check out the\n[documentation](https://linkerd.io/2/features/multicluster_support/).\n\n* CLI\n  * Added a section to the `linkerd check` that validates that all clusters\n    part of a multicluster setup have compatible trust anchors\n  * Modified the `inkerd cluster export-service` command to work by\n    transforming yaml instead of modifying cluster state\n  * Added functionality that allows the `linkerd cluster export-service`\n    command to operate on lists of services\n* Controller\n  * Changed the multicluster gateway to always require TLS on connections\n    originating from outside the cluster\n  * Removed admin server timeouts from control plane components, thereby\n    fixing a bug that can cause liveness checks to fail\n* Helm\n  * Moved Grafana templates into a separate add-on chart\n* Proxy\n  * Improved latency under high-concurrency use cases.\n\n## edge-20.5.1\n\n* CLI\n  * Fixed all commands to use kubeconfig's default namespace if specified\n    (thanks @Matei207!)\n  * Added multicluster checks to the `linkerd check` command\n  * Hid development flags in the `linkerd install` command for release builds\n* Controller\n  * Added ability to configure Prometheus Alertmanager as well as recording\n    and alerting rules on the Linkerd Prometheus (thanks @naseemkullah!)\n  * Added ability to add more commandline flags to the Prometheus command\n    (thanks @naseemkullah!)\n* Web UI\n  * Fixed TrafficSplit detail page not loading\n  * Added Jaeger links to the dashboard when the tracing addon is enabled\n* Proxy\n  * Modified internal buffering to avoid idling out services as a request\n    arrives, fixing failures for requests that are sent exactly once per\n    minute--such as Prometheus scrapes\n\n## edge-20.4.5\n\nThis edge release includes several new CLI commands for use with multi-cluster\ngateways, and adds liveness checks and metrics for gateways. Additionally, it\nmakes the proxy's gRPC error-handling behavior more consistent with other\nimplementations, and includes a fix for a bug in the web UI.\n\n* CLI\n  * Added `linkerd cluster setup-remote` command for setting up a\n    multi-cluster gateway\n  * Added `linkerd cluster gateways` command to display stats for\n    multi-cluster gateways\n  * Changed `linkerd cluster export-service` to modify a provided YAML file\n    and output it, rather than mutating the cluster\n* Controller\n  * Added liveness checks and Prometheus metrics for multi-cluster gateways\n  * Changed the proxy injector to configure proxies to do destination lookups\n    for IPs in the private IP range\n* Web UI\n  * Fixed errors when viewing resource detail pages\n* Internal\n  * Created script and config to build a Linkerd CLI Chocolatey package for\n    Windows users, which will be published with stable releases (thanks to\n    @drholmie!)\n* Proxy\n  * Changed the proxy to set a `grpc-status: UNAVAILABLE` trailer when a gRPC\n    response stream is interrupted by a transport error\n\n## edge-20.4.4\n\nThis edge release fixes a packaging issue in `edge-20.4.3`.\n\n_From `edge.20.4.3` release notes_:\n\nThis edge release adds functionality to the CLI to output more detail and\nincludes changes which support the multi-cluster functionality. Also, the helm\nsupport has been expanded to make installation more configurable. Finally, the\nHA reliability is improved by ensuring that control plane pods are restarted\nwith a rolling strategy\n\n* CLI\n  * Added output to the `linkerd check --proxy` command to list all data plane\n    pods which are not up-to-date rather than just printing the first one it\n    encounters\n  * Added a `--proxy` flag to the `linkerd version` command which lists all\n    proxy versions running in the cluster and the number of pods running each\n    version\n  * Lifted requirement of using --unmeshed for linkerd stat when querying\n    TrafficSplit resources\n  * Added support for multi-stage installs with Add-Ons\n* Controller\n  * Added a rolling update strategy to Linkerd deployments that have multiple\n    replicas during HA deployments to ensure that at most one pod begins\n    terminating before a new pod ready is ready\n  * Added a new label for the proxy injector to write to the template,\n    `linkerd.io/workload-ns` which indicates the namespace of the workload/pod\n* Internal\n  * Added a [security\n    policy](https://help.github.com/en/github/managing-security-vulnerabilities/adding-a-security-policy-to-your-repository)\n    to facilitate conversations around security\n* Helm\n  * Changed charts to use downwardAPI to mount labels to the proxy container\n    making them easier to identify\n* Proxy\n  * Changed the Linkerd proxy endpoint for liveness to use the new `/live`\n    admin endpoint instead of the `/metrics` endpoint, because the `/live`\n    endpoint returns a smaller payload\n  * Added a per-endpoint authority-override feature to support multi-cluster\n    gateways\n\n## edge-20.4.3\n\n**This release is superseded by `edge-20.4.4`**\n\nThis edge release adds functionality to the CLI to output more detail and\nincludes changes which support the multi-cluster functionality. Also, the helm\nsupport has been expanded to make installation more configurable. Finally, the\nHA reliability is improved by ensuring that control plane pods are restarted\nwith a rolling strategy\n\n* CLI\n  * Added output to the `linkerd check --proxy` command to list all data plane\n    pods which are not up-to-date rather than just printing the first one it\n    encounters\n  * Added a `--proxy` flag to the `linkerd version` command which lists all\n    proxy versions running in the cluster and the number of pods running each\n    version\n  * Lifted requirement of using --unmeshed for linkerd stat when querying\n    TrafficSplit resources\n  * Added support for multi-stage installs with Add-Ons\n* Controller\n  * Added a rolling update strategy to Linkerd deployments that have multiple\n    replicas during HA deployments to ensure that at most one pod begins\n    terminating before a new pod ready is ready\n  * Added a new label for the proxy injector to write to the template,\n    `linkerd.io/workload-ns` which indicates the namespace of the workload/pod\n* Internal\n  * Added a [security\n    policy](https://help.github.com/en/github/managing-security-vulnerabilities/adding-a-security-policy-to-your-repository)\n    to facilitate conversations around security\n* Helm\n  * Changed charts to use downwardAPI to mount labels to the proxy container\n    making them easier to identify\n* Proxy\n  * Changed the Linkerd proxy endpoint for liveness to use the new `/live`\n    admin endpoint instead of the `/metrics` endpoint, because the `/live`\n    endpoint returns a smaller payload\n  * Added a per-endpoint authority-override feature to support multi-cluster\n    gateways\n\n## edge-20.4.2\n\nThis release brings a number of CLI fixes and Controller improvements.\n\n* CLI\n  * Fixed a bug that caused pods to crash after upgrade if\n    `--skip-outbound-ports` or `--skip-inbound-ports` were used\n  * Added `unmeshed` flag to the `stat` command, such that unmeshed resources\n    are only displayed if the user opts-in\n  * Added a `--smi-metrics` flag to `install`, to allow installation of the\n    experimental `linkerd-smi-metrics` component\n  * Fixed a bug in `linkerd stat`, causing incorrect output formatting when\n    using the `--o wide` flag\n  * Fixed a bug, causing `linkerd uninstall` to fail when attempting to delete\n    PSPs\n* Controller\n  * Improved the anti-affinity of `linkerd-smi-metrics` deployment to avoid\n    pod scheduling problems during `upgrade`\n  * Improved endpoints change detection in the `linkerd-destination` service,\n    enabling mirrored remote services to change cluster gateways\n  * Added `operationID` field to tap OpenAPI response to prevent issues during\n    upgrade from 2.6 to 2.7\n* Proxy\n  * Added a new protocol detection timeout to prevent clients from consuming\n    resources indefinitely when not sending any data\n\n## edge-20.4.1\n\nThis release introduces some cool new functionalities, all provided by our\nawesome community of contributors! Also two bugs were fixed that were\nintroduced since edge-20.3.2.\n\n* CLI\n  * Added `linkerd uninstall` command to uninstall the control plane (thanks\n    @Matei207!)\n  * Fixed a bug causing `linkerd routes -o wide` to not show the proper actual\n    success rate\n* Controller\n  * Fail proxy injection if the pod spec has `automountServiceAccountToken`\n    disabled (thanks @mayankshah1607!)\n* Web UI\n  * Added a route dashboard to Grafana (thanks @lundbird!)\n* Proxy\n  * Fixed a bug causing the proxy's inbound to spuriously return 503 timeouts\n\n## edge-20.3.4\n\nThis release introduces several fixes and improvements to the CLI.\n\n* CLI\n  * Added support for kubectl-style label selectors in many CLI commands\n    (thanks @mayankshah1607!)\n  * Fixed the path regex in service profiles generated from proto files\n    without a package name (thanks @amariampolskiy!)\n  * Fixed an error when injecting Cronjobs that have no metadata\n  * Relaxed the clock skew check to match the default node heartbeat interval\n    on Kubernetes 1.17 and made this check a warning\n  * Fixed a bug where the linkerd-smi-metrics pod could not be created on\n    clusters with pod security policy enabled\n* Internal\n  * Upgraded tracing components to more recent versions and improved resource\n    defaults (thanks @Pothulapati!)\n\n## edge-20.3.3\n\nThis release introduces new experimental CLI commands for querying metrics\nusing the Service Mesh Interface (SMI) and for multi-cluster support via\nservice mirroring.\n\nIf you would like to learn more about service mirroring or SMI, or are\ninterested in experimenting with these features, please join us in [Linkerd\nSlack](https://slack.linkerd.io) for help and feedback.\n\n* CLI\n  * Added experimental `linkerd cluster` commands for managing multi-cluster\n    service mirroring\n  * Added the experimental `linkerd alpha clients` command, which uses the\n    smi-metrics API to display client-side metrics from each of a resource's\n    clients\n  * Added retries to some `linkerd check` checks to prevent spurious failures\n    when run immediately after cluster creation or Linkerd installation\n\n## edge-20.3.2\n\nThis release introduces substantial proxy improvements as well as new\nobservability and security functionality.\n\n* CLI\n  * Added the `linkerd alpha stat` command, which uses the smi-metrics API;\n    the latter enables access to metrics to be controlled with RBAC\n* Controller\n  * Added support for configuring service profile timeouts\n    `(x-linkerd-timeout)` via OpenAPI spec (thanks @lewiscowper!)\n* Web UI\n  * Improved the Grafana dashboards to use a globing operator for Prometheus\n    in order to avoid producing queries that are too large (thanks @mmiller1!)\n* Helm\n  * Improved the `linkerd2` chart README (thanks @lundbird!)\n* Proxy\n  * Fixed a bug that could cause log levels to be processed incorrectly\n\n## edge-20.3.1\n\nThis release introduces new functionality mainly focused around observability\nand multi-cluster support via `service mirroring`.\n\nIf you would like to learn more about `service mirroring` or are interested in\nexperimenting with this feature, please join us in [Linkerd\nSlack](https://slack.linkerd.io) for help and feedback.\n\n* CLI\n  * Improved the `linkerd check` command to check for extension server\n    certificate (thanks @christyjacob4!)\n* Controller\n  * Removed restrictions preventing Linkerd from injecting proxies into\n    Contour (thanks @alfatraining!)\n  * Added an experimental version of a service mirroring controller, allowing\n    discovery of services on remote clusters.\n* Web UI\n  * Fixed a bug causing incorrect Grafana links to be rendered in the web\n    dashboard.\n* Proxy\n  * Fixed a bug that could cause the proxy's load balancer to stop processing\n    updates from service discovery.\n\n## edge-20.2.3\n\nThis release introduces the first optional add-on `tracing`, added through the\nnew add-on model!\n\nThe existing optional `tracing` components Jaeger and OpenCensus can now be\ninstalled as add-on components.\n\nThere will be more information to come about the new add-on model, but please\nrefer to the details of [#3955](https://github.com/linkerd/linkerd2/pull/3955)\nfor how to get started.\n\n* CLI\n  * Added the `linkerd diagnostics` command to get metrics only from the\n    control plane, excluding metrics from the data plane proxies (thanks\n    @srv-twry!)\n  * Added the `linkerd install --prometheus-image` option for installing a\n    custom Prometheus image (thanks @christyjacob4!)\n  * Fixed an issue with `linkerd upgrade` where changes to the `Namespace`\n    object were ignored (thanks @supra08!)\n* Controller\n  * Added the `tracing` add-on which installs Jaeger and OpenCensus as add-on\n    components (thanks @Pothulapati!!)\n* Proxy\n  * Increased the inbound router's default capacity from 100 to 10k to\n    accommodate environments that have a high cardinality of virtual hosts\n    served by a single pod\n* Web UI\n  * Fixed styling in the CallToAction banner (thanks @aliariff!)\n\n## edge-20.2.2\n\nThis release includes the results from continued profiling & performance\nanalysis on the Linkerd proxy. In addition to modifying internals to prevent\nunwarranted memory growth, new metrics were introduced to aid in debugging and\ndiagnostics.\n\nAlso, Linkerd's CNI plugin is out of experimental, check out the docs at\n<https://linkerd.io/2/features/cni/> !\n\n* CLI\n  * Added support for label selectors in the `linkerd stat` command (thanks\n    @mayankshah1607!)\n  * Added scrolling functionality to the `linkerd top` output (thanks\n    @kohsheen1234!)\n  * Fixed bug in `linkerd metrics` that was causing a panic when\n    port-forwarding failed (thanks @mayankshah1607!)\n  * Added check to `linkerd check` verifying the number of replicas for\n    Linkerd components in HA (thanks @mayankshah1607!)\n  * Unified trust anchors terminology across the CLI commands\n  * Removed some messages from `linkerd upgrade`'s output that are no longer\n    relevant (thanks @supra08!)\n\n* Controller\n  * Added support for configuring service profile retries\n    `(x-linkerd-retryable)` via OpenAPI spec (thanks @kohsheen1234!)\n  * Improved traffic split metrics so sources in all namespaces are shown, not\n    just traffic from the traffic split's own namespace\n  * Improved linkerd-identity's logs and events to help diagnosing certificate\n    validation issues (thanks @mayankshah1607!)\n\n* Proxy\n  * Added `request_errors_total` metric exposing the number of requests that\n    receive synthesized responses due to proxy errors\n\n* Helm\n  * Added a new `enforcedHostRegexp` variable to allow configuring the\n    linkerd-web component enforced host (that was previously introduced to\n    protect against DNS rebinding attacks) (thanks @sannimichaelse!)\n\n* Internal\n  * Removed various es-lint warnings from the dashboard code (thanks\n    @christyjacob4 and @kohsheen1234!)\n  * Fixed go module file syntax (thanks @daxmc99!)\n\n## stable-2.7.0\n\nThis release adds support for integrating Linkerd's PKI with an external\ncertificate issuer such as [`cert-manager`] as well as streamlining the\ncertificate rotation process in general. For more details about cert-manager\nand certificate rotation, see the\n[docs](https://linkerd.io/2/tasks/use_external_certs/). This release also\nincludes performance improvements to the dashboard, reduced memory usage of\nthe proxy, various improvements to the Helm chart, and much much more.\n\nTo install this release, run: `curl https://run.linkerd.io/install | sh`\n\n**Upgrade notes**: This release includes breaking changes to our Helm charts.\nPlease see the [upgrade\ninstructions](https://linkerd.io/2/tasks/upgrade/#upgrade-notice-stable-270).\n\n**Special thanks to**: @alenkacz, @bmcstdio, @daxmc99, @droidnoob, @ereslibre,\n@javaducky, @joakimr-axis, @JohannesEH, @KIVagant, @mayankshah1607,\n@Pothulapati, and @StupidScience!\n\n**Full release notes**:\n\n* CLI\n  * Updated the mTLS trust anchor checks to eliminate false positives caused\n    by extra trailing spaces\n  * Reduced the severity level of the Linkerd version checks, so that they\n    don't fail when the external version endpoint is unreachable (thanks\n    @mayankshah1607!)\n  * Added a new `tap` APIService check to aid with uncovering Kubernetes API\n    aggregation layer issues (thanks @droidnoob!)\n  * Introduced CNI checks to confirm the CNI plugin is installed and ready;\n    this is done through `linkerd check --pre --linkerd-cni-enabled` before\n    installation and `linkerd check` after installation if the CNI plugin is\n    present\n  * Added support for the `--as-group` flag so that users can impersonate\n    groups for Kubernetes operations (thanks @mayankshah1607!)\n  * Added HA specific checks to `linkerd check` to ensure that the\n    `kube-system` namespace has the\n    `config.linkerd.io/admission-webhooks:disabled` label set\n  * Fixed a problem causing the presence of unnecessary empty fields in\n    generated resource definitions (thanks @mayankshah1607)\n  * Added the ability to pass both port numbers and port ranges to\n    `--skip-inbound-ports` and `--skip-outbound-ports` (thanks to @javaducky!)\n  * Increased the comprehensiveness of `linkerd check --pre`\n  * Added TLS certificate validation to `check` and `upgrade` commands\n  * Added support for injecting CronJobs and ReplicaSets, as well as the\n    ability to use them as targets in the CLI subcommands\n  * Introduced the new flags `--identity-issuer-certificate-file`,\n    `--identity-issuer-key-file` and `identity-trust-anchors-file` to `linkerd\n    upgrade` to support trust anchor and issuer certificate rotation\n  * Added a check that ensures using `--namespace` and `--all-namespaces`\n    results in an error as they are mutually exclusive\n  * Added a `Dashboard.Replicas` parameter to the Linkerd Helm chart to allow\n    configuring the number of dashboard replicas (thanks @KIVagant!)\n  * Removed redundant service profile check (thanks @alenkacz!)\n  * Updated `uninject` command to work with namespace resources (thanks\n    @mayankshah1607!)\n  * Added a new `--identity-external-issuer` flag to `linkerd install` that\n    configures Linkerd to use certificates issued by an external certificate\n    issuer (such as `cert-manager`)\n  * Added support for injecting a namespace to `linkerd inject` (thanks\n    @mayankshah1607!)\n  * Added checks to `linkerd check --preinstall` ensuring Kubernetes Secrets\n    can be created and accessed\n  * Fixed `linkerd tap` sometimes displaying incorrect pod names for unmeshed\n    IPs that match multiple running pods\n  * Made `linkerd install --ignore-cluster` and `--skip-checks` faster\n  * Fixed a bug causing `linkerd upgrade` to fail when used with\n    `--from-manifest`\n  * Made `--cluster-domain` an install-only flag (thanks @bmcstdio!)\n  * Updated `check` to ensure that proxy trust anchors match configuration\n       (thanks @ereslibre!)\n  * Added condition to the `linkerd stat` command that requires a window size\n    of at least 15 seconds to work properly with Prometheus\n* Controller\n  * Fixed an issue where an override of the Docker registry was not being\n    applied to debug containers (thanks @javaducky!)\n  * Added check for the Subject Alternate Name attributes to the API server\n    when access restrictions have been enabled (thanks @javaducky!)\n  * Added support for arbitrary pod labels so that users can leverage the\n    Linkerd provided Prometheus instance to scrape for their own labels\n    (thanks @daxmc99!)\n  * Fixed an issue with CNI config parsing\n  * Fixed a race condition in the `linkerd-web` service\n  * Updated Prometheus to 2.15.2 (thanks @Pothulapati)\n  * Increased minimum kubernetes version to 1.13.0\n  * Added support for pod ip and service cluster ip lookups in the destination\n    service\n  * Added recommended kubernetes labels to control-plane\n  * Added the `--wait-before-exit-seconds` flag to linkerd inject for the\n    proxy sidecar to delay the start of its shutdown process (a huge commit\n    from @KIVagant, thanks!)\n  * Added a pre-sign check to the identity service\n  * Fixed inject failures for pods with security context capabilities\n  * Added `conntrack` to the `debug` container to help with connection\n    tracking debugging\n  * Fixed a bug in `tap` where mismatch cluster domain and trust domain caused\n    `tap` to hang\n  * Fixed an issue in the `identity` RBAC resource which caused start up\n    errors in k8s 1.6 (thanks @Pothulapati!)\n  * Added support for using trust anchors from an external certificate issuer\n    (such as `cert-manager`) to the `linkerd-identity` service\n  * Added support for headless services (thanks @JohannesEH!)\n* Helm\n  * **Breaking change**: Renamed `noInitContainer` parameter to `cniEnabled`\n  * **Breaking Change** Updated Helm charts to follow best practices (thanks\n    @Pothulapati and @javaducky!)\n  * Fixed an issue with `helm install` where the lists of ignored inbound and\n    outbound ports would not be reflected\n  * Fixed the `linkerd-cni` Helm chart not setting proper namespace\n    annotations and labels\n  * Fixed certificate issuance lifetime not being set when installing through\n    Helm\n  * Updated the helm build to retain previous releases\n  * Moved CNI template into its own Helm chart\n* Proxy\n  * Fixed an issue that could cause the OpenCensus exporter to stall\n  * Improved error classification and error responses for gRPC services\n  * Fixed a bug where the proxy could stop receiving service discovery\n    updates, resulting in 503 errors\n  * Improved debug/error logging to include detailed contextual information\n  * Fixed a bug in the proxy's logging subsystem that could cause the proxy to\n    consume memory until the process is OOM killed, especially when the proxy\n    was configured to log diagnostic information\n  * Updated proxy dependencies to address RUSTSEC-2019-0033,\n    RUSTSEC-2019-0034, and RUSTSEC-2020-02\n* Web UI\n  * Fixed an error when refreshing an already open dashboard when the Linkerd\n    version has changed\n  * Increased the speed of the dashboard by pausing network activity when the\n    dashboard is not visible to the user\n  * Added support for CronJobs and ReplicaSets, including new Grafana\n    dashboards for them\n  * Added `linkerd check` to the dashboard in the `/controlplane` view\n  * Added request and response headers to the `tap` expanded view in the\n    dashboard\n  * Added filter to namespace select button\n  * Improved how empty tables are displayed\n  * Added `Host:` header validation to the `linkerd-web` service, to protect\n    against DNS rebinding attacks\n  * Made the dashboard sidebar component responsive\n  * Changed the navigation bar color to the one used on the\n    [Linkerd](https://linkerd.io/) website\n* Internal\n  * Added validation to incoming sidecar injection requests that ensures the\n    value of `linkerd.io/inject` is either `enabled` or `disabled` (thanks\n    @mayankshah1607)\n  * Upgraded the Prometheus Go client library to v1.2.1 (thanks @daxmc99!)\n  * Fixed an issue causing `tap`, `injector` and `sp-validator` to use old\n    certificates after `helm upgrade` due to not being restarted\n  * Fixed incomplete Swagger definition of the tap api, causing benign error\n    logging in the kube-apiserver\n  * Removed the destination container from the linkerd-controller deployment\n    as it now runs in the linkerd-destination deployment\n  * Allowed the control plane to be injected with the `debug` container\n  * Updated proxy image build script to support HTTP proxy options (thanks\n    @joakimr-axis!)\n  * Updated the CLI `doc` command to auto-generate documentation for the proxy\n    configuration annotations (thanks @StupidScience!)\n  * Added new `--trace-collector` and `--trace-collector-svc-account` flags to\n    `linkerd inject` that configures the OpenCensus trace collector used by\n    proxies in the injected workload (thanks @Pothulapati!)\n  * Added a new `--control-plane-tracing` flag to `linkerd install` that\n    enables distributed tracing in the control plane (thanks @Pothulapati!)\n  * Added distributed tracing support to the control plane (thanks\n    @Pothulapati!)\n\n## edge-20.2.1\n\nThis edge release is a release candidate for `stable-2.7` and fixes an issue\nwhere the proxy could consume inappropriate amounts of memory.\n\n* Proxy\n  * Fixed a bug in the proxy's logging subsystem that could cause the proxy to\n    consume memory until the process is OOM killed, especially when the proxy\n    was configured to log diagnostic information\n  * Fixed properly emitting `grpc-status` headers when signaling proxy errors\n    to gRPC clients\n  * Updated certain proxy dependencies to address RUSTSEC-2019-0033,\n    RUSTSEC-2019-0034, and RUSTSEC-2020-02\n\n## edge-20.1.4\n\nThis edge release is a release candidate for `stable-2.7`.\n\nThe `linkerd check` command has been updated to improve the control plane\ndebugging experience.\n\n* CLI\n  * Updated the mTLS trust anchor checks to eliminate false positives caused\n    by extra trailing spaces\n  * Reduced the severity level of the Linkerd version checks, so that they\n    don't fail when the external version endpoint is unreachable (thanks\n    @mayankshah1607!)\n  * Added a new `tap` APIService check to aid with uncovering Kubernetes API\n    aggregation layer issues (thanks @droidnoob!)\n\n## edge-20.1.3\n\nThis edge release is a release candidate for `stable-2.7`.\n\nAn update to the Helm charts has caused a **breaking change** for users who\nhave installed Linkerd using Helm. In order to make the purpose of the\n`noInitContainer` parameter more explicit, it has been renamed to\n`cniEnabled`.\n\n* CLI\n  * Introduced CNI checks to confirm the CNI plugin is installed and ready;\n    this is done through `linkerd check --pre --linkerd-cni-enabled` before\n    installation and `linkerd check` after installation if the CNI plugin is\n    present\n  * Added support for the `--as-group` flag so that users can impersonate\n    groups for Kubernetes operations (thanks @mayankshah160!)\n* Controller\n  * Fixed an issue where an override of the Docker registry was not being\n    applied to debug containers (thanks @javaducky!)\n  * Added check for the Subject Alternate Name attributes to the API server\n    when access restrictions have been enabled (thanks @javaducky!)\n  * Added support for arbitrary pod labels so that users can leverage the\n    Linkerd provided Prometheus instance to scrape for their own labels\n    (thanks @daxmc99!)\n  * Fixed an issue with CNI config parsing\n* Helm\n  * **Breaking change**: Renamed `noInitContainer` parameter to `cniEnabled`\n  * Fixed an issue with `helm install` where the lists of ignored inbound and\n    outbound ports would not be reflected\n\n## edge-20.1.2\n\n* CLI\n  * Added HA specific checks to `linkerd check` to ensure that the\n    `kube-system` namespace has the\n    `config.linkerd.io/admission-webhooks:disabled` label set\n  * Fixed a problem causing the presence of unnecessary empty fields in\n    generated resource definitions (thanks @mayankshah1607)\n* Proxy\n  * Fixed an issue that could cause the OpenCensus exporter to stall\n* Internal\n  * Added validation to incoming sidecar injection requests that ensures the\n    value of `linkerd.io/inject` is either `enabled` or `disabled` (thanks\n    @mayankshah1607)\n\n## edge-20.1.1\n\nThis edge release includes experimental improvements to the Linkerd proxy's\nrequest buffering and backpressure infrastructure.\n\nAdditionally, we've fixed several bugs when installing Linkerd with Helm,\nupdated the CLI to allow using both port numbers _and_ port ranges with the\n`--skip-inbound-ports` and `--skip-outbound-ports`  flags, and fixed a\ndashboard error that can occur if the dashboard is open in a browser while\nupdating Linkerd.\n\n**Note**: The `linkerd-proxy` version included with this release is more\nexperimental than usual. We'd love your help testing, but be aware that there\nmight be stability issues.\n\n* CLI\n  * Added the ability to pass both port numbers and port ranges to\n    `--skip-inbound-ports` and `--skip-outbound-ports` (thanks to @javaducky!)\n* Controller\n  * Fixed a race condition in the `linkerd-web` service\n  * Updated Prometheus to 2.15.2 (thanks @Pothulapati)\n* Web UI\n  * Fixed an error when refreshing an already open dashboard when the Linkerd\n    version has changed\n* Proxy\n  * Internal changes to the proxy's request buffering and backpressure\n    infrastructure\n* Helm\n  * Fixed the `linkerd-cni` Helm chart not setting proper namespace\n    annotations and labels\n  * Fixed certificate issuance lifetime not being set when installing through\n    Helm\n  * More improvements to Helm best practices (thanks to @Pothulapati!)\n\n## edge-19.12.3\n\nThis edge release adds support for pod IP and service cluster IP lookups,\nimproves performance of the dashboard, and makes `linkerd check --pre` perform\nmore comprehensive checks.\n\nThe `--wait-before-exit-seconds` flag has been added to allow Linkerd users to\n opt in to `preStop hooks`. The details of this change are in\n [#3798](https://github.com/linkerd/linkerd2/pull/3798).\n\nAlso, the proxy has been updated to `v2.82.0` which improves gRPC error\nclassification and [ensures that\nresolutions](https://github.com/linkerd/linkerd2/pull/3848) are released when\nthe associated balancer becomes idle.\n\nFinally, an update to follow best practices in the Helm charts has caused a\n_breaking change_. Users who have installed Linkerd using Helm must be certain\nto read the details of\n[#3822](https://github.com/linkerd/linkerd2/issues/3822)\n\n* CLI\n  * Increased the comprehensiveness of `linkerd check --pre`\n  * Added TLS certificate validation to `check` and `upgrade` commands\n* Controller\n  * Increased minimum kubernetes version to 1.13.0\n  * Added support for pod ip and service cluster ip lookups in the destination\n    service\n  * Added recommended kubernetes labels to control-plane\n  * Added the `--wait-before-exit-seconds` flag to linkerd inject for the\n    proxy sidecar to delay the start of its shutdown process (a huge commit\n    from @KIVagant, thanks!)\n  * Added a pre-sign check to the identity service\n* Web UI\n  * Increased the speed of the dashboard by pausing network activity when the\n    dashboard is not visible to the user\n* Proxy\n  * Added a timeout to release resolutions to idle balancers\n  * Improved error classification for gRPC services\n* Internal\n  * **Breaking Change** Updated Helm charts to follow best practices using\n    proper casing (thanks @Pothulapati!)\n\n## edge-19.12.2\n\n* CLI\n  * Added support for injecting CronJobs and ReplicaSets, as well as the\n    ability to use them as targets in the CLI subcommands\n  * Introduced the new flags `--identity-issuer-certificate-file`,\n    `--identity-issuer-key-file` and `identity-trust-anchors-file` to `linkerd\n    upgrade` to support trust anchor and issuer certificate rotation\n* Controller\n  * Fixed inject failures for pods with security context capabilities\n* Web UI\n  * Added support for CronJobs and ReplicaSets, including new Grafana\n    dashboards for them\n* Proxy\n  * Fixed a bug where the proxy could stop receiving service discovery\n    updates, resulting in 503 errors\n* Internal\n  * Moved CNI template into a Helm chart to prepare for future publication\n  * Upgraded the Prometheus Go client library to v1.2.1 (thanks @daxmc99!)\n  * Reenabled certificates rotation integration tests\n\n## edge-19.12.1\n\n* CLI\n  * Added condition to the `linkerd stat` command that requires a window size\n    of at least 15 seconds to work properly with Prometheus\n* Internal\n  * Fixed whitespace path handling in non-docker build scripts (thanks\n    @joakimr-axis!)\n  * Removed Calico logutils dependency that was incompatible with Go 1.13\n  * Updated Helm templates to use fully-qualified variable references based\n    upon Helm best practices (thanks @javaducky!)\n\n## edge-19.11.3\n\n* CLI\n  * Added a check that ensures using `--namespace` and `--all-namespaces`\n    results in an error as they are mutually exclusive\n* Internal\n  * Fixed an issue causing `tap`, `injector` and `sp-validator` to use old\n    certificates after `helm upgrade` due to not being restarted\n  * Fixed incomplete Swagger definition of the tap api, causing benign error\n    logging in the kube-apiserver\n\n## edge-19.11.2\n\n* CLI\n  * Added a `Dashboard.Replicas` parameter to the Linkerd Helm chart to allow\n    configuring the number of dashboard replicas (thanks @KIVagant!)\n  * Removed redundant service profile check (thanks @alenkacz!)\n* Web UI\n  * Added `linkerd check` to the dashboard in the `/controlplane` view\n  * Added request and response headers to the `tap` expanded view in the\n    dashboard\n* Internal\n  * Removed the destination container from the linkerd-controller deployment\n    as it now runs in the linkerd-destination deployment\n  * Upgraded Go to version 1.13.4\n\n## edge-19.11.1\n\n* CLI\n  * Updated `uninject` command to work with namespace resources (thanks\n    @mayankshah1607!)\n* Controller\n  * Added `conntrack` to the `debug` container to help with connection\n    tracking debugging\n  * Fixed a bug in `tap` where mismatch cluster domain and trust domain caused\n    `tap` to hang\n  * Fixed an issue in the `identity` RBAC resource which caused start up\n    errors in k8s 1.6 (thanks @Pothulapati!)\n* Proxy\n  * Improved debug/error logging to include detailed contextual information\n* Web UI\n  * Added filter to namespace select button\n  * Improved how empty tables are displayed\n* Internal\n  * Added integration test for custom cluster domain\n  * Allowed the control plane to be injected with the `debug` container\n  * Updated proxy image build script to support HTTP proxy options (thanks\n    @joakimr-axis!)\n  * Updated the CLI `doc` command to auto-generate documentation for the proxy\n    configuration annotations (thanks @StupidScience!)\n\n## edge-19.10.5\n\nThis edge release adds support for integrating Linkerd's PKI with an external\ncertificate issuer such as [`cert-manager`], adds distributed tracing support\nto the Linkerd control plane, and adds protection against DNS rebinding\nattacks to the web dashboard. In addition, it includes several improvements to\nthe Linkerd CLI.\n\n* CLI\n  * Added a new `--identity-external-issuer` flag to `linkerd install` that\n    configures Linkerd to use certificates issued by an external certificate\n    issuer (such as `cert-manager`)\n  * Added support for injecting a namespace to `linkerd inject` (thanks\n    @mayankshah1607!)\n  * Added checks to `linkerd check --preinstall` ensuring Kubernetes Secrets\n    can be created and accessed\n  * Fixed `linkerd tap` sometimes displaying incorrect pod names for unmeshed\n    IPs that match multiple running pods\n* Controller\n  * Added support for using trust anchors from an external certificate issuer\n    (such as `cert-manager`) to the `linkerd-identity` service\n* Web UI\n  * Added `Host:` header validation to the `linkerd-web` service, to protect\n    against DNS rebinding attacks\n* Internal\n  * Added new `--trace-collector` and `--trace-collector-svc-account` flags to\n    `linkerd inject` that configures the OpenCensus trace collector used by\n    proxies in the injected workload (thanks @Pothulapati!)\n  * Added a new `--control-plane-tracing` flag to `linkerd install` that\n    enables distributed tracing in the control plane (thanks @Pothulapati!)\n  * Added distributed tracing support to the control plane (thanks\n    @Pothulapati!)\n\nAlso, thanks to @joakimr-axis for several fixes and improvements to internal\nbuild scripts!\n\n[`cert-manager`]: https://github.com/jetstack/cert-manager\n\n## edge-19.10.4\n\nThis edge release adds dashboard UX enhancements, and improves the speed of\nthe CLI.\n\n* CLI\n  * Made `linkerd install --ignore-cluster` and `--skip-checks` faster\n  * Fixed a bug causing `linkerd upgrade` to fail when used with\n    `--from-manifest`\n* Web UI\n  * Made the dashboard sidebar component responsive\n  * Changed the navigation bar color to the one used on the\n    [Linkerd](https://linkerd.io/) website\n\n## edge-19.10.3\n\nThis edge release adds support for headless services, improves the upgrade\nprocess after installing Linkerd with a custom cluster domain, and enhances\nthe `check` functionality to report invalid trust anchors.\n\n* CLI\n  * Made `--cluster-domain` an install-only flag (thanks @bmcstdio!)\n  * Updated `check` to ensure that proxy trust anchors match configuration\n       (thanks @ereslibre!)\n* Controller\n  * Added support for headless services (thanks @JohannesEH!)\n* Helm\n  * Updated the helm build to retain previous releases\n\n## stable-2.6.0\n\nThis release introduces distributed tracing support, adds request and response\nheaders to `linkerd tap`, dramatically improves the performance of the\ndashboard on large clusters, adds traffic split visualizations to the\ndashboard, adds a public Helm repo, and many more improvements!\n\nFor more details, see the announcement blog post:\n<https://linkerd.io/2019/10/10/announcing-linkerd-2.6/>\n\nTo install this release, run: `curl https://run.linkerd.io/install | sh`\n\n**Upgrade notes**: Please see the [upgrade\ninstructions](https://linkerd.io/2/tasks/upgrade/#upgrade-notice-stable-2-6-0).\n\n**Special thanks to**: @alenkacz, @arminbuerkle, @bmcstdio, @bourquep,\n@brianstorti, @kevtaylor, @KIVagant, @pierDipi, and @Pothulapati!\n\n**Full release notes**:\n\n* CLI\n  * Added a new `json` output option to the `linkerd tap` command, which\n    exposes request and response headers\n  * Added a public Helm repo - for full installation instructions, see our\n    [Helm documentation](https://linkerd.io/2/tasks/install-helm/).\n  * Added an `--address` flag to `linkerd dashboard`, allowing users to\n    specify a port-forwarding address (thanks @bmcstdio!)\n  * Added node selector constraints to Helm installation, so users can control\n    which nodes the control plane is deployed to (thanks @bmcstdio!)\n  * Added a `--cluster-domain` flag to the `linkerd install` command that\n    allows setting a custom cluster domain (thanks @arminbuerkle!)\n  * Added a `--disable-heartbeat` flag for `linkerd install | upgrade`\n    commands\n  * Allowed disabling namespace creation when installing Linkerd using Helm\n    (thanks @KIVagant!)\n  * Improved the error message when the CLI cannot connect to Kubernetes\n    (thanks @alenkacz!)\n* Controller\n  * Updated the Prometheus config to keep only needed `cadvisor` metrics,\n    substantially reducing the number of time-series stored in most clusters\n  * Introduced `config.linkerd.io/trace-collector` and\n    `config.alpha.linkerd.io/trace-collector-service-account` pod spec\n    annotations to support per-pod tracing\n  * Instrumented the proxy injector to provide additional metrics about\n    injection (thanks @Pothulapati!)\n  * Added Kubernetes events (and log lines) when the proxy injector injects a\n    deployment, and when injection is skipped\n  * Fixed a workload admission error between the Kubernetes apiserver and the\n    HA proxy injector, by allowing workloads in a namespace to be omitted from\n    the admission webhooks phase using the\n    `config.linkerd.io/admission-webhooks: disabled` label (thanks\n    @hasheddan!)\n  * Fixed proxy injector timeout during a large number of concurrent\n    injections\n  * Added support for disabling the heartbeat cronjob (thanks @kevtaylor!)\n* Proxy\n  * Added distributed tracing support\n  * Decreased proxy Docker image size by removing bundled debug tools\n  * Added 587 (SMTP) to the list of ports to ignore in protocol detection\n    (bound to server-speaks-first protocols) (thanks @brianstorti!)\n* Web UI\n  * Redesigned dashboard navigation so workloads are now viewed by namespace,\n    with an \"All Namespaces\" option, in order to increase dashboard speed\n  * Added Traffic Splits as a resource to the dashboard, including a Traffic\n    Split detail page\n  * Added a `Linkerd Namespace` Grafana dashboard, allowing users to view\n    historical data for a given namespace, similar to CLI output for `linkerd\n    stat deploy -n myNs` (thanks @bourquep!)\n  * Fixed bad request in the top routes tab on empty fields (thanks\n    @pierDipi!)\n* Internal\n  * Moved CI from Travis to GitHub Actions\n  * Added requirement for Go `1.12.9` for controller builds to include\n    security fixes\n  * Added support for Kubernetes `1.16`\n  * Upgraded client-go to `v12.0.0`\n\n## edge-19.10.2\n\nThis edge release is a release candidate for `stable-2.6`.\n\n* Controller\n  * Added the destination container back to the controller; it had previously\n    been separated into its own deployment. This ensures backwards\n    compatibility and allows users to avoid data plane downtime during an\n    upcoming upgrade to `stable-2.6`.\n\n## edge-19.10.1\n\nThis edge release is a release candidate for `stable-2.6`.\n\n* Proxy\n  * Improved error logging when the proxy fails to emit trace spans\n  * Fixed bug in distributed tracing where trace ids with fewer than 16 bytes\n    were discarded\n* Internal\n  * Added integration tests for `linkerd edges` and `linkerd endpoints`\n\n## edge-19.9.5\n\nThis edge release is a release candidate for `stable-2.6`.\n\n* Helm\n  * Added node selector constraints, so users can control which nodes the\n    control plane is deployed to (thanks @bmcstdio!)\n* CLI\n  * Added request and response headers to the JSON output option for `linkerd\n    tap`\n\n## edge-19.9.4\n\nThis edge release introduces experimental support for distributed tracing as\nwell as a redesigned sidebar in the Web UI!\n\nExperimental support for distributed tracing means that Linkerd data plane\nproxies can now emit trace spans, allowing you to see the exact amount of time\nspent in the Linkerd proxy for traced requests. The new\n`config.linkerd.io/trace-collector` and\n`config.alpha.linkerd.io/trace-collector-service-account` tracing annotations\nallow specifying which pods should emit trace spans.\n\nThe goal of the dashboard's sidebar redesign was to reduce load on Prometheus\nand simplify navigation by providing top-level views centered around\nnamespaces and workloads.\n\n* CLI\n  * Introduced a new `--cluster-domain` flag to the `linkerd install` command\n    that allows setting a custom cluster domain (thanks @arminbuerkle!)\n  * Fixed the `linkerd endpoints` command to use the correct Destination API\n    address (thanks @Pothulapati!)\n  * Added `--disable-heartbeat` flag for `linkerd` `install|upgrade` commands\n* Controller\n  * Instrumented the proxy-injector to provide additional metrics about\n    injection (thanks @Pothulapati!)\n  * Added support for `config.linkerd.io/admission-webhooks: disabled` label\n    on namespaces so that the pods creation events in these namespaces are\n    ignored by the proxy injector; this fixes situations in HA deployments\n    where the proxy-injector is installed in `kube-system` (thanks\n    @hasheddan!)\n  * Introduced `config.linkerd.io/trace-collector` and\n    `config.alpha.linkerd.io/trace-collector-service-account` pod spec\n    annotations to support per-pod tracing\n* Web UI\n  * Workloads are now viewed by namespace, with an \"All Namespaces\" option, to\n    improve dashboard performance\n* Proxy\n  * Added experimental distributed tracing support\n\n## edge-19.9.3\n\n* Helm\n  * Allowed disabling namespace creation during install (thanks @KIVagant!)\n* CLI\n  * Added a new `json` output option to the `linkerd tap` command\n* Controller\n  * Fixed proxy injector timeout during a large number of concurrent\n    injections\n  * Separated the destination controller into its own separate deployment\n  * Updated Prometheus config to keep only needed `cadvisor` metrics,\n    substantially reducing the number of time-series stored in most clusters\n* Web UI\n  * Fixed bad request in the top routes tab on empty fields (thanks\n    @pierDipi!)\n* Proxy\n  * Fixes to the client's backoff logic\n  * Added 587 (SMTP) to the list of ports to ignore in protocol detection\n    (bound to server-speaks-first protocols) (thanks @brianstorti!)\n\n## edge-19.9.2\n\nMuch of our effort has been focused on improving our build and test\ninfrastructure, but this edge release lays the groundwork for some big new\nfeatures to land in the coming releases!\n\n* Helm\n  * There's now a public Helm repo! This release can be installed with: `helm\n    repo add linkerd-edge https://helm.linkerd.io/edge && helm install\n    linkerd-edge/linkerd2`\n  * Improved TLS credential parsing by ignoring spurious newlines\n* Proxy\n  * Decreased proxy-init Docker image size by removing bundled debug tools\n* Web UI\n  * Fixed an issue where the edges table could end up with duplicates\n  * Added an icon to more clearly label external links\n* Internal\n  * Upgraded client-go to v12.0.0\n  * Moved CI from Travis to GitHub Actions\n\n## edge-19.9.1\n\nThis edge release adds traffic splits into the Linkerd dashboard as well as a\nvariety of other improvements.\n\n* CLI\n  * Improved the error message when the CLI cannot connect to Kubernetes\n    (thanks @alenkacz!)\n  * Added `--address` flag to `linkerd dashboard` (thanks @bmcstdio!)\n* Controller\n  * Fixed an issue where the proxy-injector had insufficient RBAC permissions\n  * Added support for disabling the heartbeat cronjob (thanks @kevtaylor!)\n* Proxy\n  * Decreased proxy Docker image size by removing bundled debug tools\n  * Fixed an issue where the incorrect content-length could be set for GET\n    requests with bodies\n* Web UI\n  * Added trafficsplits as a resource to the dashboard, including a\n    trafficsplit detail page\n* Internal\n  * Added support for Kubernetes 1.16\n\n## edge-19.8.7\n\n* Controller\n  * Added Kubernetes events (and log lines) when the proxy injector injects a\n    deployment, and when injection is skipped\n  * Additional preparation for configuring the cluster base domain (thanks\n    @arminbuerkle!)\n* Proxy\n  * Changed the proxy to require the `LINKERD2_PROXY_DESTINATION_SVC_ADDR`\n    environment variable when starting up\n* Web UI\n  * Increased dashboard speed by consolidating existing Prometheus queries\n\n## edge-19.8.6\n\nA new Grafana dashboard has been added which shows historical data for a\nselected namespace. The build process for controller components now requires\n`Go 1.12.9`. Additional contributions were made towards support for custom\ncluster domains.\n\n* Web UI\n  * Added a `Linkerd Namespace` Grafana dashboard, allowing users to view\n    historical data for a given namespace, similar to CLI output for `linkerd\n    stat deploy -n myNs` (thanks @bourquep!)\n* Internal\n  * Added requirement for Go `1.12.9` for controller builds to include\n    security fixes\n  * Set `LINKERD2_PROXY_DESTINATION_GET_SUFFIXES` proxy environment variable,\n    in preparation for custom cluster domain support (thanks @arminbuerkle!)\n\n## stable-2.5.0\n\nThis release adds [Helm support](https://linkerd.io/2/tasks/install-helm/),\n[tap authentication and authorization via RBAC](https://linkerd.io/tap-rbac),\ntraffic split stats, dynamic logging levels, a new cluster monitoring\ndashboard, and countless performance enhancements and bug fixes.\n\nFor more details, see the announcement blog post:\n<https://linkerd.io/2019/08/20/announcing-linkerd-2.5/>\n\nTo install this release, run: `curl https://run.linkerd.io/install | sh`\n\n**Upgrade notes**: Use the `linkerd upgrade` command to upgrade the control\nplane. This command ensures that all existing control plane's configuration\nand mTLS secrets are retained. For more details, please see the [upgrade\ninstructions](https://linkerd.io/2/tasks/upgrade/#upgrade-notice-stable-2-5-0).\n\n**Special thanks to**: @alenkacz, @codeman9, @ethan-daocloud, @jonathanbeber,\nand @Pothulapati!\n\n**Full release notes**:\n\n* CLI\n  * **New** Updated `linkerd tap`, `linkerd top` and `linkerd profile --tap`\n    to require `tap.linkerd.io` RBAC privileges. See\n    <https://linkerd.io/tap-rbac> for more info\n  * **New** Added traffic split metrics via `linkerd stat trafficsplits`\n    subcommand\n  * Made the `linkerd routes` command traffic split aware\n  * Introduced the `linkerd --as` flag which allows users to impersonate\n    another user for Kubernetes operations\n  * Introduced the `--all-namespaces` (`-A`) option to the `linkerd get`,\n    `linkerd edges` and `linkerd stat` commands to retrieve resources across\n    all namespaces\n  * Improved the installation report produced by the `linkerd check` command\n    to include the control plane pods' live status\n  * Fixed bug in the `linkerd upgrade config` command that was causing it to\n    crash\n  * Introduced `--use-wait-flag` to the `linkerd install-cni` command, to\n    configure the CNI plugin to use the `-w` flag for `iptables` commands\n  * Introduced `--restrict-dashboard-privileges` flag to `linkerd install`\n    command, to disallow tap in the dashboard\n  * Fixed `linkerd uninject` not removing `linkerd.io/inject: enabled`\n    annotations\n  * Fixed `linkerd stat -h` example commands (thanks @ethan-daocloud!)\n  * Fixed incorrect \"meshed\" count in `linkerd stat` when resources share the\n    same label selector for pods (thanks @jonathanbeber!)\n  * Added pod status to the output of the `linkerd stat` command (thanks\n    @jonathanbeber!)\n  * Added namespace information to the `linkerd edges` command output and a\n    new `-o wide` flag that shows the identity of the client and server if\n    known\n  * Added a check to the `linkerd check` command to validate the user has\n    privileges necessary to create CronJobs\n  * Added a new check to the `linkerd check --pre` command validating that if\n    PSP is enabled, the NET_RAW capability is available\n* Controller\n  * **New** Disabled all unauthenticated tap endpoints. Tap requests now\n    require [RBAC authentication and\n    authorization](https://linkerd.io/tap-rbac)\n  * The `l5d-require-id` header is now set on tap requests so that a\n    connection is established over TLS\n  * Introduced a new RoleBinding in the `kube-system` namespace to provide\n    [access to tap](https://linkerd.io/tap-rbac)\n  * Added HTTP security headers on all dashboard responses\n  * Added support for namespace-level proxy override annotations (thanks\n    @Pothulapati!)\n  * Added resource limits when HA is enabled (thanks @Pothulapati!)\n  * Added pod anti-affinity rules to the control plane pods when HA is enabled\n    (thanks @Pothulapati!)\n  * Fixed a crash in the destination service when an endpoint does not have a\n    `TargetRef`\n  * Updated the destination service to return `InvalidArgument` for external\n    name services so that the proxy does not immediately fail the request\n  * Fixed an issue with discovering StatefulSet pods via their unique hostname\n  * Fixed an issue with traffic split where outbound proxy stats are missing\n  * Upgraded the service profile CRD to v1alpha2. No changes required for\n    users currently using v1alpha1\n  * Updated the control plane's pod security policy to restrict workloads from\n    running as `root` in the CNI mode (thanks @codeman9!)\n  * Introduced optional cluster heartbeat cron job\n  * Bumped Prometheus to 2.11.1\n  * Bumped Grafana to 6.2.5\n* Proxy\n  * **New** Added a new `/proxy-log-level` endpoint to update the log level at\n    runtime\n  * **New** Updated the tap server to only admit requests from the control\n    plane's tap controller\n  * Added `request_handle_us` histogram to measure proxy overhead\n  * Fixed gRPC client cancellations getting recorded as failures rather than\n    as successful\n  * Fixed a bug where tap would stop streaming after a short amount of time\n  * Fixed a bug that could cause the proxy to leak service discovery\n    resolutions to the Destination controller\n* Web UI\n  * **New** Added \"Kubernetes cluster monitoring\" Grafana dashboard with\n    cluster and containers metrics\n  * Updated the web server to use the new tap APIService. If the `linkerd-web`\n    service account is not authorized to tap resources, users will see a link\n    to documentation to remedy the error\n\n## edge-19.8.5\n\nThis edge release is a release candidate for `stable-2.5`.\n\n* CLI\n  * Fixed CLI filepath issue on Windows\n* Proxy\n  * Fixed gRPC client cancellations getting recorded as failures rather than\n    as successful\n\n## edge-19.8.4\n\nThis edge release is a release candidate for `stable-2.5`.\n\n* CLI\n  * Introduced `--use-wait-flag` to the `linkerd install-cni` command, to\n    configure the CNI plugin to use the `-w` flag for `iptables` commands\n* Controller\n  * Disabled the tap gRPC server listener. All tap requests now require RBAC\n    authentication and authorization\n\n## edge-19.8.3\n\nThis edge release introduces a new `linkerd stat trafficsplits` subcommand, to\nshow traffic split metrics. It also introduces a \"Kubernetes cluster\nmonitoring\" Grafana dashboard.\n\n* CLI\n  * Added traffic split metrics via `linkerd stat trafficsplits` subcommand\n  * Fixed `linkerd uninject` not removing `linkerd.io/inject: enabled`\n    annotations\n  * Fixed `linkerd stat -h` example commands (thanks @ethan-daocloud!)\n* Controller\n  * Added support for namespace-level proxy override annotations\n  * Removed unauthenticated tap from the Public API\n* Proxy\n  * Added `request_handle_us` histogram to measure proxy overhead\n  * Updated the tap server to only admit requests from the control plane's tap\n    controller\n  * Fixed a bug where tap would stop streaming after a short amount of time\n  * Fixed a bug that could cause the proxy to leak service discovery\n    resolutions to the Destination controller\n* Web UI\n  * Added \"Kubernetes cluster monitoring\" Grafana dashboard with cluster and\n    containers metrics\n* Internal\n  * Updated `linkerd install` and `linkerd upgrade` to use Helm charts for\n    templating\n  * Pinned Helm tooling to `v2.14.3`\n  * Added Helm integration tests\n  * Added container CPU and memory usage to `linkerd-heartbeat` requests\n  * Removed unused inject code (thanks @alenkacz!)\n\n## edge-19.8.2\n\nThis edge release introduces the new Linkerd control plane Helm chart, named\n`linkerd2`. Helm users can now install and remove the Linkerd control plane by\nusing the `helm install` and `helm delete` commands. Proxy injection also now\nuses Helm charts.\n\nNo changes were made to the existing `linkerd install` behavior.\n\nFor detailed installation steps using Helm, see the notes for\n[#3146](https://github.com/linkerd/linkerd2/pull/3146).\n\n* CLI\n  * Updated `linkerd top` and `linkerd profile --tap` to require\n    `tap.linkerd.io` RBAC privileges, see <https://linkerd.io/tap-rbac> for\n    more info\n  * Modified `tap.linkerd.io` APIService to enable usage in `kubectl auth\n    can-i` commands\n  * Introduced `--restrict-dashboard-privileges` flag to `linkerd install`\n    command, to restrict the dashboard's default privileges to disallow tap\n* Controller\n  * Introduced a new ClusterRole, `linkerd-linkerd-tap-admin`, which gives\n    cluster-wide tap privileges. Also introduced a new ClusterRoleBinding,\n    `linkerd-linkerd-web-admin`, which binds the `linkerd-web` service account\n    to the new tap ClusterRole\n  * Removed successfully completed `linkerd-heartbeat` jobs from pod listing\n    in the linkerd control plane to streamline `get po` output (thanks\n    @Pothulapati!)\n* Web UI\n  * Updated the web server to use the new tap APIService. If the `linkerd-web`\n    service account is not authorized to tap resources, users will see a link\n    to documentation to remedy the error\n\n## edge-19.8.1\n\n### Significant Update\n\nThis edge release introduces a new tap APIService. The Kubernetes apiserver\nauthenticates the requesting tap user and then forwards tap requests to the\nnew tap APIServer. The `linkerd tap` command now makes requests against the\nAPIService.\n\nWith this release, users must be authorized via RBAC to use the `linkerd tap`\ncommand. Specifically `linkerd tap` requires the `watch` verb on all resources\nin the `tap.linkerd.io/v1alpha1` APIGroup. More granular access is also\navailable via sub-resources such as `deployments/tap` and `pods/tap`.\n\n* CLI\n  * Added a check to the `linkerd check` command to validate the user has\n    privileges necessary to create CronJobs\n  * Introduced the `linkerd --as` flag which allows users to impersonate\n    another user for Kubernetes operations\n  * The `linkerd tap` command now makes requests against the tap APIService\n* Controller\n  * Added HTTP security headers on all dashboard responses\n  * Fixed nil pointer dereference in the destination service when an endpoint\n    does not have a `TargetRef`\n  * Added resource limits when HA is enabled\n  * Added RSA support to TLS libraries\n  * Updated the destination service to return `InvalidArgument` for external\n    name services so that the proxy does not immediately fail the request\n  * The `l5d-require-id` header is now set on tap requests so that a\n    connection is established over TLS\n  * Introduced the `APIService/v1alpha1.tap.linkerd.io` global resource\n  * Introduced the `ClusterRoleBinding/linkerd-linkerd-tap-auth-delegator`\n    global resource\n  * Introduced the `Secret/linkerd-tap-tls` resource into the `linkerd`\n    namespace\n  * Introduced the `RoleBinding/linkerd-linkerd-tap-auth-reader` resource into\n    the `kube-system` namespace\n* Proxy\n  * Added the `LINKERD2_PROXY_TAP_SVC_NAME` environment variable so that the\n    tap server attempts to authorize client identities\n* Internal\n  * Replaced `dep` with Go modules for dependency management\n\n## edge-19.7.5\n\n* CLI\n  * Improved the installation report produced by the `linkerd check` command\n    to include the control plane pods' live status\n  * Added the `--all-namespaces` (`-A`) option to the `linkerd get`, `linkerd\n    edges` and `linkerd stat` commands to retrieve resources across all\n    namespaces\n* Controller\n  * Fixed an issue with discovering StatefulSet pods via their unique hostname\n  * Fixed an issue with traffic split where outbound proxy stats are missing\n  * Bumped Prometheus to 2.11.1\n  * Bumped Grafana to 6.2.5\n  * Upgraded the service profile CRD to v1alpha2 where the openAPIV3Schema\n    validation is replaced by a validating admission webhook. No changes\n    required for users currently using v1alpha1\n  * Updated the control plane's pod security policy to restrict workloads from\n    running as `root` in the CNI mode (thanks @codeman9!)\n  * Introduced cluster heartbeat cron job\n* Proxy\n  * Introduced the `l5d-require-id` header to enforce TLS outbound\n    communication from the Tap server\n\n## edge-19.7.4\n\n* CLI\n  * Made the `linkerd routes` command traffic-split aware\n  * Fixed bug in the `linkerd upgrade config` command that was causing it to\n    crash\n  * Added pod status to the output of the `linkerd stat`command (thanks\n    @jonathanbeber!)\n  * Fixed incorrect \"meshed\" count in `linkerd stat` when resources share the\n    same label selector for pods (thanks @jonathanbeber!)\n  * Added namespace information to the `linkerd edges` command output and a\n    new `-o wide` flag that shows the identity of the client and server if\n    known\n  * Added a new check to the `linkerd check --pre` command validating that if\n    PSP is enabled, the NET_RAW capability is available\n* Controller\n  * Added pod anti-affinity rules to the control plane pods when HA is enabled\n    (thanks @Pothulapati!)\n* Proxy\n  * Improved performance by using a constant-time load balancer\n  * Added a new `/proxy-log-level` endpoint to update the log level at runtime\n\n## stable-2.4.0\n\nThis release adds traffic splitting functionality, support for the Kubernetes\nService Mesh Interface (SMI), graduates high-availability support out of\nexperimental status, and adds a tremendous list of other improvements,\nperformance enhancements, and bug fixes.\n\nLinkerd's new traffic splitting feature allows users to dynamically control\nthe percentage of traffic destined for a service. This powerful feature can be\nused to implement rollout strategies like canary releases and blue-green\ndeploys. Support for the [Service Mesh Interface](https://smi-spec.io) (SMI)\nmakes it easier for ecosystem tools to work across all service mesh\nimplementations.\n\nAlong with the introduction of optional install stages via the `linkerd\ninstall config` and `linkerd install control-plane` commands, the default\nbehavior of the `linkerd inject` command only adds annotations and defers\ninjection to the always-installed proxy injector component.\n\nFinally, there have been many performance and usability improvements to the\nproxy and UI, as well as production-ready features including:\n\n* A new `linkerd edges` command that provides fine-grained observability into\n  the TLS-based identity system\n* A `--enable-debug-sidecar` flag for the `linkerd inject` command that\n  improves debugging efforts\n\nLinkerd recently passed a CNCF-sponsored security audit! Check out the\nin-depth report\n[here](https://github.com/linkerd/linkerd2/blob/master/SECURITY_AUDIT.pdf).\n\nTo install this release, run: `curl https://run.linkerd.io/install | sh`\n\n**Upgrade notes**: Use the `linkerd upgrade` command to upgrade the control\nplane. This command ensures that all existing control plane's configuration\nand mTLS secrets are retained. For more details, please see the [upgrade\ninstructions](https://linkerd.io/2/tasks/upgrade/#upgrade-notice-stable-2-4-0)\nfor more details.\n\n**Special thanks to**: @alenkacz, @codeman9, @dwj300, @jackprice, @liquidslr,\n@matej-g, @Pothulapati, @zaharidichev\n\n**Full release notes**:\n\n* CLI\n  * **Breaking Change** Removed the `--proxy-auto-inject` flag, as the proxy\n    injector is now always installed\n  * **Breaking Change** Replaced the `--linkerd-version` flag with the\n    `--proxy-version` flag in the `linkerd install` and `linkerd upgrade`\n    commands, which allows setting the version for the injected proxy sidecar\n    image, without changing the image versions for the control plane\n  * Introduced install stages: `linkerd install config` and `linkerd install\n    control-plane`\n  * Introduced upgrade stages: `linkerd upgrade config` and `linkerd upgrade\n    control-plane`\n  * Introduced a new `--from-manifests` flag to `linkerd upgrade` allowing\n    manually feeding a previously saved output of `linkerd install` into the\n    command, instead of requiring a connection to the cluster to fetch the\n    config\n  * Introduced a new `--manual` flag to `linkerd inject` to output the proxy\n    sidecar container spec\n  * Introduced a new `--enable-debug-sidecar` flag to `linkerd inject`, that\n    injects a debug sidecar to inspect traffic to and from the meshed pod\n  * Added a new check for unschedulable pods and PSP issues (thanks,\n    @liquidslr!)\n  * Disabled the spinner in `linkerd check` when running without a TTY\n  * Ensured the ServiceAccount for the proxy injector is created before its\n    Deployment to avoid warnings when installing the proxy injector (thanks,\n    @dwj300!)\n  * Added a `linkerd check config` command for verifying that `linkerd install\n    config` was successful\n  * Improved the help documentation of `linkerd install` to clarify flag usage\n  * Added support for private Kubernetes clusters by changing the CLI to\n    connect to the control plane using a port-forward (thanks, @jackprice!)\n  * Fixed `linkerd check` and `linkerd dashboard` failing when any control\n    plane pod is not ready, even when multiple replicas exist (as in HA mode)\n  * **New** Added a `linkerd edges` command that shows the source and\n    destination name and identity for proxied connections, to assist in\n    debugging\n  * Tap can now be disabled for specific pods during injection by using the\n    `--disable-tap` flag, or by using the `config.linkerd.io/disable-tap`\n    annotation\n  * Introduced pre-install healthcheck for clock skew (thanks, @matej-g!)\n  * Added a JSON option to the `linkerd edges` command so that output is\n    scripting friendly and can be parsed easily (thanks @alenkacz!)\n  * Fixed an issue when Linkerd is installed with `--ha`, running `linkerd\n    upgrade` without `--ha` will disable the high availability control plane\n  * Fixed an issue with `linkerd upgrade` where running without `--ha` would\n    unintentionally disable high availability features if they were previously\n    enabled\n  * Added a `--init-image-version` flag to `linkerd inject` to override the\n    injected proxy-init container version\n  * Added the `--linkerd-cni-enabled` flag to the `install` subcommands so\n    that `NET_ADMIN` capability is omitted from the CNI-enabled control\n    plane's PSP\n  * Updated `linkerd check` to validate the caller can create\n    `PodSecurityPolicy` resources\n  * Added a check to `linkerd install` to prevent installing multiple control\n    planes into different namespaces avoid conflicts between global resources\n  * Added support for passing a URL directly to `linkerd inject` (thanks\n    @Pothulapati!)\n  * Added more descriptive output to the `linkerd check` output for control\n    plane ReplicaSet readiness\n  * Refactored the `linkerd endpoints` to use the same interface as used by\n    the proxy for service discovery information\n  * Fixed a bug where `linkerd inject` would fail when given a path to a file\n    outside the current directory\n  * Graduated high-availability support out of experimental status\n  * Modified the error message for `linkerd install` to provide instructions\n    for proceeding when an existing installation is found\n* Controller\n  * Added Go pprof HTTP endpoints to all control plane components' admin\n    servers to better assist debugging efforts\n  * Fixed bug in the proxy injector, where sporadically the pod workload owner\n    wasn't properly determined, which would result in erroneous stats\n  * Added support for a new `config.linkerd.io/disable-identity` annotation to\n    opt out of identity for a specific pod\n  * Fixed pod creation failure when a `ResourceQuota` exists by adding a\n    default resource spec for the proxy-init init container\n  * Fixed control plane components failing on startup when the Kubernetes API\n    returns an `ErrGroupDiscoveryFailed`\n  * Added Controller Component Labels to the webhook config resources (thanks,\n    @Pothulapati!)\n  * Moved the tap service into its own pod\n  * **New** Control plane installations now generate a self-signed certificate\n    and private key pair for each webhook, to prepare for future work to make\n    the proxy injector and service profile validator HA\n  * Added the `config.linkerd.io/enable-debug-sidecar` annotation allowing the\n    `--enable-debug-sidecar` flag to work when auto-injecting Linkerd proxies\n  * Added multiple replicas for the `proxy-injector` and `sp-validator`\n    controllers when run in high availability mode (thanks to @Pothulapati!)\n  * Defined least privilege default security context values for the proxy\n    container so that auto-injection does not fail (thanks @codeman9!)\n  * Default the webhook failure policy to `Fail` in order to account for\n    unexpected errors during auto-inject; this ensures uninjected applications\n    are not deployed\n  * Introduced control plane's PSP and RBAC resources into Helm templates;\n    these policies are only in effect if the PSP admission controller is\n    enabled\n  * Removed `UPDATE` operation from proxy-injector webhook because pod\n    mutations are disallowed during update operations\n  * Default the mutating and validating webhook configurations `sideEffects`\n    property to `None` to indicate that the webhooks have no side effects on\n    other resources (thanks @Pothulapati!)\n  * Added support for the SMI TrafficSplit API which allows users to define\n    traffic splits in TrafficSplit custom resources\n  * Added the `linkerd.io/control-plane-ns` label to all Linkerd resources\n    allowing them to be identified using a label selector\n  * Added Prometheus metrics for the Kubernetes watchers in the destination\n    service for better visibility\n* Proxy\n  * Replaced the fixed reconnect backoff with an exponential one (thanks,\n    @zaharidichev!)\n  * Fixed an issue where load balancers can become stuck\n  * Added a dispatch timeout that limits the amount of time a request can be\n    buffered in the proxy\n  * Removed the limit on the number of concurrently active service discovery\n    queries to the destination service\n  * Fix an epoll notification issue that could cause excessive CPU usage\n  * Added the ability to disable tap by setting an env var (thanks,\n    @zaharidichev!)\n  * Changed the proxy's routing behavior so that, when the control plane does\n    not resolve a destination, the proxy forwards the request with minimal\n    additional routing logic\n  * Fixed a bug in the proxy's HPACK codec that could cause requests with very\n    large header values to hang indefinitely\n  * Fixed a memory leak that can occur if an HTTP/2 request with a payload\n    ends before the entire payload is sent to the destination\n  * The `l5d-override-dst` header is now used for inbound service profile\n    discovery\n  * Added errors totals to `response_total` metrics\n  * Changed the load balancer to require that Kubernetes services are resolved\n    via the control plane\n  * Added the `NET_RAW` capability to the proxy-init container to be\n    compatible with `PodSecurityPolicy`s that use `drop: all`\n  * Fixed the proxy rejecting HTTP2 requests that don't have an `:authority`\n  * Improved idle service eviction to reduce resource consumption for clients\n    that send requests to many services\n  * Fixed proxied HTTP/2 connections returning 502 errors when the upstream\n    connection is reset, rather than propagating the reset to the client\n  * Changed the proxy to treat unexpected HTTP/2 frames as stream errors\n    rather than connection errors\n  * Fixed a bug where DNS queries could persist longer than necessary\n  * Improved router eviction to remove idle services in a more timely manner\n  * Fixed a bug where the proxy would fail to process requests with obscure\n    characters in the URI\n* Web UI\n  * Added the Font Awesome stylesheet locally; this allows both Font Awesome\n    and Material-UI sidebar icons to display consistently with no/limited\n    internet access (thanks again, @liquidslr!)\n  * Removed the Authorities table and sidebar link from the dashboard to\n    prepare for a new, improved dashboard view communicating authority data\n  * Fixed dashboard behavior that caused incorrect table sorting\n  * Removed the \"Debug\" page from the Linkerd dashboard while the\n    functionality of that page is being redesigned\n  * Added an Edges table to the resource detail view that shows the source,\n    destination name, and identity for proxied connections\n  * Improved UI for Edges table in dashboard by changing column names, adding\n    a \"Secured\" icon and showing an empty Edges table in the case of no\n    returned edges\n* Internal\n  * Known container errors were hidden in the integration tests; now they are\n    reported in the output without having the tests fail\n  * Fixed integration tests by adding known proxy-injector log warning to\n    tests\n  * Modified the integration test for `linkerd upgrade` in order to test\n    upgrading from the latest stable release instead of the latest edge and\n    reflect the typical use case\n  * Moved the proxy-init container to a separate `linkerd/proxy-init` Git\n    repository\n\n## edge-19.7.3\n\n* CLI\n  * Graduated high-availability support out of experimental status\n  * Modified the error message for `linkerd install` to provide instructions\n    for proceeding when an existing installation is found\n* Controller\n  * Added Prometheus metrics for the Kubernetes watchers in the destination\n    service for better visibility\n\n## edge-19.7.2\n\n* CLI\n  * Refactored the `linkerd endpoints` to use the same interface as used by\n    the proxy for service discovery information\n  * Fixed a bug where `linkerd inject` would fail when given a path to a file\n    outside the current directory\n* Proxy\n  * Fixed a bug where DNS queries could persist longer than necessary\n  * Improved router eviction to remove idle services in a more timely manner\n  * Fixed a bug where the proxy would fail to process requests with obscure\n    characters in the URI\n\n## edge-19.7.1\n\n* CLI\n  * Added more descriptive output to the `linkerd check` output for control\n    plane ReplicaSet readiness\n  * **Breaking change** Renamed `config.linkerd.io/debug` annotation to\n    `config.linkerd.io/enable-debug-sidecar`, to match the\n    `--enable-debug-sidecar` CLI flag that sets it\n  * Fixed a bug in `linkerd edges` that caused incorrect identities to be\n    displayed when requests were sent from two or more namespaces\n* Controller\n  * Added the `linkerd.io/control-plane-ns` label to the SMI Traffic Split CRD\n* Proxy\n  * Fixed proxied HTTP/2 connections returning 502 errors when the upstream\n    connection is reset, rather than propagating the reset to the client\n  * Changed the proxy to treat unexpected HTTP/2 frames as stream errors\n    rather than connection errors\n\n## edge-19.6.4\n\nThis release adds support for the SMI [Traffic\nSplit](https://github.com/deislabs/smi-spec/blob/master/traffic-split.md) API.\nCreating a TrafficSplit resource will cause Linkerd to split traffic between\nthe specified backend services. Please see [the\nspec](https://github.com/deislabs/smi-spec/blob/master/traffic-split.md) for\nmore details.\n\n* CLI\n  * Added a check to `install` to prevent installing multiple control planes\n    into different namespaces\n  * Added support for passing a URL directly to `linkerd inject` (thanks\n    @Pothulapati!)\n  * Added the `--all-namespaces` flag to `linkerd edges`\n* Controller\n  * Added support for the SMI TrafficSplit API which allows users to define\n    traffic splits in TrafficSplit custom resources\n* Web UI\n  * Improved UI for Edges table in dashboard by changing column names, adding\n    a \"Secured\" icon and showing an empty Edges table in the case of no\n    returned edges\n\n## edge-19.6.3\n\n* CLI\n  * Updated `linkerd check` to validate the caller can create\n    `PodSecurityPolicy` resources\n* Controller\n  * Default the mutating and validating webhook configurations `sideEffects`\n    property to `None` to indicate that the webhooks have no side effects on\n    other resources (thanks @Pothulapati!)\n* Proxy\n  * Added the `NET_RAW` capability to the proxy-init container to be\n    compatible with `PodSecurityPolicy`s that use `drop: all`\n  * Fixed the proxy rejecting HTTP2 requests that don't have an `:authority`\n  * Improved idle service eviction to reduce resource consumption for clients\n    that send requests to many services\n* Web UI\n  * Removed the \"Debug\" page from the Linkerd dashboard while the\n    functionality of that page is being redesigned\n  * Added an Edges table to the resource detail view that shows the source,\n    destination name, and identity for proxied connections\n\n## edge-19.6.2\n\n* CLI\n  * Added the `--linkerd-cni-enabled` flag to the `install` subcommands so\n    that `NET_ADMIN` capability is omitted from the CNI-enabled control\n    plane's PSP\n* Controller\n  * Default to least-privilege security context values for the proxy container\n    so that auto-inject does not fail on restricted PSPs (thanks @codeman9!)\n  * Defined least privilege default security context values for the proxy\n    container so that auto-injection does not fail on (thanks @codeman9!)\n  * Default the webhook failure policy to `Fail` in order to account for\n    unexpected errors during auto-inject; this ensures uninjected applications\n    are not deployed\n  * Introduced control plane's PSP and RBAC resources into Helm templates;\n    these policies are only in effect if the PSP admission controller is\n    enabled\n  * Removed `UPDATE` operation from proxy-injector webhook because pod\n    mutations are disallowed during update operations\n* Proxy\n  * The `l5d-override-dst` header is now used for inbound service profile\n    discovery\n  * Include errors in `response_total` metrics\n  * Changed the load balancer to require that Kubernetes services are resolved\n    via the control plane\n* Web UI\n  * Fixed dashboard behavior that caused incorrect table sorting\n\n## edge-19.6.1\n\n* CLI\n  * Fixed an issue where, when Linkerd is installed with `--ha`, running\n    `linkerd upgrade` without `--ha` will disable the high availability\n    control plane\n  * Added a `--init-image-version` flag to `linkerd inject` to override the\n    injected proxy-init container version\n* Controller\n  * Added multiple replicas for the `proxy-injector` and `sp-validator`\n    controllers when run in high availability mode (thanks to @Pothulapati!)\n* Proxy\n  * Fixed a memory leak that can occur if an HTTP/2 request with a payload\n    ends before the entire payload is sent to the destination\n* Internal\n  * Moved the proxy-init container to a separate `linkerd/proxy-init` Git\n    repository\n\n## stable-2.3.2\n\nThis stable release fixes a memory leak in the proxy.\n\nTo install this release, run: `curl https://run.linkerd.io/install | sh`\n\n**Full release notes**:\n\n* Proxy\n  * Fixed a memory leak that can occur if an HTTP/2 request with a payload\n    ends before the entire payload is sent to the destination\n\n## edge-19.5.4\n\n* CLI\n  * Added a JSON option to the `linkerd edges` command so that output is\n    scripting friendly and can be parsed easily (thanks @alenkacz!)\n* Controller\n  * **New** Control plane installations now generate a self-signed certificate\n    and private key pair for each webhook, to prepare for future work to make\n    the proxy injector and service profile validator HA\n  * Added a debug container annotation, allowing the `--enable-debug-sidecar`\n    flag to work when auto-injecting Linkerd proxies\n* Proxy\n  * Changed the proxy's routing behavior so that, when the control plane does\n    not resolve a destination, the proxy forwards the request with minimal\n    additional routing logic\n  * Fixed a bug in the proxy's HPACK codec that could cause requests with very\n    large header values to hang indefinitely\n* Web UI\n  * Removed the Authorities table and sidebar link from the dashboard to\n    prepare for a new, improved dashboard view communicating authority data\n* Internal\n  * Modified the integration test for `linkerd upgrade` to test upgrading from\n    the latest stable release instead of the latest edge, to reflect the\n    typical use case\n\n## stable-2.3.1\n\nThis stable release adds a number of proxy stability improvements.\n\nTo install this release, run: `curl https://run.linkerd.io/install | sh`\n\n**Special thanks to**: @zaharidichev and @11Takanori!\n\n**Full release notes**:\n\n* Proxy\n  * Changed the proxy's routing behavior so that, when the control plane does\n    not resolve a destination, the proxy forwards the request with minimal\n    additional routing logic\n  * Fixed a bug in the proxy's HPACK codec that could cause requests with very\n    large header values to hang indefinitely\n  * Replaced the fixed reconnect backoff with an exponential one (thanks,\n    @zaharidichev!)\n  * Fixed an issue where requests could be held indefinitely by the load\n    balancer\n  * Added a dispatch timeout that limits the amount of time a request can be\n    buffered in the proxy\n  * Removed the limit on the number of concurrently active service discovery\n    queries to the destination service\n  * Fixed an epoll notification issue that could cause excessive CPU usage\n  * Added the ability to disable tap by setting an env var (thanks,\n    @zaharidichev!)\n\n## edge-19.5.3\n\n* CLI\n  * **New** Added a `linkerd edges` command that shows the source and\n    destination name and identity for proxied connections, to assist in\n    debugging\n  * Tap can now be disabled for specific pods during injection by using the\n    `--disable-tap` flag, or by using the `config.linkerd.io/disable-tap`\n    annotation\n  * Introduced pre-install healthcheck for clock skew (thanks, @matej-g!)\n* Controller\n  * Added Controller Component Labels to the webhook config resources (thanks,\n    @Pothulapati!)\n  * Moved the tap service into its own pod\n* Proxy\n  * Fix an epoll notification issue that could cause excessive CPU usage\n  * Added the ability to disable tap by setting an env var (thanks,\n    @zaharidichev!)\n\n## edge-19.5.2\n\n* CLI\n  * Fixed `linkerd check` and `linkerd dashboard` failing when any control\n    plane pod is not ready, even when multiple replicas exist (as in HA mode)\n* Controller\n  * Fixed control plane components failing on startup when the Kubernetes API\n    returns an `ErrGroupDiscoveryFailed`\n* Proxy\n  * Added a dispatch timeout that limits the amount of time a request can be\n    buffered in the proxy\n  * Removed the limit on the number of concurrently active service discovery\n    queries to the destination service\n\nSpecial thanks to @zaharidichev for adding end to end tests for proxies with\nTLS!\n\n## edge-19.5.1\n\n* CLI\n  * Added a `linkerd check config` command for verifying that `linkerd install\n    config` was successful\n  * Improved the help documentation of `linkerd install` to clarify flag usage\n  * Added support for private Kubernetes clusters by changing the CLI to\n    connect to the control plane using a port-forward (thanks, @jackprice!)\n* Controller\n  * Fixed pod creation failure when a `ResourceQuota` exists by adding a\n    default resource spec for the proxy-init init container\n* Proxy\n  * Replaced the fixed reconnect backoff with an exponential one (thanks,\n    @zaharidichev!)\n  * Fixed an issue where load balancers can become stuck\n* Internal\n  * Fixed integration tests by adding known proxy-injector log warning to\n    tests\n\n## edge-19.4.5\n\n### Significant Update\n\nAs of this edge release the proxy injector component is always installed. To\nhave the proxy injector inject a pod you still can manually add the\n`linkerd.io/inject: enable` annotation into the pod spec, or at the namespace\nlevel to have all your pods be injected by default. With this release the\nbehaviour of the `linkerd inject` command changes, where the proxy sidecar\ncontainer YAML is no longer included in its output by default, but instead it\nwill just add the annotations to defer the injection to the proxy injector.\nFor use cases that require the full injected YAML to be output, a new\n`--manual` flag has been added.\n\nAnother important update is the introduction of install stages. You still have\nthe old `linkerd install` command, but now it can be broken into `linkerd\ninstall config` which installs the resources that require cluster-level\nprivileges, and `linkerd install control-plane` that continues with the\nresources that only require namespace-level privileges. This also applies to\nthe `linkerd upgrade` command.\n\n* CLI\n  * **Breaking Change** Removed the `--proxy-auto-inject` flag, as the proxy\n    injector is now always installed\n  * **Breaking Change** Replaced the `--linkerd-version` flag with the\n    `--proxy-version` flag in the `linkerd install` and `linkerd upgrade`\n    commands, which allows setting the version for the injected proxy sidecar\n    image, without changing the image versions for the control plane\n  * Introduced install stages: `linkerd install config` and `linkerd install\n    control-plane`\n  * Introduced upgrade stages: `linkerd upgrade config` and `linkerd upgrade\n    control-plane`\n  * Introduced a new `--from-manifests` flag to `linkerd upgrade` allowing\n    manually feeding a previously saved output of `linkerd install` into the\n    command, instead of requiring a connection to the cluster to fetch the\n    config\n  * Introduced a new `--manual` flag to `linkerd inject` to output the proxy\n    sidecar container spec\n  * Introduced a new `--enable-debug-sidecar` option to `linkerd inject`, that\n    injects a debug sidecar to inspect traffic to and from the meshed pod\n  * Added a new check for unschedulable pods and PSP issues (thanks,\n    @liquidslr!)\n  * Disabled the spinner in `linkerd check` when running without a TTY\n  * Ensured the ServiceAccount for the proxy injector is created before its\n    Deployment to avoid warnings when installing the proxy injector (thanks,\n    @dwj300!)\n\n* Controller\n  * Added Go pprof HTTP endpoints to all control plane components' admin\n    servers to better assist debugging efforts\n  * Fixed bug in the proxy injector, where sporadically the pod workload owner\n    wasn't properly determined, which would result in erroneous stats\n  * Added support for a new `config.linkerd.io/disable-identity` annotation to\n    opt out of identity for a specific pod\n\n* Web UI\n  * Added the Font Awesome stylesheet locally; this allows both Font Awesome\n    and Material-UI sidebar icons to display consistently with no/limited\n    internet access (thanks again, @liquidslr!)\n\n* Internal\n  * Known container errors were hidden in the integration tests; now they are\n    reported in the output, still without having the tests fail\n\n## stable-2.3.0\n\nThis stable release introduces a new TLS-based service identity system into\nthe default Linkerd installation, replacing `--tls=optional` and the\n`linkerd-ca` controller. Now, proxies generate ephemeral private keys into a\ntmpfs directory and dynamically refresh certificates, authenticated by\nKubernetes ServiceAccount tokens, and tied to ServiceAccounts as the identity\nprimitive\n\nIn this release, all meshed HTTP communication is private and authenticated by\ndefault.\n\nAmong the many improvements to the web dashboard, we've added a Community page\nto surface news and updates from linkerd.io.\n\nFor more details, see the announcement blog post:\n<https://linkerd.io/2019/04/16/announcing-linkerd-2.3/>\n\nTo install this release, run: `curl https://run.linkerd.io/install | sh`\n\n**Upgrade notes**: The `linkerd-ca` controller has been removed in favor of\nthe `linkerd-identity` controller. If you had previously installed Linkerd\nwith `--tls=optional`, manually delete the `linkerd-ca` deployment after\nupgrading. Also, `--single-namespace` mode is no longer supported. For full\ndetails on upgrading to this release, please see the [upgrade\ninstructions](https://linkerd.io/2/tasks/upgrade/#upgrade-notice-stable-2-3-0).\n\n**Special thanks to**: @codeman9, @harsh-98, @huynq0911, @KatherineMelnyk,\n@liquidslr, @paranoidaditya, @Pothulapati, @TwinProduction, and @yb172!\n\n**Full release notes**:\n\n* CLI\n  * Introduced an `upgrade` command! This allows an existing Linkerd control\n    plane to be reinstalled or reconfigured; it is particularly useful for\n    automatically reusing flags set in the previous `install` or `upgrade`\n  * Introduced the `linkerd metrics` command for fetching proxy metrics\n  * **Breaking Change:** The `--linkerd-cni-enabled` flag has been removed\n    from the `inject` command; CNI is configured at the cluster level with the\n    `install` command and no longer applies to the `inject` command\n  * **Breaking Change** Removed the `--disable-external-profiles` flag from\n    the `install` command; external profiles are now disabled by default and\n    can be enabled with the new `--enable-external-profiles` flag\n  * **Breaking change** Removed the `--api-port` flag from the `inject` and\n    `install` commands, since there's no benefit to running the control\n    plane's destination API on a non-default port (thanks, @paranoidaditya)\n  * **Breaking change** Removed the `--tls=optional` flag from the `linkerd\n    install` command, since TLS is now enabled by default\n  * Changed `install` to accept or generate an issuer Secret for the Identity\n    controller\n  * Changed `install` to fail in the case of a conflict with an existing\n    installation; this can be disabled with the `--ignore-cluster` flag\n  * Added the ability to adjust the Prometheus log level via\n    `--controller-log-level`\n  * Implemented `--proxy-cpu-limit` and `--proxy-memory-limit` for setting the\n    proxy resources limits (`--proxy-cpu` and `--proxy-memory` were deprecated\n    in favor of `proxy-cpu-request` and `proxy-memory-request`) (thanks\n    @TwinProduction!)\n  * Added a validator for the `--proxy-log-level` flag\n  * Updated the `inject` and `uninject` subcommands to issue warnings when\n    resources lack a `Kind` property (thanks @Pothulapati!)\n  * The `inject` command proxy options are now converted into config\n    annotations; the annotations ensure that these configs are persisted in\n    subsequent resource updates\n  * Changed `inject` to require fetching a configuration from the control\n    plane; this can be disabled with the `--ignore-cluster` and\n    `--disable-identity` flags, though this will prevent the injected pods\n    from participating in mesh identity\n  * Included kubectl version check as part of `linkerd check` (thanks @yb172!)\n  * Updated `linkerd check` to ensure hint URLs are displayed for RPC checks\n  * Fixed sporadic (and harmless) race condition error in `linkerd check`\n  * Introduced a check for NET_ADMIN in `linkerd check`\n  * Fixed permissions check for CRDs\n  * Updated the `linkerd dashboard` command to serve the dashboard on a fixed\n    port, allowing it to leverage browser local storage for user settings\n  * Updated the `linkerd routes` command to display rows for routes that are\n    not receiving any traffic\n  * Added TCP stats to the stat command, under the `-o wide` and `-o json`\n    flags\n  * The `stat` command now always shows the number of open TCP connections\n  * Removed TLS metrics from the `stat` command; this is in preparation for\n    surfacing identity metrics in a clearer way\n  * Exposed the `install-cni` command and its flags, and tweaked their\n    descriptions\n  * Eliminated false-positive vulnerability warnings related to go.uuid\n* Controller\n  * Added a new public API endpoint for fetching control plane configuration\n  * **Breaking change** Removed support for running the control plane in\n    single-namespace mode, which was severely limited in the number of\n    features it supported due to not having access to cluster-wide resources;\n    the end goal being Linkerd degrading gracefully depending on its\n    privileges\n  * Updated automatic proxy injection and CLI injection to support overriding\n    inject defaults via pod spec annotations\n  * Added support for the `config.linkerd.io/proxy-version` annotation on pod\n    specs; this will override the injected proxy version\n  * The auto-inject admission controller webhook is updated to watch pods\n    creation and update events; with this change, proxy auto-injection now\n    works for all kinds of workloads, including StatefulSets, DaemonSets,\n    Jobs, etc\n  * Service profile validation is now performed via a webhook endpoint; this\n    prevents Kubernetes from accepting invalid service profiles\n  * Changed the default CPU request from `10m` to `100m` for HA deployments;\n    this will help some intermittent liveness/readiness probes from failing\n    due to tight resource constraints\n  * Updated destination service to return TLS identities only when the\n    destination pod is TLS-aware and is in the same controller namespace\n  * Lessen klog level to improve security\n  * Updated control plane components to query Kubernetes at startup to\n    determine authorized namespaces and if ServiceProfile support is available\n  * Modified the stats payload to include the following TCP stats:\n    `tcp_open_connections`, `tcp_read_bytes_total`, `tcp_write_bytes_total`\n  * Instrumented clients in the control plane connecting to Kubernetes, thus\n    providing better visibility for diagnosing potential problems with those\n    connections\n  * Renamed the \"linkerd-proxy-api\" service to \"linkerd-destination\"\n  * Bumped Prometheus to version 2.7.1 and Grafana to version 5.4.3\n* Proxy\n  * Introduced per-proxy private key generation and dynamic certificate\n    renewal\n  * **Fixed** a connection starvation issue where TLS discovery detection on\n    slow or idle connections could block all other connections from being\n    accepted on the inbound listener of the proxy\n  * **Fixed** a stream leak between the proxy and the control plane that could\n    cause the `linkerd-controller` pod to use an excessive amount of memory\n  * Added a readiness check endpoint on `:4191/ready` so that Kubernetes\n    doesn't consider pods ready until they have acquired a certificate from\n    the Identity controller\n  * Some `l5d-*` informational headers have been temporarily removed from\n    requests and responses because they could leak information to external\n    clients\n  * The proxy's connect timeouts have been updated, especially to improve\n    reconnect behavior between the proxy and the control plane\n  * Increased the inbound/router cap on MAX_CONCURRENT_STREAMS\n  * The `l5d-remote-ip` header is now set on inbound requests and outbound\n    responses\n  * Fixed issue with proxy falling back to filesystem polling due to\n    improperly sized inotify buffer\n* Web UI\n  * **New** Added a Community page to surface news and updates from linkerd.io\n  * Added a Debug page to the web dashboard, allowing you to introspect\n    service discovery state\n  * The Overview page in the Linkerd dashboard now renders appropriately when\n    viewed on mobile devices\n  * Added filter functionality to the metrics tables\n  * Added stable sorting for table rows\n  * Added TCP stats to the Linkerd Pod Grafana dashboard\n  * Added TCP stat tables on the namespace landing page and resource detail\n    page\n  * The topology graph now shows TCP stats if no HTTP stats are available\n  * Improved table display on the resource detail page for resources with\n    TCP-only traffic\n  * Updated the resource detail page to start displaying a table with TCP\n    stats\n  * Modified the Grafana variable queries to use a TCP-based metric, so that\n    if there is only TCP traffic then the dropdowns don't end up empty\n  * Fixed sidebar not updating when resources were added/deleted (thanks\n    @liquidslr!)\n  * Added validation to the \"new service profile\" form (thanks @liquidslr!)\n  * Added a Grafana dashboard and web tables for displaying Job stats (thanks,\n    @Pothulapati!)\n  * Removed TLS columns from the dashboard tables; this is in preparation for\n    surfacing identity metrics in a clearer way\n  * Fixed the behavior of the Top query 'Start' button if a user's query\n    returns no data\n  * Fixed an issue with the order of tables returned from a Top Routes query\n  * Added text wrap for paths in the modal for expanded Tap query data\n  * Fixed a quoting issue with service profile downloads (thanks, @liquidslr!)\n  * Updated sorting of route table to move default routes to the bottom\n  * Removed 'Help' hierarchy and surfaced links on navigation sidebar\n  * Ensured that all the tooltips in Grafana displaying the series are shared\n    across all the graphs\n* Internals\n  * Improved the `bin/go-run` script for the build process so that on failure,\n    all associated background processes are terminated\n  * Added more log errors to the integration tests\n  * Removed the GOPATH dependence from the CLI dev environment\n  * Consolidated injection code from CLI and admission controller code paths\n  * Enabled the following linters: `unparam`, `unconvert`, `goimports`,\n    `goconst`, `scopelint`, `unused`, `gosimple`\n  * Bumped base Docker images\n  * Added the flags `-update` and `-pretty-diff` to tests to allow overwriting\n    fixtures and to print the full text of the fixtures upon mismatches\n  * Introduced golangci-lint tooling, using `.golangci.yml` to centralize the\n    config\n  * Added a `-cover` parameter to track code coverage in go tests (more info\n    in TEST.md)\n  * Renamed a function in a test that was shadowing a go built-in function\n    (thanks @huynq0911!)\n\n## edge-19.4.4\n\n* Proxy\n  * **Fixed** a connection starvation issue where TLS discovery detection on\n    slow or idle connections could block all other connections from being\n    accepted on the inbound listener of the proxy\n* CLI\n  * **Fixed** `inject` to allow the `--disable-identity` flag to be used\n    without having to specify the `--ignore-cluster` flag\n* Web UI\n  * The Overview page in the Linkerd dashboard now renders appropriately when\n    viewed on mobile devices\n\n## edge-19.4.3\n\n* CLI\n  * **Fixed** `linkerd upgrade` command not upgrading proxy containers (thanks\n    @jon-walton for the issue report!)\n  * **Fixed** `linkerd upgrade` command not installing the identity service\n    when it was not already installed\n  * Eliminate false-positive vulnerability warnings related to go.uuid\n\nSpecial thanks to @KatherineMelnyk for updating the web component to read the\nUUID from the `linkerd-config` ConfigMap!\n\n## edge-19.4.2\n\n* CLI\n  * Removed TLS metrics from the `stat` command; this is in preparation for\n    surfacing identity metrics in a clearer way\n  * The `upgrade` command now outputs a URL that explains next steps for\n    upgrading\n  * **Breaking Change:** The `--linkerd-cni-enabled` flag has been removed\n    from the `inject` command; CNI is configured at the cluster level with the\n    `install` command and no longer applies to the `inject` command\n* Controller\n  * Service profile validation is now performed via a webhook endpoint; this\n    prevents Kubernetes from accepting invalid service profiles\n  * Added support for the `config.linkerd.io/proxy-version` annotation on pod\n    specs; this will override the injected proxy version\n  * Changed the default CPU request from `10m` to `100m` for HA deployments;\n    this will help some intermittent liveness/readiness probes from failing\n    due to tight resource constraints\n* Proxy\n  * The `CommonName` field on CSRs is now set to the proxy's identity name\n* Web UI\n  * Removed TLS columns from the dashboard tables; this is in preparation for\n    surfacing identity metrics in a clearer way\n\n## edge-19.4.1\n\n* CLI\n  * Introduced an `upgrade` command! This allows an existing Linkerd control\n    plane to be reinstalled or reconfigured; it is particularly useful for\n    automatically reusing flags set in the previous `install` or `upgrade`\n  * The `inject` command proxy options are now converted into config\n    annotations; the annotations ensure that these configs are persisted in\n    subsequent resource updates\n  * The `stat` command now always shows the number of open TCP connections\n  * **Breaking Change** Removed the `--disable-external-profiles` flag from\n    the `install` command; external profiles are now disabled by default and\n    can be enabled with the new `--enable-external-profiles` flag\n* Controller\n  * The auto-inject admission controller webhook is updated to watch pods\n    creation and update events; with this change, proxy auto-injection now\n    works for all kinds of workloads, including StatefulSets, DaemonSets,\n    Jobs, etc\n* Proxy\n  * Some `l5d-*` informational headers have been temporarily removed from\n    requests and responses because they could leak information to external\n    clients\n* Web UI\n  * The topology graph now shows TCP stats if no HTTP stats are available\n  * Improved table display on the resource detail page for resources with\n    TCP-only traffic\n  * Added validation to the \"new service profile\" form (thanks @liquidslr!)\n\n## edge-19.3.3\n\n### Significant Update\n\nThis edge release introduces a new TLS Identity system into the default\nLinkerd installation, replacing `--tls=optional` and the `linkerd-ca`\ncontroller. Now, proxies generate ephemeral private keys into a tmpfs\ndirectory and dynamically refresh certificates, authenticated by Kubernetes\nServiceAccount tokens, via the newly-introduced Identity controller.\n\nNow, all meshed HTTP communication is private and authenticated by default.\n\n* CLI\n  * Changed `install` to accept or generate an issuer Secret for the Identity\n    controller\n  * Changed `install` to fail in the case of a conflict with an existing\n    installation; this can be disabled with the `--ignore-cluster` flag\n  * Changed `inject` to require fetching a configuration from the control\n    plane; this can be disabled with the `--ignore-cluster` and\n    `--disable-identity` flags, though this will prevent the injected pods\n    from participating in mesh identity\n  * **Breaking change** Removed the `--tls=optional` flag from the `linkerd\n    install` command, since TLS is now enabled by default\n  * Added the ability to adjust the Prometheus log level\n* Proxy\n  * **Fixed** a stream leak between the proxy and the control plane that could\n    cause the `linkerd-controller` pod to use an excessive amount of memory\n  * Introduced per-proxy private key generation and dynamic certificate\n    renewal\n  * Added a readiness check endpoint on `:4191/ready` so that Kubernetes\n    doesn't consider pods ready until they have acquired a certificate from\n    the Identity controller\n  * The proxy's connect timeouts have been updated, especially to improve\n    reconnect behavior between the proxy and the control plane\n* Web UI\n  * Added TCP stats to the Linkerd Pod Grafana dashboard\n  * Fixed the behavior of the Top query 'Start' button if a user's query\n    returns no data\n  * Added stable sorting for table rows\n  * Fixed an issue with the order of tables returned from a Top Routes query\n  * Added text wrap for paths in the modal for expanded Tap query data\n* Internal\n  * Improved the `bin/go-run` script for the build process so that on failure,\n    all associated background processes are terminated\n\nSpecial thanks to @liquidslr for many useful UI and log changes, and to\n@mmalone and @sourishkrout at @smallstep for collaboration and advice on the\nIdentity system!\n\n## edge-19.3.2\n\n* Controller\n  * **Breaking change** Removed support for running the control plane in\n    single-namespace mode, which was severely limited in the number of\n    features it supported due to not having access to cluster-wide resources\n  * Updated automatic proxy injection and CLI injection to support overriding\n    inject defaults via pod spec annotations\n  * Added a new public API endpoint for fetching control plane configuration\n* CLI\n  * **Breaking change** Removed the `--api-port` flag from the `inject` and\n    `install` commands, since there's no benefit to running the control\n    plane's destination API on a non-default port (thanks, @paranoidaditya)\n  * Introduced the `linkerd metrics` command for fetching proxy metrics\n  * Updated the `linkerd routes` command to display rows for routes that are\n    not receiving any traffic\n  * Updated the `linkerd dashboard` command to serve the dashboard on a fixed\n    port, allowing it to leverage browser local storage for user settings\n* Web UI\n  * **New** Added a Community page to surface news and updates from linkerd.io\n  * Fixed a quoting issue with service profile downloads (thanks, @liquidslr!)\n  * Added a Grafana dashboard and web tables for displaying Job stats (thanks,\n    @Pothulapati!)\n  * Updated sorting of route table to move default routes to the bottom\n  * Added TCP stat tables on the namespace landing page and resource detail\n    page\n\n## edge-19.3.1\n\n* CLI\n  * Introduced a check for NET_ADMIN in `linkerd check`\n  * Fixed permissions check for CRDs\n  * Included kubectl version check as part of `linkerd check` (thanks @yb172!)\n  * Added TCP stats to the stat command, under the `-o wide` and `-o json`\n    flags\n* Controller\n  * Updated the `mutatingwebhookconfiguration` so that it is recreated when\n    the proxy injector is restarted, so that the MWC always picks up the\n    latest config template during version upgrade\n* Proxy\n  * Increased the inbound/router cap on MAX_CONCURRENT_STREAMS\n  * The `l5d-remote-ip` header is now set on inbound requests and outbound\n    responses\n* Web UI\n  * Fixed sidebar not updating when resources were added/deleted (thanks\n    @liquidslr!)\n  * Added filter functionality to the metrics tables\n* Internal\n  * Added more log errors to the integration tests\n  * Removed the GOPATH dependence from the CLI dev environment\n  * Consolidated injection code from CLI and admission controller code paths\n\n## edge-19.2.5\n\n* CLI\n  * Updated `linkerd check` to ensure hint URLs are displayed for RPC checks\n* Controller\n  * Updated the auto-inject admission controller webhook to respond to UPDATE\n    events for deployment workloads\n  * Updated destination service to return TLS identities only when the\n    destination pod is TLS-aware and is in the same controller namespace\n  * Lessen klog level to improve security\n  * Updated control plane components to query Kubernetes at startup to\n    determine authorized namespaces and if ServiceProfile support is available\n  * Modified the stats payload to include the following TCP stats:\n    `tcp_open_connections`, `tcp_read_bytes_total`, `tcp_write_bytes_total`\n* Proxy\n  * Fixed issue with proxy falling back to filesystem polling due to\n    improperly sized inotify buffer\n* Web UI\n  * Removed 'Help' hierarchy and surfaced links on navigation sidebar\n  * Added a Debug page to the web dashboard, allowing you to introspect\n    service discovery state\n  * Updated the resource detail page to start displaying a table with TCP\n    stats\n* Internal\n  * Enabled the following linters: `unparam`, `unconvert`, `goimports`,\n    `goconst`, `scopelint`, `unused`, `gosimple`\n  * Bumped base Docker images\n\n## stable-2.2.1\n\nThis stable release polishes some of the CLI help text and fixes two issues\nthat came up since the stable-2.2.0 release.\n\nTo install this release, run: `curl https://run.linkerd.io/install | sh`\n\n**Full release notes**:\n\n* CLI\n  * Fixed handling of kubeconfig server urls that include paths\n  * Updated the description of the `--proxy-auto-inject` flag to indicate that\n    it is no longer experimental\n  * Updated the `profile` help text to match the other commands\n  * Added the \"ep\" alias for the `endpoints` command\n* Controller\n  * Stopped logging an error when a route doesn't specify a timeout\n\n## edge-19-2.4\n\n* CLI\n  * Implemented `--proxy-cpu-limit` and `--proxy-memory-limit` for setting the\n    proxy resources limits (`--proxy-cpu` and `--proxy-memory` were deprecated\n    in favor of `proxy-cpu-request` and `proxy-memory-request`) (thanks\n    @TwinProduction!)\n  * Updated the `inject` and `uninject` subcommands to issue warnings when\n    resources lack a `Kind` property (thanks @Pothulapati!)\n  * Exposed the `install-cni` command and its flags, and tweaked their\n    descriptions\n  * Fixed handling of kubeconfig server urls that include paths\n  * Updated the description of the `--proxy-auto-inject` flag to indicate that\n    it is no longer experimental\n  * Updated the `profile` help text to match the other commands\n  * Added the \"ep\" alias for the `endpoints` command (also @Pothulapati!)\n  * Added a validator for the `--proxy-log-level` flag\n  * Fixed sporadic (and harmless) race condition error in `linkerd check`\n* Controller\n  * Instrumented clients in the control plane connecting to Kubernetes, thus\n    providing better visibility for diagnosing potential problems with those\n    connections\n  * Stopped logging an error when a route doesn't specify a timeout\n  * Renamed the \"linkerd-proxy-api\" service to \"linkerd-destination\"\n  * Bumped Prometheus to version 2.7.1 and Grafana to version 5.4.3\n* Web UI\n  * Modified the Grafana variable queries to use a TCP-based metric, so that\n    if there is only TCP traffic then the dropdowns don't end up empty\n  * Ensured that all the tooltips in Grafana displaying the series are shared\n    across all the graphs\n* Internals\n  * Added the flags `-update` and `-pretty-diff` to tests to allow overwriting\n    fixtures and to print the full text of the fixtures upon mismatches\n  * Introduced golangci-lint tooling, using `.golangci.yml` to centralize the\n    config\n  * Added a `-cover` parameter to track code coverage in go tests (more info\n    in TEST.md)\n  * Added integration tests for `--single-namespace`\n  * Renamed a function in a test that was shadowing a go built-in function\n    (thanks @huynq0911!)\n\n## stable-2.2.0\n\nThis stable release introduces automatic request retries and timeouts, and\ngraduates auto-inject to be a fully-supported (non-experimental) feature. It\nadds several new CLI commands, including `logs` and `endpoints`, that provide\ndiagnostic visibility into Linkerd's control plane. Finally, it introduces two\nexciting experimental features: a cryptographically-secured client identity\nheader, and a CNI plugin that avoids the need for `NET_ADMIN` kernel\ncapabilities at deploy time.\n\nFor more details, see the announcement blog post:\n<https://blog.linkerd.io/2019/02/12/announcing-linkerd-2-2/>\n\nTo install this release, run: `curl https://run.linkerd.io/install | sh`\n\n**Upgrade notes**: The default behavior for proxy auto injection and service\nprofile ownership has changed as part of this release. Please see the [upgrade\ninstructions](https://linkerd.io/2/tasks/upgrade/#upgrade-notice-stable-2-2-0)\nfor more details.\n\n**Special thanks to**: @alenkacz, @codeman9, @jonrichards, @radu-matei,\n@yeya24, and @zknill\n\n**Full release notes**:\n\n* CLI\n  * Improved service profile validation when running `linkerd check` in order\n    to validate service profiles in all namespaces\n  * Added the `linkerd endpoints` command to introspect Linkerd's service\n    discovery state\n  * Added the `--tap` flag to `linkerd profile` to generate service profiles\n    using the route results seen during the tap\n  * Added support for the `linkerd.io/inject: disabled` annotation on pod\n    specs to disable injection for specific pods when running `linkerd inject`\n  * Added support for `basePath` in OpenAPI 2.0 files when running `linkerd\n    profile --open-api`\n  * Increased `linkerd check` client timeout from 5 seconds to 30 seconds to\n    fix issues for clusters with slow API servers\n  * Updated `linkerd routes` to no longer return rows for `ExternalName`\n    services in the namespace\n  * Broadened the set of valid URLs when connecting to the Kubernetes API\n  * Added the `--proto` flag to `linkerd profile` to output a service profile\n    based on a Protobuf spec file\n  * Fixed CLI connection failures to clusters that use self-signed\n    certificates\n  * Simplified `linkerd install` so that setting up proxy auto-injection (flag\n    `--proxy-auto-inject`) no longer requires enabling TLS (flag `--tls`)\n  * Added links for each `linkerd check` failure, pointing to a relevant\n    section in our new FAQ page with resolution steps for each case\n  * Added optional `linkerd install-sp` command to generate service profiles\n    for the control plane, providing per-route metrics for control plane\n    components\n  * Removed `--proxy-bind-timeout` flag from `linkerd install` and `linkerd\n    inject`, as the proxy no longer accepts this environment variable\n  * Improved CLI appearance on Windows systems\n  * Improved `linkerd check` output, fixed bug with `--single-namespace`\n  * Fixed panic when `linkerd routes` is called in single-namespace mode\n  * Added `linkerd logs` command to surface logs from any container in the\n    Linkerd control plane\n  * Added `linkerd uninject` command to remove the Linkerd proxy from a\n    Kubernetes config\n  * Improved `linkerd inject` to re-inject a resource that already has a\n    Linkerd proxy\n  * Improved `linkerd routes` to list all routes, including those without\n    traffic\n  * Improved readability in `linkerd check` and `linkerd inject` outputs\n  * Adjusted the set of checks that are run before executing CLI commands,\n    which allows the CLI to be invoked even when the control plane is not\n    fully ready\n  * Fixed reporting of injected resources when the `linkerd inject` command is\n    run on `List` type resources with multiple items\n  * Updated the `linkerd dashboard` command to use port-forwarding instead of\n    proxying when connecting to the web UI and Grafana\n  * Added validation for the `ServiceProfile` CRD\n  * Updated the `linkerd check` command to disallow setting both the `--pre`\n    and `--proxy` flags simultaneously\n  * Added `--routes` flag to the `linkerd top` command, for grouping table\n    rows by route instead of by path\n  * Updated Prometheus configuration to automatically load `*_rules.yml` files\n  * Removed TLS column from the `linkerd routes` command output\n  * Updated `linkerd install` output to use non-default service accounts,\n    `emptyDir` volume mounts, and non-root users\n  * Removed cluster-wide resources from single-namespace installs\n  * Fixed resource requests for proxy-injector container in `--ha` installs\n* Controller\n  * Fixed issue with auto-injector not setting the proxy ID, which is required\n    to successfully locate client service profiles\n  * Added full stat and tap support for DaemonSets and StatefulSets in the\n    CLI, Grafana, and web UI\n  * Updated auto-injector to use the proxy log level configured at install\n    time\n  * Fixed issue with auto-injector including TLS settings in injected pods\n    even when TLS was not enabled\n  * Changed automatic proxy injection to be opt-in via the `linkerd.io/inject`\n    annotation on the pod or namespace\n  * Move service profile definitions to client and server namespaces, rather\n    than the control plane namespace\n  * Added `linkerd.io/created-by` annotation to the linkerd-cni DaemonSet\n  * Added a 10 second keepalive default to resolve dropped connections in\n    Azure environments\n  * Improved node selection for installing the linkerd-cni DaemonSet\n  * Corrected the expected controller identity when configuring pods with TLS\n  * Modified klog to be verbose when controller log-level is set to `debug`\n  * Added support for retries and timeouts, configured directly in the service\n    profile for each route\n  * Added an experimental CNI plugin to avoid requiring the NET_ADMIN\n    capability when injecting proxies\n  * Improved the API for `ListPods`\n  * Fixed `GetProfiles` API call not returning immediately when no profile\n    exists (resulting in proxies logging warnings)\n  * Blocked controller initialization until caches have synced with kube API\n  * Fixed proxy-api handling of named target ports in service configs\n  * Added parameter to stats API to skip retrieving prometheus stats\n* Web UI\n  * Updated navigation to link the Linkerd logo back to the Overview page\n  * Fixed console warnings on the Top page\n  * Grayed-out the tap icon for requests from sources that are not meshed\n  * Improved resource detail pages to show all resource types\n  * Fixed stats not appearing for routes that have service profiles installed\n  * Added \"meshed\" and \"no traffic\" badges on the resource detail pages\n  * Fixed `linkerd dashboard` to maintain proxy connection when browser open\n    fails\n  * Fixed JavaScript bundling to avoid serving old versions after upgrade\n  * Reduced the size of the webpack JavaScript bundle by nearly 50%\n  * Fixed an indexing error on the top results page\n  * Restored unmeshed resources in the network graph on the resource detail\n    page\n  * Adjusted label for unknown routes in route tables, added tooltip\n  * Updated Top Routes page to persist form settings in URL\n  * Added button to create new service profiles on Top Routes page\n  * Fixed CLI commands displayed when linkerd is running in non-default\n    namespace\n* Proxy\n  * Modified the way in which canonicalization warnings are logged to reduce\n    the overall volume of error logs and make it clearer when failures occur\n  * Added TCP keepalive configuration to fix environments where peers may\n    silently drop connections\n  * Updated the `Get` and `GetProfiles` APIs to accept a `proxy_id` parameter\n    in order to return more tailored results\n  * Removed TLS fallback-to-plaintext if handshake fails\n  * Added the ability to override a proxy's normal outbound routing by adding\n    an `l5d-override-dst` header\n  * Added `LINKERD2_PROXY_DNS_CANONICALIZE_TIMEOUT` environment variable to\n    customize the timeout for DNS queries to canonicalize a name\n  * Added support for route timeouts in service profiles\n  * Improved logging for gRPC errors and for malformed HTTP/2 request headers\n  * Improved log readability by moving some noisy log messages to more verbose\n    log levels\n  * Fixed a deadlock in HTTP/2 stream reference counts\n  * Updated the proxy-init container to exit with a non-zero exit code if\n    initialization fails, making initialization errors much more visible\n  * Fixed a memory leak due to leaked UDP sockets for failed DNS queries\n  * Improved configuration of the PeakEwma load balancer\n  * Improved handling of ports configured to skip protocol detection when the\n    proxy is running with TLS enabled\n\n## edge-19.2.3\n\n* Controller\n  * Fixed issue with auto-injector not setting the proxy ID, which is required\n    to successfully locate client service profiles\n* Web UI\n  * Updated navigation to link the Linkerd logo back to the Overview page\n  * Fixed console warnings on the Top page\n\n## edge-19.2.2\n\n* CLI\n  * Improved service profile validation when running `linkerd check` in order\n    to validate service profiles in all namespaces\n* Controller\n  * Added stat and tap support for StatefulSets in the CLI, Grafana, and web\n    UI\n  * Updated auto-injector to use the proxy log level configured at install\n    time\n  * Fixed issue with auto-injector including TLS settings in injected pods\n    even when TLS was not enabled\n* Proxy\n  * Modified the way in which canonicalization warnings are logged to reduce\n    the overall volume of error logs and make it clearer when failures occur\n\n## edge-19.2.1\n\n* Controller\n  * **Breaking change** Changed automatic proxy injection to be opt-in via the\n    `linkerd.io/inject` annotation on the pod or namespace. More info:\n    <https://linkerd.io/2/proxy-injection/>\n  * **Breaking change** `ServiceProfile`s are now defined in client and server\n    namespaces, rather than the control plane namespace. `ServiceProfile`s\n    defined in the client namespace take priority over ones defined in the\n    server namespace\n  * Added `linkerd.io/created-by` annotation to the linkerd-cni DaemonSet\n    (thanks @codeman9!)\n  * Added a 10 second keepalive default to resolve dropped connections in\n    Azure environments\n  * Improved node selection for installing the linkerd-cni DaemonSet (thanks\n    @codeman9!)\n  * Corrected the expected controller identity when configuring pods with TLS\n  * Modified klog to be verbose when controller log-level is set to `Debug`\n* CLI\n  * Added the `linkerd endpoints` command to introspect Linkerd's service\n    discovery state\n  * Added the `--tap` flag to `linkerd profile` to generate a `ServiceProfile`\n    by using the route results seen during the tap\n  * Added support for the `linkerd.io/inject: disabled` annotation on pod\n    specs to disable injection for specific pods when running `linkerd inject`\n  * Added support for `basePath` in OpenAPI 2.0 files when running `linkerd\n    profile --open-api`\n  * Increased `linkerd check` client timeout from 5 seconds to 30 seconds to\n    fix issues for clusters with a slower API server\n  * `linkerd routes` will no longer return rows for `ExternalName` services in\n    the namespace\n  * Broadened set of valid URLs when connecting to the Kubernetes API\n  * Improved `ServiceProfile` field validation in `linkerd check`\n* Proxy\n  * Added TCP keepalive configuration to fix environments where peers may\n    silently drop connections\n  * The `Get` and `GetProfiles` API now accept a `proxy_id` parameter in order\n    to return more tailored results\n  * Removed TLS fallback-to-plaintext if handshake fails\n\n## edge-19.1.4\n\n* Controller\n  * Added support for timeouts! Configurable in the service profiles for each\n    route\n  * Added an experimental CNI plugin to avoid requiring the NET_ADMIN\n    capability when injecting proxies (more details at\n    <https://linkerd.io/2/cni)> (thanks @codeman9!)\n  * Added more improvements to the API for `ListPods` (thanks @alenkacz!)\n* Web UI\n  * Grayed-out the tap icon for requests from sources that are not meshed\n* CLI\n  * Added the `--proto` flag to `linkerd profile` to output a service profile\n    based on a Protobuf spec file\n  * Fixed CLI connection failure to clusters that use self-signed certificates\n  * Simplified `linkerd install` so that setting up proxy auto-injection (flag\n    `--proxy-auto-inject`) no longer requires enabling TLS (flag `--tls`)\n  * Added links for each `linkerd check` failure, pointing to a relevant\n    section in our new FAQ page with resolution steps for each case\n\n## edge-19.1.3\n\n* Controller\n  * Improved API for `ListPods` (thanks @alenkacz!)\n  * Fixed `GetProfiles` API call not returning immediately when no profile\n    exists (resulting in proxies logging warnings)\n* Web UI\n  * Improved resource detail pages now show all resource types\n  * Fixed stats not appearing for routes that have service profiles installed\n* CLI\n  * Added optional `linkerd install-sp` command to generate service profiles\n    for the control plane, providing per-route metrics for control plane\n    components\n  * Removed `--proxy-bind-timeout` flag from `linkerd install` and `linkerd\n    inject` commands, as the proxy no longer accepts this environment variable\n  * Improved CLI appearance on Windows systems\n  * Improved `linkerd check` output, fixed check bug when using\n    `--single-namespace` (thanks to @djeeg for the bug report!)\n  * Improved `linkerd stat` now supports DaemonSets (thanks @zknill!)\n  * Fixed panic when `linkerd routes` is called in single-namespace mode\n* Proxy\n  * Added the ability to override a proxy's normal outbound routing by adding\n    an `l5d-override-dst` header\n  * Added `LINKERD2_PROXY_DNS_CANONICALIZE_TIMEOUT` environment variable to\n    customize the timeout for DNS queries to canonicalize a name\n  * Added support for route timeouts in service profiles\n  * Improved logging for gRPC errors and for malformed HTTP/2 request headers\n  * Improved log readability by moving some noisy log messages to more verbose\n    log levels\n\n## edge-19.1.2\n\n* Controller\n  * Retry support! Introduce an `isRetryable` property to service profiles to\n    enable configuring retries on a per-route basis\n* Web UI\n  * Add \"meshed\" and \"no traffic\" badges on the resource detail pages\n  * Fix `linkerd dashboard` to maintain proxy connection when browser open\n    fails\n  * Fix JavaScript bundling to avoid serving old versions after upgrade\n* CLI\n  * Add `linkerd logs` command to surface logs from any container in the\n    Linkerd control plane (shout out to\n    [Stern](https://github.com/wercker/stern)!)\n  * Add `linkerd uninject` command to remove the Linkerd proxy from a\n    Kubernetes config\n  * Improve `linkerd inject` to re-inject a resource that already has a\n    Linkerd proxy\n  * Improve `linkerd routes` to list all routes, including those without\n    traffic\n  * Improve readability in `linkerd check` and `linkerd inject` outputs\n* Proxy\n  * Fix a deadlock in HTTP/2 stream reference counts\n\n## edge-19.1.1\n\n* CLI\n  * Adjust the set of checks that are run before executing CLI commands, which\n    allows the CLI to be invoked even when the control plane is not fully\n    ready\n  * Fix reporting of injected resources when the `linkerd inject` command is\n    run on `List` type resources with multiple items\n  * Update the `linkerd dashboard` command to use port-forwarding instead of\n    proxying when connecting to the web UI and Grafana\n  * Add validation for the `ServiceProfile` CRD (thanks, @alenkacz!)\n  * Update the `linkerd check` command to disallow setting both the `--pre`\n    and `--proxy` flags simultaneously (thanks again, @alenkacz!)\n* Web UI\n  * Reduce the size of the webpack JavaScript bundle by nearly 50%!\n  * Fix an indexing error on the top results page\n* Proxy\n  * **Fixed** The proxy-init container now exits with a non-zero exit code if\n    initialization fails, making initialization errors much more visible\n  * **Fixed** The proxy previously leaked UDP sockets for failed DNS queries,\n    causing a memory leak; this has been fixed\n\n## edge-18.12.4\n\nUpgrade notes: The control plane components have been renamed as of the\nedge-18.12.1 release to reduce possible naming collisions. To upgrade an older\ninstallation, see the [Upgrade Guide](https://linkerd.io/2/upgrade/).\n\n* CLI\n  * Add `--routes` flag to the `linkerd top` command, for grouping table rows\n    by route instead of by path\n  * Update Prometheus configuration to automatically load `*_rules.yml` files\n  * Remove TLS column from the `linkerd routes` command output\n* Web UI\n  * Restore unmeshed resources in the network graph on the resource detail\n    page\n  * Reduce the overall size of the asset bundle for the web frontend\n* Proxy\n  * Improve configuration of the PeakEwma load balancer\n\nSpecial thanks to @radu-matei for cleaning up a whole slew of Go lint\nwarnings, and to @jonrichards for improving the Rust build setup!\n\n## edge-18.12.3\n\nUpgrade notes: The control plane components have been renamed as of the\nedge-18.12.1 release to reduce possible naming collisions. To upgrade an older\ninstallation, see the [Upgrade Guide](https://linkerd.io/2/upgrade/).\n\n* CLI\n  * Multiple improvements to the `linkerd install` config (thanks @codeman9!)\n    * Use non-default service accounts for grafana and web deployments\n    * Use `emptyDir` volume mount for prometheus and grafana pods\n    * Set security context on control plane components to not run as root\n  * Remove cluster-wide resources from single-namespace installs\n    * Disable service profiles in single-namespace mode\n    * Require that namespace already exist for single-namespace installs\n  * Fix resource requests for proxy-injector container in `--ha` installs\n* Controller\n  * Block controller initialization until caches have synced with kube API\n  * Fix proxy-api handling of named target ports in service configs\n  * Add parameter to stats API to skip retrieving prometheus stats (thanks,\n    @alpeb!)\n* Web UI\n  * Adjust label for unknown routes in route tables, add tooltip\n  * Update Top Routes page to persist form settings in URL\n  * Add button to create new service profiles on Top Routes page\n  * Fix CLI commands displayed when linkerd is running in non-default\n    namespace\n* Proxy\n  * Proxies with TLS enabled now honor ports configured to skip protocol\n    detection\n\n## stable-2.1.0\n\nThis stable release introduces several major improvements, including per-route\nmetrics, service profiles, and a vastly improved dashboard UI. It also adds\nseveral significant experimental features, including proxy auto-injection,\nsingle namespace installs, and a high-availability mode for the control plane.\n\nFor more details, see the announcement blog post:\n<https://blog.linkerd.io/2018/12/06/announcing-linkerd-2-1/>\n\nTo install this release, run: `curl https://run.linkerd.io/install | sh`\n\n**Upgrade notes**: The control plane components have been renamed in this\nrelease to reduce possible naming collisions. Please make sure to read the\n[upgrade\ninstructions](https://linkerd.io/2/upgrade/#upgrade-notice-stable-2-1-0) if\nyou are upgrading from the `stable-2.0.0` release.\n\n**Special thanks to**: @alenkacz, @alpeb, @benjdlambert, @fahrradflucht,\n@ffd2subroutine, @hypnoglow, @ihcsim, @lucab, and @rochacon\n\n**Full release notes**:\n\n* CLI\n  * `linkerd routes` command displays per-route stats for _any resource_\n  * Service profiles are now supported for external authorities\n  * `linkerd routes --open-api` flag generates a service profile based on an\n    OpenAPI specification (swagger) file\n  * `linkerd routes` command displays per-route stats for services with\n    service profiles\n  * Add `--ha` flag to `linkerd install` command, for HA deployment of the\n    control plane\n  * Update stat command to accept multiple stat targets\n  * Fix authority stat filtering when the `--from` flag is present\n  * Various improvements to check command, including:\n    * Emit warnings instead of errors when not running the latest version\n    * Add retries if control plane health check fails initially\n    * Run all pre-install RBAC checks, instead of stopping at first failure\n  * Fixed an issue with the `--registry` install flag not accepting hosts with\n    ports\n  * Added an `--output` stat flag, for printing stats as JSON\n  * Updated the `top` table to set column widths dynamically\n  * Added a `--single-namespace` install flag for installing the control plane\n    with Role permissions instead of ClusterRole permissions\n  * Added a `--proxy-auto-inject` flag to the `install` command, allowing for\n    auto-injection of sidecar containers\n  * Added `--proxy-cpu` and `--proxy-memory` flags to the `install` and\n    `inject` commands, giving the ability to configure CPU + Memory requests\n  * Added a `--context` flag to specify the context to use to talk to the\n    Kubernetes apiserver\n  * The namespace in which Linkerd is installed is configurable via the\n    `LINKERD_NAMESPACE` env var, in addition to the `--linkerd-namespace` flag\n  * The wait time for the `check` and `dashboard` commands is configurable via\n    the `--wait` flag\n  * The `top` command now aggregates by HTTP method as well\n* Controller\n  * Rename snake case fields to camel case in service profile spec\n  * Controller components are now prefixed with `linkerd-` to prevent name\n    collisions with existing resources\n  * `linkerd install --disable-h2-upgrade` flag has been added to control\n    automatic HTTP/2 upgrading\n  * Fix auto injection issue on Kubernetes `v1.9.11` that would merge, rather\n    than append, the proxy container into the application\n  * Fixed a few issues with auto injection via the proxy-injector webhook:\n    * Injected pods now execute the linkerd-init container last, to avoid\n      rerouting requests during pod init\n    * Original pod labels and annotations are preserved when auto-injecting\n  * CLI health check now uses unified endpoint for data plane checks\n  * Include Licence files in all Docker images\n* Proxy\n  * The proxy's `tap` subsystem has been reimplemented to be more efficient\n    and and reliable\n    * The proxy now supports route metadata in tap queries and events\n  * A potential HTTP/2 window starvation bug has been fixed\n  * Prometheus counters now wrap properly for values greater than 2^53\n  * Add controller client metrics, scoped under `control_`\n  * Canonicalize outbound names via DNS for inbound profiles\n  * Fix routing issue when a pod makes a request to itself\n  * Only include `classification` label on `response_total` metric\n  * Remove panic when failing to get remote address\n  * Better logging in TCP connect error messages\n* Web UI\n  * Top routes page, served at `/routes`\n  * Route metrics are now available in the resource detail pages for services\n    with configured profiles\n  * Service profiles can be created and downloaded from the Web UI\n  * Top Routes page, served at `/routes`\n  * Fixed a smattering of small UI issues\n  * Added a new Grafana dashboard for authorities\n  * Revamped look and feel of the Linkerd dashboard by switching component\n    libraries from antd to material-ui\n  * Added a Help section in the sidebar containing useful links\n  * Tap and Top pages\n    * Added clear button to query form\n  * Resource Detail pages\n    * Limit number of resources shown in the graph\n  * Resource Detail page\n    * Better rendering of the dependency graph at the top of the page\n    * Unmeshed sources are now populated in the Inbound traffic table\n    * Sources and destinations are aligned in the popover\n  * Tap and Top pages\n    * Additional validation and polish for the form controls\n    * The top table clears older results when a new top call is started\n    * The top table now aggregates by HTTP method as well\n\n## edge-18.12.2\n\nUpgrade notes: The control plane components have been renamed as of the\nedge-18.12.1 release to reduce possible naming collisions. To upgrade an older\ninstallation, see the [Upgrade Guide](https://linkerd.io/2/upgrade/).\n\n* Controller\n  * Rename snake case fields to camel case in service profile spec\n\n## edge-18.12.1\n\nUpgrade notes: The control plane components have been renamed in this release\nto reduce possible naming collisions. To upgrade an existing installation:\n\n* Install new CLI: `curl https://run.linkerd.io/install-edge | sh`\n* Install new control plane: `linkerd install | kubectl apply -f -`\n* Remove old deploys/cms: `kubectl -n linkerd get deploy,cm -oname | grep -v\n  linkerd | xargs kubectl -n linkerd delete`\n* Re-inject your applications: `linkerd inject my-app.yml | kubectl apply -f\n  -`\n* Remove old services: `kubectl -n linkerd get svc -oname | grep -v linkerd |\n  xargs kubectl -n linkerd delete`\n\nFor more information, see the [Upgrade Guide](https://linkerd.io/2/upgrade/).\n\n* CLI\n  * **Improved** `linkerd routes` command displays per-route stats for _any\n    resource_!\n  * **New** Service profiles are now supported for external authorities!\n  * **New** `linkerd routes --open-api` flag generates a service profile based\n    on an OpenAPI specification (swagger) file\n* Web UI\n  * **New** Top routes page, served at `/routes`\n  * **New** Route metrics are now available in the resource detail pages for\n    services with configured profiles\n  * **New** Service profiles can be created and downloaded from the Web UI\n* Controller\n  * **Improved** Controller components are now prefixed with `linkerd-` to\n    prevent name collisions with existing resources\n  * **New** `linkerd install --disable-h2-upgrade` flag has been added to\n    control automatic HTTP/2 upgrading\n* Proxy\n  * **Improved** The proxy's `tap` subsystem has been reimplemented to be more\n    efficient and and reliable\n    * The proxy now supports route metadata in tap queries and events\n  * **Fixed** A potential HTTP/2 window starvation bug has been fixed\n  * **Fixed** Prometheus counters now wrap properly for values greater than\n    2^53 (thanks, @lucab!)\n\n## edge-18.11.3\n\n* CLI\n  * **New** `linkerd routes` command displays per-route stats for services\n    with service profiles\n  * **Experimental** Add `--ha` flag to `linkerd install` command, for HA\n    deployment of the control plane (thanks @benjdlambert!)\n* Web UI\n  * **Experimental** Top Routes page, served at `/routes`\n* Controller\n  * **Fixed** Fix auto injection issue on Kubernetes `v1.9.11` that would\n    merge, rather than append, the proxy container into the application\n* Proxy\n  * **Improved** Add controller client metrics, scoped under `control_`\n  * **Improved** Canonicalize outbound names via DNS for inbound profiles\n\n## edge-18.11.2\n\n* CLI\n  * **Improved** Update stat command to accept multiple stat targets\n  * **Fixed** Fix authority stat filtering when the `--from` flag is present\n  * Various improvements to check command, including:\n    * Emit warnings instead of errors when not running the latest version\n    * Add retries if control plane health check fails initially\n    * Run all pre-install RBAC checks, instead of stopping at first failure\n* Proxy / Proxy-Init\n  * **Fixed** Fix routing issue when a pod makes a request to itself (#1585)\n  * Only include `classification` label on `response_total` metric\n\n## edge-18.11.1\n\n* Proxy\n  * **Fixed** Remove panic when failing to get remote address\n  * **Improved** Better logging in TCP connect error messages\n* Web UI\n  * **Improved** Fixed a smattering of small UI issues\n\n## edge-18.10.4\n\nThis release includes a major redesign of the web frontend to make use of the\nMaterial design system. Additional features that leverage the new design are\ncoming soon! This release also includes the following changes:\n\n* CLI\n  * **Fixed** Fixed an issue with the `--registry` install flag not accepting\n    hosts with ports (thanks, @alenkacz!)\n* Web UI\n  * **New** Added a new Grafana dashboard for authorities (thanks, @alpeb!)\n  * **New** Revamped look and feel of the Linkerd dashboard by switching\n    component libraries from antd to material-ui\n\n## edge-18.10.3\n\n* CLI\n  * **New** Added an `--output` stat flag, for printing stats as JSON\n  * **Improved** Updated the `top` table to set column widths dynamically\n  * **Experimental** Added a `--single-namespace` install flag for installing\n    the control plane with Role permissions instead of ClusterRole permissions\n* Controller\n  * Fixed a few issues with auto injection via the proxy-injector webhook:\n    * Injected pods now execute the linkerd-init container last, to avoid\n      rerouting requests during pod init\n    * Original pod labels and annotations are preserved when auto-injecting\n* Web UI\n  * **New** Added a Help section in the sidebar containing useful links\n\n## edge-18.10.2\n\nThis release brings major improvements to the CLI as described below,\nincluding support for auto-injecting deployments via a Kubernetes Admission\nController. Proxy auto-injection is **experimental**, and the implementation\nmay change going forward.\n\n* CLI\n  * **New** Added a `--proxy-auto-inject` flag to the `install` command,\n    allowing for auto-injection of sidecar containers (Thanks @ihcsim!)\n  * **Improved** Added `--proxy-cpu` and `--proxy-memory` flags to the\n    `install` and `inject` commands, giving the ability to configure CPU +\n    Memory requests (Thanks @benjdlambert!)\n  * **Improved** Added a `--context` flag to specify the context to use to\n    talk to the Kubernetes apiserver (Thanks @ffd2subroutine!)\n\n## edge-18.10.1\n\n* Web UI\n  * **Improved** Tap and Top pages\n    * Added clear button to query form\n  * **Improved** Resource Detail pages\n    * Limit number of resources shown in the graph\n* Controller\n  * CLI health check now uses unified endpoint for data plane checks\n  * Include Licence files in all Docker images\n\nSpecial thanks to @alenkacz for contributing to this release!\n\n## edge-18.9.3\n\n* Web UI\n  * **Improved** Resource Detail page\n    * Better rendering of the dependency graph at the top of the page\n    * Unmeshed sources are now populated in the Inbound traffic table\n    * Sources and destinations are aligned in the popover\n  * **Improved** Tap and Top pages\n    * Additional validation and polish for the form controls\n    * The top table clears older results when a new top call is started\n    * The top table now aggregates by HTTP method as well\n* CLI\n  * **New** The namespace in which Linkerd is installed is configurable via\n    the `LINKERD_NAMESPACE` env var, in addition to the `--linkerd-namespace`\n    flag\n  * **New** The wait time for the `check` and `dashboard` commands is\n    configurable via the `--wait` flag\n  * **Improved** The `top` command now aggregates by HTTP method as well\n\nSpecial thanks to @rochacon, @fahrradflucht and @alenkacz for contributing to\nthis release!\n\n## stable-2.0.0\n\n## edge-18.9.2\n\n* **New** _edge_ and _stable_ release channels\n* Web UI\n  * **Improved** Tap & Top UIs with better layout and linking\n* CLI\n  * **Improved** `check --pre` command verifies the caller has sufficient\n    permissions to install Linkerd\n  * **Improved** `check` command verifies that Prometheus has data for proxied\n    pods\n* Proxy\n  * **Fix** `hyper` crate dependency corrects HTTP/1.0 Keep-Alive behavior\n\n## v18.9.1\n\n* Web UI\n  * **New** Default landing page provides namespace overview with expandable\n    sections\n  * **New** Breadcrumb navigation at the top of the dashboard\n  * **Improved** Tap and Top pages\n    * Table rendering performance improvements via throttling\n    * Tables now link to resource detail pages\n    * Tap an entire namespace when no resource is specified\n    * Tap websocket errors provide more descriptive text\n    * Consolidated source and destination columns\n  * Misc ui updates\n    * Metrics tables now include a small success rate chart\n    * Improved latency formatting for seconds latencies\n    * Renamed upstream/downstream to inbound/outbound\n    * Sidebar scrolls independently from main panel, scrollbars hidden when\n      not needed\n    * Removed social links from sidebar\n* CLI\n  * **New** `linkerd check` now validates Linkerd proxy versions and readiness\n  * **New** `linkerd inject` now provides an injection status report, and\n    warns when resources are not injectable\n  * **New** `linkerd top` now has a `--hide-sources` flag, to hide the source\n    column and collapse top results accordingly\n* Control Plane\n  * Updated Prometheus to v2.4.0, Grafana to 5.2.4\n\n## v18.8.4\n\n* Web UI\n  * **Improved** Tap and Top now have a better sampling rate\n  * **Fixed** Missing sidebar headings now appear\n\n## v18.8.3\n\n* Web UI\n  * **Improved** Kubernetes resource navigation in the sidebar\n  * **Improved** resource detail pages:\n    * **New** live request view\n    * **New** success rate graphs\n* CLI\n  * `tap` and `top` have been improved to sample up to 100 RPS\n* Control plane\n  * Injected proxy containers now have readiness and liveness probes enabled\n\nSpecial thanks to @sourishkrout for contributing a web readability fix!\n\n## v18.8.2\n\n* CLI\n  * **New** `linkerd top` command has been added, displays live traffic stats\n  * `linkerd check` has been updated with additional checks, now supports a\n    `--pre` flag for running pre-install checks\n  * `linkerd check` and `linkerd dashboard` now support a `--wait` flag that\n    tells the CLI to wait for the control plane to become ready\n  * `linkerd tap` now supports a `--output` flag to display output in a wide\n    format that includes src and dst resources and namespaces\n  * `linkerd stat` includes additional validation for command line inputs\n  * All commands that talk to the Linkerd API now show better error messages\n    when the control plane is unavailable\n* Web UI\n  * **New** individual resources can now be viewed on a resource detail page,\n    which includes stats for the resource itself and its nearest neighbors\n  * **Experimental** web-based Top interface accessible at `/top`, aggregates\n    tap data in real time to display live traffic stats\n  * The `/tap` page has multiple improvements, including displaying additional\n    src/dst metadata, improved form controls, and better latency formatting\n  * All resource tables have been updated to display meshed pod counts, as\n    well as an icon linking to the resource's Grafana dashboard if it is\n    meshed\n  * The UI now shows more useful information when server errors are\n    encountered\n* Proxy\n  * The `h2` crate fixed a HTTP/2 window management bug\n  * The `rustls` crate fixed a bug that could improperly fail TLS streams\n* Control Plane\n  * The tap server now hydrates metadata for both sources and destinations\n\n## v18.8.1\n\n* Web UI\n  * **New** Tap UI makes it possible to query & inspect requests from the\n    browser!\n* Proxy\n  * **New** Automatic, transparent HTTP/2 multiplexing of HTTP/1 traffic\n    reduces the cost of short-lived HTTP/1 connections\n* Control Plane\n  * **Improved** `linkerd inject` now supports injecting all resources in a\n    folder\n  * **Fixed** `linkerd tap` no longer crashes when there are many pods\n  * **New** Prometheus now only scrapes proxies belonging to its own linkerd\n    install\n  * **Fixed** Prometheus metrics collection for clusters with >100 pods\n\nSpecial thanks to @ihcsim for contributing the `inject` improvement!\n\n## v18.7.3\n\nLinkerd2 v18.7.3 completes the rebranding from Conduit to Linkerd2, and\nimproves overall performance and stability.\n\n* Proxy\n  * **Improved** CPU utilization by ~20%\n* Web UI\n  * **Experimental** `/tap` page now supports additional filters\n* Control Plane\n  * Updated all k8s.io dependencies to 1.11.1\n\n## v18.7.2\n\nLinkerd2 v18.7.2 introduces new stability features as we work toward\nproduction readiness.\n\n* Control Plane\n  * **Breaking change** Injected pod labels have been renamed to be more\n    consistent with Kubernetes; previously injected pods must be re-injected\n    with new version of linkerd CLI in order to work with updated control\n    plane\n  * The \"ca-bundle-distributor\" deployment has been renamed to \"ca\"\n* Proxy\n  * **Fixed** HTTP/1.1 connections were not properly reused, leading to\n    elevated latencies and CPU load\n  * **Fixed** The `process_cpu_seconds_total` was calculated incorrectly\n* Web UI\n  * **New** per-namespace application topology graph\n  * **Experimental** web-based Tap interface accessible at  `/tap`\n  * Updated favicon to the Linkerd logo\n\n## v18.7.1\n\nLinkerd2 v18.7.1 is the first release of the Linkerd2 project, which was\nformerly hosted at github.com/runconduit/conduit.\n\n* Packaging\n  * Introduce new date-based versioning scheme, `vYY.M.n`\n  * Move all Docker images to `gcr.io/linkerd-io` repo\n* User Interface\n  * Update branding to reference Linkerd throughout\n  * The CLI is now called `linkerd`\n* Production Readiness\n  * Fix issue with destination service sending back incomplete pod metadata\n  * Fix high CPU usage during proxy shutdown\n  * ClusterRoles are now unique per Linkerd install, allowing multiple\n    instances to be installed in the same Kubernetes cluster\n\n## v0.5.0\n\nConduit v0.5.0 introduces a new, experimental feature that automatically\nenables Transport Layer Security between Conduit proxies to secure application\ntraffic. It also adds support for HTTP protocol upgrades, so applications that\nuse WebSockets can now benefit from Conduit.\n\n* Security\n  * **New** `conduit install --tls=optional` enables automatic, opportunistic\n    TLS. See [the docs][auto-tls] for more info.\n* Production Readiness\n  * The proxy now transparently supports HTTP protocol upgrades to support,\n    for instance, WebSockets.\n  * The proxy now seamlessly forwards HTTP `CONNECT` streams.\n  * Controller services are now configured with liveness and readiness probes.\n* User Interface\n  * `conduit stat` now supports a virtual `authority` resource that aggregates\n    traffic by the `:authority` (or `Host`) header of an HTTP request.\n  * `dashboard`, `stat`, and `tap` have been updated to describe TLS state for\n    traffic.\n  * `conduit tap` now has more detailed information, including the direction\n    of each message (outbound or inbound).\n  * `conduit stat` now more-accurately records histograms for low-latency\n    services.\n  * `conduit dashboard` now includes error messages when a Conduit-enabled pod\n    fails.\n* Internals\n  * Prometheus has been upgraded to v2.3.1.\n  * A potential live-lock has been fixed in HTTP/2 servers.\n  * `conduit tap` could crash due to a null-pointer access. This has been\n    fixed.\n\n[auto-tls]: docs/automatic-tls.md\n\n## v0.4.4\n\nConduit v0.4.4 continues to improve production suitability and sets up\ninternals for the upcoming v0.5.0 release.\n\n* Production Readiness\n  * The destination service has been mostly-rewritten to improve safety and\n    correctness, especially during controller initialization.\n  * Readiness and Liveness checks have been added for some controller\n    components.\n  * RBAC settings have been expanded so that Prometheus can access node-level\n    metrics.\n* User Interface\n  * Ad blockers like uBlock prevented the Conduit dashboard from fetching API\n    data. This has been fixed.\n  * The UI now highlights pods that have failed to start a proxy.\n* Internals\n  * Various dependency upgrades, including Rust 1.26.2.\n  * TLS testing continues to bear fruit, precipitating stability improvements\n    to dependencies like Rustls.\n\nSpecial thanks to @alenkacz for improving docker build times!\n\n## v0.4.3\n\nConduit v0.4.3 continues progress towards production readiness. It features a\nnew latency-aware load balancer.\n\n* Production Readiness\n  * The proxy now uses a latency-aware load balancer for outbound requests.\n    This implementation is based on Finagle's Peak-EWMA balancer, which has\n    been proven to significantly reduce tail latencies. This is the same load\n    balancing strategy used by Linkerd.\n* User Interface\n  * `conduit stat` is now slightly more predictable in the way it outputs\n    things, especially for commands like `watch conduit stat all\n    --all-namespaces`.\n  * Failed and completed pods are no longer shown in stat summary results.\n* Internals\n  * The proxy now supports some TLS configuration, though these features\n    remain disabled and undocumented pending further testing and\n    instrumentation.\n\nSpecial thanks to @ihcsim for contributing his first PR to the project and to\n@roanta for discussing the Peak-EWMA load balancing algorithm with us.\n\n## v0.4.2\n\nConduit v0.4.2 is a major step towards production readiness. It features a\nwide array of fixes and improvements for long-running proxies, and several new\ntelemetry features. It also lays the groundwork for upcoming releases that\nintroduce mutual TLS everywhere.\n\n* Production Readiness\n  * The proxy now drops metrics that do not update for 10 minutes, preventing\n    unbounded memory growth for long-running processes.\n  * The proxy now constrains the number of services that a node can route to\n    simultaneously (default: 100). This protects long-running proxies from\n    consuming unbounded resources by tearing down the longest-idle clients\n    when the capacity is reached.\n  * The proxy now properly honors HTTP/2 request cancellation.\n  * The proxy could incorrectly handle requests in the face of some connection\n    errors. This has been fixed.\n  * The proxy now honors DNS TTLs.\n  * `conduit inject` now works with `statefulset` resources.\n* Telemetry\n  * **New** `conduit stat` now supports the `all` Kubernetes resource, which\n    shows traffic stats for all Kubernetes resources in a namespace.\n  * **New** the Conduit web UI has been reorganized to provide namespace\n    overviews.\n  * **Fix** a bug in Tap that prevented the proxy from simultaneously\n    satisfying more than one Tap request.\n  * **Fix** a bug that could prevent stats from being reported for some TCP\n    streams in failure conditions.\n  * The proxy now measures response latency as time-to-first-byte.\n* Internals\n  * The proxy now supports user-friendly time values (e.g. `10s`) from\n    environment configuration.\n  * The control plane now uses client for Kubernetes 1.10.2.\n  * Much richer proxy debug logging, including socket and stream metadata.\n  * The proxy internals have been changed substantially in preparation for TLS\n    support.\n\nSpecial thanks to @carllhw, @kichristensen, & @sfroment for contributing to\nthis release!\n\n### Upgrading from v0.4.1\n\nWhen upgrading from v0.4.1, we suggest that the control plane be upgraded to\nv0.4.2 before injecting application pods to use v0.4.2 proxies.\n\n## v0.4.1\n\nConduit 0.4.1 builds on the telemetry work from 0.4.0, providing rich,\nKubernetes-aware observability and debugging.\n\n* Web UI\n  * **New** Automatically-configured Grafana dashboards for Services, Pods,\n    ReplicationControllers, and Conduit mesh health.\n  * **New** `conduit dashboard` Pod and ReplicationController views.\n* Command-line interface\n  * **Breaking change** `conduit tap` now operates on most Kubernetes\n    resources.\n  * `conduit stat` and `conduit tap` now both support kubectl-style resource\n    strings (`deploy`, `deploy/web`, and `deploy web`), specifically:\n    * `namespaces`\n    * `deployments`\n    * `replicationcontrollers`\n    * `services`\n    * `pods`\n* Telemetry\n  * **New** Tap support for filtering by and exporting destination metadata.\n    Now you can sample requests from A to B, where A and B are any resource or\n    group of resources.\n  * **New** TCP-level stats, including connection counts and durations, and\n    throughput, wired through to Grafana dashboards.\n* Service Discovery\n  * The proxy now uses the [trust-dns] DNS resolver. This fixes a number of\n    DNS correctness issues.\n  * The destination service could sometimes return incorrect, stale, labels\n    for an endpoint. This has been fixed!\n\n[trust-dns]: https://github.com/bluejekyll/trust-dns\n\n## v0.4.0\n\nConduit 0.4.0 overhauls Conduit's telemetry system and improves service\ndiscovery reliability.\n\n* Web UI\n  * **New** automatically-configured Grafana dashboards for all Deployments.\n* Command-line interface\n  * `conduit stat` has been completely rewritten to accept arguments like\n    `kubectl get`. The `--to` and `--from` filters can be used to filter\n    traffic by destination and source, respectively.  `conduit stat` currently\n    can operate on `Namespace` and `Deployment` Kubernetes resources. More\n    resource types will be added in the next release!\n* Proxy (data plane)\n  * **New** Prometheus-formatted metrics are now exposed on `:4191/metrics`,\n    including rich destination labeling for outbound HTTP requests. The proxy\n    no longer pushes metrics to the control plane.\n  * The proxy now handles `SIGINT` or `SIGTERM`, gracefully draining requests\n    until all are complete or `SIGQUIT` is received.\n  * SMTP and MySQL (ports 25 and 3306) are now treated as opaque TCP by\n    default. You should no longer have to specify `--skip-outbound-ports` to\n    communicate with such services.\n  * When the proxy reconnected to the controller, it could continue to send\n    requests to old endpoints. Now, when the proxy reconnects to the\n    controller, it properly removes invalid endpoints.\n  * A bug impacting some HTTP/2 reset scenarios has been fixed.\n* Service Discovery\n  * Previously, the proxy failed to resolve some domain names that could be\n    misinterpreted as a Kubernetes Service name. This has been fixed by\n    extending the _Destination_ API with a negative acknowledgement response.\n* Control Plane\n  * The _Telemetry_ service and associated APIs have been removed.\n* Documentation\n  * Updated [Roadmap](doc/roadmap.md)\n\nSpecial thanks to @ahume, @alenkacz, & @xiaods for contributing to this\nrelease!\n\n### Upgrading from v0.3.1\n\nWhen upgrading from v0.3.1, it's important to upgrade proxies before upgrading\nthe controller. As you upgrade proxies, the controller will lose visibility\ninto some data plane stats. Once all proxies are updated, `conduit install\n|kubectl apply -f -` can be run to upgrade the controller without causing any\ndata plane disruptions. Once the controller has been restarted, traffic stats\nshould become available.\n\n## v0.3.1\n\nConduit 0.3.1 improves Conduit's resilience and transparency.\n\n* Proxy (data plane)\n  * The proxy now makes fewer changes to requests and responses being proxied.\n    In particular, requests and responses without bodies or with empty bodies\n    are better supported.\n  * HTTP/1 requests with different `Host` header fields are no longer sent on\n    the same HTTP/1 connection even when those hostnames resolve to the same\n    IP address.\n  * A connection leak during proxying of non-HTTP TCP connections was fixed.\n  * The proxy now handles unavailable services more gracefully by timing out\n    while waiting for an endpoint to become available for the service.\n* Command-line interface\n  * `$KUBECONFIG` with multiple paths is now supported. (PR #482 by\n    @hypnoglow).\n  * `conduit check` now checks for the availability of a Conduit update. (PR\n    #460 by @ahume).\n* Service Discovery\n  * Kubernetes services with type `ExternalName` are now supported.\n* Control Plane\n  * The proxy is injected into the control plane during installation to\n    improve the control plane's resilience and to \"dogfood\" the proxy.\n  * The control plane is now more resilient regarding networking failures.\n* Documentation\n  * The markdown source for the documentation published at\n    <https://conduit.io/docs/> is now open source at\n    <https://github.com/runconduit/conduit/tree/master/doc.>\n\n## v0.3.0\n\nConduit 0.3 focused heavily on production hardening of Conduit's telemetry\nsystem. Conduit 0.3 should \"just work\" for most apps on Kubernetes 1.8 or 1.9\nwithout configuration, and should support Kubernetes clusters with hundreds of\nservices, thousands of instances, and hundreds of RPS per instance.\n\nWith this release, Conduit also moves from _experimental_ to _alpha_---meaning\nthat we're ready for some serious testing and vetting from you. As part of\nthis, we've published the [Conduit roadmap](https://conduit.io/roadmap/), and\nwe've also launched some new mailing lists:\n[conduit-users](https://groups.google.com/forum/#!forum/conduit-users),\n[conduit-dev](https://groups.google.com/forum/#!forum/conduit-dev), and\n[conduit-announce](https://groups.google.com/forum/#!forum/conduit-announce).\n\n* CLI\n  * CLI commands no longer depend on `kubectl`\n  * `conduit dashboard` now runs on an ephemeral port, removing port 8001\n    conflicts\n  * `conduit inject` now skips pods with `hostNetwork=true`\n  * CLI commands now have friendlier error messages, and support a `--verbose`\n    flag for debugging\n* Web UI\n  * All displayed metrics are now instantaneous snapshots rather than\n    aggregated over 10 minutes\n  * The sidebar can now be collapsed\n  * UX refinements and bug fixes\n* Conduit proxy (data plane)\n  * Proxy does load-aware (P2C + least-loaded) L7 balancing for HTTP\n  * Proxy can now route to external DNS names\n  * Proxy now properly sheds load in some pathological cases when it cannot\n    route\n* Telemetry system\n  * Many optimizations and refinements to support scale goals\n  * Per-path and per-pod metrics have been removed temporarily to improve\n    scalability and stability; they will be reintroduced in Conduit 0.4 (#405)\n* Build improvements\n  * The Conduit docker images are now much smaller.\n  * Dockerfiles have been changed to leverage caching, improving build times\n    substantially\n\nKnown Issues:\n\n* Some DNS lookups to external domains fail (#62, #155, #392)\n* Applications that use WebSockets, HTTP tunneling/proxying, or protocols such\n  as MySQL and SMTP, require additional configuration (#339)\n\n## v0.2.0\n\nThis is a big milestone! With this release, Conduit adds support for HTTP/1.x\nand raw TCP traffic, meaning it should \"just work\" for most applications that\nare running on Kubernetes without additional configuration.\n\n* Data plane\n  * Conduit now transparently proxies all TCP traffic, including HTTP/1.x and\n    HTTP/2. (See caveats below.)\n* Command-line interface\n  * Improved error handling for the `tap` command\n  * `tap` also now works with HTTP/1.x traffic\n* Dashboard\n  * Minor UI appearance tweaks\n  * Deployments now searchable from the dashboard sidebar\n\nCaveats:\n\n* Conduit will automatically work for most protocols. However, applications\n  that use WebSockets, HTTP tunneling/proxying, or protocols such as MySQL and\n  SMTP, will require some additional configuration. See the\n  [documentation](https://conduit.io/adding-your-service/#protocol-support)\n  for details.\n* Conduit doesn't yet support external DNS lookups. These will be addressed in\n  an upcoming release.\n* There are known issues with Conduit's telemetry pipeline that prevent it\n  from scaling beyond a few nodes. These will be addressed in an upcoming\n  release.\n* Conduit is still in alpha! Please help us by [filing issues and contributing\n  pull requests](https://github.com/runconduit/conduit/issues/new).\n\n## v0.1.3\n\n* This is a minor bugfix for some web dashboard UI elements that were not\n  rendering correctly.\n\n## v0.1.2\n\nConduit 0.1.2 continues down the path of increasing usability and improving\ndebugging and introspection of the service mesh itself.\n\n* Conduit CLI\n  * New `conduit check` command reports on the health of your Conduit\n    installation.\n  * New `conduit completion` command provides shell completion.\n* Dashboard\n  * Added per-path metrics to the deployment detail pages.\n  * Added animations to line graphs indicating server activity.\n  * More descriptive CSS variable names. (Thanks @natemurthy!)\n  * A variety of other minor UI bugfixes and improvements\n* Fixes\n  * Fixed Prometheus config when using RBAC. (Thanks @FaKod!)\n  * Fixed `tap` failure when pods do not belong to a deployment. (Thanks\n    @FaKod!)\n\n## v0.1.1\n\nConduit 0.1.1 is focused on making it easier to get started with Conduit.\n\n* Conduit can now be installed on Kubernetes clusters that use RBAC.\n* The `conduit inject` command now supports a `--skip-outbound-ports` flag\n  that directs Conduit to bypass proxying for specific outbound ports, making\n  Conduit easier to use with non-gRPC or HTTP/2 protocols.\n* The `conduit tap` command output has been reformatted to be line-oriented,\n  making it easier to parse with common UNIX command line utilities.\n* Conduit now supports routing of non-fully qualified domain names.\n* The web UI has improved support for large deployments and deployments that\n  don't have any inbound/outbound traffic.\n\n## v0.1.0\n\nConduit 0.1.0 is the first public release of Conduit.\n\n* This release supports services that communicate via gRPC only. non-gRPC\n  HTTP/2 services should work. More complete HTTP support, including HTTP/1.0\n  and HTTP/1.1 and non-gRPC HTTP/2, will be added in an upcoming release.\n* Kubernetes 1.8.0 or later is required.\n* kubectl 1.8.0 or later is required. `conduit dashboard` will not work with\n  earlier versions of kubectl.\n* When deploying to Minikube, Minikube 0.23 or 0.24.1 or later are required.\n  Earlier versions will not work.\n* This release has been tested using Google Kubernetes Engine and Minikube.\n  Upcoming releases will be tested on additional providers too.\n* Configuration settings and protocols are not stable yet.\n* Services written in Go must use grpc-go 1.3 or later to avoid [grpc-go bug\n  #1120](https://github.com/grpc/grpc-go/issues/1120).\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Linkerd Community Code of Conduct\n\nAs a CNCF project, we follow the [CNCF Contributor Code of\nConduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).\nAdditionally, we are committed to the following guidelines adapted from the\n[Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html):\n\n## Community Guidelines\n\nOur goal is to foster an inclusive and diverse community of technology\nenthusiasts.\n\nTry to be your best self. Treat your fellow community members with kindness and\nempathy. We welcome disagreements when they are conducted respectfully and\nwithout personal attacks.\n\nWe ask that you keep unstructured critique to a minimum. Disparaging remarks\nabout the project are unnecessary and a drain on community morale. Feedback\nshould be constructive and relevant. Having passionately held opinions on what\nshould improve is encouraged! We hope you will use that enthusiasm to roll up\nyour sleeves and get involved by submitting pull requests. We have additional\nguidelines on [how to ask constructive\nquestions](https://github.com/linkerd/linkerd/wiki/How-To-Ask-Questions-in-Slack).\n\nWe don't tolerate insults, spamming, trolling, flaming, baiting, or harassment.\nWe don't tolerate sexual language, imagery, or unwanted advances. Private\nharassment is also unacceptable.\n\nWe do our best to avoid\n[subtle-isms](https://www.recurse.com/manual#sub-sec-social-rules): small\nactions that make others feel uncomfortable. If you witness a subtle-ism, you\nmay respectfully point it out to the person publicly or privately, or you may\nask a moderator to say something. Accidentally saying something biased is\ncommon, expected, and readily forgiven. It is not in and of itself a bannable\noffense.\n\n## Moderation\n\nIf you feel that a channel needs moderation, please message one of the\n[`maintainers`](MAINTAINERS.md) directly.\n\nModerators will issue a warning to users who don't follow the code of conduct. A\nsecond offense results in a temporary ban, a third warrants a permanent ban.\nIt's at the moderator's discretion to un-ban a remorseful user, or immediately\nban a toxic user without warning. Unjustified bans can be appealed by contacting\n<banhammer@buoyant.io>.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Linkerd2 #\n\n:balloon: Thanks for your help improving the project!\n\n## Getting Help ##\n\nIf you have a question about Linkerd2 or have encountered problems using it,\nstart by [asking a question in the forums][discourse] or join us in the\n[#linkerd2 Slack channel][slack].\n\n## Developer Certificate of Origin ##\n\nTo contribute to this project, you must agree to the Developer Certificate of\nOrigin (DCO) for each commit you make. The DCO is a simple statement that you,\nas a contributor, have the legal right to make the contribution.\n\nSee the [DCO](DCO) file for the full text of what you must agree to.\n\n### Option 1: commit message signoffs ###\n\nOne way to signify that you agree to the DCO for a commit is to add a line to\nthe git commit message:\n\n```txt\nSigned-off-by: Jane Smith <jane.smith@example.com>\n```\n\nIn most cases, you can add this signoff to your commit automatically with the\n`-s` flag to `git commit`. You must use your real name and a reachable email\naddress (sorry, no pseudonyms or anonymous contributions).\n\n### Option 2: public statement ###\n\nIf you've already made the commits and don't want to engage in git shenanigans\nto retroactively apply the signoff as above, there is another option: leave a\ncomment on the PR with the following statement: \"I agree to the DCO for all the\ncommits in this PR.\"\n\nNote that this option also requires that your commits are made under your real\nname and a reachable email address.\n\nIf you use this approach, the DCO bot will still complain, but maintainers will\noverride the DCO bot at merge time.\n\n### Option 3: very simple changes ###\n\nChanges that are trivial (e.g. spelling corrections, adding to ADOPTERS.md,\none-word changes) do not require a DCO signoff. Maintainers should feel free to\noverride the DCO bot for these changes.\n\n## Submitting a Pull Request ##\n\nDo you have an improvement?\n\n1. Submit an [issue][issue] describing your proposed change.\n2. We will try to respond to your issue promptly.\n3. Fork this repo, develop and test your code changes. See the project's\n   [README](README.md) for further information about working in this repository.\n4. Submit a pull request against this repo's `main` branch.\n    - Include instructions on how to test your changes.\n    - If you are making a change to the user interface (UI), include a\n      screenshot of the UI before and after your changes.\n5. Your branch may be merged once all configured checks pass, including:\n    - The branch has passed tests in CI.\n    - A review from appropriate maintainers (see\n      [MAINTAINERS.md](MAINTAINERS.md) and [GOVERNANCE.md](GOVERNANCE.md))\n\n## Committing ##\n\nWe prefer squash or rebase commits so that all changes from a branch are\ncommitted to main as a single commit. All pull requests are squashed when\nmerged, but rebasing prior to merge gives you better control over the commit\nmessage.\n\n### Commit messages ###\n\nFinalized commit messages should be in the following format:\n\n```txt\nSubject\n\nProblem\n\nSolution\n\nValidation\n\nFixes #[GitHub issue ID]\n```\n\n#### Subject ####\n\n- one line, <= 50 characters\n- describe what is done; not the result\n- use the active voice\n- capitalize first word and proper nouns\n- do not end in a period — this is a title/subject\n- reference the GitHub issue by number\n\n##### Examples #####\n\n```txt\nbad: server disconnects should cause dst client disconnects.\ngood: Propagate disconnects from source to destination\n```\n\n```txt\nbad: support tls servers\ngood: Introduce support for server-side TLS (#347)\n```\n\n#### Problem ####\n\nExplain the context and why you're making that change.  What is the problem\nyou're trying to solve? In some cases there is not a problem and this can be\nthought of as being the motivation for your change.\n\n#### Solution ####\n\nDescribe the modifications you've made.\n\nIf this PR changes a behavior, it is helpful to describe the difference between\nthe old behavior and the new behavior. Provide before and after screenshots,\nexample CLI output, or changed YAML where applicable.\n\nDescribe any implementation changes which are particularly complex or\nunintuitive.\n\nList any follow-up work that will need to be done in a future PR and link to any\nrelevant GitHub issues.\n\n#### Validation ####\n\nDescribe the testing you've done to validate your change.  Give instructions for\nreviewers to replicate your tests.  Performance-related changes should include\nbefore- and after- benchmark results.\n\n[discourse]: https://discourse.linkerd.io/c/linkerd2\n[issue]: https://github.com/linkerd/linkerd2/issues/new\n[slack]: http://slack.linkerd.io/\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nresolver = \"2\"\nmembers = [\n    \"policy-controller\",\n    \"policy-controller/core\",\n    \"policy-controller/grpc\",\n    \"policy-controller/k8s/api\",\n    \"policy-controller/k8s/index\",\n    \"policy-controller/k8s/status\",\n    \"policy-controller/runtime\",\n    \"policy-test\",\n]\n\n[profile.release]\nlto = \"thin\"\n\n[workspace.dependencies]\ngateway-api = \"0.16\"\nhttp = \"1\"\nhyper = \"1\"\nk8s-openapi = { version = \"0.25\", features = [\"v1_33\"] }\nkube = { version = \"1.1\", default-features = false }\nkubert = { version = \"0.25\", default-features = false }\nprometheus-client = { version = \"0.23\", default-features = false }\ntonic = { version = \"0.14\", default-features = false }\ntower = { version = \"0.5\", default-features = false }\n\nlinkerd-policy-controller = { path = \"./policy-controller\" }\nlinkerd-policy-controller-core = { path = \"./policy-controller/core\" }\nlinkerd-policy-controller-grpc = { path = \"./policy-controller/grpc\" }\nlinkerd-policy-controller-k8s-api = { path = \"./policy-controller/k8s/api\" }\nlinkerd-policy-test = { path = \"./policy-test\" }\n\n[workspace.dependencies.hyper-util]\nversion = \"0.1\"\ndefault-features = false\nfeatures = [\"tracing\"]\n\n[workspace.dependencies.linkerd-policy-controller-k8s-index]\npath = \"./policy-controller/k8s/index\"\n\n[workspace.dependencies.linkerd-policy-controller-k8s-status]\npath = \"./policy-controller/k8s/status\"\n\n[workspace.dependencies.linkerd-policy-controller-runtime]\npath = \"./policy-controller/runtime\"\ndefault-features = false\n\n[workspace.dependencies.linkerd2-proxy-api]\nversion = \"0.18.0\"\nfeatures = [\"inbound\", \"outbound\"]\n"
  },
  {
    "path": "DCO",
    "content": "Developer Certificate of Origin\nVersion 1.1\n\nCopyright (C) 2004, 2006 The Linux Foundation and its contributors.\n1 Letterman Drive\nSuite D4700\nSan Francisco, CA, 94129\n\nEveryone is permitted to copy and distribute verbatim copies of this\nlicense document, but changing it is not allowed.\n\n\nDeveloper's Certificate of Origin 1.1\n\nBy making a contribution to this project, I certify that:\n\n(a) The contribution was created in whole or in part by me and I\n    have the right to submit it under the open source license\n    indicated in the file; or\n\n(b) The contribution is based upon previous work that, to the best\n    of my knowledge, is covered under an appropriate open source\n    license and I have the right under that license to submit that\n    work with modifications, whether created in whole or in part\n    by me, under the same open source license (unless I am\n    permitted to submit under a different license), as indicated\n    in the file; or\n\n(c) The contribution was provided directly to me by some other\n    person who certified (a), (b) or (c) and I have not modified\n    it.\n\n(d) I understand and agree that this project and the contribution\n    are public and that a record of the contribution (including all\n    personal information I submit with it, including my sign-off) is\n    maintained indefinitely and may be redistributed consistent with\n    this project or the open source license(s) involved.\n"
  },
  {
    "path": "Dockerfile-debug",
    "content": "FROM debian:bookworm-slim\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    curl \\\n    dnsutils \\\n    iptables \\\n    jq \\\n    nghttp2 \\\n    tcpdump \\\n    iproute2 \\\n    lsof \\\n    conntrack \\\n    tshark && \\\n    rm -rf /var/lib/apt/lists/*\n\n# We still rely on old iptables-legacy syntax.\nRUN update-alternatives --set iptables /usr/sbin/iptables-legacy \\\n    && update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy\n\nENTRYPOINT [ \"tshark\", \"-i\", \"any\" ]\n"
  },
  {
    "path": "Dockerfile.controller",
    "content": "# Precompile key slow-to-build dependencies\nFROM --platform=$BUILDPLATFORM golang:1.25-alpine AS go-deps\nWORKDIR /linkerd-build\nCOPY go.mod go.sum ./\nCOPY bin/install-deps bin/\nRUN go mod download\nARG TARGETARCH\nRUN ./bin/install-deps $TARGETARCH\n\n## compile controller service\nFROM go-deps AS golang\nWORKDIR /linkerd-build\nCOPY controller/gen controller/gen\nCOPY pkg pkg\nCOPY charts charts\nCOPY controller controller\nCOPY charts/patch charts/patch\nCOPY charts/partials charts/partials\nCOPY multicluster multicluster\n\nARG TARGETARCH\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -o /out/controller -tags prod -mod=readonly -ldflags \"-s -w\" ./controller/cmd\n\nFROM --platform=$BUILDPLATFORM ghcr.io/linkerd/dev:v48-rust-musl AS policy\nARG BUILD_TYPE=\"release\"\nWORKDIR /build\nRUN mkdir -p target/bin\nCOPY Cargo.toml Cargo.lock .\nCOPY policy-controller policy-controller\nRUN cargo new policy-test --lib\nENV CARGO=\"cargo auditable\"\nRUN --mount=type=cache,target=/usr/local/cargo/registry \\\n    just-cargo fetch\nARG TARGETARCH\n# Install cross compiler toolchains\nRUN case \"$TARGETARCH\" in \\\n        amd64) true ;; \\\n        arm64) apt-get -y update && apt-get install --no-install-recommends -y binutils-aarch64-linux-gnu ;; \\\n    esac && rm -rf /var/lib/apt/lists/*\n# Enable tokio runtime metrics\nENV RUSTFLAGS=\"--cfg tokio_unstable\"\nRUN --mount=type=cache,target=target \\\n    --mount=type=cache,target=/usr/local/cargo/registry \\\n    target=$(case \"$TARGETARCH\" in \\\n        amd64) echo x86_64-unknown-linux-musl ;; \\\n        arm64) echo aarch64-unknown-linux-musl ;; \\\n        *) echo \"unsupported architecture: $TARGETARCH\" >&2; exit 1 ;; \\\n    esac) && \\\n    just-cargo profile=$BUILD_TYPE target=$target build --package=linkerd-policy-controller && \\\n    mkdir /out && mv \"target/$target/$BUILD_TYPE/linkerd-policy-controller\" /out/\n\n## package runtime\nFROM scratch\nLABEL org.opencontainers.image.source=https://github.com/linkerd/linkerd2\nCOPY LICENSE /linkerd/LICENSE\nCOPY --from=golang /out/controller /controller\nCOPY --from=policy /out/linkerd-policy-controller /\n# for heartbeat (https://versioncheck.linkerd.io/version.json)\nCOPY --from=golang /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/\n\nARG LINKERD_VERSION\nENV LINKERD_CONTAINER_VERSION_OVERRIDE=${LINKERD_VERSION}\nENTRYPOINT [\"/controller\"]\n"
  },
  {
    "path": "Dockerfile.proxy",
    "content": "ARG BUILDPLATFORM=linux/amd64\nARG RUNTIME_IMAGE=\"cr.l5d.io/linkerd/proxy-runtime:latest\"\nARG TARGETARCH\n\n# Precompile key slow-to-build dependencies\nFROM --platform=$BUILDPLATFORM golang:1.25-alpine AS go-deps\nWORKDIR /linkerd-build\nCOPY go.mod go.sum ./\nCOPY bin/install-deps bin/\nRUN go mod download\nARG TARGETARCH\nRUN ./bin/install-deps $TARGETARCH\n\nFROM --platform=$BUILDPLATFORM debian:bookworm-slim AS fetch\nRUN DEBIAN_FRONTEND=noninteractive apt-get update && \\\n    DEBIAN_FRONTEND=noninteractive apt-get install -y curl jq && \\\n    rm -rf /var/lib/apt/lists/*\nWORKDIR /build\nCOPY bin/fetch-proxy bin/fetch-proxy\nCOPY bin/scurl bin/scurl\nARG TARGETARCH\nARG LINKERD2_PROXY_REPO=\"linkerd/linkerd2-proxy\"\nARG LINKERD2_PROXY_VERSION=\"\"\nRUN --mount=type=secret,id=github \\\n    export GITHUB_TOKEN_FILE=/run/secrets/github; \\\n    proxy=$(bin/fetch-proxy \"$LINKERD2_PROXY_VERSION\" \"$TARGETARCH\"); \\\n    mv \"$proxy\" linkerd2-proxy\nRUN echo \"$LINKERD2_PROXY_VERSION\" > proxy-version\nARG LINKERD_AWAIT_VERSION=v0.3.2\nRUN bin/scurl -o linkerd-await https://github.com/linkerd/linkerd-await/releases/download/release%2F${LINKERD_AWAIT_VERSION}/linkerd-await-${LINKERD_AWAIT_VERSION}-${TARGETARCH} && chmod +x linkerd-await\nARG LINKERD_VALIDATOR_VERSION=v0.1.7\nRUN bin/scurl -O https://github.com/linkerd/linkerd2-proxy-init/releases/download/validator%2F${LINKERD_VALIDATOR_VERSION}/linkerd-network-validator-${LINKERD_VALIDATOR_VERSION}-${TARGETARCH}-linux.tgz\nRUN tar -zxvf linkerd-network-validator-${LINKERD_VALIDATOR_VERSION}-${TARGETARCH}-linux.tgz && mv linkerd-network-validator-${LINKERD_VALIDATOR_VERSION}-${TARGETARCH}-linux/linkerd-network-validator .\n\n## compile proxy-identity agent\nFROM go-deps AS golang\nWORKDIR /linkerd-build\nCOPY pkg/util pkg/util\nCOPY pkg/flags pkg/flags\nCOPY pkg/tls pkg/tls\nCOPY pkg/version pkg/version\nARG TARGETARCH\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -mod=readonly ./pkg/...\nCOPY proxy-identity proxy-identity\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -o /out/proxy-identity -mod=readonly -ldflags \"-s -w\" ./proxy-identity\n\n## build proxy-init\nFROM --platform=$BUILDPLATFORM golang:1.25.8-alpine AS proxy-init\nWORKDIR /build\nARG PROXY_INIT_REPO=\"linkerd/linkerd2-proxy-init\"\nARG PROXY_INIT_REF=\"proxy-init/v2.4.6\"\nRUN apk add --no-cache ca-certificates git\nRUN --mount=type=secret,id=github \\\n    export GITHUB_TOKEN_FILE=/run/secrets/github; \\\n    git init --initial-branch=main . && \\\n    git remote add origin https://github.com/${PROXY_INIT_REPO}.git && \\\n    git fetch --depth 1 origin ${PROXY_INIT_REF} && \\\n    git checkout --detach FETCH_HEAD\nRUN go mod download\nARG TARGETARCH\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH GO111MODULE=on \\\n    go build -o /out/linkerd2-proxy-init -mod=readonly -ldflags \"-s -w\" -v ./proxy-init\n\nFROM $RUNTIME_IMAGE-$TARGETARCH AS runtime\nLABEL org.opencontainers.image.source=https://github.com/linkerd/linkerd2\n\nCOPY --from=proxy-init /out/linkerd2-proxy-init /usr/lib/linkerd/linkerd2-proxy-init\n# Set sys caps for iptables utilities and proxy-init\nUSER root\nRUN [\"/usr/sbin/setcap\", \"cap_net_raw,cap_net_admin+eip\", \"/usr/sbin/xtables-legacy-multi\"]\nRUN [\"/usr/sbin/setcap\", \"cap_net_raw,cap_net_admin+eip\", \"/usr/sbin/xtables-nft-multi\"]\nRUN [\"/usr/sbin/setcap\", \"cap_net_raw,cap_net_admin+eip\", \"/usr/lib/linkerd/linkerd2-proxy-init\"]\nUSER 65534\n\nCOPY --from=fetch /build/target/proxy/LICENSE /usr/lib/linkerd/LICENSE\nCOPY --from=fetch /build/proxy-version /usr/lib/linkerd/linkerd2-proxy-version.txt\nCOPY --from=fetch /build/linkerd2-proxy /usr/lib/linkerd/linkerd2-proxy\nCOPY --from=fetch /build/linkerd-await /usr/lib/linkerd/linkerd-await\nCOPY --from=fetch /build/linkerd-network-validator /usr/lib/linkerd/linkerd2-network-validator\nCOPY --from=golang /out/proxy-identity /usr/lib/linkerd/linkerd2-proxy-identity\nCOPY --from=debian:bookworm-slim /bin/sleep /bin/sleep\nARG LINKERD_VERSION\nENV LINKERD_CONTAINER_VERSION_OVERRIDE=${LINKERD_VERSION}\nENV LINKERD2_PROXY_LOG=warn,linkerd=info\nENV LINKERD2_PROXY_LOG_FORMAT=plain\nENTRYPOINT [\"/usr/lib/linkerd/linkerd2-proxy-identity\"]\n"
  },
  {
    "path": "EXTENSIONS.md",
    "content": "# Linkerd Extensions\n\nLinkerd has an extension model which allows 3rd parties to add functionality.\nEach extension consists of a binary CLI named `linkerd-name` (where `name` is\nthe name of the extension) as well as a set of Kubernetes resources.  To invoke\nan extension, users can run `linkerd name` which will search the current PATH\nfor an executable named `linkerd-name` and execute it.\n\n## Installing Extensions\n\nTo install an extension, first download the extension executable and put it\nin your PATH. The extension can then be installed into your Kubernetes cluster\nby running `linkerd <extension name> install | kubectl apply -f -`. Similarly,\nit can be uninstalled by running\n`linkerd <extension name> uninstall | kubectl delete -f -`.\n\nA full list of installed extensions can be printed by running `linkerd check`.\n\n## Developing Extensions\n\nThe extension must be an executable file named `linkerd-name` where `name` is\nthe name of the extension. The name must not be any of the built-in Linkerd\ncommands (e.g. `check`) or extensions (e.g. `viz`).\n\nThe extension must accept the following flags and respect them any time that\nit communicates with the Kubernetes API.  All of these flags must be accepted\nbut may be ignored if they are not applicable.\n\n* `--api-addr`: Override kubeconfig and communicate directly with the control\n  plane at host:port (mostly for testing)\n* `--context`: Name of the kubeconfig context to use\n* `--as`: Username to impersonate for Kubernetes operations\n* `--as-group`: Group to impersonate for Kubernetes operations\n* `--help`/`-h`: Print help message\n* `--kubeconfig`: Path to the kubeconfig file to use for CLI requests\n* `--linkerd-namespace`/`-L`: Namespace in which Linkerd is installed\n  [$LINKERD_NAMESPACE]\n* `--verbose`: Turn on debug logging\n\nThe extension must implement these commands:\n\n### `linkerd-name install`\n\nThis command must print the Kubernetes manifests for the extension as yaml\nwhich is suitable to be passed to `kubectl apply -f`. These manifests must\ninclude a Namespace resource with the label `linkerd.io/extension=name` where\n`name` is the name of the extension. This allows Linkerd to detect installed\nextensions.\n\n### `linkerd-name uninstall`\n\nThis command must print manifests for all cluster-scoped Kubernetes resources\nbelonging to this extension (including the extension Namespace) as yaml which\nis suitable to be passed to `kubectl delete -f`.\n\n### `linkerd-name check`\n\nThis command must perform any appropriate health checks for the extension\nincluding but not limited to checking that the extension resources exist and\nare in a healthy state. This command must exit with a status code of 0 if the\nchecks pass or with a nonzero status code if they do not pass. For consistency,\nit is recommended that this command follows the same output formatting as\n`linkerd check`, e.g.\n\n```console\nlinkerd-version\n---------------\n√ can determine the latest version\n√ cli is up-to-date\n\nStatus check results are √\n```\n\nThe final line of output should be either `Status check results are √` or\n`Status check results are ×`.\n\nIn addition to the flags described above, `linkerd-name check` must accept the\nfollowing flags:\n\n* `--namespace`/`-n`: Namespace to use for –proxy checks (default: all\n  namespaces)\n* `--output`/`-o`: Output format. One of: table, json, short\n* `--pre`: Only run pre-installation checks, to determine if the extension can\n  be installed\n* `--proxy`: Only run data-plane checks, to determine if the data plane is\n  healthy\n* `--wait`: Maximum allowed time for all tests to pass\n\nIf the output format is set to json then output must be in json format\ninstead of the output format described above.  E.g.\n\n```json\n{\n  \"success\": false,\n  \"categories\": [\n    {\n      \"categoryName\": \"kubernetes-api\",\n      \"checks\": [\n        {\n          \"description\": \"can initialize the client\",\n          \"result\": \"success\"\n        },\n        {\n          \"description\": \"can query the Kubernetes API\",\n          \"result\": \"success\"\n        },\n        {\n          \"description\": \"linkerd-viz Namespace exists\",\n          \"hint\": \"https://linkerd.io/2/checks/#l5d-viz-ns-exists\",\n          \"error\": \"could not find the linkerd-viz extension\",\n          \"result\": \"error\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nIn particular, the `linkerd check` command will invoke the check command for\neach extension installed in the cluster and will request json output.\n`linkerd check` may optinally invoke your extension if not installed in the\ncluster (see `linkerd-name _extension-metadata` below to opt-in).  To preserve\nforwards compatibility, it is recommended that the check command should ignore\nany unknown flags.\n\n### `linkerd-name _extension-metadata`\n\nThis subcommand is optional, and enables an extension to opt-in to being\nexecuted as part of `linkerd check`, even when there is no corresponding\nextension on the cluster. To opt-in, the output of\n`linkerd-name _extension-metadata` should be json of the form:\n\n```json\n{\n  \"name\": \"linkerd-name\",\n  \"checks\": \"always\",\n}\n```\n\nNote that for `linkerd check` to validate which extensions are opting-in, it\nruns `linkerd-* _extension-metadata` against every executable in the PATH.\n\nThe extension may also implement further commands in addition to the ones\ndefined here.\n"
  },
  {
    "path": "GOVERNANCE.md",
    "content": "# Linkerd Governance\n\nThis document defines project governance for Linkerd.\n\n> The Linkerd maintainers are 100% committed to open governance and to being\n> hosted by a neutral foundation. We believe that a diverse and active set of\n> maintainers is fundamental to the long-term health of an open source project.\n> And we want YOU to join us.\n\n&mdash; [Linkerd's Commitment to Open\nGovernance](https://linkerd.io/2019/10/03/linkerds-commitment-to-open-governance/)\n\n## Contributors\n\nLinkerd is for everyone. Anyone can become a Linkerd contributor simply by\ncontributing to the project, whether through code, documentation, blog posts,\ncommunity management, or other means. As with all Linkerd community members,\ncontributors are expected to follow the [Linkerd Code of\nConduct][coc].\n\nAll contributions to Linkerd code, documentation, or other components in the\nLinkerd GitHub org must follow the guidelines in [CONTRIBUTING.md][contrib].\nWhether these contributions are merged into the project is the prerogative of\nthe maintainers.\n\n## Directors\n\nDirectors are responsible for non-technical leadership functions within the\nproject. This includes representing Linkerd and its maintainers to the\ncommunity, to press, and to the outside world; interfacing with CNCF and other\ngovernance entities; and participating in project decision-making processes when\nappropriate.\n\nDirectors may be elected by a majority vote of the maintainers.\n\n## Maintainers\n\nMaintainers have the ability to merge code into the project. Anyone can\nbecome a Linkerd maintainer (see \"Becoming a maintainer\" below.)\n\n### Expectations\n\nLinkerd maintainers are expected to:\n\n* Review pull requests, triage issues, and fix bugs in their areas of\n  expertise, ensuring that all changes go through the project's code review\n  and integration processes.\n* Monitor cncf-linkerd-* emails and the Linkerd Slack, and help out when\n  possible.\n* Rapidly respond to any time-sensitive security release processes.\n* Attend meetings with the Linkerd Steering Committee.\n\nIf a maintainer is no longer interested in or cannot perform the duties\nlisted above, they should move themselves to emeritus status. If necessary,\nthis can also occur through the decision-making process outlined below.\n\n### Becoming a maintainer\n\nAnyone can become a Linkerd maintainer. Maintainers should be extremely\nproficient in Go and/or Rust; have relevant domain expertise; have the time\nand ability to meet the maintainer expectations above; and demonstrate the\nability to work with the existing maintainers and project processes.\n\nTo become a maintainer, start by expressing interest to existing maintainers.\nExisting maintainers will then ask you to demonstrate the qualifications\nabove by contributing PRs, doing code reviews, and other such tasks under\ntheir guidance. After several months of working together, maintainers will\ndecide whether to grant maintainer status.\n\n## Project decision-making process\n\nIdeally, all project decisions are resolved by consensus of maintainers and\ndirectors. If this is not possible, a vote will be called. The voting process is\na simple majority in which each maintainer and director receives one vote.\n\n[coc]: https://github.com/linkerd/linkerd/wiki/Linkerd-code-of-conduct\n[contrib]: https://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "MAINTAINERS.md",
    "content": "# Maintainers\n\nThe Linkerd maintainers are:\n\n* Alex Leong <alex@buoyant.io> @adleong\n* Alejandro Pedraza <alejandro@buoyant.io> @alpeb\n* katelyn martin <kate@buoyant.io> @cratelyn\n* Oliver Gould <ver@buoyant.io> @olix0r\n* Scott Fleener <scott@buoyant.io> @sfleen\n* Zahari Dichev <zahari@buoyant.io> @zaharidichev\n\n## Directors\n\nThe Linkerd directors are:\n\n* William Morgan <william@buoyant.io> @wmorgan\n\n## Steering Committee\n\nThe Linkerd Steering Committee members are:\n\n* Steve Gray (ZeroFlucs) @steve-gray\n* Priya Namasivayam (Earnin) @priyanamasivayam\n* Christian Hüning (BWI) @christianhuening\n* Dimple Thoomkuzhy (CompareTheMarket) @dimpledalby07\n\n## Emeriti\n\nFormer maintainers include:\n\n* Eliza Weisman <eliza@buoyant.io> @hawkw\n* Hema Lee <Hemalekha.Lee@nordstrom.com> @hemakl\n* Kevin Leimkuhler @kleimkuhler\n* Kevin Ingleman <ki@buoyant.io> @klingerf\n* Matei David @mateiidavid\n* Tarun Pothulapati <tarun@buoyant.io> @pothulapati\n* Risha Mars <mars@buoyant.io> @rmars\n* Andrew Seigner <siggy@buoyant.io> @siggy\n* Steve Jenson @stevej\n\n<!--\n# Adding a new maintainer\n\n* Submit a PR modifying this file\n* Sort alphabetically by GitHub handle\n* Obtain approvals per GOVERNANCE.md\n* Invite maintainer to\n  https://github.com/orgs/linkerd/teams/maintainers/members\n* Invite maintainer to https://github.com/orgs/linkerd/people\n-->\n"
  },
  {
    "path": "README.md",
    "content": "# Linkerd\n\n![Linkerd][logo]\n\n[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4629/badge)](https://bestpractices.coreinfrastructure.org/projects/4629)\n[![GitHub Actions Status][github-actions-badge]][github-actions]\n[![GitHub license](https://img.shields.io/github/license/linkerd/linkerd2.svg)](LICENSE)\n[![Go Report Card][go-report-card-badge]][go-report-card]\n[![Go Reference][go-doc-badge]][go-doc]\n[![Slack Status][slack-badge]][slack]\n\n:balloon: Welcome to Linkerd! :wave:\n\nLinkerd is an ultralight, security-first service mesh for Kubernetes. Linkerd\nadds critical security, observability, and reliability features to your\nKubernetes stack with no code change required.\n\nLinkerd is a Cloud Native Computing Foundation ([CNCF][cncf]) project.\n\n## Repo layout\n\nThis is the primary repo for the Linkerd 2.x line of development.\n\nThe complete list of Linkerd repos is:\n\n* [linkerd2][linkerd2]: Main Linkerd 2.x repo, including control plane and CLI\n* [linkerd2-proxy][proxy]: Linkerd 2.x data plane proxy\n* [linkerd2-proxy-api][proxy-api]: Linkerd 2.x gRPC API bindings\n* [linkerd][linkerd1]: Linkerd 1.x\n* [website][linkerd-website]: linkerd.io website (including docs for 1.x and\n  2.x)\n\n## Quickstart and documentation\n\nYou can run Linkerd on any modern Kubernetes cluster in a matter of seconds.\nSee the [Linkerd Getting Started Guide][getting-started] for how.\n\nFor more comprehensive documentation, start with the [Linkerd\ndocs][linkerd-docs]. (The doc source code is available in the\n[website][linkerd-website] repo.)\n\n## Working in this repo\n\n[`BUILD.md`](BUILD.md) includes general information on how to work in this repo.\n\nWe :heart: pull requests! See [`CONTRIBUTING.md`](CONTRIBUTING.md) for info on\ncontributing changes.\n\n## Get involved\n\n* Join Linkerd's [user mailing list][linkerd-users], [developer mailing\n  list][linkerd-dev], and [announcements mailing list][linkerd-announce].\n* Follow [@Linkerd][twitter] on Twitter.\n* Join the [Linkerd Slack][slack].\n\n## Steering Committee meetings\n\nWe host regular online meetings for the Linkerd Steering Committee. All are\nwelcome to attend, but audio and video participation is limited to Steering\nCommittee members and maintainers. These meetings are currently scheduled on an\nad-hoc basis and announced on the [linkerd-users][linkerd-users] mailing list.\n\n* [Zoom link](https://zoom.us/my/cncflinkerd)\n* [Minutes from previous meetings](https://docs.google.com/document/d/1GDNM5eosiyjVDo6YHXBMsvlpyzUldgg-XLMNzf7I404/edit)\n* [Recordings from previous meetings](https://www.youtube.com/playlist?list=PLI9FkLPXDscBHP91Ud3lyJScI4ZCjRG6F)\n\n## Code of Conduct\n\nThis project is for everyone. We ask that our users and contributors take a few\nminutes to review our [Code of Conduct][CoC].\n\n## Security\n\nSee [SECURITY.md](SECURITY.md) for our security policy, including how to report\nvulnerabilities.\n\nLinkerd undergoes periodic third-party security audits and we\n[publish the results here](https://github.com/linkerd/linkerd2/tree/main/audits).\n\n## License\n\nCopyright 2025 the Linkerd Authors. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthese files except in compliance with the License. You may obtain a copy of the\nLicense at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n\n<!-- refs -->\n[github-actions]: https://github.com/linkerd/linkerd2/actions\n[github-actions-badge]: https://github.com/linkerd/linkerd2/actions/workflows/actions.yml/badge.svg\n[cncf]: https://www.cncf.io/\n[CoC]: https://github.com/linkerd/linkerd/wiki/Linkerd-code-of-conduct\n[getting-started]: https://linkerd.io/2/getting-started/\n[go-report-card]: https://goreportcard.com/report/github.com/linkerd/linkerd2\n[go-report-card-badge]: https://goreportcard.com/badge/github.com/linkerd/linkerd2\n[go-doc-badge]: https://pkg.go.dev/badge/github.com/linkerd/linkerd2.svg\n[go-doc]: https://pkg.go.dev/github.com/linkerd/linkerd2\n[linkerd1]: https://github.com/linkerd/linkerd\n[linkerd2]: https://github.com/linkerd/linkerd2\n[linkerd-announce]: https://lists.cncf.io/g/cncf-linkerd-announce\n[linkerd-dev]: https://lists.cncf.io/g/cncf-linkerd-dev\n[linkerd-docs]: https://linkerd.io/2/overview/\n[linkerd-users]: https://lists.cncf.io/g/cncf-linkerd-users\n[linkerd-website]: https://github.com/linkerd/website\n[logo]: https://user-images.githubusercontent.com/9226/33582867-3e646e02-d90c-11e7-85a2-2e238737e859.png\n[proxy]: https://github.com/linkerd/linkerd2-proxy\n[proxy-api]: https://github.com/linkerd/linkerd2-proxy-api\n[slack-badge]: http://slack.linkerd.io/badge.svg\n[slack]: http://slack.linkerd.io\n[twitter]: https://twitter.com/linkerd\n"
  },
  {
    "path": "RELEASE.md",
    "content": "# Linkerd2 Release\n\nThis document contains instructions for releasing Linkerd2.\n\n## 1. Bump the proxy version\n\nDetermine the commit SHA or tag of the `linkerd2-proxy` repo to be included in\nthe release.\n\nThe [proxy-version](https://github.com/linkerd/linkerd2/blob/main/.proxy-version)\nfile is kept in sync automatically by the\n[`sync-proxy`](https://github.com/linkerd/linkerd2/actions/workflows/sync-proxy.yml)\nworkflow. If the file is already at the desired SHA or tag, skip to step 2.\n\nIf updating to `linkerd-proxy` HEAD, note the commit SHA at\n[latest.txt](https://build.l5d.io/linkerd2-proxy/latest.txt) (Look for\n`linkerd2-proxy-<linkerd2-proxy-sha>.tar.gz`).\n\n## 2. Bump the proxy-init or CNI plugin version\n\nIf the `linkerd2/proxy-init` or `linkerd2/cni-plugin` projects have a new\nrelease (which is rare), the following updates are needed:\n\n- `pkg/version/version.go` (this also implies changes in unit test fixtures)\n\n   ```go\n   var ProxyInitVersion = \"v2.3.0\"\n   var LinkerdCNIVersion = \"v1.4.0\"\n   ```\n\n- `charts/linkerd-control-plane/values.yaml`\n\n   Upgrade the version in `global.proxyInit.image.version`\n\n- `charts/linkerd2-cni/values.yaml`\n\n   Upgrade the version in `image.version`\n\nCreate a new branch in the `linkerd2` repo,\n`username/proxy-init-version-bump`.\n\nOpen a pull request that includes the changes.\n\n## 3. Tag the release\n\n- Checkout the `main` branch, and be sure to pull the latest changes.\n- Tag the current state of `main`, with a tag of the form `edge-YY.M.N`, where\n  `YY` is the year, `M` is the month, and `N` is the nth release of the month.\n- Push the tag to the repository.\n\n```sh\nTAG=\"edge-YY.M.N\"\ngit switch main\ngit pull --tags --prune\ngit tag $TAG HEAD\ngit push --tags\n```\n\nThat will kick off a CI Release workflow run that will:\n\n- Build and push the docker images for the tag that was created\n- Run the k3d integration tests in the Github actions VMs themselves\n- Run a k3d integration test on a separate ARM64 host\n- Create a release in Github, and upload the CLI binaries with their checksums\n- Dispatch an event caught by the website repo that triggers a website rebuild\n  which will update the edge version in the website\n- Retrieve the installation script from [run.linkerd.io](https://run.linkerd.io)\n  and verify it installs the current version being released\n- Deploy the updated helm charts\n\nYou can locate the CI run on the [actions page](https://github.com/linkerd/linkerd2/actions).\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Linkerd Security Policy\n\nSecurity is critical to Linkerd and we take it very seriously. Not only must\nLinkerd be secure, it must improve the security of the system around it. To this\nend, every aspect of Linkerd's development is done with security in mind.\n\nLinkerd makes use of a variety of tools to ensure software security, including:\n\n* Code review\n* Dependency hygiene and supply chain security via\n  [dependabot](https://docs.github.com/en/code-security/dependabot)\n* [Fuzz testing](https://linkerd.io/2021/05/07/fuzz-testing-for-linkerd/)\n* [Third-party security audits](#security-audits)\n* And other forms of manual, static, and dynamic checking.\n\n## Reporting a Vulnerability\n\nIf you believe you've found a security problem in Linkerd, whether in the\ncontrol plane, proxy, or any other component, please file a [GitHub security\nadvisory on the linkerd2\nrepo](https://github.com/linkerd/linkerd2/security/advisories). The maintainers\nwill diagnose the severity of the issue and determine how to address the issue.\n\n## Criticality Policy\n\nIn general, critical issues that affect Linkerd's security posture or that\nreduce its ability to provide security for users will receive immediate\nattention and be fixed as quickly as possible.\n\nIssues that do not affect Linkerd's security posture and that don't reduce its\nability to provide security for users may not be immediately addressed. For\nexample, CVEs in underlying dependencies that don't actually affect Linkerd may\nnot be immediately addressed.\n\nOnce merged into main, security updates will be available in the next edge\nrelease produced.\n\n## Security Audits\n\nThe CNCF provides periodic third-party security audits. We publish unredacted\nreports in the [audits/](audits/) subdirectory.\n\n## Security Advisories\n\nWhen vulnerabilities in Linkerd itself are discovered and corrected, we will\nissue a security advisory, describing the problem and providing a pointer to the\nfix. These will be announced to our\n[cncf-linkerd-announce](https://lists.cncf.io/g/cncf-linkerd-announce) mailing\nlist.\n\nThere are some situations where we may delay issuing a security advisory. For\nexample, when a vulnerability is found during a code audit or when several\nissues are likely to be spotted and fixed in the near future, the maintainers\nmay delay the release of a Security Advisory so that we can issue a single\ncomprehensive Security Advisory covering multiple vulnerabilities. Communication\nwith vendors and other distributions shipping the same code may also cause these\ndelays.\n"
  },
  {
    "path": "STEERING.md",
    "content": "# Linkerd Steering Committee Charter\n\nThe goal of the Linkerd Steering Committee is to ensure that Linkerd meets the\nneeds of its current and future users. The Steering Committee represents the\nvoice of the user, and works with the Linkerd maintainers and directors to help\nguide development efforts towards solving concrete and immediate problems for\nLinkerd adopters.\n\n## Responsibilities\n\nThe Steering Committee’s responsibilities are to:\n\n1. Provide feedback to project maintainers about Linkerd's featureset, UX, and\n   operational model.\n2. Assist Linkerd maintainers in prioritizing upcoming roadmap items and\n   planned work.\n3. Represent the \"voice of the Linkerd user\" both internally and to the broader\n   community.\n4. Provide neutral mediation for non-technical disputes.\n5. Develop and maintain a project continuity plan.\n\n## Membership\n\nThe Steering Committee comprises at most 7 people. To be eligible for\nmembership in the Steering Committee, you must:\n\n1. Be currently responsible for a production Linkerd deployment of non-trivial\n   size, or have been responsible for such a deployment within the past two\n   years.\n2. Be willing and able to attend regularly-scheduled Steering Committee\n   meetings.\n3. Abide by Linkerd's Code of Conduct.\n\nCandidates for membership will be nominated by current Steering Committee\nmembers or by Linkerd maintainers and directors. Once nominated, inclusion in\nthe Steering Committee will be determined by the normal project decision-making\nprocess.\n\nMembership expires if any of the eligibility conditions is unmet, or after one\nyear. Members may seek reinstatement immediately in accordance with the rules\nabove.\n\n## Meetings\n\nMeetings will happen periodically not less than once a quarter. Recordings and\nminutes will be posted publicly.\n\n## Changes to this charter\n\nChanges to this charter must be approved by a majority of Steering Committee\nmembers and a majority of Linkerd maintainers and directors.\n"
  },
  {
    "path": "TEST.md",
    "content": "# Linkerd2 Test Guide\n\nThis document covers how to run all of the tests that are present in the\nLinkerd2 repo. Most of these tests are run in CI, but you can use the\ninstructions here to run the tests from source. For more information about\nworking in this repo, see the [BUILD.md](BUILD.md) guide.\n\nNote that all shell commands in this guide are expected to be run from the root\nof this repo, unless otherwise indicated by a `cd` command.\n\n## Table of contents\n\n- [Unit tests](#unit-tests)\n  - [Go](#go)\n  - [JavaScript](#javascript)\n  - [Shell](#shell)\n- [Integration tests](#integration-tests)\n  - [Prerequisites](#prerequisites)\n  - [Running tests](#running-tests)\n  - [Writing tests](#writing-tests)\n\n## Unit tests\n\n### Go\n\nTo run tests:\n\n```bash\ngo test -cover -race ./...\n```\n\nTo investigate code coverage:\n\n```bash\ncov=`mktemp`\ngo test -coverprofile=$cov ./...\ngo tool cover -html=$cov\n```\n\n#### Pretty-printed diffs for templated text\n\nWhen running `go test`, mismatched text is usually displayed as a compact diff.\nIf you prefer to see the full text of the mismatch with colorized output, you\ncan set the `LINKERD_TEST_PRETTY_DIFF` environment variable or run `go test\n./cli/cmd/... --pretty-diff`.\n\n#### Updating templates\n\nWhen kubernetes templates change, several test fixtures usually need to be\nupdated (in `cli/cmd/testdata/*.golden`). These golden files can be\nautomatically regenerated with the command:\n\n```sh\ngo test ./cli/cmd/... --update\n```\n\n### JavaScript\n\nJavaScript dependencies are managed via [yarn](https://yarnpkg.com/) and\n[webpack](https://webpack.js.org/). We use\n[jest](https://facebook.github.io/jest) as our test runner.\n\nTo fetch dependencies and run tests, run:\n\n```bash\nbin/web setup\nbin/web test\n\n# or alternatively:\n\ncd web/app\nyarn && NODE_ENV=test yarn webpack\nyarn jest \"$*\"\n```\n\nFor faster testing, run a subset of the tests by passing flags to jest.\n\nRun tests on files that have changed since the last commit:\n\n```bash\nbin/web test -o\n```\n\nRun tests that match a spec name (regex):\n\n```bash\nbin/web test -t name-of-spec\n```\n\nRun watch mode:\n\n```bash\nbin/web test --watch # runs -o by default (tests only files changed since last commit)\nbin/web test --watchAll # runs all tests after a change to a file\n```\n\n### Shell\n\n```bash\nbin/shellcheck -x bin/*\n```\n\n## Integration tests\n\nThe `test/integration` directory contains a test suite that can be run to\nvalidate Linkerd functionality via a series of end-to-end tests.\n\n### Prerequisites\n\n#### Prerequisites for default behavior\n\nThe integration tests will configure their own k3s clusters by default (using\nthe k3d helper). There are no prerequisites for this test path.\n\n#### Prerequisites for existing cluster\n\nIf integration tests should run on an existing Kubernetes cluster, then the\n`--skip-cluster-create` flag should be passed. This will disable the tests from\ncreating their own clusters and instead use the current Kubernetes context.\n\nIn this case, ensure the following:\n\n- The Linkerd docker images you're trying to test have been built and are\n  accessible to the Kubernetes cluster to which you are deploying.\n  If you're testing locally through a KinD or k3d cluster and don't want to push\n  the images to a public registry, you can call `bin/image-load --kind|k3d` to\n  load all the Linkerd images into those clusters.\n- The `kubectl` CLI has been configured to talk to that Kubernetes cluster\n\n### Running tests\n\nYou can use the `bin/tests` script to run one or all of the tests in the test\nsuite.\n\nThe `bin/tests` script requires an absolute path to a `linkerd` binary to test.\n\nOptional flags can be passed that change the testing behavior:\n\n- `--name`: Pass an argument with this flag to specify a specific test that\n  should be run; all tests (except some special ones, see below) are run in the\n  absence of this flag. Valid test names are included in the `bin/tests --help`\n  output\n- `--skip-cluster-create`: Skip KinD cluster creation for each test and use an\n  existing Kubernetes cluster\n- `--images`: (Primarily for CI) Loads images from the `image-archive/`\n  directory into the KinD clusters created for each test\n\nView full help text:\n\n```bash\nbin/tests --help\n```\n\nRun individual test:\n\n```bash\nbin/tests --name upgrade /path/to/linkerd\n```\n\n#### Testing against the installed version of the CLI\n\nYou can run tests using your installed version of the `linkerd` CLI. For\nexample, to run the full suite of tests using your installed CLI, run:\n\n```bash\nbin/tests `which linkerd`\n```\n\nIf using an existing cluster to run tests, the resources can be cleaned up\nmanually with:\n\n```bash\nbin/test-cleanup /path/to/linkerd\n```\n\n#### Testing against a locally-built version of the CLI\n\nYou can also test a locally-built version of the `linkerd` CLI.\n\nFirst build all of the Linkerd images by running:\n\n```bash\nbin/docker-build\n```\n\nThat command also copies the corresponding `linkerd` binaries into the\n`target/cli` directory, and you can use the `bin/linkerd` script to load those\nbinaries when running tests. To run tests using your local binary, run:\n\n```bash\nbin/tests $PWD/bin/linkerd\n```\n\n**Note**: As stated above, if running tests in an existing KinD cluster by\npassing `--skip-cluster-create`, `bin/kind-load` must be run so that the images\nare available to the cluster\n\n#### Special tests: cluster-domain, cni-calico-deep and multicluster\n\nWhen running `bin/tests` without specifying `--name` all tests except for\n`cluster-domain`, `cni-calico-deep` and `multicluster` are run, because these require\ncreating the clusters with special configurations. To run any of these tests,\ninvoke them explicitly with `--name` for the script to create the cluster (using\nk3d) and trigger the test:\n\n- `bin/tests --name cluster-domain`: This simply creates the cluster with a\n  cluster domain setting different than the default `cluster.local`, then\n  installs Linkerd and triggers some smoke tests.\n- `bin/tests --name cni-calico-deep`: This installs a cluster replacing the\n  default CNI plugin (which for k3s is Flannel) with the Calico CNI plugin, then\n  installs the Linkerd CNI plugin and the Linkerd control plane, and finally\n  triggers the full suite of deep tests.\n- `bin/tests --name multicluster`: Two k3d clusters are installed each one with\n  separate instances of Linkerd sharing the same trust root. Then the\n  multicluster component is installed, both clusters are linked together and a\n  test ensures exported services can be reached between the two clusters.\n\n#### Testing the dashboard\n\nIf you're new to the repo, make sure you've installed web dependencies via\n[Yarn](https://yarnpkg.com):\n\n```bash\nbrew install yarn # if you don't already have yarn\nbin/web setup\n```\n\nThen start up the dashboard at `localhost:7777`. You can do that in one of two\nways:\n\n```bash\n# standalone\nbin/web run\n```\n\nOR\n\n```bash\n# with webpack-dev-server\nbin/web dev\n```\n\n## Writing tests\n\nTo add a new test, create a new subdirectory inside the `test/` directory.\nConfiguration files, such as Kubernetes configs, should be placed inside a\n`testdata/` directory inside the test subdirectory that you created. Then create\na test file in the subdirectory that's suffixed with `_test.go`. This test file\nwill be run automatically by the test runner script.\n\nThe tests rely heavily on the test helpers that are defined in the `testutil/`\ndirectory. For a complete description of how to use the test helpers to write\nyour own tests, view the `testutil` package's godoc, with:\n\n```bash\ngodoc github.com/linkerd/linkerd2/testutil | less\n```\n\n## Scale tests\n\nThe scale tests deploy a single Linkerd control-plane, and then scale up\nmultiple sample apps across multiple replicas across multiple namespaces.\n\nPrerequisites:\n\n- a `linkerd` CLI binary\n- Linkerd Docker images associated with the `linkerd` CLI binary\n- a Kubernetes cluster with sufficient resources to run 100s of pods\n\n## Run tests\n\n```bash\nbin/test-scale\nusage: test-scale /path/to/linkerd [namespace]\n```\n\nFor example, to test a newly built Linkerd CLI:\n\n```bash\nbin/test-scale `pwd`/bin/linkerd\n```\n\n## Cleanup\n\n```bash\nbin/test-cleanup /path/to/linkerd\n```\n\n## Test against multiple cloud providers\n\nThe [`bin/test-clouds`](bin/test-clouds) script runs the integration tests\nagainst 4 cloud providers:\n\n- Amazon (EKS)\n- DigitalOcean (DO)\n- Google (GKE)\n- Microsoft (AKS)\n\nThis script assumes you have a working Kubernetes cluster set up on each Cloud\nprovider, and that Kubernetes contexts are configured via environment variables.\n\nFor example:\n\n```bash\nexport AKS=my-aks-cluster\nexport DO=do-nyc1-my-cluster\nexport EKS=arn:aws:eks:us-east-1:123456789012:cluster/my-cluster\nexport GKE=gke_my-project_us-east1-b_my-cluster\n```\n\nFor more information on configuring access to multiple clusters, see:\n<https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/#define-clusters-users-and-contexts>\n\n```bash\nbin/test-clouds `pwd`/bin/linkerd\n```\n\nTo cleanup all integration tests:\n\n```bash\nbin/test-clouds-cleanup\n```\n"
  },
  {
    "path": "bin/_docker.sh",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\ndocker buildx &> /dev/null || { echo 'Please install docker buildx before proceeding'; exit 1; }\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\n\n# shellcheck source=_log.sh\n. \"$bindir\"/_log.sh\n# shellcheck source=_os.sh\n. \"$bindir\"/_os.sh\n\n# TODO this should be set to the canonical public docker registry; we can override this\n# docker registry in, for instance, CI.\nexport DOCKER_REGISTRY=${DOCKER_REGISTRY:-cr.l5d.io/linkerd}\n\n# populated in GitHub Actions\nexport ACTIONS_CACHE_URL=${ACTIONS_CACHE_URL:-}\n\nexport DOCKER_TARGET=${DOCKER_TARGET:-$(os)}\n\n# When set together with DOCKER_TARGET=multi-arch, it will push the multi-arch images to the registry\nexport DOCKER_PUSH=${DOCKER_PUSH:-}\n\nexport DOCKER_BUILDER=${DOCKER_BUILDER:-}\n\n# Default supported docker image architectures\nexport SUPPORTED_ARCHS=${SUPPORTED_ARCHS:-linux/amd64,linux/arm64}\n\n# Splitting of DOCKER_IMAGES variable is desired.\n# shellcheck disable=SC2206\nexport DOCKER_IMAGES=(${DOCKER_IMAGES:-\n    controller\n    metrics-api\n    debug\n    proxy\n    web\n    tap\n})\n\ndocker_repo() {\n    repo=$1\n\n    name=$repo\n    if [ \"${DOCKER_REGISTRY:-}\" ]; then\n        name=\"$DOCKER_REGISTRY/$name\"\n    fi\n\n    echo \"$name\"\n}\n\ndocker_build() {\n    name=$1\n    repo=$(docker_repo \"$name\")\n    shift\n\n    tag=$1\n    shift\n\n    file=$1\n    shift\n\n    rootdir=${ROOTDIR:-$( cd \"$bindir\"/.. && pwd )}\n    cache_params=''\n\n    if [ \"$ACTIONS_CACHE_URL\" ]; then\n      cache_params=\"--cache-from type=gha,scope=$name-$DOCKER_TARGET --cache-to type=gha,scope=$name-$DOCKER_TARGET,mode=max\"\n    fi\n\n    output_params='--load'\n    if [ \"$DOCKER_TARGET\" = 'multi-arch' ]; then\n      output_params=\"--platform $SUPPORTED_ARCHS\"\n      if [ \"$DOCKER_PUSH\" ]; then\n        output_params+=' --push'\n      else\n        echo 'Error: env DOCKER_PUSH=1 is missing\nWhen building the multi-arch images it is required to push the images to the registry\nSee https://github.com/docker/buildx/issues/59 for more details'\n        exit 1\n      fi\n    fi\n\n    # Allow for specifying docker builder engine\n    # This is a great way to use k8s to build docker images on native hardware instead of emulated\n    # See https://docs.docker.com/build/drivers/kubernetes/ for an example\n    if [ \"$DOCKER_BUILDER\" ]; then\n      output_params+=\" --builder=$DOCKER_BUILDER\"\n    fi\n\n    log_debug \"  :; docker buildx $rootdir $cache_params $output_params -t $repo:$tag -f $file $*\"\n    mkdir -p target\n    # shellcheck disable=SC2086\n    docker buildx build \"$rootdir\" $cache_params \\\n        $output_params \\\n        -t \"$repo:$tag\" \\\n        -f \"$file\" \\\n        --metadata-file target/metadata-\"$name\".json \\\n        \"$@\"\n\n    echo \"$repo:$tag\"\n}\n\ndocker_pull() {\n    repo=$(docker_repo \"$1\")\n    tag=$2\n    log_debug \"  :; docker pull $repo:$tag\"\n    docker pull \"$repo:$tag\"\n}\n\ndocker_push() {\n    repo=$(docker_repo \"$1\")\n    tag=$2\n    log_debug \"  :; docker push $repo:$tag\"\n    docker push \"$repo:$tag\"\n}\n\ndocker_retag() {\n    repo=$(docker_repo \"$1\")\n    from=$2\n    to=$3\n    log_debug \"  :; docker tag $repo:$from $repo:$to\"\n    docker tag \"$repo:$from\" \"$repo:$to\"\n    echo \"$repo:$to\"\n}\n\ndocker_rename_registry() {\n  tag=$1\n  from=$2\n  to=$3\n  for img in \"${DOCKER_IMAGES[@]}\" ; do\n    docker tag \"$from/$img:$tag\" \"$to/$img:$tag\"\n  done\n}\n"
  },
  {
    "path": "bin/_log.sh",
    "content": "#!/usr/bin/env sh\nset -eu\n\n# build debug logging is disabled by default; enable with BUILD_DEBUG=1\n# shell trace logging is disabled by default; enable with TRACE=1\n\nexport TRACE=\"${TRACE:-}\"\nif [ \"$TRACE\" ]; then\n    set -x\nfi\n\nlog_debug() {\n    if [ -z \"$TRACE\" ] && [ \"${BUILD_DEBUG:-}\" ]; then\n        echo \"$@\" >&2\n    fi\n}\n"
  },
  {
    "path": "bin/_os.sh",
    "content": "#!/usr/bin/env sh\n\nset -eu\n\nexport OS_ARCH_ALL='linux-amd64 linux-arm64 darwin darwin-arm64 windows'\n\narchitecture() {\n  arch=$(uname -m)\n  case $arch in\n    x86_64)\n      arch=amd64\n      ;;\n    armv8*)\n      arch=arm64\n      ;;\n    aarch64*)\n      arch=arm64\n      ;;\n    amd64|arm64)\n      # keep arch as is\n      ;;\n    *)\n      echo \"unsupported architecture: $arch\" >&2\n      exit 1\n      ;;\n  esac\n  echo \"$arch\"\n}\n\nos() {\n  os=$(uname -s)\n  arch=''\n  case $os in\n    CYGWIN* | MINGW64*)\n      os=windows\n      ;;\n    Darwin)\n      os=darwin\n      ;;\n    Linux)\n      os=linux\n      arch=$(architecture)\n      ;;\n    *)\n      echo \"unsupported os: $os\" >&2\n      exit 1\n      ;;\n  esac\n\n  if [ \"$arch\" ]; then\n    echo \"$os-$arch\"\n  else\n    echo \"$os\"\n  fi\n}\n"
  },
  {
    "path": "bin/_tag.sh",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\ngit_sha_head() {\n    git rev-parse --short=8 HEAD\n}\n\nclean_head() {\n    [ \"${CI_FORCE_CLEAN:-}\" ] || git diff-index --quiet HEAD --\n}\n\nnamed_tag() {\n    tag=$(git name-rev --tags --name-only \"$(git_sha_head)\")\n    tag=${tag%\"^0\"}\n    echo \"${tag}\"\n}\n\nhead_root_tag() {\n    if clean_head ; then\n        clean_head_root_tag\n    else\n        USER=${USER:-nobody}\n        name=${USER//[^[:alnum:].-]/}\n        echo \"dev-$(git_sha_head)-$name\"\n    fi\n}\n\nclean_head_root_tag() {\n    if clean_head ; then\n        if [ \"$(named_tag)\" != undefined ]; then\n            named_tag\n        else\n            echo \"git-$(git_sha_head)\"\n        fi\n    else\n        echo 'Commit unstaged changes.' >&2\n        exit 3\n    fi\n}\n"
  },
  {
    "path": "bin/_test-helpers.sh",
    "content": "#!/usr/bin/env bash\n\n# Override CI's `set -e` default, so we can catch errors manually and display\n# proper messages\nset +e\n\nk8s_version_min='+v1.23'\nk8s_version_max='docker.io/rancher/k3s:v1.31.5-k3s1'\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\ntestdir=$bindir/../test/integration\n\n##### Test setup helpers #####\n\nexport default_test_names=(deep deep-native-sidecar viz tracing external helm-upgrade uninstall upgrade-edge default-policy-deny rsa-ca)\nexport external_resource_test_names=(external-resources)\n# TODO(alpeb): add test cni-calico-deep-dual-stack\nexport dual_stack_test_names=(deep-dual-stack)\nexport all_test_names=(cluster-domain cni-calico-deep multicluster \"${default_test_names[*]}\" \"${external_resource_test_names[*]}\" \"${dual_stack_test_names[*]}\")\nimages_load_default=(proxy controller web metrics-api tap)\n\ntests_usage() {\n  progname=${0##*/}\n  echo \"Run Linkerd integration tests.\n\nOptionally specify a test with the --name flag: [${all_test_names[*]}]\n\nNote: The cluster-domain, deep-native-sidecar cni-calico-deep and multicluster tests require a custom cluster configuration (see bin/_test-helpers.sh)\n\nUsage:\n    ${progname} [--images docker|archive|skip] [--name test-name] [--skip-cluster-create] /path/to/linkerd\n\nExamples:\n    # Run all tests in isolated clusters\n    ${progname} /path/to/linkerd\n\n    # Run single test in isolated clusters\n    ${progname} --name test-name /path/to/linkerd\n\n    # Skip k3d cluster creation and run all tests in default cluster context\n    ${progname} --skip-cluster-create /path/to/linkerd\n\n    # Load images from tar files located under the 'image-archives' directory\n    # Note: This is primarily for CI\n    ${progname} --images archive /path/to/linkerd\n\nAvailable Commands:\n    --name: the argument to this option is the specific test to run\n    --skip-cluster-create: skip k3d cluster creation step and run tests in an existing cluster\n    --skip-cluster-delete: if the tests succeed, don't delete the created resources nor the cluster\n    --images: set to 'docker' (default) to load images into the cluster from the local docker cache;\n      set to 'preload' to also load them from the local docker cache, after having pulled them from\n      a public registry (appears to be faster than having k3d pulling them itself);\n      set to 'archive' to load the images from tar files located under the image-archives directory\n    --cleanup-docker: delete the 'images-archive' directory and prune the docker cache\"\n}\n\ncleanup_usage() {\n  progname=${0##*/}\n  echo \"Cleanup Linkerd integration tests.\n\nUsage:\n    ${progname} [--context k8s_context] /path/to/linkerd\n\nExamples:\n    # Cleanup tests in non-default context\n    ${progname} --context k8s_context /path/to/linkerd\n\nAvailable Commands:\n    --context: use a non-default k8s context\"\n}\n\nhandle_tests_input() {\n  export images=docker\n  export test_name=''\n  export skip_cluster_create=''\n  export skip_cluster_delete=''\n  export cleanup_docker=''\n  export linkerd_path=''\n\n  while  [ $# -ne 0 ]; do\n    case $1 in\n      -h|--help)\n        tests_usage \"$0\"\n        exit 0\n        ;;\n      --images)\n        images=$2\n        if [ -z \"$images\" ]; then\n          echo 'Error: the argument for --images was not specified' >&2\n          tests_usage \"$0\" >&2\n          exit 64\n        fi\n        if [[ $images != 'docker' && $images != 'archive' && $images != 'preload' ]]; then\n          echo 'Error: the argument for --images was invalid' >&2\n          tests_usage \"$0\" >&2\n          exit 64\n        fi\n        shift\n        shift\n        ;;\n      --name)\n        test_name=$2\n        if [ -z \"$test_name\" ]; then\n          echo 'Error: the argument for --name was not specified' >&2\n          tests_usage \"$0\" >&2\n          exit 64\n        fi\n        shift\n        shift\n        ;;\n      --skip-cluster-create)\n        skip_cluster_create=1\n        shift\n        ;;\n      --skip-cluster-delete)\n        skip_cluster_delete=1\n        shift\n        ;;\n      --cleanup-docker)\n        cleanup_docker=1\n        shift\n        ;;\n      *)\n        if echo \"$1\" | grep -q '^-.*' ; then\n          echo \"Unexpected flag: $1\" >&2\n          tests_usage \"$0\" >&2\n          exit 64\n        fi\n        if [ \"$linkerd_path\" ]; then\n          echo 'Multiple linkerd paths specified:' >&2\n          echo \"  $linkerd_path\" >&2\n          echo \"  $1\" >&2\n          tests_usage \"$0\" >&2\n          exit 64\n        fi\n        linkerd_path=$(realpath \"$1\")\n        shift\n        ;;\n    esac\n  done\n\n  if [ -z \"$linkerd_path\" ]; then\n    echo 'Error: path to linkerd binary is required' >&2\n    tests_usage \"$0\" >&2\n    exit 64\n  fi\n\n  if [ -z \"$test_name\" ] && [ \"$skip_cluster_delete\" ]; then\n    echo 'Error: must provide --name when using --skip-cluster-delete' >&2\n    tests_usage \"$0\" >&2\n    exit 64\n  fi\n}\n\nhandle_cleanup_input() {\n  export k8s_context=''\n  export linkerd_path=''\n\n  while  [ $# -ne 0 ]; do\n    case $1 in\n      -h|--help)\n        cleanup_usage \"$0\"\n        exit 0\n        ;;\n      --context)\n        k8s_context=$2\n        shift\n        shift\n        ;;\n      *)\n        if echo \"$1\" | grep -q '^-.*' ; then\n          echo \"Unexpected flag: $1\" >&2\n          cleanup_usage \"$0\" >&2\n          exit 64\n        fi\n        if [ \"$linkerd_path\" ]; then\n          echo 'Multiple linkerd paths specified:' >&2\n          echo \"  $linkerd_path\" >&2\n          echo \"  $1\" >&2\n          cleanup_usage \"$0\" >&2\n          exit 64\n        fi\n        linkerd_path=$1\n        shift\n        ;;\n    esac\n  done\n\n  if [ -z \"$linkerd_path\" ]; then\n    echo 'Error: path to linkerd binary is required' >&2\n    cleanup_usage \"$0\" >&2\n    exit 64\n  fi\n}\n\ncheck_linkerd_binary() {\n  printf 'Checking the linkerd binary...'\n  if [ ! -x \"$linkerd_path\" ]; then\n    printf '\\n[%s] does not exist or is not executable\\n' \"$linkerd_path\"\n    exit 1\n  fi\n  exit_code=0\n  \"$linkerd_path\" version --client > /dev/null 2>&1\n  exit_on_err 'error running linkerd version command'\n  echo '[ok]'\n}\n\n##### Cluster helpers #####\n\ncheck_cluster() {\n  check_if_k8s_reachable\n  kubectl version\n  check_if_l5d_exists\n}\n\ndelete_cluster() {\n  local name=$1\n  \"$bindir\"/k3d cluster delete \"$name\" 2>&1\n  exit_on_err 'error deleting cluster'\n}\n\ncleanup_cluster() {\n  \"$bindir\"/test-cleanup \"$linkerd_path\" > /dev/null 2>&1\n  exit_on_err 'error removing existing Linkerd resources'\n}\n\nsetup_min_cluster() {\n  local name=$1\n  export helm_path=$bindir/helm\n\n  check_linkerd_binary\n  if [ -z \"$skip_cluster_create\" ]; then\n    \"$bindir\"/k3d cluster create \"$@\" --image \"$k8s_version_min\"\n    image_load \"$name\"\n  fi\n  check_cluster\n}\n\nsetup_cluster() {\n  local name=$1\n  export helm_path=$bindir/helm\n\n  check_linkerd_binary\n  if [ -z \"$skip_cluster_create\" ]; then\n    \"$bindir\"/k3d cluster create \"$@\"\n    image_load \"$name\"\n  fi\n  check_cluster\n}\n\nfinish() {\n  if [ -z \"$skip_cluster_delete\" ]; then\n    local name=$1\n    if [ -z \"$skip_cluster_create\" ]; then\n      delete_cluster \"$name\"\n    else\n      cleanup_cluster\n    fi\n  fi\n}\n\ncheck_if_k8s_reachable() {\n  printf 'Checking if there is a Kubernetes cluster available...'\n  exit_code=0\n  kubectl --context=\"$context\" --request-timeout=5s get ns > /dev/null 2>&1\n  exit_on_err 'error connecting to Kubernetes cluster'\n  echo '[ok]'\n}\n\ncheck_if_l5d_exists() {\n  printf 'Checking if Linkerd resources exist on cluster...'\n  local resources\n  resources=$(kubectl --context=\"$context\" get all,clusterrole,clusterrolebinding,mutatingwebhookconfigurations,validatingwebhookconfigurations,crd -l linkerd.io/control-plane-ns --all-namespaces -oname)\n  if [ \"$resources\" ]; then\n    printf '\nLinkerd resources exist on cluster:\n\\n%s\\n\nHelp:\n    Run: [%s/test-cleanup] ' \"$resources\" \"$linkerd_path\"\n    exit 1\n  fi\n  echo '[ok]'\n}\n\n##### Test runner helpers #####\n\nimage_load() {\n  cluster_name=$1\n  images_load=(\"${images_load_default[@]}\")\n  case $images in\n    docker)\n      \"$bindir\"/image-load --k3d --cluster \"$cluster_name\" \"${images_load[@]}\"\n      exit_on_err \"error calling '$bindir/image-load'\"\n      ;;\n    preload)\n      \"$bindir\"/image-load --k3d --cluster \"$cluster_name\" --preload \"${images_load[@]}\"\n      exit_on_err \"error calling '$bindir/image-load'\"\n      ;;\n    archive)\n      \"$bindir\"/image-load --k3d --archive --cluster \"$cluster_name\" \"${images_load[@]}\"\n      exit_on_err \"error calling '$bindir/image-load'\"\n      ;;\n  esac\n}\n\nstart_test() {\n  local name=$1\n  local config=(--k3s-arg '--disable=local-storage,metrics-server@server:0')\n\n  case $name in\n    cluster-domain)\n      config=(\"$name\" \"${config[@]}\" --no-lb --k3s-arg --cluster-domain=custom.domain --k3s-arg '--disable=servicelb,traefik@server:0' --image \"$k8s_version_max\")\n      ;;\n    cni-calico-deep)\n      config=(\"$name\" \"${config[@]}\" --no-lb --k3s-arg --write-kubeconfig-mode=644 --k3s-arg --flannel-backend=none --k3s-arg --cluster-cidr=192.168.0.0/16 --k3s-arg '--disable=servicelb,traefik@server:0')\n      ;;\n    multicluster)\n      config=(\"${config[@]}\" --network multicluster-test --image \"$k8s_version_max\")\n      ;;\n    *)\n      config=(\"$name\" \"${config[@]}\" --no-lb --k3s-arg '--disable=servicelb,traefik@server:0' --image \"$k8s_version_max\")\n      ;;\n  esac\n\n  if [ \"$name\" = 'multicluster' ]; then\n    start_multicluster_test \"${config[@]}\"\n  else\n    start_single_test \"${config[@]}\"\n  fi\n}\n\nstart_single_test() {\n  name=$1\n  if [ \"$name\" = 'helm-deep' ]; then\n    setup_min_cluster \"$@\"\n  else\n    setup_cluster \"$@\"\n  fi\n  if [ \"$cleanup_docker\" ]; then\n    rm -rf image-archives\n    docker system prune --force --all\n  fi\n  run_\"$name\"_test\n  exit_on_err \"error calling 'run_${name}_test'\"\n  finish \"$name\"\n}\n\nstart_multicluster_test() {\n  setup_cluster source \"$@\"\n  setup_cluster target \"$@\"\n  if [ \"$cleanup_docker\" ]; then\n    rm -rf image-archives\n    docker system prune --force --all\n  fi\n  run_multicluster_test\n  exit_on_err \"error calling 'run_multicluster_test'\"\n  export context='k3d-source'\n  finish source\n  export context='k3d-target'\n  finish target\n}\n\nrun_test(){\n  local filename=$1\n  shift\n\n  printf 'Test script: [%s] Params: [%s]\\n' \"${filename##*/}\" \"$*\"\n  timeout=\"${TEST_TIMEOUT:-15m}\"\n  # Exit on failure here\n  GO111MODULE=on go test -v -test.timeout=\"$timeout\" --failfast --mod=readonly \"$filename\" \\\n    --linkerd=\"$linkerd_path\" \\\n    --helm-path=\"$helm_path\" \\\n    --default-inbound-policy=\"$default_inbound_policy\" \\\n    --k8s-context=\"$context\" \\\n    --integration-tests \"$@\"\n}\n\n# Returns the latest version for the release channel\n# $1: release channel to check\nlatest_release_channel() {\n    \"$bindir\"/scurl https://versioncheck.linkerd.io/version.json | grep -o \"$1-[0-9]*.[0-9]*.[0-9]*\"\n}\n\n# Run the upgrade-edge test by upgrading the most-recent edge release to the\n# HEAD of this branch.\nrun_upgrade-edge_test() {\n  run_test \"$testdir/upgrade-edge/...\"\n}\n\nrun_viz_test() {\n  run_test \"$testdir/viz/...\"\n}\n\nsetup_helm() {\n  export helm_path=\"$bindir\"/helm\n  helm_charts=\"$( cd \"$bindir\"/.. && pwd )\"/charts\n  export helm_charts\n  export helm_release_name='helm-test'\n  export helm_multicluster_release_name='multicluster-test'\n  \"$bindir\"/helm-build\n  \"$helm_path\" --kube-context=\"$context\" repo add linkerd https://helm.linkerd.io/edge\n  exit_on_err 'error setting up Helm'\n}\n\nhelm_cleanup() {\n  (\n    set -e\n   \"$helm_path\" --kube-context=\"$context\" --namespace linkerd-multicluster delete \"$helm_multicluster_release_name\" || true\n    kubectl delete ns/linkerd-multicluster || true\n    \"$helm_path\" --kube-context=\"$context\" --namespace linkerd-viz delete \"$helm_release_name-l5d-viz\" || true\n    kubectl delete ns/linkerd-viz || true\n    \"$helm_path\" --kube-context=\"$context\" --namespace linkerd delete \"$helm_release_name-control-plane\" || true\n    \"$helm_path\" --kube-context=\"$context\" --namespace linkerd delete \"$helm_release_name-crds\" || true\n    kubectl delete ns/linkerd\n  )\n  exit_on_err 'error cleaning up Helm'\n}\n\nrun_helm-upgrade_test() {\n  local edge_version\n  edge_version=$(latest_release_channel 'edge')\n\n  if [ -z \"$edge_version\" ]; then\n    echo 'error getting edge_version'\n    exit 1\n  fi\n\n  setup_helm\n  helm_viz_chart=$( cd \"$bindir\"/.. && pwd )/viz/charts/linkerd-viz\n  run_test \"$testdir/install/install_test.go\" --helm-path=\"$helm_path\" --helm-charts=\"$helm_charts\" \\\n  --viz-helm-chart=\"$helm_viz_chart\" --viz-helm-stable-chart=\"linkerd/linkerd-viz\" --helm-release=\"$helm_release_name\" --upgrade-helm-from-version=\"$edge_version\"\n  helm_cleanup\n}\n\nrun_uninstall_test() {\n  run_test \"$testdir/install/uninstall/uninstall_test.go\" --uninstall=true\n}\n\nrun_multicluster_test() {\n   run_test \"$testdir/multicluster/...\"\n}\n\nrun_deep_test() {\n  run_test \"$testdir/deep/...\"\n}\n\nrun_deep-native-sidecar_test() {\n  run_test \"$testdir/deep/...\" --native-sidecar\n}\n\nrun_deep-dual-stack_test() {\n  run_test \"$testdir/deep/...\" --dual-stack\n}\n\nrun_default-policy-deny_test() {\n  export default_inbound_policy='deny'\n  run_test \"$testdir/install/...\"\n}\n\nrun_cni-calico-deep_test() {\n  run_test \"$testdir/deep/...\" --cni\n}\n\nrun_rsa-ca_test() {\n  run_test \"$testdir/rsa-ca/...\"\n}\n\nrun_tracing_test() {\n  run_test \"$testdir/tracing/...\"\n}\n\nrun_external_test() {\n  run_test \"$testdir/external/...\"\n}\n\nrun_cluster-domain_test() {\n  run_test \"$testdir/install/...\" --cluster-domain='custom.domain'\n}\n\n# exit_on_err should be called right after a command to check the result status\n# and eventually generate a GitHub error annotation. Do not use after calls to\n# `go test` as that generates its own annotations. Note this should be called\n# outside subshells in order for the script to terminate.\nexit_on_err() {\n  exit_code=$?\n  if [ $exit_code -ne 0 ]; then\n    export GH_ANNOTATION=${GH_ANNOTATION:-}\n    if [ \"$GH_ANNOTATION\" ]; then\n      printf '::error::%s\\n' \"$1\"\n    else\n      printf '\\n=== FAIL: %s\\n' \"$1\"\n    fi\n    exit $exit_code\n  fi\n}\n"
  },
  {
    "path": "bin/build-cli-bin",
    "content": "#!/usr/bin/env sh\n\nset -eu\n\n# Builds CLI binary for current platform. Suitable for local development.\n# Note: This script is used by Brew when running `brew install linkerd`:\n# https://github.com/Homebrew/homebrew-core/blob/main/Formula/l/linkerd.rb\n\nbindir=$( cd \"${0%/*}\" && pwd )\nrootdir=$( cd \"$bindir\"/.. && pwd )\n# shellcheck source=_tag.sh\n. \"$bindir\"/_tag.sh\n# shellcheck source=_os.sh\n. \"$bindir\"/_os.sh\n\n(\n    cd \"$rootdir\"\n    cd \"$(pwd -P)\"\n    target=target/cli/$(os)/linkerd\n\n    root_tag=$(\"$bindir\"/root-tag)\n    GO111MODULE=on CGO_ENABLED=0 go build -o \"$target\" -tags prod -mod=readonly -ldflags \"-s -w -X github.com/linkerd/linkerd2/pkg/version.Version=$root_tag\" ./cli\n    echo \"$target\"\n)\n"
  },
  {
    "path": "bin/certs-openssl",
    "content": "#!/usr/bin/env sh\n\nset -eu\n\n# Creates the root and issuer (intermediary) self-signed certificates for the control plane using openssl.\n#\n# For instructions on doing this with step-cli, check https://linkerd.io/2/tasks/generate-certificates\n\n# Generate CA config\ncat > ca.cnf << EOF\n[ req ]\ndistinguished_name=dn\nprompt = no\n[ ext ]\nbasicConstraints = CA:TRUE\nkeyUsage = digitalSignature, keyCertSign, cRLSign\n[ dn ]\nCN = identity.linkerd.cluster.local\nEOF\n\n# Generate CA key\nopenssl ecparam -out ca.key -name prime256v1 -genkey -noout\n\n# Generate CA cert\nopenssl req -key ca.key -new -x509 -days 7300 -sha256 -out ca.crt -config ca.cnf -extensions ext\n\n# Generate the intermediate issuer key\nopenssl ecparam -out issuer.key -name prime256v1 -genkey -noout\n\n# Generate the intermediate issuer csr and cert\nopenssl req -new -sha256 -key issuer.key -out issuer.csr  -config ca.cnf\nopenssl x509 -sha256 -req -in issuer.csr -out issuer.crt -CA ca.crt -CAkey ca.key -days 7300 -extfile ca.cnf -extensions ext -CAcreateserial\n"
  },
  {
    "path": "bin/compute-edge-version",
    "content": "#!/usr/bin/env bash\n\n# This script bumps the patch version of all charts\n\nset -euo pipefail\nshopt -s globstar\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\n\n# shellcheck source=_tag.sh\n. \"$bindir\"/_tag.sh\ntag=$(named_tag)\n\nedge_tag_regex='edge-([0-9][0-9]).([0-9]|[0-9][0-9]).([0-9]+)'\n\n# Get the current edge version.\nurl=https://run.linkerd.io/install-edge\ncurrent_edge=$(\"$bindir\"/scurl $url | awk -v tag_format=\"$edge_tag_regex\" '$0 ~ tag_format')\n\ncurrent_mm=$(echo \"$current_edge\" | sed -n -E \"s/.*$edge_tag_regex}$/\\2/p\")\ncurrent_xx=$(echo \"$current_edge\" | sed -n -E \"s/.*$edge_tag_regex}$/\\3/p\")\nyy=$(date +\"%y\")\nyyyy=$(date +\"%Y\")\nnew_mm=$(date +\"%-m\")\n\n# If this is a new month, `new_xx` should be 1; otherwise increment it.\nif [ \"$new_mm\" != \"$current_mm\" ]; then\n    new_xx=1\nelse\n    new_xx=$((current_xx+1))\nfi\n\nexpected_tag=\"edge-$yy.$new_mm.$new_xx\"\n\nif [ \"$tag\" != \"$expected_tag\" ]; then\n    echo \"Tag ($tag) doesn't match computed edge version ($expected_tag)\"\n    exit 1\nfi\n\n[[ \"${1:-}\" == \"update-charts\" ]] || exit 0\n\nnew_version=\"$yyyy.$new_mm.$new_xx\"\n\nfor chart in **/Chart.yaml; do\n    if [[ \"$chart\" =~ \"partials\" || \"$chart\" =~ \"patch\" || \"$chart\" =~ \"multicluster-link\" ]]; then\n        continue\n    fi\n\n    echo \"Bumping $chart to $new_version\"\n    yq -i \".version = \\\"$new_version\\\"\" \"$chart\"\ndone\n"
  },
  {
    "path": "bin/docker",
    "content": "#!/usr/bin/env sh\n\nset -eu\n\ndockerversion=19.03.1\n\nbindir=$( cd \"${0%/*}\" && pwd )\ntargetbin=$( cd \"$bindir\"/.. && pwd )/target/bin\ndockerbin=$targetbin/.docker-$dockerversion\n\nif [ ! -f \"$dockerbin\" ]; then\n  filename=docker-$dockerversion.tgz\n  if [ \"$(uname -s)\" = Darwin ]; then\n    os=mac\n  else\n    os=linux\n  fi\n\n  url=https://download.docker.com/$os/static/stable/x86_64/$filename\n  tmp=$(mktemp -d -t docker.XXX)\n  mkdir -p \"$targetbin\"\n  (\n      cd \"$tmp\"\n      \"$bindir\"/scurl -o ./docker.tar.gz \"$url\"\n      tar zf ./docker.tar.gz -x docker/docker\n  )\n  mv \"$tmp/docker/docker\" \"$dockerbin\"\n  rm -rf \"$tmp\"\nfi\n\n\"$dockerbin\" \"$@\"\n"
  },
  {
    "path": "bin/docker-build",
    "content": "#!/usr/bin/env sh\n\nset -eu\n\nif [ $# -ne 0 ]; then\n    echo \"no arguments allowed for ${0##*/}, given: $*\" >&2\n    exit 64\nfi\n\nbindir=$( cd \"${0%/*}\" && pwd )\n\n\"$bindir\"/docker-build-proxy\n\"$bindir\"/docker-build-controller\n\"$bindir\"/docker-build-web\n\"$bindir\"/docker-build-debug\n\"$bindir\"/docker-build-metrics-api\n\"$bindir\"/docker-build-tap\n"
  },
  {
    "path": "bin/docker-build-cli-bin",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\nif [ $# -ne 0 ]; then\n    echo \"no arguments allowed for ${0##*/}, given: $*\" >&2\n    exit 64\nfi\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\nrootdir=$( cd \"$bindir\"/.. && pwd )\n\n# shellcheck source=_docker.sh\n. \"$bindir\"/_docker.sh\n# shellcheck source=_tag.sh\n. \"$bindir\"/_tag.sh\n\ndockerfile=$rootdir/cli/Dockerfile\n\n# shellcheck disable=SC2046\ndocker_build cli-bin \"${TAG:-$(head_root_tag)}\" \"$dockerfile\" \\\n    --build-arg LINKERD_VERSION=\"${TAG:-$(head_root_tag)}\" \\\n    --target=\"$DOCKER_TARGET\"\n\nIMG=$(docker_repo cli-bin):${TAG:-$(head_root_tag)}\nID=$(docker create \"$IMG\")\n\n# copy the newly built linkerd cli binaries to the local system\nOS=$DOCKER_TARGET\nif [ \"$DOCKER_TARGET\" = 'multi-arch' ]; then\n  OS=$OS_ARCH_ALL\nfi\nfor OS in $OS; do\n  DIR=$rootdir/target/cli/$OS\n  mkdir -p \"$DIR\"\n\n  # only copy if available\n  (docker cp \"$ID:/out/linkerd-${OS}\" \"$DIR/linkerd\" 2> /dev/null) && echo \"$DIR/linkerd\"\ndone\n\ndocker rm \"$ID\" >/dev/null\n"
  },
  {
    "path": "bin/docker-build-controller",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\nif [ $# -ne 0 ]; then\n    echo \"no arguments allowed for ${0##*/}, given: $*\" >&2\n    exit 64\nfi\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\nrootdir=$( cd \"$bindir\"/.. && pwd )\n\n# shellcheck source=_docker.sh\n. \"$bindir\"/_docker.sh\n# shellcheck source=_tag.sh\n. \"$bindir\"/_tag.sh\n\ndocker_build controller \"${TAG:-$(head_root_tag)}\" \"$rootdir/Dockerfile.controller\" --build-arg LINKERD_VERSION=\"${TAG:-$(head_root_tag)}\"\n"
  },
  {
    "path": "bin/docker-build-debug",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\nif [ $# -ne 0 ]; then\n    echo \"no arguments allowed for ${0##*/}, given: $*\" >&2\n    exit 64\nfi\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\nrootdir=$( cd \"$bindir\"/.. && pwd )\n\n# shellcheck source=_docker.sh\n. \"$bindir\"/_docker.sh\n# shellcheck source=_tag.sh\n. \"$bindir\"/_tag.sh\n\ndockerfile=$rootdir/Dockerfile-debug\n\ndocker_build debug \"${TAG:-$(head_root_tag)}\" \"$dockerfile\"\n"
  },
  {
    "path": "bin/docker-build-metrics-api",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\nif [ $# -ne 0 ]; then\n    echo \"no arguments allowed for ${0##*/}, given: $*\" >&2\n    exit 64\nfi\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\nrootdir=$( cd \"$bindir\"/.. && pwd )\n\n# shellcheck source=_docker.sh\n. \"$bindir\"/_docker.sh\n# shellcheck source=_tag.sh\n. \"$bindir\"/_tag.sh\n\ndockerfile=$rootdir/viz/metrics-api/Dockerfile\ndocker_build metrics-api \"${TAG:-$(head_root_tag)}\" \"$dockerfile\"\n\n"
  },
  {
    "path": "bin/docker-build-proxy",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\napko_version=v0.30.13\n\nif [ $# -ne 0 ]; then\n    echo \"no arguments allowed for ${0##*/}, given: $*\" >&2\n    exit 64\nfi\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\nrootdir=$( cd \"$bindir\"/.. && pwd )\n\n# shellcheck source=_docker.sh\n. \"$bindir\"/_docker.sh\n# shellcheck source=_tag.sh\n. \"$bindir\"/_tag.sh\n# shellcheck source=_os.sh\n. \"$bindir\"/_os.sh\n\ndockerfile=$rootdir/Dockerfile.proxy\nruntime_image=\"${RUNTIME_IMAGE:-\"cr.l5d.io/linkerd/proxy-runtime:${TAG:-$(head_root_tag)}\"}\"\n\nget_extra_options() {\n    options=\n    for var in http_proxy https_proxy no_proxy; do\n        [ -z \"${!var:-}\" ] || options=\"$options --build-arg '$var=${!var}'\"\n    done\n    echo \"$options\"\n}\n\n# Build proxy base image with apko\ngo install chainguard.dev/apko@$apko_version\nexport PATH=$PATH:$(go env GOPATH)/bin\n# Add --local flag unless PUSH_RUNTIME_IMAGE is set\napko build \"$rootdir/proxy-runtime.yml\" \"$runtime_image\" \"$rootdir/proxy-runtime.tar\"\ndocker load < \"$rootdir/proxy-runtime.tar\"\nif [[ -n \"${PUSH_RUNTIME_IMAGE:-}\" ]]; then\n    for arch in \"arm64\" \"amd64\"; do\n        docker push \"$runtime_image-$arch\"\n    done\nfi\n\n# We want wordsplit for the extra options here:\n# shellcheck disable=SC2046\ndocker_build proxy \"${TAG:-$(head_root_tag)}\" \"$dockerfile\" \\\n  --build-arg RUNTIME_IMAGE=\"$runtime_image\" \\\n  --build-arg LINKERD_VERSION=\"${TAG:-$(head_root_tag)}\" \\\n  --build-arg LINKERD2_PROXY_REPO=\"${LINKERD2_PROXY_REPO:-linkerd/linkerd2-proxy}\" \\\n  --build-arg LINKERD2_PROXY_VERSION=\"${LINKERD2_PROXY_VERSION:-$(cat .proxy-version)}\" \\\n  --secret id=github,env=LINKERD2_PROXY_GITHUB_TOKEN \\\n  $(get_extra_options)\n"
  },
  {
    "path": "bin/docker-build-tap",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\nif [ $# -ne 0 ]; then\n    echo \"no arguments allowed for ${0##*/}, given: $*\" >&2\n    exit 64\nfi\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\nrootdir=$( cd \"$bindir\"/.. && pwd )\n\n# shellcheck source=_docker.sh\n. \"$bindir\"/_docker.sh\n# shellcheck source=_tag.sh\n. \"$bindir\"/_tag.sh\n\ndockerfile=$rootdir/viz/tap/Dockerfile\ndocker_build tap \"${TAG:-$(head_root_tag)}\" \"$dockerfile\"\n"
  },
  {
    "path": "bin/docker-build-web",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\nif [ $# -ne 0 ]; then\n    echo \"no arguments allowed for ${0##*/}, given: $*\" >&2\n    exit 64\nfi\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\nrootdir=$( cd \"$bindir\"/.. && pwd )\n\n# shellcheck source=_docker.sh\n. \"$bindir\"/_docker.sh\n# shellcheck source=_tag.sh\n. \"$bindir\"/_tag.sh\n\ndockerfile=$rootdir/web/Dockerfile\ndocker_build web \"${TAG:-$(head_root_tag)}\" \"$dockerfile\" --build-arg LINKERD_VERSION=\"${TAG:-$(head_root_tag)}\"\n"
  },
  {
    "path": "bin/docker-pull",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\nif [ $# -eq 1 ]; then\n    tag=${1:-}\nelse\n    echo \"usage: ${0##*/} tag\" >&2\n    exit 64\nfi\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\n\n# shellcheck source=_docker.sh\n. \"$bindir\"/_docker.sh\n\nfor img in \"${DOCKER_IMAGES[@]}\"; do\n    docker_pull \"$img\" \"$tag\"\ndone\n"
  },
  {
    "path": "bin/docker-push",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\nif [ $# -eq 1 ]; then\n    tag=${1:-}\nelse\n    echo \"usage: ${0##*/} tag\" >&2\n    exit 64\nfi\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\n\n# shellcheck source=_docker.sh\n. \"$bindir\"/_docker.sh\n\nfor img in \"${DOCKER_IMAGES[@]}\"; do\n    docker_push \"$img\" \"$tag\"\ndone\n"
  },
  {
    "path": "bin/docker-retag-all",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\nif [ $# -ne 2 ]; then\n    echo \"usage: ${0##*/} from-tag to-tag\" >&2\n    exit 64\nfi\nfrom=$1\nto=$2\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\n\n# shellcheck source=_docker.sh\n. \"$bindir\"/_docker.sh\nfor img in \"${DOCKER_IMAGES[@]}\" ; do\n    docker_retag \"$img\" \"$from\" \"$to\"\ndone\n"
  },
  {
    "path": "bin/docker-test-proxy",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\nexport RUST_LOG=${RUST_LOG:-}\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\nrootdir=$( cd \"$bindir\"/.. && pwd )\n\ndocker build -f \"$rootdir/proxy/Dockerfile\" . \\\n    --target build \\\n    -t proxy-build \\\n    --build-arg=PROXY_UNOPTIMIZED=1\ndocker run --rm -it proxy-build env RUST_LOG=\"$RUST_LOG\" cargo test \"$@\"\n"
  },
  {
    "path": "bin/fetch-proxy",
    "content": "#!/usr/bin/env sh\n\n# If the first argument to this script is \"latest\" or unset, it fetches the\n# latest proxy binary from the linkerd2-proxy github releases. If it's set to\n# a linkerd2-proxy version number (such as v2.76.0), it will fetch the binary\n# matching that version number instead.\n\nset -eu\n\nbindir=$( cd \"${0%/*}\" && pwd )\nrootdir=$( cd \"$bindir\"/.. && pwd )\nbuilddir=$rootdir/target/proxy\n\nproxy_repo=${LINKERD2_PROXY_REPO:-}\nif [ -z \"$proxy_repo\" ]; then\n  proxy_repo=linkerd/linkerd2-proxy\nfi\n\nreleases_url=https://api.github.com/repos/\"$proxy_repo\"/releases\n\ngithub_token=${GITHUB_TOKEN:-}\nif [ -z \"$github_token\" ] && [ -n \"${GITHUB_TOKEN_FILE:-}\" ] && [ -f \"$GITHUB_TOKEN_FILE\" ]; then\n  github_token=$(cat \"$GITHUB_TOKEN_FILE\")\nfi\n\nghcurl() {\n  if [ -n \"${github_token:-}\" ]; then\n    \"$bindir\"/scurl -H \"Authorization: Bearer ${github_token:-}\" \"$@\"\n  else\n    \"$bindir\"/scurl \"$@\"\n  fi\n}\n\nmkdir -p \"$builddir\"\ncd \"$builddir\"\n\nversion=${1:-latest}\narch=${2:-amd64}\n\nif ! ghcurl \"$releases_url\" | jq '.[] | select(.name == \"'\"$version\"'\")' > release.json ; then\n  echo \"Failed to fetch $releases_url\" >&2\n  exit 1\nfi\n\npkgname_legacy=linkerd2-proxy-${version}-${arch}\npkgname_os=linkerd2-proxy-${version}-linux-${arch}\n\n# First try to find the Linux-specific package in the release assets\nif jq -e '.assets[] | select(.name == \"'\"${pkgname_os}.tar.gz\"'\")' release.json > /dev/null; then\n  pkgname=$pkgname_os\nelse\n  # Fall back to the legacy package name\n  if jq -e '.assets[] | select(.name == \"'\"${pkgname_legacy}.tar.gz\"'\")' release.json > /dev/null; then\n    pkgname=$pkgname_legacy\n  else\n    echo \"Neither ${pkgname_os}.tar.gz nor ${pkgname_legacy}.tar.gz found in release assets\" >&2\n    exit 1\n  fi\nfi\n\npkgfile=${pkgname}.tar.gz\npkgurl=$(jq -r '.assets[] | select(.name == \"'\"$pkgfile\"'\") | .url' release.json)\nif ! ghcurl -H 'Accept: application/octet-stream' -o \"$pkgfile\" \"$pkgurl\" ; then\n  echo \"Failed to fetch $pkgurl\" >&2\n  exit 1\nfi\n\nshafile=${pkgname}.txt\nshaurl=$(jq -r '.assets[] | select(.name == \"'\"$shafile\"'\") | .url' release.json)\nif ! ghcurl -H 'Accept: application/octet-stream' -o \"$shafile\" \"$shaurl\" ; then\n  echo \"Failed to fetch $shaurl\" >&2\n  exit 1\nfi\n\ntar -zxvf \"$pkgfile\" >&2\nexpected=$(awk '{print $1}' \"$shafile\")\nif [ \"$(uname)\" = \"Darwin\" ]; then\n  computed=$(openssl dgst -sha256 \"$pkgfile\" | awk '{print $2}')\nelse\n  computed=$(sha256sum \"$pkgfile\" | awk '{print $1}')\nfi\nif [ \"$computed\" != \"$expected\" ]; then\n  echo 'sha mismatch' >&2\n  exit 1\nfi\n\nmv \"$pkgname/LICENSE\" .\nmv \"$pkgname/linkerd2-proxy\" .\nrm -r \"$pkgfile\" \"$pkgname\"\nmv linkerd2-proxy \"$pkgname\"\necho \"$builddir/$pkgname\"\n"
  },
  {
    "path": "bin/fmt",
    "content": "#!/usr/bin/env bash\n\nset -u\n\nwhile IFS= read -r line; do dirs+=(\"$line\"); done <<< \"$(go list -f \\{\\{.Dir\\}\\} ./...)\"\n\n# go list will list all subdirectories and goimports acts recursively.  This\n# results in certain files being reported multiple time.  Therefore, we must\n# dedup them. We also ignore protobuf auto-generated code.\nfiles=()\nwhile IFS= read -r line; do files+=(\"$line\"); done <<< \"$(bin/goimports -l \"${dirs[@]}\" | sort -u | grep -v pb.go | grep -v gogen.go | grep -v /controller/gen )\"\n\nif [[ ${files[*]} ]]; then\n  for file in \"${files[@]}\"; do\n    bin/goimports -d \"$@\" \"$file\"\n  done\n  exit 64\nfi\n"
  },
  {
    "path": "bin/go-mod-tree",
    "content": "#!/usr/bin/env bash\n\n# Print all (transitive) dependencies of the specified go package.\n\nset -euo pipefail\n\nif [ $# -gt 1 ]; then\n    echo \"Usage: $0 [root]\" >&2\n    exit 64\nfi\ndeclare -r MODULE=${1:-github.com/linkerd/linkerd2}\n\nGRAPH=$(go mod graph)\ndeclare -r GRAPH\n\ndeps_of() {\n    local pkg=$1\n    echo \"$GRAPH\" | awk '$1 == \"'\"$pkg\"'\" { print $2 }' | sort | uniq\n}\n\ndeclare -A PKGS=()\nsubtree() {\n    local pkg=$1\n    local namepfx=${2:-}\n    local pfx=${3:-}\n    # If the package has already been printed, then print it with an\n    # asterisk and skip printing its dependencies\n    if (( ${PKGS[$pkg]:-0} )); then\n        echo \"$namepfx$pkg (*)\"\n    else\n        # Otherwise, print the package, mark it as printed, and the\n        # dependency tree for each of its depndencies\n        echo \"$namepfx$pkg\"\n        PKGS[$pkg]=1\n        local dep=''\n        for d in $(deps_of \"$pkg\") ; do\n            if [ -n \"$dep\" ]; then  subtree \"$dep\" \"$pfx├── \" \"$pfx│   \" ; fi\n            dep=$d\n        done\n        if [ -n \"$dep\" ]; then subtree \"$dep\" \"$pfx└── \" \"$pfx    \" ; fi\n    fi\n}\n\nif [[ \"$MODULE\" == *@* ]]; then\n    # The root specifies an exact version, so print its dependencies.\n    subtree \"$MODULE\"\nelif [ -n \"$(echo \"$GRAPH\" | awk '$1 == \"'\"$MODULE\"'\" { print $0 }' | head -n 1)\" ]; then\n    # The root does not specify an exact version, but that is how the\n    # package is listed in go.mod.\n    subtree \"$MODULE\"\nelse\n    # The root does not specify an exact version, find all versions of the\n    # package and print their depdencies.\n    first=1\n    for pkg in $(echo \"$GRAPH\" | awk '{ print $1 }' | sort | uniq) ; do\n        if [ \"${pkg%@*}\" = \"$MODULE\" ]; then\n            if (( first )); then first=0; else echo; fi\n            subtree \"$pkg\"\n        fi\n    done\nfi\n"
  },
  {
    "path": "bin/go-mod-versions",
    "content": "#!/usr/bin/env bash\n\n# Print all versions of the specified go package.\n\nset -euo pipefail\n\nif [ $# -ne 1 ]; then\n    echo \"Usage: $0 <module>\" >&2\n    exit 64\nfi\ndeclare -r MODULE=$1\n\nif [[ \"$MODULE\" == *@* ]]; then\n    echo 'The dependency must not specify an exact version.' >&2\n    exit 1\nfi\n\nfor pkg in $(go mod graph | awk '{ print $1; print $2 }' | sort | uniq) ; do\n    if [ \"${pkg%@*}\" = \"$MODULE\" ]; then\n        echo \"$pkg\"\n    fi\ndone\n\n"
  },
  {
    "path": "bin/go-mod-why",
    "content": "#!/usr/bin/env bash\n\n# Print all (transitive) dependenents of the specified go package.\n\nset -euo pipefail\n\nif [ $# -ne 1 ]; then\n    echo \"Usage: $0 <module>\" >&2\n    exit 64\nfi\ndeclare -r MODULE=$1\n\nGRAPH=$(go mod graph)\ndeclare -r GRAPH\n\ndepends_on() {\n    local pkg=$1\n    echo \"$GRAPH\" | awk '$2 == \"'\"$pkg\"'\" { print $1 }' | sort | uniq\n}\n\ndeclare -A PKGS=()\nsupertree() {\n    local pkg=$1\n    local namepfx=${2:-}\n    local pfx=${3:-}\n    if (( ${PKGS[$pkg]:-0} )); then\n        echo \"$namepfx$pkg (*)\"\n    else\n        PKGS[$pkg]=1\n        echo \"$namepfx$pkg\"\n        local parent=''\n        for p in $(depends_on \"$pkg\") ; do\n            if [ -n \"$parent\" ]; then supertree \"$parent\" \"$pfx├── \" \"$pfx│   \" ; fi\n            parent=$p\n        done\n        if [ -n \"$parent\" ]; then supertree \"$parent\" \"$pfx└── \" \"$pfx    \" ; fi\n    fi\n}\n\nif [[ \"$MODULE\" == *@* ]]; then\n    # The dependency specifies an exact version, so print the packages that\n    # depend on it.\n    supertree \"$MODULE\"\nelse\n    # The dependency does not specify an exact version, find all versions of\n    # the package and print the packages that depend on each of them.\n    first=1\n    for pkg in $(echo \"$GRAPH\" | awk '{ print $1 }' | sort | uniq) ; do\n        if [ \"${pkg%@*}\" = \"$MODULE\" ]; then\n            if (( first )); then first=0; else echo; fi\n            supertree \"$pkg\"\n        fi\n    done\nfi\n"
  },
  {
    "path": "bin/go-run",
    "content": "#!/usr/bin/env sh\n\nset -eu\ncd \"$(pwd -P)\"\n\nbindir=$( cd \"${0%/*}\" && pwd )\n\nif [ $# -eq 0 ]; then\n  echo \"Usage: bin/${0##*/} path/to/main [args]\" >&2\n  exit 1\nfi\n\nversion=$(\"$bindir\"/root-tag)\nldflags=\"-X github.com/linkerd/linkerd2/pkg/version.Version=$version\"\nmkdir -p target\nGO111MODULE=on go build -v -mod=readonly -race -o ./target/go-run -ldflags \"$ldflags\" \"./$1\"\nshift\nexec ./target/go-run \"$@\"\n"
  },
  {
    "path": "bin/goimports",
    "content": "#!/usr/bin/env sh\n\nset -eu\n\ncd \"$(pwd -P)\"\n\nbindir=$( cd \"${0%/*}\" && pwd )\nrootdir=$( cd \"$bindir/..\" && pwd )\ntargetbin=$rootdir/target/bin\nversion=$( grep golang.org/x/tools go.mod | awk '{ print $2}' )\ngoimportsbin=$targetbin/goimports-$version\n\n# install goimports if it does not exist\nif [ ! -f \"$goimportsbin\" ]; then\n  GOBIN=$targetbin go install -mod=readonly golang.org/x/tools/cmd/goimports\n  mv \"$targetbin/goimports\" \"$goimportsbin\"\nfi\n\nexec \"$goimportsbin\" \"$@\"\n"
  },
  {
    "path": "bin/helm",
    "content": "#!/usr/bin/env sh\n\nset -eu\n\nif command -v helm >/dev/null ; then\n    exec helm \"$@\"\nfi\n\nhelmversion=v3.18.4\nbindir=$( cd \"${0%/*}\" && pwd )\ntargetbin=$( cd \"$bindir\"/.. && pwd )/target/bin\nhelmbin=$targetbin/helm-$helmversion\n\nif [ ! -f \"$helmbin\" ]; then\n    if [ \"$(uname -s)\" = Darwin ]; then\n        os=darwin\n        arch=amd64\n    else\n        os=linux\n        case $(uname -m) in\n            x86_64) arch=amd64 ;;\n            arm) dpkg --print-architecture | grep -q arm64 && arch=arm64 ;;\n        esac\n    fi\n    helmcurl=https://get.helm.sh/helm-$helmversion-$os-$arch.tar.gz\n    targetdir=$os-$arch\n    tmp=$(mktemp -d -t helm.XXX)\n    mkdir -p \"$targetbin\"\n    (\n        cd \"$tmp\"\n        \"$bindir\"/scurl -o \"./helm.tar.gz\" \"$helmcurl\"\n        tar zf \"./helm.tar.gz\" -x \"$targetdir\"\n        chmod +x \"$targetdir/helm\"\n    )\n    mv \"$tmp/$targetdir/helm\" \"$helmbin\"\n    rm -rf \"$tmp\"\nfi\n\n\"$helmbin\" \"$@\"\n"
  },
  {
    "path": "bin/helm-build",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nsetValues() {\n    sed -i \"s/$1/$2/\" charts/linkerd-control-plane/values.yaml\n    sed -i \"s/$1/$2/\" charts/linkerd2-cni/values.yaml\n    sed -i \"s/$1/$2/\" multicluster/charts/linkerd-multicluster/values.yaml\n    sed -i \"s/$1/$2/\" viz/charts/linkerd-viz/values.yaml\n}\n\nshowErr() {\n  printf \"Error on exit:\\n  Exit code: %d\\n  Failed command: \\\"%s\\\"\\n\" $? \"$BASH_COMMAND\"\n  setValues \"$fullVersion\" 'linkerdVersionValue'\n}\n\n# trap the last failed command\ntrap 'showErr' ERR\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\nrootdir=$( cd \"$bindir\"/.. && pwd )\n\n# cleanup dependencies\nrm -f charts/linkerd-crds/charts/*\nrm -f charts/linkerd-control-plane/charts/*\nrm -f charts/linkerd2-cni/charts/*\nrm -f charts/patch/charts/*\nrm -f viz/charts/linkerd-viz/charts/*\n\n\"$bindir\"/helm dep up \"$rootdir\"/multicluster/charts/linkerd-multicluster\n\"$bindir\"/helm lint \"$rootdir\"/multicluster/charts/linkerd-multicluster\n\"$bindir\"/helm dep up \"$rootdir\"/multicluster/charts/linkerd-multicluster-link\n\"$bindir\"/helm lint \"$rootdir\"/multicluster/charts/linkerd-multicluster-link\n\"$bindir\"/helm lint \"$rootdir\"/charts/partials\n\"$bindir\"/helm dep up \"$rootdir\"/charts/linkerd2-cni\n\"$bindir\"/helm lint \"$rootdir\"/charts/linkerd2-cni\n\"$bindir\"/helm dep up \"$rootdir\"/charts/linkerd-crds\n\"$bindir\"/helm dep up \"$rootdir\"/charts/linkerd-control-plane\n\"$bindir\"/helm dep up \"$rootdir\"/charts/patch\n\"$bindir\"/helm lint \"$rootdir\"/charts/linkerd-crds\n\"$bindir\"/helm lint --set identityTrustAnchorsPEM=\"fake-trust\" --set identity.issuer.tls.crtPEM=\"fake-cert\" --set identity.issuer.tls.keyPEM=\"fake-key\" \"$rootdir\"/charts/linkerd-control-plane\n\"$bindir\"/helm lint \"$rootdir\"/charts/linkerd2-cni\n\"$bindir\"/helm dep up \"$rootdir\"/viz/charts/linkerd-viz\n\"$bindir\"/helm lint \"$rootdir\"/viz/charts/linkerd-viz\n\n# `bin/helm-build package` assumes the presence of \"$rootdir\"/target/helm/index-pre.yaml which is downloaded in the chart_deploy CI job\nif [ \"$1\" = package ]; then\n    # shellcheck source=_tag.sh\n    . \"$bindir\"/_tag.sh\n    tag=$(named_tag)\n\n    regex='edge-([0-9]+\\.[0-9]+\\.[0-9]+(-.*)?)'\n    if [[ ! \"$tag\" =~ $regex ]]; then\n        echo 'Version tag is malformed'\n        exit 1\n    fi\n    fullVersion=${BASH_REMATCH[0]}\n    version=${BASH_REMATCH[1]}\n\n    # set version in Values files\n    setValues 'linkerdVersionValue' \"$fullVersion\"\n\n    \"$bindir\"/helm -d \"$rootdir\"/target/helm package \"$rootdir\"/charts/linkerd-crds\n    \"$bindir\"/helm --app-version \"$tag\" -d \"$rootdir\"/target/helm package \"$rootdir\"/charts/linkerd-control-plane\n    \"$bindir\"/helm --app-version \"$tag\" -d \"$rootdir\"/target/helm package \"$rootdir\"/charts/linkerd2-cni\n    \"$bindir\"/helm --app-version \"$tag\" -d \"$rootdir\"/target/helm package \"$rootdir\"/multicluster/charts/linkerd-multicluster\n    \"$bindir\"/helm --app-version \"$tag\" -d \"$rootdir\"/target/helm package \"$rootdir\"/viz/charts/linkerd-viz\n\n    mv \"$rootdir\"/target/helm/index-pre.yaml \"$rootdir\"/target/helm/index-pre-\"$version\".yaml\n    \"$bindir\"/helm repo index --url \"https://helm.linkerd.io/edge/\" --merge \"$rootdir\"/target/helm/index-pre-\"$version\".yaml \"$rootdir\"/target/helm\n\n    # restore version in Values files\n    setValues \"$fullVersion\" 'linkerdVersionValue'\nfi\n"
  },
  {
    "path": "bin/image-load",
    "content": "#!/usr/bin/env bash\n\nset -eo pipefail\n\nkind=''\nk3d=''\ncluster=''\narchive=''\npreload=''\nimages=()\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\n# shellcheck source=_docker.sh\n. \"$bindir\"/_docker.sh\n\nusage() {\n  printf \"Load into KinD/k3d the referred Linkerd images. If no images are specified all of them are loaded (%s)\\n\" \"${DOCKER_IMAGES[*]}\"\n  echo \"\nUsage:\n    bin/image-load [--kind] [--k3d] [--cluster name] [--preload] [--archive] [image] [image]...\n\nExamples:\n\n    # Load all the images from the local docker instance into KinD\n    bin/image-load\n\n    # Load only the proxy and controller images into k3d\n    bin/image-load --k3d proxy controller\n\n    # Load all the images from tar files located under the 'image-archives' directory into k3d\n    bin/image-load --k3d --archive\n\nAvailable Commands:\n    --cluster: target cluster name (defaults to 'k3s-default' under k3d, and 'kind' under KinD).\n    --kind: use a KinD cluster (default).\n    --k3d: use a k3d cluster.\n    --preload: pull the docker images from a public registry prior to loading them into the cluster, which appears to be faster than having k3d pulling them itself.\n    --archive: load the images from local .tar files in the current directory.\"\n}\n\nwhile :\ndo\n  case ${1:-} in\n    -h|--help)\n      usage\n      exit 0\n      ;;\n    --cluster)\n      cluster=$2\n      shift\n      ;;\n    --kind)\n      kind=1\n      ;;\n    --k3d)\n      k3d=1\n      ;;\n    --preload)\n      preload=1\n      ;;\n    --archive)\n      archive=1\n      ;;\n    *)\n      if [ -z \"${1:-}\" ]; then\n        break\n      fi\n      if echo \"$1\" | grep -q '^-.*' ; then\n        echo \"Unexpected flag: $1\" >&2\n        usage\n        exit 1\n      fi\n      images+=(\"$1\")\n  esac\n  shift\ndone\n\nif [ ${#images[@]} -eq 0 ]; then\n  images=(\"${DOCKER_IMAGES[@]}\")\nfi\n\nbindir=$( cd \"${0%/*}\" && pwd )\n\nif [ \"$k3d\" ]; then\n  if [ \"$kind\" ]; then\n    echo \"$k3d\"\n    echo \"Error: --kind and --k3d can't be used simultaneously\" >&2\n    usage\n    exit 1\n  fi\n  if [ -z \"$cluster\" ]; then\n    cluster=k3s-default\n  fi\n  bin=$bindir/k3d\n  image_sub_cmd=(image import -c \"$cluster\")\nelse\n  kind=1\n  bin=$bindir/kind\n  if [ -z \"$cluster\" ]; then\n    cluster=kind\n  fi\n  if [ $archive ]; then\n    image_sub_cmd=(load image-archive --name \"$cluster\")\n  else\n    image_sub_cmd=(load docker-image --name \"$cluster\")\n  fi\nfi\n\nif [ -z \"$archive\" ]; then\n  # shellcheck source=_tag.sh\n  . \"$bindir\"/_tag.sh\n  # shellcheck source=_docker.sh\n  . \"$bindir\"/_docker.sh\n  TAG=${TAG:-$(head_root_tag)}\nfi\n\n# This is really to load the binary synchronously, before\n# the parallel executions below attempt doing so\n\"$bin\" version\n\nrm -f load_fail\nfor i in \"${!images[@]}\"; do\n  if [ $archive ]; then\n    param=image-archives/${images[$i]}.tar\n  else\n    param=$DOCKER_REGISTRY/${images[$i]}:$TAG\n    if [ $preload ]; then\n      docker pull -q \"$param\" || (echo \"Error pulling image $param\"; touch load_fail) &\n    fi\n  fi\n\n  if [ \"$kind\" ]; then\n    printf 'Importing %s...\\n' \"${images[$i]}\"\n    \"$bin\" \"${image_sub_cmd[@]}\" \"${param[@]}\" || touch load_fail &\n  else\n    # \"k3d image import\" commands don't behave well when parallelized\n    # but all the images can be loaded in a single invocation\n    images[$i]=$param\n  fi\ndone\n\nwait < <(jobs -p)\nif [ -f load_fail ]; then\n  echo 'Loading docker images into the cluster failed.'\n  rm load_fail\n  exit 1\nfi\n\nif [ \"$k3d\" ]; then\n  printf 'Importing %s...\\n' \"${images[@]}\"\n  \"$bin\" \"${image_sub_cmd[@]}\" \"${images[@]}\" -m tools-node\nfi\n"
  },
  {
    "path": "bin/install-deps",
    "content": "#!/usr/bin/env sh\n\n# This script is used in the multiple Dockerfiles for caching\n# some of the slow-to-build go dependencies.\n\nset -eu\n\nbindir=$( cd \"${0%/*}\" && pwd )\nrootdir=$( cd \"$bindir\"/.. && pwd )\narch=${1:-amd64}\ncd \"$rootdir\"\n\nCGO_ENABLED=0 GOOS=linux GOARCH=$arch go install -mod=readonly \\\n    github.com/golang/protobuf/jsonpb \\\n    github.com/grpc-ecosystem/go-grpc-prometheus \\\n    github.com/prometheus/client_golang/api \\\n    github.com/prometheus/client_golang/api/prometheus/v1 \\\n    github.com/prometheus/client_golang/prometheus \\\n    github.com/prometheus/client_golang/prometheus/promhttp \\\n    github.com/prometheus/client_model/go \\\n    github.com/prometheus/common/expfmt \\\n    github.com/prometheus/common/model \\\n    github.com/sirupsen/logrus \\\n    google.golang.org/grpc \\\n    k8s.io/client-go/discovery \\\n    k8s.io/client-go/kubernetes \\\n    k8s.io/client-go/kubernetes/scheme \\\n    k8s.io/client-go/kubernetes/typed/admissionregistration/v1 \\\n    k8s.io/client-go/kubernetes/typed/apps/v1 \\\n    k8s.io/client-go/kubernetes/typed/apps/v1beta1 \\\n    k8s.io/client-go/kubernetes/typed/apps/v1beta2 \\\n    k8s.io/client-go/kubernetes/typed/authentication/v1 \\\n    k8s.io/client-go/kubernetes/typed/authentication/v1beta1 \\\n    k8s.io/client-go/kubernetes/typed/authorization/v1 \\\n    k8s.io/client-go/kubernetes/typed/authorization/v1beta1 \\\n    k8s.io/client-go/kubernetes/typed/autoscaling/v1 \\\n    k8s.io/client-go/kubernetes/typed/autoscaling/v2beta1 \\\n    k8s.io/client-go/kubernetes/typed/batch/v1 \\\n    k8s.io/client-go/kubernetes/typed/certificates/v1beta1 \\\n    k8s.io/client-go/kubernetes/typed/core/v1 \\\n    k8s.io/client-go/kubernetes/typed/events/v1beta1 \\\n    k8s.io/client-go/kubernetes/typed/extensions/v1beta1 \\\n    k8s.io/client-go/kubernetes/typed/networking/v1 \\\n    k8s.io/client-go/kubernetes/typed/policy/v1beta1 \\\n    k8s.io/client-go/kubernetes/typed/rbac/v1 \\\n    k8s.io/client-go/kubernetes/typed/rbac/v1alpha1 \\\n    k8s.io/client-go/kubernetes/typed/rbac/v1beta1 \\\n    k8s.io/client-go/kubernetes/typed/scheduling/v1alpha1 \\\n    k8s.io/client-go/kubernetes/typed/storage/v1 \\\n    k8s.io/client-go/kubernetes/typed/storage/v1alpha1 \\\n    k8s.io/client-go/kubernetes/typed/storage/v1beta1 \\\n    k8s.io/client-go/pkg/version \\\n    k8s.io/client-go/plugin/pkg/client/auth \\\n    k8s.io/client-go/plugin/pkg/client/auth/azure \\\n    k8s.io/client-go/plugin/pkg/client/auth/gcp \\\n    k8s.io/client-go/plugin/pkg/client/auth/oidc \\\n    k8s.io/client-go/rest \\\n    k8s.io/client-go/rest/watch \\\n    k8s.io/client-go/tools/auth \\\n    k8s.io/client-go/tools/cache \\\n    k8s.io/client-go/tools/clientcmd \\\n    k8s.io/client-go/tools/pager \\\n    k8s.io/client-go/transport \\\n    k8s.io/client-go/util/cert \\\n    k8s.io/client-go/util/flowcontrol \\\n    k8s.io/client-go/util/homedir \\\n    k8s.io/client-go/util/jsonpath\n"
  },
  {
    "path": "bin/k3d",
    "content": "#!/usr/bin/env sh\n\nset -eu\n\nK3D_VERSION=v5.8.3\n\nbindir=$( cd \"${0%/*}\" && pwd )\n\n# shellcheck source=_os.sh\n. \"$bindir\"/_os.sh\n\ntargetbin=$( cd \"$bindir\"/.. && pwd )/target/bin\nk3dbin=$targetbin/k3d-${K3D_VERSION}\n\nif [ ! -f \"$k3dbin\" ]; then\n  arch=$(architecture)\n\n  if [ \"$(uname -s)\" = Darwin ]; then\n    os=darwin\n  elif [ \"$(uname -o)\" = Msys ]; then\n    os=windows\n  else\n    os=linux\n  fi\n\n  mkdir -p \"$targetbin\"\n  \"$bindir\"/scurl -o \"$k3dbin\" \"https://github.com/k3d-io/k3d/releases/download/${K3D_VERSION}/k3d-$os-$arch\"\n  chmod +x \"$k3dbin\"\nfi\n\n\"$k3dbin\" \"$@\"\n\n"
  },
  {
    "path": "bin/kind",
    "content": "#!/usr/bin/env sh\n\nset -eu\n\nkindversion=v0.11.1\n\nbindir=$( cd \"${0%/*}\" && pwd )\ntargetbin=$( cd \"$bindir\"/.. && pwd )/target/bin\nkindbin=$targetbin/.kind-$kindversion\n\nif [ ! -f \"$kindbin\" ]; then\n  if [ \"$(uname -s)\" = Darwin ]; then\n    os=darwin\n    arch=amd64\n  elif [ \"$(uname -o)\" = Msys ]; then\n    os=windows\n    arch=amd64\n  else\n    os=linux\n    case $(uname -m) in\n      x86_64) arch=amd64 ;;\n      arm) arch=arm64 ;;\n    esac\n  fi\n\n  mkdir -p \"$targetbin\"\n  \"$bindir\"/scurl -o \"$kindbin\" https://github.com/kubernetes-sigs/kind/releases/download/$kindversion/kind-$os-$arch\n  chmod +x \"$kindbin\"\nfi\n\n\"$kindbin\" \"$@\"\n"
  },
  {
    "path": "bin/kubectl",
    "content": "#!/usr/bin/env sh\n\nset -eu\n\nkubectlversion=v1.15.3\n\nbindir=$( cd \"${0%/*}\" && pwd )\ntargetbin=$( cd \"$bindir\"/.. && pwd )/target/bin\nkubectlbin=$targetbin/.kubectl-$kubectlversion\n\nif [ ! -f \"$kubectlbin\" ]; then\n  exe=\n  if [ \"$(uname -s)\" = Darwin ]; then\n    os=darwin\n    arch=amd64\n  elif [ \"$(uname -o)\" = Msys ]; then\n    os=windows\n    arch=amd64\n    exe=.exe\n  else\n    os=linux\n    case $(uname -m) in\n      x86_64) arch=amd64 ;;\n      arm) dpkg --print-architecture | grep -q arm64 && arch=arm64 ;;\n    esac\n  fi\n\n  mkdir -p \"$targetbin\"\n  \"$bindir\"/scurl -o \"$kubectlbin\" https://storage.googleapis.com/kubernetes-release/release/$kubectlversion/bin/$os/$arch/kubectl${exe}\n  chmod +x \"$kubectlbin\"\nfi\n\n\"$kubectlbin\" \"$@\"\n"
  },
  {
    "path": "bin/linkerd",
    "content": "#!/usr/bin/env sh\n\nset -eu\n\nbindir=$( cd \"${0%/*}\" && pwd )\nrootdir=$( cd \"$bindir\"/.. && pwd )\n# shellcheck source=_os.sh\n. \"$bindir\"/_os.sh\n\nbin=$rootdir/target/cli/$(os)/linkerd\n\n# Always rebuild the linkerd CLI binary before running it. This should be\n# relatively fast (<2s) if nothing has changed.\n\"$bindir\"/build-cli-bin >/dev/null\n\nexec \"$bin\" \"$@\"\n"
  },
  {
    "path": "bin/minikube-start-hyperv.bat",
    "content": "REM Starts minikube on Windows 10 using Hyper-V.\nREM\nREM Windows 10 version 1709 (Creator's Update) or later is required for the\nREM \"Default Switch (NAT with automatic DHCP).\nREM\nREM Hyper-V must be enabled in \"Turn Windows features on or off.\"\n\nminikube start --kubernetes-version=\"v1.8.0\" --vm-driver=\"hyperv\" --disk-size=30G --memory=8192 --cpus=4 --hyperv-virtual-switch=\"Default Switch\" --v=7 --alsologtostderr\n"
  },
  {
    "path": "bin/protoc-go.sh",
    "content": "#!/usr/bin/env sh\n\nset -eu\n\nrm -rf controller/gen/common controller/gen/config viz/metrics-api/gen viz/tap/gen\nmkdir -p controller/gen/common/net viz/metrics-api/gen/viz viz/tap/gen/tap\n\nprotoc -I proto --go_out=paths=source_relative:controller/gen proto/common/net.proto\nprotoc -I proto -I viz/metrics-api/proto --go_out=paths=source_relative:viz/metrics-api/gen viz/metrics-api/proto/viz.proto\nprotoc -I proto -I viz/metrics-api/proto --go-grpc_out=paths=source_relative:viz/metrics-api/gen/viz viz/metrics-api/proto/viz.proto\nprotoc -I proto -I viz/tap/proto -I viz/metrics-api/proto --go_out=paths=source_relative:viz/tap/gen viz/tap/proto/viz_tap.proto\nprotoc -I proto -I viz/tap/proto -I viz/metrics-api/proto --go-grpc_out=paths=source_relative:viz/tap/gen/tap viz/tap/proto/viz_tap.proto\n\nmv controller/gen/common/net.pb.go   controller/gen/common/net/\nmv viz/metrics-api/gen/viz.pb.go viz/metrics-api/gen/viz/viz.pb.go\nmv viz/tap/gen/viz_tap.pb.go viz/tap/gen/tap/viz_tap.pb.go\n"
  },
  {
    "path": "bin/root-tag",
    "content": "#!/usr/bin/env bash\n\nbindir=$( cd \"${0%/*}\" && pwd )\n\n# shellcheck source=_tag.sh\n. \"$bindir\"/_tag.sh\n\nhead_root_tag\n"
  },
  {
    "path": "bin/rust-toolchain-version",
    "content": "#! /usr/bin/env bash\n# Extracts the current Rust version from the toolchain file.\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\nrootdir=$( cd \"$bindir\"/.. && pwd )\n\nversion_regex='channel = \"(.+)\"'\ntoolchain=$(cat \"$rootdir\"/rust-toolchain.toml)\n\n# If the `rust-toolchain.toml` file contains a line matching the channel regex,\n# extract the channel and echo it.\nif [[ $toolchain =~ $version_regex ]]; then\n    echo \"${BASH_REMATCH[1]}\"\n    exit 0;\nfi\n\n# Otherwise, no matching line was found, so print an error.\nif [ \"${GITHUB_ACTIONS:-false}\" =  'true' ]; then\n    echo '::error file=rust-toolchain.toml::failed to parse rust-toolchain.toml'\nelse\n    echo 'failed to parse rust-toolchain.toml'\nfi\n\nexit 1\n"
  },
  {
    "path": "bin/scurl",
    "content": "#!/usr/bin/env sh\n\nexec curl --proto '=https' --tlsv1.2 -sSfL \"$@\"\n"
  },
  {
    "path": "bin/shellcheck",
    "content": "#!/usr/bin/env sh\n\nset -eu\n\nif command -v shellcheck >/dev/null ; then\n    exec shellcheck \"$@\"\nfi\n\nscversion=v0.8.0\n\nbindir=$( cd \"${0%/*}\" && pwd )\ntargetbin=$( cd \"$bindir\"/.. && pwd )/target/bin\nscbin=$targetbin/.shellcheck-$scversion\nif [ ! -f \"$scbin\" ]; then\n  if [ \"$(uname -s)\" = Darwin ]; then\n    file=darwin.x86_64.tar.xz\n  elif [ \"$(uname -o)\" = Msys ]; then\n    # TODO: work on windows\n    file=zip\n  else\n    case $(uname -m) in\n      x86_64) file=linux.x86_64.tar.xz ;;\n      arm) file=linux.aarch64.tar.xz ;;\n    esac\n  fi\n\n  mkdir -p \"$targetbin\"\n  \"$bindir\"/scurl \"https://github.com/koalaman/shellcheck/releases/download/$scversion/shellcheck-${scversion?}.$file\" | tar -OxJv \"shellcheck-${scversion}/shellcheck\" > \"$scbin\"\n  chmod +x \"$scbin\"\nfi\n\n\"$scbin\" \"$@\"\n"
  },
  {
    "path": "bin/shellcheck-all",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\nbindir=$( cd \"${0%/*}\" && pwd )\nrootdir=$( cd \"$bindir\"/.. && pwd )\n\nscripts() {\n    find \"$rootdir\" -name '*.sh' \\\n        -not -path \"$rootdir/.git/*\" \\\n        -not -path \"$rootdir/target/*\" \\\n        -not -path \"$rootdir/web/app/node_modules/*\"\n}\n\n# Make sure all files with the .sh extension are shellscripts and have a\n# proper shebang\nshebangpattern='#!/usr/bin/env (bash|sh)'\nwhile IFS= read -r file ; do\n    head -1 \"$file\" | grep -qE \"$shebangpattern\\$\" || {\n        echo \"ERROR: No valid '$shebangpattern' shebang found in '$file'\" >&2\n        exit 1\n    }\ndone < <(scripts)\n\n# For more information on shellcheck failures:\n# https://github.com/koalaman/shellcheck/wiki/Checks\n\n# We want the word splitting for the shellcheck arguments\n# shellcheck disable=SC2046\n\"$bindir\"/shellcheck -x -P \"$bindir\" $(scripts |xargs)\n"
  },
  {
    "path": "bin/test-cleanup",
    "content": "#!/usr/bin/env bash\n\nset -eu -o pipefail\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\n\n# shellcheck source=_test-helpers.sh\n. \"$bindir\"/_test-helpers.sh\nhandle_cleanup_input \"$@\"\n\ncheck_linkerd_binary\n\necho \"cleaning up viz extension resources, if present [${k8s_context}]\"\n\"$linkerd_path\" viz uninstall 2> /dev/null | kubectl --context=\"$k8s_context\" delete -f -\n\necho \"cleaning up multicluster resources, if present [${k8s_context}]\"\n\"$linkerd_path\" mc uninstall 2> /dev/null | kubectl --context=\"$k8s_context\" delete -f -\n\necho 'cleaning up the all namespaces labelled with test.linkerd.io/is-test-data-plane'\nkubectl --context=\"$k8s_context\" delete ns -l test.linkerd.io/is-test-data-plane\n\necho 'cleaning up cluster-scoped resources labelled with test.linkerd.io/is-test-data-plane'\nkubectl --context=\"$k8s_context\" delete clusterRole,clusterRoleBindings,mutatingwebhookconfiguration -l test.linkerd.io/is-test-data-plane\n\necho \"cleaning up linkerd resources [${k8s_context}]\"\n\"$linkerd_path\" uninstall | kubectl --context=\"$k8s_context\" delete -f -\n\n# Helm cleanup. Just the entries in `helm ls` as the resources should have already been cleaned up by the code above.\nreleases=$(\"$bindir/helm\" ls -A -q)\nif [[ \"${releases[*]}\" =~ 'l5d-viz' ]]; then\n  \"$bindir/helm\" --kube-context=\"$k8s_context\" --namespace linkerd-viz delete l5d-viz\n  kubectl delete ns linkerd-viz\nfi\nif [[ \"${releases[*]}\" =~ 'helm-test' ]]; then\n  \"$bindir/helm\" --kube-context=\"$k8s_context\" --namespace linkerd delete helm-test-crds\n  \"$bindir/helm\" --kube-context=\"$k8s_context\" --namespace linkerd delete helm-test-control-plane\n  kubectl delete ns linkerd\nfi\nif [[ \"${releases[*]}\" =~ 'multicluster-test' ]]; then\n  \"$bindir/helm\" --kube-context=\"$k8s_context\" --namespace linkerd-multicluster delete multicluster-test\n  kubectl delete ns linkerd-multicluster\nfi\n\n"
  },
  {
    "path": "bin/test-clouds",
    "content": "#!/usr/bin/env bash\n\n#\n# Run integration tests on 4 cloud providers:\n# - Amazon (EKS)\n# - DigitalOcean (DO)\n# - Google (GKE)\n# - Microsoft (AKS)\n#\n# This script assumes you have a working Kubernetes cluster set up on each Cloud\n# provider, and that Kubernetes contexts are configured via the environment\n# variables $AKS $DO $EKS $GKE.\n#\n# For example:\n# export AKS=my-aks-cluster\n# export DO=do-nyc1-my-cluster\n# export EKS=arn:aws:eks:us-east-1:123456789012:cluster/my-cluster\n# export GKE=gke_my-project_us-east1-b_my-cluster\n#\n# For more information on configuring access to multiple clusters, see:\n# https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/#define-clusters-users-and-contexts\n\nset -e\n\n# TODO: share this with test-run\ncheck_linkerd_binary() {\n    printf 'Checking the linkerd binary...'\n    case \"$linkerd_path\" in\n        /*)\n            ;;\n        *)\n            printf '\\n[%s] is not an absolute path\\n' \"$linkerd_path\"\n            exit 1\n            ;;\n    esac\n    if [ ! -x \"$linkerd_path\" ]; then\n        printf '\\n[%s] does not exist or is not executable\\n' \"$linkerd_path\"\n        exit 1\n    fi\n    exit_code=0\n    \"$linkerd_path\" version --client > /dev/null 2>&1 || exit_code=$?\n    if [ $exit_code -ne 0 ]; then\n        printf '\\nFailed to run linkerd version command\\n'\n        exit $exit_code\n    fi\n    echo '[ok]'\n}\n\nif [ \"$#\" -ne 1 ]; then\n    echo \"usage: ${0##*/} /path/to/linkerd\" >&2\n    exit 64\nfi\n\nfor CLUSTER in 'AKS' 'DO' 'EKS' 'GKE'; do\n    if [ -z \"${!CLUSTER}\" ]; then\n        echo \"\\$$CLUSTER not set\" >&2\n        exit 64\n    fi\ndone\n\nlinkerd_path=$1\ncheck_linkerd_binary\n\nprintf '\\nKicking off tests for:\\n- %s\\n- %s\\n- %s\\n- %s\\n\\n' \"$AKS\" \"$DO\" \"$EKS\" \"$GKE\"\n\ntrap 'trap - SIGTERM && kill -- -$$' SIGINT SIGTERM EXIT\nfor CLUSTER in $AKS $DO $EKS $GKE; do\n    bin/test-run \"$linkerd_path\" l5d-integration-cloud \"$CLUSTER\" | while IFS= read -r line; do printf '[%s] %s\\n' \"$CLUSTER\" \"$line\"; done &\ndone\n\nwait\n\n# TODO propagate error code\n"
  },
  {
    "path": "bin/test-clouds-cleanup",
    "content": "#!/usr/bin/env bash\n\n#\n# Cleans up integration tests from 4 cloud providers:\n# - Amazon (EKS)\n# - DigitalOcean (DO)\n# - Google (GKE)\n# - Microsoft (AKS)\n#\n# This script assumes you have a working Kubernetes cluster set up on each Cloud\n# provider, and that Kubernetes contexts are configured via environment\n# variables.\n\nset -e\n\nif [ $# -ne 1 ]; then\n  echo \"Error: accepts 1 argument\nUsage:\n  ${0##*/} /path/to/linkerd\"\n  exit 1\nfi\n\nfor CLUSTER in 'AKS' 'DO' 'EKS' 'GKE'; do\n  if [ -z \"${!CLUSTER}\" ]; then\n    echo \"\\$$CLUSTER not set\" >&2\n    exit 64\n  fi\ndone\n\nfor CLUSTER in $AKS $DO $EKS $GKE\ndo\n  printf '\\n%s\\n' \"$CLUSTER\"\n  bin/test-cleanup --context \"$CLUSTER\" \"$1\"\ndone\n"
  },
  {
    "path": "bin/test-scale",
    "content": "#!/usr/bin/env bash\n\n# This test script deploys the following:\n# - 1 Linkerd control-plane\n# - 5 NAMESPACES x 5 REPLICAS of each:\n#   - Emojivoto demo app\n#   - Books demo app\n#   - Lifecycle / bb test environment\n#\n# Usage:\n# test-scale /path/to/linkerd [namespace]\n\nset -e\n\nNAMESPACES=5\nREPLICAS=5\n\nbindir=$( cd \"${0%/*}\" && pwd )\n\n# TODO: share these functions with test-run\n\ncheck_linkerd_binary(){\n    printf 'Checking the linkerd binary...'\n    case \"$linkerd_path\" in\n        /*)\n            ;;\n        *)\n            printf '\\n[%s] is not an absolute path\\n' \"$linkerd_path\"\n            exit 1\n            ;;\n    esac\n    if [ ! -x \"$linkerd_path\" ]; then\n        printf '\\n[%s] does not exist or is not executable\\n' \"$linkerd_path\"\n        exit 1\n    fi\n    exit_code=0\n    \"$linkerd_path\" version --client > /dev/null 2>&1 || exit_code=$?\n    if [ $exit_code -ne 0 ]; then\n        printf '\\nFailed to run linkerd version command\\n'\n        exit $exit_code\n    fi\n    echo '[ok]'\n}\n\ncheck_if_k8s_reachable(){\n    printf 'Checking if there is a Kubernetes cluster available...'\n    exit_code=0\n    kubectl --request-timeout=5s get ns > /dev/null 2>&1 || exit_code=$?\n    if [ $exit_code -ne 0 ]; then\n        echo '\nFailed to connect to Kubernetes cluster'\n        exit $exit_code\n    fi\n    printf '[ok]\\n'\n}\n\nlinkerd_path=$1\n\nif [ -z \"$linkerd_path\" ]; then\n    echo \"usage: ${0##*/} /path/to/linkerd [namespace]\" >&2\n    exit 64\nfi\n\ncheck_linkerd_binary\ncheck_if_k8s_reachable\n\nlinkerd_version=$(\"$linkerd_path\" version --client --short)\nlinkerd_namespace=${2:-l5d-scale}\n\n#\n# Deploy Linkerd\n#\n\n\"$linkerd_path\" -l \"$linkerd_namespace\" install | kubectl apply -f -\n\"$linkerd_path\" -l \"$linkerd_namespace\" check --expected-version=\"$linkerd_version\"\n\n#\n# Deploy Books\n#\n\nBOOKS_BACKEND=$(\"$bindir\"/scurl https://raw.githubusercontent.com/BuoyantIO/booksapp/main/k8s/mysql-backend.yml)\n\nAUTHORS_SP=$(\"$bindir\"/scurl https://run.linkerd.io/booksapp/authors.swagger)\nBOOKS_SP=$(\"$bindir\"/scurl https://run.linkerd.io/booksapp/books.swagger)\nWEBAPP_SP=$(\"$bindir\"/scurl https://run.linkerd.io/booksapp/webapp.swagger)\n\n# deploy books backend and service profiles to N namespaces\nfor ((i=1; i <= NAMESPACES; i++)); do\n    booksns=$linkerd_namespace-books-$i\n    kubectl create ns \"$booksns\"\n    echo \"$BOOKS_BACKEND\" | kubectl apply -n \"$booksns\" -f -\n\n    echo \"$AUTHORS_SP\" | bin/linkerd profile -n \"$booksns\" authors --open-api - | kubectl apply -f -\n    echo \"$BOOKS_SP\"   | bin/linkerd profile -n \"$booksns\" books   --open-api - | kubectl apply -f -\n    echo \"$WEBAPP_SP\"  | bin/linkerd profile -n \"$booksns\" webapp  --open-api - | kubectl apply -f -\ndone\n\nBOOKS_APP=$(\"$bindir\"/scurl https://raw.githubusercontent.com/BuoyantIO/booksapp/main/k8s/mysql-app.yml)\n\n# add \"-sleep=10ms\" param to the traffic app (~100rps)\ntraffic_param='        - \"webapp:7000\"'\nsleep_param=$(cat <<-END\n        - \"-sleep=10ms\"\n        - \"webapp:7000\"\nEND\n)\nBOOKS_APP=${BOOKS_APP/$traffic_param/$sleep_param}\n\n# inject\nBOOKS_APP=$(echo \"$BOOKS_APP\" | \"$linkerd_path\" -l \"$linkerd_namespace\" inject -)\n\n# deploy books apps to N namespaces\nfor ((i=1; i <= NAMESPACES; i++)); do\n    booksns=$linkerd_namespace-books-$i\n    echo \"waiting for $booksns mysql-init to complete...\"\n    kubectl -n \"$booksns\" wait --for=condition=complete --timeout=5m job/mysql-init\n\n    echo \"$BOOKS_APP\" | kubectl apply -n \"$booksns\" -f -\n    kubectl -n \"$booksns\" scale --replicas=$REPLICAS deploy/authors deploy/books deploy/webapp\ndone\n\n#\n# Deploy Emojivoto\n#\n\nEMOJIVOTO=$(\"$bindir\"/scurl https://run.linkerd.io/emojivoto.yml)\n\n# delete namespace\nEMOJIVOTO=$(echo \"$EMOJIVOTO\" | tail -n +6)\nemojins='namespace: emojivoto'\nEMOJIVOTO=${EMOJIVOTO//$emojins/}\nemojins=.emojivoto:\nnewns=:\nEMOJIVOTO=${EMOJIVOTO//$emojins/$newns}\n\n# inject\nEMOJIVOTO=$(echo \"$EMOJIVOTO\" | \"$linkerd_path\" -l \"$linkerd_namespace\" inject -)\n\nfor ((i=1; i <= NAMESPACES; i++)); do\n    emojins=$linkerd_namespace-emoji-$i\n\n    kubectl create ns \"$emojins\"\n    echo \"$EMOJIVOTO\" | kubectl apply -n \"$emojins\" -f -\n\n    kubectl -n \"$emojins\" scale --replicas=$REPLICAS deploy/emoji deploy/voting deploy/web\ndone\n\n#\n# Lifecycle / bb\n#\n\nLIFECYCLE=$(\"$bindir\"/scurl https://raw.githubusercontent.com/linkerd/linkerd-examples/master/lifecycle/lifecycle.yml)\n\n# inject\nLIFECYCLE=$(echo \"$LIFECYCLE\" | $linkerd_path -l \"$linkerd_namespace\" inject -)\n\nfor ((i=1; i <= NAMESPACES; i++)); do\n    lifecyclens=$linkerd_namespace-lifecycle-$i\n\n    kubectl create ns \"$lifecyclens\"\n    echo \"$LIFECYCLE\" | kubectl apply -n \"$lifecyclens\" -f -\n\n    kubectl -n \"$lifecyclens\" scale --replicas=$REPLICAS deploy/bb-broadcast deploy/bb-p2p deploy/bb-terminus\ndone\n\n#\n# Watch performance\n#\n\nwatch \"$linkerd_path\" -l \"$linkerd_namespace\" stat ns\n"
  },
  {
    "path": "bin/tests",
    "content": "#!/usr/bin/env bash\n\nbindir=$( cd \"${BASH_SOURCE[0]%/*}\" && pwd )\n\n# shellcheck source=_test-helpers.sh\n. \"$bindir\"/_test-helpers.sh\nhandle_tests_input \"$@\"\n\nif [ \"$test_name\" ]; then\n  start_test \"$test_name\"\nelse\n  echo '==================RUNNING ALL TESTS==================\nNote: cluster-domain, cni-calico-deep and multicluster require a specific cluster configuration and are skipped by default\n'\n\n  for test_name in \"${default_test_names[@]}\"; do\n    start_test \"$test_name\"\n  done\n\n  if [ $exit_code -eq 0 ]; then\n    echo '\n=== PASS: all tests passed'\n  else\n    echo '\n=== FAIL: at least one test failed'\n  fi\n\n  exit $exit_code\nfi\n"
  },
  {
    "path": "bin/update-codegen.sh",
    "content": "#!/usr/bin/env bash\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\nSCRIPT_DIR=$(dirname \"${BASH_SOURCE[0]}\")\nSCRIPT_ROOT=$(dirname \"${SCRIPT_DIR}\")\nGEN_VER=$( awk '/k8s.io\\/code-generator/ { print $2 }' \"${SCRIPT_ROOT}/go.mod\" )\nKUBE_OPEN_API_VER=$( awk '/k8s.io\\/kube-openapi/ { print $2 }' \"${SCRIPT_ROOT}/go.mod\" )\nCODEGEN_PKG=$(mktemp -d -t \"code-generator-${GEN_VER}.XXX\")/code-generator\n\ngit clone --depth 1 --branch \"$GEN_VER\" https://github.com/kubernetes/code-generator \"$CODEGEN_PKG\"\n(\n    cd \"$CODEGEN_PKG\"\n    go get k8s.io/kube-openapi/pkg/common@\"$KUBE_OPEN_API_VER\"\n)\n\n# Remove previously generated code\nrm -rf \"${SCRIPT_ROOT}/controller/gen/client/clientset/*\"\nrm -rf \"${SCRIPT_ROOT}/controller/gen/client/listeners/*\"\nrm -rf \"${SCRIPT_ROOT}/controller/gen/client/informers/*\"\ncrds=(serviceprofile server serverauthorization link policy policy externalworkload)\nfor crd in \"${crds[@]}\"\ndo\n  rm -f \"${SCRIPT_ROOT}\"/controller/gen/apis/\"${crd}\"/*/zz_generated.deepcopy.go\ndone\n\n# shellcheck disable=SC1091\nsource \"${CODEGEN_PKG}/kube_codegen.sh\"\n\n# Create a symlink so that the root of the repo is inside github.com/linkerd/linkerd2.\n# This is required because the codegen scripts expect it.\nmkdir -p \"${SCRIPT_ROOT}/github.com/linkerd\"\nln -s \"$(realpath \"${SCRIPT_ROOT}\")\" \"${SCRIPT_ROOT}/github.com/linkerd/linkerd2\"\n\nkube::codegen::gen_helpers \\\n    --boilerplate \"${SCRIPT_ROOT}/controller/gen/boilerplate.go.txt\" \\\n    github.com/linkerd/linkerd2/controller/gen/apis\n\nif [ -n \"${API_KNOWN_VIOLATIONS_DIR:-}\" ]; then\n    report_filename=${API_KNOWN_VIOLATIONS_DIR}/codegen_violation_exceptions.list\n    if [ \"${UPDATE_API_KNOWN_VIOLATIONS:-}\" = 'true' ]; then\n        update_report='--update-report'\n    fi\nfi\n\nkube::codegen::gen_openapi \\\n    --output-pkg github.com/linkerd/linkerd2/controller/gen \\\n    --output-dir \"${SCRIPT_ROOT}/controller/gen/client\" \\\n    --report-filename \"${report_filename:-\"/dev/null\"}\" \\\n    ${update_report:+\"${update_report}\"} \\\n    --boilerplate \"${SCRIPT_ROOT}/controller/gen/boilerplate.go.txt\" \\\n    github.com/linkerd/linkerd2/controller/gen/apis\n\nkube::codegen::gen_client \\\n    --with-watch \\\n    --output-pkg github.com/linkerd/linkerd2/controller/gen/client \\\n    --output-dir \"${SCRIPT_ROOT}/controller/gen/client\" \\\n    --boilerplate \"${SCRIPT_ROOT}/controller/gen/boilerplate.go.txt\" \\\n    github.com/linkerd/linkerd2/controller/gen/apis\n\n# Once the code has been generated, we can remove the symlink.\nrm -rf \"${SCRIPT_ROOT}/github.com\"\n"
  },
  {
    "path": "bin/web",
    "content": "#!/usr/bin/env bash\n\nset -o errexit -o nounset -o pipefail\n\nROOT=$( cd \"${BASH_SOURCE[0]%/*}\"/.. && pwd )\n\nDEV_PORT=8080\nexport NODE_ENV=${NODE_ENV:=development}\n\nfunction -h {\ncat <<USAGE\nUSAGE: web <command>\n\n  * build - build the assets\n  * dev - run a dev server (with live reloading). Options:\n      -p $DEV_PORT\n  * port-forward - setup a tunnel to a controller component and port\n      Example: port-forward linkerd controller 8085\n  * run - run a local server (sans. reloading)\n  * setup - get the environment setup for development\n      Note: any command line options are passed on to yarn\n  * test - run the unit tests\n      Note: any command line options are passed on to the test runner (jest)\nUSAGE\n}; function --help { '-h' ;}\n\ncheck-for-linkerd-and-viz() {\n  metrics_api_pod=$(get-pod linkerd-viz metrics-api)\n\n  if [ -z \"${metrics_api_pod// }\" ]; then\n    err 'Metrics-api is not running. Have you installed Linkerd-Viz?'\n    exit 1\n  fi\n}\n\ndev() {\n  while getopts 'p:' opt; do\n    case \"$opt\" in\n      p) DEV_PORT=$OPTARG;;\n      *) ;;\n    esac\n  done\n\n  cd \"$ROOT\"/web/app && yarn webpack serve --port \"$DEV_PORT\" &\n  run ''\n}\n\nbuild() {\n  cd \"$ROOT\"/web/app\n  yarn lingui compile && yarn webpack\n}\n\nget-pod() {\n  if [ $# -ne 2 ]; then\n    echo \"usage: bin/${0##*/} get-pod namespace component-name\" >&2\n    exit 1\n  fi\n\n\n  selector=linkerd.io/control-plane-component=$2\n  if [ \"$1\" = 'linkerd-viz' ]; then\n    selector=\"component=$2\"\n  fi\n\n  kubectl --namespace=\"$1\" get po \\\n    --selector=\"$selector\" \\\n    --field-selector='status.phase==Running' \\\n    -o jsonpath='{.items[*].metadata.name}'\n}\n\nport-forward() {\n  if [ $# -lt 3 ]; then\n    echo \"usage: bin/${0##*/} port-forward namespace component-name local port-number [remote port-number]\" >&2\n    exit 1\n  fi\n\n  port_from=''\n  port_to=''\n  if [ $# -eq 4 ]; then\n    port_from=$3\n    port_to=$4\n  else\n    port_from=$3\n    port_to=$3\n  fi\n\n  nc -z localhost \"$3\" || \\\n    kubectl --namespace=\"$1\" port-forward \"$(get-pod \"$1\" \"$2\")\" \"$port_from:$port_to\"\n}\n\nrun() {\n  # Stop everything in the process group (in the background) whenever the\n  # parent process experiences an error and exits.\n  trap 'exit' INT TERM\n  trap 'kill 0' EXIT\n\n  build\n\n  check-for-linkerd-and-viz && (\n    port-forward linkerd-viz metrics-api 8085 &\n  )\n\n  cd \"$ROOT\"/web && \\\n    ../bin/go-run . --addr=:7777 \"$@\"\n}\n\nsetup() {\n  cd \"$ROOT\"/web/app\n  yarn \"$@\"\n}\n\nfunction test {\n  cd \"$ROOT\"/web/app\n  yarn jest \"$@\"\n}\n\nmain() {\n  setup ''\n  build\n}\n\nmsg() { out \"$*\" >&2 ;}\nerr() { local x=$? ; msg \"$*\" ; return $(( x == 0 ? 1 : x )) ;}\nout() { printf '%s\\n' \"$*\" ;}\n\nif [ ${1:-} ] && declare -F | cut -d' ' -f3 | grep -Fqx -- \"${1:-}\"; then\n  \"$@\"\nelse\n  main \"$@\"\nfi\n"
  },
  {
    "path": "charts/artifacthub-repo-edge.yml",
    "content": "repositoryID: 3f724e34-9d3c-459e-99ba-aa8f09620ff4\nowners:\n  - name: olix0r\n    email: ver@buoyant.io\n  - name: alpeb\n    email: alejandro@buoyant.io\n  - name: adleong\n    email: alex@buoyant.io\n  - name: hawkw\n    email: eliza@buoyant.io\n  - name: kleimkuhler\n    email: kevinl@buoyant.io\n  - name: pothulapati\n    email: tarun@buoyant.io\n"
  },
  {
    "path": "charts/artifacthub-repo-stable.yml",
    "content": "repositoryID: d6ca06cd-6db1-4931-bdc6-c1d5d5d52624\nowners:\n  - name: olix0r\n    email: ver@buoyant.io\n  - name: alpeb\n    email: alejandro@buoyant.io\n  - name: adleong\n    email: alex@buoyant.io\n  - name: hawkw\n    email: eliza@buoyant.io\n  - name: kleimkuhler\n    email: kevinl@buoyant.io\n  - name: pothulapati\n    email: tarun@buoyant.io\n"
  },
  {
    "path": "charts/linkerd-control-plane/.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\nOWNERS\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*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n"
  },
  {
    "path": "charts/linkerd-control-plane/Chart.yaml",
    "content": "apiVersion: \"v2\"\n# this version will be updated by the CI before publishing the Helm tarball\nappVersion: edge-XX.X.X\ndescription: |\n  Linkerd gives you observability, reliability, and security\n  for your microservices — with no code change required.\ntype: application\nhome: https://linkerd.io\nkeywords:\n  - service-mesh\nkubeVersion: \">=1.23.0-0\"\nname: \"linkerd-control-plane\"\nsources:\n  - https://github.com/linkerd/linkerd2/\ndependencies:\n  - name: partials\n    version: 0.1.0\n    repository: file://../partials\n# this version will be updated by the CI before publishing the Helm tarball\nversion: 0.0.0-undefined\nicon: https://linkerd.io/images/logo-only-200h.png\nmaintainers:\n  - name: Linkerd authors\n    email: cncf-linkerd-dev@lists.cncf.io\n    url: https://linkerd.io/\n"
  },
  {
    "path": "charts/linkerd-control-plane/README.md.gotmpl",
    "content": "{{ template \"chart.header\" . }}\n{{ template \"chart.description\" . }}\n\n{{ template \"chart.versionBadge\" . }}\n{{ template \"chart.typeBadge\" . }}\n{{ template \"chart.appVersionBadge\" . }}\n\n{{ template \"chart.homepageLine\" . }}\n\n## Quickstart and documentation\n\nYou can run Linkerd on any Kubernetes cluster in a matter of seconds. See the\n[Linkerd Getting Started Guide][getting-started] for how.\n\nFor more comprehensive documentation, start with the [Linkerd\ndocs][linkerd-docs].\n\n## Prerequisite: linkerd-crds chart\n\nBefore installing this chart, please install the `linkerd-crds` chart, which\ncreates all the CRDs that the components from the current chart require.\n\n## Prerequisite: identity certificates\n\nThe identity component of Linkerd requires setting up a trust anchor\ncertificate, and an issuer certificate with its key. These need to be provided\nto Helm by the user (unlike when using the `linkerd install` CLI which can\ngenerate these automatically). You can provide your own, or follow [these\ninstructions](https://linkerd.io/2/tasks/generate-certificates/) to generate new\nones.\n\nAlternatively, both trust anchor and identity issuer certificates may be\nderived from in-cluster resources. Existing CA (trust anchor) certificates\n**must** live in a `ConfigMap` resource named `linkerd-identity-trust-roots`.\nIssuer certificates **must** live in a `Secret` named\n`linkerd-identity-issuer`. Both resources should exist in the control-plane's\ninstall namespace. In order to use an existing CA, Linkerd needs to be\ninstalled with `identity.externalCA=true`. To use an existing issuer\ncertificate, Linkerd should be installed with\n`identity.issuer.scheme=kubernetes.io/tls`.\n\nA more comprehensive description is in the [automatic certificate rotation\nguide](https://linkerd.io/2.12/tasks/automatically-rotating-control-plane-tls-credentials/#a-note-on-third-party-cert-management-solutions).\n\nNote that the provided certificates must be ECDSA certificates.\n\n## Adding Linkerd's Helm repository\n\nIncluded here for completeness-sake, but should have already been added when\n`linkerd-crds` was installed.\n\n```bash\n# To add the repo for Linkerd edge releases:\nhelm repo add linkerd https://helm.linkerd.io/edge\n```\n\n## Installing the chart\n\nYou must provide the certificates and keys described in the preceding section,\nand the same expiration date you used to generate the Issuer certificate.\n\n```bash\nhelm install linkerd-control-plane -n linkerd \\\n  --set-file identityTrustAnchorsPEM=ca.crt \\\n  --set-file identity.issuer.tls.crtPEM=issuer.crt \\\n  --set-file identity.issuer.tls.keyPEM=issuer.key \\\n  linkerd/linkerd-control-plane\n```\n\nNote that you require to install this chart in the same namespace you installed\nthe `linkerd-crds` chart.\n\n## Setting High-Availability\n\nBesides the default `values.yaml` file, the chart provides a `values-ha.yaml`\nfile that overrides some default values as to set things up under a\nhigh-availability scenario, analogous to the `--ha` option in `linkerd install`.\nValues such as higher number of replicas, higher memory/cpu limits and\naffinities are specified in that file.\n\nYou can get ahold of `values-ha.yaml` by fetching the chart files:\n\n```bash\nhelm fetch --untar linkerd/linkerd-control-plane\n```\n\nThen use the `-f` flag to provide the override file, for example:\n\n```bash\nhelm install linkerd-control-plane -n linkerd \\\n  --set-file identityTrustAnchorsPEM=ca.crt \\\n  --set-file identity.issuer.tls.crtPEM=issuer.crt \\\n  --set-file identity.issuer.tls.keyPEM=issuer.key \\\n  -f linkerd2/values-ha.yaml\n  linkerd/linkerd-control-plane\n```\n\n## Get involved\n\n* Check out Linkerd's source code at [GitHub][linkerd2].\n* Join Linkerd's [user mailing list][linkerd-users], [developer mailing\n  list][linkerd-dev], and [announcements mailing list][linkerd-announce].\n* Follow [@linkerd][twitter] on Twitter.\n* Join the [Linkerd Slack][slack].\n\n[getting-started]: https://linkerd.io/2/getting-started/\n[linkerd2]: https://github.com/linkerd/linkerd2\n[linkerd-announce]: https://lists.cncf.io/g/cncf-linkerd-announce\n[linkerd-dev]: https://lists.cncf.io/g/cncf-linkerd-dev\n[linkerd-docs]: https://linkerd.io/2/overview/\n[linkerd-users]: https://lists.cncf.io/g/cncf-linkerd-users\n[slack]: http://slack.linkerd.io\n[twitter]: https://twitter.com/linkerd\n\n## Extensions for Linkerd\n\nThe current chart installs the core Linkerd components, which grant you\nreliability and security features. Other functionality is available through\nextensions. Check the corresponding docs for each one of the following\nextensions:\n\n* Observability:\n  [Linkerd-viz](https://github.com/linkerd/linkerd2/blob/main/viz/charts/linkerd-viz/values.yaml)\n* Multicluster:\n  [Linkerd-multicluster](https://github.com/linkerd/linkerd2/blob/main/multicluster/charts/linkerd-multicluster/values.yaml)\n\n{{ template \"chart.requirementsSection\" . }}\n\n{{ template \"chart.valuesSection\" . }}\n\n{{ template \"helm-docs.versionFooter\" . }}\n"
  },
  {
    "path": "charts/linkerd-control-plane/templates/NOTES.txt",
    "content": "The Linkerd control plane was successfully installed 🎉\n\nTo help you manage your Linkerd service mesh you can install the Linkerd CLI by running:\n\n  curl -sL https://run.linkerd.io/install | sh\n\nAlternatively, you can download the CLI directly via the Linkerd releases page:\n\n  https://github.com/linkerd/linkerd2/releases/\n\nTo make sure everything works as expected, run the following:\n\n  linkerd check\n\nThe viz extension can be installed by running:\n\n  helm install linkerd-viz linkerd/linkerd-viz\n\nLooking for more? Visit https://linkerd.io/2/getting-started/\n"
  },
  {
    "path": "charts/linkerd-control-plane/templates/config-rbac.yaml",
    "content": "---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  name: ext-namespace-metadata-linkerd-config\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n{{- with .Values.configReaders }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-config-reader\n  namespace: {{ $.Release.Namespace }}\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-ns: {{$.Release.Namespace}}\n    {{- with $.Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  kind: Role\n  name: ext-namespace-metadata-linkerd-config\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n{{- range . }}\n- kind: ServiceAccount\n  name: {{ .name }}\n  namespace: {{ .namespace }}\n{{- end }}\n...\n{{- end }}\n"
  },
  {
    "path": "charts/linkerd-control-plane/templates/config.yaml",
    "content": "---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n  {{- $values := deepCopy .Values }}\n  {{- /*\n    WARNING! All sensitive or private data such as TLS keys must be removed\n    here to avoid it being publicly readable.\n  */ -}}\n  {{- if kindIs \"map\" $values.identity.issuer.tls -}}\n    {{- $_ := unset $values.identity.issuer.tls \"keyPEM\"}}\n  {{- end -}}\n  {{- if kindIs \"map\" $values.profileValidator -}}\n    {{- $_ := unset $values.profileValidator \"keyPEM\"}}\n  {{- end -}}\n  {{- if kindIs \"map\" $values.proxyInjector -}}\n    {{- $_ := unset $values.proxyInjector \"keyPEM\"}}\n  {{- end -}}\n  {{- if kindIs \"map\" $values.policyValidator -}}\n    {{- $_ := unset $values.policyValidator \"keyPEM\"}}\n  {{- end -}}\n  {{- if (empty $values.identityTrustDomain) -}}\n  {{- $_ := set $values \"identityTrustDomain\" $values.clusterDomain}}\n  {{- end -}}\n  {{- $_ := unset $values \"partials\"}}\n  {{- $_ := unset $values \"configs\"}}\n  {{- $_ := unset $values \"stage\"}}\n  {{- toYaml $values | trim | nindent 4 }}\n"
  },
  {
    "path": "charts/linkerd-control-plane/templates/destination-rbac.yaml",
    "content": "---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n  {{- if .Values.enableEndpointSlices }}\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n  {{- end }}\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-{{.Release.Namespace}}-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: {{.Release.Namespace}}\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n{{- include \"partials.image-pull-secrets\" .Values.imagePullSecrets }}\n---\n{{- $host := printf \"linkerd-sp-validator.%s.svc\" .Release.Namespace }}\n{{- $ca := genSelfSignedCert $host (list) (list $host) 365 }}\n{{- if (not .Values.profileValidator.externalSecret) }}\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator-k8s-tls\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\ntype: kubernetes.io/tls\ndata:\n  tls.crt: {{ ternary (b64enc (trim $ca.Cert)) (b64enc (trim .Values.profileValidator.crtPEM)) (empty .Values.profileValidator.crtPEM) }}\n  tls.key: {{ ternary (b64enc (trim $ca.Key)) (b64enc (trim .Values.profileValidator.keyPEM)) (empty .Values.profileValidator.keyPEM) }}\n---\n{{- end }}\n{{- include \"linkerd.webhook.validation\" .Values.profileValidator }}\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  {{- if or (.Values.profileValidator.injectCaFrom) (.Values.profileValidator.injectCaFromSecret) }}\n  annotations:\n  {{- if .Values.profileValidator.injectCaFrom }}\n    cert-manager.io/inject-ca-from: {{ .Values.profileValidator.injectCaFrom }}\n  {{- end }}\n  {{- if .Values.profileValidator.injectCaFromSecret }}\n    cert-manager.io/inject-ca-from-secret: {{ .Values.profileValidator.injectCaFromSecret }}\n  {{- end }}\n  {{- end }}\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    {{- toYaml .Values.profileValidator.namespaceSelector | trim | nindent 4 }}\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: {{ .Release.Namespace }}\n      path: \"/\"\n    {{- if and (empty .Values.profileValidator.injectCaFrom) (empty .Values.profileValidator.injectCaFromSecret) }}\n    caBundle: {{ ternary (b64enc (trim $ca.Cert)) (b64enc (trim .Values.profileValidator.caBundle)) (empty .Values.profileValidator.caBundle) }}\n    {{- end }}\n  failurePolicy: {{.Values.webhookFailurePolicy}}\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\n{{- $host := printf \"linkerd-policy-validator.%s.svc\" .Release.Namespace }}\n{{- $ca := genSelfSignedCert $host (list) (list $host) 365 }}\n{{- if (not .Values.policyValidator.externalSecret) }}\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator-k8s-tls\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\ntype: kubernetes.io/tls\ndata:\n  tls.crt: {{ ternary (b64enc (trim $ca.Cert)) (b64enc (trim .Values.policyValidator.crtPEM)) (empty .Values.policyValidator.crtPEM) }}\n  tls.key: {{ ternary (b64enc (trim $ca.Key)) (b64enc (trim .Values.policyValidator.keyPEM)) (empty .Values.policyValidator.keyPEM) }}\n---\n{{- end }}\n{{- include \"linkerd.webhook.validation\" .Values.policyValidator }}\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  {{- if or (.Values.policyValidator.injectCaFrom) (.Values.policyValidator.injectCaFromSecret) }}\n  annotations:\n  {{- if .Values.policyValidator.injectCaFrom }}\n    cert-manager.io/inject-ca-from: {{ .Values.policyValidator.injectCaFrom }}\n  {{- end }}\n  {{- if .Values.policyValidator.injectCaFromSecret }}\n    cert-manager.io/inject-ca-from-secret: {{ .Values.policyValidator.injectCaFromSecret }}\n  {{- end }}\n  {{- end }}\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    {{- toYaml .Values.policyValidator.namespaceSelector | trim | nindent 4 }}\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: {{ .Release.Namespace }}\n      path: \"/\"\n    {{- if and (empty .Values.policyValidator.injectCaFrom) (empty .Values.policyValidator.injectCaFromSecret) }}\n    caBundle: {{ ternary (b64enc (trim $ca.Cert)) (b64enc (trim .Values.policyValidator.caBundle)) (empty .Values.policyValidator.caBundle) }}\n    {{- end }}\n  failurePolicy: {{.Values.webhookFailurePolicy}}\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: {{.Release.Namespace}}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: {{.Release.Namespace}}\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: {{.Release.Namespace}}\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: {{.Release.Namespace}}\n"
  },
  {
    "path": "charts/linkerd-control-plane/templates/destination.yaml",
    "content": "---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n{{- if .Values.enablePodDisruptionBudget }}\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-dst\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  maxUnavailable: {{ .Values.controller.podDisruptionBudget.maxUnavailable }}\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n{{- end }}\n---\n{{- $tree := deepCopy . }}\n{{ $_ := set $tree.Values.proxy \"workloadKind\" \"deployment\" -}}\n{{ $_ := set $tree.Values.proxy \"component\" \"linkerd-destination\" -}}\n{{ $_ := set $tree.Values.proxy \"waitBeforeExitSeconds\" 0 -}}\n{{- if not (empty .Values.destinationProxyResources) }}\n{{- $c := dig \"cores\" .Values.proxy.cores .Values.destinationProxyResources }}\n{{- $_ := set $tree.Values.proxy \"cores\" $c }}\n{{- $r := merge .Values.destinationProxyResources .Values.proxy.resources }}\n{{- $_ := set $tree.Values.proxy \"resources\" $r }}\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: {{.Values.linkerdVersion}}\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  name: linkerd-destination\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: {{.Values.controllerReplicas}}\n  revisionHistoryLimit: {{.Values.revisionHistoryLimit}}\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: {{.Release.Namespace}}\n      {{- include \"partials.proxy.labels\" $tree.Values.proxy | nindent 6}}\n  {{- if .Values.deploymentStrategy }}\n  strategy:\n    {{- with .Values.deploymentStrategy }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  {{- end }}\n  template:\n    metadata:\n      annotations:\n        checksum/config: {{ include (print $.Template.BasePath \"/destination-rbac.yaml\") . | sha256sum }}\n        {{ include \"partials.annotations.created-by\" . }}\n        {{- include \"partials.proxy.annotations\" . | nindent 8}}\n        {{- with (mergeOverwrite (deepCopy .Values.podAnnotations) .Values.destinationController.podAnnotations) }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: {{.Release.Namespace}}\n        linkerd.io/workload-ns: {{.Release.Namespace}}\n        {{- include \"partials.proxy.labels\" $tree.Values.proxy | nindent 8}}\n        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n    spec:\n      {{- with .Values.runtimeClassName }}\n      runtimeClassName: {{ . | quote }}\n      {{- end }}\n      {{- if .Values.tolerations -}}\n      {{- include \"linkerd.tolerations\" . | nindent 6 }}\n      {{- end -}}\n      {{- include \"linkerd.node-selector\" . | nindent 6 }}\n      {{- $_ := set $tree \"component\" \"destination\" -}}\n      {{- with include \"linkerd.affinity\" $tree }}\n      {{- . | nindent 6 }}\n      {{- end }}\n      automountServiceAccountToken: false\n      containers:\n      {{- $_ := set $tree.Values.proxy \"await\" $tree.Values.proxy.await }}\n      {{- $_ := set $tree.Values.proxy \"loadTrustBundleFromConfigMap\" true }}\n      {{- $_ := set $tree.Values.proxy \"podInboundPorts\" \"8086,8090,8443,9443,9990,9996,9997\" }}\n      {{- $_ := set $tree.Values.proxy \"outboundDiscoveryCacheUnusedTimeout\" \"5s\" }}\n      {{- $_ := set $tree.Values.proxy \"inboundDiscoveryCacheUnusedTimeout\" \"90s\" }}\n      {{- /*\n        The pod needs to accept webhook traffic, and we can't rely on that originating in the\n        cluster network.\n      */}}\n      {{- $_ := set $tree.Values.proxy \"defaultInboundPolicy\" \"all-unauthenticated\" }}\n      {{- $_ := set $tree.Values.proxy \"capabilities\" (dict \"drop\" (list \"ALL\")) }}\n      {{- if not $tree.Values.proxy.nativeSidecar }}\n      - {{- include \"partials.proxy\" $tree | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{- end }}\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace={{.Release.Namespace}}\n        - -outbound-transport-mode={{.Values.proxy.outboundTransportMode}}\n        - -enable-h2-upgrade={{.Values.enableH2Upgrade}}\n        - -log-level={{.Values.controllerLogLevel}}\n        - -log-format={{.Values.controllerLogFormat}}\n        - -enable-endpoint-slices={{.Values.enableEndpointSlices}}\n        - -cluster-domain={{.Values.clusterDomain}}\n        - -identity-trust-domain={{.Values.identityTrustDomain | default .Values.clusterDomain}}\n        - -default-opaque-ports={{.Values.proxy.opaquePorts}}\n        - -enable-ipv6={{not .Values.disableIPv6}}\n        - -enable-pprof={{.Values.enablePprof | default false}}\n        {{- if (.Values.destinationController).meshedHttp2ClientProtobuf }}\n        - --meshed-http2-client-params={{ toJson .Values.destinationController.meshedHttp2ClientProtobuf }}\n        {{- end }}\n        {{- range (.Values.destinationController).additionalArgs }}\n        - {{ . }}\n        {{- end }}\n        {{- range (.Values.destinationController).experimentalArgs }}\n        - {{ . }}\n        {{- end }}\n        {{- if or (.Values.destinationController).additionalEnv (.Values.destinationController).experimentalEnv }}\n        env:\n        {{- with (.Values.destinationController).additionalEnv }}\n        {{- toYaml . | nindent 8 -}}\n        {{- end }}\n        {{- with (.Values.destinationController).experimentalEnv }}\n        {{- toYaml . | nindent 8 -}}\n        {{- end }}\n        {{- end }}\n        {{- include \"partials.linkerd.trace\" . | nindent 8 -}}\n        image: {{.Values.controllerImage}}:{{.Values.controllerImageVersion | default .Values.linkerdVersion}}\n        imagePullPolicy: {{.Values.imagePullPolicy}}\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n          {{- with (.Values.destinationController.livenessProbe).timeoutSeconds }}\n          timeoutSeconds: {{ . }}\n          {{- end }}\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n          {{- with (.Values.destinationController.readinessProbe).timeoutSeconds }}\n          timeoutSeconds: {{ . }}\n          {{- end }}\n        {{- if .Values.destinationResources -}}\n        {{- include \"partials.resources\" .Values.destinationResources | nindent 8 }}\n        {{- end }}\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: {{.Values.controllerUID}}\n          {{- if ge (int .Values.controllerGID) 0 }}\n          runAsGroup: {{.Values.controllerGID}}\n          {{- end }}\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level={{.Values.controllerLogLevel}}\n        - -log-format={{.Values.controllerLogFormat}}\n        - -enable-pprof={{.Values.enablePprof | default false}}\n        {{- if or (.Values.spValidator).additionalEnv (.Values.spValidator).experimentalEnv }}\n        env:\n        {{- with (.Values.spValidator).additionalEnv }}\n        {{- toYaml . | nindent 8 -}}\n        {{- end }}\n        {{- with (.Values.spValidator).experimentalEnv }}\n        {{- toYaml . | nindent 8 -}}\n        {{- end }}\n        {{- end }}\n        image: {{.Values.controllerImage}}:{{.Values.controllerImageVersion | default .Values.linkerdVersion}}\n        imagePullPolicy: {{.Values.imagePullPolicy}}\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          {{- with ((.Values.spValidator).livenessProbe).timeoutSeconds }}\n          timeoutSeconds: {{ . }}\n          {{- end }}\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          {{- with ((.Values.spValidator).readinessProbe).timeoutSeconds }}\n          timeoutSeconds: {{ . }}\n          {{- end }}\n        {{- if .Values.spValidatorResources -}}\n        {{- include \"partials.resources\" .Values.spValidatorResources | nindent 8 }}\n        {{- end }}\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: {{.Values.controllerUID}}\n          {{- if ge (int .Values.controllerGID) 0 }}\n          runAsGroup: {{.Values.controllerGID}}\n          {{- end }}\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr={{ if .Values.disableIPv6 }}0.0.0.0{{ else }}[::]{{ end }}:9990\n        - --control-plane-namespace={{.Release.Namespace}}\n        - --grpc-addr={{ if .Values.disableIPv6 }}0.0.0.0{{ else }}[::]{{ end }}:8090\n        - --server-addr={{ if .Values.disableIPv6 }}0.0.0.0{{ else }}[::]{{ end }}:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks={{.Values.clusterNetworks}}\n        - --identity-domain={{.Values.identityTrustDomain | default .Values.clusterDomain}}\n        - --cluster-domain={{.Values.clusterDomain}}\n        - --default-policy={{.Values.proxy.defaultInboundPolicy}}\n        - --log-level={{.Values.policyController.logLevel | default \"linkerd=info,warn\"}}\n        - --log-format={{.Values.controllerLogFormat}}\n        - --default-opaque-ports={{.Values.proxy.opaquePorts}}\n        - --global-egress-network-namespace={{.Values.egress.globalEgressNetworkNamespace}}\n        {{- if .Values.policyController.probeNetworks }}\n        - --probe-networks={{.Values.policyController.probeNetworks | join \",\"}}\n        {{- end}}\n        {{- range .Values.policyController.additionalArgs }}\n        - {{ . }}\n        {{- end }}\n        {{- range .Values.policyController.experimentalArgs }}\n        - {{ . }}\n        {{- end }}\n        image: {{ .Values.controllerImage }}:{{ .Values.controllerImageVersion | default .Values.linkerdVersion }}\n        imagePullPolicy: {{ .Values.imagePullPolicy }}\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n          {{- with (.Values.policyController.livenessProbe).timeoutSeconds }}\n          timeoutSeconds: {{ . }}\n          {{- end }}\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n          {{- with (.Values.policyController.readinessProbe).timeoutSeconds }}\n          timeoutSeconds: {{ . }}\n          {{- end }}\n        {{- if .Values.policyController.resources }}\n        {{- include \"partials.resources\" .Values.policyController.resources | nindent 8 }}\n        {{- end }}\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: {{.Values.controllerUID}}\n          {{- if ge (int .Values.controllerGID) 0 }}\n          runAsGroup: {{.Values.controllerGID}}\n          {{- end }}\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      {{ if .Values.cniEnabled -}}\n      - {{- include \"partials.network-validator\" $tree | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ else -}}\n      {{- /*\n        The destination controller needs to connect to the Kubernetes API before the proxy is able\n        to proxy requests, so we always skip these connections.\n      */}}\n      {{- $_ := set $tree.Values.proxyInit \"ignoreOutboundPorts\" .Values.proxyInit.kubeAPIServerPorts -}}\n      - {{- include \"partials.proxy-init\" $tree | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ end -}}\n      {{- if $tree.Values.proxy.nativeSidecar }}\n        {{- $_ := set $tree.Values.proxy \"startupProbeInitialDelaySeconds\" 35 }}\n        {{- $_ := set $tree.Values.proxy \"startupProbePeriodSeconds\" 5 }}\n        {{- $_ := set $tree.Values.proxy \"startupProbeFailureThreshold\" 20 }}\n      - {{- include \"partials.proxy\" $tree | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ end -}}\n      {{- if .Values.priorityClassName -}}\n      priorityClassName: {{ .Values.priorityClassName }}\n      {{ end -}}\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - {{- include \"partials.volumes.manual-mount-service-account-token\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ if not .Values.cniEnabled -}}\n      - {{- include \"partials.proxyInit.volumes.xtables\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ end -}}\n      {{if .Values.identity.serviceAccountTokenProjection -}}\n      - {{- include \"partials.proxy.volumes.service-account-token\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ end -}}\n      - {{- include \"partials.proxy.volumes.identity\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ if ((.Values.proxy.tracing).enabled) -}}\n      - {{- include \"partials.proxy.volumes.podinfo\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ end }}\n"
  },
  {
    "path": "charts/linkerd-control-plane/templates/heartbeat-rbac.yaml",
    "content": "{{ if not .Values.disableHeartBeat -}}\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: {{.Release.Namespace}}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: {{.Release.Namespace}}\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n{{- include \"partials.image-pull-secrets\" .Values.imagePullSecrets }}\n{{- end }}\n"
  },
  {
    "path": "charts/linkerd-control-plane/templates/heartbeat.yaml",
    "content": "{{ if not .Values.disableHeartBeat -}}\n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: {{.Values.linkerdVersion}}\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  concurrencyPolicy: Replace\n  {{ if .Values.heartbeatSchedule -}}\n  schedule: \"{{.Values.heartbeatSchedule}}\"\n  {{ else -}}\n  schedule: \"{{ dateInZone \"04 15 * * *\" (now | mustDateModify \"+10m\") \"UTC\"}}\"\n  {{ end -}}\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: {{.Release.Namespace}}\n            {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 12 }}{{- end }}\n          annotations:\n            {{ include \"partials.annotations.created-by\" . }}\n            {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 12 }}{{- end }}\n        spec:\n          {{- if .Values.priorityClassName }}\n          priorityClassName: {{ .Values.priorityClassName }}\n          {{- end -}}\n          {{- with .Values.runtimeClassName }}\n          runtimeClassName: {{ . | quote }}\n          {{- end }}\n          {{- if .Values.tolerations -}}\n          {{- include \"linkerd.tolerations\" . | nindent 10 }}\n          {{- end -}}\n          {{- include \"linkerd.node-selector\" . | nindent 10 }}\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: {{.Values.controllerImage}}:{{.Values.controllerImageVersion | default .Values.linkerdVersion}}\n            imagePullPolicy: {{.Values.imagePullPolicy}}\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            {{- with (.Values.heartbeat).additionalEnv }}\n            {{- toYaml . | nindent 12 -}}\n            {{- end }}\n            {{- with (.Values.heartbeat).experimentalEnv }}\n            {{- toYaml . | nindent 12 -}}\n            {{- end }}\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace={{.Release.Namespace}}\"\n            - \"-log-level={{.Values.controllerLogLevel}}\"\n            - \"-log-format={{.Values.controllerLogFormat}}\"\n            {{- if .Values.prometheusUrl }}\n            - \"-prometheus-url={{.Values.prometheusUrl}}\"\n            {{- else }}\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.{{.Values.clusterDomain}}:9090\"\n            {{- end }}\n            {{- if .Values.heartbeatResources -}}\n            {{- include \"partials.resources\" .Values.heartbeatResources | nindent 12 }}\n            {{- end }}\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: {{.Values.controllerUID}}\n              {{- if ge (int .Values.controllerGID) 0 }}\n              runAsGroup: {{.Values.controllerGID}}\n              {{- end }}\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - {{- include \"partials.volumes.manual-mount-service-account-token\" . | indent 12 | trimPrefix (repeat 11 \" \") }}\n{{- end }}\n"
  },
  {
    "path": "charts/linkerd-control-plane/templates/identity-rbac.yaml",
    "content": "---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-{{.Release.Namespace}}-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: {{.Release.Namespace}}\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n{{- include \"partials.image-pull-secrets\" .Values.imagePullSecrets }}\n"
  },
  {
    "path": "charts/linkerd-control-plane/templates/identity.yaml",
    "content": "{{if .Values.identity -}}\n---\n###\n### Identity Controller Service\n###\n{{ if and (.Values.identity.issuer) (eq .Values.identity.issuer.scheme \"linkerd.io/tls\") -}}\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\ndata:\n  crt.pem: {{b64enc (required \"Please provide the identity issuer certificate\" .Values.identity.issuer.tls.crtPEM | trim)}}\n  key.pem: {{b64enc (required \"Please provide the identity issue private key\" .Values.identity.issuer.tls.keyPEM | trim)}}\n---\n{{- end}}\n{{ if not (.Values.identity.externalCA) -}}\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\ndata:\n  ca-bundle.crt: |-{{.Values.identityTrustAnchorsPEM | trim | nindent 4}}\n---\n{{- end}}\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\n{{- if .Values.enablePodDisruptionBudget }}\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-identity\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  maxUnavailable: {{ .Values.controller.podDisruptionBudget.maxUnavailable }}\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n---\n{{- end }}\n{{- $tree := deepCopy . }}\n{{ $_ := set $tree.Values.proxy \"workloadKind\" \"deployment\" -}}\n{{ $_ := set $tree.Values.proxy \"component\" \"linkerd-identity\" -}}\n{{ $_ := set $tree.Values.proxy \"waitBeforeExitSeconds\" 0 -}}\n{{- if not (empty .Values.identityProxyResources) }}\n{{- $c := dig \"cores\" .Values.proxy.cores .Values.identityProxyResources }}\n{{- $_ := set $tree.Values.proxy \"cores\" $c }}\n{{- $r := merge .Values.identityProxyResources .Values.proxy.resources }}\n{{- $_ := set $tree.Values.proxy \"resources\" $r }}\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: {{.Values.linkerdVersion}}\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  name: linkerd-identity\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: {{.Values.controllerReplicas}}\n  revisionHistoryLimit: {{.Values.revisionHistoryLimit}}\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: {{.Release.Namespace}}\n      {{- include \"partials.proxy.labels\" $tree.Values.proxy | nindent 6}}\n  {{- if .Values.deploymentStrategy }}\n  strategy:\n    {{- with .Values.deploymentStrategy }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  {{- end }}\n  template:\n    metadata:\n      annotations:\n        {{ include \"partials.annotations.created-by\" . }}\n        {{- include \"partials.proxy.annotations\" . | nindent 8}}\n        {{- with (mergeOverwrite (deepCopy .Values.podAnnotations) .Values.identity.podAnnotations) }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: {{.Release.Namespace}}\n        linkerd.io/workload-ns: {{.Release.Namespace}}\n        {{- include \"partials.proxy.labels\" $tree.Values.proxy | nindent 8}}\n        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n    spec:\n      {{- with .Values.runtimeClassName }}\n      runtimeClassName: {{ . | quote }}\n      {{- end }}\n      {{- if .Values.tolerations -}}\n      {{- include \"linkerd.tolerations\" . | nindent 6 }}\n      {{- end -}}\n      {{- include \"linkerd.node-selector\" . | nindent 6 }}\n      {{- $_ := set $tree \"component\" \"identity\" -}}\n      {{- with include \"linkerd.affinity\" $tree }}\n      {{- . | nindent 6 }}\n      {{- end }}\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level={{.Values.controllerLogLevel}}\n        - -log-format={{.Values.controllerLogFormat}}\n        - -controller-namespace={{.Release.Namespace}}\n        - -identity-trust-domain={{.Values.identityTrustDomain | default .Values.clusterDomain}}\n        - -identity-issuance-lifetime={{.Values.identity.issuer.issuanceLifetime}}\n        - -identity-clock-skew-allowance={{.Values.identity.issuer.clockSkewAllowance}}\n        - -identity-scheme={{.Values.identity.issuer.scheme}}\n        - -enable-pprof={{.Values.enablePprof | default false}}\n        - -kube-apiclient-qps={{.Values.identity.kubeAPI.clientQPS}}\n        - -kube-apiclient-burst={{.Values.identity.kubeAPI.clientBurst}}\n        {{- include \"partials.linkerd.trace\" . | nindent 8 -}}\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        {{- with (.Values.identity).additionalEnv }}\n        {{- toYaml . | nindent 8 -}}\n        {{- end }}\n        {{- with (.Values.identity).experimentalEnv }}\n        {{- toYaml . | nindent 8 -}}\n        {{- end }}\n        image: {{.Values.controllerImage}}:{{.Values.controllerImageVersion | default .Values.linkerdVersion}}\n        imagePullPolicy: {{.Values.imagePullPolicy}}\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n          {{- with (.Values.identity.livenessProbe).timeoutSeconds }}\n          timeoutSeconds: {{ . }}\n          {{- end }}\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n          {{- with (.Values.identity.readinessProbe).timeoutSeconds }}\n          timeoutSeconds: {{ . }}\n          {{- end }}\n        {{- if .Values.identityResources -}}\n        {{- include \"partials.resources\" .Values.identityResources | nindent 8 }}\n        {{- end }}\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: {{.Values.controllerUID}}\n          {{- if ge (int .Values.controllerGID) 0 }}\n          runAsGroup: {{.Values.controllerGID}}\n          {{- end }}\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      {{- $_ := set $tree.Values.proxy \"await\" false }}\n      {{- $_ := set $tree.Values.proxy \"loadTrustBundleFromConfigMap\" true }}\n      {{- $_ := set $tree.Values.proxy \"podInboundPorts\" \"8080,9990\" }}\n      {{- $_ := set $tree.Values.proxy \"nativeSidecar\" false }}\n      {{- /*\n        The identity controller cannot discover policies, so we configure it with defaults that\n        enforce TLS on the identity service.\n      */}}\n      {{- $_ := set $tree.Values.proxy \"defaultInboundPolicy\" \"all-unauthenticated\" }}\n      {{- $_ := set $tree.Values.proxy \"requireTLSOnInboundPorts\" \"8080\" }}\n      {{- $_ := set $tree.Values.proxy \"capabilities\" (dict \"drop\" (list \"ALL\")) }}\n      {{- $_ := set $tree.Values.proxy \"outboundDiscoveryCacheUnusedTimeout\" \"5s\" }}\n      {{- $_ := set $tree.Values.proxy \"inboundDiscoveryCacheUnusedTimeout\" \"90s\" }}\n      - {{- include \"partials.proxy\" $tree | indent 8 | trimPrefix (repeat 7 \" \") }}\n      initContainers:\n      {{ if .Values.cniEnabled -}}\n      - {{- include \"partials.network-validator\" $tree | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ else -}}\n      {{- /*\n        The identity controller needs to connect to the Kubernetes API before the proxy is able to\n        proxy requests, so we always skip these connections. The identity controller makes no other\n        outbound connections (so it's not important to persist any other skip ports here)\n      */}}\n      {{- $_ := set $tree.Values.proxyInit \"ignoreOutboundPorts\" .Values.proxyInit.kubeAPIServerPorts -}}\n      - {{- include \"partials.proxy-init\" $tree | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ end -}}\n      {{- if .Values.priorityClassName -}}\n      priorityClassName: {{ .Values.priorityClassName }}\n      {{ end -}}\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - {{- include \"partials.volumes.manual-mount-service-account-token\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ if not .Values.cniEnabled -}}\n      - {{- include \"partials.proxyInit.volumes.xtables\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ end -}}\n      {{ if .Values.identity.serviceAccountTokenProjection -}}\n      - {{- include \"partials.proxy.volumes.service-account-token\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ end -}}\n      - {{- include \"partials.proxy.volumes.identity\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{- if ((.Values.proxy.tracing).enabled) }}\n      - {{- include \"partials.proxy.volumes.podinfo\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{- end }}\n{{end -}}\n"
  },
  {
    "path": "charts/linkerd-control-plane/templates/namespace.yaml",
    "content": "{{- if eq .Release.Service \"CLI\" -}}\n---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: {{ .Release.Namespace }}\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- /* linkerd-init requires extended capabilities and so requires priviledged mode */}}\n    pod-security.kubernetes.io/enforce: {{ ternary \"restricted\" \"privileged\" .Values.cniEnabled }}\n{{ end -}}\n"
  },
  {
    "path": "charts/linkerd-control-plane/templates/podmonitor.yaml",
    "content": "{{- $podMonitor := .Values.podMonitor -}}\n{{- if and $podMonitor.enabled $podMonitor.controller.enabled }}\n---\n###\n### Prometheus Operator PodMonitor for Linkerd control-plane\n###\napiVersion: monitoring.coreos.com/v1\nkind: PodMonitor\nmetadata:\n  name: \"linkerd-controller\"\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-ns: {{ .Release.Namespace }}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n    {{- with .Values.podMonitor.labels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  namespaceSelector: {{ tpl .Values.podMonitor.controller.namespaceSelector . | nindent 4 }}\n  selector:\n    matchLabels: {}\n  podMetricsEndpoints:\n    - interval: {{ $podMonitor.scrapeInterval }}\n      scrapeTimeout: {{ $podMonitor.scrapeTimeout }}\n      relabelings:\n        - sourceLabels:\n            - __meta_kubernetes_pod_container_port_name\n          action: keep\n          regex: .*-admin$\n        - sourceLabels:\n            - __meta_kubernetes_pod_container_port_name\n          action: drop\n          regex: linkerd-admin\n        - sourceLabels:\n            - __meta_kubernetes_pod_container_name\n          action: replace\n          targetLabel: component\n{{- end }}\n{{- if and $podMonitor.enabled $podMonitor.serviceMirror.enabled }}\n---\n###\n### Prometheus Operator PodMonitor for Linkerd Service Mirror (multi-cluster)\n###\napiVersion: monitoring.coreos.com/v1\nkind: PodMonitor\nmetadata:\n  name: \"linkerd-multicluster-controller\"\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-ns: {{ .Release.Namespace }}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n    {{- with .Values.podMonitor.labels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  namespaceSelector:\n    any: true\n  selector:\n    matchLabels: {}\n  podMetricsEndpoints:\n    - interval: {{ $podMonitor.scrapeInterval }}\n      scrapeTimeout: {{ $podMonitor.scrapeTimeout }}\n      relabelings:\n        - sourceLabels:\n            - __meta_kubernetes_pod_label_linkerd_io_control_plane_component\n            - __meta_kubernetes_pod_container_port_name\n          action: keep\n          regex: (linkerd-service-mirror|controller);.*-admin$\n        - sourceLabels:\n            - __meta_kubernetes_pod_container_port_name\n          action: drop\n          regex: linkerd-admin\n        - sourceLabels:\n            - __meta_kubernetes_pod_container_name\n          action: replace\n          targetLabel: component\n{{- end }}\n{{- if and $podMonitor.enabled $podMonitor.proxy.enabled }}\n---\n###\n### Prometheus Operator PodMonitor Linkerd data-plane\n###\napiVersion: monitoring.coreos.com/v1\nkind: PodMonitor\nmetadata:\n  name: \"linkerd-proxy\"\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-ns: {{ .Release.Namespace }}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n    {{- with .Values.podMonitor.labels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  namespaceSelector:\n    any: true\n  selector:\n    matchLabels: {}\n  podMetricsEndpoints:\n    - interval: {{ $podMonitor.scrapeInterval }}\n      scrapeTimeout: {{ $podMonitor.scrapeTimeout }}\n      relabelings:\n        - sourceLabels:\n            - __meta_kubernetes_pod_container_name\n            - __meta_kubernetes_pod_container_port_name\n            - __meta_kubernetes_pod_label_linkerd_io_control_plane_ns\n          action: keep\n          regex: ^linkerd-proxy;linkerd-admin;{{ .Release.Namespace }}$\n        - sourceLabels: [ __meta_kubernetes_namespace ]\n          action: replace\n          targetLabel: namespace\n        - sourceLabels: [ __meta_kubernetes_pod_name ]\n          action: replace\n          targetLabel: pod\n        - sourceLabels: [ __meta_kubernetes_pod_label_linkerd_io_proxy_job ]\n          action: replace\n          targetLabel: k8s_job\n        - action: labeldrop\n          regex: __meta_kubernetes_pod_label_linkerd_io_proxy_job\n        - action: labelmap\n          regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)\n        - action: labeldrop\n          regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)\n        - action: labelmap\n          regex: __meta_kubernetes_pod_label_linkerd_io_(.+)\n        - action: labelmap\n          regex: __meta_kubernetes_pod_label_(.+)\n          replacement: __tmp_pod_label_$1\n        - action: labelmap\n          regex: __tmp_pod_label_linkerd_io_(.+)\n          replacement: __tmp_pod_label_$1\n        - action: labeldrop\n          regex: __tmp_pod_label_linkerd_io_(.+)\n        - action: labelmap\n          regex: __tmp_pod_label_(.+)\n{{- end }}\n"
  },
  {
    "path": "charts/linkerd-control-plane/templates/proxy-injector-rbac.yaml",
    "content": "---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: {{.Release.Namespace}}\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-{{.Release.Namespace}}-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n{{- include \"partials.image-pull-secrets\" .Values.imagePullSecrets }}\n---\n{{- $host := printf \"linkerd-proxy-injector.%s.svc\" .Release.Namespace }}\n{{- $ca := genSelfSignedCert $host (list) (list $host) 365 }}\n{{- if (not .Values.proxyInjector.externalSecret) }}\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector-k8s-tls\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\ntype: kubernetes.io/tls\ndata:\n  tls.crt: {{ ternary (b64enc (trim $ca.Cert)) (b64enc (trim .Values.proxyInjector.crtPEM)) (empty .Values.proxyInjector.crtPEM) }}\n  tls.key: {{ ternary (b64enc (trim $ca.Key)) (b64enc (trim .Values.proxyInjector.keyPEM)) (empty .Values.proxyInjector.keyPEM) }}\n---\n{{- end }}\n{{- include \"linkerd.webhook.validation\" .Values.proxyInjector }}\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  {{- if or (.Values.proxyInjector.injectCaFrom) (.Values.proxyInjector.injectCaFromSecret) }}\n  annotations:\n  {{- if .Values.proxyInjector.injectCaFrom }}\n    cert-manager.io/inject-ca-from: {{ .Values.proxyInjector.injectCaFrom }}\n  {{- end }}\n  {{- if .Values.proxyInjector.injectCaFromSecret }}\n    cert-manager.io/inject-ca-from-secret: {{ .Values.proxyInjector.injectCaFromSecret }}\n  {{- end }}\n  {{- end }}\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    {{- toYaml .Values.proxyInjector.namespaceSelector | trim | nindent 4 }}\n  objectSelector:\n    {{- toYaml .Values.proxyInjector.objectSelector | trim | nindent 4 }}\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: {{ .Release.Namespace }}\n      path: \"/\"\n    {{- if and (empty .Values.proxyInjector.injectCaFrom) (empty .Values.proxyInjector.injectCaFromSecret) }}\n    caBundle: {{ ternary (b64enc (trim $ca.Cert)) (b64enc (trim .Values.proxyInjector.caBundle)) (empty .Values.proxyInjector.caBundle) }}\n    {{- end }}\n  failurePolicy: {{.Values.webhookFailurePolicy}}\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: {{ .Values.proxyInjector.timeoutSeconds | default 10 }}\n"
  },
  {
    "path": "charts/linkerd-control-plane/templates/proxy-injector.yaml",
    "content": "---\n###\n### Proxy Injector\n###\n{{- $tree := deepCopy . }}\n{{ $_ := set $tree.Values.proxy \"workloadKind\" \"deployment\" -}}\n{{ $_ := set $tree.Values.proxy \"component\" \"linkerd-proxy-injector\" -}}\n{{ $_ := set $tree.Values.proxy \"waitBeforeExitSeconds\" 0 -}}\n{{- if not (empty .Values.proxyInjectorProxyResources) }}\n{{- $c := dig \"cores\" .Values.proxy.cores .Values.proxyInjectorProxyResources }}\n{{- $_ := set $tree.Values.proxy \"cores\" $c }}\n{{- $r := merge .Values.proxyInjectorProxyResources .Values.proxy.resources }}\n{{- $_ := set $tree.Values.proxy \"resources\" $r }}\n{{- end }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: {{.Values.linkerdVersion}}\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  name: linkerd-proxy-injector\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: {{.Values.controllerReplicas}}\n  revisionHistoryLimit: {{.Values.revisionHistoryLimit}}\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  {{- if .Values.deploymentStrategy }}\n  strategy:\n    {{- with .Values.deploymentStrategy }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  {{- end }}\n  template:\n    metadata:\n      annotations:\n        checksum/config: {{ include (print $.Template.BasePath \"/proxy-injector-rbac.yaml\") . | sha256sum }}\n        {{ include \"partials.annotations.created-by\" . }}\n        {{- include \"partials.proxy.annotations\" . | nindent 8}}\n        {{- with (mergeOverwrite (deepCopy .Values.podAnnotations) .Values.identity.podAnnotations) }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: {{.Release.Namespace}}\n        linkerd.io/workload-ns: {{.Release.Namespace}}\n        {{- include \"partials.proxy.labels\" $tree.Values.proxy | nindent 8}}\n        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n    spec:\n      {{- with .Values.runtimeClassName }}\n      runtimeClassName: {{ . | quote }}\n      {{- end }}\n      {{- if .Values.tolerations -}}\n      {{- include \"linkerd.tolerations\" . | nindent 6 }}\n      {{- end -}}\n      {{- include \"linkerd.node-selector\" . | nindent 6 }}\n      {{- $_ := set $tree \"component\" \"proxy-injector\" -}}\n      {{- with include \"linkerd.affinity\" $tree }}\n      {{- . | nindent 6 }}\n      {{- end }}\n      automountServiceAccountToken: false\n      containers:\n      {{- $_ := set $tree.Values.proxy \"await\" $tree.Values.proxy.await }}\n      {{- $_ := set $tree.Values.proxy \"loadTrustBundleFromConfigMap\" true }}\n      {{- $_ := set $tree.Values.proxy \"podInboundPorts\" \"8443,9995\" }}\n      {{- /*\n        The pod needs to accept webhook traffic, and we can't rely on that originating in the\n        cluster network.\n      */}}\n      {{- $_ := set $tree.Values.proxy \"defaultInboundPolicy\" \"all-unauthenticated\" }}\n      {{- $_ := set $tree.Values.proxy \"capabilities\" (dict \"drop\" (list \"ALL\")) }}\n      {{- $_ := set $tree.Values.proxy \"outboundDiscoveryCacheUnusedTimeout\" \"5s\" }}\n      {{- $_ := set $tree.Values.proxy \"inboundDiscoveryCacheUnusedTimeout\" \"90s\" }}\n      {{- if not $tree.Values.proxy.nativeSidecar }}\n      - {{- include \"partials.proxy\" $tree | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{- end }}\n      - args:\n        - proxy-injector\n        - -log-level={{.Values.controllerLogLevel}}\n        - -log-format={{.Values.controllerLogFormat}}\n        - -linkerd-namespace={{.Release.Namespace}}\n        - -enable-pprof={{.Values.enablePprof | default false}}\n        {{- if or (.Values.proxyInjector).additionalEnv (.Values.proxyInjector).experimentalEnv }}\n        env:\n        {{- with (.Values.proxyInjector).additionalEnv }}\n        {{- toYaml . | nindent 8 -}}\n        {{- end }}\n        {{- with (.Values.proxyInjector).experimentalEnv }}\n        {{- toYaml . | nindent 8 -}}\n        {{- end }}\n        {{- end }}\n        image: {{.Values.controllerImage}}:{{.Values.controllerImageVersion | default .Values.linkerdVersion}}\n        imagePullPolicy: {{.Values.imagePullPolicy}}\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n          {{- with (.Values.proxyInjector.livenessProbe).timeoutSeconds }}\n          timeoutSeconds: {{ . }}\n          {{- end }}\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n          {{- with (.Values.proxyInjector.readinessProbe).timeoutSeconds }}\n          timeoutSeconds: {{ . }}\n          {{- end }}\n        {{- if .Values.proxyInjectorResources -}}\n        {{- include \"partials.resources\" .Values.proxyInjectorResources | nindent 8 }}\n        {{- end }}\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: {{.Values.controllerUID}}\n          {{- if ge (int .Values.controllerGID) 0 }}\n          runAsGroup: {{.Values.controllerGID}}\n          {{- end }}\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      {{ if .Values.cniEnabled -}}\n      - {{- include \"partials.network-validator\" $tree | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ else -}}\n      {{- /*\n        The controller needs to connect to the Kubernetes API. There's no reason\n        to put the proxy in the way of that.\n      */}}\n      {{- $_ := set $tree.Values.proxyInit \"ignoreOutboundPorts\" .Values.proxyInit.kubeAPIServerPorts -}}\n      - {{- include \"partials.proxy-init\" $tree | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ end -}}\n      {{- if $tree.Values.proxy.nativeSidecar }}\n        {{- $_ := set $tree.Values.proxy \"startupProbeInitialDelaySeconds\" 35 }}\n        {{- $_ := set $tree.Values.proxy \"startupProbePeriodSeconds\" 5 }}\n        {{- $_ := set $tree.Values.proxy \"startupProbeFailureThreshold\" 20 }}\n      - {{- include \"partials.proxy\" $tree | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ end -}}\n      {{- if .Values.priorityClassName -}}\n      priorityClassName: {{ .Values.priorityClassName }}\n      {{ end -}}\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - {{- include \"partials.volumes.manual-mount-service-account-token\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ if not .Values.cniEnabled -}}\n      - {{- include \"partials.proxyInit.volumes.xtables\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ end -}}\n      {{if .Values.identity.serviceAccountTokenProjection -}}\n      - {{- include \"partials.proxy.volumes.service-account-token\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ end -}}\n      - {{- include \"partials.proxy.volumes.identity\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{if ((.Values.proxy.tracing).enabled) -}}\n      - {{- include \"partials.proxy.volumes.podinfo\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{ end }}\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n{{- if .Values.enablePodDisruptionBudget }}\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  maxUnavailable: {{ .Values.controller.podDisruptionBudget.maxUnavailable }}\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n{{- end }}\n"
  },
  {
    "path": "charts/linkerd-control-plane/templates/psp.yaml",
    "content": "{{ if .Values.enablePSP -}}\n---\n###\n### Control Plane PSP\n###\napiVersion: policy/v1beta1\nkind: PodSecurityPolicy\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-control-plane\n  annotations:\n    seccomp.security.alpha.kubernetes.io/allowedProfileNames: \"runtime/default\"\n  labels:\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nspec:\n  {{- if or .Values.proxyInit.closeWaitTimeoutSecs .Values.proxyInit.runAsRoot }}\n  allowPrivilegeEscalation: true\n  {{- else }}\n  allowPrivilegeEscalation: false\n  {{- end }}\n  readOnlyRootFilesystem: true\n  {{- if empty .Values.cniEnabled }}\n  allowedCapabilities:\n  - NET_ADMIN\n  - NET_RAW\n  {{- end}}\n  requiredDropCapabilities:\n  - ALL\n  hostNetwork: false\n  hostIPC: false\n  hostPID: false\n  seLinux:\n    rule: RunAsAny\n  runAsUser:\n    {{- if .Values.cniEnabled }}\n    rule: MustRunAsNonRoot\n    {{- else }}\n    rule: RunAsAny\n    {{- end }}\n  runAsGroup:\n    {{- if .Values.cniEnabled }}\n    rule: MustRunAs\n    ranges:\n    - min: 1000\n      max: 999999\n    {{- else }}\n    rule: RunAsAny\n    {{- end }}\n  supplementalGroups:\n    rule: MustRunAs\n    ranges:\n    {{- if .Values.cniEnabled }}\n    - min: 10001\n      max: 65535\n    {{- else }}\n    - min: 1\n      max: 65535\n    {{- end }}\n  fsGroup:\n    rule: MustRunAs\n    ranges:\n    {{- if .Values.cniEnabled }}\n    - min: 10001\n      max: 65535\n    {{- else }}\n    - min: 1\n      max: 65535\n    {{- end }}\n  volumes:\n  - configMap\n  - emptyDir\n  - secret\n  - projected\n  - downwardAPI\n  - persistentVolumeClaim\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-psp\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: ['policy', 'extensions']\n  resources: ['podsecuritypolicies']\n  verbs: ['use']\n  resourceNames:\n  - linkerd-{{.Release.Namespace}}-control-plane\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-psp\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  kind: Role\n  name: linkerd-psp\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: {{.Release.Namespace}}\n{{ if not .Values.disableHeartBeat -}}\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: {{.Release.Namespace}}\n{{ end -}}\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: {{.Release.Namespace}}\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: {{.Release.Namespace}}\n{{ end -}}\n"
  },
  {
    "path": "charts/linkerd-control-plane/values-ha.yaml",
    "content": "# This values.yaml file contains the values needed to enable HA mode.\n# Usage:\n#   helm install -f values-ha.yaml\n\n# -- Create PodDisruptionBudget resources for each control plane workload\nenablePodDisruptionBudget: true\n\ncontroller:\n  # -- sets pod disruption budget parameter for all deployments\n  podDisruptionBudget:\n    # -- Maximum number of pods that can be unavailable during disruption\n    maxUnavailable: 1\n\n# -- Specify a deployment strategy for each control plane workload\ndeploymentStrategy:\n  rollingUpdate:\n    maxUnavailable: 1\n    maxSurge: 25%\n\n# -- add PodAntiAffinity to each control plane workload\nenablePodAntiAffinity: true\n\n# nodeAffinity:\n\n# proxy configuration\nproxy:\n  resources:\n    cpu:\n      request: 100m\n    memory:\n      limit: 250Mi\n      request: 20Mi\n\n# controller configuration\ncontrollerReplicas: 3\ncontrollerResources: &controller_resources\n  cpu: &controller_resources_cpu\n    limit: \"\"\n    request: 100m\n  memory:\n    limit: 250Mi\n    request: 50Mi\ndestinationResources: *controller_resources\n\n# identity configuration\nidentityResources:\n  cpu: *controller_resources_cpu\n  memory:\n    limit: 250Mi\n    request: 10Mi\n\n# heartbeat configuration\nheartbeatResources: *controller_resources\n\n# proxy injector configuration\nproxyInjectorResources: *controller_resources\nwebhookFailurePolicy: Fail\n\n# service profile validator configuration\nspValidatorResources: *controller_resources\n\n# flag for linkerd check\nhighAvailability: true\n"
  },
  {
    "path": "charts/linkerd-control-plane/values.yaml",
    "content": "# Default values for linkerd.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\n\n# -- Kubernetes DNS Domain name to use\nclusterDomain: cluster.local\n\n# -- The cluster networks for which service discovery is performed. This should\n# include the pod and service networks, but need not include the node network.\n#\n# By default, all IPv4 private networks and all accepted IPv6 ULAs are\n# specified so that resolution works in typical Kubernetes environments.\nclusterNetworks: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n# -- Docker image pull policy\nimagePullPolicy: IfNotPresent\n# -- Specifies the number of old ReplicaSets to retain to allow rollback.\nrevisionHistoryLimit: 10\n# -- Log level for the control plane components\ncontrollerLogLevel: info\n# -- Log format for the control plane components\ncontrollerLogFormat: plain\n# -- control plane version. See Proxy section for proxy version\nlinkerdVersion: linkerdVersionValue\n# -- default kubernetes deployment strategy\ndeploymentStrategy:\n  rollingUpdate:\n    maxUnavailable: 25%\n    maxSurge: 25%\n# -- enables the use of EndpointSlice informers for the destination service;\n# enableEndpointSlices should be set to true only if EndpointSlice K8s feature\n# gate is on\nenableEndpointSlices: true\n# -- enables pod anti affinity creation on deployments for high availability\nenablePodAntiAffinity: false\n# -- enables the use of pprof endpoints on control plane component's admin\n# servers\nenablePprof: false\n# -- enables the creation of pod disruption budgets for control plane components\nenablePodDisruptionBudget: false\n# -- disables routing IPv6 traffic in addition to IPv4 traffic through the\n# proxy (IPv6 routing only available as of proxy-init v2.3.0 and linkerd-cni\n# v1.4.0)\ndisableIPv6: true\n\ncontroller:\n  # -- sets pod disruption budget parameter for all deployments\n  podDisruptionBudget:\n    # -- Maximum number of pods that can be unavailable during disruption\n    maxUnavailable: 1\n  # Configures tracing in the controllers and how traces are exported\n  tracing:\n    # -- Enables trace collection and export in the proxy\n    enabled: false\n    collector:\n      # -- The collector endpoint to send traces to. Required if tracing is\n      # enabled. If this is unset and `proxy.tracing.collector.endpoint` is set,\n      # that endpoint will be re-used here.\n      endpoint: \"\"\n# -- enabling this omits the NET_ADMIN capability in the PSP\n# and the proxy-init container when injecting the proxy;\n# requires the linkerd-cni plugin to already be installed\ncniEnabled: false\n# -- Trust root certificate (ECDSA). It must be provided during install.\nidentityTrustAnchorsPEM: |\n# -- Trust domain used for identity\n# @default -- clusterDomain\nidentityTrustDomain: \"\"\nkubeAPI: &kubeapi\n  # -- Maximum QPS sent to the kube-apiserver before throttling.\n  # See [token bucket rate limiter\n  # implementation](https://github.com/kubernetes/client-go/blob/v12.0.0/util/flowcontrol/throttle.go)\n  clientQPS: 100\n  # -- Burst value over clientQPS\n  clientBurst: 200\n# -- Additional annotations to add to all pods\npodAnnotations: {}\n# -- Additional labels to add to all pods\npodLabels: {}\n# -- Labels to apply to all resources\ncommonLabels: {}\n# -- Kubernetes priorityClassName for the Linkerd Pods\npriorityClassName: \"\"\n# -- Runtime Class Name for all the pods\nruntimeClassName: \"\"\n\n# policy controller configuration\npolicyController:\n  # `image` has been removed.\n\n  # -- Log level for the policy controller\n  logLevel: info\n\n  # -- The networks from which probes are performed.\n  #\n  # By default, all networks are allowed so that all probes are authorized.\n  probeNetworks:\n    - 0.0.0.0/0\n    - \"::/0\"\n\n  # -- policy controller resource requests & limits\n  resources:\n    cpu:\n      # -- Maximum amount of CPU units that the policy controller can use\n      limit: \"\"\n      # -- Amount of CPU units that the policy controller requests\n      request: \"\"\n    memory:\n      # -- Maximum amount of memory that the policy controller can use\n      limit: \"\"\n      # -- Maximum amount of memory that the policy controller requests\n      request: \"\"\n    ephemeral-storage:\n      # -- Maximum amount of ephemeral storage that the policy controller can use\n      limit: \"\"\n      # -- Amount of ephemeral storage that the policy controller requests\n      request: \"\"\n\n  livenessProbe:\n    timeoutSeconds: 1\n  readinessProbe:\n    timeoutSeconds: 1\n\n# proxy configuration\nproxy:\n  # -- Enable service profiles for non-Kubernetes services\n  enableExternalProfiles: false\n  # -- Maximum time allowed for the proxy to establish an outbound TCP\n  # connection\n  outboundConnectTimeout: 1000ms\n  # -- Maximum time allowed for the proxy to establish an inbound TCP\n  # connection\n  inboundConnectTimeout: 100ms\n  # -- Maximum time allowed before an unused outbound discovery result\n  # is evicted from the cache\n  outboundDiscoveryCacheUnusedTimeout: \"5s\"\n  # -- Maximum time allowed before an unused inbound discovery result\n  # is evicted from the cache\n  inboundDiscoveryCacheUnusedTimeout: \"90s\"\n  # -- When set to true, disables the protocol detection timeout on the\n  # outbound side of the proxy by setting it to a very high value\n  disableOutboundProtocolDetectTimeout: false\n  # -- When set to true, disables the protocol detection timeout on the inbound\n  # side of the proxy by setting it to a very high value\n  disableInboundProtocolDetectTimeout: false\n  image:\n    # -- Docker image for the proxy\n    name: cr.l5d.io/linkerd/proxy\n    # -- Pull policy for the proxy container image\n    # @default -- imagePullPolicy\n    pullPolicy: \"\"\n    # -- Tag for the proxy container image\n    # @default -- linkerdVersion\n    version: \"\"\n  # -- Enables the proxy's /shutdown admin endpoint\n  enableShutdownEndpoint: false\n  # -- Log level for the proxy\n  logLevel: warn,linkerd=info,hickory=error\n  # -- Log format (`plain` or `json`) for the proxy\n  logFormat: plain\n  # -- (`off` or `insecure`) If set to `off`, will prevent the proxy from\n  # logging HTTP headers. If set to `insecure`, HTTP headers may be logged\n  # verbatim. Note that setting this to `insecure` is not alone sufficient to\n  # log HTTP headers; the proxy logLevel must also be set to debug.\n  logHTTPHeaders: \"off\"\n  ports:\n    # -- Admin port for the proxy container\n    admin: 4191\n    # -- Control port for the proxy container\n    control: 4190\n    # -- Inbound port for the proxy container\n    inbound: 4143\n    # -- Outbound port for the proxy container\n    outbound: 4140\n\n  # -- Deprecated: use runtime.workers.minimum\n  cores:\n\n  runtime:\n    # -- Worker threadpool configuration. The minimum will be automatically\n    # derived from workload proxy CPU requests, when they are configured by\n    # annotation. A cluster-level maximum may be configured here (and a\n    # workload-level annotation is supported as well).\n    workers:\n      # -- Maximum number of worker threads that the proxy can use, by ratio of\n      # the number of available CPUs. A value of 1.0 allocates a worker thread\n      # for all available CPUs. A value of 0.1 allocates a worker thread for\n      # 10% of the available CPUs.\n      maximumCPURatio:\n\n      # -- Configures a lower bound on the number of worker threads that the\n      # proxy can use. When maximumCPURatio is not set, this value\n      # is used.\n      minimum: 1\n\n  resources:\n    # -- CPU configuration, when specified globally in Helm values, should be\n    # kept in sync with the above runtime.workers.minimum configuration. The\n    # minimum should reflect _at least_ the CPU request. When a limit is set,\n    # the minimum should match the limit (and the maximumCPURatio should be\n    # unset).\n    cpu:\n      # -- Maximum amount of CPU units that the proxy can use\n      limit: \"\"\n      # -- Amount of CPU units that the proxy requests\n      request: \"\"\n\n    memory:\n      # -- Maximum amount of memory that the proxy can use\n      limit: \"\"\n      # -- Maximum amount of memory that the proxy requests\n      request: \"\"\n\n    ephemeral-storage:\n      # -- Maximum amount of ephemeral storage that the proxy can use\n      limit: \"\"\n      # -- Amount of ephemeral storage that the proxy requests\n      request: \"\"\n\n  # -- User id under which the proxy runs\n  uid: 2102\n  # -- (int) Optional customisation of the group id under which the proxy runs (the group ID will be omitted if lower than 0)\n  gid: -1\n  # -- Optional custom security context for the proxy container.\n  securityContext: {}\n\n  # -- If set the injected proxy sidecars in the data plane will stay alive for\n  # at least the given period before receiving the SIGTERM signal from\n  # Kubernetes but no longer than the pod's `terminationGracePeriodSeconds`.\n  # See [Lifecycle\n  # hooks](https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks)\n  # for more info on container lifecycle hooks.\n  waitBeforeExitSeconds: 0\n  # -- If set, the application container will not start until the proxy is\n  # ready\n  await: true\n  requireIdentityOnInboundPorts: \"\"\n  # -- Default set of opaque ports\n  # - SMTP (25,587) server-first\n  # - MYSQL (3306) server-first\n  # - Galera (4444) server-first\n  # - PostgreSQL (5432) server-first\n  # - Redis (6379) server-first\n  # - ElasticSearch (9300) server-first\n  # - Memcached (11211) clients do not issue any preamble, which breaks detection\n  opaquePorts: \"25,587,3306,4444,5432,6379,9300,11211\"\n  # -- Grace period for graceful proxy shutdowns. If this timeout elapses before all open connections have completed, the proxy will terminate forcefully, closing any remaining connections.\n  shutdownGracePeriod: \"\"\n  # -- The default allow policy to use when no `Server` selects a pod.  One of: \"all-authenticated\",\n  # \"all-unauthenticated\", \"cluster-authenticated\", \"cluster-unauthenticated\", \"deny\", \"audit\"\n  # @default -- \"all-unauthenticated\"\n  defaultInboundPolicy: \"all-unauthenticated\"\n  # -- Configures the outbound transport mode. Valid values are \"transport-header\" and \"transparent\"\n  outboundTransportMode: transport-header\n  # -- Enable KEP-753 native sidecars\n  # This is a beta feature. It requires Kubernetes >= 1.29.\n  # If enabled, .proxy.waitBeforeExitSeconds should not be used.\n  nativeSidecar: false\n  # -- Native sidecar proxy startup probe parameters.\n  # -- LivenessProbe timeout and delay configuration\n  livenessProbe:\n    initialDelaySeconds: 10\n    timeoutSeconds: 1\n  # -- ReadinessProbe timeout and delay configuration\n  readinessProbe:\n    initialDelaySeconds: 2\n    timeoutSeconds: 1\n  startupProbe:\n    initialDelaySeconds: 0\n    periodSeconds: 1\n    failureThreshold: 120\n  # Configures general properties of the proxy's control plane clients.\n  control:\n    # Configures limits on API response streams.\n    streams:\n      # -- The timeout for the first update from the control plane.\n      initialTimeout: \"3s\"\n      # -- The timeout between consecutive updates from the control plane.\n      idleTimeout: \"5m\"\n      # -- The maximum duration for a response stream (i.e. before it will be\n      # reinitialized).\n      lifetime: \"1h\"\n  # Configures proxy metrics\n  metrics:\n      # -- Whether or not to export hostname labels in outbound request metrics.\n      hostnameLabels: false\n  # Configures tracing in the proxy and how they are exported\n  tracing:\n    # -- Enables trace collection and export in the proxy\n    enabled: false\n    traceServiceName: linkerd-proxy\n    # -- Additional labels to add to the traces emitted by the proxy.\n    # These should generally not be set globally, and instead overridden on\n    # individual workloads via `resource.opentelemetry.io/<label>` annotations.\n    labels:\n      k8s.pod.ip: \"$(_pod_ip)\"\n      k8s.pod.uid: \"$(_pod_uid)\"\n      k8s.container.name: \"$(_pod_containerName)\"\n    collector:\n      # -- The collector endpoint to send traces to.\n      endpoint: \"\"\n      # -- The identity of the collector in the linkerd mesh.\n      meshIdentity:\n        # -- Mesh identity name for the trace collector. This should be set to\n        # the name of the service account attached to the collector. If\n        # there's no explicitly set service account, this will probably be\n        # \"default\".\n        serviceAccountName: \"\"\n        # -- Mesh identity namespace for the trace collector. This should be\n        # set to the namespace of the service account attached to the\n        # collector. If there's no explicitly set service account, this is the\n        # namespace of the collector.\n        namespace: \"\"\n  inbound:\n    server:\n      http2:\n        # -- The interval at which PINGs are issued to remote HTTP/2 clients.\n        keepAliveInterval: \"10s\"\n        # -- The timeout within which keep-alive PINGs must be acknowledged on inbound HTTP/2 connections.\n        keepAliveTimeout: \"3s\"\n  outbound:\n    server:\n      http2:\n        # -- The interval at which PINGs are issued to local application HTTP/2 clients.\n        keepAliveInterval: \"10s\"\n        # -- The timeout within which keep-alive PINGs must be acknowledged on outbound HTTP/2 connections.\n        keepAliveTimeout: \"3s\"\n\n# proxy-init configuration\nproxyInit:\n  # -- Variant of iptables that will be used to configure routing. Currently,\n  # proxy-init can be run either in 'nft' or in 'legacy' mode. The mode will\n  # control which utility binary will be called. The host must support\n  # whichever mode will be used\n  iptablesMode: \"nft\"\n  # -- Default set of inbound ports to skip via iptables\n  # - Galera (4567,4568)\n  ignoreInboundPorts: \"4567,4568\"\n  # -- Default set of outbound ports to skip via iptables\n  # - Galera (4567,4568)\n  ignoreOutboundPorts: \"4567,4568\"\n  # -- Default set of ports to skip via iptables for control plane\n  # components so they can communicate with the Kubernetes API Server\n  kubeAPIServerPorts: \"443,6443\"\n  # -- Comma-separated list of subnets in valid CIDR format that should be skipped by the proxy\n  skipSubnets: \"\"\n  # -- Log level for the proxy-init\n  # @default -- info\n  logLevel: \"\"\n  # -- Log format (`plain` or `json`) for the proxy-init\n  # @default -- plain\n  logFormat: \"\"\n  # -- Changes the default value for the nf_conntrack_tcp_timeout_close_wait\n  # kernel parameter. If used, runAsRoot needs to be true.\n  closeWaitTimeoutSecs: 0\n  # -- Privileged mode allows the container processes to inherit all security\n  # capabilities and bypass any security limitations enforced by the kubelet.\n  # When used with 'runAsRoot: true', the container will behave exactly as if\n  # it was running as root on the host. May escape cgroup limits and see other\n  # processes and devices on the host.\n  # @default -- false\n  privileged: false\n  # -- Allow overriding the runAsNonRoot behaviour (<https://github.com/linkerd/linkerd2/issues/7308>)\n  runAsRoot: false\n  # -- This value is used only if runAsRoot is false; otherwise runAsUser will be 0\n  runAsUser: 65534\n  # -- This value is used only if runAsRoot is false; otherwise runAsGroup will be 0\n  runAsGroup: 65534\n  xtMountPath:\n    mountPath: /run\n    name: linkerd-proxy-init-xtables-lock\n\n# network validator configuration\n# This runs on a host that uses iptables to reroute network traffic. The validator\n# ensures that iptables is correctly routing requests before we start linkerd.\nnetworkValidator:\n  # -- Log level for the network-validator\n  # @default -- debug\n  logLevel: debug\n  # -- Log format (`plain` or `json`) for network-validator\n  # @default -- plain\n  logFormat: plain\n  # -- Address to which the network-validator will attempt to connect. This should be an IP\n  # that the cluster is expected to be able to reach but a port it should not, e.g., a public IP\n  # for public clusters and a private IP for air-gapped clusters with a port like 20001.\n  # If empty, defaults to 1.1.1.1:20001 and [fd00::1]:20001 for IPv4 and IPv6 respectively.\n  connectAddr: \"\"\n  # -- Address to which network-validator listens to requests from itself.\n  # If empty, defaults to 0.0.0.0:4140 and [::]:4140 for IPv4 and IPv6 respectively.\n  listenAddr: \"\"\n  # -- Timeout before network-validator fails to validate the pod's network connectivity\n  timeout: \"10s\"\n  # -- The securityContext in the network-validator pod spec\n  securityContext:\n    allowPrivilegeEscalation: false\n    capabilities:\n      drop:\n      - ALL\n    readOnlyRootFilesystem: true\n    runAsGroup: 65534\n    runAsNonRoot: true\n    runAsUser: 65534\n    seccompProfile:\n      type: RuntimeDefault\n\n# -- For Private docker registries, authentication is needed.\n#  Registry secrets are applied to the respective service accounts\nimagePullSecrets: []\n# - name: my-private-docker-registry-login-secret\n\n# -- Allow proxies to perform transparent HTTP/2 upgrading\nenableH2Upgrade: true\n\n# -- Add a PSP resource and bind it to the control plane ServiceAccounts. Note\n# PSP has been deprecated since k8s v1.21\nenablePSP: false\n\n# -- Failure policy for the proxy injector\nwebhookFailurePolicy: Ignore\n\n# controllerImage -- Docker image for the destination and identity components\ncontrollerImage: cr.l5d.io/linkerd/controller\n# -- Optionally allow a specific container image Tag (or SHA) to be specified for the controllerImage.\ncontrollerImageVersion: \"\"\n\n# -- Number of replicas for each control plane pod\ncontrollerReplicas: 1\n# -- User ID for the control plane components\ncontrollerUID: 2103\n# -- (int) Optional customisation of the group ID for the control plane components (the group ID will be omitted if lower than 0)\ncontrollerGID: -1\n\n# destination configuration\n# set resources for the sp-validator and its linkerd proxy respectively\n# see proxy.resources for details.\n# destinationResources -- CPU, Memory and Ephemeral Storage resources required by destination (see `proxy.resources` for sub-fields)\n#destinationResources:\n# destinationProxyResources -- CPU, Memory and Ephemeral Storage resources required by proxy injected into destination pod (see `proxy.resources` for sub-fields)\n#destinationProxyResources:\n\ndestinationController:\n  meshedHttp2ClientProtobuf:\n    keep_alive:\n      interval:\n        seconds: 10\n      timeout:\n        seconds: 3\n      while_idle: true\n  # -- Additional annotations to add to destination pods\n  podAnnotations: {}\n  livenessProbe:\n    timeoutSeconds: 1\n  readinessProbe:\n    timeoutSeconds: 1\n\n# debug configuration\ndebugContainer:\n  image:\n    # -- Docker image for the debug container\n    name: cr.l5d.io/linkerd/debug\n    # -- Pull policy for the debug container image\n    # @default -- imagePullPolicy\n    pullPolicy: \"\"\n    # -- Tag for the debug container image\n    # @default -- linkerdVersion\n    version: \"\"\n\nidentity:\n  # -- If the linkerd-identity-trust-roots ConfigMap has already been created\n  externalCA: false\n\n  # -- Use [Service Account token Volume projection](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#service-account-token-volume-projection) for pod validation instead of the default token\n  serviceAccountTokenProjection: true\n\n  issuer:\n    scheme: linkerd.io/tls\n\n    # -- Amount of time to allow for clock skew within a Linkerd cluster\n    clockSkewAllowance: 20s\n\n    # -- Amount of time for which the Identity issuer should certify identity\n    issuanceLifetime: 24h0m0s\n\n    # -- Which scheme is used for the identity issuer secret format\n    tls:\n      # -- Issuer certificate (ECDSA). It must be provided during install.\n      crtPEM: |\n\n      # -- Key for the issuer certificate (ECDSA). It must be provided during\n      # install\n      keyPEM: |\n\n  kubeAPI: *kubeapi\n\n  # -- Additional annotations to add to identity pods\n  podAnnotations: {}\n\n  livenessProbe:\n    timeoutSeconds: 1\n  readinessProbe:\n    timeoutSeconds: 1\n\n# -|- CPU, Memory and Ephemeral Storage resources required by the identity controller (see `proxy.resources` for sub-fields)\n#identityResources:\n# -|- CPU, Memory and Ephemeral Storage resources required by proxy injected into identity pod (see `proxy.resources` for sub-fields)\n#identityProxyResources:\n\n# heartbeat configuration\n# disableHeartBeat -- Set to true to not start the heartbeat cronjob\ndisableHeartBeat: false\n# -- Config for the heartbeat cronjob\n# heartbeatSchedule: \"0 0 * * *\"\n\n# proxy injector configuration\nproxyInjector:\n  # -- Timeout in seconds before the API Server cancels a request to the proxy\n  # injector. If timeout is exceeded, the webhookfailurePolicy is used.\n  timeoutSeconds: 10\n  # -- Do not create a secret resource for the proxyInjector webhook.\n  # If this is set to `true`, the value `proxyInjector.caBundle` must be set\n  # or the ca bundle must injected with cert-manager ca injector using\n  # `proxyInjector.injectCaFrom` or `proxyInjector.injectCaFromSecret` (see below).\n  externalSecret: false\n\n  # -- Namespace selector used by admission webhook.\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n\n  # -- Object selector used by admission webhook.\n  objectSelector:\n    matchExpressions:\n    - key: linkerd.io/control-plane-component\n      operator: DoesNotExist\n    - key: linkerd.io/cni-resource\n      operator: DoesNotExist\n\n  # -- Certificate for the proxy injector. If not provided and not using an external secret\n  # then Helm will generate one.\n  crtPEM: |\n\n  # -- Certificate key for the proxy injector. If not provided and not using an external secret\n  # then Helm will generate one.\n  keyPEM: |\n\n  # -- Bundle of CA certificates for proxy injector.\n  # If not provided nor injected with cert-manager,\n  # then Helm will use the certificate generated for `proxyInjector.crtPEM`.\n  # If `proxyInjector.externalSecret` is set to true, this value, injectCaFrom, or\n  # injectCaFromSecret must be set, as no certificate will be generated.\n  # See the cert-manager [CA Injector Docs](https://cert-manager.io/docs/concepts/ca-injector) for more information.\n  caBundle: |\n\n  # -- Inject the CA bundle from a cert-manager Certificate.\n  # See the cert-manager [CA Injector Docs](https://cert-manager.io/docs/concepts/ca-injector/#injecting-ca-data-from-a-certificate-resource)\n  # for more information.\n  injectCaFrom: \"\"\n\n  # -- Inject the CA bundle from a Secret.\n  # If set, the `cert-manager.io/inject-ca-from-secret` annotation will be added to the webhook.\n  # The Secret must have the CA Bundle stored in the `ca.crt` key and have\n  # the `cert-manager.io/allow-direct-injection` annotation set to `true`.\n  # See the cert-manager [CA Injector Docs](https://cert-manager.io/docs/concepts/ca-injector/#injecting-ca-data-from-a-secret-resource)\n  # for more information.\n  injectCaFromSecret: \"\"\n\n  # -- Additional annotations to add to proxy-injector pods\n  podAnnotations: {}\n\n  livenessProbe:\n    timeoutSeconds: 1\n  readinessProbe:\n    timeoutSeconds: 1\n\n# -|- CPU, Memory and Ephemeral Storage resources required by the proxy injector (see\n#`proxy.resources` for sub-fields)\n#proxyInjectorResources:\n#-|- CPU, Memory and Ephemeral Storage resources required by proxy injected into the proxy injector\n#pod (see `proxy.resources` for sub-fields)\n#proxyInjectorProxyResources:\n\n# service profile validator configuration\nprofileValidator:\n  # -- Do not create a secret resource for the profileValidator webhook.\n  # If this is set to `true`, the value `proxyInjector.caBundle` must be set\n  # or the ca bundle must injected with cert-manager ca injector using\n  # `proxyInjector.injectCaFrom` or `proxyInjector.injectCaFromSecret` (see below).\n  externalSecret: false\n\n  # -- Namespace selector used by admission webhook\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n\n  # -- Certificate for the service profile validator. If not provided and not using an external secret\n  # then Helm will generate one.\n  crtPEM: |\n\n  # -- Certificate key for the service profile validator. If not provided and not using an external secret\n  # then Helm will generate one.\n  keyPEM: |\n\n  # -- Bundle of CA certificates for proxy injector.\n  # If not provided nor injected with cert-manager,\n  # then Helm will use the certificate generated for `profileValidator.crtPEM`.\n  # If `profileValidator.externalSecret` is set to true, this value, injectCaFrom, or\n  # injectCaFromSecret must be set, as no certificate will be generated.\n  # See the cert-manager [CA Injector Docs](https://cert-manager.io/docs/concepts/ca-injector) for more information.\n  caBundle: |\n\n  # -- Inject the CA bundle from a cert-manager Certificate.\n  # See the cert-manager [CA Injector Docs](https://cert-manager.io/docs/concepts/ca-injector/#injecting-ca-data-from-a-certificate-resource)\n  # for more information.\n  injectCaFrom: \"\"\n\n  # -- Inject the CA bundle from a Secret.\n  # If set, the `cert-manager.io/inject-ca-from-secret` annotation will be added to the webhook.\n  # The Secret must have the CA Bundle stored in the `ca.crt` key and have\n  # the `cert-manager.io/allow-direct-injection` annotation set to `true`.\n  # See the cert-manager [CA Injector Docs](https://cert-manager.io/docs/concepts/ca-injector/#injecting-ca-data-from-a-secret-resource)\n  # for more information.\n  injectCaFromSecret: \"\"\n\n# policy validator configuration\npolicyValidator:\n  # -- Do not create a secret resource for the policyValidator webhook.\n  # If this is set to `true`, the value `policyValidator.caBundle` must be set\n  # or the ca bundle must injected with cert-manager ca injector using\n  # `policyValidator.injectCaFrom` or `policyValidator.injectCaFromSecret` (see below).\n  externalSecret: false\n\n  # -- Namespace selector used by admission webhook\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n\n  # -- Certificate for the policy validator. If not provided and not using an external secret\n  # then Helm will generate one.\n  crtPEM: |\n\n  # -- Certificate key for the policy validator. If not provided and not using an external secret\n  # then Helm will generate one.\n  keyPEM: |\n\n  # -- Bundle of CA certificates for proxy injector.\n  # If not provided nor injected with cert-manager,\n  # then Helm will use the certificate generated for `policyValidator.crtPEM`.\n  # If `policyValidator.externalSecret` is set to true, this value, injectCaFrom, or\n  # injectCaFromSecret must be set, as no certificate will be generated.\n  # See the cert-manager [CA Injector Docs](https://cert-manager.io/docs/concepts/ca-injector) for more information.\n  caBundle: |\n\n  # -- Inject the CA bundle from a cert-manager Certificate.\n  # See the cert-manager [CA Injector Docs](https://cert-manager.io/docs/concepts/ca-injector/#injecting-ca-data-from-a-certificate-resource)\n  # for more information.\n  injectCaFrom: \"\"\n\n  # -- Inject the CA bundle from a Secret.\n  # If set, the `cert-manager.io/inject-ca-from-secret` annotation will be added to the webhook.\n  # The Secret must have the CA Bundle stored in the `ca.crt` key and have\n  # the `cert-manager.io/allow-direct-injection` annotation set to `true`.\n  # See the cert-manager [CA Injector Docs](https://cert-manager.io/docs/concepts/ca-injector/#injecting-ca-data-from-a-secret-resource)\n  # for more information.\n  injectCaFromSecret: \"\"\n\n# -- NodeSelector section, See the [K8S\n# documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector)\n# for more information\nnodeSelector:\n  kubernetes.io/os: linux\n\n# -- SP validator configuration\nspValidator:\n  livenessProbe:\n    timeoutSeconds: 1\n  readinessProbe:\n    timeoutSeconds: 1\n\n# -|- CPU, Memory and Ephemeral Storage resources required by the SP validator (see\n#`proxy.resources` for sub-fields)\n#spValidatorResources:\n\n# -|- Tolerations section, See the\n# [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/)\n# for more information\n#tolerations:\n\n# -|- NodeAffinity section, See the\n# [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity)\n# for more information\n#nodeAffinity:\n\n# -- url of external prometheus instance (used for the heartbeat)\nprometheusUrl: \"\"\n\n# Prometheus Operator PodMonitor configuration\npodMonitor:\n  # -- Enables the creation of Prometheus Operator [PodMonitor](https://prometheus-operator.dev/docs/operator/api/#monitoring.coreos.com/v1.PodMonitor)\n  enabled: false\n  # -- Interval at which metrics should be scraped\n  scrapeInterval: 10s\n  # -- Iimeout after which the scrape is ended\n  scrapeTimeout: 10s\n  # -- Labels to apply to all pod Monitors\n  labels: {}\n  controller:\n    # -- Enables the creation of PodMonitor for the control-plane\n    enabled: true\n    # -- Selector to select which namespaces the Endpoints objects are discovered from\n    namespaceSelector: |\n      matchNames:\n        - {{ .Release.Namespace }}\n        - linkerd-viz\n  serviceMirror:\n    # -- Enables the creation of PodMonitor for the Service Mirror component\n    enabled: true\n  proxy:\n    # -- Enables the creation of PodMonitor for the data-plane\n    enabled: true\n\n\n# Egress related configuration\negress:\n  # -- The namespace that is used to store egress configuration that affects all client workloads in the cluster\n  globalEgressNetworkNamespace: linkerd-egress\n\n# -- List of additional service accounts with read access to the linkerd-config\n# ConfigMap\nconfigReaders:\n#- name:\n#  namespace:\n"
  },
  {
    "path": "charts/linkerd-crds/.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\nOWNERS\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*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n"
  },
  {
    "path": "charts/linkerd-crds/Chart.yaml",
    "content": "apiVersion: \"v2\"\ndescription: |\n  Linkerd gives you observability, reliability, and security\n  for your microservices — with no code change required.\ntype: application\nhome: https://linkerd.io\nkeywords:\n  - service-mesh\nkubeVersion: \">=1.23.0-0\"\nname: \"linkerd-crds\"\nsources:\n  - https://github.com/linkerd/linkerd2/\ndependencies:\n  - name: partials\n    version: 0.1.0\n    repository: file://../partials\n# this version will be updated by the CI before publishing the Helm tarball\nversion: 0.0.0-undefined\nicon: https://linkerd.io/images/logo-only-200h.png\nmaintainers:\n  - name: Linkerd authors\n    email: cncf-linkerd-dev@lists.cncf.io\n    url: https://linkerd.io/\n"
  },
  {
    "path": "charts/linkerd-crds/README.md.gotmpl",
    "content": "{{ template \"chart.header\" . }}\n{{ template \"chart.description\" . }}\n\n{{ template \"chart.versionBadge\" . }}\n{{ template \"chart.typeBadge\" . }}\n{{ template \"chart.appVersionBadge\" . }}\n\n{{ template \"chart.homepageLine\" . }}\n\n## Quickstart and documentation\n\nYou can run Linkerd on any Kubernetes cluster in a matter of seconds. See the\n[Linkerd Getting Started Guide][getting-started] for how.\n\nFor more comprehensive documentation, start with the [Linkerd\ndocs][linkerd-docs].\n\n## Adding Linkerd's Helm repository\n\n```bash\n# To add the repo for Linkerd edge releases:\nhelm repo add linkerd https://helm.linkerd.io/edge\n```\n\n## Installing the linkerd-crds chart\n\nThis installs the `linkerd-crds` chart, which only persists the CRDs that\nLinkerd requires.\n\nAfter installing this chart, you need then to install the\n`linkerd-control-plane` chart in the same namespace, which provides all the\nlinkerd core control components.\n\n```bash\nhelm install linkerd-crds -n linkerd --create-namespace linkerd/linkerd-crds\n```\n\n## Get involved\n\n* Check out Linkerd's source code at [GitHub][linkerd2].\n* Join Linkerd's [user mailing list][linkerd-users], [developer mailing\n  list][linkerd-dev], and [announcements mailing list][linkerd-announce].\n* Follow [@linkerd][twitter] on Twitter.\n* Join the [Linkerd Slack][slack].\n\n[getting-started]: https://linkerd.io/2/getting-started/\n[linkerd2]: https://github.com/linkerd/linkerd2\n[linkerd-announce]: https://lists.cncf.io/g/cncf-linkerd-announce\n[linkerd-dev]: https://lists.cncf.io/g/cncf-linkerd-dev\n[linkerd-docs]: https://linkerd.io/2/overview/\n[linkerd-users]: https://lists.cncf.io/g/cncf-linkerd-users\n[slack]: http://slack.linkerd.io\n[twitter]: https://twitter.com/linkerd\n\n{{ template \"chart.requirementsSection\" . }}\n\n{{ template \"chart.valuesSection\" . }}\n\n{{ template \"helm-docs.versionFooter\" . }}\n"
  },
  {
    "path": "charts/linkerd-crds/templates/NOTES.txt",
    "content": "The linkerd-crds chart was successfully installed 🎉\n\nTo complete the linkerd core installation, please now proceed to install the\nlinkerd-control-plane chart in the {{ .Release.Namespace }} namespace.\n\nLooking for more? Visit https://linkerd.io/2/getting-started/\n"
  },
  {
    "path": "charts/linkerd-crds/templates/gateway.networking.k8s.io_grpcroutes.yaml",
    "content": "{{- if (ternary .Values.enableHttpRoutes .Values.installGatewayAPI (hasKey .Values \"enableHttpRoutes\")) }}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997\n    gateway.networking.k8s.io/bundle-version: v1.1.1\n    gateway.networking.k8s.io/channel: experimental\n    {{ include \"partials.annotations.created-by\" . }}\n    helm.sh/resource-policy: keep\n  labels:\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n  creationTimestamp: null\n  name: grpcroutes.gateway.networking.k8s.io\nspec:\n  group: gateway.networking.k8s.io\n  names:\n    categories:\n    - gateway-api\n    kind: GRPCRoute\n    listKind: GRPCRouteList\n    plural: grpcroutes\n    singular: grpcroute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1\n    schema:\n      openAPIV3Schema:\n        description: |-\n          GRPCRoute provides a way to route gRPC requests. This includes the capability\n          to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests will be routed.\n\n\n          GRPCRoute falls under extended support within the Gateway API. Within the\n          following specification, the word \"MUST\" indicates that an implementation\n          supporting GRPCRoute must conform to the indicated requirement, but an\n          implementation not supporting this route type need not follow the requirement\n          unless explicitly indicated.\n\n\n          Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST\n          accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via\n          ALPN. If the implementation does not support this, then it MUST set the\n          \"Accepted\" condition to \"False\" for the affected listener with a reason of\n          \"UnsupportedProtocol\".  Implementations MAY also accept HTTP/2 connections\n          with an upgrade from HTTP/1.\n\n\n          Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST\n          support HTTP/2 over cleartext TCP (h2c,\n          https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial\n          upgrade from HTTP/1.1, i.e. with prior knowledge\n          (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation\n          does not support this, then it MUST set the \"Accepted\" condition to \"False\"\n          for the affected listener with a reason of \"UnsupportedProtocol\".\n          Implementations MAY also accept HTTP/2 connections with an upgrade from\n          HTTP/1, i.e. without prior knowledge.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of GRPCRoute.\n            properties:\n              hostnames:\n                description: |-\n                  Hostnames defines a set of hostnames to match against the GRPC\n                  Host header to select a GRPCRoute to process the request. This matches\n                  the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                  1. IPs are not allowed.\n                  2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                     label MUST appear by itself as the first label.\n\n\n                  If a hostname is specified by both the Listener and GRPCRoute, there\n                  MUST be at least one intersecting hostname for the GRPCRoute to be\n                  attached to the Listener. For example:\n\n\n                  * A Listener with `test.example.com` as the hostname matches GRPCRoutes\n                    that have either not specified any hostnames, or have specified at\n                    least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches GRPCRoutes\n                    that have either not specified any hostnames or have specified at least\n                    one hostname that matches the Listener hostname. For example,\n                    `test.example.com` and `*.example.com` would both match. On the other\n                    hand, `example.com` and `test.example.net` would not match.\n\n\n                  Hostnames that are prefixed with a wildcard label (`*.`) are interpreted\n                  as a suffix match. That means that a match for `*.example.com` would match\n                  both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\n\n                  If both the Listener and GRPCRoute have specified hostnames, any\n                  GRPCRoute hostnames that do not match the Listener hostname MUST be\n                  ignored. For example, if a Listener specified `*.example.com`, and the\n                  GRPCRoute specified `test.example.com` and `test.example.net`,\n                  `test.example.net` MUST NOT be considered for a match.\n\n\n                  If both the Listener and GRPCRoute have specified hostnames, and none\n                  match with the criteria above, then the GRPCRoute MUST NOT be accepted by\n                  the implementation. The implementation MUST raise an 'Accepted' Condition\n                  with a status of `False` in the corresponding RouteParentStatus.\n\n\n                  If a Route (A) of type HTTPRoute or GRPCRoute is attached to a\n                  Listener and that listener already has another Route (B) of the other\n                  type attached and the intersection of the hostnames of A and B is\n                  non-empty, then the implementation MUST accept exactly one of these two\n                  routes, determined by the following criteria, in order:\n\n\n                  * The oldest Route based on creation timestamp.\n                  * The Route appearing first in alphabetical order by\n                    \"{namespace}/{name}\".\n\n\n                  The rejected Route MUST raise an 'Accepted' condition with a status of\n                  'False' in the corresponding RouteParentStatus.\n\n\n                  Support: Core\n                items:\n                  description: |-\n                    Hostname is the fully qualified domain name of a network host. This matches\n                    the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                     1. IPs are not allowed.\n                     2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                        label must appear by itself as the first label.\n\n\n                    Hostname can be \"precise\" which is a domain name without the terminating\n                    dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a\n                    domain name prefixed with a single wildcard label (e.g. `*.example.com`).\n\n\n                    Note that as per RFC1035 and RFC1123, a *label* must consist of lower case\n                    alphanumeric characters or '-', and must start and end with an alphanumeric\n                    character. No other punctuation is allowed.\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                description: Rules are a list of GRPC matchers, filters and actions.\n                items:\n                  description: |-\n                    GRPCRouteRule defines the semantics for matching a gRPC request based on\n                    conditions (matches), processing it (filters), and forwarding the request to\n                    an API object (backendRefs).\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent.\n\n\n                        Failure behavior here depends on how many BackendRefs are specified and\n                        how many are invalid.\n\n\n                        If *all* entries in BackendRefs are invalid, and there are also no filters\n                        specified in this route rule, *all* traffic which matches this rule MUST\n                        receive an `UNAVAILABLE` status.\n\n\n                        See the GRPCBackendRef definition for the rules about what makes a single\n                        GRPCBackendRef invalid.\n\n\n                        When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for\n                        requests that would have otherwise been routed to an invalid backend. If\n                        multiple backends are specified, and some are invalid, the proportion of\n                        requests that would otherwise have been routed to an invalid backend\n                        MUST receive an `UNAVAILABLE` status.\n\n\n                        For example, if two backends are specified with equal weights, and one is\n                        invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status.\n                        Implementations may choose how that 50 percent is determined.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Core\n                      items:\n                        description: |-\n                          GRPCBackendRef defines how a GRPCRoute forwards a gRPC request.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n                        properties:\n                          filters:\n                            description: |-\n                              Filters defined at this level MUST be executed if and only if the\n                              request is being forwarded to the backend defined here.\n\n\n                              Support: Implementation-specific (For broader support of filters, use the\n                              Filters field in GRPCRouteRule.)\n                            items:\n                              description: |-\n                                GRPCRouteFilter defines processing steps that must be completed during the\n                                request or response lifecycle. GRPCRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway implementations. Some\n                                examples include request or response modification, implementing\n                                authentication strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type of the filter.\n                              properties:\n                                extensionRef:\n                                  description: |-\n                                    ExtensionRef is an optional, implementation-specific extension to the\n                                    \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                                    \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                                    extended filters.\n\n\n                                    Support: Implementation-specific\n\n\n                                    This filter can be used multiple times within the same rule.\n                                  properties:\n                                    group:\n                                      description: |-\n                                        Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                        When unspecified or empty string, core API group is inferred.\n                                      maxLength: 253\n                                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    kind:\n                                      description: Kind is kind of the referent. For\n                                        example \"HTTPRoute\" or \"Service\".\n                                      maxLength: 63\n                                      minLength: 1\n                                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                      type: string\n                                    name:\n                                      description: Name is the name of the referent.\n                                      maxLength: 253\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - group\n                                  - kind\n                                  - name\n                                  type: object\n                                requestHeaderModifier:\n                                  description: |-\n                                    RequestHeaderModifier defines a schema for a filter that modifies request\n                                    headers.\n\n\n                                    Support: Core\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestMirror:\n                                  description: |-\n                                    RequestMirror defines a schema for a filter that mirrors requests.\n                                    Requests are sent to the specified destination, but responses from\n                                    that destination are ignored.\n\n\n                                    This filter can be used multiple times within the same rule. Note that\n                                    not all implementations will be able to support mirroring to multiple\n                                    backends.\n\n\n                                    Support: Extended\n                                  properties:\n                                    backendRef:\n                                      description: |-\n                                        BackendRef references a resource where mirrored requests are sent.\n\n\n                                        Mirrored requests must be sent only to a single destination endpoint\n                                        within this BackendRef, irrespective of how many endpoints are present\n                                        within this BackendRef.\n\n\n                                        If the referent cannot be found, this BackendRef is invalid and must be\n                                        dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                        condition on the Route status is set to `status: False` and not configure\n                                        this backend in the underlying implementation.\n\n\n                                        If there is a cross-namespace reference to an *existing* object\n                                        that is not allowed by a ReferenceGrant, the controller must ensure the\n                                        \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                        with the \"RefNotPermitted\" reason and not configure this backend in the\n                                        underlying implementation.\n\n\n                                        In either error case, the Message of the `ResolvedRefs` Condition\n                                        should be used to provide more detail about the problem.\n\n\n                                        Support: Extended for Kubernetes Service\n\n\n                                        Support: Implementation-specific for any other resource\n                                      properties:\n                                        group:\n                                          default: \"\"\n                                          description: |-\n                                            Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                            When unspecified or empty string, core API group is inferred.\n                                          maxLength: 253\n                                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                          type: string\n                                        kind:\n                                          default: Service\n                                          description: |-\n                                            Kind is the Kubernetes resource kind of the referent. For example\n                                            \"Service\".\n\n\n                                            Defaults to \"Service\" when not specified.\n\n\n                                            ExternalName services can refer to CNAME DNS records that may live\n                                            outside of the cluster and as such are difficult to reason about in\n                                            terms of conformance. They also may not be safe to forward to (see\n                                            CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                            support ExternalName Services.\n\n\n                                            Support: Core (Services with a type other than ExternalName)\n\n\n                                            Support: Implementation-specific (Services with type ExternalName)\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                          type: string\n                                        name:\n                                          description: Name is the name of the referent.\n                                          maxLength: 253\n                                          minLength: 1\n                                          type: string\n                                        namespace:\n                                          description: |-\n                                            Namespace is the namespace of the backend. When unspecified, the local\n                                            namespace is inferred.\n\n\n                                            Note that when a namespace different than the local namespace is specified,\n                                            a ReferenceGrant object is required in the referent namespace to allow that\n                                            namespace's owner to accept the reference. See the ReferenceGrant\n                                            documentation for details.\n\n\n                                            Support: Core\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                          type: string\n                                        port:\n                                          description: |-\n                                            Port specifies the destination port number to use for this resource.\n                                            Port is required when the referent is a Kubernetes Service. In this\n                                            case, the port number is the service port number, not the target port.\n                                            For other resources, destination port might be derived from the referent\n                                            resource or this field.\n                                          format: int32\n                                          maximum: 65535\n                                          minimum: 1\n                                          type: integer\n                                      required:\n                                      - name\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: Must have port for Service reference\n                                        rule: '(size(self.group) == 0 && self.kind\n                                          == ''Service'') ? has(self.port) : true'\n                                  required:\n                                  - backendRef\n                                  type: object\n                                responseHeaderModifier:\n                                  description: |-\n                                    ResponseHeaderModifier defines a schema for a filter that modifies response\n                                    headers.\n\n\n                                    Support: Extended\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: |+\n                                    Type identifies the type of filter to apply. As with other API fields,\n                                    types are classified into three conformance levels:\n\n\n                                    - Core: Filter types and their corresponding configuration defined by\n                                      \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                      implementations supporting GRPCRoute MUST support core filters.\n\n\n                                    - Extended: Filter types and their corresponding configuration defined by\n                                      \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                      are encouraged to support extended filters.\n\n\n                                    - Implementation-specific: Filters that are defined and supported by specific vendors.\n                                      In the future, filters showing convergence in behavior across multiple\n                                      implementations will be considered for inclusion in extended or core\n                                      conformance levels. Filter-specific configuration for such filters\n                                      is specified using the ExtensionRef field. `Type` MUST be set to\n                                      \"ExtensionRef\" for custom filters.\n\n\n                                    Implementers are encouraged to define custom implementation types to\n                                    extend the core API with implementation-specific behavior.\n\n\n                                    If a reference to a custom filter type cannot be resolved, the filter\n                                    MUST NOT be skipped. Instead, requests that would have been processed by\n                                    that filter MUST receive a HTTP error response.\n\n\n                                  enum:\n                                  - ResponseHeaderModifier\n                                  - RequestHeaderModifier\n                                  - RequestMirror\n                                  - ExtensionRef\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                              x-kubernetes-validations:\n                              - message: filter.requestHeaderModifier must be nil\n                                  if the filter.type is not RequestHeaderModifier\n                                rule: '!(has(self.requestHeaderModifier) && self.type\n                                  != ''RequestHeaderModifier'')'\n                              - message: filter.requestHeaderModifier must be specified\n                                  for RequestHeaderModifier filter.type\n                                rule: '!(!has(self.requestHeaderModifier) && self.type\n                                  == ''RequestHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be nil\n                                  if the filter.type is not ResponseHeaderModifier\n                                rule: '!(has(self.responseHeaderModifier) && self.type\n                                  != ''ResponseHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be specified\n                                  for ResponseHeaderModifier filter.type\n                                rule: '!(!has(self.responseHeaderModifier) && self.type\n                                  == ''ResponseHeaderModifier'')'\n                              - message: filter.requestMirror must be nil if the filter.type\n                                  is not RequestMirror\n                                rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                              - message: filter.requestMirror must be specified for\n                                  RequestMirror filter.type\n                                rule: '!(!has(self.requestMirror) && self.type ==\n                                  ''RequestMirror'')'\n                              - message: filter.extensionRef must be nil if the filter.type\n                                  is not ExtensionRef\n                                rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                              - message: filter.extensionRef must be specified for\n                                  ExtensionRef filter.type\n                                rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-validations:\n                            - message: RequestHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                                <= 1\n                            - message: ResponseHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                                <= 1\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: |-\n                        Filters define the filters that are applied to requests that match\n                        this rule.\n\n\n                        The effects of ordering of multiple behaviors are currently unspecified.\n                        This can change in the future based on feedback during the alpha stage.\n\n\n                        Conformance-levels at this level are defined based on the type of filter:\n\n\n                        - ALL core filters MUST be supported by all implementations that support\n                          GRPCRoute.\n                        - Implementers are encouraged to support extended filters.\n                        - Implementation-specific custom filters have no API guarantees across\n                          implementations.\n\n\n                        Specifying the same filter multiple times is not supported unless explicitly\n                        indicated in the filter.\n\n\n                        If an implementation can not support a combination of filters, it must clearly\n                        document that limitation. In cases where incompatible or unsupported\n                        filters are specified and cause the `Accepted` condition to be set to status\n                        `False`, implementations may use the `IncompatibleFilters` reason to specify\n                        this configuration error.\n\n\n                        Support: Core\n                      items:\n                        description: |-\n                          GRPCRouteFilter defines processing steps that must be completed during the\n                          request or response lifecycle. GRPCRouteFilters are meant as an extension\n                          point to express processing that may be done in Gateway implementations. Some\n                          examples include request or response modification, implementing\n                          authentication strategies, rate-limiting, and traffic shaping. API\n                          guarantee/conformance is defined based on the type of the filter.\n                        properties:\n                          extensionRef:\n                            description: |-\n                              ExtensionRef is an optional, implementation-specific extension to the\n                              \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                              \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                              extended filters.\n\n\n                              Support: Implementation-specific\n\n\n                              This filter can be used multiple times within the same rule.\n                            properties:\n                              group:\n                                description: |-\n                                  Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                  When unspecified or empty string, core API group is inferred.\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                description: Kind is kind of the referent. For example\n                                  \"HTTPRoute\" or \"Service\".\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: Name is the name of the referent.\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                            required:\n                            - group\n                            - kind\n                            - name\n                            type: object\n                          requestHeaderModifier:\n                            description: |-\n                              RequestHeaderModifier defines a schema for a filter that modifies request\n                              headers.\n\n\n                              Support: Core\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestMirror:\n                            description: |-\n                              RequestMirror defines a schema for a filter that mirrors requests.\n                              Requests are sent to the specified destination, but responses from\n                              that destination are ignored.\n\n\n                              This filter can be used multiple times within the same rule. Note that\n                              not all implementations will be able to support mirroring to multiple\n                              backends.\n\n\n                              Support: Extended\n                            properties:\n                              backendRef:\n                                description: |-\n                                  BackendRef references a resource where mirrored requests are sent.\n\n\n                                  Mirrored requests must be sent only to a single destination endpoint\n                                  within this BackendRef, irrespective of how many endpoints are present\n                                  within this BackendRef.\n\n\n                                  If the referent cannot be found, this BackendRef is invalid and must be\n                                  dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                  condition on the Route status is set to `status: False` and not configure\n                                  this backend in the underlying implementation.\n\n\n                                  If there is a cross-namespace reference to an *existing* object\n                                  that is not allowed by a ReferenceGrant, the controller must ensure the\n                                  \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                  with the \"RefNotPermitted\" reason and not configure this backend in the\n                                  underlying implementation.\n\n\n                                  In either error case, the Message of the `ResolvedRefs` Condition\n                                  should be used to provide more detail about the problem.\n\n\n                                  Support: Extended for Kubernetes Service\n\n\n                                  Support: Implementation-specific for any other resource\n                                properties:\n                                  group:\n                                    default: \"\"\n                                    description: |-\n                                      Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                      When unspecified or empty string, core API group is inferred.\n                                    maxLength: 253\n                                    pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                    type: string\n                                  kind:\n                                    default: Service\n                                    description: |-\n                                      Kind is the Kubernetes resource kind of the referent. For example\n                                      \"Service\".\n\n\n                                      Defaults to \"Service\" when not specified.\n\n\n                                      ExternalName services can refer to CNAME DNS records that may live\n                                      outside of the cluster and as such are difficult to reason about in\n                                      terms of conformance. They also may not be safe to forward to (see\n                                      CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                      support ExternalName Services.\n\n\n                                      Support: Core (Services with a type other than ExternalName)\n\n\n                                      Support: Implementation-specific (Services with type ExternalName)\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                    type: string\n                                  name:\n                                    description: Name is the name of the referent.\n                                    maxLength: 253\n                                    minLength: 1\n                                    type: string\n                                  namespace:\n                                    description: |-\n                                      Namespace is the namespace of the backend. When unspecified, the local\n                                      namespace is inferred.\n\n\n                                      Note that when a namespace different than the local namespace is specified,\n                                      a ReferenceGrant object is required in the referent namespace to allow that\n                                      namespace's owner to accept the reference. See the ReferenceGrant\n                                      documentation for details.\n\n\n                                      Support: Core\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                    type: string\n                                  port:\n                                    description: |-\n                                      Port specifies the destination port number to use for this resource.\n                                      Port is required when the referent is a Kubernetes Service. In this\n                                      case, the port number is the service port number, not the target port.\n                                      For other resources, destination port might be derived from the referent\n                                      resource or this field.\n                                    format: int32\n                                    maximum: 65535\n                                    minimum: 1\n                                    type: integer\n                                required:\n                                - name\n                                type: object\n                                x-kubernetes-validations:\n                                - message: Must have port for Service reference\n                                  rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                                    ? has(self.port) : true'\n                            required:\n                            - backendRef\n                            type: object\n                          responseHeaderModifier:\n                            description: |-\n                              ResponseHeaderModifier defines a schema for a filter that modifies response\n                              headers.\n\n\n                              Support: Extended\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          type:\n                            description: |+\n                              Type identifies the type of filter to apply. As with other API fields,\n                              types are classified into three conformance levels:\n\n\n                              - Core: Filter types and their corresponding configuration defined by\n                                \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                implementations supporting GRPCRoute MUST support core filters.\n\n\n                              - Extended: Filter types and their corresponding configuration defined by\n                                \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                are encouraged to support extended filters.\n\n\n                              - Implementation-specific: Filters that are defined and supported by specific vendors.\n                                In the future, filters showing convergence in behavior across multiple\n                                implementations will be considered for inclusion in extended or core\n                                conformance levels. Filter-specific configuration for such filters\n                                is specified using the ExtensionRef field. `Type` MUST be set to\n                                \"ExtensionRef\" for custom filters.\n\n\n                              Implementers are encouraged to define custom implementation types to\n                              extend the core API with implementation-specific behavior.\n\n\n                              If a reference to a custom filter type cannot be resolved, the filter\n                              MUST NOT be skipped. Instead, requests that would have been processed by\n                              that filter MUST receive a HTTP error response.\n\n\n                            enum:\n                            - ResponseHeaderModifier\n                            - RequestHeaderModifier\n                            - RequestMirror\n                            - ExtensionRef\n                            type: string\n                        required:\n                        - type\n                        type: object\n                        x-kubernetes-validations:\n                        - message: filter.requestHeaderModifier must be nil if the\n                            filter.type is not RequestHeaderModifier\n                          rule: '!(has(self.requestHeaderModifier) && self.type !=\n                            ''RequestHeaderModifier'')'\n                        - message: filter.requestHeaderModifier must be specified\n                            for RequestHeaderModifier filter.type\n                          rule: '!(!has(self.requestHeaderModifier) && self.type ==\n                            ''RequestHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be nil if the\n                            filter.type is not ResponseHeaderModifier\n                          rule: '!(has(self.responseHeaderModifier) && self.type !=\n                            ''ResponseHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be specified\n                            for ResponseHeaderModifier filter.type\n                          rule: '!(!has(self.responseHeaderModifier) && self.type\n                            == ''ResponseHeaderModifier'')'\n                        - message: filter.requestMirror must be nil if the filter.type\n                            is not RequestMirror\n                          rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                        - message: filter.requestMirror must be specified for RequestMirror\n                            filter.type\n                          rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')'\n                        - message: filter.extensionRef must be nil if the filter.type\n                            is not ExtensionRef\n                          rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                        - message: filter.extensionRef must be specified for ExtensionRef\n                            filter.type\n                          rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                      maxItems: 16\n                      type: array\n                      x-kubernetes-validations:\n                      - message: RequestHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                          <= 1\n                      - message: ResponseHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                          <= 1\n                    matches:\n                      description: |-\n                        Matches define conditions used for matching the rule against incoming\n                        gRPC requests. Each match is independent, i.e. this rule will be matched\n                        if **any** one of the matches is satisfied.\n\n\n                        For example, take the following matches configuration:\n\n\n                        ```\n                        matches:\n                        - method:\n                            service: foo.bar\n                          headers:\n                            values:\n                              version: 2\n                        - method:\n                            service: foo.bar.v2\n                        ```\n\n\n                        For a request to match against this rule, it MUST satisfy\n                        EITHER of the two conditions:\n\n\n                        - service of foo.bar AND contains the header `version: 2`\n                        - service of foo.bar.v2\n\n\n                        See the documentation for GRPCRouteMatch on how to specify multiple\n                        match conditions to be ANDed together.\n\n\n                        If no matches are specified, the implementation MUST match every gRPC request.\n\n\n                        Proxy or Load Balancer routing configuration generated from GRPCRoutes\n                        MUST prioritize rules based on the following criteria, continuing on\n                        ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes.\n                        Precedence MUST be given to the rule with the largest number of:\n\n\n                        * Characters in a matching non-wildcard hostname.\n                        * Characters in a matching hostname.\n                        * Characters in a matching service.\n                        * Characters in a matching method.\n                        * Header matches.\n\n\n                        If ties still exist across multiple Routes, matching precedence MUST be\n                        determined in order of the following criteria, continuing on ties:\n\n\n                        * The oldest Route based on creation timestamp.\n                        * The Route appearing first in alphabetical order by\n                          \"{namespace}/{name}\".\n\n\n                        If ties still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching rule meeting\n                        the above criteria.\n                      items:\n                        description: |-\n                          GRPCRouteMatch defines the predicate used to match requests to a given\n                          action. Multiple match types are ANDed together, i.e. the match will\n                          evaluate to true only if all conditions are satisfied.\n\n\n                          For example, the match below will match a gRPC request only if its service\n                          is `foo` AND it contains the `version: v1` header:\n\n\n                          ```\n                          matches:\n                            - method:\n                              type: Exact\n                              service: \"foo\"\n                              headers:\n                            - name: \"version\"\n                              value \"v1\"\n\n\n                          ```\n                        properties:\n                          headers:\n                            description: |-\n                              Headers specifies gRPC request header matchers. Multiple match values are\n                              ANDed together, meaning, a request MUST match all the specified headers\n                              to select the route.\n                            items:\n                              description: |-\n                                GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request\n                                headers.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the gRPC Header to be matched.\n\n\n                                    If multiple entries specify equivalent header names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be ignored. Due to the\n                                    case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                    equivalent.\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: Type specifies how to match against\n                                    the value of the header.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of the gRPC Header\n                                    to be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: |-\n                              Method specifies a gRPC request service/method matcher. If this field is\n                              not specified, all services and methods will match.\n                            properties:\n                              method:\n                                description: |-\n                                  Value of the method to match against. If left empty or omitted, will\n                                  match all services.\n\n\n                                  At least one of Service and Method MUST be a non-empty string.\n                                maxLength: 1024\n                                type: string\n                              service:\n                                description: |-\n                                  Value of the service to match against. If left empty or omitted, will\n                                  match any service.\n\n\n                                  At least one of Service and Method MUST be a non-empty string.\n                                maxLength: 1024\n                                type: string\n                              type:\n                                default: Exact\n                                description: |-\n                                  Type specifies how to match against the service and/or method.\n                                  Support: Core (Exact with service and method specified)\n\n\n                                  Support: Implementation-specific (Exact with method specified but no service specified)\n\n\n                                  Support: Implementation-specific (RegularExpression)\n                                enum:\n                                - Exact\n                                - RegularExpression\n                                type: string\n                            type: object\n                            x-kubernetes-validations:\n                            - message: One or both of 'service' or 'method' must be\n                                specified\n                              rule: 'has(self.type) ? has(self.service) || has(self.method)\n                                : true'\n                            - message: service must only contain valid characters\n                                (matching ^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$)\n                              rule: '(!has(self.type) || self.type == ''Exact'') &&\n                                has(self.service) ? self.service.matches(r\"\"\"^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$\"\"\"):\n                                true'\n                            - message: method must only contain valid characters (matching\n                                ^[A-Za-z_][A-Za-z_0-9]*$)\n                              rule: '(!has(self.type) || self.type == ''Exact'') &&\n                                has(self.method) ? self.method.matches(r\"\"\"^[A-Za-z_][A-Za-z_0-9]*$\"\"\"):\n                                true'\n                        type: object\n                      maxItems: 8\n                      type: array\n                    sessionPersistence:\n                      description: |+\n                        SessionPersistence defines and configures session persistence\n                        for the route rule.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        absoluteTimeout:\n                          description: |-\n                            AbsoluteTimeout defines the absolute timeout of the persistent\n                            session. Once the AbsoluteTimeout duration has elapsed, the\n                            session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        cookieConfig:\n                          description: |-\n                            CookieConfig provides configuration settings that are specific\n                            to cookie-based session persistence.\n\n\n                            Support: Core\n                          properties:\n                            lifetimeType:\n                              default: Session\n                              description: |-\n                                LifetimeType specifies whether the cookie has a permanent or\n                                session-based lifetime. A permanent cookie persists until its\n                                specified expiry time, defined by the Expires or Max-Age cookie\n                                attributes, while a session cookie is deleted when the current\n                                session ends.\n\n\n                                When set to \"Permanent\", AbsoluteTimeout indicates the\n                                cookie's lifetime via the Expires or Max-Age cookie attributes\n                                and is required.\n\n\n                                When set to \"Session\", AbsoluteTimeout indicates the\n                                absolute lifetime of the cookie tracked by the gateway and\n                                is optional.\n\n\n                                Support: Core for \"Session\" type\n\n\n                                Support: Extended for \"Permanent\" type\n                              enum:\n                              - Permanent\n                              - Session\n                              type: string\n                          type: object\n                        idleTimeout:\n                          description: |-\n                            IdleTimeout defines the idle timeout of the persistent session.\n                            Once the session has been idle for more than the specified\n                            IdleTimeout duration, the session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        sessionName:\n                          description: |-\n                            SessionName defines the name of the persistent session token\n                            which may be reflected in the cookie or the header. Users\n                            should avoid reusing session names to prevent unintended\n                            consequences, such as rejection or unpredictable behavior.\n\n\n                            Support: Implementation-specific\n                          maxLength: 128\n                          type: string\n                        type:\n                          default: Cookie\n                          description: |-\n                            Type defines the type of session persistence such as through\n                            the use a header or cookie. Defaults to cookie based session\n                            persistence.\n\n\n                            Support: Core for \"Cookie\" type\n\n\n                            Support: Extended for \"Header\" type\n                          enum:\n                          - Cookie\n                          - Header\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: AbsoluteTimeout must be specified when cookie lifetimeType\n                          is Permanent\n                        rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType)\n                          || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)'\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of GRPCRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    deprecated: true\n    deprecationWarning: The v1alpha2 version of GRPCRoute has been deprecated and\n      will be removed in a future release of the API. Please upgrade to v1.\n    name: v1alpha2\n    schema:\n      openAPIV3Schema:\n        description: |-\n          GRPCRoute provides a way to route gRPC requests. This includes the capability\n          to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests will be routed.\n\n\n          GRPCRoute falls under extended support within the Gateway API. Within the\n          following specification, the word \"MUST\" indicates that an implementation\n          supporting GRPCRoute must conform to the indicated requirement, but an\n          implementation not supporting this route type need not follow the requirement\n          unless explicitly indicated.\n\n\n          Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST\n          accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via\n          ALPN. If the implementation does not support this, then it MUST set the\n          \"Accepted\" condition to \"False\" for the affected listener with a reason of\n          \"UnsupportedProtocol\".  Implementations MAY also accept HTTP/2 connections\n          with an upgrade from HTTP/1.\n\n\n          Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST\n          support HTTP/2 over cleartext TCP (h2c,\n          https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial\n          upgrade from HTTP/1.1, i.e. with prior knowledge\n          (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation\n          does not support this, then it MUST set the \"Accepted\" condition to \"False\"\n          for the affected listener with a reason of \"UnsupportedProtocol\".\n          Implementations MAY also accept HTTP/2 connections with an upgrade from\n          HTTP/1, i.e. without prior knowledge.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of GRPCRoute.\n            properties:\n              hostnames:\n                description: |-\n                  Hostnames defines a set of hostnames to match against the GRPC\n                  Host header to select a GRPCRoute to process the request. This matches\n                  the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                  1. IPs are not allowed.\n                  2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                     label MUST appear by itself as the first label.\n\n\n                  If a hostname is specified by both the Listener and GRPCRoute, there\n                  MUST be at least one intersecting hostname for the GRPCRoute to be\n                  attached to the Listener. For example:\n\n\n                  * A Listener with `test.example.com` as the hostname matches GRPCRoutes\n                    that have either not specified any hostnames, or have specified at\n                    least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches GRPCRoutes\n                    that have either not specified any hostnames or have specified at least\n                    one hostname that matches the Listener hostname. For example,\n                    `test.example.com` and `*.example.com` would both match. On the other\n                    hand, `example.com` and `test.example.net` would not match.\n\n\n                  Hostnames that are prefixed with a wildcard label (`*.`) are interpreted\n                  as a suffix match. That means that a match for `*.example.com` would match\n                  both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\n\n                  If both the Listener and GRPCRoute have specified hostnames, any\n                  GRPCRoute hostnames that do not match the Listener hostname MUST be\n                  ignored. For example, if a Listener specified `*.example.com`, and the\n                  GRPCRoute specified `test.example.com` and `test.example.net`,\n                  `test.example.net` MUST NOT be considered for a match.\n\n\n                  If both the Listener and GRPCRoute have specified hostnames, and none\n                  match with the criteria above, then the GRPCRoute MUST NOT be accepted by\n                  the implementation. The implementation MUST raise an 'Accepted' Condition\n                  with a status of `False` in the corresponding RouteParentStatus.\n\n\n                  If a Route (A) of type HTTPRoute or GRPCRoute is attached to a\n                  Listener and that listener already has another Route (B) of the other\n                  type attached and the intersection of the hostnames of A and B is\n                  non-empty, then the implementation MUST accept exactly one of these two\n                  routes, determined by the following criteria, in order:\n\n\n                  * The oldest Route based on creation timestamp.\n                  * The Route appearing first in alphabetical order by\n                    \"{namespace}/{name}\".\n\n\n                  The rejected Route MUST raise an 'Accepted' condition with a status of\n                  'False' in the corresponding RouteParentStatus.\n\n\n                  Support: Core\n                items:\n                  description: |-\n                    Hostname is the fully qualified domain name of a network host. This matches\n                    the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                     1. IPs are not allowed.\n                     2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                        label must appear by itself as the first label.\n\n\n                    Hostname can be \"precise\" which is a domain name without the terminating\n                    dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a\n                    domain name prefixed with a single wildcard label (e.g. `*.example.com`).\n\n\n                    Note that as per RFC1035 and RFC1123, a *label* must consist of lower case\n                    alphanumeric characters or '-', and must start and end with an alphanumeric\n                    character. No other punctuation is allowed.\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                description: Rules are a list of GRPC matchers, filters and actions.\n                items:\n                  description: |-\n                    GRPCRouteRule defines the semantics for matching a gRPC request based on\n                    conditions (matches), processing it (filters), and forwarding the request to\n                    an API object (backendRefs).\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent.\n\n\n                        Failure behavior here depends on how many BackendRefs are specified and\n                        how many are invalid.\n\n\n                        If *all* entries in BackendRefs are invalid, and there are also no filters\n                        specified in this route rule, *all* traffic which matches this rule MUST\n                        receive an `UNAVAILABLE` status.\n\n\n                        See the GRPCBackendRef definition for the rules about what makes a single\n                        GRPCBackendRef invalid.\n\n\n                        When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for\n                        requests that would have otherwise been routed to an invalid backend. If\n                        multiple backends are specified, and some are invalid, the proportion of\n                        requests that would otherwise have been routed to an invalid backend\n                        MUST receive an `UNAVAILABLE` status.\n\n\n                        For example, if two backends are specified with equal weights, and one is\n                        invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status.\n                        Implementations may choose how that 50 percent is determined.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Core\n                      items:\n                        description: |-\n                          GRPCBackendRef defines how a GRPCRoute forwards a gRPC request.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n                        properties:\n                          filters:\n                            description: |-\n                              Filters defined at this level MUST be executed if and only if the\n                              request is being forwarded to the backend defined here.\n\n\n                              Support: Implementation-specific (For broader support of filters, use the\n                              Filters field in GRPCRouteRule.)\n                            items:\n                              description: |-\n                                GRPCRouteFilter defines processing steps that must be completed during the\n                                request or response lifecycle. GRPCRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway implementations. Some\n                                examples include request or response modification, implementing\n                                authentication strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type of the filter.\n                              properties:\n                                extensionRef:\n                                  description: |-\n                                    ExtensionRef is an optional, implementation-specific extension to the\n                                    \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                                    \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                                    extended filters.\n\n\n                                    Support: Implementation-specific\n\n\n                                    This filter can be used multiple times within the same rule.\n                                  properties:\n                                    group:\n                                      description: |-\n                                        Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                        When unspecified or empty string, core API group is inferred.\n                                      maxLength: 253\n                                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    kind:\n                                      description: Kind is kind of the referent. For\n                                        example \"HTTPRoute\" or \"Service\".\n                                      maxLength: 63\n                                      minLength: 1\n                                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                      type: string\n                                    name:\n                                      description: Name is the name of the referent.\n                                      maxLength: 253\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - group\n                                  - kind\n                                  - name\n                                  type: object\n                                requestHeaderModifier:\n                                  description: |-\n                                    RequestHeaderModifier defines a schema for a filter that modifies request\n                                    headers.\n\n\n                                    Support: Core\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestMirror:\n                                  description: |-\n                                    RequestMirror defines a schema for a filter that mirrors requests.\n                                    Requests are sent to the specified destination, but responses from\n                                    that destination are ignored.\n\n\n                                    This filter can be used multiple times within the same rule. Note that\n                                    not all implementations will be able to support mirroring to multiple\n                                    backends.\n\n\n                                    Support: Extended\n                                  properties:\n                                    backendRef:\n                                      description: |-\n                                        BackendRef references a resource where mirrored requests are sent.\n\n\n                                        Mirrored requests must be sent only to a single destination endpoint\n                                        within this BackendRef, irrespective of how many endpoints are present\n                                        within this BackendRef.\n\n\n                                        If the referent cannot be found, this BackendRef is invalid and must be\n                                        dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                        condition on the Route status is set to `status: False` and not configure\n                                        this backend in the underlying implementation.\n\n\n                                        If there is a cross-namespace reference to an *existing* object\n                                        that is not allowed by a ReferenceGrant, the controller must ensure the\n                                        \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                        with the \"RefNotPermitted\" reason and not configure this backend in the\n                                        underlying implementation.\n\n\n                                        In either error case, the Message of the `ResolvedRefs` Condition\n                                        should be used to provide more detail about the problem.\n\n\n                                        Support: Extended for Kubernetes Service\n\n\n                                        Support: Implementation-specific for any other resource\n                                      properties:\n                                        group:\n                                          default: \"\"\n                                          description: |-\n                                            Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                            When unspecified or empty string, core API group is inferred.\n                                          maxLength: 253\n                                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                          type: string\n                                        kind:\n                                          default: Service\n                                          description: |-\n                                            Kind is the Kubernetes resource kind of the referent. For example\n                                            \"Service\".\n\n\n                                            Defaults to \"Service\" when not specified.\n\n\n                                            ExternalName services can refer to CNAME DNS records that may live\n                                            outside of the cluster and as such are difficult to reason about in\n                                            terms of conformance. They also may not be safe to forward to (see\n                                            CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                            support ExternalName Services.\n\n\n                                            Support: Core (Services with a type other than ExternalName)\n\n\n                                            Support: Implementation-specific (Services with type ExternalName)\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                          type: string\n                                        name:\n                                          description: Name is the name of the referent.\n                                          maxLength: 253\n                                          minLength: 1\n                                          type: string\n                                        namespace:\n                                          description: |-\n                                            Namespace is the namespace of the backend. When unspecified, the local\n                                            namespace is inferred.\n\n\n                                            Note that when a namespace different than the local namespace is specified,\n                                            a ReferenceGrant object is required in the referent namespace to allow that\n                                            namespace's owner to accept the reference. See the ReferenceGrant\n                                            documentation for details.\n\n\n                                            Support: Core\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                          type: string\n                                        port:\n                                          description: |-\n                                            Port specifies the destination port number to use for this resource.\n                                            Port is required when the referent is a Kubernetes Service. In this\n                                            case, the port number is the service port number, not the target port.\n                                            For other resources, destination port might be derived from the referent\n                                            resource or this field.\n                                          format: int32\n                                          maximum: 65535\n                                          minimum: 1\n                                          type: integer\n                                      required:\n                                      - name\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: Must have port for Service reference\n                                        rule: '(size(self.group) == 0 && self.kind\n                                          == ''Service'') ? has(self.port) : true'\n                                  required:\n                                  - backendRef\n                                  type: object\n                                responseHeaderModifier:\n                                  description: |-\n                                    ResponseHeaderModifier defines a schema for a filter that modifies response\n                                    headers.\n\n\n                                    Support: Extended\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: |+\n                                    Type identifies the type of filter to apply. As with other API fields,\n                                    types are classified into three conformance levels:\n\n\n                                    - Core: Filter types and their corresponding configuration defined by\n                                      \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                      implementations supporting GRPCRoute MUST support core filters.\n\n\n                                    - Extended: Filter types and their corresponding configuration defined by\n                                      \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                      are encouraged to support extended filters.\n\n\n                                    - Implementation-specific: Filters that are defined and supported by specific vendors.\n                                      In the future, filters showing convergence in behavior across multiple\n                                      implementations will be considered for inclusion in extended or core\n                                      conformance levels. Filter-specific configuration for such filters\n                                      is specified using the ExtensionRef field. `Type` MUST be set to\n                                      \"ExtensionRef\" for custom filters.\n\n\n                                    Implementers are encouraged to define custom implementation types to\n                                    extend the core API with implementation-specific behavior.\n\n\n                                    If a reference to a custom filter type cannot be resolved, the filter\n                                    MUST NOT be skipped. Instead, requests that would have been processed by\n                                    that filter MUST receive a HTTP error response.\n\n\n                                  enum:\n                                  - ResponseHeaderModifier\n                                  - RequestHeaderModifier\n                                  - RequestMirror\n                                  - ExtensionRef\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                              x-kubernetes-validations:\n                              - message: filter.requestHeaderModifier must be nil\n                                  if the filter.type is not RequestHeaderModifier\n                                rule: '!(has(self.requestHeaderModifier) && self.type\n                                  != ''RequestHeaderModifier'')'\n                              - message: filter.requestHeaderModifier must be specified\n                                  for RequestHeaderModifier filter.type\n                                rule: '!(!has(self.requestHeaderModifier) && self.type\n                                  == ''RequestHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be nil\n                                  if the filter.type is not ResponseHeaderModifier\n                                rule: '!(has(self.responseHeaderModifier) && self.type\n                                  != ''ResponseHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be specified\n                                  for ResponseHeaderModifier filter.type\n                                rule: '!(!has(self.responseHeaderModifier) && self.type\n                                  == ''ResponseHeaderModifier'')'\n                              - message: filter.requestMirror must be nil if the filter.type\n                                  is not RequestMirror\n                                rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                              - message: filter.requestMirror must be specified for\n                                  RequestMirror filter.type\n                                rule: '!(!has(self.requestMirror) && self.type ==\n                                  ''RequestMirror'')'\n                              - message: filter.extensionRef must be nil if the filter.type\n                                  is not ExtensionRef\n                                rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                              - message: filter.extensionRef must be specified for\n                                  ExtensionRef filter.type\n                                rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-validations:\n                            - message: RequestHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                                <= 1\n                            - message: ResponseHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                                <= 1\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: |-\n                        Filters define the filters that are applied to requests that match\n                        this rule.\n\n\n                        The effects of ordering of multiple behaviors are currently unspecified.\n                        This can change in the future based on feedback during the alpha stage.\n\n\n                        Conformance-levels at this level are defined based on the type of filter:\n\n\n                        - ALL core filters MUST be supported by all implementations that support\n                          GRPCRoute.\n                        - Implementers are encouraged to support extended filters.\n                        - Implementation-specific custom filters have no API guarantees across\n                          implementations.\n\n\n                        Specifying the same filter multiple times is not supported unless explicitly\n                        indicated in the filter.\n\n\n                        If an implementation can not support a combination of filters, it must clearly\n                        document that limitation. In cases where incompatible or unsupported\n                        filters are specified and cause the `Accepted` condition to be set to status\n                        `False`, implementations may use the `IncompatibleFilters` reason to specify\n                        this configuration error.\n\n\n                        Support: Core\n                      items:\n                        description: |-\n                          GRPCRouteFilter defines processing steps that must be completed during the\n                          request or response lifecycle. GRPCRouteFilters are meant as an extension\n                          point to express processing that may be done in Gateway implementations. Some\n                          examples include request or response modification, implementing\n                          authentication strategies, rate-limiting, and traffic shaping. API\n                          guarantee/conformance is defined based on the type of the filter.\n                        properties:\n                          extensionRef:\n                            description: |-\n                              ExtensionRef is an optional, implementation-specific extension to the\n                              \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                              \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                              extended filters.\n\n\n                              Support: Implementation-specific\n\n\n                              This filter can be used multiple times within the same rule.\n                            properties:\n                              group:\n                                description: |-\n                                  Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                  When unspecified or empty string, core API group is inferred.\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                description: Kind is kind of the referent. For example\n                                  \"HTTPRoute\" or \"Service\".\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: Name is the name of the referent.\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                            required:\n                            - group\n                            - kind\n                            - name\n                            type: object\n                          requestHeaderModifier:\n                            description: |-\n                              RequestHeaderModifier defines a schema for a filter that modifies request\n                              headers.\n\n\n                              Support: Core\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestMirror:\n                            description: |-\n                              RequestMirror defines a schema for a filter that mirrors requests.\n                              Requests are sent to the specified destination, but responses from\n                              that destination are ignored.\n\n\n                              This filter can be used multiple times within the same rule. Note that\n                              not all implementations will be able to support mirroring to multiple\n                              backends.\n\n\n                              Support: Extended\n                            properties:\n                              backendRef:\n                                description: |-\n                                  BackendRef references a resource where mirrored requests are sent.\n\n\n                                  Mirrored requests must be sent only to a single destination endpoint\n                                  within this BackendRef, irrespective of how many endpoints are present\n                                  within this BackendRef.\n\n\n                                  If the referent cannot be found, this BackendRef is invalid and must be\n                                  dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                  condition on the Route status is set to `status: False` and not configure\n                                  this backend in the underlying implementation.\n\n\n                                  If there is a cross-namespace reference to an *existing* object\n                                  that is not allowed by a ReferenceGrant, the controller must ensure the\n                                  \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                  with the \"RefNotPermitted\" reason and not configure this backend in the\n                                  underlying implementation.\n\n\n                                  In either error case, the Message of the `ResolvedRefs` Condition\n                                  should be used to provide more detail about the problem.\n\n\n                                  Support: Extended for Kubernetes Service\n\n\n                                  Support: Implementation-specific for any other resource\n                                properties:\n                                  group:\n                                    default: \"\"\n                                    description: |-\n                                      Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                      When unspecified or empty string, core API group is inferred.\n                                    maxLength: 253\n                                    pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                    type: string\n                                  kind:\n                                    default: Service\n                                    description: |-\n                                      Kind is the Kubernetes resource kind of the referent. For example\n                                      \"Service\".\n\n\n                                      Defaults to \"Service\" when not specified.\n\n\n                                      ExternalName services can refer to CNAME DNS records that may live\n                                      outside of the cluster and as such are difficult to reason about in\n                                      terms of conformance. They also may not be safe to forward to (see\n                                      CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                      support ExternalName Services.\n\n\n                                      Support: Core (Services with a type other than ExternalName)\n\n\n                                      Support: Implementation-specific (Services with type ExternalName)\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                    type: string\n                                  name:\n                                    description: Name is the name of the referent.\n                                    maxLength: 253\n                                    minLength: 1\n                                    type: string\n                                  namespace:\n                                    description: |-\n                                      Namespace is the namespace of the backend. When unspecified, the local\n                                      namespace is inferred.\n\n\n                                      Note that when a namespace different than the local namespace is specified,\n                                      a ReferenceGrant object is required in the referent namespace to allow that\n                                      namespace's owner to accept the reference. See the ReferenceGrant\n                                      documentation for details.\n\n\n                                      Support: Core\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                    type: string\n                                  port:\n                                    description: |-\n                                      Port specifies the destination port number to use for this resource.\n                                      Port is required when the referent is a Kubernetes Service. In this\n                                      case, the port number is the service port number, not the target port.\n                                      For other resources, destination port might be derived from the referent\n                                      resource or this field.\n                                    format: int32\n                                    maximum: 65535\n                                    minimum: 1\n                                    type: integer\n                                required:\n                                - name\n                                type: object\n                                x-kubernetes-validations:\n                                - message: Must have port for Service reference\n                                  rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                                    ? has(self.port) : true'\n                            required:\n                            - backendRef\n                            type: object\n                          responseHeaderModifier:\n                            description: |-\n                              ResponseHeaderModifier defines a schema for a filter that modifies response\n                              headers.\n\n\n                              Support: Extended\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          type:\n                            description: |+\n                              Type identifies the type of filter to apply. As with other API fields,\n                              types are classified into three conformance levels:\n\n\n                              - Core: Filter types and their corresponding configuration defined by\n                                \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                implementations supporting GRPCRoute MUST support core filters.\n\n\n                              - Extended: Filter types and their corresponding configuration defined by\n                                \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                are encouraged to support extended filters.\n\n\n                              - Implementation-specific: Filters that are defined and supported by specific vendors.\n                                In the future, filters showing convergence in behavior across multiple\n                                implementations will be considered for inclusion in extended or core\n                                conformance levels. Filter-specific configuration for such filters\n                                is specified using the ExtensionRef field. `Type` MUST be set to\n                                \"ExtensionRef\" for custom filters.\n\n\n                              Implementers are encouraged to define custom implementation types to\n                              extend the core API with implementation-specific behavior.\n\n\n                              If a reference to a custom filter type cannot be resolved, the filter\n                              MUST NOT be skipped. Instead, requests that would have been processed by\n                              that filter MUST receive a HTTP error response.\n\n\n                            enum:\n                            - ResponseHeaderModifier\n                            - RequestHeaderModifier\n                            - RequestMirror\n                            - ExtensionRef\n                            type: string\n                        required:\n                        - type\n                        type: object\n                        x-kubernetes-validations:\n                        - message: filter.requestHeaderModifier must be nil if the\n                            filter.type is not RequestHeaderModifier\n                          rule: '!(has(self.requestHeaderModifier) && self.type !=\n                            ''RequestHeaderModifier'')'\n                        - message: filter.requestHeaderModifier must be specified\n                            for RequestHeaderModifier filter.type\n                          rule: '!(!has(self.requestHeaderModifier) && self.type ==\n                            ''RequestHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be nil if the\n                            filter.type is not ResponseHeaderModifier\n                          rule: '!(has(self.responseHeaderModifier) && self.type !=\n                            ''ResponseHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be specified\n                            for ResponseHeaderModifier filter.type\n                          rule: '!(!has(self.responseHeaderModifier) && self.type\n                            == ''ResponseHeaderModifier'')'\n                        - message: filter.requestMirror must be nil if the filter.type\n                            is not RequestMirror\n                          rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                        - message: filter.requestMirror must be specified for RequestMirror\n                            filter.type\n                          rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')'\n                        - message: filter.extensionRef must be nil if the filter.type\n                            is not ExtensionRef\n                          rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                        - message: filter.extensionRef must be specified for ExtensionRef\n                            filter.type\n                          rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                      maxItems: 16\n                      type: array\n                      x-kubernetes-validations:\n                      - message: RequestHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                          <= 1\n                      - message: ResponseHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                          <= 1\n                    matches:\n                      description: |-\n                        Matches define conditions used for matching the rule against incoming\n                        gRPC requests. Each match is independent, i.e. this rule will be matched\n                        if **any** one of the matches is satisfied.\n\n\n                        For example, take the following matches configuration:\n\n\n                        ```\n                        matches:\n                        - method:\n                            service: foo.bar\n                          headers:\n                            values:\n                              version: 2\n                        - method:\n                            service: foo.bar.v2\n                        ```\n\n\n                        For a request to match against this rule, it MUST satisfy\n                        EITHER of the two conditions:\n\n\n                        - service of foo.bar AND contains the header `version: 2`\n                        - service of foo.bar.v2\n\n\n                        See the documentation for GRPCRouteMatch on how to specify multiple\n                        match conditions to be ANDed together.\n\n\n                        If no matches are specified, the implementation MUST match every gRPC request.\n\n\n                        Proxy or Load Balancer routing configuration generated from GRPCRoutes\n                        MUST prioritize rules based on the following criteria, continuing on\n                        ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes.\n                        Precedence MUST be given to the rule with the largest number of:\n\n\n                        * Characters in a matching non-wildcard hostname.\n                        * Characters in a matching hostname.\n                        * Characters in a matching service.\n                        * Characters in a matching method.\n                        * Header matches.\n\n\n                        If ties still exist across multiple Routes, matching precedence MUST be\n                        determined in order of the following criteria, continuing on ties:\n\n\n                        * The oldest Route based on creation timestamp.\n                        * The Route appearing first in alphabetical order by\n                          \"{namespace}/{name}\".\n\n\n                        If ties still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching rule meeting\n                        the above criteria.\n                      items:\n                        description: |-\n                          GRPCRouteMatch defines the predicate used to match requests to a given\n                          action. Multiple match types are ANDed together, i.e. the match will\n                          evaluate to true only if all conditions are satisfied.\n\n\n                          For example, the match below will match a gRPC request only if its service\n                          is `foo` AND it contains the `version: v1` header:\n\n\n                          ```\n                          matches:\n                            - method:\n                              type: Exact\n                              service: \"foo\"\n                              headers:\n                            - name: \"version\"\n                              value \"v1\"\n\n\n                          ```\n                        properties:\n                          headers:\n                            description: |-\n                              Headers specifies gRPC request header matchers. Multiple match values are\n                              ANDed together, meaning, a request MUST match all the specified headers\n                              to select the route.\n                            items:\n                              description: |-\n                                GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request\n                                headers.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the gRPC Header to be matched.\n\n\n                                    If multiple entries specify equivalent header names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be ignored. Due to the\n                                    case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                    equivalent.\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: Type specifies how to match against\n                                    the value of the header.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of the gRPC Header\n                                    to be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: |-\n                              Method specifies a gRPC request service/method matcher. If this field is\n                              not specified, all services and methods will match.\n                            properties:\n                              method:\n                                description: |-\n                                  Value of the method to match against. If left empty or omitted, will\n                                  match all services.\n\n\n                                  At least one of Service and Method MUST be a non-empty string.\n                                maxLength: 1024\n                                type: string\n                              service:\n                                description: |-\n                                  Value of the service to match against. If left empty or omitted, will\n                                  match any service.\n\n\n                                  At least one of Service and Method MUST be a non-empty string.\n                                maxLength: 1024\n                                type: string\n                              type:\n                                default: Exact\n                                description: |-\n                                  Type specifies how to match against the service and/or method.\n                                  Support: Core (Exact with service and method specified)\n\n\n                                  Support: Implementation-specific (Exact with method specified but no service specified)\n\n\n                                  Support: Implementation-specific (RegularExpression)\n                                enum:\n                                - Exact\n                                - RegularExpression\n                                type: string\n                            type: object\n                            x-kubernetes-validations:\n                            - message: One or both of 'service' or 'method' must be\n                                specified\n                              rule: 'has(self.type) ? has(self.service) || has(self.method)\n                                : true'\n                            - message: service must only contain valid characters\n                                (matching ^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$)\n                              rule: '(!has(self.type) || self.type == ''Exact'') &&\n                                has(self.service) ? self.service.matches(r\"\"\"^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$\"\"\"):\n                                true'\n                            - message: method must only contain valid characters (matching\n                                ^[A-Za-z_][A-Za-z_0-9]*$)\n                              rule: '(!has(self.type) || self.type == ''Exact'') &&\n                                has(self.method) ? self.method.matches(r\"\"\"^[A-Za-z_][A-Za-z_0-9]*$\"\"\"):\n                                true'\n                        type: object\n                      maxItems: 8\n                      type: array\n                    sessionPersistence:\n                      description: |+\n                        SessionPersistence defines and configures session persistence\n                        for the route rule.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        absoluteTimeout:\n                          description: |-\n                            AbsoluteTimeout defines the absolute timeout of the persistent\n                            session. Once the AbsoluteTimeout duration has elapsed, the\n                            session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        cookieConfig:\n                          description: |-\n                            CookieConfig provides configuration settings that are specific\n                            to cookie-based session persistence.\n\n\n                            Support: Core\n                          properties:\n                            lifetimeType:\n                              default: Session\n                              description: |-\n                                LifetimeType specifies whether the cookie has a permanent or\n                                session-based lifetime. A permanent cookie persists until its\n                                specified expiry time, defined by the Expires or Max-Age cookie\n                                attributes, while a session cookie is deleted when the current\n                                session ends.\n\n\n                                When set to \"Permanent\", AbsoluteTimeout indicates the\n                                cookie's lifetime via the Expires or Max-Age cookie attributes\n                                and is required.\n\n\n                                When set to \"Session\", AbsoluteTimeout indicates the\n                                absolute lifetime of the cookie tracked by the gateway and\n                                is optional.\n\n\n                                Support: Core for \"Session\" type\n\n\n                                Support: Extended for \"Permanent\" type\n                              enum:\n                              - Permanent\n                              - Session\n                              type: string\n                          type: object\n                        idleTimeout:\n                          description: |-\n                            IdleTimeout defines the idle timeout of the persistent session.\n                            Once the session has been idle for more than the specified\n                            IdleTimeout duration, the session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        sessionName:\n                          description: |-\n                            SessionName defines the name of the persistent session token\n                            which may be reflected in the cookie or the header. Users\n                            should avoid reusing session names to prevent unintended\n                            consequences, such as rejection or unpredictable behavior.\n\n\n                            Support: Implementation-specific\n                          maxLength: 128\n                          type: string\n                        type:\n                          default: Cookie\n                          description: |-\n                            Type defines the type of session persistence such as through\n                            the use a header or cookie. Defaults to cookie based session\n                            persistence.\n\n\n                            Support: Core for \"Cookie\" type\n\n\n                            Support: Extended for \"Header\" type\n                          enum:\n                          - Cookie\n                          - Header\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: AbsoluteTimeout must be specified when cookie lifetimeType\n                          is Permanent\n                        rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType)\n                          || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)'\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of GRPCRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: null\n  storedVersions: null\n{{- end }}\n"
  },
  {
    "path": "charts/linkerd-crds/templates/gateway.networking.k8s.io_httproutes.yaml",
    "content": "{{- if (ternary .Values.enableHttpRoutes .Values.installGatewayAPI (hasKey .Values \"enableHttpRoutes\")) }}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997\n    gateway.networking.k8s.io/bundle-version: v1.1.1\n    gateway.networking.k8s.io/channel: experimental\n    {{ include \"partials.annotations.created-by\" . }}\n    helm.sh/resource-policy: keep\n  labels:\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n  creationTimestamp: null\n  name: httproutes.gateway.networking.k8s.io\nspec:\n  group: gateway.networking.k8s.io\n  names:\n    categories:\n    - gateway-api\n    kind: HTTPRoute\n    listKind: HTTPRouteList\n    plural: httproutes\n    singular: httproute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1\n    schema:\n      openAPIV3Schema:\n        description: |-\n          HTTPRoute provides a way to route HTTP requests. This includes the capability\n          to match requests by hostname, path, header, or query param. Filters can be\n          used to specify additional processing steps. Backends specify where matching\n          requests should be routed.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: |-\n                  Hostnames defines a set of hostnames that should match against the HTTP Host\n                  header to select a HTTPRoute used to process the request. Implementations\n                  MUST ignore any port value specified in the HTTP Host header while\n                  performing a match and (absent of any applicable header modification\n                  configuration) MUST forward this header unmodified to the backend.\n\n\n                  Valid values for Hostnames are determined by RFC 1123 definition of a\n                  hostname with 2 notable exceptions:\n\n\n                  1. IPs are not allowed.\n                  2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                     label must appear by itself as the first label.\n\n\n                  If a hostname is specified by both the Listener and HTTPRoute, there\n                  must be at least one intersecting hostname for the HTTPRoute to be\n                  attached to the Listener. For example:\n\n\n                  * A Listener with `test.example.com` as the hostname matches HTTPRoutes\n                    that have either not specified any hostnames, or have specified at\n                    least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                    that have either not specified any hostnames or have specified at least\n                    one hostname that matches the Listener hostname. For example,\n                    `*.example.com`, `test.example.com`, and `foo.test.example.com` would\n                    all match. On the other hand, `example.com` and `test.example.net` would\n                    not match.\n\n\n                  Hostnames that are prefixed with a wildcard label (`*.`) are interpreted\n                  as a suffix match. That means that a match for `*.example.com` would match\n                  both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\n\n                  If both the Listener and HTTPRoute have specified hostnames, any\n                  HTTPRoute hostnames that do not match the Listener hostname MUST be\n                  ignored. For example, if a Listener specified `*.example.com`, and the\n                  HTTPRoute specified `test.example.com` and `test.example.net`,\n                  `test.example.net` must not be considered for a match.\n\n\n                  If both the Listener and HTTPRoute have specified hostnames, and none\n                  match with the criteria above, then the HTTPRoute is not accepted. The\n                  implementation must raise an 'Accepted' Condition with a status of\n                  `False` in the corresponding RouteParentStatus.\n\n\n                  In the event that multiple HTTPRoutes specify intersecting hostnames (e.g.\n                  overlapping wildcard matching and exact matching hostnames), precedence must\n                  be given to rules from the HTTPRoute with the largest number of:\n\n\n                  * Characters in a matching non-wildcard hostname.\n                  * Characters in a matching hostname.\n\n\n                  If ties exist across multiple Routes, the matching precedence rules for\n                  HTTPRouteMatches takes over.\n\n\n                  Support: Core\n                items:\n                  description: |-\n                    Hostname is the fully qualified domain name of a network host. This matches\n                    the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                     1. IPs are not allowed.\n                     2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                        label must appear by itself as the first label.\n\n\n                    Hostname can be \"precise\" which is a domain name without the terminating\n                    dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a\n                    domain name prefixed with a single wildcard label (e.g. `*.example.com`).\n\n\n                    Note that as per RFC1035 and RFC1123, a *label* must consist of lower case\n                    alphanumeric characters or '-', and must start and end with an alphanumeric\n                    character. No other punctuation is allowed.\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: |-\n                    HTTPRouteRule defines semantics for matching an HTTP request based on\n                    conditions (matches), processing it (filters), and forwarding the request to\n                    an API object (backendRefs).\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent.\n\n\n                        Failure behavior here depends on how many BackendRefs are specified and\n                        how many are invalid.\n\n\n                        If *all* entries in BackendRefs are invalid, and there are also no filters\n                        specified in this route rule, *all* traffic which matches this rule MUST\n                        receive a 500 status code.\n\n\n                        See the HTTPBackendRef definition for the rules about what makes a single\n                        HTTPBackendRef invalid.\n\n\n                        When a HTTPBackendRef is invalid, 500 status codes MUST be returned for\n                        requests that would have otherwise been routed to an invalid backend. If\n                        multiple backends are specified, and some are invalid, the proportion of\n                        requests that would otherwise have been routed to an invalid backend\n                        MUST receive a 500 status code.\n\n\n                        For example, if two backends are specified with equal weights, and one is\n                        invalid, 50 percent of traffic must receive a 500. Implementations may\n                        choose how that 50 percent is determined.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Extended for Kubernetes ServiceImport\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Core\n                      items:\n                        description: |-\n                          HTTPBackendRef defines how a HTTPRoute forwards a HTTP request.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n                        properties:\n                          filters:\n                            description: |-\n                              Filters defined at this level should be executed if and only if the\n                              request is being forwarded to the backend defined here.\n\n\n                              Support: Implementation-specific (For broader support of filters, use the\n                              Filters field in HTTPRouteRule.)\n                            items:\n                              description: |-\n                                HTTPRouteFilter defines processing steps that must be completed during the\n                                request or response lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway implementations. Some\n                                examples include request or response modification, implementing\n                                authentication strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type of the filter.\n                              properties:\n                                extensionRef:\n                                  description: |-\n                                    ExtensionRef is an optional, implementation-specific extension to the\n                                    \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                                    \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                                    extended filters.\n\n\n                                    This filter can be used multiple times within the same rule.\n\n\n                                    Support: Implementation-specific\n                                  properties:\n                                    group:\n                                      description: |-\n                                        Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                        When unspecified or empty string, core API group is inferred.\n                                      maxLength: 253\n                                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    kind:\n                                      description: Kind is kind of the referent. For\n                                        example \"HTTPRoute\" or \"Service\".\n                                      maxLength: 63\n                                      minLength: 1\n                                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                      type: string\n                                    name:\n                                      description: Name is the name of the referent.\n                                      maxLength: 253\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - group\n                                  - kind\n                                  - name\n                                  type: object\n                                requestHeaderModifier:\n                                  description: |-\n                                    RequestHeaderModifier defines a schema for a filter that modifies request\n                                    headers.\n\n\n                                    Support: Core\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestMirror:\n                                  description: |-\n                                    RequestMirror defines a schema for a filter that mirrors requests.\n                                    Requests are sent to the specified destination, but responses from\n                                    that destination are ignored.\n\n\n                                    This filter can be used multiple times within the same rule. Note that\n                                    not all implementations will be able to support mirroring to multiple\n                                    backends.\n\n\n                                    Support: Extended\n                                  properties:\n                                    backendRef:\n                                      description: |-\n                                        BackendRef references a resource where mirrored requests are sent.\n\n\n                                        Mirrored requests must be sent only to a single destination endpoint\n                                        within this BackendRef, irrespective of how many endpoints are present\n                                        within this BackendRef.\n\n\n                                        If the referent cannot be found, this BackendRef is invalid and must be\n                                        dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                        condition on the Route status is set to `status: False` and not configure\n                                        this backend in the underlying implementation.\n\n\n                                        If there is a cross-namespace reference to an *existing* object\n                                        that is not allowed by a ReferenceGrant, the controller must ensure the\n                                        \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                        with the \"RefNotPermitted\" reason and not configure this backend in the\n                                        underlying implementation.\n\n\n                                        In either error case, the Message of the `ResolvedRefs` Condition\n                                        should be used to provide more detail about the problem.\n\n\n                                        Support: Extended for Kubernetes Service\n\n\n                                        Support: Implementation-specific for any other resource\n                                      properties:\n                                        group:\n                                          default: \"\"\n                                          description: |-\n                                            Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                            When unspecified or empty string, core API group is inferred.\n                                          maxLength: 253\n                                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                          type: string\n                                        kind:\n                                          default: Service\n                                          description: |-\n                                            Kind is the Kubernetes resource kind of the referent. For example\n                                            \"Service\".\n\n\n                                            Defaults to \"Service\" when not specified.\n\n\n                                            ExternalName services can refer to CNAME DNS records that may live\n                                            outside of the cluster and as such are difficult to reason about in\n                                            terms of conformance. They also may not be safe to forward to (see\n                                            CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                            support ExternalName Services.\n\n\n                                            Support: Core (Services with a type other than ExternalName)\n\n\n                                            Support: Implementation-specific (Services with type ExternalName)\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                          type: string\n                                        name:\n                                          description: Name is the name of the referent.\n                                          maxLength: 253\n                                          minLength: 1\n                                          type: string\n                                        namespace:\n                                          description: |-\n                                            Namespace is the namespace of the backend. When unspecified, the local\n                                            namespace is inferred.\n\n\n                                            Note that when a namespace different than the local namespace is specified,\n                                            a ReferenceGrant object is required in the referent namespace to allow that\n                                            namespace's owner to accept the reference. See the ReferenceGrant\n                                            documentation for details.\n\n\n                                            Support: Core\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                          type: string\n                                        port:\n                                          description: |-\n                                            Port specifies the destination port number to use for this resource.\n                                            Port is required when the referent is a Kubernetes Service. In this\n                                            case, the port number is the service port number, not the target port.\n                                            For other resources, destination port might be derived from the referent\n                                            resource or this field.\n                                          format: int32\n                                          maximum: 65535\n                                          minimum: 1\n                                          type: integer\n                                      required:\n                                      - name\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: Must have port for Service reference\n                                        rule: '(size(self.group) == 0 && self.kind\n                                          == ''Service'') ? has(self.port) : true'\n                                  required:\n                                  - backendRef\n                                  type: object\n                                requestRedirect:\n                                  description: |-\n                                    RequestRedirect defines a schema for a filter that responds to the\n                                    request with an HTTP redirection.\n\n\n                                    Support: Core\n                                  properties:\n                                    hostname:\n                                      description: |-\n                                        Hostname is the hostname to be used in the value of the `Location`\n                                        header in the response.\n                                        When empty, the hostname in the `Host` header of the request is used.\n\n\n                                        Support: Core\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: |-\n                                        Path defines parameters used to modify the path of the incoming request.\n                                        The modified path is then used to construct the `Location` header. When\n                                        empty, the request path is used as-is.\n\n\n                                        Support: Extended\n                                      properties:\n                                        replaceFullPath:\n                                          description: |-\n                                            ReplaceFullPath specifies the value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: |-\n                                            ReplacePrefixMatch specifies the value with which to replace the prefix\n                                            match of a request during a rewrite or redirect. For example, a request\n                                            to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                            of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                            Note that this matches the behavior of the PathPrefix match type. This\n                                            matches full path elements. A path element refers to the list of labels\n                                            in the path split by the `/` separator. When specified, a trailing `/` is\n                                            ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                            match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                            ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                            Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                            the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                            Request Path | Prefix Match | Replace Prefix | Modified Path\n                                            -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                            /foo         | /foo         | /xyz           | /xyz\n                                            /foo/        | /foo         | /xyz           | /xyz/\n                                            /foo/bar     | /foo         | <empty string> | /bar\n                                            /foo/        | /foo         | <empty string> | /\n                                            /foo         | /foo         | <empty string> | /\n                                            /foo/        | /foo         | /              | /\n                                            /foo         | /foo         | /              | /\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: |-\n                                            Type defines the type of path modifier. Additional types may be\n                                            added in a future release of the API.\n\n\n                                            Note that values may be added to this enum, implementations\n                                            must ensure that unknown values will not cause a crash.\n\n\n                                            Unknown values here must result in the implementation setting the\n                                            Accepted Condition for the Route to `status: False`, with a\n                                            Reason of `UnsupportedValue`.\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: replaceFullPath must be specified\n                                          when type is set to 'ReplaceFullPath'\n                                        rule: 'self.type == ''ReplaceFullPath'' ?\n                                          has(self.replaceFullPath) : true'\n                                      - message: type must be 'ReplaceFullPath' when\n                                          replaceFullPath is set\n                                        rule: 'has(self.replaceFullPath) ? self.type\n                                          == ''ReplaceFullPath'' : true'\n                                      - message: replacePrefixMatch must be specified\n                                          when type is set to 'ReplacePrefixMatch'\n                                        rule: 'self.type == ''ReplacePrefixMatch''\n                                          ? has(self.replacePrefixMatch) : true'\n                                      - message: type must be 'ReplacePrefixMatch'\n                                          when replacePrefixMatch is set\n                                        rule: 'has(self.replacePrefixMatch) ? self.type\n                                          == ''ReplacePrefixMatch'' : true'\n                                    port:\n                                      description: |-\n                                        Port is the port to be used in the value of the `Location`\n                                        header in the response.\n\n\n                                        If no port is specified, the redirect port MUST be derived using the\n                                        following rules:\n\n\n                                        * If redirect scheme is not-empty, the redirect port MUST be the well-known\n                                          port associated with the redirect scheme. Specifically \"http\" to port 80\n                                          and \"https\" to port 443. If the redirect scheme does not have a\n                                          well-known port, the listener port of the Gateway SHOULD be used.\n                                        * If redirect scheme is empty, the redirect port MUST be the Gateway\n                                          Listener port.\n\n\n                                        Implementations SHOULD NOT add the port number in the 'Location'\n                                        header in the following cases:\n\n\n                                        * A Location header that will use HTTP (whether that is determined via\n                                          the Listener protocol or the Scheme field) _and_ use port 80.\n                                        * A Location header that will use HTTPS (whether that is determined via\n                                          the Listener protocol or the Scheme field) _and_ use port 443.\n\n\n                                        Support: Extended\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: |-\n                                        Scheme is the scheme to be used in the value of the `Location` header in\n                                        the response. When empty, the scheme of the request is used.\n\n\n                                        Scheme redirects can affect the port of the redirect, for more information,\n                                        refer to the documentation for the port field of this filter.\n\n\n                                        Note that values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause a crash.\n\n\n                                        Unknown values here must result in the implementation setting the\n                                        Accepted Condition for the Route to `status: False`, with a\n                                        Reason of `UnsupportedValue`.\n\n\n                                        Support: Extended\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: |-\n                                        StatusCode is the HTTP status code to be used in response.\n\n\n                                        Note that values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause a crash.\n\n\n                                        Unknown values here must result in the implementation setting the\n                                        Accepted Condition for the Route to `status: False`, with a\n                                        Reason of `UnsupportedValue`.\n\n\n                                        Support: Core\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: |-\n                                    ResponseHeaderModifier defines a schema for a filter that modifies response\n                                    headers.\n\n\n                                    Support: Extended\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: |-\n                                    Type identifies the type of filter to apply. As with other API fields,\n                                    types are classified into three conformance levels:\n\n\n                                    - Core: Filter types and their corresponding configuration defined by\n                                      \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                      implementations must support core filters.\n\n\n                                    - Extended: Filter types and their corresponding configuration defined by\n                                      \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                      are encouraged to support extended filters.\n\n\n                                    - Implementation-specific: Filters that are defined and supported by\n                                      specific vendors.\n                                      In the future, filters showing convergence in behavior across multiple\n                                      implementations will be considered for inclusion in extended or core\n                                      conformance levels. Filter-specific configuration for such filters\n                                      is specified using the ExtensionRef field. `Type` should be set to\n                                      \"ExtensionRef\" for custom filters.\n\n\n                                    Implementers are encouraged to define custom implementation types to\n                                    extend the core API with implementation-specific behavior.\n\n\n                                    If a reference to a custom filter type cannot be resolved, the filter\n                                    MUST NOT be skipped. Instead, requests that would have been processed by\n                                    that filter MUST receive a HTTP error response.\n\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause a crash.\n\n\n                                    Unknown values here must result in the implementation setting the\n                                    Accepted Condition for the Route to `status: False`, with a\n                                    Reason of `UnsupportedValue`.\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestMirror\n                                  - RequestRedirect\n                                  - URLRewrite\n                                  - ExtensionRef\n                                  type: string\n                                urlRewrite:\n                                  description: |-\n                                    URLRewrite defines a schema for a filter that modifies a request during forwarding.\n\n\n                                    Support: Extended\n                                  properties:\n                                    hostname:\n                                      description: |-\n                                        Hostname is the value to be used to replace the Host header value during\n                                        forwarding.\n\n\n                                        Support: Extended\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: |-\n                                        Path defines a path rewrite.\n\n\n                                        Support: Extended\n                                      properties:\n                                        replaceFullPath:\n                                          description: |-\n                                            ReplaceFullPath specifies the value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: |-\n                                            ReplacePrefixMatch specifies the value with which to replace the prefix\n                                            match of a request during a rewrite or redirect. For example, a request\n                                            to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                            of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                            Note that this matches the behavior of the PathPrefix match type. This\n                                            matches full path elements. A path element refers to the list of labels\n                                            in the path split by the `/` separator. When specified, a trailing `/` is\n                                            ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                            match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                            ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                            Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                            the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                            Request Path | Prefix Match | Replace Prefix | Modified Path\n                                            -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                            /foo         | /foo         | /xyz           | /xyz\n                                            /foo/        | /foo         | /xyz           | /xyz/\n                                            /foo/bar     | /foo         | <empty string> | /bar\n                                            /foo/        | /foo         | <empty string> | /\n                                            /foo         | /foo         | <empty string> | /\n                                            /foo/        | /foo         | /              | /\n                                            /foo         | /foo         | /              | /\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: |-\n                                            Type defines the type of path modifier. Additional types may be\n                                            added in a future release of the API.\n\n\n                                            Note that values may be added to this enum, implementations\n                                            must ensure that unknown values will not cause a crash.\n\n\n                                            Unknown values here must result in the implementation setting the\n                                            Accepted Condition for the Route to `status: False`, with a\n                                            Reason of `UnsupportedValue`.\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: replaceFullPath must be specified\n                                          when type is set to 'ReplaceFullPath'\n                                        rule: 'self.type == ''ReplaceFullPath'' ?\n                                          has(self.replaceFullPath) : true'\n                                      - message: type must be 'ReplaceFullPath' when\n                                          replaceFullPath is set\n                                        rule: 'has(self.replaceFullPath) ? self.type\n                                          == ''ReplaceFullPath'' : true'\n                                      - message: replacePrefixMatch must be specified\n                                          when type is set to 'ReplacePrefixMatch'\n                                        rule: 'self.type == ''ReplacePrefixMatch''\n                                          ? has(self.replacePrefixMatch) : true'\n                                      - message: type must be 'ReplacePrefixMatch'\n                                          when replacePrefixMatch is set\n                                        rule: 'has(self.replacePrefixMatch) ? self.type\n                                          == ''ReplacePrefixMatch'' : true'\n                                  type: object\n                              required:\n                              - type\n                              type: object\n                              x-kubernetes-validations:\n                              - message: filter.requestHeaderModifier must be nil\n                                  if the filter.type is not RequestHeaderModifier\n                                rule: '!(has(self.requestHeaderModifier) && self.type\n                                  != ''RequestHeaderModifier'')'\n                              - message: filter.requestHeaderModifier must be specified\n                                  for RequestHeaderModifier filter.type\n                                rule: '!(!has(self.requestHeaderModifier) && self.type\n                                  == ''RequestHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be nil\n                                  if the filter.type is not ResponseHeaderModifier\n                                rule: '!(has(self.responseHeaderModifier) && self.type\n                                  != ''ResponseHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be specified\n                                  for ResponseHeaderModifier filter.type\n                                rule: '!(!has(self.responseHeaderModifier) && self.type\n                                  == ''ResponseHeaderModifier'')'\n                              - message: filter.requestMirror must be nil if the filter.type\n                                  is not RequestMirror\n                                rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                              - message: filter.requestMirror must be specified for\n                                  RequestMirror filter.type\n                                rule: '!(!has(self.requestMirror) && self.type ==\n                                  ''RequestMirror'')'\n                              - message: filter.requestRedirect must be nil if the\n                                  filter.type is not RequestRedirect\n                                rule: '!(has(self.requestRedirect) && self.type !=\n                                  ''RequestRedirect'')'\n                              - message: filter.requestRedirect must be specified\n                                  for RequestRedirect filter.type\n                                rule: '!(!has(self.requestRedirect) && self.type ==\n                                  ''RequestRedirect'')'\n                              - message: filter.urlRewrite must be nil if the filter.type\n                                  is not URLRewrite\n                                rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')'\n                              - message: filter.urlRewrite must be specified for URLRewrite\n                                  filter.type\n                                rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')'\n                              - message: filter.extensionRef must be nil if the filter.type\n                                  is not ExtensionRef\n                                rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                              - message: filter.extensionRef must be specified for\n                                  ExtensionRef filter.type\n                                rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-validations:\n                            - message: May specify either httpRouteFilterRequestRedirect\n                                or httpRouteFilterRequestRewrite, but not both\n                              rule: '!(self.exists(f, f.type == ''RequestRedirect'')\n                                && self.exists(f, f.type == ''URLRewrite''))'\n                            - message: May specify either httpRouteFilterRequestRedirect\n                                or httpRouteFilterRequestRewrite, but not both\n                              rule: '!(self.exists(f, f.type == ''RequestRedirect'')\n                                && self.exists(f, f.type == ''URLRewrite''))'\n                            - message: RequestHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                                <= 1\n                            - message: ResponseHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                                <= 1\n                            - message: RequestRedirect filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestRedirect').size()\n                                <= 1\n                            - message: URLRewrite filter cannot be repeated\n                              rule: self.filter(f, f.type == 'URLRewrite').size()\n                                <= 1\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: |-\n                        Filters define the filters that are applied to requests that match\n                        this rule.\n\n\n                        Wherever possible, implementations SHOULD implement filters in the order\n                        they are specified.\n\n\n                        Implementations MAY choose to implement this ordering strictly, rejecting\n                        any combination or order of filters that can not be supported. If implementations\n                        choose a strict interpretation of filter ordering, they MUST clearly document\n                        that behavior.\n\n\n                        To reject an invalid combination or order of filters, implementations SHOULD\n                        consider the Route Rules with this configuration invalid. If all Route Rules\n                        in a Route are invalid, the entire Route would be considered invalid. If only\n                        a portion of Route Rules are invalid, implementations MUST set the\n                        \"PartiallyInvalid\" condition for the Route.\n\n\n                        Conformance-levels at this level are defined based on the type of filter:\n\n\n                        - ALL core filters MUST be supported by all implementations.\n                        - Implementers are encouraged to support extended filters.\n                        - Implementation-specific custom filters have no API guarantees across\n                          implementations.\n\n\n                        Specifying the same filter multiple times is not supported unless explicitly\n                        indicated in the filter.\n\n\n                        All filters are expected to be compatible with each other except for the\n                        URLRewrite and RequestRedirect filters, which may not be combined. If an\n                        implementation can not support other combinations of filters, they must clearly\n                        document that limitation. In cases where incompatible or unsupported\n                        filters are specified and cause the `Accepted` condition to be set to status\n                        `False`, implementations may use the `IncompatibleFilters` reason to specify\n                        this configuration error.\n\n\n                        Support: Core\n                      items:\n                        description: |-\n                          HTTPRouteFilter defines processing steps that must be completed during the\n                          request or response lifecycle. HTTPRouteFilters are meant as an extension\n                          point to express processing that may be done in Gateway implementations. Some\n                          examples include request or response modification, implementing\n                          authentication strategies, rate-limiting, and traffic shaping. API\n                          guarantee/conformance is defined based on the type of the filter.\n                        properties:\n                          extensionRef:\n                            description: |-\n                              ExtensionRef is an optional, implementation-specific extension to the\n                              \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                              \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                              extended filters.\n\n\n                              This filter can be used multiple times within the same rule.\n\n\n                              Support: Implementation-specific\n                            properties:\n                              group:\n                                description: |-\n                                  Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                  When unspecified or empty string, core API group is inferred.\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                description: Kind is kind of the referent. For example\n                                  \"HTTPRoute\" or \"Service\".\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: Name is the name of the referent.\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                            required:\n                            - group\n                            - kind\n                            - name\n                            type: object\n                          requestHeaderModifier:\n                            description: |-\n                              RequestHeaderModifier defines a schema for a filter that modifies request\n                              headers.\n\n\n                              Support: Core\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestMirror:\n                            description: |-\n                              RequestMirror defines a schema for a filter that mirrors requests.\n                              Requests are sent to the specified destination, but responses from\n                              that destination are ignored.\n\n\n                              This filter can be used multiple times within the same rule. Note that\n                              not all implementations will be able to support mirroring to multiple\n                              backends.\n\n\n                              Support: Extended\n                            properties:\n                              backendRef:\n                                description: |-\n                                  BackendRef references a resource where mirrored requests are sent.\n\n\n                                  Mirrored requests must be sent only to a single destination endpoint\n                                  within this BackendRef, irrespective of how many endpoints are present\n                                  within this BackendRef.\n\n\n                                  If the referent cannot be found, this BackendRef is invalid and must be\n                                  dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                  condition on the Route status is set to `status: False` and not configure\n                                  this backend in the underlying implementation.\n\n\n                                  If there is a cross-namespace reference to an *existing* object\n                                  that is not allowed by a ReferenceGrant, the controller must ensure the\n                                  \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                  with the \"RefNotPermitted\" reason and not configure this backend in the\n                                  underlying implementation.\n\n\n                                  In either error case, the Message of the `ResolvedRefs` Condition\n                                  should be used to provide more detail about the problem.\n\n\n                                  Support: Extended for Kubernetes Service\n\n\n                                  Support: Implementation-specific for any other resource\n                                properties:\n                                  group:\n                                    default: \"\"\n                                    description: |-\n                                      Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                      When unspecified or empty string, core API group is inferred.\n                                    maxLength: 253\n                                    pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                    type: string\n                                  kind:\n                                    default: Service\n                                    description: |-\n                                      Kind is the Kubernetes resource kind of the referent. For example\n                                      \"Service\".\n\n\n                                      Defaults to \"Service\" when not specified.\n\n\n                                      ExternalName services can refer to CNAME DNS records that may live\n                                      outside of the cluster and as such are difficult to reason about in\n                                      terms of conformance. They also may not be safe to forward to (see\n                                      CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                      support ExternalName Services.\n\n\n                                      Support: Core (Services with a type other than ExternalName)\n\n\n                                      Support: Implementation-specific (Services with type ExternalName)\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                    type: string\n                                  name:\n                                    description: Name is the name of the referent.\n                                    maxLength: 253\n                                    minLength: 1\n                                    type: string\n                                  namespace:\n                                    description: |-\n                                      Namespace is the namespace of the backend. When unspecified, the local\n                                      namespace is inferred.\n\n\n                                      Note that when a namespace different than the local namespace is specified,\n                                      a ReferenceGrant object is required in the referent namespace to allow that\n                                      namespace's owner to accept the reference. See the ReferenceGrant\n                                      documentation for details.\n\n\n                                      Support: Core\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                    type: string\n                                  port:\n                                    description: |-\n                                      Port specifies the destination port number to use for this resource.\n                                      Port is required when the referent is a Kubernetes Service. In this\n                                      case, the port number is the service port number, not the target port.\n                                      For other resources, destination port might be derived from the referent\n                                      resource or this field.\n                                    format: int32\n                                    maximum: 65535\n                                    minimum: 1\n                                    type: integer\n                                required:\n                                - name\n                                type: object\n                                x-kubernetes-validations:\n                                - message: Must have port for Service reference\n                                  rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                                    ? has(self.port) : true'\n                            required:\n                            - backendRef\n                            type: object\n                          requestRedirect:\n                            description: |-\n                              RequestRedirect defines a schema for a filter that responds to the\n                              request with an HTTP redirection.\n\n\n                              Support: Core\n                            properties:\n                              hostname:\n                                description: |-\n                                  Hostname is the hostname to be used in the value of the `Location`\n                                  header in the response.\n                                  When empty, the hostname in the `Host` header of the request is used.\n\n\n                                  Support: Core\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: |-\n                                  Path defines parameters used to modify the path of the incoming request.\n                                  The modified path is then used to construct the `Location` header. When\n                                  empty, the request path is used as-is.\n\n\n                                  Support: Extended\n                                properties:\n                                  replaceFullPath:\n                                    description: |-\n                                      ReplaceFullPath specifies the value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: |-\n                                      ReplacePrefixMatch specifies the value with which to replace the prefix\n                                      match of a request during a rewrite or redirect. For example, a request\n                                      to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                      of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                      Note that this matches the behavior of the PathPrefix match type. This\n                                      matches full path elements. A path element refers to the list of labels\n                                      in the path split by the `/` separator. When specified, a trailing `/` is\n                                      ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                      match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                      ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                      Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                      the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                      Request Path | Prefix Match | Replace Prefix | Modified Path\n                                      -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                      /foo         | /foo         | /xyz           | /xyz\n                                      /foo/        | /foo         | /xyz           | /xyz/\n                                      /foo/bar     | /foo         | <empty string> | /bar\n                                      /foo/        | /foo         | <empty string> | /\n                                      /foo         | /foo         | <empty string> | /\n                                      /foo/        | /foo         | /              | /\n                                      /foo         | /foo         | /              | /\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: |-\n                                      Type defines the type of path modifier. Additional types may be\n                                      added in a future release of the API.\n\n\n                                      Note that values may be added to this enum, implementations\n                                      must ensure that unknown values will not cause a crash.\n\n\n                                      Unknown values here must result in the implementation setting the\n                                      Accepted Condition for the Route to `status: False`, with a\n                                      Reason of `UnsupportedValue`.\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                                x-kubernetes-validations:\n                                - message: replaceFullPath must be specified when\n                                    type is set to 'ReplaceFullPath'\n                                  rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath)\n                                    : true'\n                                - message: type must be 'ReplaceFullPath' when replaceFullPath\n                                    is set\n                                  rule: 'has(self.replaceFullPath) ? self.type ==\n                                    ''ReplaceFullPath'' : true'\n                                - message: replacePrefixMatch must be specified when\n                                    type is set to 'ReplacePrefixMatch'\n                                  rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch)\n                                    : true'\n                                - message: type must be 'ReplacePrefixMatch' when\n                                    replacePrefixMatch is set\n                                  rule: 'has(self.replacePrefixMatch) ? self.type\n                                    == ''ReplacePrefixMatch'' : true'\n                              port:\n                                description: |-\n                                  Port is the port to be used in the value of the `Location`\n                                  header in the response.\n\n\n                                  If no port is specified, the redirect port MUST be derived using the\n                                  following rules:\n\n\n                                  * If redirect scheme is not-empty, the redirect port MUST be the well-known\n                                    port associated with the redirect scheme. Specifically \"http\" to port 80\n                                    and \"https\" to port 443. If the redirect scheme does not have a\n                                    well-known port, the listener port of the Gateway SHOULD be used.\n                                  * If redirect scheme is empty, the redirect port MUST be the Gateway\n                                    Listener port.\n\n\n                                  Implementations SHOULD NOT add the port number in the 'Location'\n                                  header in the following cases:\n\n\n                                  * A Location header that will use HTTP (whether that is determined via\n                                    the Listener protocol or the Scheme field) _and_ use port 80.\n                                  * A Location header that will use HTTPS (whether that is determined via\n                                    the Listener protocol or the Scheme field) _and_ use port 443.\n\n\n                                  Support: Extended\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: |-\n                                  Scheme is the scheme to be used in the value of the `Location` header in\n                                  the response. When empty, the scheme of the request is used.\n\n\n                                  Scheme redirects can affect the port of the redirect, for more information,\n                                  refer to the documentation for the port field of this filter.\n\n\n                                  Note that values may be added to this enum, implementations\n                                  must ensure that unknown values will not cause a crash.\n\n\n                                  Unknown values here must result in the implementation setting the\n                                  Accepted Condition for the Route to `status: False`, with a\n                                  Reason of `UnsupportedValue`.\n\n\n                                  Support: Extended\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: |-\n                                  StatusCode is the HTTP status code to be used in response.\n\n\n                                  Note that values may be added to this enum, implementations\n                                  must ensure that unknown values will not cause a crash.\n\n\n                                  Unknown values here must result in the implementation setting the\n                                  Accepted Condition for the Route to `status: False`, with a\n                                  Reason of `UnsupportedValue`.\n\n\n                                  Support: Core\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          responseHeaderModifier:\n                            description: |-\n                              ResponseHeaderModifier defines a schema for a filter that modifies response\n                              headers.\n\n\n                              Support: Extended\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          type:\n                            description: |-\n                              Type identifies the type of filter to apply. As with other API fields,\n                              types are classified into three conformance levels:\n\n\n                              - Core: Filter types and their corresponding configuration defined by\n                                \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                implementations must support core filters.\n\n\n                              - Extended: Filter types and their corresponding configuration defined by\n                                \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                are encouraged to support extended filters.\n\n\n                              - Implementation-specific: Filters that are defined and supported by\n                                specific vendors.\n                                In the future, filters showing convergence in behavior across multiple\n                                implementations will be considered for inclusion in extended or core\n                                conformance levels. Filter-specific configuration for such filters\n                                is specified using the ExtensionRef field. `Type` should be set to\n                                \"ExtensionRef\" for custom filters.\n\n\n                              Implementers are encouraged to define custom implementation types to\n                              extend the core API with implementation-specific behavior.\n\n\n                              If a reference to a custom filter type cannot be resolved, the filter\n                              MUST NOT be skipped. Instead, requests that would have been processed by\n                              that filter MUST receive a HTTP error response.\n\n\n                              Note that values may be added to this enum, implementations\n                              must ensure that unknown values will not cause a crash.\n\n\n                              Unknown values here must result in the implementation setting the\n                              Accepted Condition for the Route to `status: False`, with a\n                              Reason of `UnsupportedValue`.\n                            enum:\n                            - RequestHeaderModifier\n                            - ResponseHeaderModifier\n                            - RequestMirror\n                            - RequestRedirect\n                            - URLRewrite\n                            - ExtensionRef\n                            type: string\n                          urlRewrite:\n                            description: |-\n                              URLRewrite defines a schema for a filter that modifies a request during forwarding.\n\n\n                              Support: Extended\n                            properties:\n                              hostname:\n                                description: |-\n                                  Hostname is the value to be used to replace the Host header value during\n                                  forwarding.\n\n\n                                  Support: Extended\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: |-\n                                  Path defines a path rewrite.\n\n\n                                  Support: Extended\n                                properties:\n                                  replaceFullPath:\n                                    description: |-\n                                      ReplaceFullPath specifies the value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: |-\n                                      ReplacePrefixMatch specifies the value with which to replace the prefix\n                                      match of a request during a rewrite or redirect. For example, a request\n                                      to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                      of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                      Note that this matches the behavior of the PathPrefix match type. This\n                                      matches full path elements. A path element refers to the list of labels\n                                      in the path split by the `/` separator. When specified, a trailing `/` is\n                                      ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                      match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                      ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                      Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                      the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                      Request Path | Prefix Match | Replace Prefix | Modified Path\n                                      -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                      /foo         | /foo         | /xyz           | /xyz\n                                      /foo/        | /foo         | /xyz           | /xyz/\n                                      /foo/bar     | /foo         | <empty string> | /bar\n                                      /foo/        | /foo         | <empty string> | /\n                                      /foo         | /foo         | <empty string> | /\n                                      /foo/        | /foo         | /              | /\n                                      /foo         | /foo         | /              | /\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: |-\n                                      Type defines the type of path modifier. Additional types may be\n                                      added in a future release of the API.\n\n\n                                      Note that values may be added to this enum, implementations\n                                      must ensure that unknown values will not cause a crash.\n\n\n                                      Unknown values here must result in the implementation setting the\n                                      Accepted Condition for the Route to `status: False`, with a\n                                      Reason of `UnsupportedValue`.\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                                x-kubernetes-validations:\n                                - message: replaceFullPath must be specified when\n                                    type is set to 'ReplaceFullPath'\n                                  rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath)\n                                    : true'\n                                - message: type must be 'ReplaceFullPath' when replaceFullPath\n                                    is set\n                                  rule: 'has(self.replaceFullPath) ? self.type ==\n                                    ''ReplaceFullPath'' : true'\n                                - message: replacePrefixMatch must be specified when\n                                    type is set to 'ReplacePrefixMatch'\n                                  rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch)\n                                    : true'\n                                - message: type must be 'ReplacePrefixMatch' when\n                                    replacePrefixMatch is set\n                                  rule: 'has(self.replacePrefixMatch) ? self.type\n                                    == ''ReplacePrefixMatch'' : true'\n                            type: object\n                        required:\n                        - type\n                        type: object\n                        x-kubernetes-validations:\n                        - message: filter.requestHeaderModifier must be nil if the\n                            filter.type is not RequestHeaderModifier\n                          rule: '!(has(self.requestHeaderModifier) && self.type !=\n                            ''RequestHeaderModifier'')'\n                        - message: filter.requestHeaderModifier must be specified\n                            for RequestHeaderModifier filter.type\n                          rule: '!(!has(self.requestHeaderModifier) && self.type ==\n                            ''RequestHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be nil if the\n                            filter.type is not ResponseHeaderModifier\n                          rule: '!(has(self.responseHeaderModifier) && self.type !=\n                            ''ResponseHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be specified\n                            for ResponseHeaderModifier filter.type\n                          rule: '!(!has(self.responseHeaderModifier) && self.type\n                            == ''ResponseHeaderModifier'')'\n                        - message: filter.requestMirror must be nil if the filter.type\n                            is not RequestMirror\n                          rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                        - message: filter.requestMirror must be specified for RequestMirror\n                            filter.type\n                          rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')'\n                        - message: filter.requestRedirect must be nil if the filter.type\n                            is not RequestRedirect\n                          rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')'\n                        - message: filter.requestRedirect must be specified for RequestRedirect\n                            filter.type\n                          rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')'\n                        - message: filter.urlRewrite must be nil if the filter.type\n                            is not URLRewrite\n                          rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')'\n                        - message: filter.urlRewrite must be specified for URLRewrite\n                            filter.type\n                          rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')'\n                        - message: filter.extensionRef must be nil if the filter.type\n                            is not ExtensionRef\n                          rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                        - message: filter.extensionRef must be specified for ExtensionRef\n                            filter.type\n                          rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                      maxItems: 16\n                      type: array\n                      x-kubernetes-validations:\n                      - message: May specify either httpRouteFilterRequestRedirect\n                          or httpRouteFilterRequestRewrite, but not both\n                        rule: '!(self.exists(f, f.type == ''RequestRedirect'') &&\n                          self.exists(f, f.type == ''URLRewrite''))'\n                      - message: RequestHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                          <= 1\n                      - message: ResponseHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                          <= 1\n                      - message: RequestRedirect filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestRedirect').size() <=\n                          1\n                      - message: URLRewrite filter cannot be repeated\n                        rule: self.filter(f, f.type == 'URLRewrite').size() <= 1\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: |-\n                        Matches define conditions used for matching the rule against incoming\n                        HTTP requests. Each match is independent, i.e. this rule will be matched\n                        if **any** one of the matches is satisfied.\n\n\n                        For example, take the following matches configuration:\n\n\n                        ```\n                        matches:\n                        - path:\n                            value: \"/foo\"\n                          headers:\n                          - name: \"version\"\n                            value: \"v2\"\n                        - path:\n                            value: \"/v2/foo\"\n                        ```\n\n\n                        For a request to match against this rule, a request must satisfy\n                        EITHER of the two conditions:\n\n\n                        - path prefixed with `/foo` AND contains the header `version: v2`\n                        - path prefix of `/v2/foo`\n\n\n                        See the documentation for HTTPRouteMatch on how to specify multiple\n                        match conditions that should be ANDed together.\n\n\n                        If no matches are specified, the default is a prefix\n                        path match on \"/\", which has the effect of matching every\n                        HTTP request.\n\n\n                        Proxy or Load Balancer routing configuration generated from HTTPRoutes\n                        MUST prioritize matches based on the following criteria, continuing on\n                        ties. Across all rules specified on applicable Routes, precedence must be\n                        given to the match having:\n\n\n                        * \"Exact\" path match.\n                        * \"Prefix\" path match with largest number of characters.\n                        * Method match.\n                        * Largest number of header matches.\n                        * Largest number of query param matches.\n\n\n                        Note: The precedence of RegularExpression path matches are implementation-specific.\n\n\n                        If ties still exist across multiple Routes, matching precedence MUST be\n                        determined in order of the following criteria, continuing on ties:\n\n\n                        * The oldest Route based on creation timestamp.\n                        * The Route appearing first in alphabetical order by\n                          \"{namespace}/{name}\".\n\n\n                        If ties still exist within an HTTPRoute, matching precedence MUST be granted\n                        to the FIRST matching rule (in list order) with a match meeting the above\n                        criteria.\n\n\n                        When no rules matching a request have been successfully attached to the\n                        parent a request is coming from, a HTTP 404 status code MUST be returned.\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given\\naction. Multiple match types\n                          are ANDed together, i.e. the match will\\nevaluate to true\n                          only if all conditions are satisfied.\\n\\n\\nFor example,\n                          the match below will match a HTTP request only if its path\\nstarts\n                          with `/foo` AND it contains the `version: v1` header:\\n\\n\\n```\\nmatch:\\n\\n\\n\\tpath:\\n\\t\n                          \\ value: \\\"/foo\\\"\\n\\theaders:\\n\\t- name: \\\"version\\\"\\n\\t\n                          \\ value \\\"v1\\\"\\n\\n\\n```\"\n                        properties:\n                          headers:\n                            description: |-\n                              Headers specifies HTTP request header matchers. Multiple match values are\n                              ANDed together, meaning, a request must match all the specified headers\n                              to select the route.\n                            items:\n                              description: |-\n                                HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request\n                                headers.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                    case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                    If multiple entries specify equivalent header names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be ignored. Due to the\n                                    case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                    equivalent.\n\n\n                                    When a header is repeated in an HTTP request, it is\n                                    implementation-specific behavior as to how this is represented.\n                                    Generally, proxies should follow the guidance from the RFC:\n                                    https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding\n                                    processing a repeated header, with special handling for \"Set-Cookie\".\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: |-\n                                    Type specifies how to match against the value of the header.\n\n\n                                    Support: Core (Exact)\n\n\n                                    Support: Implementation-specific (RegularExpression)\n\n\n                                    Since RegularExpression HeaderMatchType has implementation-specific\n                                    conformance, implementations can support POSIX, PCRE or any other dialects\n                                    of regular expressions. Please read the implementation's documentation to\n                                    determine the supported dialect.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: |-\n                              Method specifies HTTP method matcher.\n                              When specified, this route will be matched only if the request has the\n                              specified method.\n\n\n                              Support: Extended\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: |-\n                              Path specifies a HTTP request path matcher. If this field is not\n                              specified, a default prefix match on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: |-\n                                  Type specifies how to match against the path Value.\n\n\n                                  Support: Core (Exact, PathPrefix)\n\n\n                                  Support: Implementation-specific (RegularExpression)\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                            x-kubernetes-validations:\n                            - message: value must be an absolute path and start with\n                                '/' when type one of ['Exact', 'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'')\n                                : true'\n                            - message: must not contain '//' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'')\n                                : true'\n                            - message: must not contain '/./' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'')\n                                : true'\n                            - message: must not contain '/../' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'')\n                                : true'\n                            - message: must not contain '%2f' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'')\n                                : true'\n                            - message: must not contain '%2F' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'')\n                                : true'\n                            - message: must not contain '#' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'')\n                                : true'\n                            - message: must not end with '/..' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'')\n                                : true'\n                            - message: must not end with '/.' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'')\n                                : true'\n                            - message: type must be one of ['Exact', 'PathPrefix',\n                                'RegularExpression']\n                              rule: self.type in ['Exact','PathPrefix'] || self.type\n                                == 'RegularExpression'\n                            - message: must only contain valid characters (matching\n                                ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$)\n                                for types ['Exact', 'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r\"\"\"^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$\"\"\")\n                                : true'\n                          queryParams:\n                            description: |-\n                              QueryParams specifies HTTP query parameter matchers. Multiple match\n                              values are ANDed together, meaning, a request must match all the\n                              specified query parameters to select the route.\n\n\n                              Support: Extended\n                            items:\n                              description: |-\n                                HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP\n                                query parameters.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the HTTP query param to be matched. This must be an\n                                    exact string match. (See\n                                    https://tools.ietf.org/html/rfc7230#section-2.7.3).\n\n\n                                    If multiple entries specify equivalent query param names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent query param name MUST be ignored.\n\n\n                                    If a query param is repeated in an HTTP request, the behavior is\n                                    purposely left undefined, since different data planes have different\n                                    capabilities. However, it is *recommended* that implementations should\n                                    match against the first value of the param if the data plane supports it,\n                                    as this behavior is expected in other load balancing contexts outside of\n                                    the Gateway API.\n\n\n                                    Users SHOULD NOT route traffic based on repeated query params to guard\n                                    themselves against potential differences in the implementations.\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: |-\n                                    Type specifies how to match against the value of the query parameter.\n\n\n                                    Support: Extended (Exact)\n\n\n                                    Support: Implementation-specific (RegularExpression)\n\n\n                                    Since RegularExpression QueryParamMatchType has Implementation-specific\n                                    conformance, implementations can support POSIX, PCRE or any other\n                                    dialects of regular expressions. Please read the implementation's\n                                    documentation to determine the supported dialect.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                    sessionPersistence:\n                      description: |+\n                        SessionPersistence defines and configures session persistence\n                        for the route rule.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        absoluteTimeout:\n                          description: |-\n                            AbsoluteTimeout defines the absolute timeout of the persistent\n                            session. Once the AbsoluteTimeout duration has elapsed, the\n                            session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        cookieConfig:\n                          description: |-\n                            CookieConfig provides configuration settings that are specific\n                            to cookie-based session persistence.\n\n\n                            Support: Core\n                          properties:\n                            lifetimeType:\n                              default: Session\n                              description: |-\n                                LifetimeType specifies whether the cookie has a permanent or\n                                session-based lifetime. A permanent cookie persists until its\n                                specified expiry time, defined by the Expires or Max-Age cookie\n                                attributes, while a session cookie is deleted when the current\n                                session ends.\n\n\n                                When set to \"Permanent\", AbsoluteTimeout indicates the\n                                cookie's lifetime via the Expires or Max-Age cookie attributes\n                                and is required.\n\n\n                                When set to \"Session\", AbsoluteTimeout indicates the\n                                absolute lifetime of the cookie tracked by the gateway and\n                                is optional.\n\n\n                                Support: Core for \"Session\" type\n\n\n                                Support: Extended for \"Permanent\" type\n                              enum:\n                              - Permanent\n                              - Session\n                              type: string\n                          type: object\n                        idleTimeout:\n                          description: |-\n                            IdleTimeout defines the idle timeout of the persistent session.\n                            Once the session has been idle for more than the specified\n                            IdleTimeout duration, the session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        sessionName:\n                          description: |-\n                            SessionName defines the name of the persistent session token\n                            which may be reflected in the cookie or the header. Users\n                            should avoid reusing session names to prevent unintended\n                            consequences, such as rejection or unpredictable behavior.\n\n\n                            Support: Implementation-specific\n                          maxLength: 128\n                          type: string\n                        type:\n                          default: Cookie\n                          description: |-\n                            Type defines the type of session persistence such as through\n                            the use a header or cookie. Defaults to cookie based session\n                            persistence.\n\n\n                            Support: Core for \"Cookie\" type\n\n\n                            Support: Extended for \"Header\" type\n                          enum:\n                          - Cookie\n                          - Header\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: AbsoluteTimeout must be specified when cookie lifetimeType\n                          is Permanent\n                        rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType)\n                          || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)'\n                    timeouts:\n                      description: |+\n                        Timeouts defines the timeouts that can be configured for an HTTP request.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        backendRequest:\n                          description: |-\n                            BackendRequest specifies a timeout for an individual request from the gateway\n                            to a backend. This covers the time from when the request first starts being\n                            sent from the gateway to when the full response has been received from the backend.\n\n\n                            Setting a timeout to the zero duration (e.g. \"0s\") SHOULD disable the timeout\n                            completely. Implementations that cannot completely disable the timeout MUST\n                            instead interpret the zero duration as the longest possible value to which\n                            the timeout can be set.\n\n\n                            An entire client HTTP transaction with a gateway, covered by the Request timeout,\n                            may result in more than one call from the gateway to the destination backend,\n                            for example, if automatic retries are supported.\n\n\n                            Because the Request timeout encompasses the BackendRequest timeout, the value of\n                            BackendRequest must be <= the value of Request timeout.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        request:\n                          description: |-\n                            Request specifies the maximum duration for a gateway to respond to an HTTP request.\n                            If the gateway has not been able to respond before this deadline is met, the gateway\n                            MUST return a timeout error.\n\n\n                            For example, setting the `rules.timeouts.request` field to the value `10s` in an\n                            `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds\n                            to complete.\n\n\n                            Setting a timeout to the zero duration (e.g. \"0s\") SHOULD disable the timeout\n                            completely. Implementations that cannot completely disable the timeout MUST\n                            instead interpret the zero duration as the longest possible value to which\n                            the timeout can be set.\n\n\n                            This timeout is intended to cover as close to the whole request-response transaction\n                            as possible although an implementation MAY choose to start the timeout after the entire\n                            request stream has been received instead of immediately after the transaction is\n                            initiated by the client.\n\n\n                            When this field is unspecified, request timeout behavior is implementation-specific.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: backendRequest timeout cannot be longer than request\n                          timeout\n                        rule: '!(has(self.request) && has(self.backendRequest) &&\n                          duration(self.request) != duration(''0s'') && duration(self.backendRequest)\n                          > duration(self.request))'\n                  type: object\n                  x-kubernetes-validations:\n                  - message: RequestRedirect filter must not be used together with\n                      backendRefs\n                    rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ?\n                      (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))):\n                      true'\n                  - message: When using RequestRedirect filter with path.replacePrefixMatch,\n                      exactly one PathPrefix match must be specified\n                    rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect)\n                      && has(f.requestRedirect.path) && f.requestRedirect.path.type\n                      == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch)))\n                      ? ((size(self.matches) != 1 || !has(self.matches[0].path) ||\n                      self.matches[0].path.type != ''PathPrefix'') ? false : true)\n                      : true'\n                  - message: When using URLRewrite filter with path.replacePrefixMatch,\n                      exactly one PathPrefix match must be specified\n                    rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite)\n                      && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch''\n                      && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches)\n                      != 1 || !has(self.matches[0].path) || self.matches[0].path.type\n                      != ''PathPrefix'') ? false : true) : true'\n                  - message: Within backendRefs, when using RequestRedirect filter\n                      with path.replacePrefixMatch, exactly one PathPrefix match must\n                      be specified\n                    rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b,\n                      (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect)\n                      && has(f.requestRedirect.path) && f.requestRedirect.path.type\n                      == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch)))\n                      )) ? ((size(self.matches) != 1 || !has(self.matches[0].path)\n                      || self.matches[0].path.type != ''PathPrefix'') ? false : true)\n                      : true'\n                  - message: Within backendRefs, When using URLRewrite filter with\n                      path.replacePrefixMatch, exactly one PathPrefix match must be\n                      specified\n                    rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b,\n                      (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite)\n                      && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch''\n                      && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches)\n                      != 1 || !has(self.matches[0].path) || self.matches[0].path.type\n                      != ''PathPrefix'') ? false : true) : true'\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: |-\n          HTTPRoute provides a way to route HTTP requests. This includes the capability\n          to match requests by hostname, path, header, or query param. Filters can be\n          used to specify additional processing steps. Backends specify where matching\n          requests should be routed.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: |-\n                  Hostnames defines a set of hostnames that should match against the HTTP Host\n                  header to select a HTTPRoute used to process the request. Implementations\n                  MUST ignore any port value specified in the HTTP Host header while\n                  performing a match and (absent of any applicable header modification\n                  configuration) MUST forward this header unmodified to the backend.\n\n\n                  Valid values for Hostnames are determined by RFC 1123 definition of a\n                  hostname with 2 notable exceptions:\n\n\n                  1. IPs are not allowed.\n                  2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                     label must appear by itself as the first label.\n\n\n                  If a hostname is specified by both the Listener and HTTPRoute, there\n                  must be at least one intersecting hostname for the HTTPRoute to be\n                  attached to the Listener. For example:\n\n\n                  * A Listener with `test.example.com` as the hostname matches HTTPRoutes\n                    that have either not specified any hostnames, or have specified at\n                    least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                    that have either not specified any hostnames or have specified at least\n                    one hostname that matches the Listener hostname. For example,\n                    `*.example.com`, `test.example.com`, and `foo.test.example.com` would\n                    all match. On the other hand, `example.com` and `test.example.net` would\n                    not match.\n\n\n                  Hostnames that are prefixed with a wildcard label (`*.`) are interpreted\n                  as a suffix match. That means that a match for `*.example.com` would match\n                  both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\n\n                  If both the Listener and HTTPRoute have specified hostnames, any\n                  HTTPRoute hostnames that do not match the Listener hostname MUST be\n                  ignored. For example, if a Listener specified `*.example.com`, and the\n                  HTTPRoute specified `test.example.com` and `test.example.net`,\n                  `test.example.net` must not be considered for a match.\n\n\n                  If both the Listener and HTTPRoute have specified hostnames, and none\n                  match with the criteria above, then the HTTPRoute is not accepted. The\n                  implementation must raise an 'Accepted' Condition with a status of\n                  `False` in the corresponding RouteParentStatus.\n\n\n                  In the event that multiple HTTPRoutes specify intersecting hostnames (e.g.\n                  overlapping wildcard matching and exact matching hostnames), precedence must\n                  be given to rules from the HTTPRoute with the largest number of:\n\n\n                  * Characters in a matching non-wildcard hostname.\n                  * Characters in a matching hostname.\n\n\n                  If ties exist across multiple Routes, the matching precedence rules for\n                  HTTPRouteMatches takes over.\n\n\n                  Support: Core\n                items:\n                  description: |-\n                    Hostname is the fully qualified domain name of a network host. This matches\n                    the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                     1. IPs are not allowed.\n                     2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                        label must appear by itself as the first label.\n\n\n                    Hostname can be \"precise\" which is a domain name without the terminating\n                    dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a\n                    domain name prefixed with a single wildcard label (e.g. `*.example.com`).\n\n\n                    Note that as per RFC1035 and RFC1123, a *label* must consist of lower case\n                    alphanumeric characters or '-', and must start and end with an alphanumeric\n                    character. No other punctuation is allowed.\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: |-\n                    HTTPRouteRule defines semantics for matching an HTTP request based on\n                    conditions (matches), processing it (filters), and forwarding the request to\n                    an API object (backendRefs).\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent.\n\n\n                        Failure behavior here depends on how many BackendRefs are specified and\n                        how many are invalid.\n\n\n                        If *all* entries in BackendRefs are invalid, and there are also no filters\n                        specified in this route rule, *all* traffic which matches this rule MUST\n                        receive a 500 status code.\n\n\n                        See the HTTPBackendRef definition for the rules about what makes a single\n                        HTTPBackendRef invalid.\n\n\n                        When a HTTPBackendRef is invalid, 500 status codes MUST be returned for\n                        requests that would have otherwise been routed to an invalid backend. If\n                        multiple backends are specified, and some are invalid, the proportion of\n                        requests that would otherwise have been routed to an invalid backend\n                        MUST receive a 500 status code.\n\n\n                        For example, if two backends are specified with equal weights, and one is\n                        invalid, 50 percent of traffic must receive a 500. Implementations may\n                        choose how that 50 percent is determined.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Extended for Kubernetes ServiceImport\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Core\n                      items:\n                        description: |-\n                          HTTPBackendRef defines how a HTTPRoute forwards a HTTP request.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n                        properties:\n                          filters:\n                            description: |-\n                              Filters defined at this level should be executed if and only if the\n                              request is being forwarded to the backend defined here.\n\n\n                              Support: Implementation-specific (For broader support of filters, use the\n                              Filters field in HTTPRouteRule.)\n                            items:\n                              description: |-\n                                HTTPRouteFilter defines processing steps that must be completed during the\n                                request or response lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway implementations. Some\n                                examples include request or response modification, implementing\n                                authentication strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type of the filter.\n                              properties:\n                                extensionRef:\n                                  description: |-\n                                    ExtensionRef is an optional, implementation-specific extension to the\n                                    \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                                    \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                                    extended filters.\n\n\n                                    This filter can be used multiple times within the same rule.\n\n\n                                    Support: Implementation-specific\n                                  properties:\n                                    group:\n                                      description: |-\n                                        Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                        When unspecified or empty string, core API group is inferred.\n                                      maxLength: 253\n                                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    kind:\n                                      description: Kind is kind of the referent. For\n                                        example \"HTTPRoute\" or \"Service\".\n                                      maxLength: 63\n                                      minLength: 1\n                                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                      type: string\n                                    name:\n                                      description: Name is the name of the referent.\n                                      maxLength: 253\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - group\n                                  - kind\n                                  - name\n                                  type: object\n                                requestHeaderModifier:\n                                  description: |-\n                                    RequestHeaderModifier defines a schema for a filter that modifies request\n                                    headers.\n\n\n                                    Support: Core\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestMirror:\n                                  description: |-\n                                    RequestMirror defines a schema for a filter that mirrors requests.\n                                    Requests are sent to the specified destination, but responses from\n                                    that destination are ignored.\n\n\n                                    This filter can be used multiple times within the same rule. Note that\n                                    not all implementations will be able to support mirroring to multiple\n                                    backends.\n\n\n                                    Support: Extended\n                                  properties:\n                                    backendRef:\n                                      description: |-\n                                        BackendRef references a resource where mirrored requests are sent.\n\n\n                                        Mirrored requests must be sent only to a single destination endpoint\n                                        within this BackendRef, irrespective of how many endpoints are present\n                                        within this BackendRef.\n\n\n                                        If the referent cannot be found, this BackendRef is invalid and must be\n                                        dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                        condition on the Route status is set to `status: False` and not configure\n                                        this backend in the underlying implementation.\n\n\n                                        If there is a cross-namespace reference to an *existing* object\n                                        that is not allowed by a ReferenceGrant, the controller must ensure the\n                                        \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                        with the \"RefNotPermitted\" reason and not configure this backend in the\n                                        underlying implementation.\n\n\n                                        In either error case, the Message of the `ResolvedRefs` Condition\n                                        should be used to provide more detail about the problem.\n\n\n                                        Support: Extended for Kubernetes Service\n\n\n                                        Support: Implementation-specific for any other resource\n                                      properties:\n                                        group:\n                                          default: \"\"\n                                          description: |-\n                                            Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                            When unspecified or empty string, core API group is inferred.\n                                          maxLength: 253\n                                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                          type: string\n                                        kind:\n                                          default: Service\n                                          description: |-\n                                            Kind is the Kubernetes resource kind of the referent. For example\n                                            \"Service\".\n\n\n                                            Defaults to \"Service\" when not specified.\n\n\n                                            ExternalName services can refer to CNAME DNS records that may live\n                                            outside of the cluster and as such are difficult to reason about in\n                                            terms of conformance. They also may not be safe to forward to (see\n                                            CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                            support ExternalName Services.\n\n\n                                            Support: Core (Services with a type other than ExternalName)\n\n\n                                            Support: Implementation-specific (Services with type ExternalName)\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                          type: string\n                                        name:\n                                          description: Name is the name of the referent.\n                                          maxLength: 253\n                                          minLength: 1\n                                          type: string\n                                        namespace:\n                                          description: |-\n                                            Namespace is the namespace of the backend. When unspecified, the local\n                                            namespace is inferred.\n\n\n                                            Note that when a namespace different than the local namespace is specified,\n                                            a ReferenceGrant object is required in the referent namespace to allow that\n                                            namespace's owner to accept the reference. See the ReferenceGrant\n                                            documentation for details.\n\n\n                                            Support: Core\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                          type: string\n                                        port:\n                                          description: |-\n                                            Port specifies the destination port number to use for this resource.\n                                            Port is required when the referent is a Kubernetes Service. In this\n                                            case, the port number is the service port number, not the target port.\n                                            For other resources, destination port might be derived from the referent\n                                            resource or this field.\n                                          format: int32\n                                          maximum: 65535\n                                          minimum: 1\n                                          type: integer\n                                      required:\n                                      - name\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: Must have port for Service reference\n                                        rule: '(size(self.group) == 0 && self.kind\n                                          == ''Service'') ? has(self.port) : true'\n                                  required:\n                                  - backendRef\n                                  type: object\n                                requestRedirect:\n                                  description: |-\n                                    RequestRedirect defines a schema for a filter that responds to the\n                                    request with an HTTP redirection.\n\n\n                                    Support: Core\n                                  properties:\n                                    hostname:\n                                      description: |-\n                                        Hostname is the hostname to be used in the value of the `Location`\n                                        header in the response.\n                                        When empty, the hostname in the `Host` header of the request is used.\n\n\n                                        Support: Core\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: |-\n                                        Path defines parameters used to modify the path of the incoming request.\n                                        The modified path is then used to construct the `Location` header. When\n                                        empty, the request path is used as-is.\n\n\n                                        Support: Extended\n                                      properties:\n                                        replaceFullPath:\n                                          description: |-\n                                            ReplaceFullPath specifies the value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: |-\n                                            ReplacePrefixMatch specifies the value with which to replace the prefix\n                                            match of a request during a rewrite or redirect. For example, a request\n                                            to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                            of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                            Note that this matches the behavior of the PathPrefix match type. This\n                                            matches full path elements. A path element refers to the list of labels\n                                            in the path split by the `/` separator. When specified, a trailing `/` is\n                                            ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                            match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                            ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                            Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                            the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                            Request Path | Prefix Match | Replace Prefix | Modified Path\n                                            -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                            /foo         | /foo         | /xyz           | /xyz\n                                            /foo/        | /foo         | /xyz           | /xyz/\n                                            /foo/bar     | /foo         | <empty string> | /bar\n                                            /foo/        | /foo         | <empty string> | /\n                                            /foo         | /foo         | <empty string> | /\n                                            /foo/        | /foo         | /              | /\n                                            /foo         | /foo         | /              | /\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: |-\n                                            Type defines the type of path modifier. Additional types may be\n                                            added in a future release of the API.\n\n\n                                            Note that values may be added to this enum, implementations\n                                            must ensure that unknown values will not cause a crash.\n\n\n                                            Unknown values here must result in the implementation setting the\n                                            Accepted Condition for the Route to `status: False`, with a\n                                            Reason of `UnsupportedValue`.\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: replaceFullPath must be specified\n                                          when type is set to 'ReplaceFullPath'\n                                        rule: 'self.type == ''ReplaceFullPath'' ?\n                                          has(self.replaceFullPath) : true'\n                                      - message: type must be 'ReplaceFullPath' when\n                                          replaceFullPath is set\n                                        rule: 'has(self.replaceFullPath) ? self.type\n                                          == ''ReplaceFullPath'' : true'\n                                      - message: replacePrefixMatch must be specified\n                                          when type is set to 'ReplacePrefixMatch'\n                                        rule: 'self.type == ''ReplacePrefixMatch''\n                                          ? has(self.replacePrefixMatch) : true'\n                                      - message: type must be 'ReplacePrefixMatch'\n                                          when replacePrefixMatch is set\n                                        rule: 'has(self.replacePrefixMatch) ? self.type\n                                          == ''ReplacePrefixMatch'' : true'\n                                    port:\n                                      description: |-\n                                        Port is the port to be used in the value of the `Location`\n                                        header in the response.\n\n\n                                        If no port is specified, the redirect port MUST be derived using the\n                                        following rules:\n\n\n                                        * If redirect scheme is not-empty, the redirect port MUST be the well-known\n                                          port associated with the redirect scheme. Specifically \"http\" to port 80\n                                          and \"https\" to port 443. If the redirect scheme does not have a\n                                          well-known port, the listener port of the Gateway SHOULD be used.\n                                        * If redirect scheme is empty, the redirect port MUST be the Gateway\n                                          Listener port.\n\n\n                                        Implementations SHOULD NOT add the port number in the 'Location'\n                                        header in the following cases:\n\n\n                                        * A Location header that will use HTTP (whether that is determined via\n                                          the Listener protocol or the Scheme field) _and_ use port 80.\n                                        * A Location header that will use HTTPS (whether that is determined via\n                                          the Listener protocol or the Scheme field) _and_ use port 443.\n\n\n                                        Support: Extended\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: |-\n                                        Scheme is the scheme to be used in the value of the `Location` header in\n                                        the response. When empty, the scheme of the request is used.\n\n\n                                        Scheme redirects can affect the port of the redirect, for more information,\n                                        refer to the documentation for the port field of this filter.\n\n\n                                        Note that values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause a crash.\n\n\n                                        Unknown values here must result in the implementation setting the\n                                        Accepted Condition for the Route to `status: False`, with a\n                                        Reason of `UnsupportedValue`.\n\n\n                                        Support: Extended\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: |-\n                                        StatusCode is the HTTP status code to be used in response.\n\n\n                                        Note that values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause a crash.\n\n\n                                        Unknown values here must result in the implementation setting the\n                                        Accepted Condition for the Route to `status: False`, with a\n                                        Reason of `UnsupportedValue`.\n\n\n                                        Support: Core\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: |-\n                                    ResponseHeaderModifier defines a schema for a filter that modifies response\n                                    headers.\n\n\n                                    Support: Extended\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: |-\n                                    Type identifies the type of filter to apply. As with other API fields,\n                                    types are classified into three conformance levels:\n\n\n                                    - Core: Filter types and their corresponding configuration defined by\n                                      \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                      implementations must support core filters.\n\n\n                                    - Extended: Filter types and their corresponding configuration defined by\n                                      \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                      are encouraged to support extended filters.\n\n\n                                    - Implementation-specific: Filters that are defined and supported by\n                                      specific vendors.\n                                      In the future, filters showing convergence in behavior across multiple\n                                      implementations will be considered for inclusion in extended or core\n                                      conformance levels. Filter-specific configuration for such filters\n                                      is specified using the ExtensionRef field. `Type` should be set to\n                                      \"ExtensionRef\" for custom filters.\n\n\n                                    Implementers are encouraged to define custom implementation types to\n                                    extend the core API with implementation-specific behavior.\n\n\n                                    If a reference to a custom filter type cannot be resolved, the filter\n                                    MUST NOT be skipped. Instead, requests that would have been processed by\n                                    that filter MUST receive a HTTP error response.\n\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause a crash.\n\n\n                                    Unknown values here must result in the implementation setting the\n                                    Accepted Condition for the Route to `status: False`, with a\n                                    Reason of `UnsupportedValue`.\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestMirror\n                                  - RequestRedirect\n                                  - URLRewrite\n                                  - ExtensionRef\n                                  type: string\n                                urlRewrite:\n                                  description: |-\n                                    URLRewrite defines a schema for a filter that modifies a request during forwarding.\n\n\n                                    Support: Extended\n                                  properties:\n                                    hostname:\n                                      description: |-\n                                        Hostname is the value to be used to replace the Host header value during\n                                        forwarding.\n\n\n                                        Support: Extended\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: |-\n                                        Path defines a path rewrite.\n\n\n                                        Support: Extended\n                                      properties:\n                                        replaceFullPath:\n                                          description: |-\n                                            ReplaceFullPath specifies the value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: |-\n                                            ReplacePrefixMatch specifies the value with which to replace the prefix\n                                            match of a request during a rewrite or redirect. For example, a request\n                                            to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                            of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                            Note that this matches the behavior of the PathPrefix match type. This\n                                            matches full path elements. A path element refers to the list of labels\n                                            in the path split by the `/` separator. When specified, a trailing `/` is\n                                            ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                            match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                            ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                            Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                            the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                            Request Path | Prefix Match | Replace Prefix | Modified Path\n                                            -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                            /foo         | /foo         | /xyz           | /xyz\n                                            /foo/        | /foo         | /xyz           | /xyz/\n                                            /foo/bar     | /foo         | <empty string> | /bar\n                                            /foo/        | /foo         | <empty string> | /\n                                            /foo         | /foo         | <empty string> | /\n                                            /foo/        | /foo         | /              | /\n                                            /foo         | /foo         | /              | /\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: |-\n                                            Type defines the type of path modifier. Additional types may be\n                                            added in a future release of the API.\n\n\n                                            Note that values may be added to this enum, implementations\n                                            must ensure that unknown values will not cause a crash.\n\n\n                                            Unknown values here must result in the implementation setting the\n                                            Accepted Condition for the Route to `status: False`, with a\n                                            Reason of `UnsupportedValue`.\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: replaceFullPath must be specified\n                                          when type is set to 'ReplaceFullPath'\n                                        rule: 'self.type == ''ReplaceFullPath'' ?\n                                          has(self.replaceFullPath) : true'\n                                      - message: type must be 'ReplaceFullPath' when\n                                          replaceFullPath is set\n                                        rule: 'has(self.replaceFullPath) ? self.type\n                                          == ''ReplaceFullPath'' : true'\n                                      - message: replacePrefixMatch must be specified\n                                          when type is set to 'ReplacePrefixMatch'\n                                        rule: 'self.type == ''ReplacePrefixMatch''\n                                          ? has(self.replacePrefixMatch) : true'\n                                      - message: type must be 'ReplacePrefixMatch'\n                                          when replacePrefixMatch is set\n                                        rule: 'has(self.replacePrefixMatch) ? self.type\n                                          == ''ReplacePrefixMatch'' : true'\n                                  type: object\n                              required:\n                              - type\n                              type: object\n                              x-kubernetes-validations:\n                              - message: filter.requestHeaderModifier must be nil\n                                  if the filter.type is not RequestHeaderModifier\n                                rule: '!(has(self.requestHeaderModifier) && self.type\n                                  != ''RequestHeaderModifier'')'\n                              - message: filter.requestHeaderModifier must be specified\n                                  for RequestHeaderModifier filter.type\n                                rule: '!(!has(self.requestHeaderModifier) && self.type\n                                  == ''RequestHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be nil\n                                  if the filter.type is not ResponseHeaderModifier\n                                rule: '!(has(self.responseHeaderModifier) && self.type\n                                  != ''ResponseHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be specified\n                                  for ResponseHeaderModifier filter.type\n                                rule: '!(!has(self.responseHeaderModifier) && self.type\n                                  == ''ResponseHeaderModifier'')'\n                              - message: filter.requestMirror must be nil if the filter.type\n                                  is not RequestMirror\n                                rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                              - message: filter.requestMirror must be specified for\n                                  RequestMirror filter.type\n                                rule: '!(!has(self.requestMirror) && self.type ==\n                                  ''RequestMirror'')'\n                              - message: filter.requestRedirect must be nil if the\n                                  filter.type is not RequestRedirect\n                                rule: '!(has(self.requestRedirect) && self.type !=\n                                  ''RequestRedirect'')'\n                              - message: filter.requestRedirect must be specified\n                                  for RequestRedirect filter.type\n                                rule: '!(!has(self.requestRedirect) && self.type ==\n                                  ''RequestRedirect'')'\n                              - message: filter.urlRewrite must be nil if the filter.type\n                                  is not URLRewrite\n                                rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')'\n                              - message: filter.urlRewrite must be specified for URLRewrite\n                                  filter.type\n                                rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')'\n                              - message: filter.extensionRef must be nil if the filter.type\n                                  is not ExtensionRef\n                                rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                              - message: filter.extensionRef must be specified for\n                                  ExtensionRef filter.type\n                                rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-validations:\n                            - message: May specify either httpRouteFilterRequestRedirect\n                                or httpRouteFilterRequestRewrite, but not both\n                              rule: '!(self.exists(f, f.type == ''RequestRedirect'')\n                                && self.exists(f, f.type == ''URLRewrite''))'\n                            - message: May specify either httpRouteFilterRequestRedirect\n                                or httpRouteFilterRequestRewrite, but not both\n                              rule: '!(self.exists(f, f.type == ''RequestRedirect'')\n                                && self.exists(f, f.type == ''URLRewrite''))'\n                            - message: RequestHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                                <= 1\n                            - message: ResponseHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                                <= 1\n                            - message: RequestRedirect filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestRedirect').size()\n                                <= 1\n                            - message: URLRewrite filter cannot be repeated\n                              rule: self.filter(f, f.type == 'URLRewrite').size()\n                                <= 1\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: |-\n                        Filters define the filters that are applied to requests that match\n                        this rule.\n\n\n                        Wherever possible, implementations SHOULD implement filters in the order\n                        they are specified.\n\n\n                        Implementations MAY choose to implement this ordering strictly, rejecting\n                        any combination or order of filters that can not be supported. If implementations\n                        choose a strict interpretation of filter ordering, they MUST clearly document\n                        that behavior.\n\n\n                        To reject an invalid combination or order of filters, implementations SHOULD\n                        consider the Route Rules with this configuration invalid. If all Route Rules\n                        in a Route are invalid, the entire Route would be considered invalid. If only\n                        a portion of Route Rules are invalid, implementations MUST set the\n                        \"PartiallyInvalid\" condition for the Route.\n\n\n                        Conformance-levels at this level are defined based on the type of filter:\n\n\n                        - ALL core filters MUST be supported by all implementations.\n                        - Implementers are encouraged to support extended filters.\n                        - Implementation-specific custom filters have no API guarantees across\n                          implementations.\n\n\n                        Specifying the same filter multiple times is not supported unless explicitly\n                        indicated in the filter.\n\n\n                        All filters are expected to be compatible with each other except for the\n                        URLRewrite and RequestRedirect filters, which may not be combined. If an\n                        implementation can not support other combinations of filters, they must clearly\n                        document that limitation. In cases where incompatible or unsupported\n                        filters are specified and cause the `Accepted` condition to be set to status\n                        `False`, implementations may use the `IncompatibleFilters` reason to specify\n                        this configuration error.\n\n\n                        Support: Core\n                      items:\n                        description: |-\n                          HTTPRouteFilter defines processing steps that must be completed during the\n                          request or response lifecycle. HTTPRouteFilters are meant as an extension\n                          point to express processing that may be done in Gateway implementations. Some\n                          examples include request or response modification, implementing\n                          authentication strategies, rate-limiting, and traffic shaping. API\n                          guarantee/conformance is defined based on the type of the filter.\n                        properties:\n                          extensionRef:\n                            description: |-\n                              ExtensionRef is an optional, implementation-specific extension to the\n                              \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                              \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                              extended filters.\n\n\n                              This filter can be used multiple times within the same rule.\n\n\n                              Support: Implementation-specific\n                            properties:\n                              group:\n                                description: |-\n                                  Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                  When unspecified or empty string, core API group is inferred.\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                description: Kind is kind of the referent. For example\n                                  \"HTTPRoute\" or \"Service\".\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: Name is the name of the referent.\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                            required:\n                            - group\n                            - kind\n                            - name\n                            type: object\n                          requestHeaderModifier:\n                            description: |-\n                              RequestHeaderModifier defines a schema for a filter that modifies request\n                              headers.\n\n\n                              Support: Core\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestMirror:\n                            description: |-\n                              RequestMirror defines a schema for a filter that mirrors requests.\n                              Requests are sent to the specified destination, but responses from\n                              that destination are ignored.\n\n\n                              This filter can be used multiple times within the same rule. Note that\n                              not all implementations will be able to support mirroring to multiple\n                              backends.\n\n\n                              Support: Extended\n                            properties:\n                              backendRef:\n                                description: |-\n                                  BackendRef references a resource where mirrored requests are sent.\n\n\n                                  Mirrored requests must be sent only to a single destination endpoint\n                                  within this BackendRef, irrespective of how many endpoints are present\n                                  within this BackendRef.\n\n\n                                  If the referent cannot be found, this BackendRef is invalid and must be\n                                  dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                  condition on the Route status is set to `status: False` and not configure\n                                  this backend in the underlying implementation.\n\n\n                                  If there is a cross-namespace reference to an *existing* object\n                                  that is not allowed by a ReferenceGrant, the controller must ensure the\n                                  \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                  with the \"RefNotPermitted\" reason and not configure this backend in the\n                                  underlying implementation.\n\n\n                                  In either error case, the Message of the `ResolvedRefs` Condition\n                                  should be used to provide more detail about the problem.\n\n\n                                  Support: Extended for Kubernetes Service\n\n\n                                  Support: Implementation-specific for any other resource\n                                properties:\n                                  group:\n                                    default: \"\"\n                                    description: |-\n                                      Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                      When unspecified or empty string, core API group is inferred.\n                                    maxLength: 253\n                                    pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                    type: string\n                                  kind:\n                                    default: Service\n                                    description: |-\n                                      Kind is the Kubernetes resource kind of the referent. For example\n                                      \"Service\".\n\n\n                                      Defaults to \"Service\" when not specified.\n\n\n                                      ExternalName services can refer to CNAME DNS records that may live\n                                      outside of the cluster and as such are difficult to reason about in\n                                      terms of conformance. They also may not be safe to forward to (see\n                                      CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                      support ExternalName Services.\n\n\n                                      Support: Core (Services with a type other than ExternalName)\n\n\n                                      Support: Implementation-specific (Services with type ExternalName)\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                    type: string\n                                  name:\n                                    description: Name is the name of the referent.\n                                    maxLength: 253\n                                    minLength: 1\n                                    type: string\n                                  namespace:\n                                    description: |-\n                                      Namespace is the namespace of the backend. When unspecified, the local\n                                      namespace is inferred.\n\n\n                                      Note that when a namespace different than the local namespace is specified,\n                                      a ReferenceGrant object is required in the referent namespace to allow that\n                                      namespace's owner to accept the reference. See the ReferenceGrant\n                                      documentation for details.\n\n\n                                      Support: Core\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                    type: string\n                                  port:\n                                    description: |-\n                                      Port specifies the destination port number to use for this resource.\n                                      Port is required when the referent is a Kubernetes Service. In this\n                                      case, the port number is the service port number, not the target port.\n                                      For other resources, destination port might be derived from the referent\n                                      resource or this field.\n                                    format: int32\n                                    maximum: 65535\n                                    minimum: 1\n                                    type: integer\n                                required:\n                                - name\n                                type: object\n                                x-kubernetes-validations:\n                                - message: Must have port for Service reference\n                                  rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                                    ? has(self.port) : true'\n                            required:\n                            - backendRef\n                            type: object\n                          requestRedirect:\n                            description: |-\n                              RequestRedirect defines a schema for a filter that responds to the\n                              request with an HTTP redirection.\n\n\n                              Support: Core\n                            properties:\n                              hostname:\n                                description: |-\n                                  Hostname is the hostname to be used in the value of the `Location`\n                                  header in the response.\n                                  When empty, the hostname in the `Host` header of the request is used.\n\n\n                                  Support: Core\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: |-\n                                  Path defines parameters used to modify the path of the incoming request.\n                                  The modified path is then used to construct the `Location` header. When\n                                  empty, the request path is used as-is.\n\n\n                                  Support: Extended\n                                properties:\n                                  replaceFullPath:\n                                    description: |-\n                                      ReplaceFullPath specifies the value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: |-\n                                      ReplacePrefixMatch specifies the value with which to replace the prefix\n                                      match of a request during a rewrite or redirect. For example, a request\n                                      to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                      of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                      Note that this matches the behavior of the PathPrefix match type. This\n                                      matches full path elements. A path element refers to the list of labels\n                                      in the path split by the `/` separator. When specified, a trailing `/` is\n                                      ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                      match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                      ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                      Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                      the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                      Request Path | Prefix Match | Replace Prefix | Modified Path\n                                      -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                      /foo         | /foo         | /xyz           | /xyz\n                                      /foo/        | /foo         | /xyz           | /xyz/\n                                      /foo/bar     | /foo         | <empty string> | /bar\n                                      /foo/        | /foo         | <empty string> | /\n                                      /foo         | /foo         | <empty string> | /\n                                      /foo/        | /foo         | /              | /\n                                      /foo         | /foo         | /              | /\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: |-\n                                      Type defines the type of path modifier. Additional types may be\n                                      added in a future release of the API.\n\n\n                                      Note that values may be added to this enum, implementations\n                                      must ensure that unknown values will not cause a crash.\n\n\n                                      Unknown values here must result in the implementation setting the\n                                      Accepted Condition for the Route to `status: False`, with a\n                                      Reason of `UnsupportedValue`.\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                                x-kubernetes-validations:\n                                - message: replaceFullPath must be specified when\n                                    type is set to 'ReplaceFullPath'\n                                  rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath)\n                                    : true'\n                                - message: type must be 'ReplaceFullPath' when replaceFullPath\n                                    is set\n                                  rule: 'has(self.replaceFullPath) ? self.type ==\n                                    ''ReplaceFullPath'' : true'\n                                - message: replacePrefixMatch must be specified when\n                                    type is set to 'ReplacePrefixMatch'\n                                  rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch)\n                                    : true'\n                                - message: type must be 'ReplacePrefixMatch' when\n                                    replacePrefixMatch is set\n                                  rule: 'has(self.replacePrefixMatch) ? self.type\n                                    == ''ReplacePrefixMatch'' : true'\n                              port:\n                                description: |-\n                                  Port is the port to be used in the value of the `Location`\n                                  header in the response.\n\n\n                                  If no port is specified, the redirect port MUST be derived using the\n                                  following rules:\n\n\n                                  * If redirect scheme is not-empty, the redirect port MUST be the well-known\n                                    port associated with the redirect scheme. Specifically \"http\" to port 80\n                                    and \"https\" to port 443. If the redirect scheme does not have a\n                                    well-known port, the listener port of the Gateway SHOULD be used.\n                                  * If redirect scheme is empty, the redirect port MUST be the Gateway\n                                    Listener port.\n\n\n                                  Implementations SHOULD NOT add the port number in the 'Location'\n                                  header in the following cases:\n\n\n                                  * A Location header that will use HTTP (whether that is determined via\n                                    the Listener protocol or the Scheme field) _and_ use port 80.\n                                  * A Location header that will use HTTPS (whether that is determined via\n                                    the Listener protocol or the Scheme field) _and_ use port 443.\n\n\n                                  Support: Extended\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: |-\n                                  Scheme is the scheme to be used in the value of the `Location` header in\n                                  the response. When empty, the scheme of the request is used.\n\n\n                                  Scheme redirects can affect the port of the redirect, for more information,\n                                  refer to the documentation for the port field of this filter.\n\n\n                                  Note that values may be added to this enum, implementations\n                                  must ensure that unknown values will not cause a crash.\n\n\n                                  Unknown values here must result in the implementation setting the\n                                  Accepted Condition for the Route to `status: False`, with a\n                                  Reason of `UnsupportedValue`.\n\n\n                                  Support: Extended\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: |-\n                                  StatusCode is the HTTP status code to be used in response.\n\n\n                                  Note that values may be added to this enum, implementations\n                                  must ensure that unknown values will not cause a crash.\n\n\n                                  Unknown values here must result in the implementation setting the\n                                  Accepted Condition for the Route to `status: False`, with a\n                                  Reason of `UnsupportedValue`.\n\n\n                                  Support: Core\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          responseHeaderModifier:\n                            description: |-\n                              ResponseHeaderModifier defines a schema for a filter that modifies response\n                              headers.\n\n\n                              Support: Extended\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          type:\n                            description: |-\n                              Type identifies the type of filter to apply. As with other API fields,\n                              types are classified into three conformance levels:\n\n\n                              - Core: Filter types and their corresponding configuration defined by\n                                \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                implementations must support core filters.\n\n\n                              - Extended: Filter types and their corresponding configuration defined by\n                                \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                are encouraged to support extended filters.\n\n\n                              - Implementation-specific: Filters that are defined and supported by\n                                specific vendors.\n                                In the future, filters showing convergence in behavior across multiple\n                                implementations will be considered for inclusion in extended or core\n                                conformance levels. Filter-specific configuration for such filters\n                                is specified using the ExtensionRef field. `Type` should be set to\n                                \"ExtensionRef\" for custom filters.\n\n\n                              Implementers are encouraged to define custom implementation types to\n                              extend the core API with implementation-specific behavior.\n\n\n                              If a reference to a custom filter type cannot be resolved, the filter\n                              MUST NOT be skipped. Instead, requests that would have been processed by\n                              that filter MUST receive a HTTP error response.\n\n\n                              Note that values may be added to this enum, implementations\n                              must ensure that unknown values will not cause a crash.\n\n\n                              Unknown values here must result in the implementation setting the\n                              Accepted Condition for the Route to `status: False`, with a\n                              Reason of `UnsupportedValue`.\n                            enum:\n                            - RequestHeaderModifier\n                            - ResponseHeaderModifier\n                            - RequestMirror\n                            - RequestRedirect\n                            - URLRewrite\n                            - ExtensionRef\n                            type: string\n                          urlRewrite:\n                            description: |-\n                              URLRewrite defines a schema for a filter that modifies a request during forwarding.\n\n\n                              Support: Extended\n                            properties:\n                              hostname:\n                                description: |-\n                                  Hostname is the value to be used to replace the Host header value during\n                                  forwarding.\n\n\n                                  Support: Extended\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: |-\n                                  Path defines a path rewrite.\n\n\n                                  Support: Extended\n                                properties:\n                                  replaceFullPath:\n                                    description: |-\n                                      ReplaceFullPath specifies the value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: |-\n                                      ReplacePrefixMatch specifies the value with which to replace the prefix\n                                      match of a request during a rewrite or redirect. For example, a request\n                                      to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                      of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                      Note that this matches the behavior of the PathPrefix match type. This\n                                      matches full path elements. A path element refers to the list of labels\n                                      in the path split by the `/` separator. When specified, a trailing `/` is\n                                      ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                      match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                      ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                      Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                      the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                      Request Path | Prefix Match | Replace Prefix | Modified Path\n                                      -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                      /foo         | /foo         | /xyz           | /xyz\n                                      /foo/        | /foo         | /xyz           | /xyz/\n                                      /foo/bar     | /foo         | <empty string> | /bar\n                                      /foo/        | /foo         | <empty string> | /\n                                      /foo         | /foo         | <empty string> | /\n                                      /foo/        | /foo         | /              | /\n                                      /foo         | /foo         | /              | /\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: |-\n                                      Type defines the type of path modifier. Additional types may be\n                                      added in a future release of the API.\n\n\n                                      Note that values may be added to this enum, implementations\n                                      must ensure that unknown values will not cause a crash.\n\n\n                                      Unknown values here must result in the implementation setting the\n                                      Accepted Condition for the Route to `status: False`, with a\n                                      Reason of `UnsupportedValue`.\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                                x-kubernetes-validations:\n                                - message: replaceFullPath must be specified when\n                                    type is set to 'ReplaceFullPath'\n                                  rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath)\n                                    : true'\n                                - message: type must be 'ReplaceFullPath' when replaceFullPath\n                                    is set\n                                  rule: 'has(self.replaceFullPath) ? self.type ==\n                                    ''ReplaceFullPath'' : true'\n                                - message: replacePrefixMatch must be specified when\n                                    type is set to 'ReplacePrefixMatch'\n                                  rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch)\n                                    : true'\n                                - message: type must be 'ReplacePrefixMatch' when\n                                    replacePrefixMatch is set\n                                  rule: 'has(self.replacePrefixMatch) ? self.type\n                                    == ''ReplacePrefixMatch'' : true'\n                            type: object\n                        required:\n                        - type\n                        type: object\n                        x-kubernetes-validations:\n                        - message: filter.requestHeaderModifier must be nil if the\n                            filter.type is not RequestHeaderModifier\n                          rule: '!(has(self.requestHeaderModifier) && self.type !=\n                            ''RequestHeaderModifier'')'\n                        - message: filter.requestHeaderModifier must be specified\n                            for RequestHeaderModifier filter.type\n                          rule: '!(!has(self.requestHeaderModifier) && self.type ==\n                            ''RequestHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be nil if the\n                            filter.type is not ResponseHeaderModifier\n                          rule: '!(has(self.responseHeaderModifier) && self.type !=\n                            ''ResponseHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be specified\n                            for ResponseHeaderModifier filter.type\n                          rule: '!(!has(self.responseHeaderModifier) && self.type\n                            == ''ResponseHeaderModifier'')'\n                        - message: filter.requestMirror must be nil if the filter.type\n                            is not RequestMirror\n                          rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                        - message: filter.requestMirror must be specified for RequestMirror\n                            filter.type\n                          rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')'\n                        - message: filter.requestRedirect must be nil if the filter.type\n                            is not RequestRedirect\n                          rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')'\n                        - message: filter.requestRedirect must be specified for RequestRedirect\n                            filter.type\n                          rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')'\n                        - message: filter.urlRewrite must be nil if the filter.type\n                            is not URLRewrite\n                          rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')'\n                        - message: filter.urlRewrite must be specified for URLRewrite\n                            filter.type\n                          rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')'\n                        - message: filter.extensionRef must be nil if the filter.type\n                            is not ExtensionRef\n                          rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                        - message: filter.extensionRef must be specified for ExtensionRef\n                            filter.type\n                          rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                      maxItems: 16\n                      type: array\n                      x-kubernetes-validations:\n                      - message: May specify either httpRouteFilterRequestRedirect\n                          or httpRouteFilterRequestRewrite, but not both\n                        rule: '!(self.exists(f, f.type == ''RequestRedirect'') &&\n                          self.exists(f, f.type == ''URLRewrite''))'\n                      - message: RequestHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                          <= 1\n                      - message: ResponseHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                          <= 1\n                      - message: RequestRedirect filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestRedirect').size() <=\n                          1\n                      - message: URLRewrite filter cannot be repeated\n                        rule: self.filter(f, f.type == 'URLRewrite').size() <= 1\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: |-\n                        Matches define conditions used for matching the rule against incoming\n                        HTTP requests. Each match is independent, i.e. this rule will be matched\n                        if **any** one of the matches is satisfied.\n\n\n                        For example, take the following matches configuration:\n\n\n                        ```\n                        matches:\n                        - path:\n                            value: \"/foo\"\n                          headers:\n                          - name: \"version\"\n                            value: \"v2\"\n                        - path:\n                            value: \"/v2/foo\"\n                        ```\n\n\n                        For a request to match against this rule, a request must satisfy\n                        EITHER of the two conditions:\n\n\n                        - path prefixed with `/foo` AND contains the header `version: v2`\n                        - path prefix of `/v2/foo`\n\n\n                        See the documentation for HTTPRouteMatch on how to specify multiple\n                        match conditions that should be ANDed together.\n\n\n                        If no matches are specified, the default is a prefix\n                        path match on \"/\", which has the effect of matching every\n                        HTTP request.\n\n\n                        Proxy or Load Balancer routing configuration generated from HTTPRoutes\n                        MUST prioritize matches based on the following criteria, continuing on\n                        ties. Across all rules specified on applicable Routes, precedence must be\n                        given to the match having:\n\n\n                        * \"Exact\" path match.\n                        * \"Prefix\" path match with largest number of characters.\n                        * Method match.\n                        * Largest number of header matches.\n                        * Largest number of query param matches.\n\n\n                        Note: The precedence of RegularExpression path matches are implementation-specific.\n\n\n                        If ties still exist across multiple Routes, matching precedence MUST be\n                        determined in order of the following criteria, continuing on ties:\n\n\n                        * The oldest Route based on creation timestamp.\n                        * The Route appearing first in alphabetical order by\n                          \"{namespace}/{name}\".\n\n\n                        If ties still exist within an HTTPRoute, matching precedence MUST be granted\n                        to the FIRST matching rule (in list order) with a match meeting the above\n                        criteria.\n\n\n                        When no rules matching a request have been successfully attached to the\n                        parent a request is coming from, a HTTP 404 status code MUST be returned.\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given\\naction. Multiple match types\n                          are ANDed together, i.e. the match will\\nevaluate to true\n                          only if all conditions are satisfied.\\n\\n\\nFor example,\n                          the match below will match a HTTP request only if its path\\nstarts\n                          with `/foo` AND it contains the `version: v1` header:\\n\\n\\n```\\nmatch:\\n\\n\\n\\tpath:\\n\\t\n                          \\ value: \\\"/foo\\\"\\n\\theaders:\\n\\t- name: \\\"version\\\"\\n\\t\n                          \\ value \\\"v1\\\"\\n\\n\\n```\"\n                        properties:\n                          headers:\n                            description: |-\n                              Headers specifies HTTP request header matchers. Multiple match values are\n                              ANDed together, meaning, a request must match all the specified headers\n                              to select the route.\n                            items:\n                              description: |-\n                                HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request\n                                headers.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                    case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                    If multiple entries specify equivalent header names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be ignored. Due to the\n                                    case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                    equivalent.\n\n\n                                    When a header is repeated in an HTTP request, it is\n                                    implementation-specific behavior as to how this is represented.\n                                    Generally, proxies should follow the guidance from the RFC:\n                                    https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding\n                                    processing a repeated header, with special handling for \"Set-Cookie\".\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: |-\n                                    Type specifies how to match against the value of the header.\n\n\n                                    Support: Core (Exact)\n\n\n                                    Support: Implementation-specific (RegularExpression)\n\n\n                                    Since RegularExpression HeaderMatchType has implementation-specific\n                                    conformance, implementations can support POSIX, PCRE or any other dialects\n                                    of regular expressions. Please read the implementation's documentation to\n                                    determine the supported dialect.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: |-\n                              Method specifies HTTP method matcher.\n                              When specified, this route will be matched only if the request has the\n                              specified method.\n\n\n                              Support: Extended\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: |-\n                              Path specifies a HTTP request path matcher. If this field is not\n                              specified, a default prefix match on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: |-\n                                  Type specifies how to match against the path Value.\n\n\n                                  Support: Core (Exact, PathPrefix)\n\n\n                                  Support: Implementation-specific (RegularExpression)\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                            x-kubernetes-validations:\n                            - message: value must be an absolute path and start with\n                                '/' when type one of ['Exact', 'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'')\n                                : true'\n                            - message: must not contain '//' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'')\n                                : true'\n                            - message: must not contain '/./' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'')\n                                : true'\n                            - message: must not contain '/../' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'')\n                                : true'\n                            - message: must not contain '%2f' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'')\n                                : true'\n                            - message: must not contain '%2F' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'')\n                                : true'\n                            - message: must not contain '#' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'')\n                                : true'\n                            - message: must not end with '/..' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'')\n                                : true'\n                            - message: must not end with '/.' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'')\n                                : true'\n                            - message: type must be one of ['Exact', 'PathPrefix',\n                                'RegularExpression']\n                              rule: self.type in ['Exact','PathPrefix'] || self.type\n                                == 'RegularExpression'\n                            - message: must only contain valid characters (matching\n                                ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$)\n                                for types ['Exact', 'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r\"\"\"^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$\"\"\")\n                                : true'\n                          queryParams:\n                            description: |-\n                              QueryParams specifies HTTP query parameter matchers. Multiple match\n                              values are ANDed together, meaning, a request must match all the\n                              specified query parameters to select the route.\n\n\n                              Support: Extended\n                            items:\n                              description: |-\n                                HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP\n                                query parameters.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the HTTP query param to be matched. This must be an\n                                    exact string match. (See\n                                    https://tools.ietf.org/html/rfc7230#section-2.7.3).\n\n\n                                    If multiple entries specify equivalent query param names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent query param name MUST be ignored.\n\n\n                                    If a query param is repeated in an HTTP request, the behavior is\n                                    purposely left undefined, since different data planes have different\n                                    capabilities. However, it is *recommended* that implementations should\n                                    match against the first value of the param if the data plane supports it,\n                                    as this behavior is expected in other load balancing contexts outside of\n                                    the Gateway API.\n\n\n                                    Users SHOULD NOT route traffic based on repeated query params to guard\n                                    themselves against potential differences in the implementations.\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: |-\n                                    Type specifies how to match against the value of the query parameter.\n\n\n                                    Support: Extended (Exact)\n\n\n                                    Support: Implementation-specific (RegularExpression)\n\n\n                                    Since RegularExpression QueryParamMatchType has Implementation-specific\n                                    conformance, implementations can support POSIX, PCRE or any other\n                                    dialects of regular expressions. Please read the implementation's\n                                    documentation to determine the supported dialect.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                    sessionPersistence:\n                      description: |+\n                        SessionPersistence defines and configures session persistence\n                        for the route rule.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        absoluteTimeout:\n                          description: |-\n                            AbsoluteTimeout defines the absolute timeout of the persistent\n                            session. Once the AbsoluteTimeout duration has elapsed, the\n                            session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        cookieConfig:\n                          description: |-\n                            CookieConfig provides configuration settings that are specific\n                            to cookie-based session persistence.\n\n\n                            Support: Core\n                          properties:\n                            lifetimeType:\n                              default: Session\n                              description: |-\n                                LifetimeType specifies whether the cookie has a permanent or\n                                session-based lifetime. A permanent cookie persists until its\n                                specified expiry time, defined by the Expires or Max-Age cookie\n                                attributes, while a session cookie is deleted when the current\n                                session ends.\n\n\n                                When set to \"Permanent\", AbsoluteTimeout indicates the\n                                cookie's lifetime via the Expires or Max-Age cookie attributes\n                                and is required.\n\n\n                                When set to \"Session\", AbsoluteTimeout indicates the\n                                absolute lifetime of the cookie tracked by the gateway and\n                                is optional.\n\n\n                                Support: Core for \"Session\" type\n\n\n                                Support: Extended for \"Permanent\" type\n                              enum:\n                              - Permanent\n                              - Session\n                              type: string\n                          type: object\n                        idleTimeout:\n                          description: |-\n                            IdleTimeout defines the idle timeout of the persistent session.\n                            Once the session has been idle for more than the specified\n                            IdleTimeout duration, the session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        sessionName:\n                          description: |-\n                            SessionName defines the name of the persistent session token\n                            which may be reflected in the cookie or the header. Users\n                            should avoid reusing session names to prevent unintended\n                            consequences, such as rejection or unpredictable behavior.\n\n\n                            Support: Implementation-specific\n                          maxLength: 128\n                          type: string\n                        type:\n                          default: Cookie\n                          description: |-\n                            Type defines the type of session persistence such as through\n                            the use a header or cookie. Defaults to cookie based session\n                            persistence.\n\n\n                            Support: Core for \"Cookie\" type\n\n\n                            Support: Extended for \"Header\" type\n                          enum:\n                          - Cookie\n                          - Header\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: AbsoluteTimeout must be specified when cookie lifetimeType\n                          is Permanent\n                        rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType)\n                          || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)'\n                    timeouts:\n                      description: |+\n                        Timeouts defines the timeouts that can be configured for an HTTP request.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        backendRequest:\n                          description: |-\n                            BackendRequest specifies a timeout for an individual request from the gateway\n                            to a backend. This covers the time from when the request first starts being\n                            sent from the gateway to when the full response has been received from the backend.\n\n\n                            Setting a timeout to the zero duration (e.g. \"0s\") SHOULD disable the timeout\n                            completely. Implementations that cannot completely disable the timeout MUST\n                            instead interpret the zero duration as the longest possible value to which\n                            the timeout can be set.\n\n\n                            An entire client HTTP transaction with a gateway, covered by the Request timeout,\n                            may result in more than one call from the gateway to the destination backend,\n                            for example, if automatic retries are supported.\n\n\n                            Because the Request timeout encompasses the BackendRequest timeout, the value of\n                            BackendRequest must be <= the value of Request timeout.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        request:\n                          description: |-\n                            Request specifies the maximum duration for a gateway to respond to an HTTP request.\n                            If the gateway has not been able to respond before this deadline is met, the gateway\n                            MUST return a timeout error.\n\n\n                            For example, setting the `rules.timeouts.request` field to the value `10s` in an\n                            `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds\n                            to complete.\n\n\n                            Setting a timeout to the zero duration (e.g. \"0s\") SHOULD disable the timeout\n                            completely. Implementations that cannot completely disable the timeout MUST\n                            instead interpret the zero duration as the longest possible value to which\n                            the timeout can be set.\n\n\n                            This timeout is intended to cover as close to the whole request-response transaction\n                            as possible although an implementation MAY choose to start the timeout after the entire\n                            request stream has been received instead of immediately after the transaction is\n                            initiated by the client.\n\n\n                            When this field is unspecified, request timeout behavior is implementation-specific.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: backendRequest timeout cannot be longer than request\n                          timeout\n                        rule: '!(has(self.request) && has(self.backendRequest) &&\n                          duration(self.request) != duration(''0s'') && duration(self.backendRequest)\n                          > duration(self.request))'\n                  type: object\n                  x-kubernetes-validations:\n                  - message: RequestRedirect filter must not be used together with\n                      backendRefs\n                    rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ?\n                      (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))):\n                      true'\n                  - message: When using RequestRedirect filter with path.replacePrefixMatch,\n                      exactly one PathPrefix match must be specified\n                    rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect)\n                      && has(f.requestRedirect.path) && f.requestRedirect.path.type\n                      == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch)))\n                      ? ((size(self.matches) != 1 || !has(self.matches[0].path) ||\n                      self.matches[0].path.type != ''PathPrefix'') ? false : true)\n                      : true'\n                  - message: When using URLRewrite filter with path.replacePrefixMatch,\n                      exactly one PathPrefix match must be specified\n                    rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite)\n                      && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch''\n                      && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches)\n                      != 1 || !has(self.matches[0].path) || self.matches[0].path.type\n                      != ''PathPrefix'') ? false : true) : true'\n                  - message: Within backendRefs, when using RequestRedirect filter\n                      with path.replacePrefixMatch, exactly one PathPrefix match must\n                      be specified\n                    rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b,\n                      (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect)\n                      && has(f.requestRedirect.path) && f.requestRedirect.path.type\n                      == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch)))\n                      )) ? ((size(self.matches) != 1 || !has(self.matches[0].path)\n                      || self.matches[0].path.type != ''PathPrefix'') ? false : true)\n                      : true'\n                  - message: Within backendRefs, When using URLRewrite filter with\n                      path.replacePrefixMatch, exactly one PathPrefix match must be\n                      specified\n                    rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b,\n                      (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite)\n                      && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch''\n                      && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches)\n                      != 1 || !has(self.matches[0].path) || self.matches[0].path.type\n                      != ''PathPrefix'') ? false : true) : true'\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: null\n  storedVersions: null\n{{- end }}\n\n"
  },
  {
    "path": "charts/linkerd-crds/templates/gateway.networking.k8s.io_tcproutes.yaml",
    "content": "{{- if (ternary .Values.enableTcpRoutes .Values.installGatewayAPI (hasKey .Values \"enableTcpRoutes\")) }}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997\n    gateway.networking.k8s.io/bundle-version: v1.1.1\n    gateway.networking.k8s.io/channel: experimental\n    {{ include \"partials.annotations.created-by\" . }}\n    helm.sh/resource-policy: keep\n  labels:\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n  creationTimestamp: null\n  name: tcproutes.gateway.networking.k8s.io\nspec:\n  group: gateway.networking.k8s.io\n  names:\n    categories:\n    - gateway-api\n    kind: TCPRoute\n    listKind: TCPRouteList\n    plural: tcproutes\n    singular: tcproute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1alpha2\n    schema:\n      openAPIV3Schema:\n        description: |-\n          TCPRoute provides a way to route TCP requests. When combined with a Gateway\n          listener, it can be used to forward connections on the port specified by the\n          listener to a set of backends specified by the TCPRoute.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of TCPRoute.\n            properties:\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                description: Rules are a list of TCP matchers and actions.\n                items:\n                  description: TCPRouteRule is the configuration for a given rule.\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent. If unspecified or invalid (refers to a non-existent resource or a\n                        Service with no endpoints), the underlying implementation MUST actively\n                        reject connection attempts to this backend. Connection rejections must\n                        respect weight; if an invalid backend is requested to have 80% of\n                        connections, then 80% of connections must be rejected instead.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Extended for Kubernetes ServiceImport\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Extended\n                      items:\n                        description: |-\n                          BackendRef defines how a Route should forward a request to a Kubernetes\n                          resource.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n\n\n                          Note that when the BackendTLSPolicy object is enabled by the implementation,\n                          there are some extra rules about validity to consider here. See the fields\n                          where this struct is used for more information about the exact behavior.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      minItems: 1\n                      type: array\n                  type: object\n                maxItems: 16\n                minItems: 1\n                type: array\n            required:\n            - rules\n            type: object\n          status:\n            description: Status defines the current state of TCPRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: null\n  storedVersions: null\n{{- end }}\n"
  },
  {
    "path": "charts/linkerd-crds/templates/gateway.networking.k8s.io_tlsroutes.yaml",
    "content": "{{- if (ternary .Values.enableTlsRoutes .Values.installGatewayAPI (hasKey .Values \"enableTlsRoutes\")) }}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997\n    gateway.networking.k8s.io/bundle-version: v1.1.1\n    gateway.networking.k8s.io/channel: experimental\n    {{ include \"partials.annotations.created-by\" . }}\n    helm.sh/resource-policy: keep\n  labels:\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\n  creationTimestamp: null\n  name: tlsroutes.gateway.networking.k8s.io\nspec:\n  group: gateway.networking.k8s.io\n  names:\n    categories:\n    - gateway-api\n    kind: TLSRoute\n    listKind: TLSRouteList\n    plural: tlsroutes\n    singular: tlsroute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1alpha2\n    schema:\n      openAPIV3Schema:\n        description: |-\n          The TLSRoute resource is similar to TCPRoute, but can be configured\n          to match against TLS-specific metadata. This allows more flexibility\n          in matching streams for a given TLS listener.\n\n\n          If you need to forward traffic to a single target for a TLS listener, you\n          could choose to use a TCPRoute with a TLS listener.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of TLSRoute.\n            properties:\n              hostnames:\n                description: |-\n                  Hostnames defines a set of SNI names that should match against the\n                  SNI attribute of TLS ClientHello message in TLS handshake. This matches\n                  the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                  1. IPs are not allowed in SNI names per RFC 6066.\n                  2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                     label must appear by itself as the first label.\n\n\n                  If a hostname is specified by both the Listener and TLSRoute, there\n                  must be at least one intersecting hostname for the TLSRoute to be\n                  attached to the Listener. For example:\n\n\n                  * A Listener with `test.example.com` as the hostname matches TLSRoutes\n                    that have either not specified any hostnames, or have specified at\n                    least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches TLSRoutes\n                    that have either not specified any hostnames or have specified at least\n                    one hostname that matches the Listener hostname. For example,\n                    `test.example.com` and `*.example.com` would both match. On the other\n                    hand, `example.com` and `test.example.net` would not match.\n\n\n                  If both the Listener and TLSRoute have specified hostnames, any\n                  TLSRoute hostnames that do not match the Listener hostname MUST be\n                  ignored. For example, if a Listener specified `*.example.com`, and the\n                  TLSRoute specified `test.example.com` and `test.example.net`,\n                  `test.example.net` must not be considered for a match.\n\n\n                  If both the Listener and TLSRoute have specified hostnames, and none\n                  match with the criteria above, then the TLSRoute is not accepted. The\n                  implementation must raise an 'Accepted' Condition with a status of\n                  `False` in the corresponding RouteParentStatus.\n\n\n                  Support: Core\n                items:\n                  description: |-\n                    Hostname is the fully qualified domain name of a network host. This matches\n                    the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                     1. IPs are not allowed.\n                     2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                        label must appear by itself as the first label.\n\n\n                    Hostname can be \"precise\" which is a domain name without the terminating\n                    dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a\n                    domain name prefixed with a single wildcard label (e.g. `*.example.com`).\n\n\n                    Note that as per RFC1035 and RFC1123, a *label* must consist of lower case\n                    alphanumeric characters or '-', and must start and end with an alphanumeric\n                    character. No other punctuation is allowed.\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                description: Rules are a list of TLS matchers and actions.\n                items:\n                  description: TLSRouteRule is the configuration for a given rule.\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent. If unspecified or invalid (refers to a non-existent resource or\n                        a Service with no endpoints), the rule performs no forwarding; if no\n                        filters are specified that would result in a response being sent, the\n                        underlying implementation must actively reject request attempts to this\n                        backend, by rejecting the connection or returning a 500 status code.\n                        Request rejections must respect weight; if an invalid backend is\n                        requested to have 80% of requests, then 80% of requests must be rejected\n                        instead.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Extended for Kubernetes ServiceImport\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Extended\n                      items:\n                        description: |-\n                          BackendRef defines how a Route should forward a request to a Kubernetes\n                          resource.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n\n\n                          Note that when the BackendTLSPolicy object is enabled by the implementation,\n                          there are some extra rules about validity to consider here. See the fields\n                          where this struct is used for more information about the exact behavior.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      minItems: 1\n                      type: array\n                  type: object\n                maxItems: 16\n                minItems: 1\n                type: array\n            required:\n            - rules\n            type: object\n          status:\n            description: Status defines the current state of TLSRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: null\n  storedVersions: null\n{{- end }}\n"
  },
  {
    "path": "charts/linkerd-crds/templates/policy/authorization-policy.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: authorizationpolicies.policy.linkerd.io\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  labels:\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: AuthorizationPolicy\n    plural: authorizationpolicies\n    singular: authorizationpolicy\n    shortNames: [authzpolicy]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied server\n                resources.\n              type: object\n              required: [targetRef, requiredAuthenticationRefs]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the authorization\n                    policy applies.\n                  type: object\n                  required: [kind, name]\n                  # Modified from the gateway API.\n                  # Copyright 2020 The Kubernetes Authors\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: >-\n                        Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                requiredAuthenticationRefs:\n                  description: >-\n                    RequiredAuthenticationRefs enumerates a set of required\n                    authentications. ALL authentications must be satisfied for\n                    the authorization to apply. If any of the referred objects\n                    cannot be found, the authorization will be ignored.\n                  type: array\n                  items:\n                    type: object\n                    required: [kind, name]\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n"
  },
  {
    "path": "charts/linkerd-crds/templates/policy/egress-network.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: egressnetworks.policy.linkerd.io\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  labels:\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\nspec:\n  group: policy.linkerd.io\n  names:\n    categories:\n    - policy\n    kind: EgressNetwork\n    listKind: EgressNetworkList\n    plural: egressnetworks\n    singular: egressnetwork\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An EgressNetwork captures traffic to egress destinations\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              trafficPolicy:\n                description: >-\n                  This field controls the traffic policy enforced upon traffic\n                  that does not match any explicit route resources associated\n                  with an instance of this object. The values that are allowed\n                  currently are:\n                   - Allow - permits all traffic, even if it has not been\n                                explicitly described via attaching an xRoute\n                                resources.\n                   - Deny -  blocks all traffic that has not been described via\n                                attaching an xRoute resource.\n                type: string\n                enum:\n                - Allow\n                - Deny\n              networks:\n                type: array\n                items:\n                  type: object\n                  required: [cidr]\n                  properties:\n                    cidr:\n                      description: >-\n                        The CIDR of the network to be authorized.\n                      type: string\n                    except:\n                      description: >-\n                        A list of IP networks/addresses not to be included in\n                        the above `cidr`.\n                      type: array\n                      items:\n                        type: string\n            type: object\n            required:\n            - trafficPolicy\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n"
  },
  {
    "path": "charts/linkerd-crds/templates/policy/http-local-ratelimit-policy.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httplocalratelimitpolicies.policy.linkerd.io\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  labels:\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPLocalRateLimitPolicy\n    listKind: HTTPLocalRateLimitPolicyList\n    plural: httplocalratelimitpolicies\n    singular: httplocalratelimitpolicy\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      subresources:\n        status: {}\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required: [targetRef]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the rate limit\n                    policy applies. Only Server is allowed.\n                  type: object\n                  required: [kind, name]\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                total:\n                  description: >-\n                    Overall rate-limit, which all traffic coming to this\n                    target should abide.\n                    If unset no overall limit is applied.\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                identity:\n                  description: >-\n                    Fairness for individual identities; each separate client,\n                    grouped by identity, will have this rate-limit. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                overrides:\n                  description: >-\n                    Overrides for traffic from a specific client. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: array\n                  items:\n                    type: object\n                    required: [requestsPerSecond, clientRefs]\n                    properties:\n                      requestsPerSecond:\n                        format: int64\n                        type: integer\n                      clientRefs:\n                        type: array\n                        items:\n                          type: object\n                          required: [kind, name]\n                          properties:\n                            group:\n                              description: >-\n                                Group is the group of the referent. When empty, the\n                                Kubernetes core API group is inferred.\n                              maxLength: 253\n                              pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                              type: string\n                            kind:\n                              description: Kind is the kind of the referent.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                              type: string\n                            namespace:\n                              description: >-\n                                  Namespace is the namespace of the referent.\n                                  When unspecified (or empty string), this refers to the\n                                  local namespace of the Policy.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                              type: string\n                            name:\n                              description: Name is the name of the referent.\n                              maxLength: 253\n                              minLength: 1\n                              type: string\n            status:\n              type: object\n              properties:\n                conditions:\n                  type: array\n                  items:\n                    type: object\n                    properties:\n                      lastTransitionTime:\n                        description: lastTransitionTime is the last time the\n                          condition transitioned from one status to another.\n                        format: date-time\n                        type: string\n                      status:\n                        description: status of the condition (one of True, False, Unknown)\n                        enum:\n                        - \"True\"\n                        - \"False\"\n                        - Unknown\n                        type: string\n                      type:\n                        description: type of the condition in CamelCase or in\n                          foo.example.com/CamelCase.\n                        maxLength: 316\n                        pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                        type: string\n                      reason:\n                        description: reason contains a programmatic identifier\n                          indicating the reason for the condition's last\n                          transition. Producers of specific condition types may\n                          define expected values and meanings for this field, and\n                          whether the values are considered a guaranteed API. The\n                          value should be a CamelCase string. This field may not\n                          be empty.\n                        maxLength: 1024\n                        minLength: 1\n                        pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                        type: string\n                      message:\n                        description: message is a human readable message\n                          indicating details about the transition. This may be an\n                          empty string.\n                        maxLength: 32768\n                        type: string\n                  required:\n                  - status\n                  - type\n                targetRef:\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Server\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                  required:\n                  - name\n                  type: object\n              required:\n              - targetRef\n      additionalPrinterColumns:\n      - name: Target_kind\n        description: The resource kind to which the rate-limit applies\n        type: string\n        jsonPath: .spec.targetRef.kind\n      - name: Target_name\n        type: string\n        description: The resource name to which the rate-limit applies\n        jsonPath: .spec.targetRef.name\n      - name: Total_RPS\n        description: The overall rate-limit\n        type: integer\n        format: int32\n        jsonPath: .spec.total.requestsPerSecond\n      - name: Identity_RPS\n        description: The rate-limit per identity\n        type: integer\n        format: int32\n        jsonPath: .spec.identity.requestsPerSecond\n"
  },
  {
    "path": "charts/linkerd-crds/templates/policy/httproute.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httproutes.policy.linkerd.io\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  labels:\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPRoute\n    listKind: HTTPRouteList\n    plural: httproutes\n    singular: httproute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\n                              All   implementations must support core filters. \\n\\n \"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta2\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta3\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                    timeouts:\n                      description: \"Timeouts defines the timeouts that can be configured\n                        for an HTTP request. \\n Support: Core \\n <gateway:experimental>\"\n                      properties:\n                        backendRequest:\n                          description: \"BackendRequest specifies a timeout for an\n                            individual request from the gateway to a backend service.\n                            Typically used in conjunction with automatic retries,\n                            if supported by an implementation. Default is the value\n                            of Request timeout. \\n Support: Extended\"\n                          format: duration\n                          type: string\n                        request:\n                          description: \"Request specifies a timeout for responding\n                            to client HTTP requests, disabled by default. \\n For example,\n                            the following rule will timeout if a client request is\n                            taking longer than 10 seconds to complete: \\n ``` rules:\n                            - timeouts: request: 10s backendRefs: ... ``` \\n Support:\n                            Core\"\n                          format: duration\n                          type: string\n                      type: object\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n"
  },
  {
    "path": "charts/linkerd-crds/templates/policy/meshtls-authentication.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: meshtlsauthentications.policy.linkerd.io\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  labels:\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: MeshTLSAuthentication\n    plural: meshtlsauthentications\n    singular: meshtlsauthentication\n    shortNames: [meshtlsauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                MeshTLSAuthentication defines a list of authenticated client IDs\n                to be referenced by an `AuthorizationPolicy`. If a client\n                connection has the mutually-authenticated identity that matches\n                ANY of the of the provided identities, the connection is\n                considered authenticated.\n              type: object\n              oneOf:\n                - required: [identities]\n                - required: [identityRefs]\n              properties:\n                identities:\n                  description: >-\n                    Authorizes clients with the provided proxy identity strings\n                    (as provided via MTLS)\n\n                    The `*` prefix can be used to match all identities in\n                    a domain. An identity string of `*` indicates that\n                    all authentication clients are authorized.\n                  type: array\n                  minItems: 1\n                  items:\n                    type: string\n                identityRefs:\n                  type: array\n                  minItems: 1\n                  items:\n                    type: object\n                    required:\n                      - kind\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this refers to all resources of the specified Group\n                          and Kind in the specified namespace.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n"
  },
  {
    "path": "charts/linkerd-crds/templates/policy/network-authentication.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: networkauthentications.policy.linkerd.io\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  labels:\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: NetworkAuthentication\n    plural: networkauthentications\n    singular: networkauthentication\n    shortNames: [netauthn, networkauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                NetworkAuthentication defines a list of authenticated client\n                networks to be referenced by an `AuthorizationPolicy`. If a\n                client connection originates from ANY of the of the provided\n                networks, the connection is considered authenticated.\n              type: object\n              required: [networks]\n              properties:\n                networks:\n                  type: array\n                  items:\n                    type: object\n                    required: [cidr]\n                    properties:\n                      cidr:\n                        description: >-\n                          The CIDR of the network to be authorized.\n                        type: string\n                      except:\n                        description: >-\n                          A list of IP networks/addresses not to be included in\n                          the above `cidr`.\n                        type: array\n                        items:\n                          type: string\n"
  },
  {
    "path": "charts/linkerd-crds/templates/policy/server-authorization.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serverauthorizations.policy.linkerd.io\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  labels:\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: ServerAuthorization\n    plural: serverauthorizations\n    singular: serverauthorization\n    shortNames: [saz, serverauthz, srvauthz]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 ServerAuthorization is deprecated; use policy.linkerd.io/v1beta1 ServerAuthorization\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n    - name: v1beta1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n      additionalPrinterColumns:\n      - name: Server\n        type: string\n        description: The server that this grants access to\n        jsonPath: .spec.server.name\n"
  },
  {
    "path": "charts/linkerd-crds/templates/policy/server.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: servers.policy.linkerd.io\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  labels:\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: Server\n    plural: servers\n    singular: server\n    shortNames: [srv]\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 Server is deprecated; use policy.linkerd.io/v1beta1 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n                  oneOf:\n                    - required: [matchExpressions]\n                    - required: [matchLabels]\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n    - name: v1beta1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1beta1 Server is deprecated; use policy.linkerd.io/v1beta3 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta2\n      served: true\n      storage: false\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta3\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                accessPolicy:\n                  type: string\n                  default: deny\n                  description: >-\n                    Default access policy to apply when the traffic doesn't match any of the policy rules.\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n      - name: Access Policy\n        type: string\n        description: The default access policy applied when the traffic doesn't match any of the policy rules\n        jsonPath: .spec.accessPolicy\n"
  },
  {
    "path": "charts/linkerd-crds/templates/serviceprofile.yaml",
    "content": "---\n###\n### Service Profile CRD\n###\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serviceprofiles.linkerd.io\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  labels:\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\nspec:\n  group: linkerd.io\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            required:\n            - routes\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  - name: v1alpha2\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  scope: Namespaced\n  names:\n    plural: serviceprofiles\n    singular: serviceprofile\n    kind: ServiceProfile\n    shortNames:\n    - sp\n"
  },
  {
    "path": "charts/linkerd-crds/templates/workload/external-workload.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: externalworkloads.workload.linkerd.io\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  labels:\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    linkerd.io/control-plane-ns: {{.Release.Namespace}}\nspec:\n  group: workload.linkerd.io\n  names:\n    categories:\n    - external\n    kind: ExternalWorkload\n    listKind: ExternalWorkloadList\n    plural: externalworkloads\n    singular: externalworkload\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTls:\n                description: meshTls describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                # TODO: relax this in the future when ipv6 is supported\n                # an external workload (like a pod) should only\n                # support 2 interfaces\n                maxItems: 1\n            type: object\n            required:\n            - meshTls\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTls.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n  - name: v1beta1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTLS:\n                description: meshTLS describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload. This field may\n                  hold a maximum of two entries. If one entry, it can be an\n                  IPv4 or IPv6 address; if two entries it should contain one\n                  IPv4 address and one IPv6 address.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                maxItems: 2\n            type: object\n            required:\n            - meshTLS\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTLS.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n"
  },
  {
    "path": "charts/linkerd-crds/values.yaml",
    "content": "# -- Controls if Linkerd should install the Gateway API CRDs. This value acts\n# as a default and can be overridden by the more specific `enable*Routes`\n# values.\ninstallGatewayAPI: false\n# -- Controls if Linkerd should install the Gateway API HTTPRoutes and\n# GRPCRoutes CRDs. If unspecified, the value of `installGatewayAPI` is used.\n# enableHttpRoutes: false\n# -- Controls if Linkerd should install the Gateway API TLSRoutes CRD. If\n# unspecified, the value of `installGatewayAPI` is used.\n# enableTlsRoutes: false\n# -- Controls if Linkerd should install the Gateway API TCPRoutes CRD. If\n# unspecified, the value of `installGatewayAPI` is used.\n# enableTcpRoutes: false\n"
  },
  {
    "path": "charts/linkerd2-cni/.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\nOWNERS\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*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n"
  },
  {
    "path": "charts/linkerd2-cni/Chart.yaml",
    "content": "apiVersion: v1\nappVersion: edge-XX.X.X\ndescription: |\n  Linkerd is a *service mesh*, designed to give platform-wide observability,\n  reliability, and security without requiring configuration or code changes. The\n  Linkerd [CNI plugin](https://linkerd.io/2/features/cni/) takes care of setting\n  up your pod's network so incoming and outgoing traffic is proxied through the\n  data plane.\nkubeVersion: \">=1.23.0-0\"\nicon: https://linkerd.io/images/logo-only-200h.png\nname: \"linkerd2-cni\"\n# this version will be updated by the CI before publishing the Helm tarball\nversion: 0.0.0-undefined\n"
  },
  {
    "path": "charts/linkerd2-cni/README.md.gotmpl",
    "content": "{{ template \"chart.header\" . }}\n{{ template \"chart.description\" . }}\n\n{{ template \"chart.versionBadge\" . }}\n{{ template \"chart.typeBadge\" . }}\n{{ template \"chart.appVersionBadge\" . }}\n\n{{ template \"chart.homepageLine\" . }}\n\n{{ template \"chart.requirementsSection\" . }}\n\n{{ template \"chart.valuesSection\" . }}\n\n{{ template \"helm-docs.versionFooter\" . }}\n"
  },
  {
    "path": "charts/linkerd2-cni/requirements.yaml",
    "content": "dependencies:\n- name: partials\n  version: 0.1.0\n  repository: file://../partials\n"
  },
  {
    "path": "charts/linkerd2-cni/templates/cni-plugin.yaml",
    "content": "{{- /*\nCopyright 2017 CNI authors\nModifications copyright (c) Linkerd 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\nThis file was inspired by\n1) https://github.com/istio/cni/blob/c63a509539b5ed165a6617548c31b686f13c2133/deployments/kubernetes/install/manifests/istio-cni.yaml\n*/ -}}\n{{- if eq .Release.Service \"CLI\" -}}\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: {{.Release.Namespace}}\n  labels:\n    linkerd.io/cni-resource: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    pod-security.kubernetes.io/enforce: privileged\n---\n{{ end -}}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: linkerd-cni\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/cni-resource: \"true\"\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n{{- if .Values.imagePullSecrets }}\nimagePullSecrets:\n{{ toYaml .Values.imagePullSecrets | indent 2 }}\n{{- end }}\n{{ if .Values.enablePSP -}}\n---\napiVersion: policy/v1beta1\nkind: PodSecurityPolicy\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-cni\n  annotations:\n    seccomp.security.alpha.kubernetes.io/allowedProfileNames: \"runtime/default\"\n  labels:\n    linkerd.io/cni-resource: \"true\"\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nspec:\n  {{- if not .Values.privileged }}\n  allowPrivilegeEscalation: false\n  {{- end }}\n  fsGroup:\n    rule: RunAsAny\n  runAsUser:\n    rule: RunAsAny\n  runAsGroup:\n    rule: RunAsAny\n  seLinux:\n    rule: RunAsAny\n  supplementalGroups:\n    rule: RunAsAny\n  volumes:\n  - hostPath\n  - secret\n  - emptyDir\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-cni\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/cni-resource: \"true\"\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: ['extensions', 'policy']\n  resources: ['podsecuritypolicies']\n  resourceNames:\n  - linkerd-{{.Release.Namespace}}-cni\n  verbs: ['use']\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-cni\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/cni-resource: \"true\"\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: linkerd-cni\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-cni\n  namespace: {{.Release.Namespace}}\n{{ end -}}\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"nodes\", \"namespaces\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n{{- if .Values.repairController.enabled }}\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"delete\"]\n- apiGroups: [\"events.k8s.io\"]\n  resources: [\"events\"]\n  verbs: [\"create\"]\n{{- end }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-cni\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-cni\n  namespace: {{.Release.Namespace}}\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-cni-config\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/cni-resource: \"true\"\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\ndata:\n  dest_cni_net_dir: \"{{.Values.destCNINetDir}}\"\n  dest_cni_bin_dir: \"{{.Values.destCNIBinDir}}\"\n  # The CNI network configuration to install on each node. The special\n  # values in this config will be automatically populated.\n  # iptables-mode and ipv6 flags are only considered as of linkerd-cni v1.4.0\n  cni_network_config: |-\n    {\n      \"name\": \"linkerd-cni\",\n      \"type\": \"linkerd-cni\",\n      \"log_level\": \"{{.Values.logLevel}}\",\n      \"kubernetes\": {\n          \"kubeconfig\": \"__KUBECONFIG_FILEPATH__\"\n      },\n      \"linkerd\": {\n        \"incoming-proxy-port\": {{.Values.inboundProxyPort}},\n        \"outgoing-proxy-port\": {{.Values.outboundProxyPort}},\n        \"proxy-uid\": {{.Values.proxyUID}},\n        {{- if ge (int .Values.proxyGID) 0 }}\n        \"proxy-gid\": {{.Values.proxyGID}},\n        {{- end }}\n        \"ports-to-redirect\": [{{.Values.portsToRedirect}}],\n        \"inbound-ports-to-ignore\": [\"{{- .Values.proxyAdminPort }}\",\"{{ .Values.proxyControlPort }}\"\n        {{- if .Values.ignoreInboundPorts }},{{- include \"partials.splitStringList\" .Values.ignoreInboundPorts -}}{{- end }}],\n        {{- if .Values.ignoreOutboundPorts }}\n        \"outbound-ports-to-ignore\": [\n          {{- include \"partials.splitStringList\" .Values.ignoreOutboundPorts -}}\n        ],\n        {{- end }}\n        \"simulate\": false,\n        \"use-wait-flag\": {{.Values.useWaitFlag}},\n        \"iptables-mode\": {{.Values.iptablesMode | quote}},\n        \"ipv6\": {{.Values.disableIPv6 | ternary \"false\" \"true\"}}\n      }\n    }\n---\nkind: DaemonSet\napiVersion: apps/v1\nmetadata:\n  name: linkerd-cni\n  namespace: {{ .Release.Namespace }}\n  labels:\n    k8s-app: linkerd-cni\n    linkerd.io/cni-resource: \"true\"\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  revisionHistoryLimit: {{.Values.revisionHistoryLimit}}\n  selector:\n    matchLabels:\n      k8s-app: linkerd-cni\n  updateStrategy:{{ .Values.updateStrategy | toYaml | nindent 4 }}\n  template:\n    metadata:\n      labels:\n        k8s-app: linkerd-cni\n        linkerd.io/cni-resource: \"true\"\n        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n      annotations:\n        {{ include \"partials.annotations.created-by\" . }}\n        linkerd.io/inject: disabled\n    spec:\n      {{- if .Values.tolerations }}\n      {{- include \"linkerd.tolerations\" . | nindent 6 }}\n      {{- end }}\n      nodeSelector:\n        kubernetes.io/os: linux\n      {{- if .Values.nodeAffinity }}\n      affinity:\n      {{- include \"linkerd.node-affinity\" . | nindent 8 }}\n      {{- end }}\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-cni\n      {{- if .Values.priorityClassName }}\n      priorityClassName: {{ .Values.priorityClassName }}\n      {{- end }}\n      {{- if .Values.extraInitContainers }}\n      initContainers:\n      {{- toYaml .Values.extraInitContainers | nindent 6 }}\n      {{- end }}\n      containers:\n      # This container installs the linkerd CNI binaries\n      # and CNI network config file on each node. The install\n      # script copies the files into place and then sleeps so\n      # that Kubernetes doesn't keep trying to restart it.\n      - name: install-cni\n        image: {{ .Values.image.name -}}:{{- .Values.image.version }}\n        imagePullPolicy: {{ .Values.image.pullPolicy }}\n        env:\n        - name: DEST_CNI_NET_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_net_dir\n        - name: DEST_CNI_BIN_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_bin_dir\n        - name: CNI_NETWORK_CONFIG\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: cni_network_config\n        - name: SLEEP\n          value: \"true\"\n        lifecycle:\n          # In some edge-cases this helps ensure that cleanup() is called in the container's script\n          # https://github.com/linkerd/linkerd2/issues/2355\n          preStop:\n            exec:\n              command:\n              - /bin/sh\n              - -c\n              - kill -15 1; sleep 15s\n        volumeMounts:\n        {{- if ne .Values.destCNIBinDir .Values.destCNINetDir }}\n        - mountPath: /host{{.Values.destCNIBinDir}}\n          name: cni-bin-dir\n        - mountPath: /host{{.Values.destCNINetDir}}\n          name: cni-net-dir\n        {{- else }}\n        - mountPath: /host{{.Values.destCNINetDir}}\n          name: cni-net-dir\n        {{- end }}\n        - mountPath: /tmp\n          name: linkerd-tmp-dir\n        securityContext:\n          readOnlyRootFilesystem: true\n          privileged: {{.Values.privileged}}\n        {{- if .Values.resources }}\n        {{- include \"partials.resources\" .Values.resources | nindent 8 }}\n        {{- end }}\n      {{- if .Values.repairController.enabled }}\n      # This container watches over pods whose linkerd-network-validator\n      # container failed, probably because of a race condition while setting up\n      # the CNI plugin chain, and deletes those pods so they can try acquiring a\n      # proper network config again\n      - name: repair-controller\n        image: {{ .Values.image.name -}}:{{- .Values.image.version }}\n        imagePullPolicy: {{ .Values.image.pullPolicy }}\n        {{- if .Values.repairController.enableSecurityContext }}\n        env:\n        - name: LINKERD_CNI_REPAIR_CONTROLLER_NODE_NAME\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: LINKERD_CNI_REPAIR_CONTROLLER_POD_NAME\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        command:\n          - /usr/lib/linkerd/linkerd-cni-repair-controller\n        args:\n          - --admin-addr\n          - \"{{ if .Values.disableIPv6 }}0.0.0.0{{ else }}[::]{{ end }}:9990\"\n          - --log-format\n          - {{ .Values.repairController.logFormat }}\n          - --log-level\n          - {{ .Values.repairController.logLevel }}\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: repair-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: repair-admin\n          initialDelaySeconds: 10\n        ports:\n        - containerPort: 9990\n          name: repair-admin\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        {{- end }}\n        {{- if .Values.repairController.resources }}\n        {{- include \"partials.resources\" .Values.repairController.resources | nindent 8 }}\n        {{- end }}\n      {{- end }}\n      volumes:\n      {{- if ne .Values.destCNIBinDir .Values.destCNINetDir }}\n      - name: cni-bin-dir\n        hostPath:\n          path: {{.Values.destCNIBinDir}}\n      - name: cni-net-dir\n        hostPath:\n          path: {{.Values.destCNINetDir}}\n      {{- else }}\n      - name: cni-net-dir\n        hostPath:\n          path: {{.Values.destCNINetDir}}\n      {{- end }}\n      - name: linkerd-tmp-dir\n        emptyDir: {}\n"
  },
  {
    "path": "charts/linkerd2-cni/values.yaml",
    "content": "# -- Inbound port for the proxy container\ninboundProxyPort: 4143\n# -- Outbound port for the proxy container\noutboundProxyPort: 4140\n# -- Default set of inbound ports to skip via iptables\nignoreInboundPorts: \"\"\n# -- Default set of outbound ports to skip via iptables\nignoreOutboundPorts: \"\"\n# -- Admin port for the proxy container\nproxyAdminPort: 4191\n# -- Control port for the proxy container\nproxyControlPort: 4190\n# -- Additional labels to add to all pods\npodLabels: {}\n# -- Labels to apply to all resources\ncommonLabels: {}\n# -- Log level for the CNI plugin\nlogLevel:         info\n# -- Ports to redirect to proxy\nportsToRedirect:  \"\"\n# -- User id under which the proxy shall be ran\nproxyUID:         2102\n# -- (int) Optional customisation of the group id under which the proxy shall be ran (the group ID will be omitted if lower than 0)\nproxyGID: -1\n# -- Directory on the host where the CNI plugin binaries reside\ndestCNINetDir:    \"/etc/cni/net.d\"\n# -- Directory on the host where the CNI configuration will be placed\ndestCNIBinDir:    \"/opt/cni/bin\"\n# -- Configures the CNI plugin to use the -w flag for the iptables command\nuseWaitFlag:      false\n# -- Variant of iptables that will be used to configure routing. Allowed values\n# are 'nft', 'legacy' and 'plain'. They invoke the 'iptables-nft',\n# 'iptables-legacy' and 'iptables' commands respectively. The 'plain' mode is\n# targeted at RHEL, which ships with an nftables-based 'iptables' command.\niptablesMode: \"nft\"\n# -- Disables adding IPv6 rules on top of IPv4 rules\ndisableIPv6: true\n# -- Kubernetes priorityClassName for the CNI plugin's Pods\npriorityClassName: \"\"\n# -- Specifies the number of old ReplicaSets to retain to allow rollback.\nrevisionHistoryLimit: 10\n# -- Specifies the update strategy for how the new pods are rolled out\nupdateStrategy:\n  type: RollingUpdate\n  rollingUpdate:\n    maxUnavailable: 1\n\n# -- Add a PSP resource and bind it to the linkerd-cni ServiceAccounts.\n# Note PSP has been deprecated since k8s v1.21\nenablePSP: false\n\n# -- Run the install-cni container in privileged mode\nprivileged: false\n\n# -|- Tolerations section, See the\n# [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/)\n# for more information\ntolerations:\n  # -- toleration properties\n  - operator: Exists\n\n# -|- NodeAffinity section, See the\n# [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity)\n# for more information\n#nodeAffinity:\n\n# -|- Image section\nimage:\n  # -- Docker image for the CNI plugin\n  name: \"cr.l5d.io/linkerd/cni-plugin\"\n  # -- Tag for the CNI container Docker image\n  version: \"v1.6.6\"\n  # -- Pull policy for the linkerd-cni container\n  pullPolicy: IfNotPresent\n\n#\n## For Private docker registries, authentication is needed.\n# If the control plane service images are pulled from a\n# protected docker registry, define pull secrets as follows:\n#\n#imagePullSecrets:\n#    - name: my-private-docker-registry-login-secret\n#\n# The pull secrets are applied to the respective service accounts\n# which will further orchestrate the deployments.\nimagePullSecrets: []\n\n# -- Add additional initContainers to the daemonset\nextraInitContainers: []\n\n# The cni-repair-controller scans pods in each node to find those that have\n# been injected by linkerd, and whose linkerd-network-validator container has\n# failed. This is usually caused by a race between linkerd-cni and the CNI\n# plugin used in the cluster. This controller deletes those failed pods so they\n# can restart and rety re-acquiring a proper network config.\nrepairController:\n  # -- Enables the repair-controller container\n  enabled: false\n\n  # -- Log level for the repair-controller container\n  # @default -- info\n  logLevel: info\n  # -- Log format (`plain` or `json`) for the repair-controller container\n  # @default -- plain\n  logFormat: plain\n\n  # -- Include a securityContext in the repair-controller container\n  enableSecurityContext: true\n\n  resources:\n    cpu:\n      # -- Maximum amount of CPU units that the repair-controller container can use\n      limit: \"\"\n      # -- Amount of CPU units that the repair-controller container requests\n      request: \"\"\n    memory:\n      # -- Maximum amount of memory that the repair-controller container can use\n      limit: \"\"\n      # -- Amount of memory that the repair-controller container requests\n      request: \"\"\n    ephemeral-storage:\n      # -- Maximum amount of ephemeral storage that the repair-controller container can use\n      limit: \"\"\n      # -- Amount of ephemeral storage that the repair-controller container requests\n      request: \"\"\n\n# -- Resource requests and limits for linkerd-cni daemonset container\nresources:\n  cpu:\n    # -- Maximum amount of CPU units that the cni container can use\n    limit: \"\"\n    # -- Amount of CPU units that the cni container requests\n    request: \"\"\n  memory:\n    # -- Maximum amount of memory that the cni container can use\n    limit: \"\"\n    # -- Amount of memory that the cni container requests\n    request: \"\"\n  ephemeral-storage:\n    # -- Maximum amount of ephemeral storage that the cni container can use\n    limit: \"\"\n    # -- Amount of ephemeral storage that the cni container requests\n    request: \"\"\n"
  },
  {
    "path": "charts/partials/.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*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n"
  },
  {
    "path": "charts/partials/Chart.yaml",
    "content": "apiVersion: v1\ndescription: |\n  A Helm chart containing Linkerd partial templates,\n  depended by the 'linkerd' and 'patch' charts.\nname: partials\nversion: 0.1.0\n"
  },
  {
    "path": "charts/partials/README.md.gotmpl",
    "content": "{{ template \"chart.header\" . }}\n{{ template \"chart.description\" . }}\n\n{{ template \"chart.versionBadge\" . }}\n{{ template \"chart.typeBadge\" . }}\n{{ template \"chart.appVersionBadge\" . }}\n\n{{ template \"chart.homepageLine\" . }}\n\n{{ template \"chart.requirementsSection\" . }}\n\n{{ template \"chart.valuesSection\" . }}\n\n{{ template \"helm-docs.versionFooter\" . }}\n"
  },
  {
    "path": "charts/partials/templates/NOTES.txt",
    "content": ""
  },
  {
    "path": "charts/partials/templates/_affinity.tpl",
    "content": "{{ define \"linkerd.pod-affinity\" -}}\npodAntiAffinity:\n  preferredDuringSchedulingIgnoredDuringExecution:\n  - podAffinityTerm:\n      labelSelector:\n        matchExpressions:\n        - key: {{ default \"linkerd.io/control-plane-component\" .label }}\n          operator: In\n          values:\n          - {{ .component }}\n      topologyKey: topology.kubernetes.io/zone\n    weight: 100\n  requiredDuringSchedulingIgnoredDuringExecution:\n  - labelSelector:\n      matchExpressions:\n      - key: {{ default \"linkerd.io/control-plane-component\" .label }}\n        operator: In\n        values:\n        - {{ .component }}\n    topologyKey: kubernetes.io/hostname\n{{- end }}\n\n{{ define \"linkerd.node-affinity\" -}}\nnodeAffinity:\n{{- toYaml .Values.nodeAffinity | trim | nindent 2 }}\n{{- end }}\n\n{{ define \"linkerd.affinity\" -}}\n{{- if or .Values.enablePodAntiAffinity .Values.nodeAffinity -}}\naffinity:\n{{- end }}\n{{- if .Values.enablePodAntiAffinity -}}\n{{- include \"linkerd.pod-affinity\" . | nindent 2 }}\n{{- end }}\n{{- if .Values.nodeAffinity -}}\n{{- include \"linkerd.node-affinity\" . | nindent 2 }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/partials/templates/_capabilities.tpl",
    "content": "{{- define \"partials.proxy.capabilities\" -}}\ncapabilities:\n  {{- if .Values.proxy.capabilities.add }}\n  add:\n  {{- toYaml .Values.proxy.capabilities.add | trim | nindent 4 }}\n  {{- end }}\n  {{- if .Values.proxy.capabilities.drop }}\n  drop:\n  {{- toYaml .Values.proxy.capabilities.drop | trim | nindent 4 }}\n  {{- end }}\n{{- end -}}\n\n{{- define \"partials.proxy-init.capabilities.drop\" -}}\ndrop:\n{{ toYaml .Values.proxyInit.capabilities.drop | trim }}\n{{- end -}}\n"
  },
  {
    "path": "charts/partials/templates/_debug.tpl",
    "content": "{{- define \"partials.debug\" -}}\nimage: {{.Values.debugContainer.image.name}}:{{.Values.debugContainer.image.version | default .Values.linkerdVersion}}\nimagePullPolicy: {{.Values.debugContainer.image.pullPolicy | default .Values.imagePullPolicy}}\nname: linkerd-debug\nterminationMessagePolicy: FallbackToLogsOnError\n# some environments require probes, so we provide some infallible ones\nlivenessProbe:\n    exec:\n      command:\n      - \"true\"\nreadinessProbe:\n    exec:\n      command:\n      - \"true\"\n{{- end -}}\n"
  },
  {
    "path": "charts/partials/templates/_helpers.tpl",
    "content": "{{/* vim: set filetype=mustache: */}}\n{{/*\nSplits a coma separated list into a list of string values.\nFor example \"11,22,55,44\" will become \"11\",\"22\",\"55\",\"44\"\n*/}}\n{{- define \"partials.splitStringList\" -}}\n{{- if gt (len (toString .)) 0 -}}\n{{- $ports := toString . | splitList \",\" -}}\n{{- $last := sub (len $ports) 1 -}}\n{{- range $i,$port := $ports -}}\n\"{{$port}}\"{{ternary \",\" \"\" (ne $i $last)}}\n{{- end -}}\n{{- end -}}\n{{- end -}}\n"
  },
  {
    "path": "charts/partials/templates/_metadata.tpl",
    "content": "{{- define \"partials.annotations.created-by\" -}}\nlinkerd.io/created-by: {{ .Values.cliVersion | default (printf \"linkerd/helm %s\" ( (.Values.image).version | default .Values.linkerdVersion)) }}\n{{- end -}}\n\n{{- define \"partials.proxy.annotations\" -}}\nlinkerd.io/proxy-version: {{.Values.proxy.image.version | default .Values.linkerdVersion}}\ncluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\nlinkerd.io/trust-root-sha256: {{ .Values.identityTrustAnchorsPEM | sha256sum }}\n{{- end -}}\n\n{{/*\nTo add labels to the control-plane components, instead update at individual component manifests as\nadding here would also update `spec.selector.matchLabels` which are immutable and would fail upgrades.\n*/}}\n{{- define \"partials.proxy.labels\" -}}\nlinkerd.io/proxy-{{.workloadKind}}: {{.component}}\n{{- end -}}\n"
  },
  {
    "path": "charts/partials/templates/_network-validator.tpl",
    "content": "{{- define \"partials.network-validator\" -}}\nname: linkerd-network-validator\nimage: {{.Values.proxy.image.name}}:{{.Values.proxy.image.version | default .Values.linkerdVersion }}\nimagePullPolicy: {{.Values.proxy.image.pullPolicy | default .Values.imagePullPolicy}}\n{{ include \"partials.resources\" .Values.proxy.resources }}\n{{- if ne .Values.networkValidator.enableSecurityContext false }}\nsecurityContext:\n  {{- if .Values.networkValidator.securityContext }}\n  {{- toYaml .Values.networkValidator.securityContext | trim | nindent 2 }}\n  {{- else }}\n  allowPrivilegeEscalation: false\n  capabilities:\n    drop:\n    - ALL\n  readOnlyRootFilesystem: true\n  runAsGroup: 65534\n  runAsNonRoot: true\n  runAsUser: 65534\n  seccompProfile:\n    type: RuntimeDefault  \n  {{- end }}\n{{- end }}\ncommand:\n  - /usr/lib/linkerd/linkerd2-network-validator\nargs:\n  - --log-format\n  - {{ .Values.networkValidator.logFormat }}\n  - --log-level\n  - {{ .Values.networkValidator.logLevel }}\n  - --connect-addr\n    {{- if .Values.networkValidator.connectAddr }}\n  - {{ .Values.networkValidator.connectAddr | quote }}\n    {{- else if .Values.disableIPv6}}\n  - \"1.1.1.1:20001\"\n    {{- else }}\n  - \"[fd00::1]:20001\"\n    {{- end }}\n  - --listen-addr\n    {{- if .Values.networkValidator.listenAddr }}\n  - {{ .Values.networkValidator.listenAddr | quote }}\n    {{- else if .Values.disableIPv6}}\n  - \"0.0.0.0:4140\"\n    {{- else }}\n  - \"[::]:4140\"\n    {{- end }}\n  - --timeout\n  - {{ .Values.networkValidator.timeout }}\n\n{{- end -}}\n"
  },
  {
    "path": "charts/partials/templates/_nodeselector.tpl",
    "content": "{{- define \"linkerd.node-selector\" -}}\nnodeSelector:\n{{- toYaml .Values.nodeSelector | trim | nindent 2 }}\n{{- end -}}\n"
  },
  {
    "path": "charts/partials/templates/_proxy-config-ann.tpl",
    "content": "{{- define \"partials.proxy.config.annotations\" -}}\n{{- with .cpu }}\n{{- with .request -}}\nconfig.linkerd.io/proxy-cpu-request: {{. | quote}}\n{{end}}\n{{- with .limit -}}\nconfig.linkerd.io/proxy-cpu-limit: {{. | quote}}\n{{- end}}\n{{- end}}\n{{- with .memory }}\n{{- with .request }}\nconfig.linkerd.io/proxy-memory-request: {{. | quote}}\n{{end}}\n{{- with .limit -}}\nconfig.linkerd.io/proxy-memory-limit: {{. | quote}}\n{{- end}}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/partials/templates/_proxy-init.tpl",
    "content": "{{- define \"partials.proxy-init\" -}}\nargs:\n{{- if (.Values.proxyInit.iptablesMode | default \"legacy\" | eq \"nft\") }}\n- --firewall-bin-path\n- \"iptables-nft\"\n- --firewall-save-bin-path\n- \"iptables-nft-save\"\n{{- else if not (eq .Values.proxyInit.iptablesMode \"legacy\") }}\n{{ fail (printf \"Unsupported value \\\"%s\\\" for proxyInit.iptablesMode\\nValid values: [\\\"nft\\\", \\\"legacy\\\"]\" .Values.proxyInit.iptablesMode) }}\n{{end -}}\n{{- if .Values.disableIPv6 }}\n- --ipv6=false\n{{- end }}\n- --incoming-proxy-port\n- {{.Values.proxy.ports.inbound | quote}}\n- --outgoing-proxy-port\n- {{.Values.proxy.ports.outbound | quote}}\n- --proxy-uid\n- {{.Values.proxy.uid | quote}}\n{{- if ge (int .Values.proxy.gid) 0 }}\n- --proxy-gid\n- {{.Values.proxy.gid | quote}}\n{{- end }}\n- --inbound-ports-to-ignore\n- \"{{.Values.proxy.ports.control}},{{.Values.proxy.ports.admin}}{{ternary (printf \",%s\" (.Values.proxyInit.ignoreInboundPorts | toString)) \"\" (not (empty .Values.proxyInit.ignoreInboundPorts)) }}\"\n{{- if .Values.proxyInit.ignoreOutboundPorts }}\n- --outbound-ports-to-ignore\n- {{.Values.proxyInit.ignoreOutboundPorts | quote}}\n{{- end }}\n{{- if .Values.proxyInit.closeWaitTimeoutSecs }}\n  {{- if not .Values.proxyInit.runAsRoot }}\n{{ fail \"proxyInit.runAsRoot must be set to use proxyInit.closeWaitTimeoutSecs\" }}\n  {{- end }}\n- --timeout-close-wait-secs\n- {{ .Values.proxyInit.closeWaitTimeoutSecs | quote}}\n{{- end }}\n{{- if .Values.proxyInit.logFormat }}\n- --log-format\n- {{ .Values.proxyInit.logFormat }}\n{{- end }}\n{{- if .Values.proxyInit.logLevel }}\n- --log-level\n- {{ .Values.proxyInit.logLevel }}\n{{- end }}\n{{- if .Values.proxyInit.skipSubnets }}\n- --subnets-to-ignore\n- {{ .Values.proxyInit.skipSubnets | quote }}\n{{- end }}\nimage: {{.Values.proxy.image.name}}:{{.Values.proxy.image.version | default .Values.linkerdVersion}}\ncommand: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\nimagePullPolicy: {{.Values.proxy.image.pullPolicy | default .Values.imagePullPolicy}}\nname: linkerd-init\n{{ include \"partials.resources\" .Values.proxy.resources }}\nsecurityContext:\n  {{- if or .Values.proxyInit.closeWaitTimeoutSecs .Values.proxyInit.privileged }}\n  allowPrivilegeEscalation: true\n  {{- else }}\n  allowPrivilegeEscalation: false\n  {{- end }}\n  capabilities:\n    add:\n    - NET_ADMIN\n    - NET_RAW\n    {{- if .Values.proxyInit.capabilities -}}\n    {{- if .Values.proxyInit.capabilities.add }}\n    {{- toYaml .Values.proxyInit.capabilities.add | trim | nindent 4 }}\n    {{- end }}\n    {{- if .Values.proxyInit.capabilities.drop -}}\n    {{- include \"partials.proxy-init.capabilities.drop\" . | nindent 4 -}}\n    {{- end }}\n    {{- end }}\n  {{- if or .Values.proxyInit.closeWaitTimeoutSecs .Values.proxyInit.privileged }}\n  privileged: true\n  {{- else }}\n  privileged: false\n  {{- end }}\n  {{- if .Values.proxyInit.runAsRoot }}\n  runAsGroup: 0\n  runAsNonRoot: false\n  runAsUser: 0\n  {{- else }}\n  runAsNonRoot: true\n  runAsUser: {{ .Values.proxyInit.runAsUser | int | eq 0 | ternary 65534 .Values.proxyInit.runAsUser }}\n  runAsGroup: {{ .Values.proxyInit.runAsGroup | int | eq 0 | ternary 65534 .Values.proxyInit.runAsGroup }}\n  {{- end }}\n  readOnlyRootFilesystem: true\n  seccompProfile:\n    type: RuntimeDefault\nterminationMessagePolicy: FallbackToLogsOnError\n{{- if or (not .Values.cniEnabled) .Values.proxyInit.saMountPath }}\nvolumeMounts:\n{{- end -}}\n{{- if not .Values.cniEnabled }}\n- mountPath: {{.Values.proxyInit.xtMountPath.mountPath}}\n  name: {{.Values.proxyInit.xtMountPath.name}}\n{{- end -}}\n{{- if .Values.proxyInit.saMountPath }}\n- mountPath: {{.Values.proxyInit.saMountPath.mountPath}}\n  name: {{.Values.proxyInit.saMountPath.name}}\n  readOnly: {{.Values.proxyInit.saMountPath.readOnly}}\n{{- end -}}\n{{- end -}}\n"
  },
  {
    "path": "charts/partials/templates/_proxy.tpl",
    "content": "{{ define \"partials.proxy\" -}}\n{{ if and .Values.proxy.nativeSidecar .Values.proxy.waitBeforeExitSeconds }}\n{{ fail \"proxy.nativeSidecar and waitBeforeExitSeconds cannot be used simultaneously\" }}\n{{- end }}\n{{- if not (has .Values.proxy.logHTTPHeaders (list \"insecure\" \"off\" \"\")) }}\n{{- fail \"logHTTPHeaders must be one of: insecure | off\" }}\n{{- end }}\n{{- $trustDomain := (.Values.identityTrustDomain | default .Values.clusterDomain) -}}\nenv:\n- name: _pod_name\n  valueFrom:\n    fieldRef:\n      fieldPath: metadata.name\n- name: _pod_ns\n  valueFrom:\n    fieldRef:\n      fieldPath: metadata.namespace\n- name: _pod_uid\n  valueFrom:\n    fieldRef:\n      fieldPath: metadata.uid\n- name: _pod_ip\n  valueFrom:\n    fieldRef:\n      fieldPath: status.podIP\n- name: _pod_nodeName\n  valueFrom:\n    fieldRef:\n      fieldPath: spec.nodeName\n- name: _pod_containerName\n  value: &containerName linkerd-proxy\n{{/* Configure CPU cores usage */ -}}\n{{/* 1. Fixed configuration (DEPRECATED) */ -}}\n{{ if .Values.proxy.cores -}}\n- name: LINKERD2_PROXY_CORES\n  value: {{ .Values.proxy.cores | quote }}\n- name: LINKERD2_PROXY_CORES_MIN\n  value: {{ .Values.proxy.cores | quote }}\n{{ else -}}\n{{/* 2. Variable configuration */ -}}\n{{ with (.Values.proxy.runtime).workers -}}\n- name: LINKERD2_PROXY_CORES\n  value: {{ .minimum | default 1 | quote }}\n- name: LINKERD2_PROXY_CORES_MIN\n  value: {{ .minimum | default 1 | quote }}\n{{ if .maximum -}}\n- name: LINKERD2_PROXY_CORES_MAX\n  value: {{ .maximum | quote }}\n{{ end -}}\n{{ if .maximumCPURatio -}}\n- name: LINKERD2_PROXY_CORES_MAX_RATIO\n  value: {{ .maximumCPURatio | quote }}\n{{ end -}}\n{{ end -}}\n{{ end -}}\n{{ if .Values.proxy.requireIdentityOnInboundPorts -}}\n- name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_IDENTITY\n  value: {{.Values.proxy.requireIdentityOnInboundPorts | quote}}\n{{ end -}}\n{{ if .Values.proxy.requireTLSOnInboundPorts -}}\n- name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n  value: {{.Values.proxy.requireTLSOnInboundPorts | quote}}\n{{ end -}}\n- name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n  value: {{.Values.proxy.enableShutdownEndpoint | quote}}\n- name: LINKERD2_PROXY_LOG\n  value: {{ printf \"%s%s\" .Values.proxy.logLevel (.Values.proxy.logHTTPHeaders | eq \"insecure\" | ternary \"\" \",[{headers}]=off,[{request}]=off\") | quote }}\n- name: LINKERD2_PROXY_LOG_FORMAT\n  value: {{.Values.proxy.logFormat | quote}}\n- name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n  value: {{ternary \"localhost.:8086\" (printf \"linkerd-dst-headless.%s.svc.%s.:8086\" .Release.Namespace .Values.clusterDomain) (eq (toString .Values.proxy.component) \"linkerd-destination\")}}\n- name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n  value: {{.Values.clusterNetworks | quote}}\n- name: LINKERD2_PROXY_POLICY_SVC_ADDR\n  value: {{ternary \"localhost.:8090\" (printf \"linkerd-policy.%s.svc.%s.:8090\" .Release.Namespace .Values.clusterDomain) (eq (toString .Values.proxy.component) \"linkerd-destination\")}}\n- name: LINKERD2_PROXY_POLICY_WORKLOAD\n  value: |\n    {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n- name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n  value: {{.Values.proxy.defaultInboundPolicy}}\n- name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n  value: {{.Values.clusterNetworks | quote}}\n- name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n  value: {{((.Values.proxy.control).streams).initialTimeout | default \"\" | quote}}\n- name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n  value: {{((.Values.proxy.control).streams).idleTimeout | default \"\" | quote}}\n- name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n  value: {{((.Values.proxy.control).streams).lifetime | default \"\" | quote}}\n{{ if .Values.proxy.inboundConnectTimeout -}}\n- name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n  value: {{.Values.proxy.inboundConnectTimeout | quote}}\n{{ end -}}\n{{ if .Values.proxy.outboundConnectTimeout -}}\n- name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n  value: {{.Values.proxy.outboundConnectTimeout | quote}}\n{{ end -}}\n{{ if .Values.proxy.outboundDiscoveryCacheUnusedTimeout -}}\n- name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n  value: {{.Values.proxy.outboundDiscoveryCacheUnusedTimeout | quote}}\n{{ end -}}\n{{ if .Values.proxy.inboundDiscoveryCacheUnusedTimeout -}}\n- name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n  value: {{.Values.proxy.inboundDiscoveryCacheUnusedTimeout | quote}}\n{{ end -}}\n{{ if .Values.proxy.disableOutboundProtocolDetectTimeout -}}\n- name: LINKERD2_PROXY_OUTBOUND_DETECT_TIMEOUT\n  value: \"365d\"\n{{ end -}}\n{{ if .Values.proxy.disableInboundProtocolDetectTimeout -}}\n- name: LINKERD2_PROXY_INBOUND_DETECT_TIMEOUT\n  value: \"365d\"\n{{ end -}}\n- name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n  value: \"{{ if .Values.disableIPv6 }}0.0.0.0{{ else }}[::]{{ end }}:{{.Values.proxy.ports.control}}\"\n- name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n  value: \"{{ if .Values.disableIPv6 }}0.0.0.0{{ else }}[::]{{ end }}:{{.Values.proxy.ports.admin}}\"\n{{- /* Deprecated, superseded by LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS since proxy's v2.228.0 (deployed since edge-24.4.5) */}}\n- name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n  value: \"127.0.0.1:{{.Values.proxy.ports.outbound}}\"\n- name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n  value: \"127.0.0.1:{{.Values.proxy.ports.outbound}}{{ if not .Values.disableIPv6}},[::1]:{{.Values.proxy.ports.outbound}}{{ end }}\"\n- name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n  value: \"{{ if .Values.disableIPv6 }}0.0.0.0{{ else }}[::]{{ end }}:{{.Values.proxy.ports.inbound}}\"\n- name: LINKERD2_PROXY_INBOUND_IPS\n  valueFrom:\n    fieldRef:\n      fieldPath: status.podIPs\n- name: LINKERD2_PROXY_INBOUND_PORTS\n  value: {{ .Values.proxy.podInboundPorts | quote }}\n{{ if .Values.proxy.isGateway -}}\n- name: LINKERD2_PROXY_INBOUND_GATEWAY_SUFFIXES\n  value: {{printf \"svc.%s.\" .Values.clusterDomain}}\n{{ end -}}\n{{ if .Values.proxy.isIngress -}}\n- name: LINKERD2_PROXY_INGRESS_MODE\n  value: \"true\"\n{{ end -}}\n- name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n  {{- $internalDomain := printf \"svc.%s.\" .Values.clusterDomain }}\n  value: {{ternary \".\" $internalDomain .Values.proxy.enableExternalProfiles}}\n- name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n  value: 10000ms\n- name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n  value: 10000ms\n- name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n  value: 30s\n- name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n  value: 30s\n- name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n  value: {{ .Values.proxy.metrics.hostnameLabels | quote }}\n{{ if .Values.proxy.tracing | default (dict) | dig \"enabled\" false -}}\n- name: LINKERD2_PROXY_TRACE_ATTRIBUTES_PATH\n  value: /var/run/linkerd/podinfo/labels\n- name: LINKERD2_PROXY_TRACE_ANNOTATIONS_PATH\n  value: /var/run/linkerd/podinfo/annotations\n- name: LINKERD2_PROXY_TRACE_PROTOCOL\n  value: {{ default \"opentelemetry\" .Values.proxy.tracing.protocol }}\n- name: LINKERD2_PROXY_TRACE_SERVICE_NAME\n  value: {{ .Values.proxy.tracing.traceServiceName }}\n{{- if empty .Values.proxy.tracing.collector.endpoint }}\n{{- fail \"proxy.tracing.collector.endpoint must be set if proxy tracing is enabled\" }}\n{{- end }}\n- name: LINKERD2_PROXY_TRACE_COLLECTOR_SVC_ADDR\n  value: {{ .Values.proxy.tracing.collector.endpoint }}\n{{- if empty .Values.proxy.tracing.collector.meshIdentity.serviceAccountName }}\n{{- fail \"proxy.tracing.collector.meshIdentity.serviceAccountName must be set if proxy tracing is enabled\" }}\n{{- end }}\n{{- if empty .Values.proxy.tracing.collector.meshIdentity.namespace }}\n{{- fail \"proxy.tracing.collector.meshIdentity.namespace must be set if proxy tracing is enabled\" }}\n{{- end }}\n- name: LINKERD2_PROXY_TRACE_COLLECTOR_SVC_NAME\n  value: {{ .Values.proxy.tracing.collector.meshIdentity.serviceAccountName }}.{{ .Values.proxy.tracing.collector.meshIdentity.namespace }}.serviceaccount.identity.{{.Release.Namespace}}.{{ .Values.clusterDomain }}\n- name: LINKERD2_PROXY_TRACE_EXTRA_ATTRIBUTES\n  value: |\n    {{- range $k, $v := .Values.proxy.tracing.labels }}\n    {{ $k }}={{ $v }}\n    {{- end }}\n{{ end -}}\n{{/* Configure inbound and outbound parameters, e.g. for HTTP/2 servers. */ -}}\n{{ range $proxyK, $proxyV := (dict \"inbound\" .Values.proxy.inbound \"outbound\" .Values.proxy.outbound) -}}\n{{   range $scopeK, $scopeV := $proxyV -}}\n{{     range $protoK, $protoV := $scopeV -}}\n{{       range $paramK, $paramV := $protoV -}}\n- name: LINKERD2_PROXY_{{snakecase $proxyK | upper}}_{{snakecase $scopeK | upper}}_{{snakecase $protoK | upper}}_{{snakecase $paramK | upper}}\n  value: {{ quote $paramV }}\n{{       end -}}\n{{     end -}}\n{{   end -}}\n{{ end -}}\n{{ if .Values.proxy.opaquePorts -}}\n- name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n  value: {{.Values.proxy.opaquePorts | quote}}\n{{ end -}}\n- name: LINKERD2_PROXY_DESTINATION_CONTEXT\n  value: |\n    {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n- name: _pod_sa\n  valueFrom:\n    fieldRef:\n      fieldPath: spec.serviceAccountName\n- name: _l5d_ns\n  value: {{.Release.Namespace}}\n- name: _l5d_trustdomain\n  value: {{$trustDomain}}\n- name: LINKERD2_PROXY_IDENTITY_DIR\n  value: /var/run/linkerd/identity/end-entity\n- name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n{{- /*\nPods in the `linkerd` namespace are not injected by the proxy injector and instead obtain\nthe trust anchor bundle from the `linkerd-identity-trust-roots` configmap. This should not\nbe used in other contexts.\n*/}}\n{{- if .Values.proxy.loadTrustBundleFromConfigMap }}\n  valueFrom:\n    configMapKeyRef:\n      name: linkerd-identity-trust-roots\n      key: ca-bundle.crt\n{{ else }}\n  value: |\n    {{- required \"Please provide the identity trust anchors\" .Values.identityTrustAnchorsPEM | trim | nindent 4 }}\n{{ end -}}\n- name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n{{- if .Values.identity.serviceAccountTokenProjection }}\n  value: /var/run/secrets/tokens/linkerd-identity-token\n{{ else }}\n  value: /var/run/secrets/kubernetes.io/serviceaccount/token\n{{ end -}}\n- name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n  value: {{ternary \"localhost.:8080\" (printf \"linkerd-identity-headless.%s.svc.%s.:8080\" .Release.Namespace .Values.clusterDomain) (eq (toString .Values.proxy.component) \"linkerd-identity\")}}\n- name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n  value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.{{.Release.Namespace}}.{{$trustDomain}}\n- name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n  value: linkerd-identity.{{.Release.Namespace}}.serviceaccount.identity.{{.Release.Namespace}}.{{$trustDomain}}\n- name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n  value: linkerd-destination.{{.Release.Namespace}}.serviceaccount.identity.{{.Release.Namespace}}.{{$trustDomain}}\n- name: LINKERD2_PROXY_POLICY_SVC_NAME\n  value: linkerd-destination.{{.Release.Namespace}}.serviceaccount.identity.{{.Release.Namespace}}.{{$trustDomain}}\n{{ if .Values.proxy.accessLog -}}\n- name: LINKERD2_PROXY_ACCESS_LOG\n  value: {{.Values.proxy.accessLog | quote}}\n{{ end -}}\n{{ if .Values.proxy.shutdownGracePeriod -}}\n- name: LINKERD2_PROXY_SHUTDOWN_GRACE_PERIOD\n  value: {{.Values.proxy.shutdownGracePeriod | quote}}\n{{ end -}}\n{{ if .Values.proxy.additionalEnv -}}\n{{ toYaml .Values.proxy.additionalEnv }}\n{{ end -}}\n{{ if .Values.proxy.experimentalEnv -}}\n{{ toYaml .Values.proxy.experimentalEnv }}\n{{ end -}}\nimage: {{.Values.proxy.image.name}}:{{.Values.proxy.image.version | default .Values.linkerdVersion}}\nimagePullPolicy: {{.Values.proxy.image.pullPolicy | default .Values.imagePullPolicy}}\nlivenessProbe:\n  httpGet:\n    path: /live\n    port: {{.Values.proxy.ports.admin}}\n  initialDelaySeconds: {{.Values.proxy.livenessProbe.initialDelaySeconds }}\n  timeoutSeconds: {{.Values.proxy.livenessProbe.timeoutSeconds }}\nname: *containerName\nports:\n- containerPort: {{.Values.proxy.ports.inbound}}\n  name: linkerd-proxy\n- containerPort: {{.Values.proxy.ports.admin}}\n  name: linkerd-admin\nreadinessProbe:\n  httpGet:\n    path: /ready\n    port: {{.Values.proxy.ports.admin}}\n  initialDelaySeconds: {{.Values.proxy.readinessProbe.initialDelaySeconds }}\n  timeoutSeconds: {{.Values.proxy.readinessProbe.timeoutSeconds }}\n{{- if and .Values.proxy.nativeSidecar .Values.proxy.await }}\nstartupProbe:\n  httpGet:\n    path: /ready\n    port: {{.Values.proxy.ports.admin}}\n  initialDelaySeconds: {{.Values.proxy.startupProbe.initialDelaySeconds}}\n  periodSeconds: {{.Values.proxy.startupProbe.periodSeconds}}\n  failureThreshold: {{.Values.proxy.startupProbe.failureThreshold}}\n{{- end }}\n{{- if .Values.proxy.resources }}\n{{ include \"partials.resources\" .Values.proxy.resources }}\n{{- end }}\nsecurityContext:\n{{- if .Values.proxy.securityContext }}\n  {{- toYaml .Values.proxy.securityContext | trim | nindent 2 }}\n{{- else }}\n  allowPrivilegeEscalation: false\n  {{- if .Values.proxy.capabilities -}}\n  {{- include \"partials.proxy.capabilities\" . | nindent 2 -}}\n  {{- end }}\n  readOnlyRootFilesystem: true\n  runAsNonRoot: true\n  runAsUser: {{.Values.proxy.uid}}\n{{- if ge (int .Values.proxy.gid) 0 }}\n  runAsGroup: {{.Values.proxy.gid}}\n{{- end }}\n  seccompProfile:\n    type: RuntimeDefault\n{{- end }}\nterminationMessagePolicy: FallbackToLogsOnError\n{{- if and (not .Values.proxy.nativeSidecar) (or .Values.proxy.await .Values.proxy.waitBeforeExitSeconds) }}\nlifecycle:\n{{- if .Values.proxy.await }}\n  postStart:\n    exec:\n      command:\n        - /usr/lib/linkerd/linkerd-await\n        - --timeout=2m\n        - --port={{.Values.proxy.ports.admin}}\n{{- end }}\n{{- if .Values.proxy.waitBeforeExitSeconds }}\n  preStop:\n    exec:\n      command:\n        - /bin/sleep\n        - {{.Values.proxy.waitBeforeExitSeconds | quote}}\n{{- end }}\n{{- end }}\nvolumeMounts:\n- mountPath: /var/run/linkerd/identity/end-entity\n  name: linkerd-identity-end-entity\n{{- if .Values.proxy.tracing | default (dict) | dig \"enabled\" false }}\n- mountPath: /var/run/linkerd/podinfo\n  name: linkerd-podinfo\n{{- end }}\n{{- if .Values.identity.serviceAccountTokenProjection }}\n- mountPath: /var/run/secrets/tokens\n  name: linkerd-identity-token\n{{- end }}\n{{- if .Values.proxy.saMountPath }}\n- mountPath: {{.Values.proxy.saMountPath.mountPath}}\n  name: {{.Values.proxy.saMountPath.name}}\n  readOnly: {{.Values.proxy.saMountPath.readOnly}}\n{{- end -}}\n{{- if .Values.proxy.nativeSidecar }}\nrestartPolicy: Always\n{{- end -}}\n{{- end }}\n"
  },
  {
    "path": "charts/partials/templates/_pull-secrets.tpl",
    "content": "{{- define \"partials.image-pull-secrets\"}}\n{{- if . }}\nimagePullSecrets:\n{{ toYaml . | indent 2 }}\n{{- end }}\n{{- end -}}\n"
  },
  {
    "path": "charts/partials/templates/_resources.tpl",
    "content": "{{- define \"partials.resources\" -}}\n{{- $ephemeralStorage := index . \"ephemeral-storage\" -}}\nresources:\n  {{- if or (.cpu).limit (.memory).limit ($ephemeralStorage).limit }}\n  limits:\n    {{- with (.cpu).limit }}\n    cpu: {{. | quote}}\n    {{- end }}\n    {{- with (.memory).limit }}\n    memory: {{. | quote}}\n    {{- end }}\n    {{- with ($ephemeralStorage).limit }}\n    ephemeral-storage: {{. | quote}}\n    {{- end }}\n  {{- end }}\n  {{- if or (.cpu).request (.memory).request ($ephemeralStorage).request }}\n  requests:\n    {{- with (.cpu).request }}\n    cpu: {{. | quote}}\n    {{- end }}\n    {{- with (.memory).request }}\n    memory: {{. | quote}}\n    {{- end }}\n    {{- with ($ephemeralStorage).request }}\n    ephemeral-storage: {{. | quote}}\n    {{- end }}\n  {{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/partials/templates/_tolerations.tpl",
    "content": "{{- define \"linkerd.tolerations\" -}}\ntolerations:\n{{ toYaml .Values.tolerations | trim | indent 2 }}\n{{- end -}}\n"
  },
  {
    "path": "charts/partials/templates/_trace.tpl",
    "content": "{{ define \"partials.linkerd.trace\" -}}\n{{ if ((.Values.controller.tracing).enabled) -}}\n{{- if (.Values.controller.tracing.collector).endpoint }}\n- -trace-collector={{.Values.controller.tracing.collector.endpoint}}\n{{- else if ((.Values.proxy.tracing).collector).endpoint }}\n- -trace-collector={{.Values.proxy.tracing.collector.endpoint}}\n{{- else }}\n{{- fail \"controller.tracing.collector.endpoint or proxy.tracing.collector.endpoint must be set if control plane tracing is enabled\" }}\n{{- end }}\n{{ end -}}\n{{- end }}\n"
  },
  {
    "path": "charts/partials/templates/_validate.tpl",
    "content": "{{- define \"linkerd.webhook.validation\" -}}\n\n{{- if and (.injectCaFrom) (.injectCaFromSecret) -}}\n{{- fail \"injectCaFrom and injectCaFromSecret cannot both be set\" -}}\n{{- end -}}\n\n{{- if and (or (.injectCaFrom) (.injectCaFromSecret)) (.caBundle) -}}\n{{- fail \"injectCaFrom or injectCaFromSecret cannot be set if providing a caBundle\" -}}\n{{- end -}}\n\n{{- if and (.externalSecret) (empty .caBundle) (empty .injectCaFrom) (empty .injectCaFromSecret) -}}\n{{- fail \"if externalSecret is set, then caBundle, injectCaFrom, or injectCaFromSecret must be set\" -}}\n{{- end }}\n\n{{- if and (or .injectCaFrom .injectCaFromSecret) (not .externalSecret) -}}\n{{- fail \"if injectCaFrom or injectCaFromSecret is set, then externalSecret must be set\" -}}\n{{- end -}}\n\n{{- end -}}\n"
  },
  {
    "path": "charts/partials/templates/_volumes.tpl",
    "content": "{{ define \"partials.proxy.volumes.identity\" -}}\nemptyDir:\n  medium: Memory\nname: linkerd-identity-end-entity\n{{- end -}}\n\n{{- define \"partials.proxy.volumes.podinfo\" -}}\nname: linkerd-podinfo\ndownwardAPI:\n  items:\n    - path: labels\n      fieldRef:\n        fieldPath: metadata.labels\n    - path: annotations\n      fieldRef:\n        fieldPath: metadata.annotations\n{{- end -}}\n\n{{ define \"partials.proxyInit.volumes.xtables\" -}}\nemptyDir: {}\nname: {{ .Values.proxyInit.xtMountPath.name }}\n{{- end -}}\n\n{{- define \"partials.proxy.volumes.service-account-token\" -}}\nname: linkerd-identity-token\nprojected:\n  sources:\n  - serviceAccountToken:\n      path: linkerd-identity-token\n      expirationSeconds: 86400 {{- /* # 24 hours */}}\n      audience: identity.l5d.io\n{{- end -}}\n\n{{- define \"partials.volumes.manual-mount-service-account-token\" -}}\nname: kube-api-access\nprojected:\n  defaultMode: 420\n  sources:\n  - serviceAccountToken:\n      expirationSeconds: 3607\n      path: token\n  - configMap:\n      items:\n      - key: ca.crt\n        path: ca.crt\n      name: kube-root-ca.crt\n  - downwardAPI:\n      items:\n      - fieldRef:\n          apiVersion: v1\n          fieldPath: metadata.namespace\n        path: namespace\n{{- end -}}\n"
  },
  {
    "path": "charts/partials/values.yaml",
    "content": ""
  },
  {
    "path": "charts/patch/Chart.yaml",
    "content": "apiVersion: \"v1\"\nname: \"patch\"\nversion: 0.1.0\ndescription: This chart generates the JSON patch necessary to inject the proxy container into pods\nhome: https://linkerd.io\nsources:\n  - https://github.com/linkerd/linkerd2/\n"
  },
  {
    "path": "charts/patch/requirements.yaml",
    "content": "dependencies:\n- name: partials\n  version: 0.1.0\n  repository: file://../partials\n"
  },
  {
    "path": "charts/patch/templates/patch.json",
    "content": "{{ $prefix := .Values.pathPrefix -}}\n{{/*\n$initIndex represents the patch insertion index of the next initContainer when\nproxy.nativeSidecar is true. If enabled, the proxy-init or network-validator\nshould run first, immediately followed by the proxy. This ordering allows us\nto proxy traffic in subsequent initContainers.\n\nNote: dig is not used directly on .Values because it rejects chartutil.Values\nstructs.\n*/}}\n{{- $initIndex := ternary \"0\" \"-\" (.Values.proxy | default (dict) | dig \"nativeSidecar\" false) -}}\n[\n  {{- if .Values.addRootMetadata }}\n  {\n    \"op\": \"add\",\n    \"path\": \"{{$prefix}}/metadata\",\n    \"value\": {}\n  },\n  {{- end }}\n  {{- if .Values.addRootAnnotations }}\n  {\n    \"op\": \"add\",\n    \"path\": \"{{$prefix}}/metadata/annotations\",\n    \"value\": {}\n  },\n  {{- end }}\n  {{- range $label, $value := .Values.annotations }}\n  {\n    \"op\": \"add\",\n    \"path\": \"{{$prefix}}/metadata/annotations/{{$label | replace \"/\" \"~1\"}}\",\n    \"value\": \"{{$value}}\"\n  },\n  {{- end }}\n  {{- if .Values.addRootLabels }}\n  {\n    \"op\": \"add\",\n    \"path\": \"{{$prefix}}/metadata/labels\",\n    \"value\": {}\n  },\n  {{- end }}\n  {{- range $label, $value := .Values.labels }}\n  {\n    \"op\": \"add\",\n    \"path\": \"{{$prefix}}/metadata/labels/{{$label | replace \"/\" \"~1\"}}\",\n    \"value\": \"{{$value}}\"\n  },\n  {{- end }}\n  {{- if or .Values.proxyInit .Values.proxy }}\n  {{- if .Values.addRootVolumes }}\n  {\n    \"op\": \"add\",\n    \"path\": \"{{$prefix}}/spec/volumes\",\n    \"value\": []\n  },\n  {{- end }}\n  {{- end}}\n  {{- if .Values.addRootInitContainers }}\n  {\n    \"op\": \"add\",\n    \"path\": \"{{$prefix}}/spec/initContainers\",\n    \"value\": []\n  },\n  {{- end}}\n  {{- if and .Values.proxyInit (not .Values.cniEnabled) }}\n  {\n    \"op\": \"add\",\n    \"path\": \"{{$prefix}}/spec/volumes/-\",\n    \"value\": {\n      \"emptyDir\": {},\n      \"name\": \"linkerd-proxy-init-xtables-lock\"\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"{{$prefix}}/spec/initContainers/{{$initIndex}}{{$initIndex = add1 $initIndex}}\",\n    \"value\":\n      {{- include \"partials.proxy-init\" . | fromYaml | toPrettyJson | nindent 6 }}\n  },\n  {{- else if and .Values.proxy .Values.cniEnabled }}\n  {\n    \"op\": \"add\",\n    \"path\": \"{{$prefix}}/spec/initContainers/{{$initIndex}}{{$initIndex = add1 $initIndex}}\",\n    \"value\":\n      {{- include \"partials.network-validator\" . | fromYaml | toPrettyJson | nindent 6 }}\n  },\n  {{- end }}\n  {{- if .Values.debugContainer }}\n  {\n    \"op\": \"add\",\n    \"path\": \"{{$prefix}}/spec/containers/-\",\n    \"value\":\n      {{- include \"partials.debug\" . | fromYaml | toPrettyJson | nindent 6 }}\n  },\n  {{- end }}\n  {{- if .Values.proxy }}\n  {\n    \"op\": \"add\",\n    \"path\": \"{{$prefix}}/spec/volumes/-\",\n    \"value\": {\n      \"name\": \"linkerd-identity-end-entity\",\n      \"emptyDir\": {\n        \"medium\": \"Memory\"\n      }\n    }\n  },\n  {{- if .Values.proxy.tracing | default (dict) | dig \"enabled\" false }}\n  {\n    \"op\": \"add\",\n    \"path\": \"{{$prefix}}/spec/volumes/-\",\n    \"value\": {\n       \"downwardAPI\": {\n         \"items\": [\n            {\n              \"fieldRef\": {\n                \"fieldPath\": \"metadata.labels\"\n              },\n              \"path\": \"labels\"\n            }\n          ]\n       },\n       \"name\": \"linkerd-podinfo\"\n     }\n  },\n  {{- end }}\n  {{- if .Values.identity.serviceAccountTokenProjection}}\n  {\n    \"op\": \"add\",\n    \"path\": \"{{$prefix}}/spec/volumes/-\",\n    \"value\":\n      {{- include \"partials.proxy.volumes.service-account-token\" . | fromYaml | toPrettyJson | nindent 6 }}\n  },\n  {{- end }}\n  {\n    \"op\": \"add\",\n  {{- if .Values.proxy.nativeSidecar }}\n    \"path\": \"{{$prefix}}/spec/initContainers/{{$initIndex}}\",\n  {{- else if .Values.proxy.await }}\n    \"path\": \"{{$prefix}}/spec/containers/0\",\n  {{- else }}\n    \"path\": \"{{$prefix}}/spec/containers/-\",\n  {{- end }}\n    \"value\":\n      {{- include \"partials.proxy\" . | fromYaml | toPrettyJson | nindent 6 }}\n  },\n  {{- end }}\n]\n"
  },
  {
    "path": "charts/templates.go",
    "content": "package charts\n\nimport (\n\t\"embed\"\n)\n\n//go:embed linkerd-control-plane linkerd-crds linkerd2-cni all:partials patch\nvar Templates embed.FS\n"
  },
  {
    "path": "cli/Dockerfile",
    "content": "ARG BUILDPLATFORM=linux/amd64\n\n# Precompile key slow-to-build dependencies\nFROM --platform=$BUILDPLATFORM golang:1.25-alpine AS go-deps\nWORKDIR /linkerd-build\nCOPY go.mod go.sum ./\nRUN go mod download\n\n## compile binaries\nFROM go-deps AS go-gen\nWORKDIR /linkerd-build\nCOPY cli cli\nCOPY charts charts\nCOPY multicluster multicluster\nCOPY viz viz\n\nCOPY controller/k8s controller/k8s\nCOPY controller/api controller/api\nCOPY controller/gen controller/gen\nCOPY pkg pkg\n\nRUN mkdir -p /out\n\nARG LINKERD_VERSION=\"dev-0.0.0\"\nENV CGO_ENABLED=0\nENV GO_LDFLAGS=\"-s -w -X github.com/linkerd/linkerd2/pkg/version.Version=${LINKERD_VERSION}\"\n\nFROM go-gen AS build-linux-amd64\nENV GOOS=linux GOARCH=amd64\nENV BIN=/out/linkerd2-cli-${LINKERD_VERSION}-linux-amd64\nRUN go build -o \"$BIN\" -tags prod -mod=readonly -ldflags \"${GO_LDFLAGS}\" ./cli\n\nFROM go-gen AS build-linux-arm64\nENV GOOS=linux GOARCH=arm64\nENV BIN=/out/linkerd2-cli-${LINKERD_VERSION}-linux-arm64\nRUN go build -o \"$BIN\" -tags prod -mod=readonly -ldflags \"${GO_LDFLAGS}\" ./cli\n\nFROM go-gen AS build-darwin\nENV GOOS=darwin GOARCH=amd64\nENV BIN=/out/linkerd2-cli-${LINKERD_VERSION}-${GOOS}\nRUN go build -o \"$BIN\" -tags prod -mod=readonly -ldflags \"${GO_LDFLAGS}\" ./cli\n\nFROM go-gen AS build-darwin-arm64\nENV GOOS=darwin GOARCH=arm64\nENV BIN=/out/linkerd2-cli-${LINKERD_VERSION}-${GOOS}-${GOARCH}\nRUN go build -o \"$BIN\" -tags prod -mod=readonly -ldflags \"${GO_LDFLAGS}\" ./cli\n\nFROM go-gen AS build-windows\nENV GOOS=windows GOARCH=amd64\nENV BIN=/out/linkerd2-cli-${LINKERD_VERSION}-${GOOS}.exe\nRUN go build -o \"$BIN\" -tags prod -mod=readonly -ldflags \"${GO_LDFLAGS}\" ./cli\n\n# Used in CI\nFROM scratch AS linux-amd64\nCOPY --from=build-linux-amd64 /out/* /\n\nFROM scratch AS multi-arch\nCOPY --from=build-linux-amd64  /out/* /\nCOPY --from=build-linux-arm64  /out/* /\nCOPY --from=build-darwin       /out/* /\nCOPY --from=build-darwin-arm64 /out/* /\nCOPY --from=build-windows      /out/* /\n\n# NB: This image does not contain an `ENTRYPOINT` directive. The filesystem\n# of this image is used as a layer in other images in CI, and the CLI is\n# typically used by operators outside of a cluster.\n#\n# Running `docker build` for this image will likely fail with an error like:\n# \"Error response from daemon: No command specified.\"\n"
  },
  {
    "path": "cli/cmd/authz.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/linkerd/linkerd2/cli/table\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/spf13/cobra\"\n)\n\nfunc newCmdAuthz() *cobra.Command {\n\n\tvar namespace string\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"authz [flags] resource\",\n\t\tShort: \"List authorizations for a resource\",\n\t\tLong:  \"List authorizations for a resource.\",\n\t\tArgs:  cobra.RangeArgs(1, 2),\n\t\tValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tif namespace == \"\" {\n\t\t\t\tnamespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\tcc := k8s.NewCommandCompletion(k8sAPI, namespace)\n\n\t\t\tresults, err := cc.Complete(args, toComplete)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\treturn results, cobra.ShellCompDirectiveDefault\n\t\t},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\n\t\t\tif namespace == \"\" {\n\t\t\t\tnamespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tvar resource string\n\t\t\tif len(args) == 1 {\n\t\t\t\tresource = args[0]\n\t\t\t} else if len(args) == 2 {\n\t\t\t\tresource = args[0] + \"/\" + args[1]\n\t\t\t}\n\n\t\t\trows := make([]table.Row, 0)\n\n\t\t\tauthzs, err := k8s.AuthorizationsForResource(cmd.Context(), k8sAPI, namespace, resource)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"Failed to get serverauthorization resources: %s\\n\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\tfor _, authz := range authzs {\n\t\t\t\tif authz.Route == \"\" {\n\t\t\t\t\tauthz.Route = \"*\"\n\t\t\t\t}\n\t\t\t\trows = append(rows, table.Row{authz.Route, authz.Server, authz.AuthorizationPolicy, authz.ServerAuthorization})\n\t\t\t}\n\n\t\t\tcols := []table.Column{\n\t\t\t\t{Header: \"ROUTE\", Width: 6, Flexible: true, LeftAlign: true},\n\t\t\t\t{Header: \"SERVER\", Width: 6, Flexible: true, LeftAlign: true},\n\t\t\t\t{Header: \"AUTHORIZATION_POLICY\", Width: 21, Flexible: true, LeftAlign: true},\n\t\t\t\t{Header: \"SERVER_AUTHORIZATION\", Width: 21, Flexible: true, LeftAlign: true},\n\t\t\t}\n\n\t\t\ttable := table.NewTable(cols, rows)\n\t\t\ttable.Render(os.Stdout)\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVarP(&namespace, \"namespace\", \"n\", \"\", \"Namespace of resource\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(cmd, []string{\"namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\treturn cmd\n}\n"
  },
  {
    "path": "cli/cmd/check.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\tcharts \"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\tvaluespkg \"helm.sh/helm/v3/pkg/cli/values\"\n\tutilsexec \"k8s.io/utils/exec\"\n)\n\ntype checkOptions struct {\n\tversionOverride    string\n\tpreInstallOnly     bool\n\tcrdsOnly           bool\n\tdataPlaneOnly      bool\n\twait               time.Duration\n\tnamespace          string\n\tcniEnabled         bool\n\toutput             string\n\tcliVersionOverride string\n}\n\nfunc newCheckOptions() *checkOptions {\n\treturn &checkOptions{\n\t\tversionOverride:    \"\",\n\t\tpreInstallOnly:     false,\n\t\tcrdsOnly:           false,\n\t\tdataPlaneOnly:      false,\n\t\twait:               300 * time.Second,\n\t\tnamespace:          \"\",\n\t\tcniEnabled:         false,\n\t\toutput:             tableOutput,\n\t\tcliVersionOverride: \"\",\n\t}\n}\n\n// nonConfigFlagSet specifies flags not allowed with `linkerd check config`\nfunc (options *checkOptions) nonConfigFlagSet() *pflag.FlagSet {\n\tflags := pflag.NewFlagSet(\"non-config-check\", pflag.ExitOnError)\n\n\tflags.BoolVar(&options.cniEnabled, \"linkerd-cni-enabled\", options.cniEnabled, \"When running pre-installation checks (--pre), assume the linkerd-cni plugin is already installed, and a NET_ADMIN check is not needed\")\n\tflags.StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace, \"Namespace to use for --proxy checks (default: all namespaces)\")\n\tflags.BoolVar(&options.preInstallOnly, \"pre\", options.preInstallOnly, \"Only run pre-installation checks, to determine if the control plane can be installed\")\n\tflags.BoolVar(&options.crdsOnly, \"crds\", options.crdsOnly, \"Only run checks which determine if the Linkerd CRDs have been installed\")\n\tflags.BoolVar(&options.dataPlaneOnly, \"proxy\", options.dataPlaneOnly, \"Only run data-plane checks, to determine if the data plane is healthy\")\n\n\treturn flags\n}\n\n// checkFlagSet specifies flags allowed with and without `config`\nfunc (options *checkOptions) checkFlagSet() *pflag.FlagSet {\n\tflags := pflag.NewFlagSet(\"check\", pflag.ExitOnError)\n\n\tflags.StringVar(&options.versionOverride, \"expected-version\", options.versionOverride, \"Overrides the version used when checking if Linkerd is running the latest version (mostly for testing)\")\n\tflags.StringVar(&options.cliVersionOverride, \"cli-version-override\", \"\", \"Used to override the version of the cli (mostly for testing)\")\n\tflags.StringVarP(&options.output, \"output\", \"o\", options.output, \"Output format. One of: table, json, short\")\n\tflags.DurationVar(&options.wait, \"wait\", options.wait, \"Maximum allowed time for all tests to pass\")\n\n\treturn flags\n}\n\nfunc (options *checkOptions) validate() error {\n\tif options.preInstallOnly && options.dataPlaneOnly {\n\t\treturn errors.New(\"--pre and --proxy flags are mutually exclusive\")\n\t}\n\tif options.preInstallOnly && options.crdsOnly {\n\t\treturn errors.New(\"--pre and --crds flags are mutually exclusive\")\n\t}\n\tif !options.preInstallOnly && options.cniEnabled {\n\t\treturn errors.New(\"--linkerd-cni-enabled can only be used with --pre\")\n\t}\n\tif options.output != tableOutput && options.output != jsonOutput && options.output != shortOutput {\n\t\treturn fmt.Errorf(\"Invalid output type '%s'. Supported output types are: %s, %s, %s\", options.output, jsonOutput, tableOutput, shortOutput)\n\t}\n\treturn nil\n}\n\nfunc newCmdCheck() *cobra.Command {\n\toptions := newCheckOptions()\n\tcheckFlags := options.checkFlagSet()\n\tnonConfigFlags := options.nonConfigFlagSet()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"check [flags]\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Check the Linkerd installation for potential problems\",\n\t\tLong: `Check the Linkerd installation for potential problems.\n\nThe check command will perform a series of checks to validate that the linkerd\nCLI and control plane are configured correctly. If the command encounters a\nfailure it will print additional information about the failure and exit with a\nnon-zero exit code.`,\n\t\tExample: `  # Check that the Linkerd control plane is up and running\n  linkerd check\n\n  # Check that the Linkerd control plane can be installed in the \"test\" namespace\n  linkerd check --pre --linkerd-namespace test\n\n  # Check that the Linkerd data plane proxies in the \"app\" namespace are up and running\n  linkerd check --proxy --namespace app`,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn configureAndRunChecks(cmd, stdout, stderr, options)\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().AddFlagSet(checkFlags)\n\tcmd.Flags().AddFlagSet(nonConfigFlags)\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(cmd, []string{\"namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\n\treturn cmd\n}\n\nfunc configureAndRunChecks(cmd *cobra.Command, wout io.Writer, werr io.Writer, options *checkOptions) error {\n\terr := options.validate()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Validation error when executing check command: %w\", err)\n\t}\n\n\tif options.cliVersionOverride != \"\" {\n\t\tversion.Version = options.cliVersionOverride\n\t}\n\n\tchecks := []healthcheck.CategoryID{\n\t\thealthcheck.KubernetesAPIChecks,\n\t\thealthcheck.KubernetesVersionChecks,\n\t\thealthcheck.LinkerdVersionChecks,\n\t}\n\n\tcrdManifest := bytes.Buffer{}\n\terr = renderCRDs(cmd.Context(), nil, &crdManifest, valuespkg.Options{\n\t\t// GatewayAPI CRDs are optional so don't check for them.\n\t\tValues: []string{\n\t\t\t\"installGatewayAPI=false\",\n\t\t},\n\t}, \"yaml\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar installManifest string\n\tvar values *charts.Values\n\tif options.preInstallOnly {\n\t\tchecks = append(checks, healthcheck.LinkerdPreInstallChecks)\n\t\tif options.cniEnabled {\n\t\t\tchecks = append(checks, healthcheck.LinkerdCNIPluginChecks)\n\t\t}\n\t\tvalues, installManifest, err = renderInstallManifest(cmd.Context())\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"Error rendering install manifest: %s\\n\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t} else if options.crdsOnly {\n\t\tchecks = append(checks, healthcheck.LinkerdCRDChecks)\n\t} else {\n\t\tchecks = append(checks, healthcheck.LinkerdConfigChecks)\n\n\t\tchecks = append(checks, healthcheck.LinkerdControlPlaneExistenceChecks)\n\t\tchecks = append(checks, healthcheck.LinkerdIdentity)\n\t\tchecks = append(checks, healthcheck.LinkerdWebhooksAndAPISvcTLS)\n\t\tchecks = append(checks, healthcheck.LinkerdControlPlaneProxyChecks)\n\n\t\tif options.dataPlaneOnly {\n\t\t\tchecks = append(checks, healthcheck.LinkerdDataPlaneChecks)\n\t\t\tchecks = append(checks, healthcheck.LinkerdIdentityDataPlane)\n\t\t\tchecks = append(checks, healthcheck.LinkerdOpaquePortsDefinitionChecks)\n\t\t} else {\n\t\t\tchecks = append(checks, healthcheck.LinkerdControlPlaneVersionChecks)\n\t\t\tchecks = append(checks, healthcheck.LinkerdExtensionChecks)\n\t\t}\n\t\tchecks = append(checks, healthcheck.LinkerdCNIPluginChecks)\n\t\tchecks = append(checks, healthcheck.LinkerdHAChecks)\n\t}\n\n\thc := healthcheck.NewHealthChecker(checks, &healthcheck.Options{\n\t\tIsMainCheckCommand:    true,\n\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\tCNINamespace:          cniNamespace,\n\t\tDataPlaneNamespace:    options.namespace,\n\t\tKubeConfig:            kubeconfigPath,\n\t\tKubeContext:           kubeContext,\n\t\tImpersonate:           impersonate,\n\t\tImpersonateGroup:      impersonateGroup,\n\t\tAPIAddr:               apiAddr,\n\t\tVersionOverride:       options.versionOverride,\n\t\tRetryDeadline:         time.Now().Add(options.wait),\n\t\tCNIEnabled:            options.cniEnabled,\n\t\tInstallManifest:       installManifest,\n\t\tCRDManifest:           crdManifest.String(),\n\t\tChartValues:           values,\n\t})\n\n\tsuccess, warning := healthcheck.RunChecks(wout, werr, hc, options.output)\n\n\tif !options.preInstallOnly && !options.crdsOnly {\n\t\textensionSuccess, extensionWarning, err := runExtensionChecks(cmd, wout, werr, options)\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(werr, \"Failed to run extensions checks: %s\\n\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tsuccess = success && extensionSuccess\n\t\twarning = warning || extensionWarning\n\t}\n\n\thealthcheck.PrintChecksResult(wout, options.output, success, warning)\n\n\tif !success {\n\t\tos.Exit(1)\n\t}\n\n\treturn nil\n}\n\nfunc runExtensionChecks(cmd *cobra.Command, wout io.Writer, werr io.Writer, opts *checkOptions) (bool, bool, error) {\n\tkubeAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\tif err != nil {\n\t\treturn false, false, err\n\t}\n\n\tnamespaces, err := kubeAPI.GetAllNamespacesWithExtensionLabel(cmd.Context())\n\tif err != nil {\n\t\treturn false, false, err\n\t}\n\tnsLabels := []string{}\n\tfor _, ns := range namespaces {\n\t\text := ns.Labels[k8s.LinkerdExtensionLabel]\n\t\tnsLabels = append(nsLabels, ext)\n\t}\n\n\texec := utilsexec.New()\n\n\textensions, missing := findExtensions(os.Getenv(\"PATH\"), filepath.Glob, exec, nsLabels)\n\n\t// no extensions to check\n\tif len(extensions) == 0 && len(missing) == 0 {\n\t\treturn true, false, nil\n\t}\n\n\textensionSuccess, extensionWarning := runExtensionsChecks(\n\t\twout, werr, extensions, missing, exec, getExtensionCheckFlags(cmd.Flags()), opts.output,\n\t)\n\treturn extensionSuccess, extensionWarning, nil\n}\n\nfunc getExtensionCheckFlags(lf *pflag.FlagSet) []string {\n\textensionFlags := []string{\n\t\t\"api-addr\", \"context\", \"as\", \"as-group\", \"kubeconfig\", \"linkerd-namespace\", \"verbose\",\n\t\t\"namespace\", \"proxy\", \"wait\",\n\t}\n\tcmdLineFlags := []string{}\n\tfor _, flag := range extensionFlags {\n\t\tf := lf.Lookup(flag)\n\t\tif f != nil {\n\t\t\tval := f.Value.String()\n\t\t\tif val != \"\" {\n\t\t\t\tcmdLineFlags = append(cmdLineFlags, fmt.Sprintf(\"--%s=%s\", f.Name, val))\n\t\t\t}\n\t\t}\n\t}\n\tcmdLineFlags = append(cmdLineFlags, \"--output=json\")\n\treturn cmdLineFlags\n}\n\nfunc renderInstallManifest(ctx context.Context) (*charts.Values, string, error) {\n\t// Create the default values.\n\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 30*time.Second)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\tvalues, err := charts.NewValues()\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\terr = initializeIssuerCredentials(ctx, k8sAPI, values)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\n\t// Use empty valuesOverrides because there are no option values to merge.\n\tvar b strings.Builder\n\terr = renderControlPlane(&b, values, map[string]interface{}{}, \"yaml\")\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\treturn values, b.String(), nil\n}\n"
  },
  {
    "path": "cli/cmd/check_extensions.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/briandowns/spinner\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\t\"github.com/mattn/go-isatty\"\n\tutilsexec \"k8s.io/utils/exec\"\n)\n\n// glob is satisfied by filepath.Glob.\ntype glob func(string) ([]string, error)\n\n// extension contains the full path of an extension executable. If it's a\n// a built-in extension, path will be the `linkerd` executable and builtin will\n// be the extension name (multicluster, or viz).\ntype extension struct {\n\tpath    string\n\tbuiltin string\n}\n\nvar (\n\tbuiltInChecks = map[string]struct{}{\n\t\t\"multicluster\": {},\n\t\t\"viz\":          {},\n\t}\n)\n\n// findExtensions searches the path for all linkerd-* executables and returns a\n// slice of check commands, and a slice of missing checks.\nfunc findExtensions(pathEnv string, glob glob, exec utilsexec.Interface, nsLabels []string) ([]extension, []string) {\n\tcliExtensions := findCLIExtensionsOnPath(pathEnv, glob, exec)\n\n\t// first, collect extensions that are \"always\" enabled\n\textensions := findAlwaysChecks(cliExtensions, exec)\n\n\talwaysSuffixSet := map[string]struct{}{}\n\tfor _, e := range extensions {\n\t\talwaysSuffixSet[suffix(e.path)] = struct{}{}\n\t}\n\n\t// nsLabelSet is the set of extension names which are installed on the cluster\n\t// but are not \"always\" checks\n\tnsLabelSet := map[string]struct{}{}\n\tfor _, label := range nsLabels {\n\t\tif _, ok := alwaysSuffixSet[label]; !ok {\n\t\t\tnsLabelSet[label] = struct{}{}\n\t\t}\n\t}\n\n\t// second, collect on-cluster extensions\n\tfor _, e := range cliExtensions {\n\t\tsuffix := suffix(e)\n\t\tif _, ok := nsLabelSet[suffix]; ok {\n\t\t\textensions = append(extensions, extension{path: e})\n\t\t\tdelete(nsLabelSet, suffix)\n\t\t}\n\t}\n\n\t// third, collect built-in extensions\n\tfor label := range nsLabelSet {\n\t\tif _, ok := builtInChecks[label]; ok {\n\t\t\textensions = append(extensions, extension{path: os.Args[0], builtin: label})\n\t\t\tdelete(nsLabelSet, label)\n\t\t}\n\t}\n\n\t// anything left in nsLabelSet is a missing executable\n\tmissing := []string{}\n\tfor label := range nsLabelSet {\n\t\tmissing = append(missing, fmt.Sprintf(\"linkerd-%s\", label))\n\t}\n\n\tsort.Slice(extensions, func(i, j int) bool {\n\t\tif extensions[i].path != extensions[j].path {\n\t\t\t_, filename1 := filepath.Split(extensions[i].path)\n\t\t\t_, filename2 := filepath.Split(extensions[j].path)\n\t\t\treturn filename1 < filename2\n\t\t}\n\t\treturn extensions[i].builtin < extensions[j].builtin\n\t})\n\tsort.Strings(missing)\n\n\treturn extensions, missing\n}\n\n// findCLIExtensionsOnPath searches the path for all linkerd-* executables and\n// returns a slice of unique filepaths. if multiple executables have the same\n// name, only the one which comes earliest in the pathEnv is returned.\nfunc findCLIExtensionsOnPath(pathEnv string, glob glob, exec utilsexec.Interface) []string {\n\texecutables := []string{}\n\tseen := map[string]struct{}{}\n\n\tfor _, dir := range filepath.SplitList(pathEnv) {\n\t\tmatches, err := glob(filepath.Join(dir, \"linkerd-*\"))\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tsort.Strings(matches)\n\n\t\tfor _, match := range matches {\n\t\t\tsuffix := suffix(match)\n\t\t\tif _, ok := seen[suffix]; ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpath, err := exec.LookPath(match)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\texecutables = append(executables, path)\n\t\t\tseen[suffix] = struct{}{}\n\t\t}\n\t}\n\n\treturn executables\n}\n\n// findAlwaysChecks filters a slice of linkerd-* executables to only those that\n// support the \"_extension-metadata\" subcommand, and announce themselves to\n// \"always\" run.\nfunc findAlwaysChecks(cliExtensions []string, exec utilsexec.Interface) []extension {\n\textensions := []extension{}\n\n\tfor _, e := range cliExtensions {\n\t\tif isAlwaysCheck(e, exec) {\n\t\t\textensions = append(extensions, extension{path: e})\n\t\t}\n\t}\n\n\treturn extensions\n}\n\n// isAlwaysCheck executes a command with an \"_extension-metadata\" subcommand,\n// and returns true if the output is a valid ExtensionMetadataOutput struct.\nfunc isAlwaysCheck(path string, exec utilsexec.Interface) bool {\n\tcmd := exec.Command(path, healthcheck.ExtensionMetadataSubcommand)\n\tvar stdout, stderr bytes.Buffer\n\tcmd.SetStdout(&stdout)\n\tcmd.SetStderr(&stderr)\n\terr := cmd.Run()\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tmetadataOutput, err := parseJSONMetadataOutput(stdout.Bytes())\n\tif err != nil {\n\t\treturn false\n\t}\n\n\t// output of _extension-metadata must match the executable name, and specific\n\t// \"always\"\n\t// i.e. linkerd-foo is allowed, linkerd-foo-v0.XX.X is not\n\t_, filename := filepath.Split(path)\n\treturn strings.EqualFold(metadataOutput.Name, filename) && metadataOutput.Checks == healthcheck.Always\n}\n\n// parseJSONMetadataOutput parses the output of an _extension-metadata\n// subcommand. The data is expected to be a ExtensionMetadataOutput struct\n// serialized to json.\nfunc parseJSONMetadataOutput(data []byte) (healthcheck.ExtensionMetadataOutput, error) {\n\tvar metadata healthcheck.ExtensionMetadataOutput\n\terr := json.Unmarshal(data, &metadata)\n\tif err != nil {\n\t\treturn healthcheck.ExtensionMetadataOutput{}, err\n\t}\n\treturn metadata, nil\n}\n\n// runExtensionsChecks runs checks for each extension name passed into the\n// `extensions` parameter and handles formatting the output for each extension's\n// check. This function also prints check warnings for missing extensions.\nfunc runExtensionsChecks(\n\twout io.Writer, werr io.Writer, extensions []extension, missing []string, utilsexec utilsexec.Interface, flags []string, output string,\n) (bool, bool) {\n\tspin := spinner.New(spinner.CharSets[9], 100*time.Millisecond)\n\tspin.Writer = wout\n\n\tsuccess := true\n\twarning := false\n\tfor _, extension := range extensions {\n\t\targs := append([]string{\"check\"}, flags...)\n\t\tif extension.builtin != \"\" {\n\t\t\targs = append([]string{extension.builtin}, args...)\n\t\t}\n\n\t\tif isatty.IsTerminal(os.Stdout.Fd()) {\n\t\t\tname := suffix(extension.path)\n\t\t\tif extension.builtin != \"\" {\n\t\t\t\tname = extension.builtin\n\t\t\t}\n\n\t\t\tspin.Suffix = fmt.Sprintf(\" Running %s extension check\", name)\n\t\t\tspin.Color(\"bold\") // this calls spin.Restart()\n\t\t}\n\n\t\tplugin := utilsexec.Command(extension.path, args...)\n\t\tvar stdout, stderr bytes.Buffer\n\t\tplugin.SetStdout(&stdout)\n\t\tplugin.SetStderr(&stderr)\n\t\tplugin.Run()\n\t\tresults, err := parseJSONCheckOutput(stdout.Bytes())\n\t\tspin.Stop()\n\t\tif err != nil {\n\t\t\tsuccess = false\n\n\t\t\tcommand := fmt.Sprintf(\"%s %s\", extension.path, strings.Join(args, \" \"))\n\t\t\tif len(stderr.String()) > 0 {\n\t\t\t\terr = errors.New(stderr.String())\n\t\t\t} else {\n\t\t\t\terr = fmt.Errorf(\"invalid extension check output from \\\"%s\\\" (JSON object expected):\\n%s\\n[%w]\", command, stdout.String(), err)\n\t\t\t}\n\t\t\t_, filename := filepath.Split(extension.path)\n\t\t\tresults = healthcheck.CheckResults{\n\t\t\t\tResults: []healthcheck.CheckResult{\n\t\t\t\t\t{\n\t\t\t\t\t\tCategory:    healthcheck.CategoryID(filename),\n\t\t\t\t\t\tDescription: fmt.Sprintf(\"Running: %s\", command),\n\t\t\t\t\t\tErr:         err,\n\t\t\t\t\t\tHintURL:     healthcheck.HintBaseURL(version.Version) + \"extensions\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\textensionSuccess, extensionWarning := healthcheck.RunChecks(wout, werr, results, output)\n\t\tif !extensionSuccess {\n\t\t\tsuccess = false\n\t\t}\n\t\tif extensionWarning {\n\t\t\twarning = true\n\t\t}\n\t}\n\n\tfor _, m := range missing {\n\t\tresults := healthcheck.CheckResults{\n\t\t\tResults: []healthcheck.CheckResult{\n\t\t\t\t{\n\t\t\t\t\tCategory:    healthcheck.CategoryID(m),\n\t\t\t\t\tDescription: fmt.Sprintf(\"Linkerd extension command %s exists\", m),\n\t\t\t\t\tErr:         &exec.Error{Name: m, Err: exec.ErrNotFound},\n\t\t\t\t\tHintURL:     healthcheck.HintBaseURL(version.Version) + \"extensions\",\n\t\t\t\t\tWarning:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\textensionSuccess, extensionWarning := healthcheck.RunChecks(wout, werr, results, output)\n\t\tif !extensionSuccess {\n\t\t\tsuccess = false\n\t\t}\n\t\tif extensionWarning {\n\t\t\twarning = true\n\t\t}\n\t}\n\n\treturn success, warning\n}\n\n// parseJSONCheckOutput parses the output of a check command run with json\n// output mode. The data is expected to be a CheckOutput struct serialized\n// to json. In addition to deserializing, this function will convert the result\n// to a CheckResults struct.\nfunc parseJSONCheckOutput(data []byte) (healthcheck.CheckResults, error) {\n\tvar checks healthcheck.CheckOutput\n\terr := json.Unmarshal(data, &checks)\n\tif err != nil {\n\t\treturn healthcheck.CheckResults{}, err\n\t}\n\tresults := []healthcheck.CheckResult{}\n\tfor _, category := range checks.Categories {\n\t\tfor _, check := range category.Checks {\n\t\t\tvar err error\n\t\t\tif check.Error != \"\" {\n\t\t\t\terr = errors.New(check.Error)\n\t\t\t}\n\t\t\tresults = append(results, healthcheck.CheckResult{\n\t\t\t\tCategory:    category.Name,\n\t\t\t\tDescription: check.Description,\n\t\t\t\tErr:         err,\n\t\t\t\tHintURL:     check.Hint,\n\t\t\t\tWarning:     check.Result == healthcheck.CheckWarn,\n\t\t\t})\n\t\t}\n\t}\n\treturn healthcheck.CheckResults{Results: results}, nil\n}\n\n// suffix returns the last part of a CLI check name, e.g.:\n// linkerd-foo                => foo\n// linkerd-foo-bar            => foo-bar\n// /usr/local/bin/linkerd-foo => foo\n// s is assumed to be a filepath where the filename begins with \"linkerd-\"\nfunc suffix(s string) string {\n\t_, filename := filepath.Split(s)\n\tsuffix := strings.TrimPrefix(filename, \"linkerd-\")\n\tif suffix == filename {\n\t\t// we should never get here\n\t\treturn \"\"\n\t}\n\treturn suffix\n}\n"
  },
  {
    "path": "cli/cmd/check_extensions_test.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"k8s.io/utils/exec\"\n\tfakeexec \"k8s.io/utils/exec/testing\"\n)\n\nfunc TestFindExtensions(t *testing.T) {\n\tfakeGlob := func(path string) ([]string, error) {\n\t\tdir, _ := filepath.Split(path)\n\t\treturn []string{\n\t\t\tfilepath.Join(dir, \"linkerd-bar\"),\n\t\t\tfilepath.Join(dir, \"linkerd-baz\"),\n\t\t\tfilepath.Join(dir, \"linkerd-foo\"),\n\t\t}, nil\n\t}\n\n\tfcmd := fakeexec.FakeCmd{\n\t\tRunScript: []fakeexec.FakeAction{\n\t\t\tfunc() ([]byte, []byte, error) {\n\t\t\t\treturn []byte(`{\"name\":\"linkerd-baz\",\"checks\":\"always\"}`), nil, nil\n\t\t\t},\n\t\t\tfunc() ([]byte, []byte, error) {\n\t\t\t\treturn []byte(`{\"name\":\"linkerd-foo-no-match\",\"checks\":\"always\"}`), nil, nil\n\t\t\t},\n\t\t\tfunc() ([]byte, []byte, error) { return []byte(`{\"name\":\"linkerd-bar\",\"checks\":\"always\"}`), nil, nil },\n\t\t},\n\t}\n\n\tlookPathSuccess := false\n\n\tfexec := &fakeexec.FakeExec{\n\t\tCommandScript: []fakeexec.FakeCommandAction{\n\t\t\tfunc(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },\n\t\t\tfunc(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },\n\t\t\tfunc(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },\n\t\t},\n\t\tLookPathFunc: func(cmd string) (string, error) {\n\t\t\tif lookPathSuccess {\n\t\t\t\treturn cmd, nil\n\t\t\t}\n\t\t\tlookPathSuccess = true\n\t\t\treturn \"\", errors.New(\"fake-error\")\n\t\t},\n\t}\n\n\textensions, missing := findExtensions(\"/path1:/this/is/a/fake/path2\", fakeGlob, fexec, []string{\"foo\", \"missing-cli\"})\n\n\texpExtensions := []extension{\n\t\t{path: \"/this/is/a/fake/path2/linkerd-bar\"},\n\t\t{path: \"/path1/linkerd-baz\"},\n\t\t{path: \"/path1/linkerd-foo\"},\n\t}\n\texpMissing := []string{\"linkerd-missing-cli\"}\n\n\tif !reflect.DeepEqual(expExtensions, extensions) {\n\t\tt.Errorf(\"Expected [%+v] Got [%+v]\", expExtensions, extensions)\n\t}\n\tif !reflect.DeepEqual(expMissing, missing) {\n\t\tt.Errorf(\"Expected [%+v] Got [%+v]\", expMissing, missing)\n\t}\n}\n\nfunc TestRunExtensionsChecks(t *testing.T) {\n\tsuccessJSON := `\n\t{\n\t\t\"success\": true,\n\t\t\"categories\": [\n\t\t\t{\n\t\t\t\t\"categoryName\": \"success check name\",\n\t\t\t\t\"checks\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"description\": \"success check desc\",\n\t\t\t\t\t\t\"result\": \"success\"\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t]\n\t}\n\t`\n\n\twarningJSON := `\n\t{\n\t\t\"success\": true,\n\t\t\"categories\": [\n\t\t\t{\n\t\t\t\t\"categoryName\": \"warning check name\",\n\t\t\t\t\"checks\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"description\": \"warning check desc\",\n\t\t\t\t\t\t\"hint\": \"https://example.com/warning\",\n\t\t\t\t\t\t\"error\": \"this is the warning message\",\n\t\t\t\t\t\t\"result\": \"warning\"\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t]\n\t}\n\t`\n\n\terrorJSON := `\n\t{\n\t\t\"success\": false,\n\t\t\"categories\": [\n\t\t\t{\n\t\t\t\t\"categoryName\": \"error check name\",\n\t\t\t\t\"checks\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"description\": \"error check desc\",\n\t\t\t\t\t\t\"hint\": \"https://example.com/error\",\n\t\t\t\t\t\t\"error\": \"this is the error message\",\n\t\t\t\t\t\t\"result\": \"error\"\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t]\n\t}\n\t`\n\n\tmultiJSON := `\n\t{\n\t\t\"success\": true,\n\t\t\"categories\": [\n\t\t\t{\n\t\t\t\t\"categoryName\": \"multi check name\",\n\t\t\t\t\"checks\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"description\": \"multi check desc success\",\n\t\t\t\t\t\t\"result\": \"success\"\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"description\": \"multi check desc warning\",\n\t\t\t\t\t\t\"hint\": \"https://example.com/multi\",\n\t\t\t\t\t\t\"error\": \"this is the multi warning message\",\n\t\t\t\t\t\t\"result\": \"warning\"\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t]\n\t}\n\t`\n\n\ttestCases := []struct {\n\t\tname        string\n\t\textensions  []extension\n\t\tmissing     []string\n\t\tfakeActions []fakeexec.FakeAction\n\t\texpSuccess  bool\n\t\texpWarning  bool\n\t\texpOutput   string\n\t}{\n\t\t{\n\t\t\t\"no checks\",\n\t\t\tnil,\n\t\t\tnil,\n\t\t\t[]fakeexec.FakeAction{\n\t\t\t\tfunc() ([]byte, []byte, error) {\n\t\t\t\t\treturn nil, nil, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\ttrue,\n\t\t\tfalse,\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"invalid JSON\",\n\t\t\t[]extension{{path: \"/path/linkerd-invalid\"}},\n\t\t\tnil,\n\t\t\t[]fakeexec.FakeAction{\n\t\t\t\tfunc() ([]byte, []byte, error) {\n\t\t\t\t\treturn []byte(\"bad json\"), nil, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t\tfalse,\n\t\t\t`linkerd-invalid\n---------------\n× Running: /path/linkerd-invalid check\n    invalid extension check output from \"/path/linkerd-invalid check\" (JSON object expected):\nbad json\n[invalid character 'b' looking for beginning of value]\n    see https://linkerd.io/2/checks/#extensions for hints\n\n`,\n\t\t},\n\t\t{\n\t\t\t\"one successful check\",\n\t\t\t[]extension{{path: \"/path/linkerd-success\"}},\n\t\t\tnil,\n\t\t\t[]fakeexec.FakeAction{\n\t\t\t\tfunc() ([]byte, []byte, error) {\n\t\t\t\t\treturn []byte(successJSON), nil, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\ttrue,\n\t\t\tfalse,\n\t\t\t`success check name\n------------------\n√ success check desc\n\n`,\n\t\t},\n\t\t{\n\t\t\t\"one warning check\",\n\t\t\t[]extension{{path: \"/path/linkerd-warning\"}},\n\t\t\tnil,\n\t\t\t[]fakeexec.FakeAction{\n\t\t\t\tfunc() ([]byte, []byte, error) {\n\t\t\t\t\treturn []byte(warningJSON), nil, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\ttrue,\n\t\t\ttrue,\n\t\t\t`warning check name\n------------------\n‼ warning check desc\n    this is the warning message\n    see https://example.com/warning for hints\n\n`,\n\t\t},\n\t\t{\n\t\t\t\"one error check\",\n\t\t\t[]extension{{path: \"/path/linkerd-error\"}},\n\t\t\tnil,\n\t\t\t[]fakeexec.FakeAction{\n\t\t\t\tfunc() ([]byte, []byte, error) {\n\t\t\t\t\treturn []byte(errorJSON), nil, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t\tfalse,\n\t\t\t`error check name\n----------------\n× error check desc\n    this is the error message\n    see https://example.com/error for hints\n\n`,\n\t\t},\n\t\t{\n\t\t\t\"one missing check\",\n\t\t\tnil,\n\t\t\t[]string{\"missing\"},\n\t\t\tnil,\n\t\t\ttrue,\n\t\t\ttrue,\n\t\t\t`missing\n-------\n‼ Linkerd extension command missing exists\n    exec: \"missing\": executable file not found in $PATH\n    see https://linkerd.io/2/checks/#extensions for hints\n\n`,\n\t\t},\n\t\t{\n\t\t\t\"multiple checks with success, warnings, errors, and missing\",\n\t\t\t[]extension{{path: \"/path/linkerd-success\"}, {path: \"/path/linkerd-warning\"}, {path: \"/path/linkerd-error\"}, {path: \"/path/linkerd-multi\"}},\n\t\t\t[]string{\"missing1\", \"missing2\"},\n\t\t\t[]fakeexec.FakeAction{\n\t\t\t\tfunc() ([]byte, []byte, error) {\n\t\t\t\t\treturn []byte(successJSON), nil, nil\n\t\t\t\t},\n\t\t\t\tfunc() ([]byte, []byte, error) {\n\t\t\t\t\treturn []byte(warningJSON), nil, nil\n\t\t\t\t},\n\t\t\t\tfunc() ([]byte, []byte, error) {\n\t\t\t\t\treturn []byte(errorJSON), nil, nil\n\t\t\t\t},\n\t\t\t\tfunc() ([]byte, []byte, error) {\n\t\t\t\t\treturn []byte(multiJSON), nil, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t\ttrue,\n\t\t\t`success check name\n------------------\n√ success check desc\n\nwarning check name\n------------------\n‼ warning check desc\n    this is the warning message\n    see https://example.com/warning for hints\n\nerror check name\n----------------\n× error check desc\n    this is the error message\n    see https://example.com/error for hints\n\nmulti check name\n----------------\n√ multi check desc success\n‼ multi check desc warning\n    this is the multi warning message\n    see https://example.com/multi for hints\n\nmissing1\n--------\n‼ Linkerd extension command missing1 exists\n    exec: \"missing1\": executable file not found in $PATH\n    see https://linkerd.io/2/checks/#extensions for hints\n\nmissing2\n--------\n‼ Linkerd extension command missing2 exists\n    exec: \"missing2\": executable file not found in $PATH\n    see https://linkerd.io/2/checks/#extensions for hints\n\n`,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tfcmd := fakeexec.FakeCmd{\n\t\t\t\tRunScript: tc.fakeActions,\n\t\t\t}\n\n\t\t\tfakeCommandActions := make([]fakeexec.FakeCommandAction, len(tc.fakeActions))\n\t\t\tfor i := 0; i < len(tc.fakeActions); i++ {\n\t\t\t\tfakeCommandActions[i] = func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) }\n\t\t\t}\n\t\t\tfexec := &fakeexec.FakeExec{\n\t\t\t\tCommandScript: fakeCommandActions,\n\t\t\t}\n\n\t\t\tvar stdout, stderr bytes.Buffer\n\t\t\tsuccess, warning := runExtensionsChecks(&stdout, &stderr, tc.extensions, tc.missing, fexec, nil, \"\")\n\t\t\tif tc.expSuccess != success {\n\t\t\t\tt.Errorf(\"Expected success to be %t, got %t\", tc.expSuccess, success)\n\t\t\t}\n\t\t\tif tc.expWarning != warning {\n\t\t\t\tt.Errorf(\"Expected warning to be %t, got %t\", tc.expWarning, warning)\n\t\t\t}\n\t\t\toutput := stdout.String()\n\t\t\tif tc.expOutput != output {\n\t\t\t\tt.Errorf(\"Expected output to be:\\n%s\\nGot:\\n%s\", tc.expOutput, output)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSuffix(t *testing.T) {\n\ttestCases := []*struct {\n\t\ttestName string\n\t\tinput    string\n\t\texp      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\"no path\",\n\t\t\t\"linkerd-foo\",\n\t\t\t\"foo\",\n\t\t},\n\t\t{\n\t\t\t\"extra dash\",\n\t\t\t\"linkerd-foo-bar\",\n\t\t\t\"foo-bar\",\n\t\t},\n\t\t{\n\t\t\t\"with path\",\n\t\t\t\"/tmp/linkerd-foo\",\n\t\t\t\"foo\",\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tresult := suffix(tc.input)\n\t\t\tif !reflect.DeepEqual(tc.exp, result) {\n\t\t\t\tt.Fatalf(\"Expected [%s] Got [%s]\", tc.exp, result)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cli/cmd/check_test.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n)\n\nfunc TestCheckStatus(t *testing.T) {\n\tt.Run(\"Prints expected output\", func(t *testing.T) {\n\t\thc := healthcheck.NewHealthChecker(\n\t\t\t[]healthcheck.CategoryID{},\n\t\t\t&healthcheck.Options{},\n\t\t)\n\t\thc.AppendCategories(healthcheck.NewCategory(\"category\", []healthcheck.Checker{\n\t\t\t*healthcheck.NewChecker(\"check1\").\n\t\t\t\tWithCheck(func(context.Context) error {\n\t\t\t\t\treturn nil\n\t\t\t\t}),\n\t\t\t*healthcheck.NewChecker(\"check2\").\n\t\t\t\tWithHintAnchor(\"hint-anchor\").\n\t\t\t\tWithCheck(func(context.Context) error {\n\t\t\t\t\treturn fmt.Errorf(\"This should contain instructions for fail\")\n\t\t\t\t}),\n\t\t},\n\t\t\ttrue,\n\t\t))\n\n\t\toutput := bytes.NewBufferString(\"\")\n\t\thealthcheck.RunChecks(output, stderr, hc, tableOutput)\n\n\t\tgoldenFileBytes, err := os.ReadFile(\"testdata/check_output.golden\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\texpectedContent := string(goldenFileBytes)\n\n\t\tif expectedContent != output.String() {\n\t\t\tt.Fatalf(\"Expected function to render:\\n%s\\bbut got:\\n%s\", expectedContent, output)\n\t\t}\n\t})\n\n\tt.Run(\"Prints expected output in json\", func(t *testing.T) {\n\t\thc := healthcheck.NewHealthChecker(\n\t\t\t[]healthcheck.CategoryID{},\n\t\t\t&healthcheck.Options{},\n\t\t)\n\t\thc.AppendCategories(healthcheck.NewCategory(\"category\", []healthcheck.Checker{\n\t\t\t*healthcheck.NewChecker(\"check1\").\n\t\t\t\tWithCheck(func(context.Context) error {\n\t\t\t\t\treturn nil\n\t\t\t\t}),\n\t\t\t*healthcheck.NewChecker(\"check2\").\n\t\t\t\tWithHintAnchor(\"hint-anchor\").\n\t\t\t\tWithCheck(func(context.Context) error {\n\t\t\t\t\treturn fmt.Errorf(\"This should contain instructions for fail\")\n\t\t\t\t}),\n\t\t},\n\t\t\ttrue,\n\t\t))\n\n\t\toutput := bytes.NewBufferString(\"\")\n\t\thealthcheck.RunChecks(output, stderr, hc, jsonOutput)\n\n\t\tgoldenFileBytes, err := os.ReadFile(\"testdata/check_output_json.golden\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\texpectedContent := string(goldenFileBytes)\n\n\t\tif expectedContent != output.String() {\n\t\t\tt.Fatalf(\"Expected function to render:\\n%s\\bbut got:\\n%s\", expectedContent, output)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "cli/cmd/completion.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n)\n\n// newCmdCompletion creates a new cobra command `completion` which contains commands for\n// enabling linkerd auto completion\nfunc newCmdCompletion() *cobra.Command {\n\texample := `  # bash <= 3.2:\n  # To load shell completion into your current shell session\n  source /dev/stdin <<< \"$(linkerd completion bash)\"\n\n  # bash >= 4.0:\n  source <(linkerd completion bash)\n\n  # To load shell completion for every shell session\n  # bash <= 3.2 on osx:\n  brew install bash-completion # ensure you have bash-completion 1.3+\n  linkerd completion bash > $(brew --prefix)/etc/bash_completion.d/linkerd\n\n  # bash >= 4.0 on osx:\n  brew install bash-completion@2\n  linkerd completion bash > $(brew --prefix)/etc/bash_completion.d/linkerd\n\n  # bash >= 4.0 on linux:\n  linkerd completion bash > /etc/bash_completion.d/linkerd\n\n  # You will need to start a new shell for this setup to take effect.\n\n  # zsh:\n  # If shell completion is not already enabled in your environment you will need\n  # to enable it.  You can execute the following once:\n\n  echo \"autoload -U compinit && compinit\" >> ~/.zshrc\n\n  # create a linkerd 'plugins' folder and add it to your $fpath\n  mkdir $ZSH/plugins/linkerd && echo \"fpath=($ZSH/plugins/linkerd $fpath)\" >> ~/.zshrc\n\n  # To load completions for each session, execute once:\n  linkerd completion zsh > \"${fpath[1]}/_linkerd\" && exec $SHELL\n\n  # You will need to start a new shell for this setup to take effect.\n\n  # fish:\n  linkerd completion fish | source\n\n  # To load fish shell completions for each session, execute once:\n  linkerd completion fish > ~/.config/fish/completions/linkerd.fish`\n\n\tcmd := &cobra.Command{\n\t\tUse:       \"completion [bash|zsh|fish]\",\n\t\tShort:     \"Output shell completion code for the specified shell (bash, zsh or fish)\",\n\t\tLong:      \"Output shell completion code for the specified shell (bash, zsh or fish).\",\n\t\tExample:   example,\n\t\tArgs:      cobra.ExactArgs(1),\n\t\tValidArgs: []string{\"bash\", \"zsh\", \"fish\"},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tout, err := getCompletion(args[0], cmd.Parent())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfmt.Print(out)\n\t\t\treturn nil\n\t\t},\n\t}\n\n\treturn cmd\n}\n\n// getCompletion will return the auto completion shell script, if supported\nfunc getCompletion(sh string, parent *cobra.Command) (string, error) {\n\tvar err error\n\tvar buf bytes.Buffer\n\n\tswitch sh {\n\tcase \"bash\":\n\t\terr = parent.GenBashCompletion(&buf)\n\tcase \"zsh\":\n\t\terr = parent.GenZshCompletion(&buf)\n\tcase \"fish\":\n\t\terr = parent.GenFishCompletion(&buf, true)\n\n\tdefault:\n\t\terr = errors.New(\"unsupported shell type (must be bash, zsh or fish): \" + sh)\n\t}\n\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn buf.String(), nil\n}\n"
  },
  {
    "path": "cli/cmd/completion_test.go",
    "content": "package cmd\n\nimport (\n\t\"testing\"\n)\n\nfunc TestCompletion(t *testing.T) {\n\trootCmd := NewRootCmd()\n\tt.Run(\"Returns completion code\", func(t *testing.T) {\n\n\t\t_, err := getCompletion(\"bash\", rootCmd)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %+v\", err)\n\t\t}\n\n\t\t_, err = getCompletion(\"zsh\", rootCmd)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %+v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"Fails with invalid shell type\", func(t *testing.T) {\n\t\tout, err := getCompletion(\"foo\", rootCmd)\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"Unexpected success for invalid shell type: %+v\", out)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "cli/cmd/controller-metrics.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// ControllerMetricsOptions holds values for command line flags that apply to the controller-metrics\n// command.\ntype ControllerMetricsOptions struct {\n\twait time.Duration\n}\n\n// newControllerMetricsOptions initializes controller-metrics options setting\n// the max wait time duration as 30 seconds to fetch controller-metrics\n//\n// This option may be overridden on the CLI at run-time\nfunc newControllerMetricsOptions() *ControllerMetricsOptions {\n\treturn &ControllerMetricsOptions{\n\t\twait: 30 * time.Second,\n\t}\n}\n\n// newCmdControllerMetrics creates a new cobra command `controller-metrics` which contains commands to fetch control plane container's metrics\nfunc newCmdControllerMetrics() *cobra.Command {\n\toptions := newControllerMetricsOptions()\n\n\tcmd := &cobra.Command{\n\t\tUse:     \"controller-metrics\",\n\t\tAliases: []string{\"cp-metrics\"},\n\t\tShort:   \"Fetch metrics directly from the Linkerd control plane containers\",\n\t\tLong: `Fetch metrics directly from Linkerd control plane containers.\n\n  This command initiates port-forward to each control plane process, and\n  queries the /metrics endpoint on them.`,\n\t\tArgs: cobra.NoArgs,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tpods, err := k8sAPI.CoreV1().Pods(controlPlaneNamespace).List(cmd.Context(), metav1.ListOptions{})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tresults := getMetrics(k8sAPI, pods.Items, k8s.AdminHTTPPortNameSuffix, options.wait, verbose)\n\n\t\t\tvar buf bytes.Buffer\n\t\t\tfor i, result := range results {\n\t\t\t\tcontent := fmt.Sprintf(\"#\\n# POD %s (%d of %d)\\n\", result.pod, i+1, len(results))\n\t\t\t\tif result.err != nil {\n\t\t\t\t\tcontent += fmt.Sprintf(\"# ERROR %s\\n\", result.err)\n\t\t\t\t} else {\n\t\t\t\t\tcontent += fmt.Sprintf(\"# CONTAINER %s \\n#\\n\", result.container)\n\t\t\t\t\tcontent += string(result.metrics)\n\t\t\t\t}\n\t\t\t\tbuf.WriteString(content)\n\t\t\t}\n\t\t\tfmt.Printf(\"%s\", buf.String())\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcmd.Flags().DurationVarP(&options.wait, \"wait\", \"w\", options.wait, \"Time allowed to fetch diagnostics\")\n\n\treturn cmd\n}\n"
  },
  {
    "path": "cli/cmd/diagnostics.go",
    "content": "package cmd\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\n// newCmdDiagnostics creates a new cobra command `diagnostics` which contains commands to fetch Linkerd diagnostics\nfunc newCmdDiagnostics() *cobra.Command {\n\n\tdiagnosticsCmd := &cobra.Command{\n\t\tUse:     \"diagnostics [flags]\",\n\t\tAliases: []string{\"dg\"},\n\t\tArgs:    cobra.NoArgs,\n\t\tShort:   \"Commands used to diagnose Linkerd components\",\n\t\tLong: `Commands used to diagnose Linkerd components.\n\nThis command provides subcommands to diagnose the functionality of Linkerd.`,\n\t\tExample: `  # Get control-plane component metrics\n  linkerd diagnostics controller-metrics\n\n  # Get metrics from the web deployment in the emojivoto namespace.\n  linkerd diagnostics proxy-metrics -n emojivoto deploy/web\n\n  # Get the endpoints for authorities in Linkerd's control-plane itself\n  linkerd diagnostics endpoints web.linkerd-viz.svc.cluster.local:8084\n  `,\n\t}\n\n\tdiagnosticsCmd.AddCommand(newCmdControllerMetrics())\n\tdiagnosticsCmd.AddCommand(newCmdEndpoints())\n\tdiagnosticsCmd.AddCommand(newCmdMetrics())\n\tdiagnosticsCmd.AddCommand(newCmdPolicy())\n\tdiagnosticsCmd.AddCommand(newCmdDiagnosticsProfile())\n\n\treturn diagnosticsCmd\n}\n"
  },
  {
    "path": "cli/cmd/diagnostics_profile.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\tdestinationPb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/spf13/cobra\"\n\t\"google.golang.org/grpc\"\n)\n\ntype diagProfileOptions struct {\n\tdestinationPod string\n\tcontextToken   string\n}\n\n// validate performs all validation on the command-line options.\n// It returns the first error encountered, or `nil` if the options are valid.\nfunc (o *diagProfileOptions) validate() error {\n\treturn nil\n}\n\nfunc newDiagProfileOptions() *diagProfileOptions {\n\treturn &diagProfileOptions{}\n}\n\nfunc newCmdDiagnosticsProfile() *cobra.Command {\n\toptions := newDiagProfileOptions()\n\n\texample := `  # Get the service profile for the service or endpoint at 10.20.2.4:8080\n  linkerd diagnostics profile 10.20.2.4:8080`\n\n\tcmd := &cobra.Command{\n\t\tUse:     \"profile [flags] address\",\n\t\tAliases: []string{\"ep\"},\n\t\tShort:   \"Introspect Linkerd's service discovery state\",\n\t\tLong: `Introspect Linkerd's service discovery state.\n\nThis command provides debug information about the internal state of the\ncontrol-plane's destination controller. It queries the same Destination service\nendpoint as the linkerd-proxy's, and returns the profile associated with that\ndestination.`,\n\t\tExample: example,\n\t\tArgs:    cobra.ExactArgs(1),\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\terr := options.validate()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tvar client destinationPb.DestinationClient\n\t\t\tvar conn *grpc.ClientConn\n\t\t\tif apiAddr != \"\" {\n\t\t\t\tclient, conn, err = destination.NewClient(apiAddr)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"Error creating destination client: %s\\n\", err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tclient, conn, err = destination.NewExternalClient(cmd.Context(), controlPlaneNamespace, k8sAPI, options.destinationPod)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"Error creating destination client: %s\\n\", err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdefer conn.Close()\n\n\t\t\tprofile, err := requestProfileFromAPI(client, options.contextToken, args[0])\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"Destination API error: %s\\n\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\treturn writeProfileJSON(os.Stdout, profile)\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVar(&options.destinationPod, \"destination-pod\", \"\", \"Target a specific destination Pod when there are multiple running\")\n\tcmd.PersistentFlags().StringVar(&options.contextToken, \"token\", \"\", \"The context token to use when making the request to the destination API\")\n\n\tpkgcmd.ConfigureOutputFlagCompletion(cmd)\n\n\treturn cmd\n}\n\nfunc requestProfileFromAPI(client destinationPb.DestinationClient, token string, addr string) (*destinationPb.DestinationProfile, error) {\n\tdest := &destinationPb.GetDestination{\n\t\tPath:         addr,\n\t\tContextToken: token,\n\t}\n\n\trsp, err := client.GetProfile(context.Background(), dest)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rsp.Recv()\n}\n\nfunc writeProfileJSON(w io.Writer, profile *destinationPb.DestinationProfile) error {\n\tb, err := json.MarshalIndent(profile, \"\", \"  \")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = fmt.Fprintln(w, string(b))\n\treturn err\n}\n"
  },
  {
    "path": "cli/cmd/doc.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\tcobradoc \"github.com/spf13/cobra/doc\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n)\n\ntype references struct {\n\tCLIReference         []cmdDoc\n\tAnnotationsReference []annotationDoc\n}\n\ntype cmdOption struct {\n\tName         string\n\tShorthand    string\n\tDefaultValue string\n\tUsage        string\n}\n\ntype cmdDoc struct {\n\tName             string\n\tSynopsis         string\n\tDescription      string\n\tOptions          []cmdOption\n\tInheritedOptions []cmdOption\n\tExample          string\n\tSeeAlso          []string\n}\n\ntype annotationDoc struct {\n\tName        string\n\tDescription string\n}\n\nfunc newCmdDoc(rootCmd *cobra.Command) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:    \"doc\",\n\t\tHidden: true,\n\t\tShort:  \"Generate YAML documentation for the Linkerd CLI & Proxy annotations\",\n\t\tArgs:   cobra.NoArgs,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tcmdList, err := generateCLIDocs(rootCmd)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tannotations := generateAnnotationsDocs()\n\n\t\t\tref := references{\n\t\t\t\tCLIReference:         cmdList,\n\t\t\t\tAnnotationsReference: annotations,\n\t\t\t}\n\t\t\tout, err := yaml.Marshal(ref)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\twarn := \"# Automatically generated by the linkerd doc command, do not manually edit\"\n\t\t\tfmt.Printf(\"%s\\n\\n%s\\n\", warn, out)\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\treturn cmd\n}\n\n// generateCLIDocs takes a command and recursively walks the tree of commands,\n// adding each as an item to cmdList.\nfunc generateCLIDocs(cmd *cobra.Command) ([]cmdDoc, error) {\n\tvar cmdList []cmdDoc\n\n\tfor _, c := range cmd.Commands() {\n\t\tif !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {\n\t\t\tcontinue\n\t\t}\n\n\t\tsubList, err := generateCLIDocs(c)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcmdList = append(cmdList, subList...)\n\t}\n\n\tvar buf bytes.Buffer\n\n\tif err := cobradoc.GenYaml(cmd, io.Writer(&buf)); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar doc cmdDoc\n\tif err := yaml.Unmarshal(buf.Bytes(), &doc); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Cobra names start with linkerd, strip that off for the docs.\n\tdoc.Name = strings.TrimPrefix(doc.Name, \"linkerd \")\n\n\t// Don't include the root command.\n\tif doc.Name != \"linkerd\" {\n\t\tcmdList = append(cmdList, doc)\n\t}\n\n\treturn cmdList, nil\n}\n\n// generateAnnotationsDocs make list of annotations and its docs\nfunc generateAnnotationsDocs() []annotationDoc {\n\treturn []annotationDoc{\n\t\t{\n\t\t\tName:        k8s.ProxyInjectAnnotation,\n\t\t\tDescription: \"Controls whether or not a pod should be injected; accepted values are `enabled`, `disabled` and `ingress`\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyImageAnnotation,\n\t\t\tDescription: \"Linkerd proxy container image name\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyImagePullPolicyAnnotation,\n\t\t\tDescription: \"Docker image pull policy\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.DebugImageAnnotation,\n\t\t\tDescription: \"Linkerd debug container image name\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.DebugImageVersionAnnotation,\n\t\t\tDescription: \"Linkerd debug container image version\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.DebugImagePullPolicyAnnotation,\n\t\t\tDescription: \"Docker image pull policy for debug image\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyControlPortAnnotation,\n\t\t\tDescription: \"Proxy port to use for control\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyIgnoreInboundPortsAnnotation,\n\t\t\tDescription: \"Ports that should skip the proxy and send directly to the application. Comma-separated list of values, where each value can be a port number or a range `a-b`.\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyOpaquePortsAnnotation,\n\t\t\tDescription: \"Ports that skip the proxy's protocol detection mechanism and are proxied opaquely. Comma-separated list of values, where each value can be a port number or a range `a-b`.\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyIgnoreOutboundPortsAnnotation,\n\t\t\tDescription: \"Outbound ports that should skip the proxy. Comma-separated list of values, where each value can be a port number or a range `a-b`.\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyInboundPortAnnotation,\n\t\t\tDescription: \"Proxy port to use for inbound traffic\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyAdminPortAnnotation,\n\t\t\tDescription: \"Proxy port to serve metrics on\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyOutboundPortAnnotation,\n\t\t\tDescription: \"Proxy port to use for outbound traffic\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyPodInboundPortsAnnotation,\n\t\t\tDescription: \"Comma-separated list of (non-proxy) container ports exposed by the pod spec. Useful when other mutating webhooks inject sidecar containers after the proxy injector has run\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyCPURequestAnnotation,\n\t\t\tDescription: \"Amount of CPU units that the proxy sidecar requests\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyCPULimitAnnotation,\n\t\t\tDescription: \"Maximum amount of CPU units that the proxy sidecar can use\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyCPURatioLimitAnnotation,\n\t\t\tDescription: \"Maximum ratio of proxy worker threads to total available CPUs on the node\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyMemoryRequestAnnotation,\n\t\t\tDescription: \"Amount of Memory that the proxy sidecar requests\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyMemoryLimitAnnotation,\n\t\t\tDescription: \"Maximum amount of Memory that the proxy sidecar can use\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyEphemeralStorageRequestAnnotation,\n\t\t\tDescription: \"Used to override the requestEphemeralStorage config\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyEphemeralStorageLimitAnnotation,\n\t\t\tDescription: \"Used to override the limitEphemeralStorage config\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyUIDAnnotation,\n\t\t\tDescription: \"Run the proxy under this user ID\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyGIDAnnotation,\n\t\t\tDescription: \"Run the proxy under this group ID\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyLogLevelAnnotation,\n\t\t\tDescription: \"Log level for the proxy\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyLogFormatAnnotation,\n\t\t\tDescription: \"Log format (plain or json) for the proxy\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyAccessLogAnnotation,\n\t\t\tDescription: \"Enables HTTP access logging in the proxy. Accepted values are `apache`, to output the access log in the Appache Common Log Format, and `json`, to output the access log in JSON.\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyEnableExternalProfilesAnnotation,\n\t\t\tDescription: \"Enable service profiles for non-Kubernetes services\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyVersionOverrideAnnotation,\n\t\t\tDescription: \"Tag to be used for the Linkerd proxy images\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyDefaultInboundPolicyAnnotation,\n\t\t\tDescription: \"Proxy's default inbound policy\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyEnableDebugAnnotation,\n\t\t\tDescription: \"Inject a debug sidecar for data plane debugging\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyOutboundConnectTimeout,\n\t\t\tDescription: \"Used to configure the outbound TCP connection timeout in the proxy. Defaults to `1000ms`\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyInboundConnectTimeout,\n\t\t\tDescription: \"Inbound TCP connection timeout in the proxy. Defaults to `100ms`\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyOutboundDiscoveryCacheUnusedTimeout,\n\t\t\tDescription: \"Maximum time allowed before an unused outbound discovery result is evicted from the cache. Defaults to `5s`\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyInboundDiscoveryCacheUnusedTimeout,\n\t\t\tDescription: \"Maximum time allowed before an unused inbound discovery result is evicted from the cache. Defaults to `90s`\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyDisableOutboundProtocolDetectTimeout,\n\t\t\tDescription: \"When set to true, disables the protocol detection timeout on the outbound side of the proxy by setting it to a very high value\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyDisableInboundProtocolDetectTimeout,\n\t\t\tDescription: \"When set to true, disables the protocol detection timeout on the inbound side of the proxy by setting it to a very high value\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyWaitBeforeExitSecondsAnnotation,\n\t\t\tDescription: \"Adds a preStop hook to the proxy container to delay receiving SIGTERM signal from Kubernetes but no longer than pod's `terminationGracePeriodSeconds`. Defaults to `0`\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyAwait,\n\t\t\tDescription: \"The application container will not start until the proxy is ready; accepted values are `enabled` and `disabled`\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.CloseWaitTimeoutAnnotation,\n\t\t\tDescription: \"Sets nf_conntrack_tcp_timeout_close_wait. Accepts a duration string, e.g. `1m` or `3600s`\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxySkipSubnetsAnnotation,\n\t\t\tDescription: \"Comma-separated list of subnets in valid CIDR format that should be skipped by the proxy\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyShutdownGracePeriodAnnotation,\n\t\t\tDescription: \"Grace period for graceful proxy shutdowns. If this timeout elapses before all open connections have completed, the proxy will terminate forcefully, closing any remaining connections.\",\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyEnableNativeSidecarAnnotationAlpha,\n\t\t\tDescription: \"Enable KEP-753 native sidecars. This is a beta feature. It requires Kubernetes >= 1.29. If enabled, .proxy.waitBeforeExitSeconds should not be used. Deprecated in favor of \" + k8s.ProxyEnableNativeSidecarAnnotationBeta,\n\t\t},\n\t\t{\n\t\t\tName:        k8s.ProxyEnableNativeSidecarAnnotationBeta,\n\t\t\tDescription: \"Enable KEP-753 native sidecars. This is a beta feature. It requires Kubernetes >= 1.29. If enabled, .proxy.waitBeforeExitSeconds should not be used.\",\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "cli/cmd/endpoints.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\t\"time\"\n\n\tdestinationPb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\tnetPb \"github.com/linkerd/linkerd2-proxy-api/go/net\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination\"\n\t\"github.com/linkerd/linkerd2/pkg/addr\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/status\"\n)\n\ntype endpointsOptions struct {\n\toutputFormat   string\n\tdestinationPod string\n\tcontextToken   string\n}\n\ntype (\n\t// map[ServiceID]map[Port][]podData\n\tendpointsInfo map[string]map[uint32][]podData\n\tpodData       struct {\n\t\tname    string\n\t\taddress string\n\t\tip      string\n\t\tweight  uint32\n\t\tlabels  map[string]string\n\t\thttp2   *destinationPb.Http2ClientParams\n\t}\n)\n\nconst (\n\tpodHeader       = \"POD\"\n\tnamespaceHeader = \"NAMESPACE\"\n\tpadding         = 3\n)\n\n// validate performs all validation on the command-line options.\n// It returns the first error encountered, or `nil` if the options are valid.\nfunc (o *endpointsOptions) validate() error {\n\tif o.outputFormat == tableOutput || o.outputFormat == jsonOutput {\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"--output currently only supports %s and %s\", tableOutput, jsonOutput)\n}\n\nfunc newEndpointsOptions() *endpointsOptions {\n\treturn &endpointsOptions{\n\t\toutputFormat: tableOutput,\n\t}\n}\n\nfunc newCmdEndpoints() *cobra.Command {\n\toptions := newEndpointsOptions()\n\n\texample := `  # get all endpoints for the authorities emoji-svc.emojivoto.svc.cluster.local:8080 and web-svc.emojivoto.svc.cluster.local:80\n  linkerd diagnostics endpoints emoji-svc.emojivoto.svc.cluster.local:8080 web-svc.emojivoto.svc.cluster.local:80\n\n  # get that same information in json format\n  linkerd diagnostics endpoints -o json emoji-svc.emojivoto.svc.cluster.local:8080 web-svc.emojivoto.svc.cluster.local:80\n\n  # get the endpoints for authorities in Linkerd's control-plane itself\n  linkerd diagnostics endpoints web.linkerd-viz.svc.cluster.local:8084`\n\n\tcmd := &cobra.Command{\n\t\tUse:     \"endpoints [flags] authorities\",\n\t\tAliases: []string{\"ep\"},\n\t\tShort:   \"Introspect Linkerd's service discovery state\",\n\t\tLong: `Introspect Linkerd's service discovery state.\n\nThis command provides debug information about the internal state of the\ncontrol-plane's destination container. It queries the same Destination service\nendpoint as the linkerd-proxy's, and returns the addresses associated with that\ndestination.`,\n\t\tExample: example,\n\t\tArgs:    cobra.MinimumNArgs(1),\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\terr := options.validate()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tvar client destinationPb.DestinationClient\n\t\t\tvar conn *grpc.ClientConn\n\t\t\tif apiAddr != \"\" {\n\t\t\t\tclient, conn, err = destination.NewClient(apiAddr)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"Error creating destination client: %s\\n\", err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tclient, conn, err = destination.NewExternalClient(cmd.Context(), controlPlaneNamespace, k8sAPI, options.destinationPod)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"Error creating destination client: %s\\n\", err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdefer conn.Close()\n\n\t\t\tendpoints, err := requestEndpointsFromAPI(client, options.contextToken, args)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"Destination API error: %s\\n\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\toutput := renderEndpoints(endpoints, options)\n\t\t\t_, err = fmt.Print(output)\n\n\t\t\treturn err\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVarP(&options.outputFormat, \"output\", \"o\", options.outputFormat, fmt.Sprintf(\"Output format; one of: \\\"%s\\\" or \\\"%s\\\"\", tableOutput, jsonOutput))\n\tcmd.PersistentFlags().StringVar(&options.destinationPod, \"destination-pod\", \"\", \"Target a specific destination Pod when there are multiple running\")\n\tcmd.PersistentFlags().StringVar(&options.contextToken, \"token\", \"\", \"The context token to use when making the request to the destination API\")\n\n\tpkgcmd.ConfigureOutputFlagCompletion(cmd)\n\n\treturn cmd\n}\n\nfunc requestEndpointsFromAPI(client destinationPb.DestinationClient, token string, authorities []string) (endpointsInfo, error) {\n\tinfo := make(endpointsInfo)\n\tevents := make(chan *destinationPb.Update, 1000)\n\terrs := make(chan error, 1000)\n\n\tfor _, authority := range authorities {\n\t\tgo func(authority string) {\n\t\t\tif len(errs) == 0 {\n\t\t\t\tdest := &destinationPb.GetDestination{\n\t\t\t\t\tScheme:       \"http:\",\n\t\t\t\t\tPath:         authority,\n\t\t\t\t\tContextToken: token,\n\t\t\t\t}\n\n\t\t\t\trsp, err := client.Get(context.Background(), dest)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrs <- err\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Endpoint state may be sent in multiple messages so it's not\n\t\t\t\t// sufficient to read only the first message. Instead, we\n\t\t\t\t// continuously read from the stream. This goroutine will never\n\t\t\t\t// terminate if there are no errors, but this is okay for a\n\t\t\t\t// short lived CLI command.\n\t\t\t\tfor {\n\t\t\t\t\tevent, err := rsp.Recv()\n\t\t\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t} else if err != nil {\n\t\t\t\t\t\tif grpcError, ok := status.FromError(err); ok {\n\t\t\t\t\t\t\terr = errors.New(grpcError.Message())\n\t\t\t\t\t\t}\n\t\t\t\t\t\terrs <- err\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tevents <- event\n\t\t\t\t}\n\t\t\t}\n\t\t}(authority)\n\t}\n\t// Wait an amount of time for some endpoint responses to be received.\n\ttimeout := time.NewTimer(5 * time.Second)\n\n\tfor {\n\t\tselect {\n\t\tcase err := <-errs:\n\t\t\t// we only care about the first error\n\t\t\treturn nil, err\n\t\tcase event := <-events:\n\t\t\taddressSet := event.GetAdd()\n\t\t\tlabels := addressSet.GetMetricLabels()\n\t\t\tserviceID := labels[\"service\"] + \".\" + labels[\"namespace\"]\n\t\t\tif _, ok := info[serviceID]; !ok {\n\t\t\t\tinfo[serviceID] = make(map[uint32][]podData)\n\t\t\t}\n\n\t\t\tfor _, addr := range addressSet.GetAddrs() {\n\t\t\t\ttcpAddr := addr.GetAddr()\n\t\t\t\tport := tcpAddr.GetPort()\n\n\t\t\t\tif info[serviceID][port] == nil {\n\t\t\t\t\tinfo[serviceID][port] = make([]podData, 0)\n\t\t\t\t}\n\n\t\t\t\tlabels := addr.GetMetricLabels()\n\t\t\t\tinfo[serviceID][port] = append(info[serviceID][port], podData{\n\t\t\t\t\tname:    labels[\"pod\"],\n\t\t\t\t\taddress: tcpAddr.String(),\n\t\t\t\t\tip:      getIP(tcpAddr),\n\t\t\t\t\tweight:  addr.GetWeight(),\n\t\t\t\t\tlabels:  addr.GetMetricLabels(),\n\t\t\t\t\thttp2:   addr.GetHttp2(),\n\t\t\t\t})\n\t\t\t}\n\t\tcase <-timeout.C:\n\t\t\treturn info, nil\n\t\t}\n\t}\n}\n\nfunc getIP(tcpAddr *netPb.TcpAddress) string {\n\tip := addr.FromProxyAPI(tcpAddr.GetIp())\n\tif ip == nil {\n\t\treturn \"\"\n\t}\n\treturn addr.PublicIPToString(ip)\n}\n\nfunc renderEndpoints(endpoints endpointsInfo, options *endpointsOptions) string {\n\tvar buffer bytes.Buffer\n\tw := tabwriter.NewWriter(&buffer, 0, 0, padding, ' ', 0)\n\twriteEndpointsToBuffer(endpoints, w, options)\n\tw.Flush()\n\n\treturn buffer.String()\n}\n\ntype rowEndpoint struct {\n\tNamespace string `json:\"namespace\"`\n\tIP        string `json:\"ip\"`\n\tPort      uint32 `json:\"port\"`\n\tPod       string `json:\"pod\"`\n\tService   string `json:\"service\"`\n\tWeight    uint32 `json:\"weight\"`\n\n\tHttp2 *destinationPb.Http2ClientParams `json:\"http2,omitempty\"`\n\n\tLabels map[string]string `json:\"labels\"`\n}\n\nfunc writeEndpointsToBuffer(endpoints endpointsInfo, w *tabwriter.Writer, options *endpointsOptions) {\n\tmaxPodLength := len(podHeader)\n\tmaxNamespaceLength := len(namespaceHeader)\n\tendpointsTables := map[string][]rowEndpoint{}\n\n\tfor serviceID, servicePort := range endpoints {\n\t\tnamespace := \"\"\n\t\tparts := strings.SplitN(serviceID, \".\", 2)\n\t\tnamespace = parts[1]\n\n\t\tfor port, podAddrs := range servicePort {\n\t\t\tfor _, pod := range podAddrs {\n\t\t\t\tname := pod.name\n\t\t\t\tparts := strings.SplitN(name, \"/\", 2)\n\t\t\t\tif len(parts) == 2 {\n\t\t\t\t\tname = parts[1]\n\t\t\t\t}\n\t\t\t\trow := rowEndpoint{\n\t\t\t\t\tNamespace: namespace,\n\t\t\t\t\tIP:        pod.ip,\n\t\t\t\t\tPort:      port,\n\t\t\t\t\tPod:       name,\n\t\t\t\t\tService:   serviceID,\n\t\t\t\t\tWeight:    pod.weight,\n\t\t\t\t\tLabels:    pod.labels,\n\t\t\t\t\tHttp2:     pod.http2,\n\t\t\t\t}\n\n\t\t\t\tendpointsTables[namespace] = append(endpointsTables[namespace], row)\n\n\t\t\t\tif len(name) > maxPodLength {\n\t\t\t\t\tmaxPodLength = len(name)\n\t\t\t\t}\n\t\t\t\tif len(namespace) > maxNamespaceLength {\n\t\t\t\t\tmaxNamespaceLength = len(namespace)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsort.Slice(endpointsTables[namespace], func(i, j int) bool {\n\t\t\t\treturn endpointsTables[namespace][i].Service < endpointsTables[namespace][j].Service\n\t\t\t})\n\t\t}\n\t}\n\n\tswitch options.outputFormat {\n\tcase tableOutput:\n\t\tif len(endpointsTables) == 0 {\n\t\t\tfmt.Fprintln(os.Stderr, \"No endpoints found.\")\n\t\t\tos.Exit(0)\n\t\t}\n\t\tprintEndpointsTables(endpointsTables, w, maxPodLength, maxNamespaceLength)\n\tcase jsonOutput:\n\t\tprintEndpointsJSON(endpointsTables, w)\n\t}\n}\n\nfunc printEndpointsTables(endpointsTables map[string][]rowEndpoint, w *tabwriter.Writer, maxPodLength int, maxNamespaceLength int) {\n\tfirstTable := true // don't print a newline before the first table\n\n\tfor _, ns := range sortNamespaceKeys(endpointsTables) {\n\t\tif !firstTable {\n\t\t\tfmt.Fprint(w, \"\\n\")\n\t\t}\n\t\tfirstTable = false\n\t\tprintEndpointsTable(ns, endpointsTables[ns], w, maxPodLength, maxNamespaceLength)\n\t}\n}\n\nfunc printEndpointsTable(namespace string, rows []rowEndpoint, w *tabwriter.Writer, maxPodLength int, maxNamespaceLength int) {\n\theaders := make([]string, 0)\n\ttemplateString := \"%s\\t%d\\t%s\\t%s\\n\"\n\n\theaders = append(headers, namespaceHeader+strings.Repeat(\" \", maxNamespaceLength-len(namespaceHeader)))\n\ttemplateString = \"%s\\t\" + templateString\n\n\theaders = append(headers, []string{\n\t\t\"IP\",\n\t\t\"PORT\",\n\t\tpodHeader + strings.Repeat(\" \", maxPodLength-len(podHeader)),\n\t\t\"SERVICE\",\n\t}...)\n\tfmt.Fprintln(w, strings.Join(headers, \"\\t\"))\n\n\tfor _, row := range rows {\n\t\tvalues := []interface{}{\n\t\t\tnamespace + strings.Repeat(\" \", maxNamespaceLength-len(namespace)),\n\t\t\trow.IP,\n\t\t\trow.Port,\n\t\t\trow.Pod,\n\t\t\trow.Service,\n\t\t}\n\n\t\tfmt.Fprintf(w, templateString, values...)\n\t}\n}\n\nfunc printEndpointsJSON(endpointsTables map[string][]rowEndpoint, w *tabwriter.Writer) {\n\tentries := []rowEndpoint{}\n\n\tfor _, ns := range sortNamespaceKeys(endpointsTables) {\n\t\tentries = append(entries, endpointsTables[ns]...)\n\t}\n\n\tb, err := json.MarshalIndent(entries, \"\", \"  \")\n\tif err != nil {\n\t\tlog.Error(err.Error())\n\t\treturn\n\t}\n\tfmt.Fprintf(w, \"%s\\n\", b)\n}\n\nfunc sortNamespaceKeys(endpointsTables map[string][]rowEndpoint) []string {\n\tvar sortedKeys []string\n\tfor key := range endpointsTables {\n\t\tsortedKeys = append(sortedKeys, key)\n\t}\n\tsort.Strings(sortedKeys)\n\treturn sortedKeys\n}\n"
  },
  {
    "path": "cli/cmd/endpoints_test.go",
    "content": "package cmd\n\nimport (\n\t\"testing\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2/controller/api/util\"\n)\n\ntype endpointsExp struct {\n\toptions     *endpointsOptions\n\tauthorities []string\n\tendpoints   []util.AuthorityEndpoints\n\tfile        string\n}\n\nfunc TestEndpoints(t *testing.T) {\n\toptions := newEndpointsOptions()\n\n\tt.Run(\"Returns endpoints same namespace\", func(t *testing.T) {\n\t\ttestEndpointsCall(endpointsExp{\n\t\t\toptions:     options,\n\t\t\tauthorities: []string{\"emoji-svc.emojivoto.svc.cluster.local:8080\", \"voting-svc.emojivoto.svc.cluster.local:8080\"},\n\t\t\tendpoints: []util.AuthorityEndpoints{\n\t\t\t\t{\n\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\tServiceID: \"emoji-svc\",\n\t\t\t\t\tPods: []util.PodDetails{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"emoji-6bf9f47bd5-jjcrl\",\n\t\t\t\t\t\t\tIP:   16909060,\n\t\t\t\t\t\t\tPort: 8080,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\tServiceID: \"voting-svc\",\n\t\t\t\t\tPods: []util.PodDetails{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"voting-7bf9f47bd5-jjdrl\",\n\t\t\t\t\t\t\tIP:   84281096,\n\t\t\t\t\t\t\tPort: 8080,\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\tfile: \"endpoints_one_output.golden\",\n\t\t}, t)\n\t})\n\n\tt.Run(\"Returns endpoints different namespace\", func(t *testing.T) {\n\t\ttestEndpointsCall(endpointsExp{\n\t\t\toptions:     options,\n\t\t\tauthorities: []string{\"emoji-svc.emojivoto.svc.cluster.local:8080\", \"voting-svc.emojivoto2.svc.cluster.local:8080\"},\n\t\t\tendpoints: []util.AuthorityEndpoints{\n\t\t\t\t{\n\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\tServiceID: \"emoji-svc\",\n\t\t\t\t\tPods: []util.PodDetails{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"emoji-6bf9f47bd5-jjcrl\",\n\t\t\t\t\t\t\tIP:   16909060,\n\t\t\t\t\t\t\tPort: 8080,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tNamespace: \"emojivoto2\",\n\t\t\t\t\tServiceID: \"voting-svc\",\n\t\t\t\t\tPods: []util.PodDetails{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"voting-7bf9f47bd5-jjdrl\",\n\t\t\t\t\t\t\tIP:   84281096,\n\t\t\t\t\t\t\tPort: 8080,\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\tfile: \"endpoints_two_outputs.golden\",\n\t\t}, t)\n\t})\n\n\toptions.outputFormat = jsonOutput\n\tt.Run(\"Returns endpoints same namespace (json)\", func(t *testing.T) {\n\t\ttestEndpointsCall(endpointsExp{\n\t\t\toptions:     options,\n\t\t\tauthorities: []string{\"emoji-svc.emojivoto.svc.cluster.local:8080\", \"voting-svc.emojivoto.svc.cluster.local:8080\"},\n\t\t\tendpoints: []util.AuthorityEndpoints{\n\t\t\t\t{\n\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\tServiceID: \"emoji-svc\",\n\t\t\t\t\tPods: []util.PodDetails{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"emoji-6bf9f47bd5-jjcrl\",\n\t\t\t\t\t\t\tIP:   16909060,\n\t\t\t\t\t\t\tPort: 8080,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\tServiceID: \"voting-svc\",\n\t\t\t\t\tPods: []util.PodDetails{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"voting-7bf9f47bd5-jjdrl\",\n\t\t\t\t\t\t\tIP:   84281096,\n\t\t\t\t\t\t\tPort: 8080,\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\tfile: \"endpoints_one_output_json.golden\",\n\t\t}, t)\n\t})\n}\n\nfunc testEndpointsCall(exp endpointsExp, t *testing.T) {\n\tt.Helper()\n\n\tupdates := make([]pb.Update, 0)\n\tfor _, endpoint := range exp.endpoints {\n\t\taddrSet := util.BuildAddrSet(endpoint)\n\t\tupdates = append(updates, pb.Update{Update: &pb.Update_Add{Add: addrSet}})\n\t}\n\n\tmockClient := &util.MockAPIClient{\n\t\tDestinationGetClientToReturn: &util.MockDestinationGetClient{\n\t\t\tUpdatesToReturn: updates,\n\t\t},\n\t}\n\n\tendpoints, err := requestEndpointsFromAPI(mockClient, \"\", exp.authorities)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\toutput := renderEndpoints(endpoints, exp.options)\n\n\ttestDataDiffer.DiffTestdata(t, exp.file, output)\n}\n"
  },
  {
    "path": "cli/cmd/identity.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/grantae/certinfo\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\nvar emitLog bool\n\ntype certificate struct {\n\tpod         string\n\tcontainer   string\n\tCertificate []*x509.Certificate\n\terr         error\n}\n\ntype identityOptions struct {\n\tpod       string\n\tnamespace string\n\tselector  string\n}\n\nfunc newIdentityOptions() *identityOptions {\n\treturn &identityOptions{\n\t\tpod:      \"\",\n\t\tselector: \"\",\n\t}\n}\n\nfunc newCmdIdentity() *cobra.Command {\n\temitLog = false\n\toptions := newIdentityOptions()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"identity [flags] (PODS)\",\n\t\tShort: \"Display the certificate(s) of one or more selected pod(s)\",\n\t\tLong: `Display the certificate(s) of one or more selected pod(s).\n\nThis command initiates a port-forward to a given pod or a set of pods and fetches the TLS certificate.\n\t\t`,\n\t\tExample: `\n # Get certificate from pod foo-bar in the default namespace.\n linkerd identity foo-bar\n\n # Get certificate from all pods with the label name=nginx\n linkerd identity -l name=nginx\n\t\t`,\n\t\tValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\tcc := k8s.NewCommandCompletion(k8sAPI, options.namespace)\n\n\t\t\t// insert pod resource type as first argument to suggest\n\t\t\t// pod resources only\n\t\t\targs = append([]string{k8s.Pod}, args...)\n\t\t\tresults, err := cc.Complete(args, toComplete)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\treturn results, cobra.ShellCompDirectiveDefault\n\t\t},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif len(args) == 0 && options.selector == \"\" {\n\t\t\t\treturn fmt.Errorf(\"Provide the pod name argument or use the selector flag\")\n\t\t\t}\n\n\t\t\tpods, err := getPods(cmd.Context(), k8sAPI, options.namespace, options.selector, args)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tresultCerts := getCertificate(k8sAPI, pods, k8s.ProxyAdminPortName, emitLog)\n\t\t\tif len(resultCerts) == 0 {\n\t\t\t\tfmt.Print(\"Could not fetch Certificate. Ensure that the pod(s) are meshed by running `linkerd inject`\\n\")\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tfor i, resultCert := range resultCerts {\n\t\t\t\tfmt.Printf(\"\\nPOD %s (%d of %d)\\n\\n\", resultCert.pod, i+1, len(resultCerts))\n\t\t\t\tif resultCert.err != nil {\n\t\t\t\t\tfmt.Printf(\"\\n%s\\n\", resultCert.err)\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tfor _, cert := range resultCert.Certificate {\n\t\t\t\t\tif cert.IsCA {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tresult, err := certinfo.CertificateText(cert)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Printf(\"\\n%s\\n\", err)\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t\tfmt.Print(result)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace, \"Namespace of the pod\")\n\tcmd.PersistentFlags().StringVarP(&options.selector, \"selector\", \"l\", options.selector, \"Selector (label query) to filter on, supports ‘=’, ‘==’, and ‘!=’ \")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(cmd, []string{\"namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\n\treturn cmd\n}\n\nfunc getCertificate(k8sAPI *k8s.KubernetesAPI, pods []corev1.Pod, portName string, emitLog bool) []certificate {\n\tvar certificates []certificate\n\tfor _, pod := range pods {\n\t\tcontainer, err := getContainerWithPort(pod, portName)\n\t\tif err != nil {\n\t\t\tcertificates = append(certificates, certificate{\n\t\t\t\tpod: pod.GetName(),\n\t\t\t\terr: err,\n\t\t\t})\n\t\t\treturn certificates\n\t\t}\n\t\tcert, err := getContainerCertificate(k8sAPI, pod, container, portName, emitLog)\n\t\tcertificates = append(certificates, certificate{\n\t\t\tpod:         pod.GetName(),\n\t\t\tcontainer:   container.Name,\n\t\t\tCertificate: cert,\n\t\t\terr:         err,\n\t\t})\n\t}\n\treturn certificates\n}\n\nfunc getContainerWithPort(pod corev1.Pod, portName string) (corev1.Container, error) {\n\tvar container corev1.Container\n\tif pod.Status.Phase != corev1.PodRunning {\n\t\treturn container, fmt.Errorf(\"pod not running: %s\", pod.GetName())\n\t}\n\n\tcontainers := append(pod.Spec.InitContainers, pod.Spec.Containers...)\n\tfor _, c := range containers {\n\t\tif c.Name != k8s.ProxyContainerName {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, p := range c.Ports {\n\t\t\tif p.Name == portName {\n\t\t\t\treturn c, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn container, fmt.Errorf(\"failed to find %s port in %s container for given pod spec\", portName, k8s.ProxyContainerName)\n}\n\nfunc getContainerCertificate(k8sAPI *k8s.KubernetesAPI, pod corev1.Pod, container corev1.Container, portName string, emitLog bool) ([]*x509.Certificate, error) {\n\tportForward, err := k8s.NewContainerMetricsForward(k8sAPI, pod, container, emitLog, portName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdefer portForward.Stop()\n\tif err = portForward.Init(); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Error running port-forward: %s\\n\", err)\n\t\treturn nil, err\n\t}\n\n\tcertURL := portForward.URLFor(\"\")\n\treturn getCertResponse(certURL, pod)\n}\n\nfunc getCertResponse(url string, pod corev1.Pod) ([]*x509.Certificate, error) {\n\tserverName, err := k8s.PodIdentity(&pod)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconnURL := strings.TrimPrefix(url, \"http://\")\n\tconn, err := tls.Dial(\"tcp\", connURL, &tls.Config{\n\t\t// We want to connect directly to a proxy port to dump its certificate. We don't necessarily\n\t\t// want to verify the server's certificate, since this is purely for diagnostics and may be\n\t\t// used when a proxy's issuer doesn't match the control plane's trust root.\n\t\t//nolint:gosec\n\t\tInsecureSkipVerify: true,\n\t\tServerName:         serverName,\n\t})\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcert := conn.ConnectionState().PeerCertificates\n\treturn cert, nil\n}\n\nfunc getPods(ctx context.Context, clientset kubernetes.Interface, namespace string, selector string, args []string) ([]corev1.Pod, error) {\n\tif len(args) > 0 {\n\t\tvar pods []corev1.Pod\n\t\tfor _, arg := range args {\n\t\t\tpod, err := clientset.CoreV1().Pods(namespace).Get(ctx, arg, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tpods = append(pods, *pod)\n\t\t}\n\t\treturn pods, nil\n\t}\n\n\tpodList, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{\n\t\tLabelSelector: selector,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn podList.Items, nil\n}\n"
  },
  {
    "path": "cli/cmd/inject.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tjsonpatch \"github.com/evanphx/json-patch\"\n\t\"github.com/linkerd/linkerd2/cli/flag\"\n\t\"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/inject\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nconst (\n\t// for inject reports\n\thostNetworkDesc                  = \"pods do not use host networking\"\n\tsidecarDesc                      = \"pods do not have a 3rd party proxy or initContainer already injected\"\n\tinjectDisabledDesc               = \"pods are not annotated to disable injection\"\n\tunsupportedDesc                  = \"at least one resource can be injected or annotated\"\n\tudpDesc                          = \"pod specs do not include UDP ports\"\n\tautomountServiceAccountTokenDesc = \"pods do not have automountServiceAccountToken set to \\\"false\\\" or service account token projection is enabled\"\n\tslash                            = \"/\"\n)\n\ntype resourceTransformerInject struct {\n\tallowNsInject       bool\n\tinjectProxy         bool\n\tvalues              *linkerd2.Values\n\toverrideAnnotations map[string]string\n\tenableDebugSidecar  bool\n\tcloseWaitTimeout    time.Duration\n\toverrider           inject.ValueOverrider\n}\n\nfunc runInjectCmd(inputs []io.Reader, errWriter, outWriter io.Writer, transformer *resourceTransformerInject, output string) int {\n\treturn transformInput(inputs, errWriter, outWriter, transformer, output)\n}\n\nfunc NewCmdInject(overrider inject.ValueOverrider) *cobra.Command {\n\tdefaults, err := linkerd2.NewValues()\n\tif err != nil {\n\t\tfmt.Fprint(os.Stderr, err.Error())\n\t\tos.Exit(1)\n\t}\n\tflags, proxyFlagSet := makeProxyFlags(defaults)\n\tinjectFlags, injectFlagSet := makeInjectFlags(defaults)\n\tvar manualOption, enableDebugSidecar bool\n\tvar closeWaitTimeout time.Duration\n\tvar output string\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"inject [flags] CONFIG-FILE\",\n\t\tShort: \"Add the Linkerd proxy to a Kubernetes config\",\n\t\tLong: `Add the Linkerd proxy to a Kubernetes config.\n\nYou can inject resources contained in a single file, inside a folder and its\nsub-folders, or coming from stdin.`,\n\t\tExample: `  # Inject all the deployments in the default namespace.\n  kubectl get deploy -o yaml | linkerd inject - | kubectl apply -f -\n\n  # Injecting a file from a remote URL\n  linkerd inject https://url.to/yml | kubectl apply -f -\n\n  # Inject all the resources inside a folder and its sub-folders.\n  linkerd inject <folder> | kubectl apply -f -`,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif len(args) < 1 {\n\t\t\t\treturn fmt.Errorf(\"please specify a kubernetes resource file\")\n\t\t\t}\n\n\t\t\tvalues := defaults\n\t\t\tif !ignoreCluster {\n\t\t\t\tvalues, err = fetchConfigs(cmd.Context())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tbaseValues, err := values.DeepCopy()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = flag.ApplySetFlags(values, append(flags, injectFlags...))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tin, err := read(args[0])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\toverrideAnnotations := getOverrideAnnotations(values, baseValues)\n\n\t\t\ttransformer := &resourceTransformerInject{\n\t\t\t\tallowNsInject:       true,\n\t\t\t\tinjectProxy:         manualOption,\n\t\t\t\tvalues:              values,\n\t\t\t\toverrideAnnotations: overrideAnnotations,\n\t\t\t\tenableDebugSidecar:  enableDebugSidecar,\n\t\t\t\tcloseWaitTimeout:    closeWaitTimeout,\n\t\t\t\toverrider:           overrider,\n\t\t\t}\n\t\t\texitCode := uninjectAndInject(in, stderr, stdout, transformer, output)\n\t\t\tos.Exit(exitCode)\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcmd.Flags().BoolVar(\n\t\t&manualOption, \"manual\", manualOption,\n\t\t\"Include the proxy sidecar container spec in the YAML output (the auto-injector won't pick it up, so config annotations aren't supported) (default false)\",\n\t)\n\n\tcmd.Flags().BoolVar(\n\t\t&ignoreCluster, \"ignore-cluster\", false,\n\t\t\"Ignore the current Kubernetes cluster when checking for existing cluster configuration (default false)\",\n\t)\n\n\tcmd.Flags().BoolVar(&enableDebugSidecar, \"enable-debug-sidecar\", enableDebugSidecar,\n\t\t\"Inject a debug sidecar for data plane debugging\")\n\n\tcmd.Flags().DurationVar(\n\t\t&closeWaitTimeout, \"close-wait-timeout\", closeWaitTimeout,\n\t\t\"Sets nf_conntrack_tcp_timeout_close_wait\")\n\n\tcmd.Flags().StringVarP(&output, \"output\", \"o\", \"yaml\", \"Output format, one of: json|yaml\")\n\n\tcmd.Flags().AddFlagSet(proxyFlagSet)\n\tcmd.Flags().AddFlagSet(injectFlagSet)\n\n\treturn cmd\n}\n\nfunc uninjectAndInject(inputs []io.Reader, errWriter, outWriter io.Writer, transformer *resourceTransformerInject, output string) int {\n\tvar out bytes.Buffer\n\tif exitCode := runUninjectSilentCmd(inputs, errWriter, &out, transformer.values, \"yaml\"); exitCode != 0 {\n\t\treturn exitCode\n\t}\n\treturn runInjectCmd([]io.Reader{&out}, errWriter, outWriter, transformer, output)\n}\n\nfunc (rt resourceTransformerInject) transform(bytes []byte) ([]byte, []inject.Report, error) {\n\tconf := inject.NewResourceConfig(rt.values, inject.OriginCLI, controlPlaneNamespace)\n\n\tif rt.enableDebugSidecar {\n\t\tconf.AppendPodAnnotation(k8s.ProxyEnableDebugAnnotation, \"true\")\n\t}\n\n\tif rt.closeWaitTimeout != time.Duration(0) {\n\t\tconf.AppendPodAnnotation(k8s.CloseWaitTimeoutAnnotation, rt.closeWaitTimeout.String())\n\t}\n\n\treport, err := conf.ParseMetaAndYAML(bytes)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif conf.IsControlPlaneComponent() && !rt.injectProxy {\n\t\treturn nil, nil, errors.New(\"--manual must be set when injecting control plane components\")\n\t}\n\n\tif conf.IsService() {\n\t\topaquePorts, ok := rt.overrideAnnotations[k8s.ProxyOpaquePortsAnnotation]\n\t\tif ok {\n\t\t\tannotations := map[string]string{k8s.ProxyOpaquePortsAnnotation: opaquePorts}\n\t\t\tbytes, err = conf.AnnotateService(annotations)\n\t\t\treport.Annotated = true\n\t\t}\n\t\treturn bytes, []inject.Report{*report}, err\n\t}\n\tif rt.allowNsInject && conf.IsNamespace() {\n\t\tbytes, err = conf.AnnotateNamespace(rt.overrideAnnotations)\n\t\treport.Annotated = true\n\t\treturn bytes, []inject.Report{*report}, err\n\t}\n\tif conf.HasPodTemplate() && len(rt.overrideAnnotations) > 0 {\n\t\tconf.AppendPodAnnotations(rt.overrideAnnotations)\n\t\treport.Annotated = true\n\t}\n\n\tif ok, _ := report.Injectable(); !ok {\n\t\tif errs := report.ThrowInjectError(); len(errs) > 0 {\n\t\t\treturn bytes, []inject.Report{*report}, fmt.Errorf(\"failed to inject %s%s%s: %w\", report.Kind, slash, report.Name, concatErrors(errs, \", \"))\n\t\t}\n\t\treturn bytes, []inject.Report{*report}, nil\n\t}\n\n\tif rt.injectProxy {\n\t\t// delete the inject annotation if present as its not needed in the manual case\n\t\t// prevents injector from taking a different code path in the ingress mode\n\t\tdelete(rt.overrideAnnotations, k8s.ProxyInjectAnnotation)\n\t\tconf.AppendPodAnnotation(k8s.CreatedByAnnotation, k8s.CreatedByAnnotationValue())\n\t} else if !rt.values.Proxy.IsIngress { // Add enabled annotation only if its not ingress mode to prevent overriding the annotation\n\t\t// flag the auto-injector to inject the proxy, regardless of the namespace annotation\n\t\tconf.AppendPodAnnotation(k8s.ProxyInjectAnnotation, k8s.ProxyInjectEnabled)\n\t}\n\n\tpatchJSON, err := conf.GetPodPatch(rt.injectProxy, rt.overrider)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif len(patchJSON) == 0 {\n\t\treturn bytes, []inject.Report{*report}, nil\n\t}\n\tlog.Infof(\"patch generated for: %s\", report.ResName())\n\tlog.Debugf(\"patch: %s\", patchJSON)\n\tpatch, err := jsonpatch.DecodePatch(patchJSON)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\torigJSON, err := yaml.YAMLToJSON(bytes)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tinjectedJSON, err := patch.Apply(origJSON)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tinjectedYAML, err := conf.JSONToYAML(injectedJSON)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn injectedYAML, []inject.Report{*report}, nil\n}\n\nfunc (resourceTransformerInject) generateReport(reports []inject.Report, output io.Writer) {\n\tinjected := []inject.Report{}\n\tannotatable := false\n\thostNetwork := []string{}\n\tsidecar := []string{}\n\tudp := []string{}\n\tinjectDisabled := []string{}\n\tautomountServiceAccountTokenFalse := []string{}\n\twarningsPrinted := verbose\n\n\tfor _, r := range reports {\n\t\tif b, _ := r.Injectable(); b {\n\t\t\tinjected = append(injected, r)\n\t\t}\n\n\t\tif r.IsAnnotatable() {\n\t\t\tannotatable = true\n\t\t}\n\n\t\tif r.HostNetwork {\n\t\t\thostNetwork = append(hostNetwork, r.ResName())\n\t\t\twarningsPrinted = true\n\t\t}\n\n\t\tif r.Sidecar {\n\t\t\tsidecar = append(sidecar, r.ResName())\n\t\t\twarningsPrinted = true\n\t\t}\n\n\t\tif r.UDP {\n\t\t\tudp = append(udp, r.ResName())\n\t\t\twarningsPrinted = true\n\t\t}\n\n\t\tif r.InjectDisabled {\n\t\t\tinjectDisabled = append(injectDisabled, r.ResName())\n\t\t\twarningsPrinted = true\n\t\t}\n\n\t\tif !r.AutomountServiceAccountToken {\n\t\t\tautomountServiceAccountTokenFalse = append(automountServiceAccountTokenFalse, r.ResName())\n\t\t\twarningsPrinted = true\n\t\t}\n\t}\n\n\t//\n\t// Warnings\n\t//\n\n\t// Leading newline to separate from yaml output on stdout\n\toutput.Write([]byte(\"\\n\"))\n\n\tif len(hostNetwork) > 0 {\n\t\toutput.Write([]byte(fmt.Sprintf(\"%s \\\"hostNetwork: true\\\" detected in %s\\n\", warnStatus, strings.Join(hostNetwork, \", \"))))\n\t} else if verbose {\n\t\toutput.Write([]byte(fmt.Sprintf(\"%s %s\\n\", okStatus, hostNetworkDesc)))\n\t}\n\n\tif len(sidecar) > 0 {\n\t\toutput.Write([]byte(fmt.Sprintf(\"%s known 3rd party sidecar detected in %s\\n\", warnStatus, strings.Join(sidecar, \", \"))))\n\t} else if verbose {\n\t\toutput.Write([]byte(fmt.Sprintf(\"%s %s\\n\", okStatus, sidecarDesc)))\n\t}\n\n\tif len(injectDisabled) > 0 {\n\t\toutput.Write([]byte(fmt.Sprintf(\"%s \\\"%s: %s\\\" annotation set on %s\\n\",\n\t\t\twarnStatus, k8s.ProxyInjectAnnotation, k8s.ProxyInjectDisabled, strings.Join(injectDisabled, \", \"))))\n\t} else if verbose {\n\t\toutput.Write([]byte(fmt.Sprintf(\"%s %s\\n\", okStatus, injectDisabledDesc)))\n\t}\n\n\tif len(injected) == 0 && !annotatable {\n\t\toutput.Write([]byte(fmt.Sprintf(\"%s no supported objects found\\n\", warnStatus)))\n\t\twarningsPrinted = true\n\t} else if verbose {\n\t\toutput.Write([]byte(fmt.Sprintf(\"%s %s\\n\", okStatus, unsupportedDesc)))\n\t}\n\n\tif len(udp) > 0 {\n\t\tverb := \"uses\"\n\t\tif len(udp) > 1 {\n\t\t\tverb = \"use\"\n\t\t}\n\t\toutput.Write([]byte(fmt.Sprintf(\"%s %s %s \\\"protocol: UDP\\\"\\n\", warnStatus, strings.Join(udp, \", \"), verb)))\n\t} else if verbose {\n\t\toutput.Write([]byte(fmt.Sprintf(\"%s %s\\n\", okStatus, udpDesc)))\n\t}\n\n\tif len(automountServiceAccountTokenFalse) == 0 && verbose {\n\t\toutput.Write([]byte(fmt.Sprintf(\"%s %s\\n\", okStatus, automountServiceAccountTokenDesc)))\n\t}\n\n\t//\n\t// Summary\n\t//\n\tif warningsPrinted {\n\t\toutput.Write([]byte(\"\\n\"))\n\t}\n\n\tfor _, r := range reports {\n\t\tok, _ := r.Injectable()\n\t\tif ok {\n\t\t\toutput.Write([]byte(fmt.Sprintf(\"%s \\\"%s\\\" injected\\n\", r.Kind, r.Name)))\n\t\t}\n\t\tif !ok && !r.Annotated {\n\t\t\tif r.Kind != \"\" {\n\t\t\t\toutput.Write([]byte(fmt.Sprintf(\"%s \\\"%s\\\" skipped\\n\", r.Kind, r.Name)))\n\t\t\t} else {\n\t\t\t\toutput.Write([]byte(fmt.Sprintln(\"document missing \\\"kind\\\" field, skipped\")))\n\t\t\t}\n\t\t}\n\t\tif !ok && r.Annotated {\n\t\t\toutput.Write([]byte(fmt.Sprintf(\"%s \\\"%s\\\" annotated\\n\", r.Kind, r.Name)))\n\t\t}\n\t}\n\n\t// Trailing newline to separate from kubectl output if piping\n\toutput.Write([]byte(\"\\n\"))\n}\n\nfunc fetchConfigs(ctx context.Context) (*linkerd2.Values, error) {\n\n\thc := healthcheck.NewWithCoreChecks(&healthcheck.Options{\n\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\tKubeConfig:            kubeconfigPath,\n\t\tImpersonate:           impersonate,\n\t\tImpersonateGroup:      impersonateGroup,\n\t\tKubeContext:           kubeContext,\n\t\tAPIAddr:               apiAddr,\n\t\tRetryDeadline:         time.Time{},\n\t})\n\thc.RunWithExitOnError()\n\n\tapi, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Get the New Linkerd Configuration\n\t_, values, err := healthcheck.FetchCurrentConfiguration(ctx, api, controlPlaneNamespace)\n\treturn values, err\n}\n\n// getOverrideAnnotations uses command-line overrides to update the provided configs.\n// the overrideAnnotations map keeps track of which configs are overridden, by\n// storing the corresponding annotations and values.\nfunc getOverrideAnnotations(values *linkerd2.Values, base *linkerd2.Values) map[string]string {\n\toverrideAnnotations := make(map[string]string)\n\n\tproxy := values.Proxy\n\tbaseProxy := base.Proxy\n\tif proxy.Image.Version != baseProxy.Image.Version {\n\t\toverrideAnnotations[k8s.ProxyVersionOverrideAnnotation] = proxy.Image.Version\n\t}\n\n\tif values.ProxyInit.IgnoreInboundPorts != base.ProxyInit.IgnoreInboundPorts {\n\t\toverrideAnnotations[k8s.ProxyIgnoreInboundPortsAnnotation] = values.ProxyInit.IgnoreInboundPorts\n\t}\n\tif values.ProxyInit.IgnoreOutboundPorts != base.ProxyInit.IgnoreOutboundPorts {\n\t\toverrideAnnotations[k8s.ProxyIgnoreOutboundPortsAnnotation] = values.ProxyInit.IgnoreOutboundPorts\n\t}\n\n\tif proxy.Ports.Admin != baseProxy.Ports.Admin {\n\t\toverrideAnnotations[k8s.ProxyAdminPortAnnotation] = fmt.Sprintf(\"%d\", proxy.Ports.Admin)\n\t}\n\tif proxy.Ports.Control != baseProxy.Ports.Control {\n\t\toverrideAnnotations[k8s.ProxyControlPortAnnotation] = fmt.Sprintf(\"%d\", proxy.Ports.Control)\n\t}\n\tif proxy.Ports.Inbound != baseProxy.Ports.Inbound {\n\t\toverrideAnnotations[k8s.ProxyInboundPortAnnotation] = fmt.Sprintf(\"%d\", proxy.Ports.Inbound)\n\t}\n\tif proxy.Ports.Outbound != baseProxy.Ports.Outbound {\n\t\toverrideAnnotations[k8s.ProxyOutboundPortAnnotation] = fmt.Sprintf(\"%d\", proxy.Ports.Outbound)\n\t}\n\tif proxy.OpaquePorts != baseProxy.OpaquePorts {\n\t\toverrideAnnotations[k8s.ProxyOpaquePortsAnnotation] = proxy.OpaquePorts\n\t}\n\n\tif proxy.Image.Name != baseProxy.Image.Name {\n\t\toverrideAnnotations[k8s.ProxyImageAnnotation] = proxy.Image.Name\n\t}\n\tif values.DebugContainer.Image.Name != base.DebugContainer.Image.Name {\n\t\toverrideAnnotations[k8s.DebugImageAnnotation] = values.DebugContainer.Image.Name\n\t}\n\n\tif values.DebugContainer.Image.Version != base.DebugContainer.Image.Version {\n\t\toverrideAnnotations[k8s.DebugImageVersionAnnotation] = values.DebugContainer.Image.Version\n\t}\n\n\tif proxy.Image.PullPolicy != baseProxy.Image.PullPolicy {\n\t\toverrideAnnotations[k8s.ProxyImagePullPolicyAnnotation] = proxy.Image.PullPolicy\n\t}\n\n\tif proxy.UID != baseProxy.UID {\n\t\toverrideAnnotations[k8s.ProxyUIDAnnotation] = strconv.FormatInt(proxy.UID, 10)\n\t}\n\n\tif proxy.GID >= 0 && (baseProxy.GID < 0 || proxy.GID != baseProxy.GID) {\n\t\toverrideAnnotations[k8s.ProxyGIDAnnotation] = strconv.FormatInt(proxy.GID, 10)\n\t}\n\n\tif proxy.LogLevel != baseProxy.LogLevel {\n\t\toverrideAnnotations[k8s.ProxyLogLevelAnnotation] = proxy.LogLevel\n\t}\n\n\tif proxy.LogFormat != baseProxy.LogFormat {\n\t\toverrideAnnotations[k8s.ProxyLogFormatAnnotation] = proxy.LogFormat\n\t}\n\n\tif proxy.RequireIdentityOnInboundPorts != baseProxy.RequireIdentityOnInboundPorts {\n\t\toverrideAnnotations[k8s.ProxyRequireIdentityOnInboundPortsAnnotation] = proxy.RequireIdentityOnInboundPorts\n\t}\n\n\tif proxy.EnableExternalProfiles != baseProxy.EnableExternalProfiles {\n\t\toverrideAnnotations[k8s.ProxyEnableExternalProfilesAnnotation] = strconv.FormatBool(proxy.EnableExternalProfiles)\n\t}\n\n\tif proxy.Metrics.HostnameLabels != baseProxy.Metrics.HostnameLabels {\n\t\toverrideAnnotations[k8s.ProxyEnableHostnameLabels] = strconv.FormatBool(proxy.Metrics.HostnameLabels)\n\t}\n\n\tif proxy.IsIngress != baseProxy.IsIngress {\n\t\toverrideAnnotations[k8s.ProxyInjectAnnotation] = k8s.ProxyInjectIngress\n\t}\n\n\tif proxy.Resources.CPU.Request != baseProxy.Resources.CPU.Request {\n\t\toverrideAnnotations[k8s.ProxyCPURequestAnnotation] = proxy.Resources.CPU.Request\n\t}\n\tif proxy.Resources.CPU.Limit != baseProxy.Resources.CPU.Limit {\n\t\toverrideAnnotations[k8s.ProxyCPULimitAnnotation] = proxy.Resources.CPU.Limit\n\t}\n\tif proxy.Resources.Memory.Request != baseProxy.Resources.Memory.Request {\n\t\toverrideAnnotations[k8s.ProxyMemoryRequestAnnotation] = proxy.Resources.Memory.Request\n\t}\n\tif proxy.Resources.Memory.Limit != baseProxy.Resources.Memory.Limit {\n\t\toverrideAnnotations[k8s.ProxyMemoryLimitAnnotation] = proxy.Resources.Memory.Limit\n\t}\n\tif proxy.WaitBeforeExitSeconds != baseProxy.WaitBeforeExitSeconds {\n\t\toverrideAnnotations[k8s.ProxyWaitBeforeExitSecondsAnnotation] = uintToString(proxy.WaitBeforeExitSeconds)\n\t}\n\n\tif proxy.Await != baseProxy.Await {\n\t\tif proxy.Await {\n\t\t\toverrideAnnotations[k8s.ProxyAwait] = k8s.Enabled\n\t\t} else {\n\t\t\toverrideAnnotations[k8s.ProxyAwait] = k8s.Disabled\n\t\t}\n\t}\n\n\tif proxy.DefaultInboundPolicy != baseProxy.DefaultInboundPolicy {\n\t\toverrideAnnotations[k8s.ProxyDefaultInboundPolicyAnnotation] = proxy.DefaultInboundPolicy\n\t}\n\n\tif proxy.AccessLog != baseProxy.AccessLog {\n\t\toverrideAnnotations[k8s.ProxyAccessLogAnnotation] = proxy.AccessLog\n\t}\n\n\tif proxy.ShutdownGracePeriod != baseProxy.ShutdownGracePeriod {\n\t\toverrideAnnotations[k8s.ProxyShutdownGracePeriodAnnotation] = proxy.ShutdownGracePeriod\n\t}\n\n\tif proxy.NativeSidecar != baseProxy.NativeSidecar {\n\t\toverrideAnnotations[k8s.ProxyEnableNativeSidecarAnnotationBeta] = strconv.FormatBool(proxy.NativeSidecar)\n\t}\n\n\treturn overrideAnnotations\n}\n\nfunc uintToString(v uint64) string {\n\treturn strconv.FormatUint(v, 10)\n}\n"
  },
  {
    "path": "cli/cmd/inject_test.go",
    "content": "package cmd\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/inject\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\ntype testCase struct {\n\tinputFileName          string\n\tgoldenFileName         string\n\treportFileName         string\n\tinjectProxy            bool\n\ttestInjectConfig       *linkerd2.Values\n\tenableDebugSidecarFlag bool\n}\n\nfunc mkFilename(filename string, verbose bool) string {\n\tif verbose {\n\t\treturn fmt.Sprintf(\"%s.verbose\", filename)\n\t}\n\treturn filename\n}\n\nfunc testUninjectAndInject(t *testing.T, tc testCase) {\n\tt.Helper()\n\n\tfile, err := os.Open(\"testdata/\" + tc.inputFileName)\n\tif err != nil {\n\t\tt.Errorf(\"error opening test input file: %v\\n\", err)\n\t}\n\n\tread := bufio.NewReader(file)\n\n\toutput := new(bytes.Buffer)\n\treport := new(bytes.Buffer)\n\ttransformer := &resourceTransformerInject{\n\t\tinjectProxy:         tc.injectProxy,\n\t\tvalues:              tc.testInjectConfig,\n\t\toverrideAnnotations: getOverrideAnnotations(tc.testInjectConfig, defaultConfig()),\n\t\tenableDebugSidecar:  tc.enableDebugSidecarFlag,\n\t\tallowNsInject:       true,\n\t\toverrider:           inject.GetOverriddenValues,\n\t}\n\n\tif exitCode := uninjectAndInject([]io.Reader{read}, report, output, transformer, \"yaml\"); exitCode != 0 {\n\t\tt.Errorf(\"Unexpected error injecting YAML: %v\", report)\n\t}\n\tif err := testDataDiffer.DiffTestYAML(tc.goldenFileName, output.String()); err != nil {\n\t\tt.Error(err)\n\t}\n\n\treportFileName := mkFilename(tc.reportFileName, verbose)\n\ttestDataDiffer.DiffTestdata(t, reportFileName, report.String())\n}\n\nfunc defaultConfig() *linkerd2.Values {\n\tdefaultConfig, err := testInstallValues()\n\tif err != nil {\n\t\tlog.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tdefaultConfig.LinkerdVersion = \"test-inject-control-plane-version\"\n\tdefaultConfig.Proxy.Image.Version = \"test-inject-proxy-version\"\n\tdefaultConfig.DebugContainer.Image.Version = \"test-inject-debug-version\"\n\n\treturn defaultConfig\n}\n\nfunc TestUninjectAndInject(t *testing.T) {\n\tdefaultValues := defaultConfig()\n\n\toverrideConfig := defaultConfig()\n\toverrideConfig.Proxy.Image.Version = \"override\"\n\n\tproxyResourceConfig := defaultConfig()\n\tproxyResourceConfig.Proxy.Resources = &linkerd2.Resources{\n\t\tCPU: linkerd2.Constraints{\n\t\t\tRequest: \"110m\",\n\t\t\tLimit:   \"160m\",\n\t\t},\n\t\tMemory: linkerd2.Constraints{\n\t\t\tRequest: \"100Mi\",\n\t\t\tLimit:   \"150Mi\",\n\t\t},\n\t}\n\n\tcniEnabledConfig := defaultConfig()\n\tcniEnabledConfig.CNIEnabled = true\n\n\topaquePortsConfig := defaultConfig()\n\topaquePortsConfig.Proxy.OpaquePorts = \"3000,5000-6000,mysql\"\n\n\tingressConfig := defaultConfig()\n\tingressConfig.Proxy.IsIngress = true\n\n\tproxyIgnorePortsConfig := defaultConfig()\n\tproxyIgnorePortsConfig.ProxyInit.IgnoreInboundPorts = \"22,8100-8102\"\n\tproxyIgnorePortsConfig.ProxyInit.IgnoreOutboundPorts = \"5432\"\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_deployment.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_deployment.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_deployment.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: defaultValues,\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_deployment.input.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_deployment_overridden_noinject.golden.yml\",\n\t\t\treportFileName: \"inject_emojivoto_deployment.report\",\n\t\t\tinjectProxy:    false,\n\t\t\ttestInjectConfig: func() *linkerd2.Values {\n\t\t\t\tvalues := defaultConfig()\n\t\t\t\tvalues.Proxy.Ports.Admin = 1234\n\t\t\t\treturn values\n\t\t\t}(),\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_deployment.input.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_deployment_overridden.golden.yml\",\n\t\t\treportFileName: \"inject_emojivoto_deployment.report\",\n\t\t\tinjectProxy:    true,\n\t\t\ttestInjectConfig: func() *linkerd2.Values {\n\t\t\t\tvalues := defaultConfig()\n\t\t\t\tvalues.Proxy.Ports.Admin = 1234\n\t\t\t\treturn values\n\t\t\t}(),\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_deployment.input.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_deployment_access_log.golden.yml\",\n\t\t\treportFileName: \"inject_emojivoto_deployment.report\",\n\t\t\tinjectProxy:    true,\n\t\t\ttestInjectConfig: func() *linkerd2.Values {\n\t\t\t\tvalues := defaultConfig()\n\t\t\t\tvalues.Proxy.AccessLog = \"apache\"\n\t\t\t\treturn values\n\t\t\t}(),\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_list.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_list.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_list.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: defaultValues,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_deployment_hostNetwork_false.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_deployment_hostNetwork_false.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_deployment_hostNetwork_false.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: defaultValues,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_deployment_capabilities.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_deployment_capabilities.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_deployment.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: defaultValues,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_deployment_injectDisabled.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_deployment_injectDisabled.input.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_deployment_injectDisabled.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: defaultValues,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_deployment_controller_name.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_deployment_controller_name.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_deployment_controller_name.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: defaultValues,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_statefulset.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_statefulset.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_statefulset.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: defaultValues,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_cronjob.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_cronjob.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_cronjob.report\",\n\t\t\tinjectProxy:      false,\n\t\t\ttestInjectConfig: defaultValues,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_cronjob_nometa.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_cronjob_nometa.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_cronjob.report\",\n\t\t\tinjectProxy:      false,\n\t\t\ttestInjectConfig: defaultValues,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_pod.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_pod.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_pod.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: defaultValues,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_pod_with_requests.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_pod_with_requests.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_pod_with_requests.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: proxyResourceConfig,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_deployment_udp.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_deployment_udp.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_deployment_udp.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: defaultValues,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_already_injected.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_already_injected.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_already_injected.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: defaultValues,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_contour.input.yml\",\n\t\t\tgoldenFileName:   \"inject_contour.golden.yml\",\n\t\t\treportFileName:   \"inject_contour.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: defaultValues,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_deployment_empty_resources.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_deployment_empty_resources.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_deployment_empty_resources.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: defaultValues,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_list_empty_resources.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_list_empty_resources.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_list_empty_resources.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: defaultValues,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_deployment.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_deployment_no_init_container.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_deployment.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: cniEnabledConfig,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_deployment_config_overrides.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_deployment_config_overrides.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_deployment.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: overrideConfig,\n\t\t},\n\t\t{\n\t\t\tinputFileName:          \"inject_emojivoto_deployment.input.yml\",\n\t\t\tgoldenFileName:         \"inject_emojivoto_deployment_debug.golden.yml\",\n\t\t\treportFileName:         \"inject_emojivoto_deployment.report\",\n\t\t\tinjectProxy:            true,\n\t\t\ttestInjectConfig:       defaultValues,\n\t\t\tenableDebugSidecarFlag: true,\n\t\t},\n\t\t{\n\t\t\tinputFileName:          \"inject_tap_deployment.input.yml\",\n\t\t\tgoldenFileName:         \"inject_tap_deployment_debug.golden.yml\",\n\t\t\treportFileName:         \"inject_tap_deployment_debug.report\",\n\t\t\tinjectProxy:            true,\n\t\t\ttestInjectConfig:       defaultValues,\n\t\t\tenableDebugSidecarFlag: true,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_namespace_good.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_namespace_good.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_namespace_good.golden.report\",\n\t\t\tinjectProxy:      false,\n\t\t\ttestInjectConfig: defaultConfig(),\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_namespace_good.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_namespace_overidden_good.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_namespace_good.golden.report\",\n\t\t\tinjectProxy:      false,\n\t\t\ttestInjectConfig: defaultConfig(),\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_deployment.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_deployment_proxyignores.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_deployment.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: proxyIgnorePortsConfig,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_pod.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_pod_proxyignores.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_pod.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: proxyIgnorePortsConfig,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_deployment.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_deployment_opaque_ports.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_deployment_opaque_ports.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: opaquePortsConfig,\n\t\t},\n\t\t{\n\t\t\tinputFileName:    \"inject_emojivoto_pod.input.yml\",\n\t\t\tgoldenFileName:   \"inject_emojivoto_pod_ingress.golden.yml\",\n\t\t\treportFileName:   \"inject_emojivoto_pod_ingress.report\",\n\t\t\tinjectProxy:      true,\n\t\t\ttestInjectConfig: ingressConfig,\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_deployment.input.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_deployment_default_inbound_policy.golden.yml\",\n\t\t\treportFileName: \"inject_emojivoto_deployment_default_inbound_policy.golden.report\",\n\t\t\tinjectProxy:    false,\n\t\t\ttestInjectConfig: func() *linkerd2.Values {\n\t\t\t\tvalues := defaultConfig()\n\t\t\t\tvalues.Proxy.DefaultInboundPolicy = k8s.AllAuthenticated\n\t\t\t\treturn values\n\t\t\t}(),\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_pod.input.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_pod_default_inbound_policy.golden.yml\",\n\t\t\treportFileName: \"inject_emojivoto_pod_default_inbound_policy.golden.report\",\n\t\t\tinjectProxy:    false,\n\t\t\ttestInjectConfig: func() *linkerd2.Values {\n\t\t\t\tvalues := defaultConfig()\n\t\t\t\tvalues.Proxy.DefaultInboundPolicy = k8s.AllAuthenticated\n\t\t\t\treturn values\n\t\t\t}(),\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_deployment.input.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_deployment_native_sidecar.golden.yml\",\n\t\t\treportFileName: \"inject_emojivoto_deployment.report\",\n\t\t\tinjectProxy:    true,\n\t\t\ttestInjectConfig: func() *linkerd2.Values {\n\t\t\t\tvalues := defaultConfig()\n\t\t\t\tvalues.Proxy.NativeSidecar = true\n\t\t\t\treturn values\n\t\t\t}(),\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_deployment.input.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_deployment_params.golden.yml\",\n\t\t\treportFileName: \"inject_emojivoto_deployment.report\",\n\t\t\tinjectProxy:    true,\n\t\t\ttestInjectConfig: func() *linkerd2.Values {\n\t\t\t\tvalues := defaultConfig()\n\t\t\t\tvalues.Proxy.Inbound = linkerd2.ProxyParams{\n\t\t\t\t\t\"scope\": linkerd2.ProxyScopeParams{\n\t\t\t\t\t\t\"proto\": linkerd2.ProxyProtoParams{\n\t\t\t\t\t\t\t\"appleSauce\": \"valueA\",\n\t\t\t\t\t\t\t\"blueberry\":  3.14,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tvalues.Proxy.Outbound = linkerd2.ProxyParams{\n\t\t\t\t\t\"scope\": linkerd2.ProxyScopeParams{\n\t\t\t\t\t\t\"proto\": linkerd2.ProxyProtoParams{\n\t\t\t\t\t\t\t\"applesauce\": \"valueA\",\n\t\t\t\t\t\t\t\"blueBerry\":  true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\treturn values\n\t\t\t}(),\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tverbose = true\n\t\tt.Run(fmt.Sprintf(\"%d: %s --verbose\", i, tc.inputFileName), func(t *testing.T) {\n\t\t\ttestUninjectAndInject(t, tc)\n\t\t})\n\t\tverbose = false\n\t\tt.Run(fmt.Sprintf(\"%d: %s\", i, tc.inputFileName), func(t *testing.T) {\n\t\t\ttestUninjectAndInject(t, tc)\n\t\t})\n\t}\n}\n\ntype injectCmd struct {\n\tinputFileName        string\n\tstdErrGoldenFileName string\n\tstdOutGoldenFileName string\n\texitCode             int\n\tinjectProxy          bool\n\tvalues               *linkerd2.Values\n}\n\nfunc testInjectCmd(t *testing.T, tc injectCmd) {\n\tt.Helper()\n\n\ttestConfig := tc.values\n\tif testConfig == nil {\n\t\tvar err error\n\t\ttestConfig, err = testInstallValues()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\t}\n\ttestConfig.Proxy.Image.Version = \"testinjectversion\"\n\n\terrBuffer := &bytes.Buffer{}\n\toutBuffer := &bytes.Buffer{}\n\n\tin, err := os.Open(fmt.Sprintf(\"testdata/%s\", tc.inputFileName))\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\ttransformer := &resourceTransformerInject{\n\t\tinjectProxy: tc.injectProxy,\n\t\tvalues:      testConfig,\n\t\toverrider:   inject.GetOverriddenValues,\n\t}\n\texitCode := runInjectCmd([]io.Reader{in}, errBuffer, outBuffer, transformer, \"yaml\")\n\tif exitCode != tc.exitCode {\n\t\tt.Fatalf(\"Expected exit code to be %d but got: %d\", tc.exitCode, exitCode)\n\t}\n\tif tc.stdOutGoldenFileName != \"\" {\n\t\ttestDataDiffer.DiffTestdata(t, tc.stdOutGoldenFileName, outBuffer.String())\n\t} else if outBuffer.Len() != 0 {\n\t\tt.Fatalf(\"Expected no standard output, but got: %s\", outBuffer)\n\t}\n\n\tstdErrGoldenFileName := mkFilename(tc.stdErrGoldenFileName, verbose)\n\ttestDataDiffer.DiffTestdata(t, stdErrGoldenFileName, errBuffer.String())\n}\n\nfunc TestRunInjectCmd(t *testing.T) {\n\ttestCases := []injectCmd{\n\t\t{\n\t\t\tinputFileName:        \"inject_gettest_deployment.bad.input.yml\",\n\t\t\tstdErrGoldenFileName: \"inject_gettest_deployment.bad.golden\",\n\t\t\texitCode:             1,\n\t\t\tinjectProxy:          true,\n\t\t},\n\t\t{\n\t\t\tinputFileName:        \"inject_tap_deployment.input.yml\",\n\t\t\tstdErrGoldenFileName: \"inject_tap_deployment.bad.golden\",\n\t\t\texitCode:             1,\n\t\t\tinjectProxy:          false,\n\t\t},\n\t\t{\n\t\t\tinputFileName:        \"inject_gettest_deployment.good.input.yml\",\n\t\t\tstdOutGoldenFileName: \"inject_gettest_deployment.good.golden.yml\",\n\t\t\tstdErrGoldenFileName: \"inject_gettest_deployment.good.golden.stderr\",\n\t\t\texitCode:             0,\n\t\t\tinjectProxy:          true,\n\t\t},\n\t\t{\n\t\t\tinputFileName:        \"inject_emojivoto_deployment_automountServiceAccountToken_false.input.yml\",\n\t\t\tstdOutGoldenFileName: \"inject_emojivoto_deployment_automountServiceAccountToken_false.golden.yml\",\n\t\t\tstdErrGoldenFileName: \"inject_emojivoto_deployment_automountServiceAccountToken_false.golden.stderr\",\n\t\t\texitCode:             0,\n\t\t\tinjectProxy:          true,\n\t\t},\n\t\t{\n\t\t\tinputFileName:        \"inject_emojivoto_deployment_automountServiceAccountToken_false.input.yml\",\n\t\t\tstdOutGoldenFileName: \"inject_emojivoto_deployment_automountServiceAccountToken_false_volumeProjection_disabled.golden.yml\",\n\t\t\tstdErrGoldenFileName: \"inject_emojivoto_deployment_automountServiceAccountToken_false_volumeProjection_disabled.golden.stderr\",\n\t\t\texitCode:             1,\n\t\t\tinjectProxy:          false,\n\t\t\tvalues: func() *linkerd2.Values {\n\t\t\t\tvalues, _ := testInstallValues()\n\t\t\t\tvalues.Identity.ServiceAccountTokenProjection = false\n\t\t\t\treturn values\n\t\t\t}(),\n\t\t},\n\t\t{\n\t\t\tinputFileName:        \"inject_emojivoto_istio.input.yml\",\n\t\t\tstdOutGoldenFileName: \"inject_emojivoto_istio.golden.yml\",\n\t\t\tstdErrGoldenFileName: \"inject_emojivoto_istio.golden.stderr\",\n\t\t\texitCode:             1,\n\t\t\tinjectProxy:          true,\n\t\t},\n\t\t{\n\t\t\tinputFileName:        \"inject_emojivoto_deployment_hostNetwork_true.input.yml\",\n\t\t\tstdOutGoldenFileName: \"inject_emojivoto_deployment_hostNetwork_true.golden.yml\",\n\t\t\tstdErrGoldenFileName: \"inject_emojivoto_deployment_hostNetwork_true.golden.stderr\",\n\t\t\texitCode:             1,\n\t\t\tinjectProxy:          true,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tverbose = true\n\t\tt.Run(fmt.Sprintf(\"%d: %s --verbose\", i, tc.inputFileName), func(t *testing.T) {\n\t\t\ttestInjectCmd(t, tc)\n\t\t})\n\t\tverbose = false\n\t\tt.Run(fmt.Sprintf(\"%d: %s\", i, tc.inputFileName), func(t *testing.T) {\n\t\t\ttestInjectCmd(t, tc)\n\t\t})\n\t}\n}\n\ntype injectFilePath struct {\n\tresource     string\n\tresourceFile string\n\texpectedFile string\n\tstdErrFile   string\n}\n\nfunc testInjectFilePath(t *testing.T, tc injectFilePath) {\n\tin, err := read(\"testdata/\" + tc.resourceFile)\n\tif err != nil {\n\t\tt.Fatal(\"Unexpected error: \", err)\n\t}\n\n\terrBuf := &bytes.Buffer{}\n\tactual := &bytes.Buffer{}\n\tvalues, err := testInstallValues()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\ttransformer := &resourceTransformerInject{\n\t\tinjectProxy: true,\n\t\tvalues:      values,\n\t\toverrider:   inject.GetOverriddenValues,\n\t}\n\tif exitCode := runInjectCmd(in, errBuf, actual, transformer, \"yaml\"); exitCode != 0 {\n\t\tt.Fatal(\"Unexpected error. Exit code from runInjectCmd: \", exitCode)\n\t}\n\tif err := testDataDiffer.DiffTestYAML(tc.expectedFile, actual.String()); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tstdErrFile := mkFilename(tc.stdErrFile, verbose)\n\ttestDataDiffer.DiffTestdata(t, stdErrFile, errBuf.String())\n}\n\nfunc testReadFromFolder(t *testing.T, resourceFolder string, expectedFolder string) {\n\tin, err := read(\"testdata/\" + resourceFolder)\n\tif err != nil {\n\t\tt.Fatal(\"Unexpected error: \", err)\n\t}\n\n\tvalues, err := testInstallValues()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\terrBuf := &bytes.Buffer{}\n\tactual := &bytes.Buffer{}\n\ttransformer := &resourceTransformerInject{\n\t\tinjectProxy: true,\n\t\tvalues:      values,\n\t\toverrider:   inject.GetOverriddenValues,\n\t}\n\tif exitCode := runInjectCmd(in, errBuf, actual, transformer, \"yaml\"); exitCode != 0 {\n\t\tt.Fatal(\"Unexpected error. Exit code from runInjectCmd: \", exitCode)\n\t}\n\n\texpectedFile := filepath.Join(expectedFolder, \"injected_nginx_redis.yaml\")\n\tif err := testDataDiffer.DiffTestYAML(expectedFile, actual.String()); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tstdErrFileName := mkFilename(filepath.Join(expectedFolder, \"injected_nginx_redis.stderr\"), verbose)\n\ttestDataDiffer.DiffTestdata(t, stdErrFileName, errBuf.String())\n}\n\nfunc TestInjectFilePath(t *testing.T) {\n\tvar (\n\t\tresourceFolder = filepath.Join(\"inject-filepath\", \"resources\")\n\t\texpectedFolder = filepath.Join(\"inject-filepath\", \"expected\")\n\t)\n\n\tt.Run(\"read from files\", func(t *testing.T) {\n\t\ttestCases := []injectFilePath{\n\t\t\t{\n\t\t\t\tresource:     \"nginx\",\n\t\t\t\tresourceFile: filepath.Join(resourceFolder, \"nginx.yaml\"),\n\t\t\t\texpectedFile: filepath.Join(expectedFolder, \"injected_nginx.yaml\"),\n\t\t\t\tstdErrFile:   filepath.Join(expectedFolder, \"injected_nginx.stderr\"),\n\t\t\t},\n\t\t\t{\n\t\t\t\tresource:     \"redis\",\n\t\t\t\tresourceFile: filepath.Join(resourceFolder, \"db/redis.yaml\"),\n\t\t\t\texpectedFile: filepath.Join(expectedFolder, \"injected_redis.yaml\"),\n\t\t\t\tstdErrFile:   filepath.Join(expectedFolder, \"injected_redis.stderr\"),\n\t\t\t},\n\t\t}\n\n\t\tfor i, testCase := range testCases {\n\t\t\ttestCase := testCase // pin\n\t\t\tverbose = true\n\t\t\tt.Run(fmt.Sprintf(\"%d %s\", i, testCase.resource), func(t *testing.T) {\n\t\t\t\ttestInjectFilePath(t, testCase)\n\t\t\t})\n\t\t\tverbose = false\n\t\t\tt.Run(fmt.Sprintf(\"%d %s\", i, testCase.resource), func(t *testing.T) {\n\t\t\t\ttestInjectFilePath(t, testCase)\n\t\t\t})\n\t\t}\n\t})\n\n\tverbose = true\n\tt.Run(\"read from folder --verbose\", func(t *testing.T) {\n\t\ttestReadFromFolder(t, resourceFolder, expectedFolder)\n\t})\n\tverbose = false\n\tt.Run(\"read from folder --verbose\", func(t *testing.T) {\n\t\ttestReadFromFolder(t, resourceFolder, expectedFolder)\n\t})\n}\n\nfunc TestToURL(t *testing.T) {\n\t// if the string follows a URL pattern, true has to be returned\n\t// if not false is returned\n\n\ttests := map[string]bool{\n\t\t\"http://www.linkerd.io\":  true,\n\t\t\"https://www.linkerd.io\": true,\n\t\t\"www.linkerd.io/\":        false,\n\t\t\"~/foo/bar.yaml\":         false,\n\t\t\"./foo/bar.yaml\":         false,\n\t\t\"/foo/bar/baz.yml\":       false,\n\t\t\"../foo/bar/baz.yaml\":    false,\n\t\t\"https//\":                false,\n\t}\n\n\tfor url, expectedValue := range tests {\n\t\t_, ok := toURL(url)\n\t\tif ok != expectedValue {\n\t\t\tt.Errorf(\"Result mismatch for %s. expected %v, but got %v\", url, expectedValue, ok)\n\t\t}\n\t}\n\n}\n\nfunc TestWalk(t *testing.T) {\n\t// create two data files, one in the root folder and the other in a subfolder.\n\t// walk should be able to read the content of the two data files recursively.\n\tvar (\n\t\ttmpFolderRoot = \"linkerd-testdata\"\n\t\ttmpFolderData = filepath.Join(tmpFolderRoot, \"data\")\n\t)\n\n\tif err := os.MkdirAll(tmpFolderData, os.ModeDir|os.ModePerm); err != nil {\n\t\tt.Fatal(\"Unexpected error: \", err)\n\t}\n\tdefer func() {\n\t\terr := os.RemoveAll(tmpFolderRoot)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"failed to remove temp dir %q: %v\", tmpFolderRoot, err)\n\t\t}\n\t}()\n\n\tvar (\n\t\tdata  = []byte(testutil.ReadTestdata(\"inject_gettest_deployment.bad.input.yml\"))\n\t\tfile1 = filepath.Join(tmpFolderRoot, \"root.txt\")\n\t\tfile2 = filepath.Join(tmpFolderData, \"data.txt\")\n\t)\n\tif err := os.WriteFile(file1, data, 0600); err != nil {\n\t\tt.Fatal(\"Unexpected error: \", err)\n\t}\n\tif err := os.WriteFile(file2, data, 0600); err != nil {\n\t\tt.Fatal(\"Unexpected error: \", err)\n\t}\n\n\tactual, err := walk(tmpFolderRoot)\n\tif err != nil {\n\t\tt.Fatal(\"Unexpected error: \", err)\n\t}\n\n\tfor _, r := range actual {\n\t\tb := make([]byte, len(data))\n\t\tr.Read(b)\n\n\t\tif string(b) != string(data) {\n\t\t\tt.Errorf(\"Content mismatch. Expected %q, but got %q\", data, b)\n\t\t}\n\t}\n}\n\nfunc TestProxyConfigurationAnnotations(t *testing.T) {\n\tbaseValues, err := linkerd2.NewValues()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvalues, err := baseValues.DeepCopy()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvalues.ProxyInit.IgnoreInboundPorts = \"8500-8505\"\n\tvalues.ProxyInit.IgnoreOutboundPorts = \"3306\"\n\tvalues.Proxy.Ports.Admin = 1234\n\tvalues.Proxy.Ports.Control = 4191\n\tvalues.Proxy.Ports.Inbound = 4144\n\tvalues.Proxy.Ports.Outbound = 4141\n\tvalues.Proxy.UID = 999\n\tvalues.Proxy.GID = 999\n\tvalues.Proxy.LogLevel = \"debug\"\n\tvalues.Proxy.LogFormat = \"cool\"\n\tvalues.Proxy.EnableExternalProfiles = true\n\tvalues.Proxy.Resources.CPU.Request = \"10m\"\n\tvalues.Proxy.Resources.CPU.Limit = \"100m\"\n\tvalues.Proxy.Resources.Memory.Request = \"10Mi\"\n\tvalues.Proxy.Resources.Memory.Limit = \"50Mi\"\n\tvalues.Proxy.WaitBeforeExitSeconds = 10\n\tvalues.Proxy.Await = false\n\tvalues.Proxy.AccessLog = \"apache\"\n\tvalues.Proxy.ShutdownGracePeriod = \"60s\"\n\tvalues.Proxy.NativeSidecar = true\n\n\texpectedOverrides := map[string]string{\n\t\tk8s.ProxyIgnoreInboundPortsAnnotation:  \"8500-8505\",\n\t\tk8s.ProxyIgnoreOutboundPortsAnnotation: \"3306\",\n\t\tk8s.ProxyAdminPortAnnotation:           \"1234\",\n\t\tk8s.ProxyControlPortAnnotation:         \"4191\",\n\t\tk8s.ProxyInboundPortAnnotation:         \"4144\",\n\t\tk8s.ProxyOutboundPortAnnotation:        \"4141\",\n\t\tk8s.ProxyUIDAnnotation:                 \"999\",\n\t\tk8s.ProxyGIDAnnotation:                 \"999\",\n\t\tk8s.ProxyLogLevelAnnotation:            \"debug\",\n\t\tk8s.ProxyLogFormatAnnotation:           \"cool\",\n\n\t\tk8s.ProxyEnableExternalProfilesAnnotation:  \"true\",\n\t\tk8s.ProxyCPURequestAnnotation:              \"10m\",\n\t\tk8s.ProxyCPULimitAnnotation:                \"100m\",\n\t\tk8s.ProxyMemoryRequestAnnotation:           \"10Mi\",\n\t\tk8s.ProxyMemoryLimitAnnotation:             \"50Mi\",\n\t\tk8s.ProxyWaitBeforeExitSecondsAnnotation:   \"10\",\n\t\tk8s.ProxyAwait:                             \"disabled\",\n\t\tk8s.ProxyAccessLogAnnotation:               \"apache\",\n\t\tk8s.ProxyShutdownGracePeriodAnnotation:     \"60s\",\n\t\tk8s.ProxyEnableNativeSidecarAnnotationBeta: \"true\",\n\t}\n\n\toverrides := getOverrideAnnotations(values, baseValues)\n\n\tdiffOverrides(t, expectedOverrides, overrides)\n}\n\nfunc TestProxyImageAnnotations(t *testing.T) {\n\tbaseValues, err := linkerd2.NewValues()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvalues, err := baseValues.DeepCopy()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvalues.Proxy.Image = &linkerd2.Image{\n\t\tName:       \"my.registry/linkerd/proxy\",\n\t\tVersion:    \"test-proxy-version\",\n\t\tPullPolicy: \"Always\",\n\t}\n\n\texpectedOverrides := map[string]string{\n\t\tk8s.ProxyImageAnnotation:           \"my.registry/linkerd/proxy\",\n\t\tk8s.ProxyVersionOverrideAnnotation: \"test-proxy-version\",\n\t\tk8s.ProxyImagePullPolicyAnnotation: \"Always\",\n\t}\n\n\toverrides := getOverrideAnnotations(values, baseValues)\n\n\tdiffOverrides(t, expectedOverrides, overrides)\n}\n\nfunc TestNoAnnotations(t *testing.T) {\n\tbaseValues, err := linkerd2.NewValues()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvalues, err := baseValues.DeepCopy()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpectedOverrides := map[string]string{}\n\n\toverrides := getOverrideAnnotations(values, baseValues)\n\n\tdiffOverrides(t, expectedOverrides, overrides)\n}\n\nfunc TestOverwriteRegistry(t *testing.T) {\n\ttestCases := []struct {\n\t\timage    string\n\t\tregistry string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\timage:    \"cr.l5d.io/linkerd/image\",\n\t\t\tregistry: \"my.custom.registry\",\n\t\t\texpected: \"my.custom.registry/image\",\n\t\t},\n\t\t{\n\t\t\timage:    \"cr.l5d.io/linkerd/image\",\n\t\t\tregistry: \"my.custom.registry/\",\n\t\t\texpected: \"my.custom.registry/image\",\n\t\t},\n\t\t{\n\t\t\timage:    \"my.custom.registry/image\",\n\t\t\tregistry: \"my.custom.registry\",\n\t\t\texpected: \"my.custom.registry/image\",\n\t\t},\n\t\t{\n\t\t\timage:    \"my.custom.registry/image\",\n\t\t\tregistry: \"cr.l5d.io/linkerd\",\n\t\t\texpected: \"cr.l5d.io/linkerd/image\",\n\t\t},\n\t\t{\n\t\t\timage:    \"\",\n\t\t\tregistry: \"my.custom.registry\",\n\t\t\texpected: \"\",\n\t\t},\n\t\t{\n\t\t\timage:    \"cr.l5d.io/linkerd/image\",\n\t\t\tregistry: \"\",\n\t\t\texpected: \"image\",\n\t\t},\n\t\t{\n\t\t\timage:    \"image\",\n\t\t\tregistry: \"cr.l5d.io/linkerd\",\n\t\t\texpected: \"cr.l5d.io/linkerd/image\",\n\t\t},\n\t}\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\tactual := cmd.RegistryOverride(tc.image, tc.registry)\n\t\t\tif actual != tc.expected {\n\t\t\t\tt.Fatalf(\"expected %q, but got %q\", tc.expected, actual)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc diffOverrides(t *testing.T, expectedOverrides map[string]string, actualOverrides map[string]string) {\n\tif len(expectedOverrides) != len(actualOverrides) {\n\t\tt.Fatalf(\"expected annotations:\\n%s\\nbut received:\\n%s\", expectedOverrides, actualOverrides)\n\t}\n\tfor key, expected := range expectedOverrides {\n\t\tactual := actualOverrides[key]\n\t\tif actual != expected {\n\t\t\tt.Fatalf(\"expected annotation %q with %q, but got %q\", key, expected, actual)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cli/cmd/inject_util.go",
    "content": "package cmd\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/pkg/inject\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tyamlDecoder \"k8s.io/apimachinery/pkg/util/yaml\"\n\t\"sigs.k8s.io/yaml\"\n)\n\ntype resourceTransformer interface {\n\ttransform([]byte) ([]byte, []inject.Report, error)\n\tgenerateReport([]inject.Report, io.Writer)\n}\n\n// Returns the integer representation of os.Exit code; 0 on success and 1 on failure.\nfunc transformInput(inputs []io.Reader, errWriter, outWriter io.Writer, rt resourceTransformer, format string) int {\n\tpostInjectBuf := &bytes.Buffer{}\n\treportBuf := &bytes.Buffer{}\n\n\tfor _, input := range inputs {\n\t\terrs := processYAML(input, postInjectBuf, reportBuf, rt, format)\n\t\tif len(errs) > 0 {\n\t\t\tfmt.Fprintf(errWriter, \"Error transforming resources:\\n%v\", concatErrors(errs, \"\\n\"))\n\t\t\treturn 1\n\t\t}\n\n\t\t_, err := io.Copy(outWriter, postInjectBuf)\n\n\t\t// print error report after yaml output, for better visibility\n\t\tio.Copy(errWriter, reportBuf)\n\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(errWriter, \"Error printing YAML: %v\\n\", err)\n\t\t\treturn 1\n\t\t}\n\t}\n\treturn 0\n}\n\n// processYAML takes an input stream of YAML, outputting injected/uninjected YAML to out.\nfunc processYAML(in io.Reader, out io.Writer, report io.Writer, rt resourceTransformer, format string) []error {\n\treader := yamlDecoder.NewYAMLReader(bufio.NewReaderSize(in, 4096))\n\n\treports := []inject.Report{}\n\n\terrs := []error{}\n\n\t// Iterate over all YAML objects in the input\n\tfor {\n\t\t// Read a single YAML object\n\t\tbytes, err := reader.Read()\n\t\tif err != nil {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn []error{err}\n\t\t}\n\n\t\tvar result []byte\n\t\tvar irs []inject.Report\n\n\t\tisList, err := kindIsList(bytes)\n\t\tif err != nil {\n\t\t\treturn []error{err}\n\t\t}\n\t\tif isList {\n\t\t\tresult, irs, err = processList(bytes, rt)\n\t\t} else {\n\t\t\tresult, irs, err = rt.transform(bytes)\n\t\t}\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t\treports = append(reports, irs...)\n\n\t\t// If the format is set to json, we need to convert the yaml to json\n\t\tif format == jsonOutput {\n\t\t\tresult, err = yaml.YAMLToJSON(result)\n\t\t\tif err != nil {\n\t\t\t\terrs = append(errs, err)\n\t\t\t}\n\t\t} else if format == yamlOutput {\n\t\t\t// result is already in yaml format: noop.\n\t\t} else {\n\t\t\terrs = append(errs, fmt.Errorf(\"unsupported format %s\", format))\n\t\t}\n\n\t\tif len(errs) == 0 {\n\t\t\tout.Write(result)\n\t\t\tif format == yamlOutput {\n\t\t\t\tout.Write([]byte(\"---\\n\"))\n\t\t\t}\n\t\t\tif format == jsonOutput {\n\t\t\t\tout.Write([]byte(\"\\n\"))\n\t\t\t}\n\t\t}\n\t}\n\n\trt.generateReport(reports, report)\n\n\treturn errs\n}\n\nfunc kindIsList(bytes []byte) (bool, error) {\n\tvar meta metav1.TypeMeta\n\tif err := yaml.Unmarshal(bytes, &meta); err != nil {\n\t\treturn false, err\n\t}\n\treturn meta.Kind == \"List\", nil\n}\n\nfunc processList(bytes []byte, rt resourceTransformer) ([]byte, []inject.Report, error) {\n\tvar sourceList corev1.List\n\tif err := yaml.Unmarshal(bytes, &sourceList); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treports := []inject.Report{}\n\titems := []runtime.RawExtension{}\n\n\tfor _, item := range sourceList.Items {\n\t\tresult, irs, err := rt.transform(item.Raw)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\t// At this point, we have yaml. The kubernetes internal representation is\n\t\t// json. Because we're building a list from RawExtensions, the yaml needs\n\t\t// to be converted to json.\n\t\tinjected, err := yaml.YAMLToJSON(result)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\titems = append(items, runtime.RawExtension{Raw: injected})\n\t\treports = append(reports, irs...)\n\t}\n\n\tsourceList.Items = items\n\tresult, err := yaml.Marshal(sourceList)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn result, reports, nil\n}\n\n// Read all the resource files found in path into a slice of readers.\n// path can be either a file, directory or stdin.\nfunc read(path string) ([]io.Reader, error) {\n\tif path == \"-\" {\n\t\treturn []io.Reader{os.Stdin}, nil\n\t}\n\n\tif url, ok := toURL(path); ok {\n\t\tif strings.ToLower(url.Scheme) != \"https\" {\n\t\t\treturn nil, fmt.Errorf(\"only HTTPS URLs are allowed\")\n\t\t}\n\t\tresp, err := http.Get(url.String())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn nil, fmt.Errorf(\"unable to read URL %q, server reported %s, status code=%d\", path, resp.Status, resp.StatusCode)\n\t\t}\n\n\t\t// Save to a buffer, so that response can be closed here\n\t\tbuf := new(bytes.Buffer)\n\t\t_, err = buf.ReadFrom(resp.Body)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn []io.Reader{buf}, nil\n\t}\n\n\treturn walk(path)\n}\n\n// checks if the given string is a valid URL\nfunc toURL(path string) (*url.URL, bool) {\n\tu, err := url.ParseRequestURI(path)\n\tif err == nil && u.Host != \"\" && u.Scheme != \"\" {\n\t\treturn u, true\n\t}\n\n\treturn nil, false\n}\n\n// walk walks the file tree rooted at path. path may be a file or a directory.\n// Creates a reader for each file found.\nfunc walk(path string) ([]io.Reader, error) {\n\tp := filepath.Clean(path)\n\tstat, err := os.Stat(p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !stat.IsDir() {\n\t\tfile, err := os.Open(p)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn []io.Reader{file}, nil\n\t}\n\n\tvar in []io.Reader\n\twerr := filepath.Walk(p, func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif info.IsDir() {\n\t\t\treturn nil\n\t\t}\n\n\t\tfile, err := os.Open(filepath.Clean(path))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tin = append(in, file)\n\t\treturn nil\n\t})\n\n\tif werr != nil {\n\t\treturn nil, werr\n\t}\n\n\treturn in, nil\n}\n\n// a helper function to concatenate the items in a []error\n// into a single error\nfunc concatErrors(errs []error, delimiter string) error {\n\tmessage, errs := errs[0].Error(), errs[1:] // pop the first element of the errs\n\t// this is done so that the first error message is not prefixed by the delimiter\n\n\tfor _, err := range errs {\n\t\tmessage = fmt.Sprintf(\"%s%s%s\", message, delimiter, err.Error())\n\t}\n\treturn errors.New(message)\n}\n"
  },
  {
    "path": "cli/cmd/install-cni-plugin.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/charts\"\n\tchartspkg \"github.com/linkerd/linkerd2/pkg/charts\"\n\tcnicharts \"github.com/linkerd/linkerd2/pkg/charts/cni\"\n\t\"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\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/values\"\n)\n\nconst (\n\thelmCNIDefaultChartName = \"linkerd2-cni\"\n\thelmCNIDefaultChartDir  = \"linkerd2-cni\"\n)\n\ntype cniPluginImage struct {\n\tname       string\n\tversion    string\n\tpullPolicy interface{}\n}\n\ntype cniPluginOptions struct {\n\tlinkerdVersion      string\n\tdockerRegistry      string\n\tproxyControlPort    uint\n\tproxyAdminPort      uint\n\tinboundPort         uint\n\toutboundPort        uint\n\tignoreInboundPorts  []string\n\tignoreOutboundPorts []string\n\tportsToRedirect     []uint\n\tproxyUID            int64\n\tproxyGID            int64\n\timage               cniPluginImage\n\tlogLevel            string\n\tdestCNINetDir       string\n\tdestCNIBinDir       string\n\tuseWaitFlag         bool\n\tpriorityClassName   string\n}\n\nfunc (options *cniPluginOptions) validate() error {\n\tif !alphaNumDashDot.MatchString(options.linkerdVersion) {\n\t\treturn fmt.Errorf(\"%s is not a valid version\", options.linkerdVersion)\n\t}\n\n\tif !alphaNumDashDotSlashColon.MatchString(options.dockerRegistry) {\n\t\treturn fmt.Errorf(\"%s is not a valid Docker registry. The url can contain only letters, numbers, dash, dot, slash and colon\", options.dockerRegistry)\n\t}\n\n\tif _, err := log.ParseLevel(options.logLevel); err != nil {\n\t\treturn fmt.Errorf(\"--cni-log-level must be one of: panic, fatal, error, warn, info, debug, trace\")\n\t}\n\n\tif err := validateRangeSlice(options.ignoreInboundPorts); err != nil {\n\t\treturn err\n\t}\n\n\tif err := validateRangeSlice(options.ignoreOutboundPorts); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (options *cniPluginOptions) pluginImage() cnicharts.Image {\n\timage := cnicharts.Image{\n\t\tName:       options.image.name,\n\t\tVersion:    options.image.version,\n\t\tPullPolicy: options.image.pullPolicy,\n\t}\n\t// env var overrides CLI flag\n\tif override := os.Getenv(flags.EnvOverrideDockerRegistry); override != \"\" {\n\t\timage.Name = cmd.RegistryOverride(options.image.name, override)\n\t\treturn image\n\t}\n\tif options.dockerRegistry != cmd.DefaultDockerRegistry {\n\t\timage.Name = cmd.RegistryOverride(options.image.name, options.dockerRegistry)\n\t\treturn image\n\t}\n\treturn image\n}\n\nfunc newCmdInstallCNIPlugin() *cobra.Command {\n\toptions, err := newCNIInstallOptionsWithDefaults()\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\tvar valOpts values.Options\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"install-cni [flags]\",\n\t\tShort: \"Output Kubernetes configs to install Linkerd CNI\",\n\t\tLong: `Output Kubernetes configs to install Linkerd CNI.\n\nThis command installs a DaemonSet into the Linkerd control plane. The DaemonSet\ncopies the necessary linkerd-cni plugin binaries and configs onto the host. It\nassumes that the 'linkerd install' command will be executed with the\n'--linkerd-cni-enabled' flag. This command needs to be executed before the\n'linkerd install --linkerd-cni-enabled' command.\n\nThe installation can be configured by using the --set, --values, --set-string and --set-file flags. A full list of configurable values can be found at https://artifacthub.io/packages/helm/linkerd2/linkerd2-cni#values`,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn renderCNIPlugin(os.Stdout, valOpts, options)\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVarP(&options.linkerdVersion, \"linkerd-version\", \"v\", options.linkerdVersion, \"Tag to be used for Linkerd images\")\n\tcmd.PersistentFlags().StringVar(&options.dockerRegistry, \"registry\", options.dockerRegistry,\n\t\tfmt.Sprintf(\"Docker registry to pull images from ($%s)\", flags.EnvOverrideDockerRegistry))\n\tcmd.PersistentFlags().Int64Var(&options.proxyUID, \"proxy-uid\", options.proxyUID, \"Run the proxy under this user ID\")\n\tcmd.PersistentFlags().Int64Var(&options.proxyGID, \"proxy-gid\", options.proxyGID, \"Run the proxy under this group ID\")\n\tcmd.PersistentFlags().UintVar(&options.inboundPort, \"inbound-port\", options.inboundPort, \"Proxy port to use for inbound traffic\")\n\tcmd.PersistentFlags().UintVar(&options.outboundPort, \"outbound-port\", options.outboundPort, \"Proxy port to use for outbound traffic\")\n\tcmd.PersistentFlags().UintVar(&options.proxyControlPort, \"control-port\", options.proxyControlPort, \"Proxy port to use for control\")\n\tcmd.PersistentFlags().UintVar(&options.proxyAdminPort, \"admin-port\", options.proxyAdminPort, \"Proxy port to serve metrics on\")\n\tcmd.PersistentFlags().StringSliceVar(&options.ignoreInboundPorts, \"skip-inbound-ports\", options.ignoreInboundPorts, \"Ports and/or port ranges (inclusive) that should skip the proxy and send directly to the application\")\n\tcmd.PersistentFlags().StringSliceVar(&options.ignoreOutboundPorts, \"skip-outbound-ports\", options.ignoreOutboundPorts, \"Outbound ports and/or port ranges (inclusive) that should skip the proxy\")\n\tcmd.PersistentFlags().UintSliceVar(&options.portsToRedirect, \"redirect-ports\", options.portsToRedirect, \"Ports to redirect to proxy, if no port is specified then ALL ports are redirected\")\n\tcmd.PersistentFlags().StringVar(&options.image.name, \"cni-image\", options.image.name, \"Image for the cni-plugin\")\n\tcmd.PersistentFlags().StringVar(&options.image.version, \"cni-image-version\", options.image.version, \"Image Version for the cni-plugin\")\n\tcmd.PersistentFlags().StringVar(&options.logLevel, \"cni-log-level\", options.logLevel, \"Log level for the cni-plugin\")\n\tcmd.PersistentFlags().StringVar(&options.destCNINetDir, \"dest-cni-net-dir\", options.destCNINetDir, \"Directory on the host where the CNI configuration will be placed\")\n\tcmd.PersistentFlags().StringVar(&options.destCNIBinDir, \"dest-cni-bin-dir\", options.destCNIBinDir, \"Directory on the host where the CNI binary will be placed\")\n\tcmd.PersistentFlags().StringVar(&options.priorityClassName, \"priority-class-name\", options.priorityClassName, \"Pod priorityClassName for CNI daemonset's pods\")\n\tcmd.PersistentFlags().BoolVar(\n\t\t&options.useWaitFlag,\n\t\t\"use-wait-flag\",\n\t\toptions.useWaitFlag,\n\t\t\"Configures the CNI plugin to use the \\\"-w\\\" flag for the iptables command. (default false)\")\n\n\tflags.AddValueOptionsFlags(cmd.Flags(), &valOpts)\n\n\treturn cmd\n}\n\nfunc newCNIInstallOptionsWithDefaults() (*cniPluginOptions, error) {\n\tdefaults, err := cnicharts.NewValues()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcniPluginImage := cniPluginImage{\n\t\tname:    cmd.DefaultDockerRegistry + \"/cni-plugin\",\n\t\tversion: version.LinkerdCNIVersion,\n\t}\n\n\tcniOptions := cniPluginOptions{\n\t\tlinkerdVersion:      version.Version,\n\t\tdockerRegistry:      cmd.DefaultDockerRegistry,\n\t\tproxyControlPort:    4190,\n\t\tproxyAdminPort:      4191,\n\t\tinboundPort:         defaults.InboundProxyPort,\n\t\toutboundPort:        defaults.OutboundProxyPort,\n\t\tignoreInboundPorts:  nil,\n\t\tignoreOutboundPorts: nil,\n\t\tproxyUID:            defaults.ProxyUID,\n\t\tproxyGID:            defaults.ProxyGID,\n\t\timage:               cniPluginImage,\n\t\tlogLevel:            \"info\",\n\t\tdestCNINetDir:       defaults.DestCNINetDir,\n\t\tdestCNIBinDir:       defaults.DestCNIBinDir,\n\t\tuseWaitFlag:         defaults.UseWaitFlag,\n\t\tpriorityClassName:   defaults.PriorityClassName,\n\t}\n\n\tif defaults.IgnoreInboundPorts != \"\" {\n\t\tcniOptions.ignoreInboundPorts = strings.Split(defaults.IgnoreInboundPorts, \",\")\n\n\t}\n\tif defaults.IgnoreOutboundPorts != \"\" {\n\t\tcniOptions.ignoreOutboundPorts = strings.Split(defaults.IgnoreOutboundPorts, \",\")\n\t}\n\n\treturn &cniOptions, nil\n}\n\nfunc (options *cniPluginOptions) buildValues() (*cnicharts.Values, error) {\n\tinstallValues, err := cnicharts.NewValues()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tportsToRedirect := []string{}\n\tfor _, p := range options.portsToRedirect {\n\t\tportsToRedirect = append(portsToRedirect, fmt.Sprintf(\"%d\", p))\n\t}\n\n\tinstallValues.Image = options.pluginImage()\n\tinstallValues.LogLevel = options.logLevel\n\tinstallValues.InboundProxyPort = options.inboundPort\n\tinstallValues.OutboundProxyPort = options.outboundPort\n\tinstallValues.IgnoreInboundPorts = strings.Join(options.ignoreInboundPorts, \",\")\n\tinstallValues.IgnoreOutboundPorts = strings.Join(options.ignoreOutboundPorts, \",\")\n\tinstallValues.PortsToRedirect = strings.Join(portsToRedirect, \",\")\n\tinstallValues.ProxyUID = options.proxyUID\n\tinstallValues.ProxyGID = options.proxyGID\n\tinstallValues.DestCNINetDir = options.destCNINetDir\n\tinstallValues.DestCNIBinDir = options.destCNIBinDir\n\tinstallValues.UseWaitFlag = options.useWaitFlag\n\tinstallValues.PriorityClassName = options.priorityClassName\n\treturn installValues, nil\n}\n\nfunc renderCNIPlugin(w io.Writer, valOpts values.Options, config *cniPluginOptions) error {\n\n\tif err := config.validate(); err != nil {\n\t\treturn err\n\t}\n\n\tvaluesOverrides, err := valOpts.MergeValues(nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvalues, err := config.buildValues()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmapValues, err := values.ToMap()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvaluesWrapper := &chart.Chart{\n\t\tMetadata: &chart.Metadata{Name: \"\"},\n\t\tValues:   mapValues,\n\t}\n\tmergedValues, err := chartutil.CoalesceValues(valuesWrapper, valuesOverrides)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfiles := []*loader.BufferedFile{\n\t\t{Name: chartutil.ChartfileName},\n\t\t{Name: \"templates/cni-plugin.yaml\"},\n\t}\n\n\tch := &chartspkg.Chart{\n\t\tName:      helmCNIDefaultChartName,\n\t\tDir:       helmCNIDefaultChartDir,\n\t\tNamespace: defaultCNINamespace,\n\t\tValues:    mergedValues,\n\t\tFiles:     files,\n\t\tFs:        charts.Templates,\n\t}\n\n\tbuf, err := ch.RenderCNI()\n\tif err != nil {\n\t\treturn err\n\t}\n\tw.Write(buf.Bytes())\n\tw.Write([]byte(\"---\\n\"))\n\n\treturn nil\n}\n"
  },
  {
    "path": "cli/cmd/install-cni-plugin_test.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"helm.sh/helm/v3/pkg/cli/values\"\n)\n\nfunc TestRenderCNIPlugin(t *testing.T) {\n\tdefaultOptions, err := newCNIInstallOptionsWithDefaults()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error from newCNIInstallOptionsWithDefaults(): %v\", err)\n\t}\n\n\timage := cniPluginImage{\n\t\tname:       \"my-docker-registry.io/awesome/cni-plugin-test-image\",\n\t\tversion:    \"v1.4.0\",\n\t\tpullPolicy: nil,\n\t}\n\tfullyConfiguredOptions := &cniPluginOptions{\n\t\tlinkerdVersion:      \"awesome-linkerd-version.1\",\n\t\tdockerRegistry:      \"cr.l5d.io/linkerd\",\n\t\tproxyControlPort:    5190,\n\t\tproxyAdminPort:      5191,\n\t\tinboundPort:         5143,\n\t\toutboundPort:        5140,\n\t\tignoreInboundPorts:  make([]string, 0),\n\t\tignoreOutboundPorts: make([]string, 0),\n\t\tproxyUID:            12102,\n\t\tproxyGID:            12102,\n\t\timage:               image,\n\t\tlogLevel:            \"debug\",\n\t\tdestCNINetDir:       \"/etc/kubernetes/cni/net.d\",\n\t\tdestCNIBinDir:       \"/opt/my-cni/bin\",\n\t\tpriorityClassName:   \"system-node-critical\",\n\t}\n\n\tfullyConfiguredOptionsEqualDsts := &cniPluginOptions{\n\t\tlinkerdVersion:      \"awesome-linkerd-version.1\",\n\t\tdockerRegistry:      \"cr.l5d.io/linkerd\",\n\t\tproxyControlPort:    5190,\n\t\tproxyAdminPort:      5191,\n\t\tinboundPort:         5143,\n\t\toutboundPort:        5140,\n\t\tignoreInboundPorts:  make([]string, 0),\n\t\tignoreOutboundPorts: make([]string, 0),\n\t\tproxyUID:            12102,\n\t\tproxyGID:            12102,\n\t\timage:               image,\n\t\tlogLevel:            \"debug\",\n\t\tdestCNINetDir:       \"/etc/kubernetes/cni/net.d\",\n\t\tdestCNIBinDir:       \"/etc/kubernetes/cni/net.d\",\n\t\tpriorityClassName:   \"system-node-critical\",\n\t}\n\n\tfullyConfiguredOptionsNoNamespace := &cniPluginOptions{\n\t\tlinkerdVersion:      \"awesome-linkerd-version.1\",\n\t\tdockerRegistry:      \"cr.l5d.io/linkerd\",\n\t\tproxyControlPort:    5190,\n\t\tproxyAdminPort:      5191,\n\t\tinboundPort:         5143,\n\t\toutboundPort:        5140,\n\t\tignoreInboundPorts:  make([]string, 0),\n\t\tignoreOutboundPorts: make([]string, 0),\n\t\tproxyUID:            12102,\n\t\tproxyGID:            12102,\n\t\timage:               image,\n\t\tlogLevel:            \"debug\",\n\t\tdestCNINetDir:       \"/etc/kubernetes/cni/net.d\",\n\t\tdestCNIBinDir:       \"/opt/my-cni/bin\",\n\t\tpriorityClassName:   \"system-node-critical\",\n\t}\n\n\tdefaultOptionsWithSkipPorts, err := newCNIInstallOptionsWithDefaults()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error from newCNIInstallOptionsWithDefaults(): %v\", err)\n\t}\n\n\tdefaultOptionsWithSkipPorts.ignoreInboundPorts = append(defaultOptionsWithSkipPorts.ignoreInboundPorts, []string{\"80\", \"8080\"}...)\n\tdefaultOptionsWithSkipPorts.ignoreOutboundPorts = append(defaultOptionsWithSkipPorts.ignoreOutboundPorts, []string{\"443\", \"1000\"}...)\n\n\tvalOpts := values.Options{\n\t\tValues: []string{\"resources.cpu.limit=1m\"},\n\t}\n\n\ttestCases := []struct {\n\t\t*cniPluginOptions\n\t\tvalOpts        values.Options\n\t\tgoldenFileName string\n\t}{\n\t\t{defaultOptions, valOpts, \"install-cni-plugin_default.golden\"},\n\t\t{fullyConfiguredOptions, values.Options{}, \"install-cni-plugin_fully_configured.golden\"},\n\t\t{fullyConfiguredOptionsEqualDsts, values.Options{}, \"install-cni-plugin_fully_configured_equal_dsts.golden\"},\n\t\t{fullyConfiguredOptionsNoNamespace, values.Options{}, \"install-cni-plugin_fully_configured_no_namespace.golden\"},\n\t\t{defaultOptionsWithSkipPorts, values.Options{}, \"install-cni-plugin_skip_ports.golden\"},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d: %s\", i, tc.goldenFileName), func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\terr := renderCNIPlugin(&buf, tc.valOpts, tc.cniPluginOptions)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif err = testDataDiffer.DiffTestYAML(tc.goldenFileName, buf.String()); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cli/cmd/install.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path\"\n\t\"strconv\"\n\t\"strings\"\n\t\"text/template\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/charts\"\n\t\"github.com/linkerd/linkerd2/cli/flag\"\n\tchartspkg \"github.com/linkerd/linkerd2/pkg/charts\"\n\tl5dcharts \"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\tflagspkg \"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/tree\"\n\t\"github.com/spf13/cobra\"\n\t\"helm.sh/helm/v3/pkg/chart/loader\"\n\t\"helm.sh/helm/v3/pkg/chartutil\"\n\tvaluespkg \"helm.sh/helm/v3/pkg/cli/values\"\n\t\"helm.sh/helm/v3/pkg/engine\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nconst (\n\thelmDefaultChartNameCrds = \"linkerd-crds\"\n\thelmDefaultChartNameCP   = \"linkerd-control-plane\"\n\n\terrMsgCannotInitializeClient = `Unable to install the Linkerd control plane. Cannot connect to the Kubernetes cluster:\n\n%s\n\nYou can use the --ignore-cluster flag if you just want to generate the installation config.\n`\n\n\terrMsgLinkerdConfigResourceConflict = \"Can't install the Linkerd control plane in the '%s' namespace. Reason: %s.\\nRun the command `linkerd upgrade`, if you are looking to upgrade Linkerd.\\n\"\n)\n\nvar (\n\tTemplatesCrdFiles = []string{\n\t\t\"templates/policy/authorization-policy.yaml\",\n\t\t\"templates/policy/egress-network.yaml\",\n\t\t\"templates/policy/http-local-ratelimit-policy.yaml\",\n\t\t\"templates/policy/httproute.yaml\",\n\t\t\"templates/policy/meshtls-authentication.yaml\",\n\t\t\"templates/policy/network-authentication.yaml\",\n\t\t\"templates/policy/server-authorization.yaml\",\n\t\t\"templates/policy/server.yaml\",\n\t\t\"templates/serviceprofile.yaml\",\n\t\t\"templates/gateway.networking.k8s.io_httproutes.yaml\",\n\t\t\"templates/gateway.networking.k8s.io_grpcroutes.yaml\",\n\t\t\"templates/gateway.networking.k8s.io_tlsroutes.yaml\",\n\t\t\"templates/gateway.networking.k8s.io_tcproutes.yaml\",\n\t\t\"templates/workload/external-workload.yaml\",\n\t}\n\n\tTemplatesControlPlane = []string{\n\t\t\"templates/namespace.yaml\",\n\t\t\"templates/identity-rbac.yaml\",\n\t\t\"templates/destination-rbac.yaml\",\n\t\t\"templates/heartbeat-rbac.yaml\",\n\t\t\"templates/podmonitor.yaml\",\n\t\t\"templates/proxy-injector-rbac.yaml\",\n\t\t\"templates/psp.yaml\",\n\t\t\"templates/config.yaml\",\n\t\t\"templates/config-rbac.yaml\",\n\t\t\"templates/identity.yaml\",\n\t\t\"templates/destination.yaml\",\n\t\t\"templates/heartbeat.yaml\",\n\t\t\"templates/proxy-injector.yaml\",\n\t}\n\n\tignoreCluster bool\n)\n\n/* Commands */\nfunc newCmdInstall() *cobra.Command {\n\tvalues, err := l5dcharts.NewValues()\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\tos.Exit(1)\n\t}\n\tvar crds bool\n\tvar options valuespkg.Options\n\tvar output string\n\n\tinstallOnlyFlags, installOnlyFlagSet := makeInstallFlags(values)\n\tinstallUpgradeFlags, installUpgradeFlagSet, err := makeInstallUpgradeFlags(values)\n\tif err != nil {\n\t\tfmt.Fprint(os.Stderr, err.Error())\n\t\tos.Exit(1)\n\t}\n\tproxyFlags, proxyFlagSet := makeProxyFlags(values)\n\n\tflags := flattenFlags(installOnlyFlags, installUpgradeFlags, proxyFlags)\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"install [flags]\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Output Kubernetes configs to install Linkerd\",\n\t\tLong: `Output Kubernetes configs to install Linkerd.\n\nThis command provides all Kubernetes configs necessary to install the Linkerd\ncontrol plane.`,\n\t\tExample: `  # Install CRDs first.\n  linkerd install --crds | kubectl apply -f -\n\n  # Install the core control plane.\n  linkerd install | kubectl apply -f -\n\nThe installation can be configured by using the --set, --values, --set-string and --set-file flags.\nA full list of configurable values can be found at https://artifacthub.io/packages/helm/linkerd2/linkerd-control-plane#values`,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tvar k8sAPI *k8s.KubernetesAPI\n\t\t\tif !ignoreCluster {\n\t\t\t\t// Ensure k8s is reachable\n\t\t\t\tif err := errAfterRunningChecks(values.CNIEnabled); err != nil {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, errMsgCannotInitializeClient, err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure there is not already an existing Linkerd installation.\n\t\t\t\tk8sAPI, err = k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 30*time.Second)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tif !crds {\n\t\t\t\t\tcrds := bytes.Buffer{}\n\t\t\t\t\terr = renderCRDs(cmd.Context(), nil, &crds, valuespkg.Options{\n\t\t\t\t\t\t// GatewayAPI CRDs are optional so don't check for them.\n\t\t\t\t\t\tValues: []string{\n\t\t\t\t\t\t\t\"installGatewayAPI=false\",\n\t\t\t\t\t\t},\n\t\t\t\t\t}, \"yaml\")\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Fprintln(os.Stderr, err)\n\t\t\t\t\t\tos.Exit(1)\n\t\t\t\t\t}\n\t\t\t\t\terr = healthcheck.CheckCustomResourceDefinitions(cmd.Context(), k8sAPI, crds.String())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Fprintln(os.Stderr, \"Linkerd CRDs must be installed first. Run linkerd install with the --crds flag.\")\n\t\t\t\t\t\tos.Exit(1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif crds {\n\t\t\t\tif err = installCRDs(cmd.Context(), k8sAPI, os.Stdout, options, output); err != nil {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"%s\\n\", err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\n\t\t\t\tfmt.Fprintln(os.Stderr, \"Rendering Linkerd CRDs...\")\n\t\t\t\tfmt.Fprintln(os.Stderr, \"Next, run `linkerd install | kubectl apply -f -` to install the control plane.\")\n\t\t\t\tfmt.Fprintln(os.Stderr)\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\treturn installControlPlane(cmd.Context(), k8sAPI, os.Stdout, values, flags, options, output)\n\t\t},\n\t}\n\n\tcmd.Flags().AddFlagSet(installOnlyFlagSet)\n\tcmd.Flags().AddFlagSet(installUpgradeFlagSet)\n\tcmd.Flags().AddFlagSet(proxyFlagSet)\n\tcmd.Flags().BoolVar(&crds, \"crds\", false, \"Install Linkerd CRDs\")\n\tcmd.PersistentFlags().BoolVar(&ignoreCluster, \"ignore-cluster\", false,\n\t\t\"Ignore the current Kubernetes cluster when checking for existing cluster configuration (default false)\")\n\tcmd.PersistentFlags().StringVarP(&output, \"output\", \"o\", \"yaml\", \"Output format. One of: json|yaml\")\n\n\tflagspkg.AddValueOptionsFlags(cmd.Flags(), &options)\n\n\treturn cmd\n}\n\nfunc checkNoConfig(ctx context.Context, k8sAPI *k8s.KubernetesAPI) error {\n\tif k8sAPI == nil {\n\t\t// When `ingoreCluster` is set, there is no k8sAPI.\n\t\treturn nil\n\t}\n\n\t// We just want to check if `linkerd-configmap` exists\n\t_, err := k8sAPI.CoreV1().ConfigMaps(controlPlaneNamespace).Get(ctx, k8s.ConfigConfigMapName, metav1.GetOptions{})\n\tif err == nil {\n\t\tfmt.Fprintf(os.Stderr, errMsgLinkerdConfigResourceConflict, controlPlaneNamespace, \"ConfigMap/linkerd-config already exists\")\n\t\tos.Exit(1)\n\t}\n\tif !kerrors.IsNotFound(err) {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\ntype GatewayAPICRDs int\n\nconst (\n\tAbsent GatewayAPICRDs = iota\n\tLinkerd\n\tExternal\n)\n\n// checkGatewayAPICRDs returns true if the Gateway API CRDs are installed in the\n// cluster, and false otherwise.\nfunc checkGatewayAPICRDs(ctx context.Context, k8sAPI *k8s.KubernetesAPI) (GatewayAPICRDs, error) {\n\tcrds := k8sAPI.Apiextensions.ApiextensionsV1().CustomResourceDefinitions()\n\tresult := Absent\n\tnames := []string{\n\t\t\"httproutes.gateway.networking.k8s.io\",\n\t\t\"grpcroutes.gateway.networking.k8s.io\",\n\t}\n\tfor _, name := range names {\n\t\tcrd, err := crds.Get(ctx, name, metav1.GetOptions{})\n\t\tif err == nil && crd != nil {\n\t\t\tif crd.Annotations[k8s.CreatedByAnnotation] != \"\" {\n\t\t\t\treturn Linkerd, nil\n\t\t\t}\n\t\t\tresult = External\n\t\t\tif !crdIncludesV1(crd) {\n\t\t\t\treturn result, fmt.Errorf(\"the %s CRD is missing the v1 version, please upgrade to Gateway API v1.1.1 or later\", name)\n\t\t\t}\n\t\t} else if kerrors.IsNotFound(err) {\n\t\t\t// No action if CRD is not found.\n\t\t} else {\n\t\t\treturn Absent, err\n\t\t}\n\t}\n\treturn result, nil\n}\n\nfunc crdIncludesV1(crd *v1.CustomResourceDefinition) bool {\n\tfor _, version := range crd.Spec.Versions {\n\t\tif version.Name == \"v1\" {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc installCRDs(ctx context.Context, k8sAPI *k8s.KubernetesAPI, w io.Writer, options valuespkg.Options, format string) error {\n\tif err := checkNoConfig(ctx, k8sAPI); err != nil {\n\t\treturn err\n\t}\n\n\treturn renderCRDs(ctx, k8sAPI, w, options, format)\n}\n\nfunc installControlPlane(ctx context.Context, k8sAPI *k8s.KubernetesAPI, w io.Writer, values *l5dcharts.Values, flags []flag.Flag, options valuespkg.Options, format string) error {\n\terr := flag.ApplySetFlags(values, flags)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := checkNoConfig(ctx, k8sAPI); err != nil {\n\t\treturn err\n\t}\n\n\t// Create values override\n\tvaluesOverrides, err := options.MergeValues(nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif k8sAPI != nil {\n\t\t// We just want to check if `linkerd-configmap` exists\n\t\t_, err = k8sAPI.CoreV1().ConfigMaps(controlPlaneNamespace).Get(ctx, k8s.ConfigConfigMapName, metav1.GetOptions{})\n\t\tif err == nil {\n\t\t\tfmt.Fprintf(os.Stderr, errMsgLinkerdConfigResourceConflict, controlPlaneNamespace, \"ConfigMap/linkerd-config already exists\")\n\t\t\tos.Exit(1)\n\t\t}\n\t\tif !kerrors.IsNotFound(err) {\n\t\t\treturn err\n\t\t}\n\n\t\tif !isRunAsRoot(valuesOverrides) {\n\t\t\terr = healthcheck.CheckNodesHaveNonDockerRuntime(ctx, k8sAPI)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintln(os.Stderr, err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t}\n\n\t\t// Check 'kubernetes' service in default namespace to see what ports the API\n\t\t// Server listens on. If the ports are different from the default ('443,6443')\n\t\t// then replace with ports from the service spec.\n\t\tapiSrvPorts := getApiServerPorts(ctx, k8sAPI)\n\t\tif apiSrvPorts != \"\" {\n\t\t\tvalues.ProxyInit.KubeAPIServerPorts = apiSrvPorts\n\t\t}\n\t}\n\n\terr = initializeIssuerCredentials(ctx, k8sAPI, values)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = validateValues(ctx, k8sAPI, values)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn renderControlPlane(w, values, valuesOverrides, format)\n}\n\nfunc isRunAsRoot(values map[string]interface{}) bool {\n\tif proxyInit, ok := values[\"proxyInit\"]; ok {\n\t\tif val, ok := proxyInit.(map[string]interface{})[\"runAsRoot\"]; ok {\n\t\t\tif truth, ok := template.IsTrue(val); ok {\n\t\t\t\treturn truth\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n// renderChartToBuffer takes a slice of loaded template files and configuration values and renders\n// them into a buffer. The coalesced values are also returned so that they may be rendered via\n// `renderOverrides` if appropriate.\nfunc renderChartToBuffer(files []*loader.BufferedFile, values map[string]interface{}, valuesOverrides map[string]interface{}) (*bytes.Buffer, chartutil.Values, error) {\n\tpartials, err := chartspkg.LoadPartials()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tchart, err := loader.LoadFiles(append(files, partials...))\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t// Store final Values generated from values.yaml and CLI flags\n\tchart.Values = values\n\n\tvals, err := chartutil.CoalesceValues(chart, valuesOverrides)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tfullValues := map[string]interface{}{\n\t\t\"Values\": vals,\n\t\t\"Release\": map[string]interface{}{\n\t\t\t\"Namespace\": controlPlaneNamespace,\n\t\t\t\"Service\":   \"CLI\",\n\t\t},\n\t}\n\n\t// Attach the final values into the `Values` field for rendering to work\n\trenderedTemplates, err := engine.Render(chart, fullValues)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"failed to render the template: %w\", err)\n\t}\n\n\t// Merge templates and inject\n\tvar buf bytes.Buffer\n\tfor _, tmpl := range chart.Templates {\n\t\tt := path.Join(chart.Metadata.Name, tmpl.Name)\n\t\tif _, err := buf.WriteString(renderedTemplates[t]); err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t}\n\n\treturn &buf, vals, nil\n}\n\nfunc updateDefaultValues(installed GatewayAPICRDs, defaultValues map[string]interface{}) map[string]interface{} {\n\tif installed == Absent {\n\t\t// if GW API is not installed, default to false\n\t\tdefaultValues[\"installGatewayAPI\"] = false\n\t} else if installed == Linkerd {\n\t\t// if it is installed by Linkerd, default to true\n\t\tdefaultValues[\"installGatewayAPI\"] = true\n\t} else if installed == External {\n\t\t// if it is external, default to false as we are not managing it\n\t\tdefaultValues[\"installGatewayAPI\"] = false\n\t}\n\n\treturn defaultValues\n}\n\nfunc validateFinalValues(installed GatewayAPICRDs, finalValues map[string]interface{}) error {\n\tinstalling := false\n\n\tif installGatewayAPI, ok := finalValues[\"installGatewayAPI\"]; ok {\n\t\tinstalling = installGatewayAPI == true\n\t}\n\n\tif enableHttpRoutes, ok := finalValues[\"enableHttpRoutes\"]; ok {\n\t\tinstalling = enableHttpRoutes == true\n\t}\n\n\tif installed == Absent {\n\t\tif !installing {\n\t\t\t// if we are not installing GW API Resources and they are not present, error\n\t\t\treturn errors.New(`The Gateway API CRDs must be installed prior to installing Linkerd. Run:\n\nkubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml\n\nor see https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api for more options.`)\n\t\t}\n\t} else if installed == Linkerd {\n\t\tif !installing {\n\t\t\t// if they are installed and managed by Linkerd, we cannot uninstall them\n\t\t\treturn errors.New(\"Linkerd is providing GW API, but your current install configuration will remove it\")\n\t\t}\n\t} else if installed == External {\n\t\tif installing {\n\t\t\t// if they are installed but are external, we cannot be installing as well\n\t\t\treturn errors.New(\"Linkerd cannot install the Gateway API CRDs because they are already installed by an external source. Please set `installGatewayAPI` to `false`.\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc renderCRDs(ctx context.Context, k *k8s.KubernetesAPI, w io.Writer, options valuespkg.Options, format string) error {\n\tfiles := []*loader.BufferedFile{\n\t\t{Name: chartutil.ChartfileName},\n\t}\n\tfor _, template := range TemplatesCrdFiles {\n\t\tfiles = append(files, &loader.BufferedFile{Name: template})\n\t}\n\tif err := chartspkg.FilesReader(charts.Templates, l5dcharts.HelmChartDirCrds+\"/\", files); err != nil {\n\t\treturn err\n\t}\n\n\t// Load defaults from values.yaml\n\tvaluesFile := &loader.BufferedFile{Name: l5dcharts.HelmChartDirCrds + \"/values.yaml\"}\n\tif err := chartspkg.ReadFile(charts.Templates, \"\", valuesFile); err != nil {\n\t\treturn err\n\t}\n\t// Ensure the map is not nil, even if the default `values.yaml` is empty ---\n\t// if there are no values in the YAML file, `yaml.Unmarshal` will not\n\t// allocate the map, and the subsequent assignment to `cliVersion` will\n\t// panic because the map is nil.\n\tdefaultValues := make(map[string]interface{})\n\terr := yaml.Unmarshal(valuesFile.Data, &defaultValues)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefaultValues[\"cliVersion\"] = k8s.CreatedByAnnotationValue()\n\n\t// Create values override\n\tvaluesOverrides, err := options.MergeValues(nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// If any of the Gateway API CRDs are installed, we default to rendering the\n\t// Gateway API CRDs.\n\tif k != nil {\n\t\tinstalled, err := checkGatewayAPICRDs(ctx, k)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tdefaultValues = updateDefaultValues(installed, defaultValues)\n\t\tfinalValues := chartspkg.MergeMaps(defaultValues, valuesOverrides)\n\n\t\tif err := validateFinalValues(installed, finalValues); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tbuf, _, err := renderChartToBuffer(files, defaultValues, valuesOverrides)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn pkgcmd.RenderYAMLAs(buf, w, format)\n}\n\nfunc renderControlPlane(w io.Writer, values *l5dcharts.Values, valuesOverrides map[string]interface{}, format string) error {\n\tfiles := []*loader.BufferedFile{\n\t\t{Name: chartutil.ChartfileName},\n\t}\n\tfor _, template := range TemplatesControlPlane {\n\t\tfiles = append(files, &loader.BufferedFile{Name: template})\n\t}\n\tif err := chartspkg.FilesReader(charts.Templates, l5dcharts.HelmChartDirCP+\"/\", files); err != nil {\n\t\treturn err\n\t}\n\n\tvaluesMap, err := values.ToMap()\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuf, vals, err := renderChartToBuffer(files, valuesMap, valuesOverrides)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\toverrides, err := renderOverrides(vals, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuf.WriteString(yamlSep)\n\tbuf.WriteString(string(overrides))\n\n\treturn pkgcmd.RenderYAMLAs(buf, w, format)\n}\n\n// renderOverrides outputs the Secret/linkerd-config-overrides resource which\n// contains the subset of the values which have been changed from their defaults.\n// This secret is used by the upgrade command the load configuration which was\n// specified at install time.  Note that if identity issuer credentials were\n// supplied to the install command or if they were generated by the install\n// command, those credentials will be saved here so that they are preserved\n// during upgrade.  Note also that this Secret/linkerd-config-overrides\n// resource is not part of the Helm chart and will not be present when installing\n// with Helm. If stringData is set to true, the secret will be rendered using\n// the StringData field instead of the Data field, making the output more\n// human readable.\nfunc renderOverrides(values chartutil.Values, stringData bool) ([]byte, error) {\n\tdefaults, err := l5dcharts.NewValues()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// Remove unnecessary fields, including fields added by helm's `chartutil.CoalesceValues`\n\tdelete(values, \"configs\")\n\tdelete(values, \"partials\")\n\n\toverrides, err := tree.Diff(defaults, values)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\toverridesBytes, err := yaml.Marshal(overrides)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsecret := corev1.Secret{\n\t\tTypeMeta: metav1.TypeMeta{Kind: \"Secret\", APIVersion: \"v1\"},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"linkerd-config-overrides\",\n\t\t\tNamespace: controlPlaneNamespace,\n\t\t\tLabels: map[string]string{\n\t\t\t\tk8s.ControllerNSLabel: controlPlaneNamespace,\n\t\t\t},\n\t\t},\n\t}\n\tif stringData {\n\t\tsecret.StringData = map[string]string{\n\t\t\t\"linkerd-config-overrides\": string(overridesBytes),\n\t\t}\n\t} else {\n\t\tsecret.Data = map[string][]byte{\n\t\t\t\"linkerd-config-overrides\": overridesBytes,\n\t\t}\n\t}\n\tbytes, err := yaml.Marshal(secret)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn bytes, nil\n}\n\nfunc errAfterRunningChecks(cniEnabled bool) error {\n\tchecks := []healthcheck.CategoryID{\n\t\thealthcheck.KubernetesAPIChecks,\n\t}\n\thc := healthcheck.NewHealthChecker(checks, &healthcheck.Options{\n\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\tKubeConfig:            kubeconfigPath,\n\t\tImpersonate:           impersonate,\n\t\tImpersonateGroup:      impersonateGroup,\n\t\tKubeContext:           kubeContext,\n\t\tAPIAddr:               apiAddr,\n\t\tCNIEnabled:            cniEnabled,\n\t})\n\n\tvar err error\n\thc.RunChecks(func(result *healthcheck.CheckResult) {\n\t\tif result.Err != nil {\n\t\t\terr = result.Err\n\t\t}\n\t})\n\n\treturn err\n}\n\n// getApiServerPorts looks at the 'kubernetes' service in the 'default'\n// namespace and returns the ClusterIP port for the API Server (by default 443),\n// and the port that the API Server backend is expecting TLS connections on (by\n// default 6443.)\nfunc getApiServerPorts(ctx context.Context, api *k8s.KubernetesAPI) string {\n\tservice, err := api.CoreV1().Services(\"default\").Get(ctx, \"kubernetes\", metav1.GetOptions{})\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\tports := make([]string, 0)\n\tfor _, port := range service.Spec.Ports {\n\t\tports = append(ports, strconv.Itoa(int(port.Port)))\n\t\t// We only care about int ports since string ports (e.g targetPort: web)\n\t\t// correspond to a named port in a pod spec.\n\t\tif port.TargetPort.Type == intstr.Int {\n\t\t\tports = append(ports, strconv.Itoa(port.TargetPort.IntValue()))\n\t\t}\n\t}\n\n\treturn strings.Join(ports, \",\")\n}\n"
  },
  {
    "path": "cli/cmd/install_cni_helm_test.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\tcnicharts \"github.com/linkerd/linkerd2/pkg/charts/cni\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n\t\"helm.sh/helm/v3/pkg/chart\"\n\t\"helm.sh/helm/v3/pkg/chartutil\"\n\t\"helm.sh/helm/v3/pkg/engine\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nfunc TestRenderCniHelm(t *testing.T) {\n\t// read the cni plugin chart and its defaults from the local folder.\n\t// override most defaults with pinned values.\n\t// use the Helm lib to render the templates.\n\t// the golden file is generated using the following `helm template` command:\n\t// bin/helm template --set namespace=\"linkerd-test\" --set inboundProxyPort=1234 --set outboundProxyPort=5678 --set cniPluginImage=\"cr.l5d.io/linkerd/cni-plugin-test\" --set cniPluginVersion=\"test-version\" --set logLevel=\"debug\" --set proxyUID=1111 --set proxyGID=1111 --set destCNINetDir=\"/etc/cni/net.d-test\" --set destCNIBinDir=\"/opt/cni/bin-test\" --set useWaitFlag=true --set cliVersion=test-version charts/linkerd2-cni\n\n\tt.Run(\"Cni Install with defaults\", func(t *testing.T) {\n\t\tchartCni := chartCniPlugin(t)\n\t\ttestRenderCniHelm(t, chartCni, &chartutil.Values{}, \"install_cni_helm_default_output.golden\")\n\t})\n\n\tt.Run(\"Cni Install with overridden values\", func(t *testing.T) {\n\t\tchartCni := chartCniPlugin(t)\n\t\toverrideJSON :=\n\t\t\t`{\n\t\t\t\"namespace\": \"linkerd-test\",\n  \t\t\t\"inboundProxyPort\": 1234,\n  \t\t\t\"outboundProxyPort\": 5678,\n  \t\t\t\"logLevel\": \"debug\",\n\t\t\t\"image\": {\n\t\t\t\t\"name\": \"cr.l5d.io/linkerd/cni-plugin\",\n\t\t\t\t\"version\": \"v1.4.0\"\n\t\t\t},\n  \t\t\t\"proxyUID\": 1111,\n  \t\t\t\"proxyGID\": 1111,\n  \t\t\t\"destCNINetDir\": \"/etc/cni/net.d-test\",\n  \t\t\t\"destCNIBinDir\": \"/opt/cni/bin-test\",\n  \t\t\t\"useWaitFlag\": true,\n\t\t\t\"cliVersion\": \"test-version\",\n\t\t\t\"priorityClassName\": \"system-node-critical\",\n\t\t\t\"revisionHistoryLimir\": 10\n\t\t}`\n\n\t\tvar overrideConfig chartutil.Values\n\t\terr := yaml.Unmarshal([]byte(overrideJSON), &overrideConfig)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unexpected error\", err)\n\t\t}\n\t\ttestRenderCniHelm(t, chartCni, &overrideConfig, \"install_cni_helm_override_output.golden\")\n\t})\n\n}\n\nfunc testRenderCniHelm(t *testing.T, chart *chart.Chart, overrideConfig *chartutil.Values, goldenFileName string) {\n\tvar (\n\t\tchartName = \"linkerd2-cni\"\n\t\tnamespace = \"linkerd-test\"\n\t)\n\n\treleaseOptions := chartutil.ReleaseOptions{\n\t\tName:      chartName,\n\t\tNamespace: namespace,\n\t\tIsUpgrade: false,\n\t\tIsInstall: true,\n\t}\n\n\tvaluesToRender, err := chartutil.ToRenderValues(chart, *overrideConfig, releaseOptions, nil)\n\tif err != nil {\n\t\tt.Fatal(\"Unexpected error\", err)\n\t}\n\n\trendered, err := engine.Render(chart, valuesToRender)\n\tif err != nil {\n\t\tt.Fatal(\"Unexpected error\", err)\n\t}\n\n\tvar buf bytes.Buffer\n\tfor _, template := range chart.Templates {\n\t\tsource := chartName + \"/\" + template.Name\n\t\tv, exists := rendered[source]\n\t\tif !exists {\n\t\t\t// skip partial templates\n\t\t\tcontinue\n\t\t}\n\t\tbuf.WriteString(\"---\\n# Source: \" + source + \"\\n\")\n\t\tbuf.WriteString(v)\n\t}\n\n\tif err := testDataDiffer.DiffTestYAML(goldenFileName, buf.String()); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc chartCniPlugin(t *testing.T) *chart.Chart {\n\trawValues, err := readCniTestValues(t)\n\tif err != nil {\n\t\tt.Fatal(\"Unexpected error\", err)\n\t}\n\n\tvar values chartutil.Values\n\terr = yaml.Unmarshal(rawValues, &values)\n\tif err != nil {\n\t\tt.Fatal(\"Unexpected error\", err)\n\t}\n\tchartPartials := chartPartials([]string{\n\t\t\"templates/_helpers.tpl\",\n\t\t\"templates/_metadata.tpl\",\n\t\t\"templates/_tolerations.tpl\",\n\t\t\"templates/_resources.tpl\",\n\t})\n\n\tcniChart := &chart.Chart{\n\t\tMetadata: &chart.Metadata{\n\t\t\tName: helmCNIDefaultChartName,\n\t\t\tSources: []string{\n\t\t\t\tfilepath.Join(\"..\", \"..\", \"..\", \"charts\", \"linkerd2-cni\"),\n\t\t\t},\n\t\t},\n\t\tValues: values,\n\t}\n\n\tcniChart.AddDependency(chartPartials)\n\n\tcniChart.Templates = append(cniChart.Templates, &chart.File{\n\t\tName: \"templates/cni-plugin.yaml\",\n\t})\n\n\tfor _, template := range cniChart.Templates {\n\t\tfilepath := filepath.Join(cniChart.Metadata.Sources[0], template.Name)\n\t\ttemplate.Data = []byte(testutil.ReadTestdata(filepath))\n\t}\n\n\treturn cniChart\n}\n\nfunc readCniTestValues(t *testing.T) ([]byte, error) {\n\tvalues, err := cnicharts.NewValues()\n\tif err != nil {\n\t\tt.Fatal(\"Unexpected error\", err)\n\t}\n\n\treturn yaml.Marshal(values)\n}\n"
  },
  {
    "path": "cli/cmd/install_helm_test.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/charts\"\n\tchartspkg \"github.com/linkerd/linkerd2/pkg/charts\"\n\tl5dcharts \"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/testutil\"\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/engine\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nfunc TestRenderHelm(t *testing.T) {\n\t// read the control plane chart and its defaults from the local folder.\n\t// override certain defaults with pinned values.\n\t// use the Helm lib to render the templates.\n\tt.Run(\"Non-HA mode\", func(t *testing.T) {\n\t\tchartCrds := chartCrds(t)\n\t\tchartControlPlane := chartControlPlane(t, false, \"\", \"111\", \"222\")\n\t\ttestRenderHelm(t, chartCrds, nil, \"install_helm_crds_output.golden\")\n\t\ttestRenderHelm(t, chartControlPlane, nil, \"install_helm_control_plane_output.golden\")\n\t})\n\n\tt.Run(\"CRDs without Gateway API\", func(t *testing.T) {\n\t\tchartCrds := chartCrds(t)\n\t\ttestRenderHelm(t, chartCrds, map[string]interface{}{\n\t\t\t\"installGatewayAPI\": false,\n\t\t}, \"install_helm_crds_without_gateway_output.golden\")\n\t})\n\n\tt.Run(\"CRDs without Gateway API routes\", func(t *testing.T) {\n\t\tchartCrds := chartCrds(t)\n\t\ttestRenderHelm(t, chartCrds, map[string]interface{}{\n\t\t\t\"enableHttpRoutes\": false,\n\t\t\t\"enableTcpRoutes\":  false,\n\t\t\t\"enableTlsRoutes\":  false,\n\t\t}, \"install_helm_crds_without_gateway_output.golden\")\n\t})\n\n\tt.Run(\"HA mode\", func(t *testing.T) {\n\t\tchartCrds := chartCrds(t)\n\t\tchartControlPlane := chartControlPlane(t, true, \"\", \"111\", \"222\")\n\t\ttestRenderHelm(t, chartCrds, nil, \"install_helm_crds_output_ha.golden\")\n\t\ttestRenderHelm(t, chartControlPlane, nil, \"install_helm_control_plane_output_ha.golden\")\n\t})\n\n\tt.Run(\"HA mode with GID\", func(t *testing.T) {\n\t\tadditionalConfig := `\ncontrollerGID: 1324\nproxy:\n  gid: 4231\n`\n\t\tchartControlPlane := chartControlPlane(t, true, additionalConfig, \"111\", \"222\")\n\t\ttestRenderHelm(t, chartControlPlane, nil, \"install_helm_control_plane_output_ha_with_gid.golden\")\n\t})\n\n\tt.Run(\"HA mode with podLabels and podAnnotations\", func(t *testing.T) {\n\t\tadditionalConfig := `\npodLabels:\n  foo: bar\n  fiz: buz\npodAnnotations:\n  bingo: bongo\n  asda: fasda\n`\n\t\tchartControlPlane := chartControlPlane(t, true, additionalConfig, \"333\", \"444\")\n\t\ttestRenderHelm(t, chartControlPlane, nil, \"install_helm_output_ha_labels.golden\")\n\t})\n\n\tt.Run(\"HA mode with custom namespaceSelector\", func(t *testing.T) {\n\t\tadditionalConfig := `\nproxyInjector:\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: In\n      values:\n      - enabled\nprofileValidator:\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: In\n      values:\n      - enabled\n`\n\t\tchartControlPlane := chartControlPlane(t, true, additionalConfig, \"111\", \"222\")\n\t\ttestRenderHelm(t, chartControlPlane, nil, \"install_helm_output_ha_namespace_selector.golden\")\n\t})\n}\n\nfunc testRenderHelm(t *testing.T, linkerd2Chart *chart.Chart, additionalValues map[string]interface{}, goldenFileName string) {\n\tvar (\n\t\tchartName = \"linkerd2\"\n\t\tnamespace = \"linkerd-dev\"\n\t)\n\n\t// pin values that are changed by Helm functions on each test run\n\toverrideJSON := `{\n   \"cliVersion\":\"\",\n   \"linkerdVersion\":\"linkerd-version\",\n   \"identityTrustAnchorsPEM\":\"test-trust-anchor\",\n   \"identityTrustDomain\":\"test.trust.domain\",\n   \"proxy\":{\n    \"image\":{\n     \"version\":\"test-proxy-version\"\n    }\n   },\n  \"identity\":{\n    \"issuer\":{\n      \"tls\":{\n        \"keyPEM\":\"test-key-pem\",\n        \"crtPEM\":\"test-crt-pem\"\n      }\n    }\n  },\n  \"configs\": null,\n  \"debugContainer\":{\n    \"image\":{\n      \"version\":\"test-debug-version\"\n    }\n  },\n  \"proxyInjector\":{\n    \"externalSecret\": true,\n    \"caBundle\":\"test-proxy-injector-ca-bundle\"\n  },\n  \"profileValidator\":{\n    \"externalSecret\": true,\n    \"caBundle\":\"test-profile-validator-ca-bundle\"\n  },\n  \"policyValidator\":{\n    \"externalSecret\": true,\n    \"caBundle\":\"test-profile-validator-ca-bundle\"\n  },\n  \"tap\":{\n    \"externalSecret\": true,\n    \"caBundle\":\"test-tap-ca-bundle\"\n  }\n}`\n\n\tvar overrideConfig chartutil.Values\n\terr := yaml.Unmarshal([]byte(overrideJSON), &overrideConfig)\n\tif err != nil {\n\t\tt.Fatal(\"Unexpected error\", err)\n\t}\n\tfor k, v := range additionalValues {\n\t\toverrideConfig[k] = v\n\t}\n\n\treleaseOptions := chartutil.ReleaseOptions{\n\t\tName:      chartName,\n\t\tNamespace: namespace,\n\t\tIsUpgrade: false,\n\t\tIsInstall: true,\n\t}\n\n\tvaluesToRender, err := chartutil.ToRenderValues(linkerd2Chart, overrideConfig, releaseOptions, nil)\n\tif err != nil {\n\t\tt.Fatal(\"Unexpected error\", err)\n\t}\n\n\trendered, err := engine.Render(linkerd2Chart, valuesToRender)\n\tif err != nil {\n\t\tt.Fatal(\"Unexpected error\", err)\n\t}\n\n\tvar buf bytes.Buffer\n\tfor _, template := range linkerd2Chart.Templates {\n\t\tsource := linkerd2Chart.Metadata.Name + \"/\" + template.Name\n\t\tv, exists := rendered[source]\n\t\tif !exists {\n\t\t\t// skip partial templates\n\t\t\tcontinue\n\t\t}\n\t\tbuf.WriteString(\"---\\n# Source: \" + source + \"\\n\")\n\t\tbuf.WriteString(v)\n\t}\n\n\tfor _, dep := range linkerd2Chart.Dependencies() {\n\t\tfor _, template := range dep.Templates {\n\t\t\tsource := linkerd2Chart.Metadata.Name + \"/charts\" + \"/\" + dep.Metadata.Name + \"/\" + template.Name\n\t\t\tv, exists := rendered[source]\n\t\t\tif !exists {\n\t\t\t\t// skip partial templates\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbuf.WriteString(\"---\\n# Source: \" + source + \"\\n\")\n\t\t\tbuf.WriteString(v)\n\t\t}\n\t}\n\n\tif err := testDataDiffer.DiffTestYAML(goldenFileName, buf.String()); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc chartCrds(t *testing.T) *chart.Chart {\n\tpartialPaths := []string{\n\t\t\"templates/_proxy.tpl\",\n\t\t\"templates/_proxy-init.tpl\",\n\t\t\"templates/_volumes.tpl\",\n\t\t\"templates/_resources.tpl\",\n\t\t\"templates/_metadata.tpl\",\n\t\t\"templates/_debug.tpl\",\n\t\t\"templates/_trace.tpl\",\n\t\t\"templates/_capabilities.tpl\",\n\t\t\"templates/_affinity.tpl\",\n\t\t\"templates/_nodeselector.tpl\",\n\t\t\"templates/_tolerations.tpl\",\n\t\t\"templates/_validate.tpl\",\n\t\t\"templates/_pull-secrets.tpl\",\n\t}\n\n\tchartPartials := chartPartials(partialPaths)\n\n\t// Load defaults from values.yaml\n\tvaluesFile := &loader.BufferedFile{Name: l5dcharts.HelmChartDirCrds + \"/values.yaml\"}\n\tif err := chartspkg.ReadFile(charts.Templates, \"\", valuesFile); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefaultValues := make(map[string]interface{})\n\terr := yaml.Unmarshal(valuesFile.Data, &defaultValues)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefaultValues[\"cliVersion\"] = k8s.CreatedByAnnotationValue()\n\n\tlinkerd2Chart := &chart.Chart{\n\t\tMetadata: &chart.Metadata{\n\t\t\tName: helmDefaultChartNameCrds,\n\t\t\tSources: []string{\n\t\t\t\tfilepath.Join(\"..\", \"..\", \"..\", \"charts\", \"linkerd-crds\"),\n\t\t\t},\n\t\t},\n\t\tValues: defaultValues,\n\t}\n\n\tlinkerd2Chart.AddDependency(chartPartials)\n\n\tfor _, filepath := range TemplatesCrdFiles {\n\t\tlinkerd2Chart.Templates = append(linkerd2Chart.Templates, &chart.File{\n\t\t\tName: filepath,\n\t\t})\n\t}\n\n\tfor _, template := range linkerd2Chart.Templates {\n\t\tfilepath := filepath.Join(linkerd2Chart.Metadata.Sources[0], template.Name)\n\t\ttemplate.Data = []byte(testutil.ReadTestdata(filepath))\n\t}\n\n\treturn linkerd2Chart\n}\n\nfunc chartControlPlane(t *testing.T, ha bool, additionalConfig string, ignoreOutboundPorts string, ignoreInboundPorts string) *chart.Chart {\n\tvalues, err := readTestValues(ha, ignoreOutboundPorts, ignoreInboundPorts)\n\tif err != nil {\n\t\tt.Fatal(\"Unexpected error\", err)\n\t}\n\n\tif additionalConfig != \"\" {\n\t\terr := yaml.Unmarshal([]byte(additionalConfig), values)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"Unexpected error\", err)\n\t\t}\n\t}\n\n\tpartialPaths := []string{\n\t\t\"templates/_proxy.tpl\",\n\t\t\"templates/_proxy-init.tpl\",\n\t\t\"templates/_volumes.tpl\",\n\t\t\"templates/_resources.tpl\",\n\t\t\"templates/_metadata.tpl\",\n\t\t\"templates/_debug.tpl\",\n\t\t\"templates/_trace.tpl\",\n\t\t\"templates/_capabilities.tpl\",\n\t\t\"templates/_affinity.tpl\",\n\t\t\"templates/_nodeselector.tpl\",\n\t\t\"templates/_tolerations.tpl\",\n\t\t\"templates/_validate.tpl\",\n\t\t\"templates/_pull-secrets.tpl\",\n\t}\n\n\tchartPartials := chartPartials(partialPaths)\n\n\trawValues, err := yaml.Marshal(values)\n\tif err != nil {\n\t\tt.Fatal(\"Unexpected error\", err)\n\t}\n\n\tvar mapValues chartutil.Values\n\terr = yaml.Unmarshal(rawValues, &mapValues)\n\tif err != nil {\n\t\tt.Fatal(\"Unexpected error\", err)\n\t}\n\tlinkerd2Chart := &chart.Chart{\n\t\tMetadata: &chart.Metadata{\n\t\t\tName: helmDefaultChartNameCP,\n\t\t\tSources: []string{\n\t\t\t\tfilepath.Join(\"..\", \"..\", \"..\", \"charts\", \"linkerd-control-plane\"),\n\t\t\t},\n\t\t},\n\t\tValues: mapValues,\n\t}\n\n\tlinkerd2Chart.AddDependency(chartPartials)\n\n\tfor _, filepath := range TemplatesControlPlane {\n\t\tlinkerd2Chart.Templates = append(linkerd2Chart.Templates, &chart.File{\n\t\t\tName: filepath,\n\t\t})\n\t}\n\n\tfor _, template := range linkerd2Chart.Templates {\n\t\tfilepath := filepath.Join(linkerd2Chart.Metadata.Sources[0], template.Name)\n\t\ttemplate.Data = []byte(testutil.ReadTestdata(filepath))\n\t}\n\n\treturn linkerd2Chart\n}\n\nfunc chartPartials(paths []string) *chart.Chart {\n\tvar partialTemplates []*chart.File\n\tfor _, path := range paths {\n\t\tpartialTemplates = append(partialTemplates, &chart.File{Name: path})\n\t}\n\n\tchart := &chart.Chart{\n\t\tMetadata: &chart.Metadata{\n\t\t\tName: \"partials\",\n\t\t\tSources: []string{\n\t\t\t\tfilepath.Join(\"..\", \"..\", \"..\", \"charts\", \"partials\"),\n\t\t\t},\n\t\t},\n\t\tTemplates: partialTemplates,\n\t}\n\n\tfor _, template := range chart.Templates {\n\t\ttemplate := template\n\t\tfilepath := filepath.Join(chart.Metadata.Sources[0], template.Name)\n\t\ttemplate.Data = []byte(testutil.ReadTestdata(filepath))\n\t}\n\n\treturn chart\n}\n\nfunc readTestValues(ha bool, ignoreOutboundPorts string, ignoreInboundPorts string) (*l5dcharts.Values, error) {\n\tvalues, err := l5dcharts.NewValues()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif ha {\n\t\tif err = l5dcharts.MergeHAValues(values); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tvalues.ProxyInit.IgnoreOutboundPorts = ignoreOutboundPorts\n\tvalues.ProxyInit.IgnoreInboundPorts = ignoreInboundPorts\n\tvalues.HeartbeatSchedule = \"1 2 3 4 5\"\n\n\treturn values, nil\n}\n"
  },
  {
    "path": "cli/cmd/install_test.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/cli/flag\"\n\tcharts \"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n\t\"helm.sh/helm/v3/pkg/cli/values\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\nconst (\n\tinstallProxyVersion        = \"install-proxy-version\"\n\tinstallControlPlaneVersion = \"install-control-plane-version\"\n\tinstallDebugVersion        = \"install-debug-version\"\n\n\texternalGatewayAPIManifest = `---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httproutes.gateway.networking.k8s.io\nspec:\n  versions:\n    - name: v1\n`\n)\n\nfunc TestRender(t *testing.T) {\n\tdefaultValues, err := testInstallOptionsFakeCerts()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tgidValues, err := testInstallOptionsFakeCerts()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgidValues.ControllerGID = 1234\n\tgidValues.Proxy.GID = 4321\n\n\t// A configuration that shows that all config setting strings are honored\n\t// by `render()`.\n\tvar controllerGID int64 = 2103\n\tvar proxyGID int64 = 2102\n\tmetaValues := &charts.Values{\n\t\tControllerImage:         \"ControllerImage\",\n\t\tLinkerdVersion:          \"LinkerdVersion\",\n\t\tControllerUID:           2103,\n\t\tControllerGID:           controllerGID,\n\t\tEnableH2Upgrade:         true,\n\t\tWebhookFailurePolicy:    \"WebhookFailurePolicy\",\n\t\tHeartbeatSchedule:       \"1 2 3 4 5\",\n\t\tIdentity:                defaultValues.Identity,\n\t\tNodeSelector:            defaultValues.NodeSelector,\n\t\tTolerations:             defaultValues.Tolerations,\n\t\tClusterDomain:           \"cluster.local\",\n\t\tClusterNetworks:         \"ClusterNetworks\",\n\t\tImagePullPolicy:         \"ImagePullPolicy\",\n\t\tCliVersion:              \"CliVersion\",\n\t\tControllerLogLevel:      \"ControllerLogLevel\",\n\t\tControllerLogFormat:     \"ControllerLogFormat\",\n\t\tProxyContainerName:      \"ProxyContainerName\",\n\t\tRevisionHistoryLimit:    10,\n\t\tCNIEnabled:              false,\n\t\tIdentityTrustDomain:     defaultValues.IdentityTrustDomain,\n\t\tIdentityTrustAnchorsPEM: defaultValues.IdentityTrustAnchorsPEM,\n\t\tController:              defaultValues.Controller,\n\t\tDestinationController:   defaultValues.DestinationController,\n\t\tPodAnnotations:          map[string]string{},\n\t\tPodLabels:               map[string]string{},\n\t\tPriorityClassName:       \"PriorityClassName\",\n\t\tPolicyController: &charts.PolicyController{\n\t\t\tLogLevel: \"log-level\",\n\t\t\tResources: &charts.Resources{\n\t\t\t\tCPU: charts.Constraints{\n\t\t\t\t\tLimit:   \"cpu-limit\",\n\t\t\t\t\tRequest: \"cpu-request\",\n\t\t\t\t},\n\t\t\t\tMemory: charts.Constraints{\n\t\t\t\t\tLimit:   \"memory-limit\",\n\t\t\t\t\tRequest: \"memory-request\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tProbeNetworks: []string{\"1.0.0.0/0\", \"2.0.0.0/0\"},\n\t\t},\n\t\tProxy: &charts.Proxy{\n\t\t\tImage: &charts.Image{\n\t\t\t\tName:       \"ProxyImageName\",\n\t\t\t\tPullPolicy: \"ImagePullPolicy\",\n\t\t\t\tVersion:    \"ProxyVersion\",\n\t\t\t},\n\t\t\tLogLevel:       \"warn,linkerd=info\",\n\t\t\tLogFormat:      \"plain\",\n\t\t\tLogHTTPHeaders: \"off\",\n\t\t\tResources: &charts.Resources{\n\t\t\t\tCPU: charts.Constraints{\n\t\t\t\t\tLimit:   \"cpu-limit\",\n\t\t\t\t\tRequest: \"cpu-request\",\n\t\t\t\t},\n\t\t\t\tMemory: charts.Constraints{\n\t\t\t\t\tLimit:   \"memory-limit\",\n\t\t\t\t\tRequest: \"memory-request\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tPorts: &charts.Ports{\n\t\t\t\tAdmin:    4191,\n\t\t\t\tControl:  4190,\n\t\t\t\tInbound:  4143,\n\t\t\t\tOutbound: 4140,\n\t\t\t},\n\t\t\tUID:                  2102,\n\t\t\tGID:                  proxyGID,\n\t\t\tOpaquePorts:          \"25,443,587,3306,5432,11211\",\n\t\t\tAwait:                true,\n\t\t\tDefaultInboundPolicy: \"default-allow-policy\",\n\t\t\tMetrics: &charts.ProxyMetrics{\n\t\t\t\tHostnameLabels: false,\n\t\t\t},\n\t\t\tTracing: &charts.Tracing{\n\t\t\t\tEnabled: false,\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"k8s.pod.ip\":         \"$(_pod_ip)\",\n\t\t\t\t\t\"k8s.pod.uid\":        \"$(_pod_uid)\",\n\t\t\t\t\t\"k8s.container.name\": \"$(_pod_containerName)\",\n\t\t\t\t},\n\t\t\t\tTraceServiceName: \"linkerd-proxy\",\n\t\t\t\tCollector: &charts.TracingCollector{\n\t\t\t\t\tEndpoint: \"\",\n\t\t\t\t\tMeshIdentity: &charts.TracingCollectorIdentity{\n\t\t\t\t\t\tServiceAccountName: \"\",\n\t\t\t\t\t\tNamespace:          \"\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tLivenessProbe: &charts.Probe{\n\t\t\t\tInitialDelaySeconds: 10,\n\t\t\t\tTimeoutSeconds:      1,\n\t\t\t},\n\t\t\tReadinessProbe: &charts.Probe{\n\t\t\t\tInitialDelaySeconds: 2,\n\t\t\t\tTimeoutSeconds:      1,\n\t\t\t},\n\t\t},\n\t\tProxyInit: &charts.ProxyInit{\n\t\t\tIptablesMode:        \"legacy\",\n\t\t\tIgnoreOutboundPorts: \"443\",\n\t\t\tXTMountPath: &charts.VolumeMountPath{\n\t\t\t\tMountPath: \"/run\",\n\t\t\t\tName:      \"linkerd-proxy-init-xtables-lock\",\n\t\t\t},\n\t\t\tRunAsRoot:  false,\n\t\t\tRunAsUser:  65534,\n\t\t\tRunAsGroup: 65534,\n\t\t},\n\t\tNetworkValidator: &charts.NetworkValidator{\n\t\t\tLogLevel:    \"debug\",\n\t\t\tLogFormat:   \"plain\",\n\t\t\tConnectAddr: \"1.1.1.1:20001\",\n\t\t\tListenAddr:  \"[::]:4140\",\n\t\t\tTimeout:     \"10s\",\n\t\t},\n\t\tConfigs: charts.ConfigJSONs{\n\t\t\tGlobal:  \"GlobalConfig\",\n\t\t\tProxy:   \"ProxyConfig\",\n\t\t\tInstall: \"InstallConfig\",\n\t\t},\n\t\tDebugContainer: &charts.DebugContainer{\n\t\t\tImage: &charts.Image{\n\t\t\t\tName:       \"DebugImageName\",\n\t\t\t\tPullPolicy: \"DebugImagePullPolicy\",\n\t\t\t\tVersion:    \"DebugVersion\",\n\t\t\t},\n\t\t},\n\t\tControllerReplicas: 1,\n\t\tProxyInjector:      defaultValues.ProxyInjector,\n\t\tProfileValidator:   defaultValues.ProfileValidator,\n\t\tPolicyValidator:    defaultValues.PolicyValidator,\n\t\tEgress:             defaultValues.Egress,\n\t}\n\n\thaValues, err := testInstallOptionsHA(true)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t}\n\taddFakeTLSSecrets(haValues)\n\n\thaWithOverridesValues, err := testInstallOptionsHA(true)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t}\n\n\thaWithOverridesValues.HighAvailability = true\n\thaWithOverridesValues.ControllerReplicas = 2\n\thaWithOverridesValues.Proxy.Resources.CPU.Request = \"400m\"\n\thaWithOverridesValues.Proxy.Resources.Memory.Request = \"300Mi\"\n\thaWithOverridesValues.EnablePodDisruptionBudget = true\n\taddFakeTLSSecrets(haWithOverridesValues)\n\n\tcniEnabledValues, err := testInstallOptions()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t}\n\n\tcniEnabledValues.CNIEnabled = true\n\taddFakeTLSSecrets(cniEnabledValues)\n\n\twithProxyIgnoresValues, err := testInstallOptions()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t}\n\twithProxyIgnoresValues.ProxyInit.IgnoreInboundPorts = \"22,8100-8102\"\n\twithProxyIgnoresValues.ProxyInit.IgnoreOutboundPorts = \"5432\"\n\taddFakeTLSSecrets(withProxyIgnoresValues)\n\n\twithHeartBeatDisabledValues, err := testInstallOptions()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t}\n\twithHeartBeatDisabledValues.DisableHeartBeat = true\n\taddFakeTLSSecrets(withHeartBeatDisabledValues)\n\n\twithControlPlaneTracingValues, err := testInstallOptions()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t}\n\twithControlPlaneTracingValues.Controller.Tracing = &charts.Tracing{\n\t\tEnabled: true,\n\t\tCollector: &charts.TracingCollector{\n\t\t\tEndpoint: \"tracing.foo:4317\",\n\t\t},\n\t}\n\taddFakeTLSSecrets(withControlPlaneTracingValues)\n\n\tcustomRegistryOverride := \"my.custom.registry/linkerd-io\"\n\twithCustomRegistryValues, err := testInstallOptions()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t}\n\tflags, flagSet := makeProxyFlags(withCustomRegistryValues)\n\terr = flagSet.Set(\"registry\", customRegistryOverride)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t}\n\terr = flag.ApplySetFlags(withCustomRegistryValues, flags)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t}\n\taddFakeTLSSecrets(withCustomRegistryValues)\n\n\twithCustomDestinationGetNetsValues, err := testInstallOptions()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t}\n\twithCustomDestinationGetNetsValues.ClusterNetworks = \"10.0.0.0/8,100.64.0.0/10,172.0.0.0/8\"\n\taddFakeTLSSecrets(withCustomDestinationGetNetsValues)\n\n\ttracingValues, err := testInstallOptions()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t}\n\ttracingValues.Proxy.Tracing.Enabled = true\n\ttracingValues.Proxy.Tracing.Collector.Endpoint = \"tracing.foo:4317\"\n\ttracingValues.Proxy.Tracing.Collector.MeshIdentity.ServiceAccountName = \"default\"\n\ttracingValues.Proxy.Tracing.Collector.MeshIdentity.Namespace = \"foo\"\n\taddFakeTLSSecrets(tracingValues)\n\n\ttestCases := []struct {\n\t\tvalues         *charts.Values\n\t\tgoldenFileName string\n\t\toptions        values.Options\n\t}{\n\t\t{defaultValues, \"install_default.golden\", values.Options{}},\n\t\t{metaValues, \"install_output.golden\", values.Options{}},\n\t\t{haValues, \"install_ha_output.golden\", values.Options{}},\n\t\t{haWithOverridesValues, \"install_ha_with_overrides_output.golden\", values.Options{}},\n\t\t{cniEnabledValues, \"install_no_init_container.golden\", values.Options{}},\n\t\t{withProxyIgnoresValues, \"install_proxy_ignores.golden\", values.Options{}},\n\t\t{withHeartBeatDisabledValues, \"install_heartbeat_disabled_output.golden\", values.Options{}},\n\t\t{withControlPlaneTracingValues, \"install_controlplane_tracing_output.golden\", values.Options{}},\n\t\t{withCustomRegistryValues, \"install_custom_registry.golden\", values.Options{}},\n\t\t{withCustomDestinationGetNetsValues, \"install_default_override_dst_get_nets.golden\", values.Options{}},\n\t\t{defaultValues, \"install_custom_domain.golden\", values.Options{}},\n\t\t{defaultValues, \"install_values_file.golden\", values.Options{ValueFiles: []string{filepath.Join(\"testdata\", \"install_config.yaml\")}}},\n\t\t{defaultValues, \"install_default_token.golden\", values.Options{Values: []string{\"identity.serviceAccountTokenProjection=false\"}}},\n\t\t{gidValues, \"install_gid_output.golden\", values.Options{}},\n\t\t{tracingValues, \"install_tracing.golden\", values.Options{}},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d: %s\", i, tc.goldenFileName), func(t *testing.T) {\n\t\t\tvaluesOverrides, err := tc.options.MergeValues(nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to get values overrides: %v\", err)\n\t\t\t}\n\t\t\tvar buf bytes.Buffer\n\t\t\tif err := renderControlPlane(&buf, tc.values, valuesOverrides, \"yaml\"); err != nil {\n\t\t\t\tt.Fatalf(\"Failed to render templates: %v\", err)\n\t\t\t}\n\n\t\t\tif err := testDataDiffer.DiffTestYAML(tc.goldenFileName, buf.String()); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIgnoreCluster(t *testing.T) {\n\tdefaultValues, err := testInstallOptions()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\taddFakeTLSSecrets(defaultValues)\n\n\tvar buf bytes.Buffer\n\tif err := installControlPlane(context.Background(), nil, &buf, defaultValues, nil, values.Options{}, \"yaml\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestGWApi(t *testing.T) {\n\tunsupportedGatewayAPIVersionManifest := `---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httproutes.gateway.networking.k8s.io\nspec:\n  versions:\n    - name: v1alpha1\n`\n\n\tlinkerdGatewayAPIManifest := `---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httproutes.gateway.networking.k8s.io\n  annotations:\n    linkerd.io/created-by: linkerd\n`\n\n\ttestCases := []struct {\n\t\tname        string\n\t\tresources   string\n\t\tvalues      values.Options\n\t\texpectError bool\n\t\tgoldenFile  string\n\t}{\n\t\t{\n\t\t\t\"render with external GW API\",\n\t\t\texternalGatewayAPIManifest,\n\t\t\tvalues.Options{},\n\t\t\tfalse,\n\t\t\t\"install_crds.golden\",\n\t\t},\n\t\t{\n\t\t\t\"error with unsupported external GW API\",\n\t\t\tunsupportedGatewayAPIVersionManifest,\n\t\t\tvalues.Options{},\n\t\t\ttrue,\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"render with missing GW API\",\n\t\t\t\"\",\n\t\t\tvalues.Options{},\n\t\t\ttrue,\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"render with GW API (installGatewayAPI flag)\",\n\t\t\t\"\",\n\t\t\tvalues.Options{\n\t\t\t\tValues: []string{\"installGatewayAPI=true\"},\n\t\t\t},\n\t\t\tfalse,\n\t\t\t\"install_crds_with_gateway_api.golden\",\n\t\t},\n\t\t{\n\t\t\t\"render with GW API (lagecy flags)\",\n\t\t\t\"\",\n\t\t\tvalues.Options{\n\t\t\t\tValues: []string{\n\t\t\t\t\t\"enableHttpRoutes=true\",\n\t\t\t\t\t\"enableTlsRoutes=true\",\n\t\t\t\t\t\"enableTcpRoutes=true\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t\t\"install_crds_with_gateway_api.golden\",\n\t\t},\n\t\t{\n\t\t\t\"render with conflicting GW API (installGatewayAPI flag)\",\n\t\t\texternalGatewayAPIManifest,\n\t\t\tvalues.Options{\n\t\t\t\tValues: []string{\"installGatewayAPI=true\"},\n\t\t\t},\n\t\t\ttrue,\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"render with conflicting GW API (legacy flags)\",\n\t\t\texternalGatewayAPIManifest,\n\t\t\tvalues.Options{\n\t\t\t\tValues: []string{\n\t\t\t\t\t\"enableHttpRoutes=true\",\n\t\t\t\t\t\"enableTlsRoutes=true\",\n\t\t\t\t\t\"enableTcpRoutes=true\",\n\t\t\t\t},\n\t\t\t},\n\t\t\ttrue,\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"error on attempt to remove a Linkerd managed version via installGatewayAPI=false\",\n\t\t\tlinkerdGatewayAPIManifest,\n\t\t\tvalues.Options{\n\t\t\t\tValues: []string{\"installGatewayAPI=false\"},\n\t\t\t},\n\t\t\ttrue,\n\t\t\t\"\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tk, err := k8s.NewFakeAPIFromManifests([]io.Reader{strings.NewReader(tc.resources)})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to initialize fake API: %s\", err)\n\t\t\t}\n\n\t\t\tvar buf bytes.Buffer\n\t\t\terr = renderCRDs(context.Background(), k, &buf, tc.values, \"yaml\")\n\t\t\tif err != nil {\n\t\t\t\tif tc.expectError {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"Failed to render templates: %v\", err)\n\t\t\t}\n\n\t\t\tif tc.expectError && err == nil {\n\t\t\t\tt.Fatal(\"an error was expected\")\n\n\t\t\t}\n\n\t\t\tif err := testDataDiffer.DiffTestYAML(tc.goldenFile, buf.String()); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t})\n\t}\n\n}\n\nfunc TestValidateAndBuild_Errors(t *testing.T) {\n\tt.Run(\"Fails validation for invalid ignoreInboundPorts\", func(t *testing.T) {\n\t\tvalues, err := testInstallOptions()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t\t}\n\t\tvalues.ProxyInit.IgnoreInboundPorts = \"-25\"\n\t\terr = validateValues(context.Background(), nil, values)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expected error but got nothing\")\n\t\t}\n\t})\n\n\tt.Run(\"Fails validation for invalid ignoreOutboundPorts\", func(t *testing.T) {\n\t\tvalues, err := testInstallOptions()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t\t}\n\t\tvalues.ProxyInit.IgnoreOutboundPorts = \"-25\"\n\t\terr = validateValues(context.Background(), nil, values)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expected error but got nothing\")\n\t\t}\n\t})\n}\n\nfunc testInstallOptionsFakeCerts() (*charts.Values, error) {\n\tvalues, err := testInstallOptions()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\taddFakeTLSSecrets(values)\n\treturn values, nil\n}\n\nfunc testInstallOptions() (*charts.Values, error) {\n\treturn testInstallOptionsHA(false)\n}\n\nfunc testInstallOptionsHA(ha bool) (*charts.Values, error) {\n\tvalues, err := testInstallOptionsNoCerts(ha)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(\"testdata\", \"valid-crt.pem\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcrt, err := tls.DecodePEMCrt(string(data))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvalues.Identity.Issuer.TLS.CrtPEM = crt.EncodeCertificatePEM()\n\n\tkey, err := loadKeyPEM(filepath.Join(\"testdata\", \"valid-key.pem\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvalues.Identity.Issuer.TLS.KeyPEM = key\n\n\tdata, err = os.ReadFile(filepath.Join(\"testdata\", \"valid-trust-anchors.pem\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvalues.IdentityTrustAnchorsPEM = string(data)\n\n\treturn values, nil\n}\n\nfunc testInstallOptionsNoCerts(ha bool) (*charts.Values, error) {\n\tvalues, err := charts.NewValues()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif ha {\n\t\tif err = charts.MergeHAValues(values); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvalues.Proxy.Image.Version = installProxyVersion\n\tvalues.DebugContainer.Image.Version = installDebugVersion\n\tvalues.LinkerdVersion = installControlPlaneVersion\n\tvalues.HeartbeatSchedule = fakeHeartbeatSchedule()\n\n\treturn values, nil\n}\n\nfunc testInstallValues() (*charts.Values, error) {\n\tvalues, err := charts.NewValues()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalues.Proxy.Image.Version = installProxyVersion\n\tvalues.DebugContainer.Image.Version = installDebugVersion\n\tvalues.LinkerdVersion = installControlPlaneVersion\n\tvalues.HeartbeatSchedule = fakeHeartbeatSchedule()\n\n\tidentityCert, err := os.ReadFile(filepath.Join(\"testdata\", \"valid-crt.pem\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tidentityKey, err := os.ReadFile(filepath.Join(\"testdata\", \"valid-key.pem\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttrustAnchorsPEM, err := os.ReadFile(filepath.Join(\"testdata\", \"valid-trust-anchors.pem\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvalues.Identity.Issuer.TLS.CrtPEM = string(identityCert)\n\tvalues.Identity.Issuer.TLS.KeyPEM = string(identityKey)\n\tvalues.IdentityTrustAnchorsPEM = string(trustAnchorsPEM)\n\treturn values, nil\n}\n\nfunc TestValidate(t *testing.T) {\n\tt.Run(\"Accepts the default options as valid\", func(t *testing.T) {\n\t\tvalues, err := testInstallOptions()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t\t}\n\n\t\tif err := validateValues(context.Background(), nil, values); err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"Rejects invalid destination networks\", func(t *testing.T) {\n\t\tvalues, err := testInstallOptions()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t\t}\n\n\t\tvalues.ClusterNetworks = \"wrong\"\n\t\texpected := \"cannot parse destination get networks: invalid CIDR address: wrong\"\n\n\t\terr = validateValues(context.Background(), nil, values)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error, got nothing\")\n\t\t}\n\t\tif err.Error() != expected {\n\t\t\tt.Fatalf(\"Expected error string\\\"%s\\\", got \\\"%s\\\"\", expected, err)\n\t\t}\n\t})\n\n\tt.Run(\"Rejects invalid controller log level\", func(t *testing.T) {\n\t\tvalues, err := testInstallOptions()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t\t}\n\n\t\tvalues.ControllerLogLevel = \"super\"\n\t\texpected := \"--controller-log-level must be one of: panic, fatal, error, warn, info, debug, trace\"\n\n\t\terr = validateValues(context.Background(), nil, values)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error, got nothing\")\n\t\t}\n\t\tif err.Error() != expected {\n\t\t\tt.Fatalf(\"Expected error string\\\"%s\\\", got \\\"%s\\\"\", expected, err)\n\t\t}\n\t})\n\n\tt.Run(\"Properly validates proxy log level\", func(t *testing.T) {\n\t\ttestCases := []struct {\n\t\t\tinput string\n\t\t\tvalid bool\n\t\t}{\n\t\t\t{\"\", false},\n\t\t\t{\"off\", true},\n\t\t\t{\"info\", true},\n\t\t\t{\"somemodule\", true},\n\t\t\t{\"bad%name\", false},\n\t\t\t{\"linkerd=debug\", true},\n\t\t\t{\"linkerd2%proxy=debug\", false},\n\t\t\t{\"linkerd=foobar\", false},\n\t\t\t{\"linker2d_proxy,std::option\", true},\n\t\t\t{\"warn,linkerd=info\", true},\n\t\t\t{\"warn,linkerd=foobar\", false},\n\t\t}\n\n\t\tvalues, err := testInstallOptions()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\tvalues.Proxy.LogLevel = tc.input\n\t\t\terr := validateValues(context.Background(), nil, values)\n\t\t\tif tc.valid && err != nil {\n\t\t\t\tt.Fatalf(\"Error not expected: %s\", err)\n\t\t\t}\n\t\t\tif !tc.valid && err == nil {\n\t\t\t\tt.Fatalf(\"Expected error string \\\"%s is not a valid proxy log level\\\", got nothing\", tc.input)\n\t\t\t}\n\t\t\texpectedErr := fmt.Sprintf(\"\\\"%s\\\" is not a valid proxy log level - for allowed syntax check https://docs.rs/env_logger/0.6.0/env_logger/#enabling-logging\", tc.input)\n\t\t\tif tc.input == \"\" {\n\t\t\t\texpectedErr = \"--proxy-log-level must not be empty\"\n\t\t\t}\n\t\t\tif !tc.valid && err.Error() != expectedErr {\n\t\t\t\tt.Fatalf(\"Expected error string \\\"%s\\\", got \\\"%s\\\"; input=\\\"%s\\\"\", expectedErr, err, tc.input)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Validates the issuer certs upon install\", func(t *testing.T) {\n\n\t\ttestCases := []struct {\n\t\t\tcrtFilePrefix string\n\t\t\texpectedError string\n\t\t}{\n\t\t\t{\"valid\", \"\"},\n\t\t\t{\"valid-with-rsa-anchor\", \"\"},\n\t\t\t{\"expired\", \"failed to validate issuer credentials: not valid anymore. Expired on 1990-01-01T01:01:11Z\"},\n\t\t\t{\"not-valid-yet\", \"failed to validate issuer credentials: not valid before: 2100-01-01T01:00:51Z\"},\n\t\t\t{\"wrong-algo\", \"failed to validate issuer credentials: must use P-256 curve for public key, instead P-521 was used\"},\n\t\t}\n\t\tfor _, tc := range testCases {\n\n\t\t\tvalues, err := testInstallOptions()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t\t\t}\n\n\t\t\tcrt, err := loadCrtPEM(filepath.Join(\"testdata\", tc.crtFilePrefix+\"-crt.pem\"))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tvalues.Identity.Issuer.TLS.CrtPEM = crt\n\n\t\t\tkey, err := loadKeyPEM(filepath.Join(\"testdata\", tc.crtFilePrefix+\"-key.pem\"))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tvalues.Identity.Issuer.TLS.KeyPEM = key\n\n\t\t\tca, err := os.ReadFile(filepath.Join(\"testdata\", tc.crtFilePrefix+\"-trust-anchors.pem\"))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tvalues.IdentityTrustAnchorsPEM = string(ca)\n\n\t\t\terr = validateValues(context.Background(), nil, values)\n\n\t\t\tif tc.expectedError != \"\" {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatal(\"Expected error, got nothing\")\n\t\t\t\t}\n\t\t\t\tif err.Error() != tc.expectedError {\n\t\t\t\t\tt.Fatalf(\"Expected error string\\\"%s\\\", got \\\"%s\\\"\", tc.expectedError, err)\n\t\t\t\t}\n\t\t\t} else if err != nil {\n\t\t\t\tt.Fatalf(\"Expected no error but got \\\"%s\\\"\", err)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Rejects identity cert files data when external issuer is set\", func(t *testing.T) {\n\n\t\tvalues, err := testInstallOptionsNoCerts(false)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t\t}\n\n\t\tvalues.Identity.Issuer.Scheme = string(corev1.SecretTypeTLS)\n\n\t\twithoutCertDataOptions, _ := values.DeepCopy()\n\n\t\twithCrtFile, _ := values.DeepCopy()\n\t\twithCrtFile.Identity.Issuer.TLS.CrtPEM = \"certificate\"\n\n\t\twithKeyFile, _ := values.DeepCopy()\n\t\twithKeyFile.Identity.Issuer.TLS.KeyPEM = \"key\"\n\n\t\ttestCases := []struct {\n\t\t\tinput         *charts.Values\n\t\t\texpectedError string\n\t\t}{\n\t\t\t{withoutCertDataOptions, \"\"},\n\t\t\t{withCrtFile, \"--identity-issuer-certificate-file must not be specified if --identity-external-issuer=true\"},\n\t\t\t{withKeyFile, \"--identity-issuer-key-file must not be specified if --identity-external-issuer=true\"},\n\t\t}\n\n\t\tfor _, tc := range testCases {\n\t\t\terr = validateValues(context.Background(), nil, tc.input)\n\n\t\t\tif tc.expectedError != \"\" {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatalf(\"Expected error '%s', got nothing\", tc.expectedError)\n\t\t\t\t}\n\t\t\t\tif err.Error() != tc.expectedError {\n\t\t\t\t\tt.Fatalf(\"Expected error string\\\"%s\\\", got \\\"%s\\\"\", tc.expectedError, err)\n\t\t\t\t}\n\t\t\t} else if err != nil {\n\t\t\t\tt.Fatalf(\"Expected no error but got \\\"%s\\\"\", err)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Rejects invalid default-inbound-policy\", func(t *testing.T) {\n\t\tvalues, err := testInstallOptions()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t\t}\n\t\tvalues.Proxy.DefaultInboundPolicy = \"everybody\"\n\t\texpected := \"--default-inbound-policy must be one of: all-authenticated, all-unauthenticated, cluster-authenticated, cluster-unauthenticated, deny, audit (got everybody)\"\n\n\t\terr = validateValues(context.Background(), nil, values)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error, got nothing\")\n\t\t}\n\t\tif err.Error() != expected {\n\t\t\tt.Fatalf(\"Expected error string \\\"%s\\\", got \\\"%s\\\"\", expected, err)\n\t\t}\n\t})\n}\n\nfunc fakeHeartbeatSchedule() string {\n\treturn \"1 2 3 4 5\"\n}\n\nfunc addFakeTLSSecrets(values *charts.Values) {\n\tvalues.ProxyInjector.ExternalSecret = true\n\tvalues.ProxyInjector.CaBundle = \"proxy injector CA bundle\"\n\tvalues.ProxyInjector.InjectCaFrom = \"\"\n\tvalues.ProxyInjector.InjectCaFromSecret = \"\"\n\tvalues.ProfileValidator.ExternalSecret = true\n\tvalues.ProfileValidator.CaBundle = \"profile validator CA bundle\"\n\tvalues.ProfileValidator.InjectCaFrom = \"\"\n\tvalues.ProfileValidator.InjectCaFromSecret = \"\"\n\tvalues.PolicyValidator.ExternalSecret = true\n\tvalues.PolicyValidator.CaBundle = \"policy validator CA bundle\"\n\tvalues.PolicyValidator.InjectCaFrom = \"\"\n\tvalues.PolicyValidator.InjectCaFromSecret = \"\"\n}\n"
  },
  {
    "path": "cli/cmd/main_test.go",
    "content": "package cmd\n\nimport (\n\t\"flag\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\nvar (\n\ttestDataDiffer testutil.TestDataDiffer\n)\n\n// TestMain parses flags before running tests\nfunc TestMain(m *testing.M) {\n\tflag.BoolVar(&testDataDiffer.UpdateFixtures, \"update\", false, \"update text fixtures in place\")\n\tprettyDiff := os.Getenv(\"LINKERD_TEST_PRETTY_DIFF\") != \"\"\n\tflag.BoolVar(&testDataDiffer.PrettyDiff, \"pretty-diff\", prettyDiff, \"display the full text when diffing\")\n\tflag.StringVar(&testDataDiffer.RejectPath, \"reject-path\", \"\", \"write results for failed tests to this path (path is relative to the test location)\")\n\tflag.Parse()\n\tos.Exit(m.Run())\n}\n"
  },
  {
    "path": "cli/cmd/metrics.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"time\"\n\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/spf13/cobra\"\n)\n\ntype metricsOptions struct {\n\tnamespace string\n\tpod       string\n\tobfuscate bool\n}\n\nfunc newMetricsOptions() *metricsOptions {\n\treturn &metricsOptions{\n\t\tpod:       \"\",\n\t\tobfuscate: false,\n\t}\n}\n\nfunc newCmdMetrics() *cobra.Command {\n\toptions := newMetricsOptions()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"proxy-metrics [flags] (RESOURCE)\",\n\t\tShort: \"Fetch metrics directly from Linkerd proxies\",\n\t\tLong: `Fetch metrics directly from Linkerd proxies.\n\n  This command initiates a port-forward to a given pod or set of pods, and\n  queries the /metrics endpoint on the Linkerd proxies.\n\n  The RESOURCE argument specifies the target resource to query metrics for:\n  (TYPE/NAME)\n\n  Examples:\n  * cronjob/my-cronjob\n  * deploy/my-deploy\n  * ds/my-daemonset\n  * job/my-job\n  * po/mypod1\n  * rc/my-replication-controller\n  * sts/my-statefulset\n\n  Valid resource types include:\n  * cronjobs\n  * daemonsets\n  * deployments\n  * jobs\n  * pods\n  * replicasets\n  * replicationcontrollers\n  * statefulsets`,\n\t\tExample: `  # Get metrics from pod-foo-bar in the default namespace.\n  linkerd diagnostics proxy-metrics po/pod-foo-bar\n\n  # Get metrics from the web deployment in the emojivoto namespace.\n  linkerd diagnostics proxy-metrics -n emojivoto deploy/web\n\n  # Get metrics from the linkerd-destination pod in the linkerd namespace.\n  linkerd diagnostics proxy-metrics -n linkerd $(\n    kubectl --namespace linkerd get pod \\\n      --selector linkerd.io/control-plane-component=destination \\\n      --output name\n  )`,\n\t\tArgs: cobra.ExactArgs(1),\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tpods, err := k8s.GetPodsFor(cmd.Context(), k8sAPI, options.namespace, args[0])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tresults := getMetrics(k8sAPI, pods, k8s.ProxyAdminPortName, 30*time.Second, verbose)\n\n\t\t\tvar buf bytes.Buffer\n\t\t\tfor i, result := range results {\n\t\t\t\tcontent := fmt.Sprintf(\"#\\n# POD %s (%d of %d)\\n#\\n\", result.pod, i+1, len(results))\n\t\t\t\tswitch {\n\t\t\t\tcase result.err != nil:\n\t\t\t\t\tcontent += fmt.Sprintf(\"# ERROR: %s\\n\", result.err)\n\t\t\t\tcase options.obfuscate:\n\t\t\t\t\tobfuscatedMetrics, err := obfuscateMetrics(result.metrics)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontent += fmt.Sprintf(\"# ERROR %s\\n\", err)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontent += string(obfuscatedMetrics)\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tcontent += string(result.metrics)\n\t\t\t\t}\n\n\t\t\t\tbuf.WriteString(content)\n\t\t\t}\n\t\t\tfmt.Printf(\"%s\", buf.String())\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace, \"Namespace of resource\")\n\tcmd.PersistentFlags().BoolVar(&options.obfuscate, \"obfuscate\", options.obfuscate, \"Obfuscate sensitive information\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(cmd, []string{\"namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\n\treturn cmd\n}\n"
  },
  {
    "path": "cli/cmd/metrics_diagnostics_util.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/prometheus/common/expfmt\"\n\t\"github.com/prometheus/common/model\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// shared between metrics and diagnostics command\ntype metricsResult struct {\n\tpod       string\n\tcontainer string\n\tmetrics   []byte\n\terr       error\n}\ntype byResult []metricsResult\n\nfunc (s byResult) Len() int {\n\treturn len(s)\n}\nfunc (s byResult) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\nfunc (s byResult) Less(i, j int) bool {\n\treturn s[i].pod < s[j].pod || ((s[i].pod == s[j].pod) && s[i].container < s[j].container)\n}\n\n// getAllContainersWithPortSuffix returns all the containers within\n// a pod which exposes metrics at a port with the given suffix\nfunc getAllContainersWithPortSuffix(\n\tpod corev1.Pod,\n\tportName string,\n) ([]corev1.Container, error) {\n\tif pod.Status.Phase != corev1.PodRunning {\n\t\treturn nil, fmt.Errorf(\"pod not running: %s\", pod.GetName())\n\t}\n\tvar containers []corev1.Container\n\n\tallContainers := append(pod.Spec.InitContainers, pod.Spec.Containers...)\n\tfor _, c := range allContainers {\n\t\tfor _, p := range c.Ports {\n\t\t\tif strings.HasSuffix(p.Name, portName) {\n\t\t\t\tcontainers = append(containers, c)\n\t\t\t}\n\t\t}\n\t}\n\treturn containers, nil\n}\n\n// getMetrics returns the metrics exposed by all the containers of the passed in list of pods\n// which exposes their metrics at portName\nfunc getMetrics(\n\tk8sAPI *k8s.KubernetesAPI,\n\tpods []corev1.Pod,\n\tportName string,\n\twaitingTime time.Duration,\n\temitLogs bool,\n) []metricsResult {\n\tvar results []metricsResult\n\n\tresultChan := make(chan metricsResult)\n\tvar activeRoutines int32\n\tfor _, pod := range pods {\n\t\tatomic.AddInt32(&activeRoutines, 1)\n\t\tgo func(p corev1.Pod) {\n\t\t\tdefer atomic.AddInt32(&activeRoutines, -1)\n\t\t\tcontainers, err := getAllContainersWithPortSuffix(p, portName)\n\t\t\tif err != nil {\n\t\t\t\tresultChan <- metricsResult{\n\t\t\t\t\tpod: p.GetName(),\n\t\t\t\t\terr: err,\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor _, c := range containers {\n\t\t\t\tcname := portName\n\t\t\t\tfor _, cp := range c.Ports {\n\t\t\t\t\tif strings.HasSuffix(cp.Name, portName) {\n\t\t\t\t\t\tcname = cp.Name\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbytes, err := k8s.GetContainerMetrics(k8sAPI, p, c, emitLogs, cname)\n\n\t\t\t\tresultChan <- metricsResult{\n\t\t\t\t\tpod:       p.GetName(),\n\t\t\t\t\tcontainer: c.Name,\n\t\t\t\t\tmetrics:   bytes,\n\t\t\t\t\terr:       err,\n\t\t\t\t}\n\t\t\t}\n\t\t}(pod)\n\t}\n\n\ttimeout := time.NewTimer(waitingTime)\n\tdefer timeout.Stop()\nwait:\n\tfor {\n\t\tselect {\n\t\tcase result := <-resultChan:\n\t\t\tresults = append(results, result)\n\t\tcase <-timeout.C:\n\t\t\tbreak wait // timed out\n\t\t}\n\t\tif atomic.LoadInt32(&activeRoutines) == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tsort.Sort(byResult(results))\n\n\treturn results\n}\n\nvar obfuscationMap = map[string]struct{}{\n\t\"authority\":     {},\n\t\"client_id\":     {},\n\t\"server_id\":     {},\n\t\"target_addr\":   {},\n\t\"dst_service\":   {},\n\t\"dst_namespace\": {},\n}\n\nfunc obfuscateMetrics(metrics []byte) ([]byte, error) {\n\treader := bytes.NewReader(metrics)\n\n\tmetricsParser := expfmt.NewTextParser(model.LegacyValidation)\n\n\tparsedMetrics, err := metricsParser.TextToMetricFamilies(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar writer bytes.Buffer\n\tfor _, v := range parsedMetrics {\n\t\tfor _, m := range v.Metric {\n\t\t\tfor _, l := range m.Label {\n\t\t\t\tif _, ok := obfuscationMap[l.GetName()]; ok {\n\t\t\t\t\tobfuscatedValue := obfuscate(l.GetValue())\n\t\t\t\t\tl.Value = &obfuscatedValue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// We'll assume MetricFamilyToText errors are insignificant\n\t\t//nolint:errcheck\n\t\texpfmt.MetricFamilyToText(&writer, v)\n\t}\n\n\treturn writer.Bytes(), nil\n}\n\nfunc obfuscate(s string) string {\n\thash := sha256.Sum256([]byte(s))\n\treturn fmt.Sprintf(\"%x\", hash[:4])\n}\n"
  },
  {
    "path": "cli/cmd/metrics_diagnostics_util_test.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestGetAllContainersWithPort(t *testing.T) {\n\ttests := []struct {\n\t\tns         string\n\t\tname       string\n\t\tk8sConfigs []string\n\t\terr        error\n\t}{\n\t\t{\n\t\t\t\"pod-ns\",\n\t\t\t\"pod-name\",\n\t\t\t[]string{`apiVersion: v1\nkind: Pod\nmetadata:\n  name: pod-name\n  namespace: pod-ns\nstatus:\n  phase: Stopped\nspec:\n  containers:\n  - name: linkerd-proxy\n    ports:\n    - name: linkerd-admin\n      port: 123`,\n\t\t\t},\n\t\t\terrors.New(\"pod not running: pod-name\"),\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tfor i, test := range tests {\n\t\ttest := test // pin\n\t\tt.Run(fmt.Sprintf(\"%d: getAllContainersWithPortSuffix returns expected result\", i), func(t *testing.T) {\n\t\t\tk8sClient, err := k8s.NewFakeAPI(test.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error %s\", err)\n\t\t\t}\n\t\t\tpod, err := k8sClient.CoreV1().Pods(test.ns).Get(ctx, test.name, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error %s\", err)\n\t\t\t}\n\t\t\t_, err = getAllContainersWithPortSuffix(*pod, \"admin\")\n\t\t\tif err != nil || test.err != nil {\n\t\t\t\tif (err == nil && test.err != nil) ||\n\t\t\t\t\t(err != nil && test.err == nil) ||\n\t\t\t\t\t(err.Error() != test.err.Error()) {\n\t\t\t\t\tt.Fatalf(\"Unexpected error (Expected: %s, Got: %s)\", test.err, err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_obfuscateMetrics(t *testing.T) {\n\ttests := []struct {\n\t\tinputFileName  string\n\t\tgoldenFileName string\n\t\twantErr        bool\n\t}{\n\t\t{\n\t\t\tinputFileName:  \"obfuscate-diagnostics-proxy-metrics.input\",\n\t\t\tgoldenFileName: \"obfuscate-diagnostics-proxy-metrics.golden\",\n\t\t\twantErr:        false,\n\t\t},\n\t}\n\tfor i, tc := range tests {\n\t\ttc := tc\n\t\tt.Run(fmt.Sprintf(\"%d: %s\", i, tc.inputFileName), func(t *testing.T) {\n\t\t\tfile, err := os.Open(\"testdata/\" + tc.inputFileName)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error opening test input file: %v\\n\", err)\n\t\t\t}\n\n\t\t\tfileBytes, err := io.ReadAll(file)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error reading test input file: %v\\n\", err)\n\t\t\t}\n\n\t\t\tgot, err := obfuscateMetrics(fileBytes)\n\t\t\tif (err != nil) != tc.wantErr {\n\t\t\t\tt.Errorf(\"obfuscateMetrics() error = %v, wantErr %v\", err, tc.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttestDataDiffer.DiffTestdata(t, tc.goldenFileName, string(got))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cli/cmd/options.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/cli/flag\"\n\tl5dcharts \"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/linkerd/linkerd2/pkg/cmd\"\n\tflagspkg \"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/inject\"\n\t\"github.com/linkerd/linkerd2/pkg/issuercerts\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/pflag\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tk8sResource \"k8s.io/apimachinery/pkg/api/resource\"\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n)\n\n// makeInstallUpgradeFlags builds the set of flags which are used by install and\n// upgrade.  These flags control the majority of how the control plane is\n// configured.\nfunc makeInstallUpgradeFlags(defaults *l5dcharts.Values) ([]flag.Flag, *pflag.FlagSet, error) {\n\tinstallUpgradeFlags := pflag.NewFlagSet(\"install\", pflag.ExitOnError)\n\n\tissuanceLifetime, err := time.ParseDuration(defaults.Identity.Issuer.IssuanceLifetime)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tclockSkewAllowance, err := time.ParseDuration(defaults.Identity.Issuer.ClockSkewAllowance)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tflags := []flag.Flag{\n\t\tflag.NewBoolFlag(installUpgradeFlags, \"linkerd-cni-enabled\", defaults.CNIEnabled,\n\t\t\t\"Omit the NET_ADMIN capability in the PSP and the proxy-init container when injecting the proxy; requires the linkerd-cni plugin to already be installed\",\n\t\t\tfunc(values *l5dcharts.Values, value bool) error {\n\t\t\t\tvalues.CNIEnabled = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringFlag(installUpgradeFlags, \"controller-log-level\", defaults.ControllerLogLevel,\n\t\t\t\"Log level for the controller and web components\", func(values *l5dcharts.Values, value string) error {\n\t\t\t\tvalues.ControllerLogLevel = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\t// The HA flag must be processed before flags that set these values individually so that the\n\t\t// individual flags can override the HA defaults.\n\t\tflag.NewBoolFlag(installUpgradeFlags, \"ha\", false, \"Enable HA deployment config for the control plane (default false)\",\n\t\t\tfunc(values *l5dcharts.Values, value bool) error {\n\t\t\t\tvalues.HighAvailability = value\n\t\t\t\tif value {\n\t\t\t\t\tif err := l5dcharts.MergeHAValues(values); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewUintFlag(installUpgradeFlags, \"controller-replicas\", defaults.ControllerReplicas,\n\t\t\t\"Replicas of the controller to deploy\", func(values *l5dcharts.Values, value uint) error {\n\t\t\t\tvalues.ControllerReplicas = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewInt64Flag(installUpgradeFlags, \"controller-uid\", defaults.ControllerUID,\n\t\t\t\"Run the control plane components under this user ID\", func(values *l5dcharts.Values, value int64) error {\n\t\t\t\tvalues.ControllerUID = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewInt64Flag(installUpgradeFlags, \"controller-gid\", defaults.ControllerGID,\n\t\t\t\"Run the control plane components under this group ID\", func(values *l5dcharts.Values, value int64) error {\n\t\t\t\tvalues.ControllerGID = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewBoolFlag(installUpgradeFlags, \"disable-h2-upgrade\", !defaults.EnableH2Upgrade,\n\t\t\t\"Prevents the controller from instructing proxies to perform transparent HTTP/2 upgrading (default false)\",\n\t\t\tfunc(values *l5dcharts.Values, value bool) error {\n\t\t\t\tvalues.EnableH2Upgrade = !value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewBoolFlag(installUpgradeFlags, \"disable-heartbeat\", defaults.DisableHeartBeat,\n\t\t\t\"Disables the heartbeat cronjob (default false)\", func(values *l5dcharts.Values, value bool) error {\n\t\t\t\tvalues.DisableHeartBeat = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewDurationFlag(installUpgradeFlags, \"identity-issuance-lifetime\", issuanceLifetime,\n\t\t\t\"The amount of time for which the Identity issuer should certify identity\",\n\t\t\tfunc(values *l5dcharts.Values, value time.Duration) error {\n\t\t\t\tvalues.Identity.Issuer.IssuanceLifetime = value.String()\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewDurationFlag(installUpgradeFlags, \"identity-clock-skew-allowance\", clockSkewAllowance,\n\t\t\t\"The amount of time to allow for clock skew within a Linkerd cluster\",\n\t\t\tfunc(values *l5dcharts.Values, value time.Duration) error {\n\t\t\t\tvalues.Identity.Issuer.ClockSkewAllowance = value.String()\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringFlag(installUpgradeFlags, \"identity-issuer-certificate-file\", \"\",\n\t\t\t\"A path to a PEM-encoded file containing the Linkerd Identity issuer certificate (generated by default)\",\n\t\t\tfunc(values *l5dcharts.Values, value string) error {\n\t\t\t\tif value != \"\" {\n\t\t\t\t\tcrt, err := loadCrtPEM(value)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tvalues.Identity.Issuer.TLS.CrtPEM = crt\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringFlag(installUpgradeFlags, \"identity-issuer-key-file\", \"\",\n\t\t\t\"A path to a PEM-encoded file containing the Linkerd Identity issuer private key (generated by default)\",\n\t\t\tfunc(values *l5dcharts.Values, value string) error {\n\t\t\t\tif value != \"\" {\n\t\t\t\t\tkey, err := loadKeyPEM(value)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tvalues.Identity.Issuer.TLS.KeyPEM = key\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringFlag(installUpgradeFlags, \"identity-trust-anchors-file\", \"\",\n\t\t\t\"A path to a PEM-encoded file containing Linkerd Identity trust anchors (generated by default)\",\n\t\t\tfunc(values *l5dcharts.Values, value string) error {\n\t\t\t\tif value != \"\" {\n\t\t\t\t\tdata, err := os.ReadFile(filepath.Clean(value))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tvalues.IdentityTrustAnchorsPEM = string(data)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewBoolFlag(installUpgradeFlags, \"enable-endpoint-slices\", defaults.EnableEndpointSlices,\n\t\t\t\"Enables the usage of EndpointSlice informers and resources for destination service\",\n\t\t\tfunc(values *l5dcharts.Values, value bool) error {\n\t\t\t\tvalues.EnableEndpointSlices = value\n\t\t\t\treturn nil\n\t\t\t}),\n\t}\n\n\t// Hide developer focused flags in release builds.\n\trelease, err := version.IsReleaseChannel(version.Version)\n\tif err != nil {\n\t\tlog.Errorf(\"Unable to parse version: %s\", version.Version)\n\t}\n\tif release {\n\t\tinstallUpgradeFlags.MarkHidden(\"control-plane-version\")\n\t}\n\n\treturn flags, installUpgradeFlags, nil\n}\n\nfunc loadCrtPEM(path string) (string, error) {\n\tdata, err := os.ReadFile(filepath.Clean(path))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tcrt, err := tls.DecodePEMCrt(string(data))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn crt.EncodeCertificatePEM(), nil\n}\n\nfunc loadKeyPEM(path string) (string, error) {\n\tdata, err := os.ReadFile(filepath.Clean(path))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tkey, err := tls.DecodePEMKey(string(data))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tcred := tls.Cred{PrivateKey: key}\n\treturn cred.EncodePrivateKeyPEM(), nil\n}\n\n// makeInstallFlags builds the set of flags which are used by install.  These\n// flags should not be changed during an upgrade and are not available to the\n// upgrade command.\nfunc makeInstallFlags(defaults *l5dcharts.Values) ([]flag.Flag, *pflag.FlagSet) {\n\n\tinstallOnlyFlags := pflag.NewFlagSet(\"install-only\", pflag.ExitOnError)\n\n\tflags := []flag.Flag{\n\t\tflag.NewStringFlag(installOnlyFlags, \"cluster-domain\", defaults.ClusterDomain,\n\t\t\t\"Set custom cluster domain\", func(values *l5dcharts.Values, value string) error {\n\t\t\t\tvalues.ClusterDomain = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringFlag(installOnlyFlags, \"identity-trust-domain\", defaults.IdentityTrustDomain,\n\t\t\t\"Configures the name suffix used for identities.\", func(values *l5dcharts.Values, value string) error {\n\t\t\t\tvalues.IdentityTrustDomain = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewBoolFlag(installOnlyFlags, \"identity-external-issuer\", false,\n\t\t\t\"Whether to use an external identity issuer (default false)\", func(values *l5dcharts.Values, value bool) error {\n\t\t\t\tif value {\n\t\t\t\t\tvalues.Identity.Issuer.Scheme = string(corev1.SecretTypeTLS)\n\t\t\t\t} else {\n\t\t\t\t\tvalues.Identity.Issuer.Scheme = k8s.IdentityIssuerSchemeLinkerd\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewBoolFlag(installOnlyFlags, \"identity-external-ca\", false,\n\t\t\t\"Whether to use an external CA provider (default false)\", func(values *l5dcharts.Values, value bool) error {\n\t\t\t\tvalues.Identity.ExternalCA = value\n\t\t\t\treturn nil\n\t\t\t}),\n\t}\n\n\treturn flags, installOnlyFlags\n}\n\n// makeProxyFlags builds the set of flags which affect how the proxy is\n// configured.  These flags are available to the inject command and to the\n// install and upgrade commands.\nfunc makeProxyFlags(defaults *l5dcharts.Values) ([]flag.Flag, *pflag.FlagSet) {\n\tproxyFlags := pflag.NewFlagSet(\"proxy\", pflag.ExitOnError)\n\n\tflags := []flag.Flag{\n\t\tflag.NewStringFlag(proxyFlags, \"proxy-image\", defaults.Proxy.Image.Name, \"Linkerd proxy container image name\",\n\t\t\tfunc(values *l5dcharts.Values, value string) error {\n\t\t\t\tvalues.Proxy.Image.Name = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringFlag(proxyFlags, \"image-pull-policy\", defaults.ImagePullPolicy,\n\t\t\t\"Docker image pull policy\", func(values *l5dcharts.Values, value string) error {\n\t\t\t\tvalues.ImagePullPolicy = value\n\t\t\t\tvalues.Proxy.Image.PullPolicy = value\n\t\t\t\tvalues.DebugContainer.Image.PullPolicy = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewUintFlag(proxyFlags, \"inbound-port\", uint(defaults.Proxy.Ports.Inbound),\n\t\t\t\"Proxy port to use for inbound traffic\", func(values *l5dcharts.Values, value uint) error {\n\t\t\t\tvalues.Proxy.Ports.Inbound = int32(value)\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewUintFlag(proxyFlags, \"outbound-port\", uint(defaults.Proxy.Ports.Outbound),\n\t\t\t\"Proxy port to use for outbound traffic\", func(values *l5dcharts.Values, value uint) error {\n\t\t\t\tvalues.Proxy.Ports.Outbound = int32(value)\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringSliceFlag(proxyFlags, \"skip-inbound-ports\", nil, \"Ports and/or port ranges (inclusive) that should skip the proxy and send directly to the application\",\n\t\t\tfunc(values *l5dcharts.Values, value []string) error {\n\t\t\t\tvalues.ProxyInit.IgnoreInboundPorts = strings.Join(value, \",\")\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringSliceFlag(proxyFlags, \"skip-outbound-ports\", nil, \"Outbound ports and/or port ranges (inclusive) that should skip the proxy\",\n\t\t\tfunc(values *l5dcharts.Values, value []string) error {\n\t\t\t\tvalues.ProxyInit.IgnoreOutboundPorts = strings.Join(value, \",\")\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewInt64Flag(proxyFlags, \"proxy-uid\", defaults.Proxy.UID, \"Run the proxy under this user ID\",\n\t\t\tfunc(values *l5dcharts.Values, value int64) error {\n\t\t\t\tvalues.Proxy.UID = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewInt64Flag(proxyFlags, \"proxy-gid\", defaults.Proxy.GID, \"Run the proxy under this group ID\",\n\t\t\tfunc(values *l5dcharts.Values, value int64) error {\n\t\t\t\tvalues.Proxy.GID = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringFlag(proxyFlags, \"proxy-log-level\", defaults.Proxy.LogLevel, \"Log level for the proxy\",\n\t\t\tfunc(values *l5dcharts.Values, value string) error {\n\t\t\t\tvalues.Proxy.LogLevel = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewUintFlag(proxyFlags, \"control-port\", uint(defaults.Proxy.Ports.Control), \"Proxy port to use for control\",\n\t\t\tfunc(values *l5dcharts.Values, value uint) error {\n\t\t\t\tvalues.Proxy.Ports.Control = int32(value)\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewUintFlag(proxyFlags, \"admin-port\", uint(defaults.Proxy.Ports.Admin), \"Proxy port to serve metrics on\",\n\t\t\tfunc(values *l5dcharts.Values, value uint) error {\n\t\t\t\tvalues.Proxy.Ports.Admin = int32(value)\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringFlag(proxyFlags, \"proxy-cpu-request\", defaults.Proxy.Resources.CPU.Request, \"Amount of CPU units that the proxy sidecar requests\",\n\t\t\tfunc(values *l5dcharts.Values, value string) error {\n\t\t\t\tq, err := k8sResource.ParseQuantity(value)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tc, err := inject.ToWholeCPUCores(q)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tvalues.Proxy.Runtime.Workers.Minimum = c\n\t\t\t\tvalues.Proxy.Resources.CPU.Request = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringFlag(proxyFlags, \"proxy-cpu-limit\", defaults.Proxy.Resources.CPU.Limit, \"Maximum amount of CPU units that the proxy sidecar can use\",\n\t\t\tfunc(values *l5dcharts.Values, value string) error {\n\t\t\t\tq, err := k8sResource.ParseQuantity(value)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tc, err := inject.ToWholeCPUCores(q)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tvalues.Proxy.Runtime.Workers.Maximum = c\n\t\t\t\tvalues.Proxy.Resources.CPU.Limit = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringFlag(proxyFlags, \"proxy-memory-request\", defaults.Proxy.Resources.Memory.Request, \"Amount of Memory that the proxy sidecar requests\",\n\t\t\tfunc(values *l5dcharts.Values, value string) error {\n\t\t\t\tvalues.Proxy.Resources.Memory.Request = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringFlag(proxyFlags, \"proxy-memory-limit\", defaults.Proxy.Resources.Memory.Limit, \"Maximum amount of Memory that the proxy sidecar can use\",\n\t\t\tfunc(values *l5dcharts.Values, value string) error {\n\t\t\t\tvalues.Proxy.Resources.Memory.Limit = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewBoolFlag(proxyFlags, \"enable-external-profiles\", defaults.Proxy.EnableExternalProfiles, \"Enable service profiles for non-Kubernetes services\",\n\t\t\tfunc(values *l5dcharts.Values, value bool) error {\n\t\t\t\tvalues.Proxy.EnableExternalProfiles = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringFlag(proxyFlags, \"default-inbound-policy\", defaults.Proxy.DefaultInboundPolicy, \"Inbound policy to use to control inbound access to the proxy\",\n\t\t\tfunc(values *l5dcharts.Values, value string) error {\n\t\t\t\tvalues.Proxy.DefaultInboundPolicy = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\t// Deprecated flags\n\n\t\tflag.NewStringFlag(proxyFlags, \"proxy-memory\", defaults.Proxy.Resources.Memory.Request, \"Amount of Memory that the proxy sidecar requests\",\n\t\t\tfunc(values *l5dcharts.Values, value string) error {\n\t\t\t\tvalues.Proxy.Resources.Memory.Request = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringFlag(proxyFlags, \"proxy-cpu\", defaults.Proxy.Resources.CPU.Request, \"Amount of CPU units that the proxy sidecar requests\",\n\t\t\tfunc(values *l5dcharts.Values, value string) error {\n\t\t\t\tvalues.Proxy.Resources.CPU.Request = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringFlagP(proxyFlags, \"proxy-version\", \"v\", defaults.Proxy.Image.Version, \"Tag to be used for the Linkerd proxy images\",\n\t\t\tfunc(values *l5dcharts.Values, value string) error {\n\t\t\t\tvalues.Proxy.Image.Version = value\n\t\t\t\treturn nil\n\t\t\t}),\n\t}\n\n\tregistryFlag := flag.NewStringFlag(proxyFlags, \"registry\", cmd.DefaultDockerRegistry,\n\t\tfmt.Sprintf(\"Docker registry to pull images from ($%s)\", flagspkg.EnvOverrideDockerRegistry),\n\t\tfunc(values *l5dcharts.Values, value string) error {\n\t\t\tvalues.ControllerImage = cmd.RegistryOverride(values.ControllerImage, value)\n\t\t\tvalues.DebugContainer.Image.Name = cmd.RegistryOverride(values.DebugContainer.Image.Name, value)\n\t\t\tvalues.Proxy.Image.Name = cmd.RegistryOverride(values.Proxy.Image.Name, value)\n\t\t\treturn nil\n\t\t})\n\tif reg := os.Getenv(flagspkg.EnvOverrideDockerRegistry); reg != \"\" {\n\t\tregistryFlag.Set(reg)\n\t}\n\tflags = append(flags, registryFlag)\n\n\tproxyFlags.MarkDeprecated(\"proxy-memory\", \"use --proxy-memory-request instead\")\n\tproxyFlags.MarkDeprecated(\"proxy-cpu\", \"use --proxy-cpu-request instead\")\n\tproxyFlags.MarkDeprecated(\"proxy-version\", \"use --set proxy.image.version=<version>\")\n\n\t// Hide developer focused flags in release builds.\n\trelease, err := version.IsReleaseChannel(version.Version)\n\tif err != nil {\n\t\tlog.Errorf(\"Unable to parse version: %s\", version.Version)\n\t}\n\tif release {\n\t\tproxyFlags.MarkHidden(\"proxy-image\")\n\t\tproxyFlags.MarkHidden(\"proxy-version\")\n\t\tproxyFlags.MarkHidden(\"image-pull-policy\")\n\t}\n\n\treturn flags, proxyFlags\n}\n\n// makeInjectFlags builds the set of flags which are exclusive to the inject\n// command.  These flags configure the proxy but are not available to the\n// install and upgrade commands.  This is generally for proxy configuration\n// which is intended to be set on individual workloads rather than being\n// cluster wide.\nfunc makeInjectFlags(defaults *l5dcharts.Values) ([]flag.Flag, *pflag.FlagSet) {\n\tinjectFlags := pflag.NewFlagSet(\"inject\", pflag.ExitOnError)\n\n\tflags := []flag.Flag{\n\t\tflag.NewBoolFlag(injectFlags, \"native-sidecar\", false, \"Enable native sidecar\",\n\t\t\tfunc(values *l5dcharts.Values, value bool) error {\n\t\t\t\tvalues.Proxy.NativeSidecar = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewInt64Flag(injectFlags, \"wait-before-exit-seconds\", int64(defaults.Proxy.WaitBeforeExitSeconds),\n\t\t\t\"The period during which the proxy sidecar must stay alive while its pod is terminating. \"+\n\t\t\t\t\"Must be smaller than terminationGracePeriodSeconds for the pod (default 0)\",\n\t\t\tfunc(values *l5dcharts.Values, value int64) error {\n\t\t\t\tvalues.Proxy.WaitBeforeExitSeconds = uint64(value)\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewBoolFlag(injectFlags, \"disable-identity\", false,\n\t\t\t\"Disables resources from participating in TLS identity\", func(values *l5dcharts.Values, value bool) error {\n\t\t\t\treturn errors.New(\"--disable-identity is no longer supported; identity is always required\")\n\t\t\t}),\n\n\t\tflag.NewStringSliceFlag(injectFlags, \"require-identity-on-inbound-ports\", strings.Split(defaults.Proxy.RequireIdentityOnInboundPorts, \",\"),\n\t\t\t\"Inbound ports on which the proxy should require identity\", func(values *l5dcharts.Values, value []string) error {\n\t\t\t\tvalues.Proxy.RequireIdentityOnInboundPorts = strings.Join(value, \",\")\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewBoolFlag(injectFlags, \"ingress\", defaults.Proxy.IsIngress, \"Enable ingress mode in the linkerd proxy\",\n\t\t\tfunc(values *l5dcharts.Values, value bool) error {\n\t\t\t\tvalues.Proxy.IsIngress = value\n\t\t\t\treturn nil\n\t\t\t}),\n\n\t\tflag.NewStringSliceFlag(injectFlags, \"opaque-ports\", strings.Split(defaults.Proxy.OpaquePorts, \",\"),\n\t\t\t\"Set opaque ports on the proxy\", func(values *l5dcharts.Values, value []string) error {\n\t\t\t\tvalues.Proxy.OpaquePorts = strings.Join(value, \",\")\n\t\t\t\treturn nil\n\t\t\t}),\n\t}\n\tinjectFlags.MarkHidden(\"disable-identity\")\n\n\treturn flags, injectFlags\n}\n\n/* Validation */\n\nfunc validateValues(ctx context.Context, k *k8s.KubernetesAPI, values *l5dcharts.Values) error {\n\tif !alphaNumDashDot.MatchString(values.LinkerdVersion) {\n\t\treturn fmt.Errorf(\"%s is not a valid version\", values.LinkerdVersion)\n\t}\n\n\tif _, err := log.ParseLevel(values.ControllerLogLevel); err != nil {\n\t\treturn fmt.Errorf(\"--controller-log-level must be one of: panic, fatal, error, warn, info, debug, trace\")\n\t}\n\n\tif values.Proxy.LogLevel == \"\" {\n\t\treturn errors.New(\"--proxy-log-level must not be empty\")\n\t}\n\n\tif values.EnableEndpointSlices && k != nil {\n\t\terr := k8s.EndpointSliceAccess(ctx, k)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Validate only if its not empty\n\tif values.IdentityTrustDomain != \"\" {\n\t\tif errs := validation.IsDNS1123Subdomain(values.IdentityTrustDomain); len(errs) > 0 {\n\t\t\treturn fmt.Errorf(\"invalid trust domain '%s': %s\", values.IdentityTrustDomain, errs[0])\n\t\t}\n\t}\n\n\terr := validateProxyValues(values)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif values.Identity.Issuer.Scheme == string(corev1.SecretTypeTLS) {\n\t\tif values.Identity.Issuer.TLS.CrtPEM != \"\" {\n\t\t\treturn errors.New(\"--identity-issuer-certificate-file must not be specified if --identity-external-issuer=true\")\n\t\t}\n\t\tif values.Identity.Issuer.TLS.KeyPEM != \"\" {\n\t\t\treturn errors.New(\"--identity-issuer-key-file must not be specified if --identity-external-issuer=true\")\n\t\t}\n\t}\n\n\tif values.Identity.Issuer.Scheme == string(corev1.SecretTypeTLS) && k != nil {\n\t\texternalIssuerData, err := issuercerts.FetchExternalIssuerData(ctx, k, controlPlaneNamespace)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = externalIssuerData.VerifyAndBuildCreds()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to validate issuer credentials: %w\", err)\n\t\t}\n\t}\n\n\tif values.Identity.Issuer.Scheme == k8s.IdentityIssuerSchemeLinkerd {\n\t\tissuerData := issuercerts.IssuerCertData{\n\t\t\tIssuerCrt:    values.Identity.Issuer.TLS.CrtPEM,\n\t\t\tIssuerKey:    values.Identity.Issuer.TLS.KeyPEM,\n\t\t\tTrustAnchors: values.IdentityTrustAnchorsPEM,\n\t\t}\n\t\t_, err := issuerData.VerifyAndBuildCreds()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to validate issuer credentials: %w\", err)\n\t\t}\n\t}\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc validateProxyValues(values *l5dcharts.Values) error {\n\tnetworks := strings.Split(values.ClusterNetworks, \",\")\n\tfor _, network := range networks {\n\t\tif _, _, err := net.ParseCIDR(network); err != nil {\n\t\t\treturn fmt.Errorf(\"cannot parse destination get networks: %w\", err)\n\t\t}\n\t}\n\n\tif values.Proxy.Image.Version != \"\" && !alphaNumDashDot.MatchString(values.Proxy.Image.Version) {\n\t\treturn fmt.Errorf(\"%s is not a valid version\", values.Proxy.Image.Version)\n\t}\n\n\tif values.ImagePullPolicy != \"Always\" && values.ImagePullPolicy != \"IfNotPresent\" && values.ImagePullPolicy != \"Never\" {\n\t\treturn fmt.Errorf(\"--image-pull-policy must be one of: Always, IfNotPresent, Never\")\n\t}\n\n\tif values.Proxy.Resources.CPU.Request != \"\" {\n\t\tif _, err := k8sResource.ParseQuantity(values.Proxy.Resources.CPU.Request); err != nil {\n\t\t\treturn fmt.Errorf(\"Invalid cpu request '%s' for --proxy-cpu-request flag\", values.Proxy.Resources.CPU.Request)\n\t\t}\n\t}\n\n\tif values.Proxy.Resources.Memory.Request != \"\" {\n\t\tif _, err := k8sResource.ParseQuantity(values.Proxy.Resources.Memory.Request); err != nil {\n\t\t\treturn fmt.Errorf(\"Invalid memory request '%s' for --proxy-memory-request flag\", values.Proxy.Resources.Memory.Request)\n\t\t}\n\t}\n\n\tif values.Proxy.Resources.CPU.Limit != \"\" {\n\t\tcpuLimit, err := k8sResource.ParseQuantity(values.Proxy.Resources.CPU.Limit)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Invalid cpu limit '%s' for --proxy-cpu-limit flag\", values.Proxy.Resources.CPU.Limit)\n\t\t}\n\t\t// Not checking for error because option proxyCPURequest was already validated\n\t\tif cpuRequest, _ := k8sResource.ParseQuantity(values.Proxy.Resources.CPU.Request); cpuRequest.MilliValue() > cpuLimit.MilliValue() {\n\t\t\treturn fmt.Errorf(\"The cpu limit '%s' cannot be lower than the cpu request '%s'\", values.Proxy.Resources.CPU.Limit, values.Proxy.Resources.CPU.Request)\n\t\t}\n\t}\n\n\tif values.Proxy.Resources.Memory.Limit != \"\" {\n\t\tmemoryLimit, err := k8sResource.ParseQuantity(values.Proxy.Resources.Memory.Limit)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Invalid memory limit '%s' for --proxy-memory-limit flag\", values.Proxy.Resources.Memory.Limit)\n\t\t}\n\t\t// Not checking for error because option proxyMemoryRequest was already validated\n\t\tif memoryRequest, _ := k8sResource.ParseQuantity(values.Proxy.Resources.Memory.Request); memoryRequest.Value() > memoryLimit.Value() {\n\t\t\treturn fmt.Errorf(\"The memory limit '%s' cannot be lower than the memory request '%s'\", values.Proxy.Resources.Memory.Limit, values.Proxy.Resources.Memory.Request)\n\t\t}\n\t}\n\n\tif !validProxyLogLevel.MatchString(values.Proxy.LogLevel) {\n\t\treturn fmt.Errorf(\"\\\"%s\\\" is not a valid proxy log level - for allowed syntax check https://docs.rs/env_logger/0.6.0/env_logger/#enabling-logging\",\n\t\t\tvalues.Proxy.LogLevel)\n\t}\n\n\tif values.ProxyInit.IgnoreInboundPorts != \"\" {\n\t\tif err := validateRangeSlice(strings.Split(values.ProxyInit.IgnoreInboundPorts, \",\")); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif values.ProxyInit.IgnoreOutboundPorts != \"\" {\n\t\tif err := validateRangeSlice(strings.Split(values.ProxyInit.IgnoreOutboundPorts, \",\")); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err := validatePolicy(values.Proxy.DefaultInboundPolicy); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc validatePolicy(policy string) error {\n\tvalidPolicies := []string{\"all-authenticated\", \"all-unauthenticated\", \"cluster-authenticated\", \"cluster-unauthenticated\", \"deny\", \"audit\"}\n\tfor _, p := range validPolicies {\n\t\tif p == policy {\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn fmt.Errorf(\"--default-inbound-policy must be one of: %s (got %s)\", strings.Join(validPolicies, \", \"), policy)\n}\n\n// initializeIssuerCredentials populates the identity issuer TLS credentials.\n// If we are using an externally managed issuer secret, all we need to do here\n// is copy the trust root from the issuer secret.  Otherwise, if no credentials\n// have already been supplied, we generate them.\nfunc initializeIssuerCredentials(ctx context.Context, k *k8s.KubernetesAPI, values *l5dcharts.Values) error {\n\tif values.Identity.Issuer.Scheme == string(corev1.SecretTypeTLS) {\n\t\t// Using externally managed issuer credentials.  We need to copy the\n\t\t// trust root.\n\t\tif k == nil {\n\t\t\treturn errors.New(\"--ignore-cluster is not supported when --identity-external-issuer=true\")\n\t\t}\n\t\texternalIssuerData, err := issuercerts.FetchExternalIssuerData(ctx, k, controlPlaneNamespace)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tvalues.IdentityTrustAnchorsPEM = externalIssuerData.TrustAnchors\n\t} else if values.Identity.Issuer.TLS.CrtPEM != \"\" || values.Identity.Issuer.TLS.KeyPEM != \"\" || values.IdentityTrustAnchorsPEM != \"\" {\n\t\t// If any credentials have already been supplied, check that they are\n\t\t// all present.\n\t\tif values.IdentityTrustAnchorsPEM == \"\" {\n\t\t\treturn errors.New(\"a trust anchors file must be specified if other credentials are provided\")\n\t\t}\n\t\tif values.Identity.Issuer.TLS.CrtPEM == \"\" {\n\t\t\treturn errors.New(\"a certificate file must be specified if other credentials are provided\")\n\t\t}\n\t\tif values.Identity.Issuer.TLS.KeyPEM == \"\" {\n\t\t\treturn errors.New(\"a private key file must be specified if other credentials are provided\")\n\t\t}\n\t} else {\n\t\t// No credentials have been supplied so we will generate them.\n\t\troot, err := tls.GenerateRootCAWithDefaults(issuerName(values.IdentityTrustDomain))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to generate root certificate for identity: %w\", err)\n\t\t}\n\t\tvalues.Identity.Issuer.TLS.KeyPEM = root.Cred.EncodePrivateKeyPEM()\n\t\tvalues.Identity.Issuer.TLS.CrtPEM = root.Cred.Crt.EncodeCertificatePEM()\n\t\tvalues.IdentityTrustAnchorsPEM = root.Cred.Crt.EncodeCertificatePEM()\n\t}\n\treturn nil\n}\n\nfunc issuerName(trustDomain string) string {\n\treturn fmt.Sprintf(\"identity.%s.%s\", controlPlaneNamespace, trustDomain)\n}\n"
  },
  {
    "path": "cli/cmd/policy.go",
    "content": "package cmd\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/linkerd/linkerd2-proxy-api/go/inbound\"\n\t\"github.com/linkerd/linkerd2-proxy-api/go/outbound\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/spf13/cobra\"\n\t\"go.opencensus.io/plugin/ocgrpc\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nconst (\n\tpolicyPort       = 8090\n\tpolicyDeployment = \"linkerd-destination\"\n)\n\nfunc newCmdPolicy() *cobra.Command {\n\toptions := newEndpointsOptions()\n\tvar (\n\t\tnamespace = \"default\"\n\t\toutput    = \"yaml\"\n\t)\n\n\texample := `  # get the inbound policy for pod emoji-6d66d87995-bvrnn on port 8080\n  linkerd diagnostics policy -n emojivoto po/emoji-6d66d87995-bvrnn 8080\n\n  # get the outbound policy for Service emoji-svc on port 8080\n  linkerd diagnostics policy -n emojivoto svc/emoji-svc 8080`\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"policy [flags] resource port\",\n\t\tShort: \"Introspect Linkerd's policy state\",\n\t\tLong: `Introspect Linkerd's policy state.\n\nThis command provides debug information about the internal state of the\ncontrol-plane's policy controller. It queries the same control-plane\nendpoint as the linkerd-proxy's, and returns the policies associated with the\ngiven resource. If the resource is a Pod, inbound policy for that Pod is\ndisplayed. If the resource is a Service, outbound policy for that Service is\ndisplayed.`,\n\t\tExample: example,\n\t\tArgs:    cobra.ExactArgs(2),\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\terr := options.validate()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif apiAddr == \"\" {\n\t\t\t\tvar portForward *k8s.PortForward\n\t\t\t\tvar err error\n\t\t\t\tif options.destinationPod == \"\" {\n\t\t\t\t\tportForward, err = k8s.NewPortForward(\n\t\t\t\t\t\tcmd.Context(),\n\t\t\t\t\t\tk8sAPI,\n\t\t\t\t\t\tcontrolPlaneNamespace,\n\t\t\t\t\t\tpolicyDeployment,\n\t\t\t\t\t\t\"localhost\",\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tpolicyPort,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tportForward, err = k8s.NewPodPortForward(k8sAPI, controlPlaneNamespace, options.destinationPod, \"localhost\", 0, policyPort, false)\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tapiAddr = portForward.AddressAndPort()\n\t\t\t\tif err = portForward.Init(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconn, err := grpc.NewClient(apiAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStatsHandler(&ocgrpc.ClientHandler{}))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer conn.Close()\n\n\t\t\telems := strings.Split(args[0], \"/\")\n\n\t\t\tif len(elems) == 1 {\n\t\t\t\treturn errors.New(\"resource type and name are required\")\n\t\t\t}\n\n\t\t\tif len(elems) != 2 {\n\t\t\t\treturn fmt.Errorf(\"invalid resource string: %s\", args[0])\n\t\t\t}\n\t\t\ttyp, err := k8s.CanonicalResourceNameFromFriendlyName(elems[0])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tname := elems[1]\n\n\t\t\tport, err := strconv.ParseUint(args[1], 10, 32)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tvar result interface{}\n\n\t\t\tif typ == k8s.Pod {\n\t\t\t\tclient := inbound.NewInboundServerPoliciesClient(conn)\n\n\t\t\t\tresult, err = client.GetPort(cmd.Context(), &inbound.PortSpec{\n\t\t\t\t\tWorkload: fmt.Sprintf(\"%s:%s\", namespace, name),\n\t\t\t\t\tPort:     uint32(port),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t} else if typ == k8s.Service {\n\t\t\t\tclient := outbound.NewOutboundPoliciesClient(conn)\n\n\t\t\t\tresult, err = client.Get(cmd.Context(), &outbound.TrafficSpec{\n\t\t\t\t\tSourceWorkload: options.contextToken,\n\t\t\t\t\tTarget:         &outbound.TrafficSpec_Authority{Authority: fmt.Sprintf(\"%s.%s.svc:%d\", name, namespace, port)},\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"invalid resource type %s; must be one of Pod or Service\", args[0])\n\t\t\t}\n\n\t\t\tvar out []byte\n\t\t\tswitch output {\n\t\t\tcase \"json\":\n\t\t\t\tout, err = json.MarshalIndent(result, \"\", \"  \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Fprint(os.Stderr, err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\tcase \"yaml\":\n\t\t\t\tout, err = yaml.Marshal(result)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Fprint(os.Stderr, err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn errors.New(\"output must be one of: yaml, json\")\n\t\t\t}\n\n\t\t\t_, err = fmt.Print(string(out))\n\t\t\treturn err\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVar(&options.destinationPod, \"destination-pod\", \"\", \"Target a specific destination Pod when there are multiple running\")\n\tcmd.PersistentFlags().StringVar(&options.contextToken, \"token\", \"default:diagnostics\", \"Token to use when querying the policy service\")\n\tcmd.PersistentFlags().StringVarP(&namespace, \"namespace\", \"n\", namespace, \"Namespace of resource\")\n\tcmd.PersistentFlags().StringVarP(&output, \"output\", \"o\", output, \"Output format. One of: yaml, json\")\n\n\tpkgcmd.ConfigureOutputFlagCompletion(cmd)\n\n\treturn cmd\n}\n"
  },
  {
    "path": "cli/cmd/profile.go",
    "content": "package cmd\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/profiles\"\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n\t\"sigs.k8s.io/yaml\"\n)\n\ntype profileOptions struct {\n\tname          string\n\tnamespace     string\n\ttemplate      bool\n\topenAPI       string\n\tproto         string\n\tignoreCluster bool\n\toutput        string\n}\n\nfunc newProfileOptions() *profileOptions {\n\treturn &profileOptions{\n\t\tname:          \"\",\n\t\ttemplate:      false,\n\t\topenAPI:       \"\",\n\t\tproto:         \"\",\n\t\tignoreCluster: false,\n\t\toutput:        \"yaml\",\n\t}\n}\n\nfunc (options *profileOptions) validate() error {\n\toutputs := 0\n\tif options.template {\n\t\toutputs++\n\t}\n\tif options.openAPI != \"\" {\n\t\toutputs++\n\t}\n\tif options.proto != \"\" {\n\t\toutputs++\n\t}\n\tif outputs != 1 {\n\t\treturn errors.New(\"You must specify exactly one of --template or --open-api or --proto\")\n\t}\n\n\t// a DNS-1035 label must consist of lower case alphanumeric characters or '-',\n\t// start with an alphabetic character, and end with an alphanumeric character\n\tif errs := validation.IsDNS1035Label(options.name); len(errs) != 0 {\n\t\treturn fmt.Errorf(\"invalid service %q: %v\", options.name, errs)\n\t}\n\n\t// a DNS-1123 label must consist of lower case alphanumeric characters or '-',\n\t// and must start and end with an alphanumeric character\n\tif errs := validation.IsDNS1123Label(options.namespace); len(errs) != 0 {\n\t\treturn fmt.Errorf(\"invalid namespace %q: %v\", options.namespace, errs)\n\t}\n\n\treturn nil\n}\n\n// newCmdProfile creates a new cobra command for the Profile subcommand which\n// generates Linkerd service profiles.\nfunc newCmdProfile() *cobra.Command {\n\toptions := newProfileOptions()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"profile [flags] (--template | --open-api file | --proto file) (SERVICE)\",\n\t\tShort: \"Output service profile config for Kubernetes\",\n\t\tLong:  \"Output service profile config for Kubernetes.\",\n\t\tExample: `  # Output a basic template to apply after modification.\n  linkerd profile -n emojivoto --template web-svc\n\n  # Generate a profile from an OpenAPI specification.\n  linkerd profile -n emojivoto --open-api web-svc.swagger web-svc\n\n  # Generate a profile from a protobuf definition.\n  linkerd profile -n emojivoto --proto Voting.proto vote-svc\n`,\n\t\tArgs: cobra.ExactArgs(1),\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\t\t\toptions.name = args[0]\n\t\t\tclusterDomain := defaultClusterDomain\n\n\t\t\terr := options.validate()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// performs an online profile generation and access-check to k8s cluster to extract\n\t\t\t// clusterDomain from linkerd configuration\n\t\t\tif !options.ignoreCluster {\n\t\t\t\tvar err error\n\t\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\t_, values, err := healthcheck.FetchCurrentConfiguration(cmd.Context(), k8sAPI, controlPlaneNamespace)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tif cd := values.ClusterDomain; cd != \"\" {\n\t\t\t\t\tclusterDomain = cd\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar profile *sp.ServiceProfile\n\t\t\tif options.template {\n\t\t\t\treturn profiles.RenderProfileTemplate(options.namespace, options.name, clusterDomain, os.Stdout, options.output)\n\t\t\t} else if options.openAPI != \"\" {\n\t\t\t\tprofile, err = profiles.RenderOpenAPI(options.openAPI, options.namespace, options.name, clusterDomain)\n\t\t\t} else if options.proto != \"\" {\n\t\t\t\tprofile, err = profiles.RenderProto(options.proto, options.namespace, options.name, clusterDomain)\n\t\t\t} else {\n\t\t\t\treturn errors.New(\"one of --template, --open-api, or --proto must be specified\")\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\treturn writeProfile(profile, os.Stdout, options.output)\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().BoolVar(&options.template, \"template\", options.template, \"Output a service profile template\")\n\tcmd.PersistentFlags().StringVar(&options.openAPI, \"open-api\", options.openAPI, \"Output a service profile based on the given OpenAPI spec file\")\n\tcmd.PersistentFlags().StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace, \"Namespace of the service\")\n\tcmd.PersistentFlags().StringVar(&options.proto, \"proto\", options.proto, \"Output a service profile based on the given Protobuf spec file\")\n\tcmd.PersistentFlags().BoolVar(&options.ignoreCluster, \"ignore-cluster\", options.ignoreCluster, \"Output a service profile through offline generation\")\n\tcmd.PersistentFlags().StringVarP(&options.output, \"output\", \"o\", options.output, \"Output format. One of: yaml, json\")\n\treturn cmd\n}\n\nfunc writeProfile(profile *sp.ServiceProfile, w io.Writer, format string) error {\n\tvar output []byte\n\tvar err error\n\tif format == yamlOutput {\n\t\toutput, err = yaml.Marshal(profile)\n\t} else if format == jsonOutput {\n\t\toutput, err = json.Marshal(profile)\n\t} else {\n\t\treturn fmt.Errorf(\"unknown output format: %s\", format)\n\t}\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error writing Service Profile: %w\", err)\n\t}\n\t_, err = w.Write(output)\n\treturn err\n}\n"
  },
  {
    "path": "cli/cmd/profile_test.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\t\"github.com/linkerd/linkerd2/pkg/profiles\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nfunc TestParseProfile(t *testing.T) {\n\tvar buf bytes.Buffer\n\n\terr := profiles.RenderProfileTemplate(\"myns\", \"mysvc\", \"mycluster.local\", &buf, \"yaml\")\n\tif err != nil {\n\t\tt.Fatalf(\"Error rendering service profile template: %v\", err)\n\t}\n\n\tvar serviceProfile v1alpha2.ServiceProfile\n\terr = yaml.Unmarshal(buf.Bytes(), &serviceProfile)\n\tif err != nil {\n\t\tt.Fatalf(\"Error parsing service profile: %v\", err)\n\t}\n\n\texpectedServiceProfile := profiles.GenServiceProfile(\"mysvc\", \"myns\", \"mycluster.local\")\n\n\terr = profiles.ServiceProfileYamlEquals(serviceProfile, expectedServiceProfile)\n\tif err != nil {\n\t\tt.Fatalf(\"ServiceProfiles are not equal: %v\", err)\n\t}\n}\n\nfunc TestValidateOptions(t *testing.T) {\n\toptions := newProfileOptions()\n\texp := errors.New(\"You must specify exactly one of --template or --open-api or --proto\")\n\terr := options.validate()\n\tif err == nil || err.Error() != exp.Error() {\n\t\tt.Fatalf(\"validateOptions returned unexpected error: %s (expected: %s) for options: %+v\", err, exp, options)\n\t}\n\n\toptions = newProfileOptions()\n\toptions.template = true\n\toptions.openAPI = \"openAPI\"\n\texp = errors.New(\"You must specify exactly one of --template or --open-api or --proto\")\n\terr = options.validate()\n\tif err == nil || err.Error() != exp.Error() {\n\t\tt.Fatalf(\"validateOptions returned unexpected error: %s (expected: %s) for options: %+v\", err, exp, options)\n\t}\n\n\toptions = newProfileOptions()\n\toptions.template = true\n\texp = errors.New(\"invalid service \\\"\\\": [a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name',  or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')]\")\n\terr = options.validate()\n\tif err == nil || err.Error() != exp.Error() {\n\t\tt.Fatalf(\"validateOptions returned unexpected error: %s (expected: %s) for options: %+v\", err, exp, options)\n\t}\n\n\toptions = newProfileOptions()\n\toptions.template = true\n\toptions.name = \"template-name\"\n\toptions.namespace = \"default\"\n\terr = options.validate()\n\tif err != nil {\n\t\tt.Fatalf(\"validateOptions returned unexpected error (%s) for options: %+v\", err, options)\n\t}\n\n\toptions = newProfileOptions()\n\toptions.template = true\n\toptions.name = \"template-name\"\n\toptions.namespace = \"namespace-name\"\n\terr = options.validate()\n\tif err != nil {\n\t\tt.Fatalf(\"validateOptions returned unexpected error (%s) for options: %+v\", err, options)\n\t}\n\n\toptions = newProfileOptions()\n\toptions.openAPI = \"openAPI\"\n\toptions.name = \"openapi-name\"\n\toptions.namespace = \"default\"\n\terr = options.validate()\n\tif err != nil {\n\t\tt.Fatalf(\"validateOptions returned unexpected error (%s) for options: %+v\", err, options)\n\t}\n\n\toptions = newProfileOptions()\n\toptions.template = true\n\toptions.name = \"service.name\"\n\texp = fmt.Errorf(\"invalid service \\\"%s\\\": [a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name',  or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')]\", options.name)\n\terr = options.validate()\n\tif err == nil || err.Error() != exp.Error() {\n\t\tt.Fatalf(\"validateOptions returned unexpected error: %s (expected: %s) for options: %+v\", err, exp, options)\n\t}\n\n\toptions = newProfileOptions()\n\toptions.template = true\n\toptions.name = \"invalid/name\"\n\texp = fmt.Errorf(\"invalid service \\\"%s\\\": [a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name',  or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')]\", options.name)\n\terr = options.validate()\n\tif err == nil || err.Error() != exp.Error() {\n\t\tt.Fatalf(\"validateOptions returned unexpected error: %s (expected: %s) for options: %+v\", err, exp, options)\n\t}\n\n\tserviceName := \"service-name\"\n\n\toptions = newProfileOptions()\n\toptions.template = true\n\toptions.name = serviceName\n\toptions.namespace = \"\"\n\texp = fmt.Errorf(\"invalid namespace \\\"%s\\\": [a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name',  or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')]\", options.namespace)\n\terr = options.validate()\n\tif err == nil || err.Error() != exp.Error() {\n\t\tt.Fatalf(\"validateOptions returned unexpected error: %s (expected: %s) for options: %+v\", err, exp, options)\n\t}\n\n\toptions = newProfileOptions()\n\toptions.template = true\n\toptions.name = serviceName\n\toptions.namespace = \"invalid/namespace\"\n\texp = fmt.Errorf(\"invalid namespace \\\"%s\\\": [a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name',  or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')]\", options.namespace)\n\terr = options.validate()\n\tif err == nil || err.Error() != exp.Error() {\n\t\tt.Fatalf(\"validateOptions returned unexpected error: %s (expected: %s) for options: %+v\", err, exp, options)\n\t}\n\n\toptions = newProfileOptions()\n\toptions.template = true\n\toptions.name = serviceName\n\toptions.namespace = \"7eet-ns\"\n\terr = options.validate()\n\tif err != nil {\n\t\tt.Fatalf(\"validateOptions returned unexpected error (%s) for options: %+v\", err, options)\n\t}\n\n\toptions = newProfileOptions()\n\toptions.template = true\n\toptions.name = \"7eet-svc\"\n\texp = fmt.Errorf(\"invalid service \\\"%s\\\": [a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name',  or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')]\", options.name)\n\terr = options.validate()\n\tif err == nil || err.Error() != exp.Error() {\n\t\tt.Fatalf(\"validateOptions returned unexpected error: %s (expected: %s) for options: %+v\", err, exp, options)\n\t}\n}\n"
  },
  {
    "path": "cli/cmd/prune.go",
    "content": "package cmd\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tpkgCmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/spf13/cobra\"\n\tvaluespkg \"helm.sh/helm/v3/pkg/cli/values\"\n)\n\nfunc newCmdPrune() *cobra.Command {\n\n\tvar output string\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"prune [flags]\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Output extraneous Kubernetes resources in the linkerd control plane\",\n\t\tLong:  `Output extraneous Kubernetes resources in the linkerd control plane.`,\n\t\tExample: `  # Prune extraneous resources.\n  linkerd prune | kubectl delete -f -\n  `,\n\t\tRunE: func(cmd *cobra.Command, _ []string) error {\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 30*time.Second)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tvalues, err := loadStoredValues(cmd.Context(), k8sAPI)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"failed to load stored values: %s\\n\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\tif values == nil {\n\t\t\t\treturn errors.New(\n\t\t\t\t\t`Could not find the linkerd-config-overrides secret.\nPlease note this command is only intended for instances of Linkerd that were installed via the CLI`)\n\t\t\t}\n\n\t\t\terr = validateValues(cmd.Context(), k8sAPI, values)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tmanifests := strings.Builder{}\n\n\t\t\tif err = renderControlPlane(&manifests, values, make(map[string]interface{}), \"yaml\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err = renderCRDs(cmd.Context(), k8sAPI, &manifests, valuespkg.Options{}, \"yaml\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\treturn pkgCmd.Prune(cmd.Context(), k8sAPI, manifests.String(), k8s.ControllerNSLabel, output)\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVarP(&output, \"output\", \"o\", \"yaml\", \"Output format. One of: json|yaml\")\n\n\treturn cmd\n}\n"
  },
  {
    "path": "cli/cmd/range_slice.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// validateRangeSlice ensures that provided slice contains valid entries\n// representing either port number(s) and/or port range(s).  Invalid entries\n// will result in an error being returned.\nfunc validateRangeSlice(rangeSlice []string) error {\n\tfor _, portOrRange := range rangeSlice {\n\t\tif strings.Contains(portOrRange, \"-\") {\n\t\t\tbounds := strings.Split(portOrRange, \"-\")\n\t\t\tif len(bounds) != 2 {\n\t\t\t\treturn fmt.Errorf(\"ranges expected as <lower>-<upper>\")\n\t\t\t}\n\t\t\tlower, err := strconv.Atoi(bounds[0])\n\t\t\tif err != nil || !isValidPort(lower) {\n\t\t\t\treturn fmt.Errorf(\"\\\"%s\\\" is not a valid lower-bound\", bounds[0])\n\t\t\t}\n\t\t\tupper, err := strconv.Atoi(bounds[1])\n\t\t\tif err != nil || !isValidPort(upper) {\n\t\t\t\treturn fmt.Errorf(\"\\\"%s\\\" is not a valid upper-bound\", bounds[1])\n\t\t\t}\n\t\t\tif upper < lower {\n\t\t\t\treturn fmt.Errorf(\"\\\"%s\\\": upper-bound must be greater than or equal to lower-bound\", portOrRange)\n\t\t\t}\n\t\t} else {\n\t\t\tport, err := strconv.Atoi(portOrRange)\n\t\t\tif err != nil || !isValidPort(port) {\n\t\t\t\treturn fmt.Errorf(\"\\\"%s\\\" is not a valid port nor port-range\", portOrRange)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// isValidPort ensures that the provided value is a valid TCP port number, 0-65535 (inclusive).\nfunc isValidPort(port int) bool {\n\treturn port >= 0 && port <= 65535\n}\n"
  },
  {
    "path": "cli/cmd/range_slice_test.go",
    "content": "package cmd\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestValidateRangeSlice(t *testing.T) {\n\tassertNoError(t, validateRangeSlice(nil))\n\tassertNoError(t, validateRangeSlice([]string{}))\n\tassertNoError(t, validateRangeSlice([]string{\"23\"}))\n\tassertNoError(t, validateRangeSlice([]string{\"23-23\"}))\n\tassertNoError(t, validateRangeSlice([]string{\"25-27\"}))\n\n\tassertError(t, validateRangeSlice([]string{\"\"}), \"not a valid port\")\n\tassertError(t, validateRangeSlice([]string{\"notanumber\"}), \"not a valid port\")\n\tassertError(t, validateRangeSlice([]string{\"not-number\"}), \"not a valid lower-bound\")\n\tassertError(t, validateRangeSlice([]string{\"-23-25\"}), \"ranges expected as\")\n\tassertError(t, validateRangeSlice([]string{\"-23\"}), \"not a valid lower-bound\")\n\tassertError(t, validateRangeSlice([]string{\"25-23\"}), \"upper-bound must be greater than or equal to\")\n\tassertError(t, validateRangeSlice([]string{\"65536\"}), \"not a valid port\")\n\tassertError(t, validateRangeSlice([]string{\"10-65536\"}), \"not a valid upper-bound\")\n}\n\nfunc assertNoError(t *testing.T, err error) {\n\tif err != nil {\n\t\tt.Fatalf(\"expected no error; got %s\", err)\n\t}\n}\n\n// assertError confirms that the provided is an error having the provided message.\nfunc assertError(t *testing.T, err error, containing string) {\n\tif err == nil {\n\t\tt.Fatalf(\"expected error containing '%s' but got nothing\", containing)\n\t}\n\tif !strings.Contains(err.Error(), containing) {\n\t\tt.Fatalf(\"expected error to contain '%s' but received '%s'\", containing, err.Error())\n\t}\n}\n"
  },
  {
    "path": "cli/cmd/root.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/linkerd/linkerd2/cli/flag\"\n\tmulticluster \"github.com/linkerd/linkerd2/multicluster/cmd\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/inject\"\n\tviz \"github.com/linkerd/linkerd2/viz/cmd\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n)\n\nconst (\n\tdefaultLinkerdNamespace = \"linkerd\"\n\tdefaultCNINamespace     = \"linkerd-cni\"\n\tdefaultClusterDomain    = \"cluster.local\"\n\n\tjsonOutput  = pkgcmd.JsonOutput\n\tyamlOutput  = pkgcmd.YamlOutput\n\ttableOutput = healthcheck.TableOutput\n\tshortOutput = healthcheck.ShortOutput\n)\n\nvar (\n\t// special handling for Windows, on all other platforms these resolve to\n\t// os.Stdout and os.Stderr, thanks to https://github.com/mattn/go-colorable\n\tstdout = color.Output\n\tstderr = color.Error\n\n\tokStatus   = color.New(color.FgGreen, color.Bold).SprintFunc()(\"\\u221A\")  // √\n\twarnStatus = color.New(color.FgYellow, color.Bold).SprintFunc()(\"\\u203C\") // ‼\n\tfailStatus = color.New(color.FgRed, color.Bold).SprintFunc()(\"\\u00D7\")    // ×\n\n\tcontrolPlaneNamespace string\n\tcniNamespace          string\n\tapiAddr               string // An empty value means \"use the Kubernetes configuration\"\n\tkubeconfigPath        string\n\tkubeContext           string\n\timpersonate           string\n\timpersonateGroup      []string\n\tverbose               bool\n\n\t// These regexs are not as strict as they could be, but are a quick and dirty\n\t// sanity check against illegal characters.\n\talphaNumDash              = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)\n\talphaNumDashDot           = regexp.MustCompile(`^[\\.a-zA-Z0-9-]+$`)\n\talphaNumDashDotSlashColon = regexp.MustCompile(`^[\\./a-zA-Z0-9-:]+$`)\n\n\t// Full Rust log level syntax at\n\t// https://docs.rs/env_logger/0.6.0/env_logger/#enabling-logging\n\tr                  = strings.NewReplacer(\"\\t\", \"\", \"\\n\", \"\")\n\tvalidProxyLogLevel = regexp.MustCompile(r.Replace(`\n\t\t^(\n\t\t\t(\n\t\t\t\t(trace|debug|warn|info|error)|\n\t\t\t\t(\\w|::)+|\n\t\t\t\t((\\w|::)+=(trace|debug|warn|info|error))\n\t\t\t)(?:,|$)\n\t\t)+$`))\n)\n\n// RootCmd represents the root Cobra command\nfunc NewRootCmd() *cobra.Command {\n\trootCmd := &cobra.Command{\n\t\tUse:   \"linkerd\",\n\t\tShort: \"linkerd manages the Linkerd service mesh\",\n\t\tLong:  `linkerd manages the Linkerd service mesh.`,\n\t\tPersistentPreRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\t// enable / disable logging\n\t\t\tif verbose {\n\t\t\t\tlog.SetLevel(log.DebugLevel)\n\t\t\t} else {\n\t\t\t\tlog.SetLevel(log.PanicLevel)\n\t\t\t}\n\n\t\t\tcontrolPlaneNamespaceFromEnv := os.Getenv(flags.EnvOverrideNamespace)\n\t\t\tif controlPlaneNamespace == defaultLinkerdNamespace && controlPlaneNamespaceFromEnv != \"\" {\n\t\t\t\tcontrolPlaneNamespace = controlPlaneNamespaceFromEnv\n\t\t\t}\n\n\t\t\tif !alphaNumDash.MatchString(controlPlaneNamespace) {\n\t\t\t\treturn fmt.Errorf(\"%s is not a valid namespace\", controlPlaneNamespace)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\trootCmd.PersistentFlags().StringVarP(&controlPlaneNamespace, \"linkerd-namespace\", \"L\",\n\t\tdefaultLinkerdNamespace,\n\t\tfmt.Sprintf(\"Namespace in which Linkerd is installed ($%s)\", flags.EnvOverrideNamespace))\n\trootCmd.PersistentFlags().StringVarP(&cniNamespace, \"cni-namespace\", \"\", defaultCNINamespace, \"Namespace in which the Linkerd CNI plugin is installed\")\n\trootCmd.PersistentFlags().StringVar(&kubeconfigPath, \"kubeconfig\", \"\", \"Path to the kubeconfig file to use for CLI requests\")\n\trootCmd.PersistentFlags().StringVar(&kubeContext, \"context\", \"\", \"Name of the kubeconfig context to use\")\n\trootCmd.PersistentFlags().StringVar(&impersonate, \"as\", \"\", \"Username to impersonate for Kubernetes operations\")\n\trootCmd.PersistentFlags().StringArrayVar(&impersonateGroup, \"as-group\", []string{}, \"Group to impersonate for Kubernetes operations\")\n\trootCmd.PersistentFlags().StringVar(&apiAddr, \"api-addr\", \"\", \"Override kubeconfig and communicate directly with the control plane at host:port (mostly for testing)\")\n\trootCmd.PersistentFlags().BoolVar(&verbose, \"verbose\", false, \"Turn on debug logging\")\n\trootCmd.AddCommand(newCmdCheck())\n\trootCmd.AddCommand(newCmdCompletion())\n\trootCmd.AddCommand(newCmdDiagnostics())\n\trootCmd.AddCommand(newCmdDoc(rootCmd))\n\trootCmd.AddCommand(newCmdIdentity())\n\trootCmd.AddCommand(NewCmdInject(inject.GetOverriddenValues))\n\trootCmd.AddCommand(newCmdInstall())\n\trootCmd.AddCommand(newCmdInstallCNIPlugin())\n\trootCmd.AddCommand(newCmdProfile())\n\trootCmd.AddCommand(newCmdAuthz())\n\trootCmd.AddCommand(newCmdUninject())\n\trootCmd.AddCommand(newCmdUpgrade())\n\trootCmd.AddCommand(newCmdVersion())\n\trootCmd.AddCommand(newCmdUninstall())\n\trootCmd.AddCommand(newCmdPrune())\n\n\t// Extension Sub Commands\n\trootCmd.AddCommand(multicluster.NewCmdMulticluster())\n\trootCmd.AddCommand(viz.NewCmdViz())\n\n\t// Viz Extension sub commands\n\trootCmd.AddCommand(deprecateCmd(viz.NewCmdDashboard()))\n\trootCmd.AddCommand(deprecateCmd(viz.NewCmdEdges()))\n\trootCmd.AddCommand(deprecateCmd(viz.NewCmdRoutes()))\n\trootCmd.AddCommand(deprecateCmd(viz.NewCmdStat()))\n\trootCmd.AddCommand(deprecateCmd(viz.NewCmdTap()))\n\trootCmd.AddCommand(deprecateCmd(viz.NewCmdTop()))\n\n\t// resource-aware completion flag configurations\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\trootCmd, []string{\"linkerd-namespace\", \"cni-namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\n\tpkgcmd.ConfigureKubeContextFlagCompletion(rootCmd, kubeconfigPath)\n\n\treturn rootCmd\n}\n\nfunc deprecateCmd(cmd *cobra.Command) *cobra.Command {\n\tcmd.Deprecated = fmt.Sprintf(\"use instead 'linkerd viz %s'\\n\", cmd.Use)\n\treturn cmd\n}\n\nfunc flattenFlags(flags ...[]flag.Flag) []flag.Flag {\n\tout := []flag.Flag{}\n\tfor _, f := range flags {\n\t\tout = append(out, f...)\n\t}\n\treturn out\n}\n"
  },
  {
    "path": "cli/cmd/test_helper.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/yaml\"\n)\n\ntype (\n\tmanifest = map[string]interface{}\n\n\tdiff struct {\n\t\tpath []string\n\t\ta    interface{}\n\t\tb    interface{}\n\t}\n)\n\nfunc splitManifests(manifest string) []string {\n\tmanifests := strings.Split(manifest, \"\\n---\\n\")\n\tfiltered := []string{}\n\tfor _, m := range manifests {\n\t\tif !isManifestEmpty(m) {\n\t\t\tfiltered = append(filtered, m)\n\t\t}\n\t}\n\treturn filtered\n}\n\nfunc isManifestEmpty(manifest string) bool {\n\tlines := strings.Split(manifest, \"\\n\")\n\tfor _, line := range lines {\n\t\tif line == \"\" || strings.HasPrefix(line, \"#\") || line == \"---\" {\n\t\t\tcontinue\n\t\t}\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc manifestKey(m manifest) string {\n\tkind := m[\"kind\"].(string)\n\tmeta := m[\"metadata\"].(map[string]interface{})\n\tname := meta[\"name\"].(string)\n\treturn fmt.Sprintf(\"%s/%s\", kind, name)\n}\n\nfunc (d diff) String() string {\n\texpected, _ := yaml.Marshal(d.a)\n\tactual, _ := yaml.Marshal(d.b)\n\treturn fmt.Sprintf(\"Diff at [%s]:\\nExpected:\\n%s\\nActual:\\n%s\", d.path, string(expected), string(actual))\n}\n\nfunc parseManifestList(in string) map[string]manifest {\n\tmanifestList := splitManifests(in)\n\tmanifestMap := map[string]manifest{}\n\tfor _, m := range manifestList {\n\t\tmanifest := manifest{}\n\t\tyaml.Unmarshal([]byte(m), &manifest)\n\t\tmanifestMap[manifestKey(manifest)] = manifest\n\t}\n\treturn manifestMap\n}\n\nfunc diffManifest(a manifest, b manifest, path []string) []diff {\n\tdiffs := []diff{}\n\tfor k, v := range a {\n\t\tbv, bvExists := b[k]\n\t\tswitch val := v.(type) {\n\t\tcase manifest:\n\t\t\tif !bvExists {\n\t\t\t\tdiffs = append(diffs, diff{\n\t\t\t\t\tpath: extend(path, k),\n\t\t\t\t\ta:    val,\n\t\t\t\t\tb:    nil,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tbvm, ok := bv.(manifest)\n\t\t\t\tif !ok {\n\t\t\t\t\tdiffs = append(diffs, diff{\n\t\t\t\t\t\tpath: extend(path, k),\n\t\t\t\t\t\ta:    val,\n\t\t\t\t\t\tb:    bv,\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\tdiffs = append(diffs, diffManifest(val, bvm, extend(path, k))...)\n\t\t\t\t}\n\t\t\t}\n\t\tcase []interface{}:\n\t\t\tbva, ok := bv.([]interface{})\n\t\t\tif !ok {\n\t\t\t\tdiffs = append(diffs, diff{\n\t\t\t\t\tpath: extend(path, k),\n\t\t\t\t\ta:    val,\n\t\t\t\t\tb:    bv,\n\t\t\t\t})\n\t\t\t} else if len(val) != len(bva) {\n\t\t\t\tdiffs = append(diffs, diff{\n\t\t\t\t\tpath: extend(path, k),\n\t\t\t\t\ta:    val,\n\t\t\t\t\tb:    bva,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tdiffs = append(diffs, diffArray(val, bva, extend(path, k))...)\n\t\t\t}\n\t\tdefault:\n\t\t\tif !bvExists {\n\t\t\t\tdiffs = append(diffs, diff{\n\t\t\t\t\tpath: extend(path, k),\n\t\t\t\t\ta:    val,\n\t\t\t\t\tb:    nil,\n\t\t\t\t})\n\t\t\t} else if !reflect.DeepEqual(val, bv) {\n\t\t\t\tdiffs = append(diffs, diff{\n\t\t\t\t\tpath: extend(path, k),\n\t\t\t\t\ta:    val,\n\t\t\t\t\tb:    bv,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\tfor k, v := range b {\n\t\t_, avExists := a[k]\n\t\tif !avExists {\n\t\t\tdiffs = append(diffs, diff{\n\t\t\t\tpath: extend(path, k),\n\t\t\t\ta:    nil,\n\t\t\t\tb:    v,\n\t\t\t})\n\t\t}\n\t}\n\treturn diffs\n}\n\nfunc diffArray(a, b []interface{}, path []string) []diff {\n\tdiffs := []diff{}\n\tfor i, v := range a {\n\t\tswitch aVal := v.(type) {\n\t\tcase manifest:\n\t\t\tbm, ok := b[i].(manifest)\n\t\t\tif !ok {\n\t\t\t\tdiffs = append(diffs, diff{\n\t\t\t\t\tpath: extend(path, fmt.Sprintf(\"%d\", i)),\n\t\t\t\t\ta:    aVal,\n\t\t\t\t\tb:    b[i],\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tdiffs = append(diffs, diffManifest(aVal, bm, extend(path, fmt.Sprintf(\"%d\", i)))...)\n\t\t\t}\n\t\tcase []interface{}:\n\t\t\tba, ok := b[i].([]interface{})\n\t\t\tif !ok {\n\t\t\t\tdiffs = append(diffs, diff{\n\t\t\t\t\tpath: extend(path, fmt.Sprintf(\"%d\", i)),\n\t\t\t\t\ta:    aVal,\n\t\t\t\t\tb:    b[i],\n\t\t\t\t})\n\t\t\t} else if len(aVal) != len(ba) {\n\t\t\t\tdiffs = append(diffs, diff{\n\t\t\t\t\tpath: extend(path, fmt.Sprintf(\"%d\", i)),\n\t\t\t\t\ta:    aVal,\n\t\t\t\t\tb:    b[i],\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tdiffs = append(diffs, diffArray(aVal, ba, extend(path, fmt.Sprintf(\"%d\", i)))...)\n\t\t\t}\n\t\tdefault:\n\t\t\tif !reflect.DeepEqual(v, b[i]) {\n\t\t\t\tdiffs = append(diffs, diff{\n\t\t\t\t\tpath: extend(path, fmt.Sprintf(\"%d\", i)),\n\t\t\t\t\ta:    v,\n\t\t\t\t\tb:    b[i],\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\treturn diffs\n}\n\nfunc diffManifestLists(a map[string]manifest, b map[string]manifest) map[string][]diff {\n\tdiffs := map[string][]diff{}\n\tfor k, am := range a {\n\t\tbm, ok := b[k]\n\t\tif !ok {\n\t\t\tdiffs[k] = []diff{{\n\t\t\t\ta:    am,\n\t\t\t\tb:    nil,\n\t\t\t\tpath: []string{},\n\t\t\t}}\n\t\t} else {\n\t\t\tdiffs[k] = diffManifest(am, bm, []string{})\n\t\t}\n\t}\n\tfor k, bm := range b {\n\t\t_, ok := a[k]\n\t\tif !ok {\n\t\t\tdiffs[k] = []diff{{\n\t\t\t\ta:    nil,\n\t\t\t\tb:    bm,\n\t\t\t\tpath: []string{},\n\t\t\t}}\n\t\t}\n\t}\n\treturn diffs\n}\n\n// extend returns a new slice which is a copy of slice with next appended to it.\n// The advantage of using extend instead of append is that modifying the\n// returned slice will not modify the original.\nfunc extend(slice []string, next string) []string {\n\tnew := make([]string, len(slice)+1)\n\tcopy(new, slice)\n\tnew[len(slice)] = next\n\treturn new\n}\n"
  },
  {
    "path": "cli/cmd/testdata/addon_config.yaml",
    "content": "tracing:\n    enabled: true\n"
  },
  {
    "path": "cli/cmd/testdata/addon_config_overwrite.yaml",
    "content": "tracing:\n  enabled: true\n  collector:\n    image: overwrite-collector-image\n"
  },
  {
    "path": "cli/cmd/testdata/check_output.golden",
    "content": "category\n--------\n√ check1\n× check2\n    This should contain instructions for fail\n    see https://linkerd.io/2/checks/#hint-anchor for hints\n\n"
  },
  {
    "path": "cli/cmd/testdata/check_output_json.golden",
    "content": "{\n  \"success\": false,\n  \"categories\": [\n    {\n      \"categoryName\": \"category\",\n      \"checks\": [\n        {\n          \"description\": \"check1\",\n          \"result\": \"success\"\n        },\n        {\n          \"description\": \"check2\",\n          \"hint\": \"https://linkerd.io/2/checks/#hint-anchor\",\n          \"error\": \"This should contain instructions for fail\",\n          \"result\": \"error\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "cli/cmd/testdata/endpoints_one_output.golden",
    "content": "NAMESPACE   IP        PORT   POD                       SERVICE\nemojivoto   1.2.3.4   8080   emoji-6bf9f47bd5-jjcrl    emoji-svc.emojivoto\nemojivoto   5.6.7.8   8080   voting-7bf9f47bd5-jjdrl   voting-svc.emojivoto\n"
  },
  {
    "path": "cli/cmd/testdata/endpoints_one_output_json.golden",
    "content": "[\n  {\n    \"namespace\": \"emojivoto\",\n    \"ip\": \"1.2.3.4\",\n    \"port\": 8080,\n    \"pod\": \"emoji-6bf9f47bd5-jjcrl\",\n    \"service\": \"emoji-svc.emojivoto\",\n    \"weight\": 0,\n    \"labels\": {\n      \"pod\": \"emoji-6bf9f47bd5-jjcrl\"\n    }\n  },\n  {\n    \"namespace\": \"emojivoto\",\n    \"ip\": \"5.6.7.8\",\n    \"port\": 8080,\n    \"pod\": \"voting-7bf9f47bd5-jjdrl\",\n    \"service\": \"voting-svc.emojivoto\",\n    \"weight\": 0,\n    \"labels\": {\n      \"pod\": \"voting-7bf9f47bd5-jjdrl\"\n    }\n  }\n]\n"
  },
  {
    "path": "cli/cmd/testdata/endpoints_two_outputs.golden",
    "content": "NAMESPACE    IP        PORT   POD                       SERVICE\nemojivoto    1.2.3.4   8080   emoji-6bf9f47bd5-jjcrl    emoji-svc.emojivoto\n\nNAMESPACE    IP        PORT   POD                       SERVICE\nemojivoto2   5.6.7.8   8080   voting-7bf9f47bd5-jjdrl   voting-svc.emojivoto2\n"
  },
  {
    "path": "cli/cmd/testdata/expired-crt.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIBhDCCASmgAwIBAgIBATAKBggqhkjOPQQDAjApMScwJQYDVQQDEx5pZGVudGl0\neS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNODkwMTAxMDEwMDUxWhcNOTAwMTAx\nMDEwMTExWjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9j\nYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQxvt8MR0+WLzCRKfzdY6omR2PY\nKNMA7LcizIw3FPB+DqK8DslYVLnRAXOmb1WBI16Zlaj3mB605c7DN8m5TY+Wo0Iw\nQDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC\nMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAOgi3IaOyFl5w9b4\nxQzEEBJ8kagB0QfVADBgd1ZYO+8KAiEAlD+PXnjNgQzY0Jxk9WXQecZcZr9/8lFy\nL+OmAJvScWg=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cli/cmd/testdata/expired-key.pem",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEINhyIre3mAKkzJ0HAxFgqsQRYC3VlQs83KZPLD45kveVoAoGCCqGSM49\nAwEHoUQDQgAEMb7fDEdPli8wkSn83WOqJkdj2CjTAOy3IsyMNxTwfg6ivA7JWFS5\n0QFzpm9VgSNemZWo95getOXOwzfJuU2Plg==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "cli/cmd/testdata/expired-trust-anchors.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIBhDCCASmgAwIBAgIBATAKBggqhkjOPQQDAjApMScwJQYDVQQDEx5pZGVudGl0\neS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNODkwMTAxMDEwMDUxWhcNOTAwMTAx\nMDEwMTExWjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9j\nYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQxvt8MR0+WLzCRKfzdY6omR2PY\nKNMA7LcizIw3FPB+DqK8DslYVLnRAXOmb1WBI16Zlaj3mB605c7DN8m5TY+Wo0Iw\nQDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC\nMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAOgi3IaOyFl5w9b4\nxQzEEBJ8kagB0QfVADBgd1ZYO+8KAiEAlD+PXnjNgQzY0Jxk9WXQecZcZr9/8lFy\nL+OmAJvScWg=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cli/cmd/testdata/inject-filepath/expected/injected_nginx.stderr",
    "content": "\ndeployment \"nginx\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject-filepath/expected/injected_nginx.stderr.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"nginx\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject-filepath/expected/injected_nginx.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx\nspec:\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: nginx\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: nginx\n        linkerd.io/workload-ns: \"\"\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - image: nginx\n        name: nginx\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject-filepath/expected/injected_nginx_redis.stderr",
    "content": "\ndeployment \"redis\" injected\n\n\ndeployment \"nginx\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject-filepath/expected/injected_nginx_redis.stderr.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"redis\" injected\n\n\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"nginx\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject-filepath/expected/injected_nginx_redis.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: redis\nspec:\n  selector:\n    matchLabels:\n      app: redis\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: redis\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: redis\n        linkerd.io/workload-ns: \"\"\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"6379\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - image: redis\n        name: redis\n        ports:\n        - containerPort: 6379\n          name: server\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx\nspec:\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: nginx\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: nginx\n        linkerd.io/workload-ns: \"\"\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - image: nginx\n        name: nginx\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject-filepath/expected/injected_redis.stderr",
    "content": "\ndeployment \"redis\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject-filepath/expected/injected_redis.stderr.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"redis\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject-filepath/expected/injected_redis.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: redis\nspec:\n  selector:\n    matchLabels:\n      app: redis\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: redis\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: redis\n        linkerd.io/workload-ns: \"\"\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"6379\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - image: redis\n        name: redis\n        ports:\n        - containerPort: 6379\n          name: server\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject-filepath/resources/db/redis.yaml",
    "content": "---\nkind: Deployment\napiVersion: apps/v1\nmetadata:\n  name: redis\nspec:\n  selector:\n    matchLabels:\n      app: redis\n  template:\n    metadata:\n      labels:\n        app: redis\n    spec:\n      containers:\n      - name: redis\n        image: redis\n        ports:\n        - name: server\n          containerPort: 6379\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject-filepath/resources/nginx.yaml",
    "content": "---\nkind: Deployment\napiVersion: apps/v1\nmetadata:\n  name: nginx\nspec:\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n      - name: nginx\n        image: nginx\n        ports:\n        - name: http\n          containerPort: 80\n"
  },
  {
    "path": "cli/cmd/testdata/inject_contour.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: contour\n  name: contour\n  namespace: heptio-contour\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: contour\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        prometheus.io/format: prometheus\n        prometheus.io/path: /stats\n        prometheus.io/port: \"9001\"\n        prometheus.io/scrape: \"true\"\n      labels:\n        app: contour\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: contour\n        linkerd.io/workload-ns: heptio-contour\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: 8080,8443\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - serve\n        - --incluster\n        command:\n        - contour\n        image: gcr.io/heptio-images/contour:master\n        imagePullPolicy: Always\n        name: contour\n      - args:\n        - --config-path /config/contour.yaml\n        - --service-cluster cluster0\n        - --service-node node0\n        - --log-level info\n        - --v2-config-only\n        command:\n        - envoy\n        image: docker.io/envoyproxy/envoy-alpine:v1.6.0\n        name: envoy\n        ports:\n        - containerPort: 8080\n          name: http\n        - containerPort: 8443\n          name: https\n        volumeMounts:\n        - mountPath: /config\n          name: contour-config\n      initContainers:\n      - args:\n        - bootstrap\n        - /config/contour.yaml\n        command:\n        - contour\n        image: gcr.io/heptio-images/contour:master\n        imagePullPolicy: Always\n        name: envoy-initconfig\n        volumeMounts:\n        - mountPath: /config\n          name: contour-config\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_contour.input.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: contour\n  name: contour\n  namespace: heptio-contour\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: contour\n  template:\n    metadata:\n      annotations:\n        prometheus.io/format: prometheus\n        prometheus.io/path: /stats\n        prometheus.io/port: \"9001\"\n        prometheus.io/scrape: \"true\"\n      labels:\n        app: contour\n    spec:\n      containers:\n      - args:\n        - serve\n        - --incluster\n        command:\n        - contour\n        image: gcr.io/heptio-images/contour:master\n        imagePullPolicy: Always\n        name: contour\n      - args:\n        - --config-path /config/contour.yaml\n        - --service-cluster cluster0\n        - --service-node node0\n        - --log-level info\n        - --v2-config-only\n        command:\n        - envoy\n        image: docker.io/envoyproxy/envoy-alpine:v1.6.0\n        name: envoy\n        ports:\n        - containerPort: 8080\n          name: http\n        - containerPort: 8443\n          name: https\n        volumeMounts:\n        - mountPath: /config\n          name: contour-config\n      initContainers:\n      - args:\n        - bootstrap\n        - /config/contour.yaml\n        command:\n        - contour\n        image: gcr.io/heptio-images/contour:master\n        imagePullPolicy: Always\n        name: envoy-initconfig\n        volumeMounts:\n        - mountPath: /config\n          name: contour-config\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_contour.report",
    "content": "\ndeployment \"contour\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_contour.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"contour\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_contour_uninject.report",
    "content": "\ndeployment \"contour\" uninjected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_already_injected.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web1\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web1\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web2\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web2\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web3\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web3\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web4\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web4\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_already_injected.input.yml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  creationTimestamp: null\n  name: web1\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  strategy: {}\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n        resources: {}\n      - image: cr.l5d.io/linkerd/proxy:foo\n        name: linkerd-proxy\nstatus: {}\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  creationTimestamp: null\n  name: web2\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  strategy: {}\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n        resources: {}\n      - name: linkerd-proxy\nstatus: {}\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  creationTimestamp: null\n  name: web3\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  strategy: {}\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n        resources: {}\n      initContainers:\n      - image: cr.l5d.io/linkerd/proxy-init:foo\n        name: linkerd-init\nstatus: {}\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  creationTimestamp: null\n  name: web4\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  strategy: {}\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n        resources: {}\n      initContainers:\n      - name: linkerd-init\nstatus: {}\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_already_injected.report",
    "content": "\ndeployment \"web1\" injected\ndeployment \"web2\" injected\ndeployment \"web3\" injected\ndeployment \"web4\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_already_injected.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"web1\" injected\ndeployment \"web2\" injected\ndeployment \"web3\" injected\ndeployment \"web4\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_cronjob.golden.yml",
    "content": "apiVersion: batch/v1beta1\nkind: CronJob\nmetadata:\n  name: hello\n  namespace: emojivoto\nspec:\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          annotations:\n            linkerd.io/inject: enabled\n          labels:\n            foo: bar\n        spec:\n          containers:\n          - args:\n            - /bin/sh\n            - -c\n            - date; echo Hello from the Kubernetes cluster\n            image: busybox\n            name: hello\n          restartPolicy: OnFailure\n  schedule: '*/10 * * * *'\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_cronjob.input.yml",
    "content": "apiVersion: batch/v1beta1\nkind: CronJob\nmetadata:\n  namespace: emojivoto\n  name: hello\nspec:\n  schedule: \"*/10 * * * *\"\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            foo: bar\n        spec:\n          containers:\n          - name: hello\n            image: busybox\n            args:\n            - /bin/sh\n            - -c\n            - date; echo Hello from the Kubernetes cluster\n          restartPolicy: OnFailure\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_cronjob.report",
    "content": "\ncronjob \"hello\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_cronjob.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ncronjob \"hello\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_cronjob_nometa.golden.yml",
    "content": "apiVersion: batch/v1beta1\nkind: CronJob\nmetadata:\n  name: hello\n  namespace: emojivoto\nspec:\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          annotations:\n            linkerd.io/inject: enabled\n        spec:\n          containers:\n          - args:\n            - /bin/sh\n            - -c\n            - date; echo Hello from the Kubernetes cluster\n            image: busybox\n            name: hello\n          restartPolicy: OnFailure\n  schedule: '*/10 * * * *'\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_cronjob_nometa.input.yml",
    "content": "apiVersion: batch/v1beta1\nkind: CronJob\nmetadata:\n  namespace: emojivoto\n  name: hello\nspec:\n  schedule: \"*/10 * * * *\"\n  jobTemplate:\n    spec:\n      template:\n        spec:\n          containers:\n          - name: hello\n            image: busybox\n            args:\n            - /bin/sh\n            - -c\n            - date; echo Hello from the Kubernetes cluster\n          restartPolicy: OnFailure\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment.input.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment.report",
    "content": "\ndeployment \"web\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"web\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_access_log.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        config.linkerd.io/access-log: apache\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_ACCESS_LOG\n          value: apache\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_automountServiceAccountToken_false.golden.stderr",
    "content": "\ndeployment \"nginx\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_automountServiceAccountToken_false.golden.stderr.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"nginx\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_automountServiceAccountToken_false.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: nginx\n  name: nginx\nspec:\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: testinjectversion\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: nginx\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: nginx\n        linkerd.io/workload-ns: \"\"\n    spec:\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:testinjectversion\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - image: nginx\n        name: nginx\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:testinjectversion\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_automountServiceAccountToken_false.input.yml",
    "content": "kind: Deployment\napiVersion: apps/v1\nmetadata:\n  name: nginx\n  labels:\n    app: nginx\nspec:\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      automountServiceAccountToken: false\n      containers:\n        - name: nginx\n          image: nginx\n          ports:\n            - name: http\n              containerPort: 80\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_automountServiceAccountToken_false_volumeProjection_disabled.golden.stderr",
    "content": "Error transforming resources:\nfailed to inject deployment/nginx: automountServiceAccountToken set to \"false\", with Values.identity.serviceAccountTokenProjection set to \"false\""
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_automountServiceAccountToken_false_volumeProjection_disabled.golden.stderr.verbose",
    "content": "Error transforming resources:\nfailed to inject deployment/nginx: automountServiceAccountToken set to \"false\", with Values.identity.serviceAccountTokenProjection set to \"false\""
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_automountServiceAccountToken_false_volumeProjection_disabled.golden.yml",
    "content": ""
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_capabilities.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_BIND_SERVICE\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n        securityContext:\n          allowPrivilegeEscalation: true\n          capabilities:\n            add:\n            - NET_BIND_SERVICE\n            drop:\n            - ALL\n          runAsGroup: 33\n          runAsUser: 33\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n            - NET_BIND_SERVICE\n            drop:\n            - ALL\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_capabilities.input.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  creationTimestamp: null\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  strategy: {}\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n        securityContext:\n          allowPrivilegeEscalation: true\n          capabilities:\n            add:\n            - NET_BIND_SERVICE\n            drop:\n            - ALL\n          runAsUser: 33\n          runAsGroup: 33\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_config_overrides.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        config.linkerd.io/admin-port: \"9998\"\n        config.linkerd.io/proxy-cpu-limit: \"1\"\n        config.linkerd.io/proxy-cpu-request: \"0.5\"\n        config.linkerd.io/proxy-memory-limit: 256Mi\n        config.linkerd.io/proxy-memory-request: 64Mi\n        config.linkerd.io/proxy-version: override\n        config.linkerd.io/skip-inbound-ports: 7777,8888\n        config.linkerd.io/skip-outbound-ports: \"9999\"\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: override\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MAX\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:9998\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:override\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=9998\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 9998\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 9998\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 9998\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            cpu: \"1\"\n            memory: 256Mi\n          requests:\n            cpu: 500m\n            memory: 64Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,9998,7777,8888\n        - --outbound-ports-to-ignore\n        - \"9999\"\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:override\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            cpu: \"1\"\n            memory: 256Mi\n          requests:\n            cpu: 500m\n            memory: 64Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_config_overrides.input.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        config.linkerd.io/admin-port: \"9998\"\n        config.linkerd.io/proxy-cpu-limit: \"1\"\n        config.linkerd.io/proxy-cpu-request: \"0.5\"\n        config.linkerd.io/proxy-memory-limit: 256Mi\n        config.linkerd.io/proxy-memory-request: 64Mi\n        config.linkerd.io/proxy-version: override\n        config.linkerd.io/skip-inbound-ports: 7777,8888\n        config.linkerd.io/skip-outbound-ports: \"9999\"\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_controller_name.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: controller\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: not-controller\n  namespace: linkerd\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: not-controller\n        linkerd.io/workload-ns: linkerd\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_controller_name.input.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: not-controller\n  namespace: linkerd\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_controller_name.report",
    "content": "\ndeployment \"controller\" injected\ndeployment \"not-controller\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_controller_name.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"controller\" injected\ndeployment \"not-controller\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_controller_name_uninject.report",
    "content": "\ndeployment \"controller\" uninjected\ndeployment \"not-controller\" uninjected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_debug.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        config.linkerd.io/enable-debug-sidecar: \"true\"\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      - image: cr.l5d.io/linkerd/debug:test-inject-debug-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          exec:\n            command:\n            - \"true\"\n        name: linkerd-debug\n        readinessProbe:\n          exec:\n            command:\n            - \"true\"\n        terminationMessagePolicy: FallbackToLogsOnError\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_default_inbound_policy.golden.report",
    "content": "\ndeployment \"web\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_default_inbound_policy.golden.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"web\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_default_inbound_policy.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        config.linkerd.io/default-inbound-policy: all-authenticated\n        linkerd.io/inject: enabled\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n\n---\n\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.input.yml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  creationTimestamp: null\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  strategy: {}\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: web-svc\n    spec:\n      containers:\n        - env:\n            - name: WEB_PORT\n              value: \"80\"\n            - name: EMOJISVC_HOST\n              value: emoji-svc.emojivoto:8080\n            - name: VOTINGSVC_HOST\n              value: voting-svc.emojivoto:8080\n            - name: INDEX_BUNDLE\n              value: dist/index_bundle.js\n          image: buoyantio/emojivoto-web:v10\n          name: web-svc\n          ports:\n            - containerPort: 80\n              name: http\n          resources: {}\nstatus: {}\n---\n\n---\n\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.report",
    "content": "\ndeployment \"web\" injected\ndocument missing \"kind\" field, skipped\ndocument missing \"kind\" field, skipped\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"web\" injected\ndocument missing \"kind\" field, skipped\ndocument missing \"kind\" field, skipped\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"9100\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 9100\n          hostPort: 9100\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.input.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  creationTimestamp: null\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  strategy: {}\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 9100\n          hostPort: 9100\n          name: http\n        resources: {}\n      hostNetwork: false\nstatus: {}\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.report",
    "content": "\ndeployment \"web\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"web\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_true.golden.stderr",
    "content": "Error transforming resources:\nfailed to inject deployment/web: hostNetwork is enabled"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_true.golden.stderr.verbose",
    "content": "Error transforming resources:\nfailed to inject deployment/web: hostNetwork is enabled"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_true.golden.yml",
    "content": ""
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_true.input.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 9100\n          hostPort: 9100\n          name: http\n      hostNetwork: true\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_true_uninject.report",
    "content": "\ndeployment \"web\" skipped\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_injectDisabled.input.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: disabled\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 9100\n          hostPort: 9100\n          name: http\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_injectDisabled.report",
    "content": "\n‼ \"linkerd.io/inject: disabled\" annotation set on deployment/web\n\ndeployment \"web\" skipped\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_injectDisabled.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n‼ \"linkerd.io/inject: disabled\" annotation set on deployment/web\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"web\" skipped\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_injectDisabled_uninject.report",
    "content": "\ndeployment \"web\" skipped\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_native_sidecar.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        config.beta.linkerd.io/proxy-enable-native-sidecar: \"true\"\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        restartPolicy: Always\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        startupProbe:\n          failureThreshold: 120\n          httpGet:\n            path: /ready\n            port: 4191\n          periodSeconds: 1\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_no_init_container.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --log-format\n        - plain\n        - --log-level\n        - debug\n        - --connect-addr\n        - 1.1.1.1:20001\n        - --listen-addr\n        - 0.0.0.0:4140\n        - --timeout\n        - 10s\n        command:\n        - /usr/lib/linkerd/linkerd2-network-validator\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-network-validator\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n      volumes:\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_opaque_ports.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        config.linkerd.io/opaque-ports: 3000,5000-6000,mysql\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 3000,5000-6000,mysql\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_opaque_ports.report",
    "content": "\ndeployment \"web\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_opaque_ports.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"web\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_overridden.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        config.linkerd.io/admin-port: \"1234\"\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:1234\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=1234\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 1234\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 1234\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 1234\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,1234,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_overridden_noinject.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        config.linkerd.io/admin-port: \"1234\"\n        linkerd.io/inject: enabled\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_params.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SCOPE_PROTO_APPLE_SAUCE\n          value: valueA\n        - name: LINKERD2_PROXY_INBOUND_SCOPE_PROTO_BLUEBERRY\n          value: \"3.14\"\n        - name: LINKERD2_PROXY_OUTBOUND_SCOPE_PROTO_APPLESAUCE\n          value: valueA\n        - name: LINKERD2_PROXY_OUTBOUND_SCOPE_PROTO_BLUE_BERRY\n          value: \"true\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_proxyignores.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        config.linkerd.io/skip-inbound-ports: 22,8100-8102\n        config.linkerd.io/skip-outbound-ports: \"5432\"\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,22,8100-8102\n        - --outbound-ports-to-ignore\n        - \"5432\"\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_udp.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: web\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"9100\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 9100\n          hostPort: 9100\n          name: http\n          protocol: UDP\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_udp.input.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 9100\n          hostPort: 9100\n          name: http\n          protocol: UDP\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_udp.report",
    "content": "\n‼ deployment/web uses \"protocol: UDP\"\n\ndeployment \"web\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_udp.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n‼ deployment/web uses \"protocol: UDP\"\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"web\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_udp_uninject.report",
    "content": "\ndeployment \"web\" uninjected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_uninject.report",
    "content": "\ndeployment \"web\" uninjected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_deployment_uninjected.input.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      annotations:\n        config.linkerd.io/admin-port: \"1234\"\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_istio.golden.stderr",
    "content": "Error transforming resources:\nfailed to inject deployment/web: pod has a sidecar injected already"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_istio.golden.stderr.verbose",
    "content": "Error transforming resources:\nfailed to inject deployment/web: pod has a sidecar injected already"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_istio.golden.yml",
    "content": ""
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_istio.input.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  template:\n    metadata:\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      - image: gcr.io/istio-release/proxyv2:1.0.2\n        name: istio-proxy\n      initContainers:\n      - image: gcr.io/istio-release/proxy_init:1.0.2\n        name: istio-init\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_istio_uninject.report",
    "content": "\ndeployment \"web\" skipped\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_list.golden.yml",
    "content": "apiVersion: v1\nitems:\n- apiVersion: apps/v1\n  kind: Deployment\n  metadata:\n    name: web\n    namespace: emojivoto\n  spec:\n    replicas: 1\n    selector:\n      matchLabels:\n        app: web-svc\n    template:\n      metadata:\n        annotations:\n          linkerd.io/created-by: linkerd/cli dev-undefined\n          linkerd.io/proxy-version: test-inject-proxy-version\n          linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        labels:\n          app: web-svc\n          linkerd.io/control-plane-ns: linkerd\n          linkerd.io/proxy-deployment: web\n          linkerd.io/workload-ns: emojivoto\n      spec:\n        containers:\n        - env:\n          - name: _pod_name\n            valueFrom:\n              fieldRef:\n                fieldPath: metadata.name\n          - name: _pod_ns\n            valueFrom:\n              fieldRef:\n                fieldPath: metadata.namespace\n          - name: _pod_uid\n            valueFrom:\n              fieldRef:\n                fieldPath: metadata.uid\n          - name: _pod_ip\n            valueFrom:\n              fieldRef:\n                fieldPath: status.podIP\n          - name: _pod_nodeName\n            valueFrom:\n              fieldRef:\n                fieldPath: spec.nodeName\n          - name: _pod_containerName\n            value: linkerd-proxy\n          - name: LINKERD2_PROXY_CORES\n            value: \"1\"\n          - name: LINKERD2_PROXY_CORES_MIN\n            value: \"1\"\n          - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n            value: \"false\"\n          - name: LINKERD2_PROXY_LOG\n            value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n          - name: LINKERD2_PROXY_LOG_FORMAT\n            value: plain\n          - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n            value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n          - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n            value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n          - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n            value: linkerd-policy.linkerd.svc.cluster.local.:8090\n          - name: LINKERD2_PROXY_POLICY_WORKLOAD\n            value: |\n              {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n          - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n            value: all-unauthenticated\n          - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n            value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n          - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n            value: 3s\n          - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n            value: 5m\n          - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n            value: 1h\n          - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n            value: 100ms\n          - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n            value: 1000ms\n          - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n            value: 5s\n          - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n            value: 90s\n          - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n            value: 0.0.0.0:4190\n          - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n            value: 0.0.0.0:4191\n          - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n            value: 127.0.0.1:4140\n          - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n            value: 127.0.0.1:4140\n          - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n            value: 0.0.0.0:4143\n          - name: LINKERD2_PROXY_INBOUND_IPS\n            valueFrom:\n              fieldRef:\n                fieldPath: status.podIPs\n          - name: LINKERD2_PROXY_INBOUND_PORTS\n            value: \"80\"\n          - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n            value: svc.cluster.local.\n          - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n            value: 10000ms\n          - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n            value: 10000ms\n          - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n            value: 30s\n          - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n            value: 30s\n          - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n            value: \"false\"\n          - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n            value: 10s\n          - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n            value: 3s\n          - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n            value: 10s\n          - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n            value: 3s\n          - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n            value: 25,587,3306,4444,5432,6379,9300,11211\n          - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n            value: |\n              {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n          - name: _pod_sa\n            valueFrom:\n              fieldRef:\n                fieldPath: spec.serviceAccountName\n          - name: _l5d_ns\n            value: linkerd\n          - name: _l5d_trustdomain\n            value: cluster.local\n          - name: LINKERD2_PROXY_IDENTITY_DIR\n            value: /var/run/linkerd/identity/end-entity\n          - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n            value: |\n              -----BEGIN CERTIFICATE-----\n              MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n              JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n              MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n              ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n              l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n              uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n              /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n              aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n              IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n              vgUC0d2/9FMueIVMb+46WTCOjsqr\n              -----END CERTIFICATE-----\n          - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n            value: /var/run/secrets/tokens/linkerd-identity-token\n          - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n            value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n          - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n            value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n          - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n            value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n          - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n            value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n          - name: LINKERD2_PROXY_POLICY_SVC_NAME\n            value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n          image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n          imagePullPolicy: IfNotPresent\n          lifecycle:\n            postStart:\n              exec:\n                command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n          livenessProbe:\n            httpGet:\n              path: /live\n              port: 4191\n            initialDelaySeconds: 10\n            timeoutSeconds: 1\n          name: linkerd-proxy\n          ports:\n          - containerPort: 4143\n            name: linkerd-proxy\n          - containerPort: 4191\n            name: linkerd-admin\n          readinessProbe:\n            httpGet:\n              path: /ready\n              port: 4191\n            initialDelaySeconds: 2\n            timeoutSeconds: 1\n          securityContext:\n            allowPrivilegeEscalation: false\n            readOnlyRootFilesystem: true\n            runAsNonRoot: true\n            runAsUser: 2102\n            seccompProfile:\n              type: RuntimeDefault\n          terminationMessagePolicy: FallbackToLogsOnError\n          volumeMounts:\n          - mountPath: /var/run/linkerd/identity/end-entity\n            name: linkerd-identity-end-entity\n          - mountPath: /var/run/secrets/tokens\n            name: linkerd-identity-token\n        - env:\n          - name: WEB_PORT\n            value: \"80\"\n          - name: EMOJISVC_HOST\n            value: emoji-svc.emojivoto:8080\n          - name: VOTINGSVC_HOST\n            value: voting-svc.emojivoto:8080\n          - name: INDEX_BUNDLE\n            value: dist/index_bundle.js\n          image: buoyantio/emojivoto-web:v10\n          name: web-svc\n          ports:\n          - containerPort: 80\n            name: http\n        initContainers:\n        - args:\n          - --firewall-bin-path\n          - iptables-nft\n          - --firewall-save-bin-path\n          - iptables-nft-save\n          - --ipv6=false\n          - --incoming-proxy-port\n          - \"4143\"\n          - --outgoing-proxy-port\n          - \"4140\"\n          - --proxy-uid\n          - \"2102\"\n          - --inbound-ports-to-ignore\n          - 4190,4191,4567,4568\n          - --outbound-ports-to-ignore\n          - 4567,4568\n          command:\n          - /usr/lib/linkerd/linkerd2-proxy-init\n          image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n          imagePullPolicy: IfNotPresent\n          name: linkerd-init\n          securityContext:\n            allowPrivilegeEscalation: false\n            capabilities:\n              add:\n              - NET_ADMIN\n              - NET_RAW\n            privileged: false\n            readOnlyRootFilesystem: true\n            runAsGroup: 65534\n            runAsNonRoot: true\n            runAsUser: 65534\n            seccompProfile:\n              type: RuntimeDefault\n          terminationMessagePolicy: FallbackToLogsOnError\n          volumeMounts:\n          - mountPath: /run\n            name: linkerd-proxy-init-xtables-lock\n        volumes:\n        - emptyDir: {}\n          name: linkerd-proxy-init-xtables-lock\n        - emptyDir:\n            medium: Memory\n          name: linkerd-identity-end-entity\n        - name: linkerd-identity-token\n          projected:\n            sources:\n            - serviceAccountToken:\n                audience: identity.l5d.io\n                expirationSeconds: 86400\n                path: linkerd-identity-token\n- apiVersion: apps/v1\n  kind: Deployment\n  metadata:\n    name: emoji\n    namespace: emojivoto\n  spec:\n    replicas: 1\n    selector:\n      matchLabels:\n        app: emoji-svc\n    template:\n      metadata:\n        annotations:\n          linkerd.io/created-by: linkerd/cli dev-undefined\n          linkerd.io/proxy-version: test-inject-proxy-version\n          linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        labels:\n          app: emoji-svc\n          linkerd.io/control-plane-ns: linkerd\n          linkerd.io/proxy-deployment: emoji\n          linkerd.io/workload-ns: emojivoto\n      spec:\n        containers:\n        - env:\n          - name: _pod_name\n            valueFrom:\n              fieldRef:\n                fieldPath: metadata.name\n          - name: _pod_ns\n            valueFrom:\n              fieldRef:\n                fieldPath: metadata.namespace\n          - name: _pod_uid\n            valueFrom:\n              fieldRef:\n                fieldPath: metadata.uid\n          - name: _pod_ip\n            valueFrom:\n              fieldRef:\n                fieldPath: status.podIP\n          - name: _pod_nodeName\n            valueFrom:\n              fieldRef:\n                fieldPath: spec.nodeName\n          - name: _pod_containerName\n            value: linkerd-proxy\n          - name: LINKERD2_PROXY_CORES\n            value: \"1\"\n          - name: LINKERD2_PROXY_CORES_MIN\n            value: \"1\"\n          - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n            value: \"false\"\n          - name: LINKERD2_PROXY_LOG\n            value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n          - name: LINKERD2_PROXY_LOG_FORMAT\n            value: plain\n          - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n            value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n          - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n            value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n          - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n            value: linkerd-policy.linkerd.svc.cluster.local.:8090\n          - name: LINKERD2_PROXY_POLICY_WORKLOAD\n            value: |\n              {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n          - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n            value: all-unauthenticated\n          - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n            value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n          - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n            value: 3s\n          - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n            value: 5m\n          - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n            value: 1h\n          - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n            value: 100ms\n          - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n            value: 1000ms\n          - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n            value: 5s\n          - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n            value: 90s\n          - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n            value: 0.0.0.0:4190\n          - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n            value: 0.0.0.0:4191\n          - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n            value: 127.0.0.1:4140\n          - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n            value: 127.0.0.1:4140\n          - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n            value: 0.0.0.0:4143\n          - name: LINKERD2_PROXY_INBOUND_IPS\n            valueFrom:\n              fieldRef:\n                fieldPath: status.podIPs\n          - name: LINKERD2_PROXY_INBOUND_PORTS\n            value: \"8080\"\n          - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n            value: svc.cluster.local.\n          - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n            value: 10000ms\n          - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n            value: 10000ms\n          - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n            value: 30s\n          - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n            value: 30s\n          - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n            value: \"false\"\n          - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n            value: 10s\n          - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n            value: 3s\n          - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n            value: 10s\n          - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n            value: 3s\n          - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n            value: 25,587,3306,4444,5432,6379,9300,11211\n          - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n            value: |\n              {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n          - name: _pod_sa\n            valueFrom:\n              fieldRef:\n                fieldPath: spec.serviceAccountName\n          - name: _l5d_ns\n            value: linkerd\n          - name: _l5d_trustdomain\n            value: cluster.local\n          - name: LINKERD2_PROXY_IDENTITY_DIR\n            value: /var/run/linkerd/identity/end-entity\n          - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n            value: |\n              -----BEGIN CERTIFICATE-----\n              MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n              JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n              MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n              ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n              l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n              uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n              /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n              aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n              IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n              vgUC0d2/9FMueIVMb+46WTCOjsqr\n              -----END CERTIFICATE-----\n          - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n            value: /var/run/secrets/tokens/linkerd-identity-token\n          - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n            value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n          - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n            value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n          - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n            value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n          - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n            value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n          - name: LINKERD2_PROXY_POLICY_SVC_NAME\n            value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n          image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n          imagePullPolicy: IfNotPresent\n          lifecycle:\n            postStart:\n              exec:\n                command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n          livenessProbe:\n            httpGet:\n              path: /live\n              port: 4191\n            initialDelaySeconds: 10\n            timeoutSeconds: 1\n          name: linkerd-proxy\n          ports:\n          - containerPort: 4143\n            name: linkerd-proxy\n          - containerPort: 4191\n            name: linkerd-admin\n          readinessProbe:\n            httpGet:\n              path: /ready\n              port: 4191\n            initialDelaySeconds: 2\n            timeoutSeconds: 1\n          securityContext:\n            allowPrivilegeEscalation: false\n            readOnlyRootFilesystem: true\n            runAsNonRoot: true\n            runAsUser: 2102\n            seccompProfile:\n              type: RuntimeDefault\n          terminationMessagePolicy: FallbackToLogsOnError\n          volumeMounts:\n          - mountPath: /var/run/linkerd/identity/end-entity\n            name: linkerd-identity-end-entity\n          - mountPath: /var/run/secrets/tokens\n            name: linkerd-identity-token\n        - env:\n          - name: GRPC_PORT\n            value: \"8080\"\n          image: buoyantio/emojivoto-emoji-svc:v10\n          name: emoji-svc\n          ports:\n          - containerPort: 8080\n            name: grpc\n            protocol: TCP\n        initContainers:\n        - args:\n          - --firewall-bin-path\n          - iptables-nft\n          - --firewall-save-bin-path\n          - iptables-nft-save\n          - --ipv6=false\n          - --incoming-proxy-port\n          - \"4143\"\n          - --outgoing-proxy-port\n          - \"4140\"\n          - --proxy-uid\n          - \"2102\"\n          - --inbound-ports-to-ignore\n          - 4190,4191,4567,4568\n          - --outbound-ports-to-ignore\n          - 4567,4568\n          command:\n          - /usr/lib/linkerd/linkerd2-proxy-init\n          image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n          imagePullPolicy: IfNotPresent\n          name: linkerd-init\n          securityContext:\n            allowPrivilegeEscalation: false\n            capabilities:\n              add:\n              - NET_ADMIN\n              - NET_RAW\n            privileged: false\n            readOnlyRootFilesystem: true\n            runAsGroup: 65534\n            runAsNonRoot: true\n            runAsUser: 65534\n            seccompProfile:\n              type: RuntimeDefault\n          terminationMessagePolicy: FallbackToLogsOnError\n          volumeMounts:\n          - mountPath: /run\n            name: linkerd-proxy-init-xtables-lock\n        volumes:\n        - emptyDir: {}\n          name: linkerd-proxy-init-xtables-lock\n        - emptyDir:\n            medium: Memory\n          name: linkerd-identity-end-entity\n        - name: linkerd-identity-token\n          projected:\n            sources:\n            - serviceAccountToken:\n                audience: identity.l5d.io\n                expirationSeconds: 86400\n                path: linkerd-identity-token\nkind: List\nmetadata: {}\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_list.input.yml",
    "content": "apiVersion: v1\nitems:\n- apiVersion: apps/v1\n  kind: Deployment\n  metadata:\n    name: web\n    namespace: emojivoto\n  spec:\n    replicas: 1\n    selector:\n      matchLabels:\n        app: web-svc\n    template:\n      metadata:\n        labels:\n          app: web-svc\n      spec:\n        containers:\n        - env:\n          - name: WEB_PORT\n            value: \"80\"\n          - name: EMOJISVC_HOST\n            value: emoji-svc.emojivoto:8080\n          - name: VOTINGSVC_HOST\n            value: voting-svc.emojivoto:8080\n          - name: INDEX_BUNDLE\n            value: dist/index_bundle.js\n          image: buoyantio/emojivoto-web:v10\n          name: web-svc\n          ports:\n          - containerPort: 80\n            name: http\n- apiVersion: apps/v1\n  kind: Deployment\n  metadata:\n    name: emoji\n    namespace: emojivoto\n  spec:\n    replicas: 1\n    selector:\n      matchLabels:\n        app: emoji-svc\n    template:\n      metadata:\n        labels:\n          app: emoji-svc\n      spec:\n        containers:\n        - env:\n          - name: GRPC_PORT\n            value: \"8080\"\n          image: buoyantio/emojivoto-emoji-svc:v10\n          name: emoji-svc\n          ports:\n          - containerPort: 8080\n            name: grpc\n            protocol: TCP\nkind: List\nmetadata: {}\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_list.report",
    "content": "\ndeployment \"web\" injected\ndeployment \"emoji\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_list.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"web\" injected\ndeployment \"emoji\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_list_empty_resources.golden.yml",
    "content": "apiVersion: v1\nitems:\n- apiVersion: apps/v1\n  kind: Deployment\n  metadata:\n    name: web\n    namespace: emojivoto\n  spec:\n    replicas: 1\n    selector:\n      matchLabels:\n        app: web-svc\n    template:\n      metadata:\n        annotations:\n          linkerd.io/created-by: linkerd/cli dev-undefined\n          linkerd.io/proxy-version: test-inject-proxy-version\n          linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        labels:\n          app: web-svc\n          linkerd.io/control-plane-ns: linkerd\n          linkerd.io/proxy-deployment: web\n          linkerd.io/workload-ns: emojivoto\n      spec:\n        containers:\n        - env:\n          - name: _pod_name\n            valueFrom:\n              fieldRef:\n                fieldPath: metadata.name\n          - name: _pod_ns\n            valueFrom:\n              fieldRef:\n                fieldPath: metadata.namespace\n          - name: _pod_uid\n            valueFrom:\n              fieldRef:\n                fieldPath: metadata.uid\n          - name: _pod_ip\n            valueFrom:\n              fieldRef:\n                fieldPath: status.podIP\n          - name: _pod_nodeName\n            valueFrom:\n              fieldRef:\n                fieldPath: spec.nodeName\n          - name: _pod_containerName\n            value: linkerd-proxy\n          - name: LINKERD2_PROXY_CORES\n            value: \"1\"\n          - name: LINKERD2_PROXY_CORES_MIN\n            value: \"1\"\n          - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n            value: \"false\"\n          - name: LINKERD2_PROXY_LOG\n            value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n          - name: LINKERD2_PROXY_LOG_FORMAT\n            value: plain\n          - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n            value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n          - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n            value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n          - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n            value: linkerd-policy.linkerd.svc.cluster.local.:8090\n          - name: LINKERD2_PROXY_POLICY_WORKLOAD\n            value: |\n              {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n          - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n            value: all-unauthenticated\n          - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n            value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n          - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n            value: 3s\n          - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n            value: 5m\n          - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n            value: 1h\n          - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n            value: 100ms\n          - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n            value: 1000ms\n          - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n            value: 5s\n          - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n            value: 90s\n          - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n            value: 0.0.0.0:4190\n          - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n            value: 0.0.0.0:4191\n          - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n            value: 127.0.0.1:4140\n          - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n            value: 127.0.0.1:4140\n          - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n            value: 0.0.0.0:4143\n          - name: LINKERD2_PROXY_INBOUND_IPS\n            valueFrom:\n              fieldRef:\n                fieldPath: status.podIPs\n          - name: LINKERD2_PROXY_INBOUND_PORTS\n            value: \"80\"\n          - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n            value: svc.cluster.local.\n          - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n            value: 10000ms\n          - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n            value: 10000ms\n          - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n            value: 30s\n          - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n            value: 30s\n          - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n            value: \"false\"\n          - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n            value: 10s\n          - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n            value: 3s\n          - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n            value: 10s\n          - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n            value: 3s\n          - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n            value: 25,587,3306,4444,5432,6379,9300,11211\n          - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n            value: |\n              {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n          - name: _pod_sa\n            valueFrom:\n              fieldRef:\n                fieldPath: spec.serviceAccountName\n          - name: _l5d_ns\n            value: linkerd\n          - name: _l5d_trustdomain\n            value: cluster.local\n          - name: LINKERD2_PROXY_IDENTITY_DIR\n            value: /var/run/linkerd/identity/end-entity\n          - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n            value: |\n              -----BEGIN CERTIFICATE-----\n              MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n              JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n              MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n              ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n              l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n              uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n              /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n              aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n              IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n              vgUC0d2/9FMueIVMb+46WTCOjsqr\n              -----END CERTIFICATE-----\n          - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n            value: /var/run/secrets/tokens/linkerd-identity-token\n          - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n            value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n          - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n            value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n          - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n            value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n          - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n            value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n          - name: LINKERD2_PROXY_POLICY_SVC_NAME\n            value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n          image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n          imagePullPolicy: IfNotPresent\n          lifecycle:\n            postStart:\n              exec:\n                command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n          livenessProbe:\n            httpGet:\n              path: /live\n              port: 4191\n            initialDelaySeconds: 10\n            timeoutSeconds: 1\n          name: linkerd-proxy\n          ports:\n          - containerPort: 4143\n            name: linkerd-proxy\n          - containerPort: 4191\n            name: linkerd-admin\n          readinessProbe:\n            httpGet:\n              path: /ready\n              port: 4191\n            initialDelaySeconds: 2\n            timeoutSeconds: 1\n          securityContext:\n            allowPrivilegeEscalation: false\n            readOnlyRootFilesystem: true\n            runAsNonRoot: true\n            runAsUser: 2102\n            seccompProfile:\n              type: RuntimeDefault\n          terminationMessagePolicy: FallbackToLogsOnError\n          volumeMounts:\n          - mountPath: /var/run/linkerd/identity/end-entity\n            name: linkerd-identity-end-entity\n          - mountPath: /var/run/secrets/tokens\n            name: linkerd-identity-token\n        - env:\n          - name: WEB_PORT\n            value: \"80\"\n          - name: EMOJISVC_HOST\n            value: emoji-svc.emojivoto:8080\n          - name: VOTINGSVC_HOST\n            value: voting-svc.emojivoto:8080\n          - name: INDEX_BUNDLE\n            value: dist/index_bundle.js\n          image: buoyantio/emojivoto-web:v10\n          name: web-svc\n          ports:\n          - containerPort: 80\n            name: http\n        initContainers:\n        - args:\n          - --firewall-bin-path\n          - iptables-nft\n          - --firewall-save-bin-path\n          - iptables-nft-save\n          - --ipv6=false\n          - --incoming-proxy-port\n          - \"4143\"\n          - --outgoing-proxy-port\n          - \"4140\"\n          - --proxy-uid\n          - \"2102\"\n          - --inbound-ports-to-ignore\n          - 4190,4191,4567,4568\n          - --outbound-ports-to-ignore\n          - 4567,4568\n          command:\n          - /usr/lib/linkerd/linkerd2-proxy-init\n          image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n          imagePullPolicy: IfNotPresent\n          name: linkerd-init\n          securityContext:\n            allowPrivilegeEscalation: false\n            capabilities:\n              add:\n              - NET_ADMIN\n              - NET_RAW\n            privileged: false\n            readOnlyRootFilesystem: true\n            runAsGroup: 65534\n            runAsNonRoot: true\n            runAsUser: 65534\n            seccompProfile:\n              type: RuntimeDefault\n          terminationMessagePolicy: FallbackToLogsOnError\n          volumeMounts:\n          - mountPath: /run\n            name: linkerd-proxy-init-xtables-lock\n        volumes:\n        - emptyDir: {}\n          name: linkerd-proxy-init-xtables-lock\n        - emptyDir:\n            medium: Memory\n          name: linkerd-identity-end-entity\n        - name: linkerd-identity-token\n          projected:\n            sources:\n            - serviceAccountToken:\n                audience: identity.l5d.io\n                expirationSeconds: 86400\n                path: linkerd-identity-token\n- apiVersion: apps/v1\n  kind: Deployment\n  metadata:\n    name: emoji\n    namespace: emojivoto\n  spec:\n    replicas: 1\n    selector:\n      matchLabels:\n        app: emoji-svc\n    template:\n      metadata:\n        annotations:\n          linkerd.io/created-by: linkerd/cli dev-undefined\n          linkerd.io/proxy-version: test-inject-proxy-version\n          linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        labels:\n          app: emoji-svc\n          linkerd.io/control-plane-ns: linkerd\n          linkerd.io/proxy-deployment: emoji\n          linkerd.io/workload-ns: emojivoto\n      spec:\n        containers:\n        - env:\n          - name: _pod_name\n            valueFrom:\n              fieldRef:\n                fieldPath: metadata.name\n          - name: _pod_ns\n            valueFrom:\n              fieldRef:\n                fieldPath: metadata.namespace\n          - name: _pod_uid\n            valueFrom:\n              fieldRef:\n                fieldPath: metadata.uid\n          - name: _pod_ip\n            valueFrom:\n              fieldRef:\n                fieldPath: status.podIP\n          - name: _pod_nodeName\n            valueFrom:\n              fieldRef:\n                fieldPath: spec.nodeName\n          - name: _pod_containerName\n            value: linkerd-proxy\n          - name: LINKERD2_PROXY_CORES\n            value: \"1\"\n          - name: LINKERD2_PROXY_CORES_MIN\n            value: \"1\"\n          - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n            value: \"false\"\n          - name: LINKERD2_PROXY_LOG\n            value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n          - name: LINKERD2_PROXY_LOG_FORMAT\n            value: plain\n          - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n            value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n          - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n            value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n          - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n            value: linkerd-policy.linkerd.svc.cluster.local.:8090\n          - name: LINKERD2_PROXY_POLICY_WORKLOAD\n            value: |\n              {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n          - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n            value: all-unauthenticated\n          - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n            value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n          - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n            value: 3s\n          - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n            value: 5m\n          - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n            value: 1h\n          - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n            value: 100ms\n          - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n            value: 1000ms\n          - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n            value: 5s\n          - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n            value: 90s\n          - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n            value: 0.0.0.0:4190\n          - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n            value: 0.0.0.0:4191\n          - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n            value: 127.0.0.1:4140\n          - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n            value: 127.0.0.1:4140\n          - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n            value: 0.0.0.0:4143\n          - name: LINKERD2_PROXY_INBOUND_IPS\n            valueFrom:\n              fieldRef:\n                fieldPath: status.podIPs\n          - name: LINKERD2_PROXY_INBOUND_PORTS\n            value: \"8080\"\n          - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n            value: svc.cluster.local.\n          - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n            value: 10000ms\n          - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n            value: 10000ms\n          - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n            value: 30s\n          - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n            value: 30s\n          - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n            value: \"false\"\n          - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n            value: 10s\n          - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n            value: 3s\n          - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n            value: 10s\n          - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n            value: 3s\n          - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n            value: 25,587,3306,4444,5432,6379,9300,11211\n          - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n            value: |\n              {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n          - name: _pod_sa\n            valueFrom:\n              fieldRef:\n                fieldPath: spec.serviceAccountName\n          - name: _l5d_ns\n            value: linkerd\n          - name: _l5d_trustdomain\n            value: cluster.local\n          - name: LINKERD2_PROXY_IDENTITY_DIR\n            value: /var/run/linkerd/identity/end-entity\n          - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n            value: |\n              -----BEGIN CERTIFICATE-----\n              MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n              JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n              MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n              ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n              l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n              uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n              /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n              aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n              IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n              vgUC0d2/9FMueIVMb+46WTCOjsqr\n              -----END CERTIFICATE-----\n          - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n            value: /var/run/secrets/tokens/linkerd-identity-token\n          - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n            value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n          - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n            value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n          - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n            value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n          - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n            value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n          - name: LINKERD2_PROXY_POLICY_SVC_NAME\n            value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n          image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n          imagePullPolicy: IfNotPresent\n          lifecycle:\n            postStart:\n              exec:\n                command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n          livenessProbe:\n            httpGet:\n              path: /live\n              port: 4191\n            initialDelaySeconds: 10\n            timeoutSeconds: 1\n          name: linkerd-proxy\n          ports:\n          - containerPort: 4143\n            name: linkerd-proxy\n          - containerPort: 4191\n            name: linkerd-admin\n          readinessProbe:\n            httpGet:\n              path: /ready\n              port: 4191\n            initialDelaySeconds: 2\n            timeoutSeconds: 1\n          securityContext:\n            allowPrivilegeEscalation: false\n            readOnlyRootFilesystem: true\n            runAsNonRoot: true\n            runAsUser: 2102\n            seccompProfile:\n              type: RuntimeDefault\n          terminationMessagePolicy: FallbackToLogsOnError\n          volumeMounts:\n          - mountPath: /var/run/linkerd/identity/end-entity\n            name: linkerd-identity-end-entity\n          - mountPath: /var/run/secrets/tokens\n            name: linkerd-identity-token\n        - env:\n          - name: GRPC_PORT\n            value: \"8080\"\n          image: buoyantio/emojivoto-emoji-svc:v10\n          name: emoji-svc\n          ports:\n          - containerPort: 8080\n            name: grpc\n            protocol: TCP\n        initContainers:\n        - args:\n          - --firewall-bin-path\n          - iptables-nft\n          - --firewall-save-bin-path\n          - iptables-nft-save\n          - --ipv6=false\n          - --incoming-proxy-port\n          - \"4143\"\n          - --outgoing-proxy-port\n          - \"4140\"\n          - --proxy-uid\n          - \"2102\"\n          - --inbound-ports-to-ignore\n          - 4190,4191,4567,4568\n          - --outbound-ports-to-ignore\n          - 4567,4568\n          command:\n          - /usr/lib/linkerd/linkerd2-proxy-init\n          image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n          imagePullPolicy: IfNotPresent\n          name: linkerd-init\n          securityContext:\n            allowPrivilegeEscalation: false\n            capabilities:\n              add:\n              - NET_ADMIN\n              - NET_RAW\n            privileged: false\n            readOnlyRootFilesystem: true\n            runAsGroup: 65534\n            runAsNonRoot: true\n            runAsUser: 65534\n            seccompProfile:\n              type: RuntimeDefault\n          terminationMessagePolicy: FallbackToLogsOnError\n          volumeMounts:\n          - mountPath: /run\n            name: linkerd-proxy-init-xtables-lock\n        volumes:\n        - emptyDir: {}\n          name: linkerd-proxy-init-xtables-lock\n        - emptyDir:\n            medium: Memory\n          name: linkerd-identity-end-entity\n        - name: linkerd-identity-token\n          projected:\n            sources:\n            - serviceAccountToken:\n                audience: identity.l5d.io\n                expirationSeconds: 86400\n                path: linkerd-identity-token\n- null\n- null\nkind: List\nmetadata: {}\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_list_empty_resources.input.yml",
    "content": "---\napiVersion: v1\nitems:\n  - apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      creationTimestamp: null\n      name: web\n      namespace: emojivoto\n    spec:\n      replicas: 1\n      selector:\n        matchLabels:\n          app: web-svc\n      strategy: {}\n      template:\n        metadata:\n          creationTimestamp: null\n          labels:\n            app: web-svc\n        spec:\n          containers:\n            - env:\n                - name: WEB_PORT\n                  value: \"80\"\n                - name: EMOJISVC_HOST\n                  value: emoji-svc.emojivoto:8080\n                - name: VOTINGSVC_HOST\n                  value: voting-svc.emojivoto:8080\n                - name: INDEX_BUNDLE\n                  value: dist/index_bundle.js\n              image: buoyantio/emojivoto-web:v10\n              name: web-svc\n              ports:\n                - containerPort: 80\n                  name: http\n              resources: {}\n    status: {}\n  - apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      creationTimestamp: null\n      name: emoji\n      namespace: emojivoto\n    spec:\n      replicas: 1\n      selector:\n        matchLabels:\n          app: emoji-svc\n      strategy: {}\n      template:\n        metadata:\n          creationTimestamp: null\n          labels:\n            app: emoji-svc\n        spec:\n          containers:\n            - env:\n                - name: GRPC_PORT\n                  value: \"8080\"\n              image: buoyantio/emojivoto-emoji-svc:v10\n              name: emoji-svc\n              ports:\n                - containerPort: 8080\n                  name: grpc\n                  protocol: TCP\n              resources: {}\n    status: {}\n  - null\n  - null\nkind: List\nmetadata: {}\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_list_empty_resources.report",
    "content": "\ndeployment \"web\" injected\ndeployment \"emoji\" injected\ndocument missing \"kind\" field, skipped\ndocument missing \"kind\" field, skipped\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_list_empty_resources.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"web\" injected\ndeployment \"emoji\" injected\ndocument missing \"kind\" field, skipped\ndocument missing \"kind\" field, skipped\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_list_uninject.report",
    "content": "\ndeployment \"web\" uninjected\ndeployment \"emoji\" uninjected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_namespace_good.golden.report",
    "content": "\nnamespace \"emojivoto\" annotated\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_namespace_good.golden.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\nnamespace \"emojivoto\" annotated\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_namespace_good.golden.yml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  annotations:\n    linkerd.io/inject: enabled\n  name: emojivoto\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_namespace_good.input.yml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: emojivoto\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_namespace_overidden_good.golden.yml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  annotations:\n    linkerd.io/inject: enabled\n  name: emojivoto\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_namespace_uninjected_good.golden.report",
    "content": "\nnamespace \"emojivoto\" uninjected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_namespace_uninjected_good.golden.yml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: emojivoto\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod.golden.yml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/proxy-version: test-inject-proxy-version\n    linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n  labels:\n    app: vote-bot\n    linkerd.io/control-plane-ns: linkerd\n    linkerd.io/workload-ns: emojivoto\n  name: vote-bot\n  namespace: emojivoto\nspec:\n  containers:\n  - env:\n    - name: _pod_name\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.name\n    - name: _pod_ns\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.namespace\n    - name: _pod_uid\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.uid\n    - name: _pod_ip\n      valueFrom:\n        fieldRef:\n          fieldPath: status.podIP\n    - name: _pod_nodeName\n      valueFrom:\n        fieldRef:\n          fieldPath: spec.nodeName\n    - name: _pod_containerName\n      value: linkerd-proxy\n    - name: LINKERD2_PROXY_CORES\n      value: \"1\"\n    - name: LINKERD2_PROXY_CORES_MIN\n      value: \"1\"\n    - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n      value: \"false\"\n    - name: LINKERD2_PROXY_LOG\n      value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n    - name: LINKERD2_PROXY_LOG_FORMAT\n      value: plain\n    - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n      value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n    - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n      value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n      value: linkerd-policy.linkerd.svc.cluster.local.:8090\n    - name: LINKERD2_PROXY_POLICY_WORKLOAD\n      value: |\n        {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n    - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n      value: all-unauthenticated\n    - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n      value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n      value: 3s\n    - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n      value: 5m\n    - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n      value: 1h\n    - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n      value: 100ms\n    - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n      value: 1000ms\n    - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n      value: 5s\n    - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n      value: 90s\n    - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n      value: 0.0.0.0:4190\n    - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n      value: 0.0.0.0:4191\n    - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n      value: 127.0.0.1:4140\n    - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n      value: 127.0.0.1:4140\n    - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n      value: 0.0.0.0:4143\n    - name: LINKERD2_PROXY_INBOUND_IPS\n      valueFrom:\n        fieldRef:\n          fieldPath: status.podIPs\n    - name: LINKERD2_PROXY_INBOUND_PORTS\n    - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n      value: svc.cluster.local.\n    - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n      value: 10000ms\n    - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n      value: 10000ms\n    - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n      value: 30s\n    - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n      value: 30s\n    - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n      value: \"false\"\n    - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n      value: 10s\n    - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n      value: 3s\n    - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n      value: 10s\n    - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n      value: 3s\n    - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n      value: 25,587,3306,4444,5432,6379,9300,11211\n    - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n      value: |\n        {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n    - name: _pod_sa\n      valueFrom:\n        fieldRef:\n          fieldPath: spec.serviceAccountName\n    - name: _l5d_ns\n      value: linkerd\n    - name: _l5d_trustdomain\n      value: cluster.local\n    - name: LINKERD2_PROXY_IDENTITY_DIR\n      value: /var/run/linkerd/identity/end-entity\n    - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n      value: |\n        -----BEGIN CERTIFICATE-----\n        MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n        JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n        MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n        ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n        l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n        uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n        /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n        aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n        IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n        vgUC0d2/9FMueIVMb+46WTCOjsqr\n        -----END CERTIFICATE-----\n    - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n      value: /var/run/secrets/tokens/linkerd-identity-token\n    - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n      value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n    - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n      value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n    - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n      value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n    - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n      value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n    - name: LINKERD2_PROXY_POLICY_SVC_NAME\n      value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n    image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n    imagePullPolicy: IfNotPresent\n    lifecycle:\n      postStart:\n        exec:\n          command:\n          - /usr/lib/linkerd/linkerd-await\n          - --timeout=2m\n          - --port=4191\n    livenessProbe:\n      httpGet:\n        path: /live\n        port: 4191\n      initialDelaySeconds: 10\n      timeoutSeconds: 1\n    name: linkerd-proxy\n    ports:\n    - containerPort: 4143\n      name: linkerd-proxy\n    - containerPort: 4191\n      name: linkerd-admin\n    readinessProbe:\n      httpGet:\n        path: /ready\n        port: 4191\n      initialDelaySeconds: 2\n      timeoutSeconds: 1\n    securityContext:\n      allowPrivilegeEscalation: false\n      readOnlyRootFilesystem: true\n      runAsNonRoot: true\n      runAsUser: 2102\n      seccompProfile:\n        type: RuntimeDefault\n    terminationMessagePolicy: FallbackToLogsOnError\n    volumeMounts:\n    - mountPath: /var/run/linkerd/identity/end-entity\n      name: linkerd-identity-end-entity\n    - mountPath: /var/run/secrets/tokens\n      name: linkerd-identity-token\n  - command:\n    - emojivoto-vote-bot\n    env:\n    - name: WEB_HOST\n      value: web-svc.emojivoto:80\n    image: buoyantio/emojivoto-web:v10\n    name: vote-bot\n  initContainers:\n  - args:\n    - --firewall-bin-path\n    - iptables-nft\n    - --firewall-save-bin-path\n    - iptables-nft-save\n    - --ipv6=false\n    - --incoming-proxy-port\n    - \"4143\"\n    - --outgoing-proxy-port\n    - \"4140\"\n    - --proxy-uid\n    - \"2102\"\n    - --inbound-ports-to-ignore\n    - 4190,4191,4567,4568\n    - --outbound-ports-to-ignore\n    - 4567,4568\n    command:\n    - /usr/lib/linkerd/linkerd2-proxy-init\n    image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n    imagePullPolicy: IfNotPresent\n    name: linkerd-init\n    securityContext:\n      allowPrivilegeEscalation: false\n      capabilities:\n        add:\n        - NET_ADMIN\n        - NET_RAW\n      privileged: false\n      readOnlyRootFilesystem: true\n      runAsGroup: 65534\n      runAsNonRoot: true\n      runAsUser: 65534\n      seccompProfile:\n        type: RuntimeDefault\n    terminationMessagePolicy: FallbackToLogsOnError\n    volumeMounts:\n    - mountPath: /run\n      name: linkerd-proxy-init-xtables-lock\n  volumes:\n  - emptyDir: {}\n    name: linkerd-proxy-init-xtables-lock\n  - emptyDir:\n      medium: Memory\n    name: linkerd-identity-end-entity\n  - name: linkerd-identity-token\n    projected:\n      sources:\n      - serviceAccountToken:\n          audience: identity.l5d.io\n          expirationSeconds: 86400\n          path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod.input.yml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    app: vote-bot\n  name: vote-bot\n  namespace: emojivoto\nspec:\n  containers:\n  - command:\n    - emojivoto-vote-bot\n    env:\n    - name: WEB_HOST\n      value: web-svc.emojivoto:80\n    image: buoyantio/emojivoto-web:v10\n    name: vote-bot\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod.report",
    "content": "\npod \"vote-bot\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\npod \"vote-bot\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod_default_inbound_policy.golden.report",
    "content": "\npod \"vote-bot\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod_default_inbound_policy.golden.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\npod \"vote-bot\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod_default_inbound_policy.golden.yml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  annotations:\n    config.linkerd.io/default-inbound-policy: all-authenticated\n    linkerd.io/inject: enabled\n  labels:\n    app: vote-bot\n  name: vote-bot\n  namespace: emojivoto\nspec:\n  containers:\n  - command:\n    - emojivoto-vote-bot\n    env:\n    - name: WEB_HOST\n      value: web-svc.emojivoto:80\n    image: buoyantio/emojivoto-web:v10\n    name: vote-bot\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod_ingress.golden.yml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: ingress\n    linkerd.io/proxy-version: test-inject-proxy-version\n    linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n  labels:\n    app: vote-bot\n    linkerd.io/control-plane-ns: linkerd\n    linkerd.io/workload-ns: emojivoto\n  name: vote-bot\n  namespace: emojivoto\nspec:\n  containers:\n  - env:\n    - name: _pod_name\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.name\n    - name: _pod_ns\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.namespace\n    - name: _pod_uid\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.uid\n    - name: _pod_ip\n      valueFrom:\n        fieldRef:\n          fieldPath: status.podIP\n    - name: _pod_nodeName\n      valueFrom:\n        fieldRef:\n          fieldPath: spec.nodeName\n    - name: _pod_containerName\n      value: linkerd-proxy\n    - name: LINKERD2_PROXY_CORES\n      value: \"1\"\n    - name: LINKERD2_PROXY_CORES_MIN\n      value: \"1\"\n    - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n      value: \"false\"\n    - name: LINKERD2_PROXY_LOG\n      value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n    - name: LINKERD2_PROXY_LOG_FORMAT\n      value: plain\n    - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n      value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n    - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n      value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n      value: linkerd-policy.linkerd.svc.cluster.local.:8090\n    - name: LINKERD2_PROXY_POLICY_WORKLOAD\n      value: |\n        {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n    - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n      value: all-unauthenticated\n    - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n      value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n      value: 3s\n    - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n      value: 5m\n    - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n      value: 1h\n    - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n      value: 100ms\n    - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n      value: 1000ms\n    - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n      value: 5s\n    - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n      value: 90s\n    - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n      value: 0.0.0.0:4190\n    - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n      value: 0.0.0.0:4191\n    - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n      value: 127.0.0.1:4140\n    - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n      value: 127.0.0.1:4140\n    - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n      value: 0.0.0.0:4143\n    - name: LINKERD2_PROXY_INBOUND_IPS\n      valueFrom:\n        fieldRef:\n          fieldPath: status.podIPs\n    - name: LINKERD2_PROXY_INBOUND_PORTS\n    - name: LINKERD2_PROXY_INGRESS_MODE\n      value: \"true\"\n    - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n      value: svc.cluster.local.\n    - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n      value: 10000ms\n    - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n      value: 10000ms\n    - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n      value: 30s\n    - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n      value: 30s\n    - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n      value: \"false\"\n    - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n      value: 10s\n    - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n      value: 3s\n    - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n      value: 10s\n    - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n      value: 3s\n    - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n      value: 25,587,3306,4444,5432,6379,9300,11211\n    - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n      value: |\n        {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n    - name: _pod_sa\n      valueFrom:\n        fieldRef:\n          fieldPath: spec.serviceAccountName\n    - name: _l5d_ns\n      value: linkerd\n    - name: _l5d_trustdomain\n      value: cluster.local\n    - name: LINKERD2_PROXY_IDENTITY_DIR\n      value: /var/run/linkerd/identity/end-entity\n    - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n      value: |\n        -----BEGIN CERTIFICATE-----\n        MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n        JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n        MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n        ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n        l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n        uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n        /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n        aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n        IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n        vgUC0d2/9FMueIVMb+46WTCOjsqr\n        -----END CERTIFICATE-----\n    - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n      value: /var/run/secrets/tokens/linkerd-identity-token\n    - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n      value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n    - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n      value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n    - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n      value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n    - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n      value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n    - name: LINKERD2_PROXY_POLICY_SVC_NAME\n      value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n    image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n    imagePullPolicy: IfNotPresent\n    lifecycle:\n      postStart:\n        exec:\n          command:\n          - /usr/lib/linkerd/linkerd-await\n          - --timeout=2m\n          - --port=4191\n    livenessProbe:\n      httpGet:\n        path: /live\n        port: 4191\n      initialDelaySeconds: 10\n      timeoutSeconds: 1\n    name: linkerd-proxy\n    ports:\n    - containerPort: 4143\n      name: linkerd-proxy\n    - containerPort: 4191\n      name: linkerd-admin\n    readinessProbe:\n      httpGet:\n        path: /ready\n        port: 4191\n      initialDelaySeconds: 2\n      timeoutSeconds: 1\n    securityContext:\n      allowPrivilegeEscalation: false\n      readOnlyRootFilesystem: true\n      runAsNonRoot: true\n      runAsUser: 2102\n      seccompProfile:\n        type: RuntimeDefault\n    terminationMessagePolicy: FallbackToLogsOnError\n    volumeMounts:\n    - mountPath: /var/run/linkerd/identity/end-entity\n      name: linkerd-identity-end-entity\n    - mountPath: /var/run/secrets/tokens\n      name: linkerd-identity-token\n  - command:\n    - emojivoto-vote-bot\n    env:\n    - name: WEB_HOST\n      value: web-svc.emojivoto:80\n    image: buoyantio/emojivoto-web:v10\n    name: vote-bot\n  initContainers:\n  - args:\n    - --firewall-bin-path\n    - iptables-nft\n    - --firewall-save-bin-path\n    - iptables-nft-save\n    - --ipv6=false\n    - --incoming-proxy-port\n    - \"4143\"\n    - --outgoing-proxy-port\n    - \"4140\"\n    - --proxy-uid\n    - \"2102\"\n    - --inbound-ports-to-ignore\n    - 4190,4191,4567,4568\n    - --outbound-ports-to-ignore\n    - 4567,4568\n    command:\n    - /usr/lib/linkerd/linkerd2-proxy-init\n    image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n    imagePullPolicy: IfNotPresent\n    name: linkerd-init\n    securityContext:\n      allowPrivilegeEscalation: false\n      capabilities:\n        add:\n        - NET_ADMIN\n        - NET_RAW\n      privileged: false\n      readOnlyRootFilesystem: true\n      runAsGroup: 65534\n      runAsNonRoot: true\n      runAsUser: 65534\n      seccompProfile:\n        type: RuntimeDefault\n    terminationMessagePolicy: FallbackToLogsOnError\n    volumeMounts:\n    - mountPath: /run\n      name: linkerd-proxy-init-xtables-lock\n  volumes:\n  - emptyDir: {}\n    name: linkerd-proxy-init-xtables-lock\n  - emptyDir:\n      medium: Memory\n    name: linkerd-identity-end-entity\n  - name: linkerd-identity-token\n    projected:\n      sources:\n      - serviceAccountToken:\n          audience: identity.l5d.io\n          expirationSeconds: 86400\n          path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod_ingress.report",
    "content": "\npod \"vote-bot\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod_ingress.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\npod \"vote-bot\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod_nativesidecar.golden.yml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/proxy-version: test-inject-proxy-version\n    linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n  labels:\n    app: vote-bot\n    linkerd.io/control-plane-ns: linkerd\n    linkerd.io/workload-ns: emojivoto\n  name: vote-bot\n  namespace: emojivoto\nspec:\n  containers:\n  - command:\n    - emojivoto-vote-bot\n    env:\n    - name: WEB_HOST\n      value: web-svc.emojivoto:80\n    image: buoyantio/emojivoto-web:v10\n    name: vote-bot\n  initContainers:\n  - args:\n    - --incoming-proxy-port\n    - \"4143\"\n    - --outgoing-proxy-port\n    - \"4140\"\n    - --proxy-uid\n    - \"2102\"\n    - --inbound-ports-to-ignore\n    - 4190,4191,4567,4568\n    - --outbound-ports-to-ignore\n    - 4567,4568\n    image: cr.l5d.io/linkerd/proxy-init:v2.3.0\n    imagePullPolicy: IfNotPresent\n    name: linkerd-init\n    resources:\n      limits:\n        cpu: 100m\n        memory: 20Mi\n      requests:\n        cpu: 100m\n        memory: 20Mi\n    securityContext:\n      allowPrivilegeEscalation: false\n      capabilities:\n        add:\n        - NET_ADMIN\n        - NET_RAW\n      privileged: false\n      readOnlyRootFilesystem: true\n      runAsNonRoot: true\n      runAsUser: 65534\n      seccompProfile:\n        type: RuntimeDefault\n    terminationMessagePolicy: FallbackToLogsOnError\n    volumeMounts:\n    - mountPath: /run\n      name: linkerd-proxy-init-xtables-lock\n  - env:\n    - name: _pod_name\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.name\n    - name: _pod_ns\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.namespace\n    - name: _pod_uid\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.uid\n    - name: _pod_nodeName\n      valueFrom:\n        fieldRef:\n          fieldPath: spec.nodeName\n    - name: _pod_containerName\n      value: linkerd-proxy\n    - name: LINKERD2_PROXY_LOG\n      value: warn,linkerd=info,trust_dns=error\n    - name: LINKERD2_PROXY_LOG_FORMAT\n      value: plain\n    - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n      value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n    - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n      value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n      value: linkerd-policy.linkerd.svc.cluster.local.:8090\n    - name: LINKERD2_PROXY_POLICY_WORKLOAD\n      value: |\n        {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n    - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n      value: all-unauthenticated\n    - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n      value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n      value: 3s\n    - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n      value: 5m\n    - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n      value: 1h\n    - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n      value: 100ms\n    - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n      value: 1000ms\n    - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n      value: 5s\n    - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n      value: 90s\n    - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n      value: 0.0.0.0:4190\n    - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n      value: 0.0.0.0:4191\n    - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n      value: 127.0.0.1:4140\n    - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n      value: 0.0.0.0:4143\n    - name: LINKERD2_PROXY_INBOUND_IPS\n      valueFrom:\n        fieldRef:\n          fieldPath: status.podIPs\n    - name: LINKERD2_PROXY_INBOUND_PORTS\n    - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n      value: svc.cluster.local.\n    - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n      value: 10000ms\n    - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n      value: 10000ms\n    - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n      value: 25,587,3306,4444,5432,6379,9300,11211\n    - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n      value: |\n        {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n    - name: _pod_sa\n      valueFrom:\n        fieldRef:\n          fieldPath: spec.serviceAccountName\n    - name: _l5d_ns\n      value: linkerd\n    - name: _l5d_trustdomain\n      value: cluster.local\n    - name: LINKERD2_PROXY_IDENTITY_DIR\n      value: /var/run/linkerd/identity/end-entity\n    - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n      value: |\n        -----BEGIN CERTIFICATE-----\n        MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n        JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n        MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n        ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n        l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n        uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n        /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n        aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n        IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n        vgUC0d2/9FMueIVMb+46WTCOjsqr\n        -----END CERTIFICATE-----\n    - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n      value: /var/run/secrets/tokens/linkerd-identity-token\n    - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n      value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n    - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n      value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n    - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n      value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n    - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n      value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n    - name: LINKERD2_PROXY_POLICY_SVC_NAME\n      value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n    image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n    imagePullPolicy: IfNotPresent\n    lifecycle:\n      postStart:\n        exec:\n          command:\n          - /usr/lib/linkerd/linkerd-await\n          - --timeout=2m\n          - --port=4191\n    livenessProbe:\n      httpGet:\n        path: /live\n        port: 4191\n      initialDelaySeconds: 10\n      timeoutSeconds: 1\n    name: linkerd-proxy\n    ports:\n    - containerPort: 4143\n      name: linkerd-proxy\n    - containerPort: 4191\n      name: linkerd-admin\n    readinessProbe:\n      httpGet:\n        path: /ready\n        port: 4191\n      initialDelaySeconds: 2\n      timeoutSeconds: 1\n    restartPolicy: Always\n    securityContext:\n      allowPrivilegeEscalation: false\n      readOnlyRootFilesystem: true\n      runAsNonRoot: true\n      runAsUser: 2102\n      seccompProfile:\n        type: RuntimeDefault\n    startupProbe:\n      failureThreshold: 120\n      httpGet:\n        path: /ready\n        port: 4191\n        scheme: HTTP\n      periodSeconds: 1\n      successThreshold: 1\n      timeoutSeconds: 1\n    terminationMessagePolicy: FallbackToLogsOnError\n    volumeMounts:\n    - mountPath: /var/run/linkerd/identity/end-entity\n      name: linkerd-identity-end-entity\n    - mountPath: /var/run/secrets/tokens\n      name: linkerd-identity-token\n  volumes:\n  - emptyDir: {}\n    name: linkerd-proxy-init-xtables-lock\n  - emptyDir:\n      medium: Memory\n    name: linkerd-identity-end-entity\n  - name: linkerd-identity-token\n    projected:\n      sources:\n      - serviceAccountToken:\n          audience: identity.l5d.io\n          expirationSeconds: 86400\n          path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod_proxyignores.golden.yml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  annotations:\n    config.linkerd.io/skip-inbound-ports: 22,8100-8102\n    config.linkerd.io/skip-outbound-ports: \"5432\"\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/proxy-version: test-inject-proxy-version\n    linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n  labels:\n    app: vote-bot\n    linkerd.io/control-plane-ns: linkerd\n    linkerd.io/workload-ns: emojivoto\n  name: vote-bot\n  namespace: emojivoto\nspec:\n  containers:\n  - env:\n    - name: _pod_name\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.name\n    - name: _pod_ns\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.namespace\n    - name: _pod_uid\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.uid\n    - name: _pod_ip\n      valueFrom:\n        fieldRef:\n          fieldPath: status.podIP\n    - name: _pod_nodeName\n      valueFrom:\n        fieldRef:\n          fieldPath: spec.nodeName\n    - name: _pod_containerName\n      value: linkerd-proxy\n    - name: LINKERD2_PROXY_CORES\n      value: \"1\"\n    - name: LINKERD2_PROXY_CORES_MIN\n      value: \"1\"\n    - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n      value: \"false\"\n    - name: LINKERD2_PROXY_LOG\n      value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n    - name: LINKERD2_PROXY_LOG_FORMAT\n      value: plain\n    - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n      value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n    - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n      value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n      value: linkerd-policy.linkerd.svc.cluster.local.:8090\n    - name: LINKERD2_PROXY_POLICY_WORKLOAD\n      value: |\n        {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n    - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n      value: all-unauthenticated\n    - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n      value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n      value: 3s\n    - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n      value: 5m\n    - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n      value: 1h\n    - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n      value: 100ms\n    - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n      value: 1000ms\n    - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n      value: 5s\n    - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n      value: 90s\n    - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n      value: 0.0.0.0:4190\n    - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n      value: 0.0.0.0:4191\n    - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n      value: 127.0.0.1:4140\n    - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n      value: 127.0.0.1:4140\n    - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n      value: 0.0.0.0:4143\n    - name: LINKERD2_PROXY_INBOUND_IPS\n      valueFrom:\n        fieldRef:\n          fieldPath: status.podIPs\n    - name: LINKERD2_PROXY_INBOUND_PORTS\n    - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n      value: svc.cluster.local.\n    - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n      value: 10000ms\n    - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n      value: 10000ms\n    - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n      value: 30s\n    - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n      value: 30s\n    - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n      value: \"false\"\n    - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n      value: 10s\n    - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n      value: 3s\n    - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n      value: 10s\n    - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n      value: 3s\n    - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n      value: 25,587,3306,4444,5432,6379,9300,11211\n    - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n      value: |\n        {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n    - name: _pod_sa\n      valueFrom:\n        fieldRef:\n          fieldPath: spec.serviceAccountName\n    - name: _l5d_ns\n      value: linkerd\n    - name: _l5d_trustdomain\n      value: cluster.local\n    - name: LINKERD2_PROXY_IDENTITY_DIR\n      value: /var/run/linkerd/identity/end-entity\n    - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n      value: |\n        -----BEGIN CERTIFICATE-----\n        MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n        JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n        MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n        ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n        l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n        uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n        /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n        aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n        IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n        vgUC0d2/9FMueIVMb+46WTCOjsqr\n        -----END CERTIFICATE-----\n    - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n      value: /var/run/secrets/tokens/linkerd-identity-token\n    - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n      value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n    - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n      value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n    - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n      value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n    - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n      value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n    - name: LINKERD2_PROXY_POLICY_SVC_NAME\n      value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n    image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n    imagePullPolicy: IfNotPresent\n    lifecycle:\n      postStart:\n        exec:\n          command:\n          - /usr/lib/linkerd/linkerd-await\n          - --timeout=2m\n          - --port=4191\n    livenessProbe:\n      httpGet:\n        path: /live\n        port: 4191\n      initialDelaySeconds: 10\n      timeoutSeconds: 1\n    name: linkerd-proxy\n    ports:\n    - containerPort: 4143\n      name: linkerd-proxy\n    - containerPort: 4191\n      name: linkerd-admin\n    readinessProbe:\n      httpGet:\n        path: /ready\n        port: 4191\n      initialDelaySeconds: 2\n      timeoutSeconds: 1\n    securityContext:\n      allowPrivilegeEscalation: false\n      readOnlyRootFilesystem: true\n      runAsNonRoot: true\n      runAsUser: 2102\n      seccompProfile:\n        type: RuntimeDefault\n    terminationMessagePolicy: FallbackToLogsOnError\n    volumeMounts:\n    - mountPath: /var/run/linkerd/identity/end-entity\n      name: linkerd-identity-end-entity\n    - mountPath: /var/run/secrets/tokens\n      name: linkerd-identity-token\n  - command:\n    - emojivoto-vote-bot\n    env:\n    - name: WEB_HOST\n      value: web-svc.emojivoto:80\n    image: buoyantio/emojivoto-web:v10\n    name: vote-bot\n  initContainers:\n  - args:\n    - --firewall-bin-path\n    - iptables-nft\n    - --firewall-save-bin-path\n    - iptables-nft-save\n    - --ipv6=false\n    - --incoming-proxy-port\n    - \"4143\"\n    - --outgoing-proxy-port\n    - \"4140\"\n    - --proxy-uid\n    - \"2102\"\n    - --inbound-ports-to-ignore\n    - 4190,4191,22,8100-8102\n    - --outbound-ports-to-ignore\n    - \"5432\"\n    command:\n    - /usr/lib/linkerd/linkerd2-proxy-init\n    image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n    imagePullPolicy: IfNotPresent\n    name: linkerd-init\n    securityContext:\n      allowPrivilegeEscalation: false\n      capabilities:\n        add:\n        - NET_ADMIN\n        - NET_RAW\n      privileged: false\n      readOnlyRootFilesystem: true\n      runAsGroup: 65534\n      runAsNonRoot: true\n      runAsUser: 65534\n      seccompProfile:\n        type: RuntimeDefault\n    terminationMessagePolicy: FallbackToLogsOnError\n    volumeMounts:\n    - mountPath: /run\n      name: linkerd-proxy-init-xtables-lock\n  volumes:\n  - emptyDir: {}\n    name: linkerd-proxy-init-xtables-lock\n  - emptyDir:\n      medium: Memory\n    name: linkerd-identity-end-entity\n  - name: linkerd-identity-token\n    projected:\n      sources:\n      - serviceAccountToken:\n          audience: identity.l5d.io\n          expirationSeconds: 86400\n          path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod_uninject.report",
    "content": "\npod \"vote-bot\" uninjected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod_with_requests.golden.yml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  annotations:\n    config.linkerd.io/proxy-cpu-limit: 160m\n    config.linkerd.io/proxy-cpu-request: 110m\n    config.linkerd.io/proxy-memory-limit: 150Mi\n    config.linkerd.io/proxy-memory-request: 100Mi\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/proxy-version: test-inject-proxy-version\n    linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n  labels:\n    app: vote-bot\n    linkerd.io/control-plane-ns: linkerd\n    linkerd.io/workload-ns: emojivoto\n  name: vote-bot\n  namespace: emojivoto\nspec:\n  containers:\n  - env:\n    - name: _pod_name\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.name\n    - name: _pod_ns\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.namespace\n    - name: _pod_uid\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.uid\n    - name: _pod_ip\n      valueFrom:\n        fieldRef:\n          fieldPath: status.podIP\n    - name: _pod_nodeName\n      valueFrom:\n        fieldRef:\n          fieldPath: spec.nodeName\n    - name: _pod_containerName\n      value: linkerd-proxy\n    - name: LINKERD2_PROXY_CORES\n      value: \"1\"\n    - name: LINKERD2_PROXY_CORES_MIN\n      value: \"1\"\n    - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n      value: \"false\"\n    - name: LINKERD2_PROXY_LOG\n      value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n    - name: LINKERD2_PROXY_LOG_FORMAT\n      value: plain\n    - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n      value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n    - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n      value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n      value: linkerd-policy.linkerd.svc.cluster.local.:8090\n    - name: LINKERD2_PROXY_POLICY_WORKLOAD\n      value: |\n        {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n    - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n      value: all-unauthenticated\n    - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n      value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n      value: 3s\n    - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n      value: 5m\n    - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n      value: 1h\n    - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n      value: 100ms\n    - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n      value: 1000ms\n    - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n      value: 5s\n    - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n      value: 90s\n    - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n      value: 0.0.0.0:4190\n    - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n      value: 0.0.0.0:4191\n    - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n      value: 127.0.0.1:4140\n    - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n      value: 127.0.0.1:4140\n    - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n      value: 0.0.0.0:4143\n    - name: LINKERD2_PROXY_INBOUND_IPS\n      valueFrom:\n        fieldRef:\n          fieldPath: status.podIPs\n    - name: LINKERD2_PROXY_INBOUND_PORTS\n    - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n      value: svc.cluster.local.\n    - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n      value: 10000ms\n    - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n      value: 10000ms\n    - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n      value: 30s\n    - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n      value: 30s\n    - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n      value: \"false\"\n    - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n      value: 10s\n    - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n      value: 3s\n    - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n      value: 10s\n    - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n      value: 3s\n    - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n      value: 25,587,3306,4444,5432,6379,9300,11211\n    - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n      value: |\n        {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n    - name: _pod_sa\n      valueFrom:\n        fieldRef:\n          fieldPath: spec.serviceAccountName\n    - name: _l5d_ns\n      value: linkerd\n    - name: _l5d_trustdomain\n      value: cluster.local\n    - name: LINKERD2_PROXY_IDENTITY_DIR\n      value: /var/run/linkerd/identity/end-entity\n    - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n      value: |\n        -----BEGIN CERTIFICATE-----\n        MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n        JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n        MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n        ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n        l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n        uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n        /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n        aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n        IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n        vgUC0d2/9FMueIVMb+46WTCOjsqr\n        -----END CERTIFICATE-----\n    - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n      value: /var/run/secrets/tokens/linkerd-identity-token\n    - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n      value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n    - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n      value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n    - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n      value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n    - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n      value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n    - name: LINKERD2_PROXY_POLICY_SVC_NAME\n      value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n    image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n    imagePullPolicy: IfNotPresent\n    lifecycle:\n      postStart:\n        exec:\n          command:\n          - /usr/lib/linkerd/linkerd-await\n          - --timeout=2m\n          - --port=4191\n    livenessProbe:\n      httpGet:\n        path: /live\n        port: 4191\n      initialDelaySeconds: 10\n      timeoutSeconds: 1\n    name: linkerd-proxy\n    ports:\n    - containerPort: 4143\n      name: linkerd-proxy\n    - containerPort: 4191\n      name: linkerd-admin\n    readinessProbe:\n      httpGet:\n        path: /ready\n        port: 4191\n      initialDelaySeconds: 2\n      timeoutSeconds: 1\n    resources:\n      limits:\n        cpu: 160m\n        memory: 150Mi\n      requests:\n        cpu: 110m\n        memory: 100Mi\n    securityContext:\n      allowPrivilegeEscalation: false\n      readOnlyRootFilesystem: true\n      runAsNonRoot: true\n      runAsUser: 2102\n      seccompProfile:\n        type: RuntimeDefault\n    terminationMessagePolicy: FallbackToLogsOnError\n    volumeMounts:\n    - mountPath: /var/run/linkerd/identity/end-entity\n      name: linkerd-identity-end-entity\n    - mountPath: /var/run/secrets/tokens\n      name: linkerd-identity-token\n  - command:\n    - emojivoto-vote-bot\n    env:\n    - name: WEB_HOST\n      value: web-svc.emojivoto:80\n    image: buoyantio/emojivoto-web:v10\n    name: vote-bot\n  initContainers:\n  - args:\n    - --firewall-bin-path\n    - iptables-nft\n    - --firewall-save-bin-path\n    - iptables-nft-save\n    - --ipv6=false\n    - --incoming-proxy-port\n    - \"4143\"\n    - --outgoing-proxy-port\n    - \"4140\"\n    - --proxy-uid\n    - \"2102\"\n    - --inbound-ports-to-ignore\n    - 4190,4191,4567,4568\n    - --outbound-ports-to-ignore\n    - 4567,4568\n    command:\n    - /usr/lib/linkerd/linkerd2-proxy-init\n    image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n    imagePullPolicy: IfNotPresent\n    name: linkerd-init\n    resources:\n      limits:\n        cpu: 160m\n        memory: 150Mi\n      requests:\n        cpu: 110m\n        memory: 100Mi\n    securityContext:\n      allowPrivilegeEscalation: false\n      capabilities:\n        add:\n        - NET_ADMIN\n        - NET_RAW\n      privileged: false\n      readOnlyRootFilesystem: true\n      runAsGroup: 65534\n      runAsNonRoot: true\n      runAsUser: 65534\n      seccompProfile:\n        type: RuntimeDefault\n    terminationMessagePolicy: FallbackToLogsOnError\n    volumeMounts:\n    - mountPath: /run\n      name: linkerd-proxy-init-xtables-lock\n  volumes:\n  - emptyDir: {}\n    name: linkerd-proxy-init-xtables-lock\n  - emptyDir:\n      medium: Memory\n    name: linkerd-identity-end-entity\n  - name: linkerd-identity-token\n    projected:\n      sources:\n      - serviceAccountToken:\n          audience: identity.l5d.io\n          expirationSeconds: 86400\n          path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod_with_requests.input.yml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    app: vote-bot\n  name: vote-bot\n  namespace: emojivoto\nspec:\n  containers:\n  - command:\n    - emojivoto-vote-bot\n    env:\n    - name: WEB_HOST\n      value: web-svc.emojivoto:80\n    image: buoyantio/emojivoto-web:v10\n    name: vote-bot\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod_with_requests.report",
    "content": "\npod \"vote-bot\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod_with_requests.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\npod \"vote-bot\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_pod_with_requests_uninject.report",
    "content": "\npod \"vote-bot\" uninjected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_statefulset.golden.yml",
    "content": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  serviceName: \"\"\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: web-svc\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-statefulset: web\n        linkerd.io/workload-ns: emojivoto\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"80\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_statefulset.input.yml",
    "content": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: web\n  namespace: emojivoto\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  serviceName: \"\"\n  template:\n    metadata:\n      labels:\n        app: web-svc\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"80\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.emojivoto:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.emojivoto:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 80\n          name: http\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_statefulset.report",
    "content": "\nstatefulset \"web\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_statefulset.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\nstatefulset \"web\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_emojivoto_statefulset_uninject.report",
    "content": "\nstatefulset \"web\" uninjected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_gettest_deployment.bad.golden",
    "content": "Error transforming resources:\nerror converting YAML to JSON: yaml: line 14: did not find expected key"
  },
  {
    "path": "cli/cmd/testdata/inject_gettest_deployment.bad.golden.verbose",
    "content": "Error transforming resources:\nerror converting YAML to JSON: yaml: line 14: did not find expected key"
  },
  {
    "path": "cli/cmd/testdata/inject_gettest_deployment.bad.input.yml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nspec:\n  template:\n    metadata:\n          labels:\n            app: get-test\n    spec:\n      containers:\n      - name: http-to-grpc-two-replicas-c1\n        image: buoyantio/bb:v0.0.6\n        args: [\"terminus\", \"--grpc-server-port\", \"9090\", \"--response-text\", \"c1\"]\n        ports:\n        - containerPort: 9090\n      - name: http-to-grpc-two-replicas-c2\n        image: buoyantio/bb:v0.0.6\n        args: [\"terminus\", \"--grpc-server-port\", \"8080\", \"--response-text\", \"c2\"]\n        ports:\n        - containerPort: 9090\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: get-test-deploy-injected-2\n  namespace: get-test\nspec:\n  replicas: 1\n  selector:\n      matchLabels:\n        app: get-test\n  template:\n      metadata:\n            labels:\n              app: get-test\n    spec:\n      containers:\n      - name: http-to-grpc-one-replica-c1\n        image: buoyantio/bb:v0.0.6\n        args: [\"terminus\", \"--grpc-server-port\", \"9090\", \"--response-text\", \"c1\"]\n        ports:\n        - containerPort: 9090\n      - name: http-to-grpc-one-replica-c2\n        image: buoyantio/bb:v0.0.6\n        args: [\"terminus\", \"--grpc-server-port\", \"8080\", \"--response-text\", \"c2\"]\n        ports:\n        - containerPort: 9090\n"
  },
  {
    "path": "cli/cmd/testdata/inject_gettest_deployment.good.golden.stderr",
    "content": "\ndeployment \"get-test-deploy-injected-1\" injected\ndeployment \"get-test-deploy-injected-2\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_gettest_deployment.good.golden.stderr.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"get-test-deploy-injected-1\" injected\ndeployment \"get-test-deploy-injected-2\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_gettest_deployment.good.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: get-test-deploy-injected-1\nspec:\n  selector: null\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: testinjectversion\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: get-test\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: get-test-deploy-injected-1\n        linkerd.io/workload-ns: \"\"\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"9090\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:testinjectversion\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - terminus\n        - --grpc-server-port\n        - \"9090\"\n        - --response-text\n        - c1\n        image: buoyantio/bb:v0.0.6\n        name: http-to-grpc-two-replicas-c1\n        ports:\n        - containerPort: 9090\n      - args:\n        - terminus\n        - --grpc-server-port\n        - \"8080\"\n        - --response-text\n        - c2\n        image: buoyantio/bb:v0.0.6\n        name: http-to-grpc-two-replicas-c2\n        ports:\n        - containerPort: 9090\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:testinjectversion\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: get-test-deploy-injected-2\nspec:\n  selector: null\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: testinjectversion\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        app: get-test\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: get-test-deploy-injected-2\n        linkerd.io/workload-ns: \"\"\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"9090\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:testinjectversion\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - terminus\n        - --grpc-server-port\n        - \"9090\"\n        - --response-text\n        - c1\n        image: buoyantio/bb:v0.0.6\n        name: http-to-grpc-one-replica-c1\n        ports:\n        - containerPort: 9090\n      - args:\n        - terminus\n        - --grpc-server-port\n        - \"8080\"\n        - --response-text\n        - c2\n        image: buoyantio/bb:v0.0.6\n        name: http-to-grpc-one-replica-c2\n        ports:\n        - containerPort: 9090\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:testinjectversion\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      volumes:\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_gettest_deployment.good.input.yml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: get-test-deploy-injected-1\nspec:\n  template:\n    metadata:\n          labels:\n            app: get-test\n    spec:\n      containers:\n      - name: http-to-grpc-two-replicas-c1\n        image: buoyantio/bb:v0.0.6\n        args: [\"terminus\", \"--grpc-server-port\", \"9090\", \"--response-text\", \"c1\"]\n        ports:\n        - containerPort: 9090\n      - name: http-to-grpc-two-replicas-c2\n        image: buoyantio/bb:v0.0.6\n        args: [\"terminus\", \"--grpc-server-port\", \"8080\", \"--response-text\", \"c2\"]\n        ports:\n        - containerPort: 9090\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: get-test-deploy-injected-2\nspec:\n  template:\n    metadata:\n          labels:\n            app: get-test\n    spec:\n      containers:\n      - name: http-to-grpc-one-replica-c1\n        image: buoyantio/bb:v0.0.6\n        args: [\"terminus\", \"--grpc-server-port\", \"9090\", \"--response-text\", \"c1\"]\n        ports:\n        - containerPort: 9090\n      - name: http-to-grpc-one-replica-c2\n        image: buoyantio/bb:v0.0.6\n        args: [\"terminus\", \"--grpc-server-port\", \"8080\", \"--response-text\", \"c2\"]\n        ports:\n        - containerPort: 9090\n"
  },
  {
    "path": "cli/cmd/testdata/inject_tap_deployment.bad.golden",
    "content": "Error transforming resources:\n--manual must be set when injecting control plane components"
  },
  {
    "path": "cli/cmd/testdata/inject_tap_deployment.bad.golden.verbose",
    "content": "Error transforming resources:\n--manual must be set when injecting control plane components"
  },
  {
    "path": "cli/cmd/testdata/inject_tap_deployment.input.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n  creationTimestamp: null\n  generation: 1\n  labels:\n    linkerd.io/control-plane-component: tap\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-tap\n  namespace: linkerd\n  resourceVersion: \"2387\"\n  selfLink: /apis/extensions/v1beta1/namespaces/linkerd/deployments/linkerd-tap\n  uid: edb24475-9371-491a-b536-b084a91d9700\nspec:\n  progressDeadlineSeconds: 600\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: tap\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-tap\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n    type: RollingUpdate\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli git-a94122bf\n        linkerd.io/proxy-version: git-a94122bf\n      creationTimestamp: null\n      labels:\n        linkerd.io/control-plane-component: tap\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-tap\n    spec:\n      containers:\n      - args:\n        - tap\n        - -controller-namespace=linkerd\n        - -log-level=info\n        image: cr.l5d.io/linkerd/controller:git-a94122bf\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          failureThreshold: 3\n          httpGet:\n            path: /ping\n            port: 9998\n            scheme: HTTP\n          initialDelaySeconds: 10\n          periodSeconds: 10\n          successThreshold: 1\n          timeoutSeconds: 1\n        name: tap\n        ports:\n        - containerPort: 8088\n          name: grpc\n          protocol: TCP\n        - containerPort: 8089\n          name: apiserver\n          protocol: TCP\n        - containerPort: 9998\n          name: tap-admin\n          protocol: TCP\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9998\n            scheme: HTTP\n          periodSeconds: 10\n          successThreshold: 1\n          timeoutSeconds: 1\n        resources: {}\n        securityContext:\n          runAsUser: 2103\n          runAsGroup: 2103\n        terminationMessagePath: /dev/termination-log\n        terminationMessagePolicy: File\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/linkerd/config\n          name: config\n      - env:\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-destination.linkerd.svc.cluster.local:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"[::]:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"[::]:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140,[::1]:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"[::]:4143\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              apiVersion: v1\n              fieldPath: metadata.namespace\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: ns:$(_pod_ns)\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBgjCCASmgAwIBAgIBATAKBggqhkjOPQQDAjApMScwJQYDVQQDEx5pZGVudGl0\n            eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMTkxMDIyMTEyMzA1WhcNMjAxMDIx\n            MTEyMzI1WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9j\n            YWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQy6ZAtJL51C4jsnaS4PL+zJ4+K\n            9cVJXGFxfRdY/yleFsSNT7/JTgUvj9sp+k2rBx69PHN63lv/n6Aq+e1DFfRVo0Iw\n            QDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC\n            MA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgUd/XaAE4B5v5l4jK\n            xHmCQR+nhuq8rJ0Y0qKZT4eoCC4CIHer48hsc1BJWeKNfsx/71nvFA/9ZCuwk25K\n            puTT5Vel\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/kubernetes.io/serviceaccount/token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity.linkerd.svc.cluster.local:8080\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              apiVersion: v1\n              fieldPath: spec.serviceAccountName\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_TAP_SVC_NAME\n          value: linkerd-tap.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:git-a94122bf\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          failureThreshold: 3\n          httpGet:\n            path: /metrics\n            port: 4191\n            scheme: HTTP\n          initialDelaySeconds: 10\n          periodSeconds: 10\n          successThreshold: 1\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n          protocol: TCP\n        - containerPort: 4191\n          name: linkerd-admin\n          protocol: TCP\n        readinessProbe:\n          failureThreshold: 3\n          httpGet:\n            path: /ready\n            port: 4191\n            scheme: HTTP\n          initialDelaySeconds: 2\n          periodSeconds: 10\n          successThreshold: 1\n          timeoutSeconds: 1\n        resources: {}\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsUser: 2102\n          runAsGroup: 2102\n        terminationMessagePath: /dev/termination-log\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n      dnsPolicy: ClusterFirst\n      initContainers:\n      - args:\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --proxy-gid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568,443\n        image: cr.l5d.io/linkerd/proxy-init:v2.0.0\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            cpu: 100m\n            memory: 50Mi\n          requests:\n            cpu: 10m\n            memory: 10Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          readOnlyRootFilesystem: true\n        terminationMessagePath: /dev/termination-log\n        terminationMessagePolicy: FallbackToLogsOnError\n      restartPolicy: Always\n      schedulerName: default-scheduler\n      securityContext: {}\n      serviceAccount: linkerd-tap\n      serviceAccountName: linkerd-tap\n      terminationGracePeriodSeconds: 30\n      volumes:\n      - configMap:\n          defaultMode: 420\n          name: linkerd-config\n        name: config\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: tls\n        secret:\n          defaultMode: 420\n          secretName: linkerd-tap-k8s-tls\nstatus: {}\n"
  },
  {
    "path": "cli/cmd/testdata/inject_tap_deployment_debug.golden.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  generation: 1\n  labels:\n    linkerd.io/control-plane-component: tap\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-tap\n  namespace: linkerd\n  resourceVersion: \"2387\"\n  selfLink: /apis/extensions/v1beta1/namespaces/linkerd/deployments/linkerd-tap\n  uid: edb24475-9371-491a-b536-b084a91d9700\nspec:\n  progressDeadlineSeconds: 600\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: tap\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-tap\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n    type: RollingUpdate\n  template:\n    metadata:\n      annotations:\n        config.linkerd.io/enable-debug-sidecar: \"true\"\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: test-inject-proxy-version\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n      labels:\n        linkerd.io/control-plane-component: tap\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-tap\n        linkerd.io/workload-ns: linkerd\n    spec:\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: plain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: 5m\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: 1h\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: 100ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: 1000ms\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 5s\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: 90s\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: 0.0.0.0:4190\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: 0.0.0.0:4191\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: 127.0.0.1:4140\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: 0.0.0.0:4143\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: 8088,8089,9998\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: 10s\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: 3s\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: 25,587,3306,4444,5432,6379,9300,11211\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          value: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n            JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n            MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n            ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n            l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n            uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n            /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n            aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n            IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n            vgUC0d2/9FMueIVMb+46WTCOjsqr\n            -----END CERTIFICATE-----\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        lifecycle:\n          postStart:\n            exec:\n              command:\n              - /usr/lib/linkerd/linkerd-await\n              - --timeout=2m\n              - --port=4191\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: linkerd-proxy\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - tap\n        - -controller-namespace=linkerd\n        - -log-level=info\n        image: cr.l5d.io/linkerd/controller:git-a94122bf\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          failureThreshold: 3\n          httpGet:\n            path: /ping\n            port: 9998\n            scheme: HTTP\n          initialDelaySeconds: 10\n          periodSeconds: 10\n          successThreshold: 1\n          timeoutSeconds: 1\n        name: tap\n        ports:\n        - containerPort: 8088\n          name: grpc\n          protocol: TCP\n        - containerPort: 8089\n          name: apiserver\n          protocol: TCP\n        - containerPort: 9998\n          name: tap-admin\n          protocol: TCP\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9998\n            scheme: HTTP\n          periodSeconds: 10\n          successThreshold: 1\n          timeoutSeconds: 1\n        securityContext:\n          runAsGroup: 2103\n          runAsUser: 2103\n        terminationMessagePath: /dev/termination-log\n        terminationMessagePolicy: File\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/linkerd/config\n          name: config\n      - image: cr.l5d.io/linkerd/debug:test-inject-debug-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          exec:\n            command:\n            - \"true\"\n        name: linkerd-debug\n        readinessProbe:\n          exec:\n            command:\n            - \"true\"\n        terminationMessagePolicy: FallbackToLogsOnError\n      dnsPolicy: ClusterFirst\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - iptables-nft\n        - --firewall-save-bin-path\n        - iptables-nft-save\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - 4190,4191,4567,4568\n        - --outbound-ports-to-ignore\n        - 4567,4568\n        command:\n        - /usr/lib/linkerd/linkerd2-proxy-init\n        image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      restartPolicy: Always\n      schedulerName: default-scheduler\n      securityContext: {}\n      serviceAccount: linkerd-tap\n      serviceAccountName: linkerd-tap\n      terminationGracePeriodSeconds: 30\n      volumes:\n      - configMap:\n          defaultMode: 420\n          name: linkerd-config\n        name: config\n      - name: tls\n        secret:\n          defaultMode: 420\n          secretName: linkerd-tap-k8s-tls\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              audience: identity.l5d.io\n              expirationSeconds: 86400\n              path: linkerd-identity-token\n---\n"
  },
  {
    "path": "cli/cmd/testdata/inject_tap_deployment_debug.report",
    "content": "\ndeployment \"linkerd-tap\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/inject_tap_deployment_debug.report.verbose",
    "content": "\n√ pods do not use host networking\n√ pods do not have a 3rd party proxy or initContainer already injected\n√ pods are not annotated to disable injection\n√ at least one resource can be injected or annotated\n√ pod specs do not include UDP ports\n√ pods do not have automountServiceAccountToken set to \"false\" or service account token projection is enabled\n\ndeployment \"linkerd-tap\" injected\n\n"
  },
  {
    "path": "cli/cmd/testdata/install-cni-plugin_default.golden",
    "content": "kind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    pod-security.kubernetes.io/enforce: privileged\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: linkerd-cni\n  namespace: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"nodes\", \"namespaces\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-cni\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-cni\n  namespace: linkerd-cni\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-cni-config\n  namespace: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\ndata:\n  dest_cni_net_dir: \"/etc/cni/net.d\"\n  dest_cni_bin_dir: \"/opt/cni/bin\"\n  # The CNI network configuration to install on each node. The special\n  # values in this config will be automatically populated.\n  # iptables-mode and ipv6 flags are only considered as of linkerd-cni v1.4.0\n  cni_network_config: |-\n    {\n      \"name\": \"linkerd-cni\",\n      \"type\": \"linkerd-cni\",\n      \"log_level\": \"info\",\n      \"kubernetes\": {\n          \"kubeconfig\": \"__KUBECONFIG_FILEPATH__\"\n      },\n      \"linkerd\": {\n        \"incoming-proxy-port\": 4143,\n        \"outgoing-proxy-port\": 4140,\n        \"proxy-uid\": 2102,\n        \"ports-to-redirect\": [],\n        \"inbound-ports-to-ignore\": [\"4191\",\"4190\"],\n        \"simulate\": false,\n        \"use-wait-flag\": false,\n        \"iptables-mode\": \"nft\",\n        \"ipv6\": false\n      }\n    }\n---\nkind: DaemonSet\napiVersion: apps/v1\nmetadata:\n  name: linkerd-cni\n  namespace: linkerd-cni\n  labels:\n    k8s-app: linkerd-cni\n    linkerd.io/cni-resource: \"true\"\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      k8s-app: linkerd-cni\n  updateStrategy:\n    rollingUpdate:\n      maxUnavailable: 1\n    type: RollingUpdate\n  template:\n    metadata:\n      labels:\n        k8s-app: linkerd-cni\n        linkerd.io/cni-resource: \"true\"\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: disabled\n    spec:\n      tolerations:\n        - operator: Exists\n      nodeSelector:\n        kubernetes.io/os: linux\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-cni\n      containers:\n      # This container installs the linkerd CNI binaries\n      # and CNI network config file on each node. The install\n      # script copies the files into place and then sleeps so\n      # that Kubernetes doesn't keep trying to restart it.\n      - name: install-cni\n        image: cr.l5d.io/linkerd/cni-plugin:v1.6.6\n        imagePullPolicy: \n        env:\n        - name: DEST_CNI_NET_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_net_dir\n        - name: DEST_CNI_BIN_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_bin_dir\n        - name: CNI_NETWORK_CONFIG\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: cni_network_config\n        - name: SLEEP\n          value: \"true\"\n        lifecycle:\n          # In some edge-cases this helps ensure that cleanup() is called in the container's script\n          # https://github.com/linkerd/linkerd2/issues/2355\n          preStop:\n            exec:\n              command:\n              - /bin/sh\n              - -c\n              - kill -15 1; sleep 15s\n        volumeMounts:\n        - mountPath: /host/opt/cni/bin\n          name: cni-bin-dir\n        - mountPath: /host/etc/cni/net.d\n          name: cni-net-dir\n        - mountPath: /tmp\n          name: linkerd-tmp-dir\n        securityContext:\n          readOnlyRootFilesystem: true\n          privileged: false\n        resources:\n          limits:\n            cpu: \"1m\"\n      volumes:\n      - name: cni-bin-dir\n        hostPath:\n          path: /opt/cni/bin\n      - name: cni-net-dir\n        hostPath:\n          path: /etc/cni/net.d\n      - name: linkerd-tmp-dir\n        emptyDir: {}\n---\n"
  },
  {
    "path": "cli/cmd/testdata/install-cni-plugin_fully_configured.golden",
    "content": "kind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    pod-security.kubernetes.io/enforce: privileged\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: linkerd-cni\n  namespace: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"nodes\", \"namespaces\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-cni\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-cni\n  namespace: linkerd-cni\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-cni-config\n  namespace: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\ndata:\n  dest_cni_net_dir: \"/etc/kubernetes/cni/net.d\"\n  dest_cni_bin_dir: \"/opt/my-cni/bin\"\n  # The CNI network configuration to install on each node. The special\n  # values in this config will be automatically populated.\n  # iptables-mode and ipv6 flags are only considered as of linkerd-cni v1.4.0\n  cni_network_config: |-\n    {\n      \"name\": \"linkerd-cni\",\n      \"type\": \"linkerd-cni\",\n      \"log_level\": \"debug\",\n      \"kubernetes\": {\n          \"kubeconfig\": \"__KUBECONFIG_FILEPATH__\"\n      },\n      \"linkerd\": {\n        \"incoming-proxy-port\": 5143,\n        \"outgoing-proxy-port\": 5140,\n        \"proxy-uid\": 12102,\n        \"proxy-gid\": 12102,\n        \"ports-to-redirect\": [],\n        \"inbound-ports-to-ignore\": [\"4191\",\"4190\"],\n        \"simulate\": false,\n        \"use-wait-flag\": false,\n        \"iptables-mode\": \"nft\",\n        \"ipv6\": false\n      }\n    }\n---\nkind: DaemonSet\napiVersion: apps/v1\nmetadata:\n  name: linkerd-cni\n  namespace: linkerd-cni\n  labels:\n    k8s-app: linkerd-cni\n    linkerd.io/cni-resource: \"true\"\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      k8s-app: linkerd-cni\n  updateStrategy:\n    rollingUpdate:\n      maxUnavailable: 1\n    type: RollingUpdate\n  template:\n    metadata:\n      labels:\n        k8s-app: linkerd-cni\n        linkerd.io/cni-resource: \"true\"\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: disabled\n    spec:\n      tolerations:\n        - operator: Exists\n      nodeSelector:\n        kubernetes.io/os: linux\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-cni\n      priorityClassName: system-node-critical\n      containers:\n      # This container installs the linkerd CNI binaries\n      # and CNI network config file on each node. The install\n      # script copies the files into place and then sleeps so\n      # that Kubernetes doesn't keep trying to restart it.\n      - name: install-cni\n        image: my-docker-registry.io/awesome/cni-plugin-test-image:v1.4.0\n        imagePullPolicy: \n        env:\n        - name: DEST_CNI_NET_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_net_dir\n        - name: DEST_CNI_BIN_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_bin_dir\n        - name: CNI_NETWORK_CONFIG\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: cni_network_config\n        - name: SLEEP\n          value: \"true\"\n        lifecycle:\n          # In some edge-cases this helps ensure that cleanup() is called in the container's script\n          # https://github.com/linkerd/linkerd2/issues/2355\n          preStop:\n            exec:\n              command:\n              - /bin/sh\n              - -c\n              - kill -15 1; sleep 15s\n        volumeMounts:\n        - mountPath: /host/opt/my-cni/bin\n          name: cni-bin-dir\n        - mountPath: /host/etc/kubernetes/cni/net.d\n          name: cni-net-dir\n        - mountPath: /tmp\n          name: linkerd-tmp-dir\n        securityContext:\n          readOnlyRootFilesystem: true\n          privileged: false\n        resources:\n      volumes:\n      - name: cni-bin-dir\n        hostPath:\n          path: /opt/my-cni/bin\n      - name: cni-net-dir\n        hostPath:\n          path: /etc/kubernetes/cni/net.d\n      - name: linkerd-tmp-dir\n        emptyDir: {}\n---\n"
  },
  {
    "path": "cli/cmd/testdata/install-cni-plugin_fully_configured_equal_dsts.golden",
    "content": "kind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    pod-security.kubernetes.io/enforce: privileged\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: linkerd-cni\n  namespace: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"nodes\", \"namespaces\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-cni\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-cni\n  namespace: linkerd-cni\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-cni-config\n  namespace: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\ndata:\n  dest_cni_net_dir: \"/etc/kubernetes/cni/net.d\"\n  dest_cni_bin_dir: \"/etc/kubernetes/cni/net.d\"\n  # The CNI network configuration to install on each node. The special\n  # values in this config will be automatically populated.\n  # iptables-mode and ipv6 flags are only considered as of linkerd-cni v1.4.0\n  cni_network_config: |-\n    {\n      \"name\": \"linkerd-cni\",\n      \"type\": \"linkerd-cni\",\n      \"log_level\": \"debug\",\n      \"kubernetes\": {\n          \"kubeconfig\": \"__KUBECONFIG_FILEPATH__\"\n      },\n      \"linkerd\": {\n        \"incoming-proxy-port\": 5143,\n        \"outgoing-proxy-port\": 5140,\n        \"proxy-uid\": 12102,\n        \"proxy-gid\": 12102,\n        \"ports-to-redirect\": [],\n        \"inbound-ports-to-ignore\": [\"4191\",\"4190\"],\n        \"simulate\": false,\n        \"use-wait-flag\": false,\n        \"iptables-mode\": \"nft\",\n        \"ipv6\": false\n      }\n    }\n---\nkind: DaemonSet\napiVersion: apps/v1\nmetadata:\n  name: linkerd-cni\n  namespace: linkerd-cni\n  labels:\n    k8s-app: linkerd-cni\n    linkerd.io/cni-resource: \"true\"\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      k8s-app: linkerd-cni\n  updateStrategy:\n    rollingUpdate:\n      maxUnavailable: 1\n    type: RollingUpdate\n  template:\n    metadata:\n      labels:\n        k8s-app: linkerd-cni\n        linkerd.io/cni-resource: \"true\"\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: disabled\n    spec:\n      tolerations:\n        - operator: Exists\n      nodeSelector:\n        kubernetes.io/os: linux\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-cni\n      priorityClassName: system-node-critical\n      containers:\n      # This container installs the linkerd CNI binaries\n      # and CNI network config file on each node. The install\n      # script copies the files into place and then sleeps so\n      # that Kubernetes doesn't keep trying to restart it.\n      - name: install-cni\n        image: my-docker-registry.io/awesome/cni-plugin-test-image:v1.4.0\n        imagePullPolicy: \n        env:\n        - name: DEST_CNI_NET_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_net_dir\n        - name: DEST_CNI_BIN_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_bin_dir\n        - name: CNI_NETWORK_CONFIG\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: cni_network_config\n        - name: SLEEP\n          value: \"true\"\n        lifecycle:\n          # In some edge-cases this helps ensure that cleanup() is called in the container's script\n          # https://github.com/linkerd/linkerd2/issues/2355\n          preStop:\n            exec:\n              command:\n              - /bin/sh\n              - -c\n              - kill -15 1; sleep 15s\n        volumeMounts:\n        - mountPath: /host/etc/kubernetes/cni/net.d\n          name: cni-net-dir\n        - mountPath: /tmp\n          name: linkerd-tmp-dir\n        securityContext:\n          readOnlyRootFilesystem: true\n          privileged: false\n        resources:\n      volumes:\n      - name: cni-net-dir\n        hostPath:\n          path: /etc/kubernetes/cni/net.d\n      - name: linkerd-tmp-dir\n        emptyDir: {}\n---\n"
  },
  {
    "path": "cli/cmd/testdata/install-cni-plugin_fully_configured_no_namespace.golden",
    "content": "kind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    pod-security.kubernetes.io/enforce: privileged\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: linkerd-cni\n  namespace: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"nodes\", \"namespaces\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-cni\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-cni\n  namespace: linkerd-cni\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-cni-config\n  namespace: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\ndata:\n  dest_cni_net_dir: \"/etc/kubernetes/cni/net.d\"\n  dest_cni_bin_dir: \"/opt/my-cni/bin\"\n  # The CNI network configuration to install on each node. The special\n  # values in this config will be automatically populated.\n  # iptables-mode and ipv6 flags are only considered as of linkerd-cni v1.4.0\n  cni_network_config: |-\n    {\n      \"name\": \"linkerd-cni\",\n      \"type\": \"linkerd-cni\",\n      \"log_level\": \"debug\",\n      \"kubernetes\": {\n          \"kubeconfig\": \"__KUBECONFIG_FILEPATH__\"\n      },\n      \"linkerd\": {\n        \"incoming-proxy-port\": 5143,\n        \"outgoing-proxy-port\": 5140,\n        \"proxy-uid\": 12102,\n        \"proxy-gid\": 12102,\n        \"ports-to-redirect\": [],\n        \"inbound-ports-to-ignore\": [\"4191\",\"4190\"],\n        \"simulate\": false,\n        \"use-wait-flag\": false,\n        \"iptables-mode\": \"nft\",\n        \"ipv6\": false\n      }\n    }\n---\nkind: DaemonSet\napiVersion: apps/v1\nmetadata:\n  name: linkerd-cni\n  namespace: linkerd-cni\n  labels:\n    k8s-app: linkerd-cni\n    linkerd.io/cni-resource: \"true\"\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      k8s-app: linkerd-cni\n  updateStrategy:\n    rollingUpdate:\n      maxUnavailable: 1\n    type: RollingUpdate\n  template:\n    metadata:\n      labels:\n        k8s-app: linkerd-cni\n        linkerd.io/cni-resource: \"true\"\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: disabled\n    spec:\n      tolerations:\n        - operator: Exists\n      nodeSelector:\n        kubernetes.io/os: linux\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-cni\n      priorityClassName: system-node-critical\n      containers:\n      # This container installs the linkerd CNI binaries\n      # and CNI network config file on each node. The install\n      # script copies the files into place and then sleeps so\n      # that Kubernetes doesn't keep trying to restart it.\n      - name: install-cni\n        image: my-docker-registry.io/awesome/cni-plugin-test-image:v1.4.0\n        imagePullPolicy: \n        env:\n        - name: DEST_CNI_NET_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_net_dir\n        - name: DEST_CNI_BIN_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_bin_dir\n        - name: CNI_NETWORK_CONFIG\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: cni_network_config\n        - name: SLEEP\n          value: \"true\"\n        lifecycle:\n          # In some edge-cases this helps ensure that cleanup() is called in the container's script\n          # https://github.com/linkerd/linkerd2/issues/2355\n          preStop:\n            exec:\n              command:\n              - /bin/sh\n              - -c\n              - kill -15 1; sleep 15s\n        volumeMounts:\n        - mountPath: /host/opt/my-cni/bin\n          name: cni-bin-dir\n        - mountPath: /host/etc/kubernetes/cni/net.d\n          name: cni-net-dir\n        - mountPath: /tmp\n          name: linkerd-tmp-dir\n        securityContext:\n          readOnlyRootFilesystem: true\n          privileged: false\n        resources:\n      volumes:\n      - name: cni-bin-dir\n        hostPath:\n          path: /opt/my-cni/bin\n      - name: cni-net-dir\n        hostPath:\n          path: /etc/kubernetes/cni/net.d\n      - name: linkerd-tmp-dir\n        emptyDir: {}\n---\n"
  },
  {
    "path": "cli/cmd/testdata/install-cni-plugin_skip_ports.golden",
    "content": "kind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    pod-security.kubernetes.io/enforce: privileged\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: linkerd-cni\n  namespace: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"nodes\", \"namespaces\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-cni\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-cni\n  namespace: linkerd-cni\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-cni-config\n  namespace: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\ndata:\n  dest_cni_net_dir: \"/etc/cni/net.d\"\n  dest_cni_bin_dir: \"/opt/cni/bin\"\n  # The CNI network configuration to install on each node. The special\n  # values in this config will be automatically populated.\n  # iptables-mode and ipv6 flags are only considered as of linkerd-cni v1.4.0\n  cni_network_config: |-\n    {\n      \"name\": \"linkerd-cni\",\n      \"type\": \"linkerd-cni\",\n      \"log_level\": \"info\",\n      \"kubernetes\": {\n          \"kubeconfig\": \"__KUBECONFIG_FILEPATH__\"\n      },\n      \"linkerd\": {\n        \"incoming-proxy-port\": 4143,\n        \"outgoing-proxy-port\": 4140,\n        \"proxy-uid\": 2102,\n        \"ports-to-redirect\": [],\n        \"inbound-ports-to-ignore\": [\"4191\",\"4190\",\"80\",\"8080\"],\n        \"outbound-ports-to-ignore\": [\"443\",\"1000\"],\n        \"simulate\": false,\n        \"use-wait-flag\": false,\n        \"iptables-mode\": \"nft\",\n        \"ipv6\": false\n      }\n    }\n---\nkind: DaemonSet\napiVersion: apps/v1\nmetadata:\n  name: linkerd-cni\n  namespace: linkerd-cni\n  labels:\n    k8s-app: linkerd-cni\n    linkerd.io/cni-resource: \"true\"\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      k8s-app: linkerd-cni\n  updateStrategy:\n    rollingUpdate:\n      maxUnavailable: 1\n    type: RollingUpdate\n  template:\n    metadata:\n      labels:\n        k8s-app: linkerd-cni\n        linkerd.io/cni-resource: \"true\"\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: disabled\n    spec:\n      tolerations:\n        - operator: Exists\n      nodeSelector:\n        kubernetes.io/os: linux\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-cni\n      containers:\n      # This container installs the linkerd CNI binaries\n      # and CNI network config file on each node. The install\n      # script copies the files into place and then sleeps so\n      # that Kubernetes doesn't keep trying to restart it.\n      - name: install-cni\n        image: cr.l5d.io/linkerd/cni-plugin:v1.6.6\n        imagePullPolicy: \n        env:\n        - name: DEST_CNI_NET_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_net_dir\n        - name: DEST_CNI_BIN_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_bin_dir\n        - name: CNI_NETWORK_CONFIG\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: cni_network_config\n        - name: SLEEP\n          value: \"true\"\n        lifecycle:\n          # In some edge-cases this helps ensure that cleanup() is called in the container's script\n          # https://github.com/linkerd/linkerd2/issues/2355\n          preStop:\n            exec:\n              command:\n              - /bin/sh\n              - -c\n              - kill -15 1; sleep 15s\n        volumeMounts:\n        - mountPath: /host/opt/cni/bin\n          name: cni-bin-dir\n        - mountPath: /host/etc/cni/net.d\n          name: cni-net-dir\n        - mountPath: /tmp\n          name: linkerd-tmp-dir\n        securityContext:\n          readOnlyRootFilesystem: true\n          privileged: false\n        resources:\n      volumes:\n      - name: cni-bin-dir\n        hostPath:\n          path: /opt/cni/bin\n      - name: cni-net-dir\n        hostPath:\n          path: /etc/cni/net.d\n      - name: linkerd-tmp-dir\n        emptyDir: {}\n---\n"
  },
  {
    "path": "cli/cmd/testdata/install_cni_helm_default_output.golden",
    "content": "---\n# Source: linkerd2-cni/templates/cni-plugin.yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: linkerd-cni\n  namespace: linkerd-test\n  labels:\n    linkerd.io/cni-resource: \"true\"\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"nodes\", \"namespaces\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-cni\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-cni\n  namespace: linkerd-test\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-cni-config\n  namespace: linkerd-test\n  labels:\n    linkerd.io/cni-resource: \"true\"\ndata:\n  dest_cni_net_dir: \"/etc/cni/net.d\"\n  dest_cni_bin_dir: \"/opt/cni/bin\"\n  # The CNI network configuration to install on each node. The special\n  # values in this config will be automatically populated.\n  # iptables-mode and ipv6 flags are only considered as of linkerd-cni v1.4.0\n  cni_network_config: |-\n    {\n      \"name\": \"linkerd-cni\",\n      \"type\": \"linkerd-cni\",\n      \"log_level\": \"info\",\n      \"kubernetes\": {\n          \"kubeconfig\": \"__KUBECONFIG_FILEPATH__\"\n      },\n      \"linkerd\": {\n        \"incoming-proxy-port\": 4143,\n        \"outgoing-proxy-port\": 4140,\n        \"proxy-uid\": 2102,\n        \"ports-to-redirect\": [],\n        \"inbound-ports-to-ignore\": [\"4191\",\"4190\"],\n        \"simulate\": false,\n        \"use-wait-flag\": false,\n        \"iptables-mode\": \"nft\",\n        \"ipv6\": false\n      }\n    }\n---\nkind: DaemonSet\napiVersion: apps/v1\nmetadata:\n  name: linkerd-cni\n  namespace: linkerd-test\n  labels:\n    k8s-app: linkerd-cni\n    linkerd.io/cni-resource: \"true\"\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      k8s-app: linkerd-cni\n  updateStrategy:\n    rollingUpdate:\n      maxUnavailable: 1\n    type: RollingUpdate\n  template:\n    metadata:\n      labels:\n        k8s-app: linkerd-cni\n        linkerd.io/cni-resource: \"true\"\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: disabled\n    spec:\n      tolerations:\n        - operator: Exists\n      nodeSelector:\n        kubernetes.io/os: linux\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-cni\n      containers:\n      # This container installs the linkerd CNI binaries\n      # and CNI network config file on each node. The install\n      # script copies the files into place and then sleeps so\n      # that Kubernetes doesn't keep trying to restart it.\n      - name: install-cni\n        image: cr.l5d.io/linkerd/cni-plugin:v1.6.6\n        imagePullPolicy: IfNotPresent\n        env:\n        - name: DEST_CNI_NET_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_net_dir\n        - name: DEST_CNI_BIN_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_bin_dir\n        - name: CNI_NETWORK_CONFIG\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: cni_network_config\n        - name: SLEEP\n          value: \"true\"\n        lifecycle:\n          # In some edge-cases this helps ensure that cleanup() is called in the container's script\n          # https://github.com/linkerd/linkerd2/issues/2355\n          preStop:\n            exec:\n              command:\n              - /bin/sh\n              - -c\n              - kill -15 1; sleep 15s\n        volumeMounts:\n        - mountPath: /host/opt/cni/bin\n          name: cni-bin-dir\n        - mountPath: /host/etc/cni/net.d\n          name: cni-net-dir\n        - mountPath: /tmp\n          name: linkerd-tmp-dir\n        securityContext:\n          readOnlyRootFilesystem: true\n          privileged: false\n        resources:\n      volumes:\n      - name: cni-bin-dir\n        hostPath:\n          path: /opt/cni/bin\n      - name: cni-net-dir\n        hostPath:\n          path: /etc/cni/net.d\n      - name: linkerd-tmp-dir\n        emptyDir: {}\n"
  },
  {
    "path": "cli/cmd/testdata/install_cni_helm_override_output.golden",
    "content": "---\n# Source: linkerd2-cni/templates/cni-plugin.yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: linkerd-cni\n  namespace: linkerd-test\n  labels:\n    linkerd.io/cni-resource: \"true\"\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"nodes\", \"namespaces\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-cni\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-cni\n  namespace: linkerd-test\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-cni-config\n  namespace: linkerd-test\n  labels:\n    linkerd.io/cni-resource: \"true\"\ndata:\n  dest_cni_net_dir: \"/etc/cni/net.d-test\"\n  dest_cni_bin_dir: \"/opt/cni/bin-test\"\n  # The CNI network configuration to install on each node. The special\n  # values in this config will be automatically populated.\n  # iptables-mode and ipv6 flags are only considered as of linkerd-cni v1.4.0\n  cni_network_config: |-\n    {\n      \"name\": \"linkerd-cni\",\n      \"type\": \"linkerd-cni\",\n      \"log_level\": \"debug\",\n      \"kubernetes\": {\n          \"kubeconfig\": \"__KUBECONFIG_FILEPATH__\"\n      },\n      \"linkerd\": {\n        \"incoming-proxy-port\": 1234,\n        \"outgoing-proxy-port\": 5678,\n        \"proxy-uid\": 1111,\n        \"proxy-gid\": 1111,\n        \"ports-to-redirect\": [],\n        \"inbound-ports-to-ignore\": [\"4191\",\"4190\"],\n        \"simulate\": false,\n        \"use-wait-flag\": true,\n        \"iptables-mode\": \"nft\",\n        \"ipv6\": false\n      }\n    }\n---\nkind: DaemonSet\napiVersion: apps/v1\nmetadata:\n  name: linkerd-cni\n  namespace: linkerd-test\n  labels:\n    k8s-app: linkerd-cni\n    linkerd.io/cni-resource: \"true\"\n  annotations:\n    linkerd.io/created-by: test-version\nspec:\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      k8s-app: linkerd-cni\n  updateStrategy:\n    rollingUpdate:\n      maxUnavailable: 1\n    type: RollingUpdate\n  template:\n    metadata:\n      labels:\n        k8s-app: linkerd-cni\n        linkerd.io/cni-resource: \"true\"\n      annotations:\n        linkerd.io/created-by: test-version\n        linkerd.io/inject: disabled\n    spec:\n      tolerations:\n        - operator: Exists\n      nodeSelector:\n        kubernetes.io/os: linux\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-cni\n      priorityClassName: system-node-critical\n      containers:\n      # This container installs the linkerd CNI binaries\n      # and CNI network config file on each node. The install\n      # script copies the files into place and then sleeps so\n      # that Kubernetes doesn't keep trying to restart it.\n      - name: install-cni\n        image: cr.l5d.io/linkerd/cni-plugin:v1.4.0\n        imagePullPolicy: IfNotPresent\n        env:\n        - name: DEST_CNI_NET_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_net_dir\n        - name: DEST_CNI_BIN_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_bin_dir\n        - name: CNI_NETWORK_CONFIG\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: cni_network_config\n        - name: SLEEP\n          value: \"true\"\n        lifecycle:\n          # In some edge-cases this helps ensure that cleanup() is called in the container's script\n          # https://github.com/linkerd/linkerd2/issues/2355\n          preStop:\n            exec:\n              command:\n              - /bin/sh\n              - -c\n              - kill -15 1; sleep 15s\n        volumeMounts:\n        - mountPath: /host/opt/cni/bin-test\n          name: cni-bin-dir\n        - mountPath: /host/etc/cni/net.d-test\n          name: cni-net-dir\n        - mountPath: /tmp\n          name: linkerd-tmp-dir\n        securityContext:\n          readOnlyRootFilesystem: true\n          privileged: false\n        resources:\n      volumes:\n      - name: cni-bin-dir\n        hostPath:\n          path: /opt/cni/bin-test\n      - name: cni-net-dir\n        hostPath:\n          path: /etc/cni/net.d-test\n      - name: linkerd-tmp-dir\n        emptyDir: {}\n"
  },
  {
    "path": "cli/cmd/testdata/install_config.yaml",
    "content": "clusterDomain: example.com\n"
  },
  {
    "path": "cli/cmd/testdata/install_controlplane_tracing_output.golden",
    "content": "---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: linkerd\n    pod-security.kubernetes.io/enforce: privileged\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGU=\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: linkerd/cli dev-undefined\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: tracing.foo:4317\n          meshIdentity: null\n        enabled: true\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 1\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: install-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 25%\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    enablePodDisruptionBudget: false\n    heartbeat: null\n    heartbeatResources: null\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: false\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\n            MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\n            ODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\n            a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\n            fcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n            2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\n            Af8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\n            HmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\n            AiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n            51tdrmkHEZRr0qlLSJdHYgEfMzk=\n            -----END CERTIFICATE-----\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources: null\n    identityTrustAnchorsPEM: |\n      -----BEGIN CERTIFICATE-----\n      MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n      JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n      MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n      ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n      l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n      uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n      /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n      aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n      IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n      vgUC0d2/9FMueIVMb+46WTCOjsqr\n      -----END CERTIFICATE-----\n    identityTrustDomain: cluster.local\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: []\n    linkerdVersion: install-control-plane-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: policy validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: profile validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: install-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: 4567,4568\n      ignoreOutboundPorts: 4567,4568\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: proxy injector CA bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tolerations: null\n    webhookFailurePolicy: Ignore\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQpPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CmEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQoyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCkFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CjUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==\n  key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgpkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  ca-bundle.crt: |-\n    -----BEGIN CERTIFICATE-----\n    MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n    JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n    MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n    ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n    l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n    uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n    /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n    aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n    IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n    vgUC0d2/9FMueIVMb+46WTCOjsqr\n    -----END CERTIFICATE-----\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-identity\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd\n        - -identity-trust-domain=cluster.local\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        \n        - -trace-collector=tracing.foo:4317\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-destination\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: a39770e2a1ac1f4bb452509e52a4ffd176bcb034f1ce906491f2649e28c67319\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=cluster.local\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        \n        - -trace-collector=tracing.foo:4317\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=cluster.local\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd\n          annotations:\n            linkerd.io/created-by: linkerd/cli dev-undefined\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:install-control-plane-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-proxy-injector\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: cd0cf730780be444ab96a4a835a244033ffb7c8cf4a8796d0e6ae5c72aa9ff31\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\napiVersion: v1\ndata:\n  linkerd-config-overrides: Y29udHJvbGxlcjoKICB0cmFjaW5nOgogICAgY29sbGVjdG9yOgogICAgICBlbmRwb2ludDogdHJhY2luZy5mb286NDMxNwogICAgZW5hYmxlZDogdHJ1ZQpkZWJ1Z0NvbnRhaW5lcjoKICBpbWFnZToKICAgIHZlcnNpb246IGluc3RhbGwtZGVidWctdmVyc2lvbgpoZWFydGJlYXRTY2hlZHVsZTogMSAyIDMgNCA1CmlkZW50aXR5OgogIGlzc3VlcjoKICAgIHRsczoKICAgICAgY3J0UEVNOiB8CiAgICAgICAgLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCiAgICAgICAgTUlJQndEQ0NBV2VnQXdJQkFnSVJBSlJJZ1o4UnRPOEV3ZzFYZXBmOFQ0NHdDZ1lJS29aSXpqMEVBd0l3S1RFbgogICAgICAgIE1DVUdBMVVFQXhNZWFXUmxiblJwZEhrdWJHbHVhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJd01EZ3kKICAgICAgICBPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CiAgICAgICAgYTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFMS9GcAogICAgICAgIGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MmRRdlJhWWFudXhEMzZEdDEKICAgICAgICAyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCiAgICAgICAgQWY4Q0FRQXdIUVlEVlIwT0JCWUVGSTFXbnJxTVlLYUhIT28renB5aWlEcTJwTzBLTUNrR0ExVWRFUVFpTUNDQwogICAgICAgIEhtbGtaVzUwYVhSNUxteHBibXRsY21RdVkyeDFjM1JsY2k1c2IyTmhiREFLQmdncWhrak9QUVFEQWdOSEFEQkUKICAgICAgICBBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CiAgICAgICAgNTF0ZHJta0hFWlJyMHFsTFNKZEhZZ0VmTXprPQogICAgICAgIC0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KICAgICAga2V5UEVNOiB8CiAgICAgICAgLS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCiAgICAgICAgTUhjQ0FRRUVJQUFlOG5mYnpadTljL09CMis4eEpNMEZ6N05Vd1RRYXp1bGtGTnM0VEk1K29Bb0dDQ3FHU000OQogICAgICAgIEF3RUhvVVFEUWdBRTEvRnBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzIKICAgICAgICBkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KICAgICAgICAtLS0tLUVORCBFQyBQUklWQVRFIEtFWS0tLS0tCmlkZW50aXR5VHJ1c3RBbmNob3JzUEVNOiB8CiAgLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCiAgTUlJQndUQ0NBV2FnQXdJQkFnSVFlRFpwNWxEYUl5Z1E1VWZNS1pyRkFUQUtCZ2dxaGtqT1BRUURBakFwTVNjdwogIEpRWURWUVFERXg1cFpHVnVkR2wwZVM1c2FXNXJaWEprTG1Oc2RYTjBaWEl1Ykc5allXd3dIaGNOTWpBd09ESTQKICBNRGN4TWpRM1doY05NekF3T0RJMk1EY3hNalEzV2pBcE1TY3dKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyCiAgWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVJxYzcwWgogIGwxdmd3NzlyakI1dVNJVElDVUE2R3lmdlNGZmN1SWlzN0IvWEZTa2t3QUhVNVMvczFBQVArUjBUWDdIQldVQzQKICB1YUc0V1dzaXdKS05uN21nbzNBd2JqQU9CZ05WSFE4QkFmOEVCQU1DQVFZd0VnWURWUjBUQVFIL0JBZ3dCZ0VCCiAgL3dJQkFUQWRCZ05WSFE0RUZnUVU1WXRqVlZQZmQ3STdOTEhzbjJDMjZFQnlHVjB3S1FZRFZSMFJCQ0l3SUlJZQogIGFXUmxiblJwZEhrdWJHbHVhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNQW9HQ0NxR1NNNDlCQU1DQTBrQU1FWUMKICBJUUNON2xCRkxERHZqeDZWMCtYa2pwS0VSUnNKWWY1YWRNdm5sb0ZsNDhpbEpnSWhBTnR4aG5kY3IrUUpQdUM4CiAgdmdVQzBkMi85Rk11ZUlWTWIrNDZXVENPanNxcgogIC0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KbGlua2VyZFZlcnNpb246IGluc3RhbGwtY29udHJvbC1wbGFuZS12ZXJzaW9uCnBvbGljeVZhbGlkYXRvcjoKICBjYUJ1bmRsZTogcG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQpwcm9maWxlVmFsaWRhdG9yOgogIGNhQnVuZGxlOiBwcm9maWxlIHZhbGlkYXRvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQpwcm94eToKICBpbWFnZToKICAgIHZlcnNpb246IGluc3RhbGwtcHJveHktdmVyc2lvbgpwcm94eUluamVjdG9yOgogIGNhQnVuZGxlOiBwcm94eSBpbmplY3RvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQo=\nkind: Secret\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-config-overrides\n  namespace: linkerd\n"
  },
  {
    "path": "cli/cmd/testdata/install_crds.golden",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: authorizationpolicies.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: AuthorizationPolicy\n    plural: authorizationpolicies\n    singular: authorizationpolicy\n    shortNames: [authzpolicy]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied server\n                resources.\n              type: object\n              required: [targetRef, requiredAuthenticationRefs]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the authorization\n                    policy applies.\n                  type: object\n                  required: [kind, name]\n                  # Modified from the gateway API.\n                  # Copyright 2020 The Kubernetes Authors\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: >-\n                        Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                requiredAuthenticationRefs:\n                  description: >-\n                    RequiredAuthenticationRefs enumerates a set of required\n                    authentications. ALL authentications must be satisfied for\n                    the authorization to apply. If any of the referred objects\n                    cannot be found, the authorization will be ignored.\n                  type: array\n                  items:\n                    type: object\n                    required: [kind, name]\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: egressnetworks.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    categories:\n    - policy\n    kind: EgressNetwork\n    listKind: EgressNetworkList\n    plural: egressnetworks\n    singular: egressnetwork\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An EgressNetwork captures traffic to egress destinations\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              trafficPolicy:\n                description: >-\n                  This field controls the traffic policy enforced upon traffic\n                  that does not match any explicit route resources associated\n                  with an instance of this object. The values that are allowed\n                  currently are:\n                   - Allow - permits all traffic, even if it has not been\n                                explicitly described via attaching an xRoute\n                                resources.\n                   - Deny -  blocks all traffic that has not been described via\n                                attaching an xRoute resource.\n                type: string\n                enum:\n                - Allow\n                - Deny\n              networks:\n                type: array\n                items:\n                  type: object\n                  required: [cidr]\n                  properties:\n                    cidr:\n                      description: >-\n                        The CIDR of the network to be authorized.\n                      type: string\n                    except:\n                      description: >-\n                        A list of IP networks/addresses not to be included in\n                        the above `cidr`.\n                      type: array\n                      items:\n                        type: string\n            type: object\n            required:\n            - trafficPolicy\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httplocalratelimitpolicies.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPLocalRateLimitPolicy\n    listKind: HTTPLocalRateLimitPolicyList\n    plural: httplocalratelimitpolicies\n    singular: httplocalratelimitpolicy\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      subresources:\n        status: {}\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required: [targetRef]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the rate limit\n                    policy applies. Only Server is allowed.\n                  type: object\n                  required: [kind, name]\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                total:\n                  description: >-\n                    Overall rate-limit, which all traffic coming to this\n                    target should abide.\n                    If unset no overall limit is applied.\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                identity:\n                  description: >-\n                    Fairness for individual identities; each separate client,\n                    grouped by identity, will have this rate-limit. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                overrides:\n                  description: >-\n                    Overrides for traffic from a specific client. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: array\n                  items:\n                    type: object\n                    required: [requestsPerSecond, clientRefs]\n                    properties:\n                      requestsPerSecond:\n                        format: int64\n                        type: integer\n                      clientRefs:\n                        type: array\n                        items:\n                          type: object\n                          required: [kind, name]\n                          properties:\n                            group:\n                              description: >-\n                                Group is the group of the referent. When empty, the\n                                Kubernetes core API group is inferred.\n                              maxLength: 253\n                              pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                              type: string\n                            kind:\n                              description: Kind is the kind of the referent.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                              type: string\n                            namespace:\n                              description: >-\n                                  Namespace is the namespace of the referent.\n                                  When unspecified (or empty string), this refers to the\n                                  local namespace of the Policy.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                              type: string\n                            name:\n                              description: Name is the name of the referent.\n                              maxLength: 253\n                              minLength: 1\n                              type: string\n            status:\n              type: object\n              properties:\n                conditions:\n                  type: array\n                  items:\n                    type: object\n                    properties:\n                      lastTransitionTime:\n                        description: lastTransitionTime is the last time the\n                          condition transitioned from one status to another.\n                        format: date-time\n                        type: string\n                      status:\n                        description: status of the condition (one of True, False, Unknown)\n                        enum:\n                        - \"True\"\n                        - \"False\"\n                        - Unknown\n                        type: string\n                      type:\n                        description: type of the condition in CamelCase or in\n                          foo.example.com/CamelCase.\n                        maxLength: 316\n                        pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                        type: string\n                      reason:\n                        description: reason contains a programmatic identifier\n                          indicating the reason for the condition's last\n                          transition. Producers of specific condition types may\n                          define expected values and meanings for this field, and\n                          whether the values are considered a guaranteed API. The\n                          value should be a CamelCase string. This field may not\n                          be empty.\n                        maxLength: 1024\n                        minLength: 1\n                        pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                        type: string\n                      message:\n                        description: message is a human readable message\n                          indicating details about the transition. This may be an\n                          empty string.\n                        maxLength: 32768\n                        type: string\n                  required:\n                  - status\n                  - type\n                targetRef:\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Server\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                  required:\n                  - name\n                  type: object\n              required:\n              - targetRef\n      additionalPrinterColumns:\n      - name: Target_kind\n        description: The resource kind to which the rate-limit applies\n        type: string\n        jsonPath: .spec.targetRef.kind\n      - name: Target_name\n        type: string\n        description: The resource name to which the rate-limit applies\n        jsonPath: .spec.targetRef.name\n      - name: Total_RPS\n        description: The overall rate-limit\n        type: integer\n        format: int32\n        jsonPath: .spec.total.requestsPerSecond\n      - name: Identity_RPS\n        description: The rate-limit per identity\n        type: integer\n        format: int32\n        jsonPath: .spec.identity.requestsPerSecond\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httproutes.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPRoute\n    listKind: HTTPRouteList\n    plural: httproutes\n    singular: httproute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\n                              All   implementations must support core filters. \\n\\n \"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta2\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta3\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                    timeouts:\n                      description: \"Timeouts defines the timeouts that can be configured\n                        for an HTTP request. \\n Support: Core \\n <gateway:experimental>\"\n                      properties:\n                        backendRequest:\n                          description: \"BackendRequest specifies a timeout for an\n                            individual request from the gateway to a backend service.\n                            Typically used in conjunction with automatic retries,\n                            if supported by an implementation. Default is the value\n                            of Request timeout. \\n Support: Extended\"\n                          format: duration\n                          type: string\n                        request:\n                          description: \"Request specifies a timeout for responding\n                            to client HTTP requests, disabled by default. \\n For example,\n                            the following rule will timeout if a client request is\n                            taking longer than 10 seconds to complete: \\n ``` rules:\n                            - timeouts: request: 10s backendRefs: ... ``` \\n Support:\n                            Core\"\n                          format: duration\n                          type: string\n                      type: object\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: meshtlsauthentications.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: MeshTLSAuthentication\n    plural: meshtlsauthentications\n    singular: meshtlsauthentication\n    shortNames: [meshtlsauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                MeshTLSAuthentication defines a list of authenticated client IDs\n                to be referenced by an `AuthorizationPolicy`. If a client\n                connection has the mutually-authenticated identity that matches\n                ANY of the of the provided identities, the connection is\n                considered authenticated.\n              type: object\n              oneOf:\n                - required: [identities]\n                - required: [identityRefs]\n              properties:\n                identities:\n                  description: >-\n                    Authorizes clients with the provided proxy identity strings\n                    (as provided via MTLS)\n\n                    The `*` prefix can be used to match all identities in\n                    a domain. An identity string of `*` indicates that\n                    all authentication clients are authorized.\n                  type: array\n                  minItems: 1\n                  items:\n                    type: string\n                identityRefs:\n                  type: array\n                  minItems: 1\n                  items:\n                    type: object\n                    required:\n                      - kind\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this refers to all resources of the specified Group\n                          and Kind in the specified namespace.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: networkauthentications.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: NetworkAuthentication\n    plural: networkauthentications\n    singular: networkauthentication\n    shortNames: [netauthn, networkauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                NetworkAuthentication defines a list of authenticated client\n                networks to be referenced by an `AuthorizationPolicy`. If a\n                client connection originates from ANY of the of the provided\n                networks, the connection is considered authenticated.\n              type: object\n              required: [networks]\n              properties:\n                networks:\n                  type: array\n                  items:\n                    type: object\n                    required: [cidr]\n                    properties:\n                      cidr:\n                        description: >-\n                          The CIDR of the network to be authorized.\n                        type: string\n                      except:\n                        description: >-\n                          A list of IP networks/addresses not to be included in\n                          the above `cidr`.\n                        type: array\n                        items:\n                          type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serverauthorizations.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: ServerAuthorization\n    plural: serverauthorizations\n    singular: serverauthorization\n    shortNames: [saz, serverauthz, srvauthz]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 ServerAuthorization is deprecated; use policy.linkerd.io/v1beta1 ServerAuthorization\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n    - name: v1beta1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n      additionalPrinterColumns:\n      - name: Server\n        type: string\n        description: The server that this grants access to\n        jsonPath: .spec.server.name\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: servers.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: Server\n    plural: servers\n    singular: server\n    shortNames: [srv]\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 Server is deprecated; use policy.linkerd.io/v1beta1 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n                  oneOf:\n                    - required: [matchExpressions]\n                    - required: [matchLabels]\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n    - name: v1beta1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1beta1 Server is deprecated; use policy.linkerd.io/v1beta3 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta2\n      served: true\n      storage: false\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta3\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                accessPolicy:\n                  type: string\n                  default: deny\n                  description: >-\n                    Default access policy to apply when the traffic doesn't match any of the policy rules.\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n      - name: Access Policy\n        type: string\n        description: The default access policy applied when the traffic doesn't match any of the policy rules\n        jsonPath: .spec.accessPolicy\n---\n###\n### Service Profile CRD\n###\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serviceprofiles.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: linkerd.io\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            required:\n            - routes\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  - name: v1alpha2\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  scope: Namespaced\n  names:\n    plural: serviceprofiles\n    singular: serviceprofile\n    kind: ServiceProfile\n    shortNames:\n    - sp\n\n\n\n\n\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: externalworkloads.workload.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: workload.linkerd.io\n  names:\n    categories:\n    - external\n    kind: ExternalWorkload\n    listKind: ExternalWorkloadList\n    plural: externalworkloads\n    singular: externalworkload\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTls:\n                description: meshTls describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                # TODO: relax this in the future when ipv6 is supported\n                # an external workload (like a pod) should only\n                # support 2 interfaces\n                maxItems: 1\n            type: object\n            required:\n            - meshTls\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTls.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n  - name: v1beta1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTLS:\n                description: meshTLS describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload. This field may\n                  hold a maximum of two entries. If one entry, it can be an\n                  IPv4 or IPv6 address; if two entries it should contain one\n                  IPv4 address and one IPv6 address.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                maxItems: 2\n            type: object\n            required:\n            - meshTLS\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTLS.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n"
  },
  {
    "path": "cli/cmd/testdata/install_crds_with_gateway_api.golden",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: authorizationpolicies.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: AuthorizationPolicy\n    plural: authorizationpolicies\n    singular: authorizationpolicy\n    shortNames: [authzpolicy]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied server\n                resources.\n              type: object\n              required: [targetRef, requiredAuthenticationRefs]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the authorization\n                    policy applies.\n                  type: object\n                  required: [kind, name]\n                  # Modified from the gateway API.\n                  # Copyright 2020 The Kubernetes Authors\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: >-\n                        Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                requiredAuthenticationRefs:\n                  description: >-\n                    RequiredAuthenticationRefs enumerates a set of required\n                    authentications. ALL authentications must be satisfied for\n                    the authorization to apply. If any of the referred objects\n                    cannot be found, the authorization will be ignored.\n                  type: array\n                  items:\n                    type: object\n                    required: [kind, name]\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: egressnetworks.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    categories:\n    - policy\n    kind: EgressNetwork\n    listKind: EgressNetworkList\n    plural: egressnetworks\n    singular: egressnetwork\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An EgressNetwork captures traffic to egress destinations\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              trafficPolicy:\n                description: >-\n                  This field controls the traffic policy enforced upon traffic\n                  that does not match any explicit route resources associated\n                  with an instance of this object. The values that are allowed\n                  currently are:\n                   - Allow - permits all traffic, even if it has not been\n                                explicitly described via attaching an xRoute\n                                resources.\n                   - Deny -  blocks all traffic that has not been described via\n                                attaching an xRoute resource.\n                type: string\n                enum:\n                - Allow\n                - Deny\n              networks:\n                type: array\n                items:\n                  type: object\n                  required: [cidr]\n                  properties:\n                    cidr:\n                      description: >-\n                        The CIDR of the network to be authorized.\n                      type: string\n                    except:\n                      description: >-\n                        A list of IP networks/addresses not to be included in\n                        the above `cidr`.\n                      type: array\n                      items:\n                        type: string\n            type: object\n            required:\n            - trafficPolicy\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httplocalratelimitpolicies.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPLocalRateLimitPolicy\n    listKind: HTTPLocalRateLimitPolicyList\n    plural: httplocalratelimitpolicies\n    singular: httplocalratelimitpolicy\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      subresources:\n        status: {}\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required: [targetRef]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the rate limit\n                    policy applies. Only Server is allowed.\n                  type: object\n                  required: [kind, name]\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                total:\n                  description: >-\n                    Overall rate-limit, which all traffic coming to this\n                    target should abide.\n                    If unset no overall limit is applied.\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                identity:\n                  description: >-\n                    Fairness for individual identities; each separate client,\n                    grouped by identity, will have this rate-limit. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                overrides:\n                  description: >-\n                    Overrides for traffic from a specific client. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: array\n                  items:\n                    type: object\n                    required: [requestsPerSecond, clientRefs]\n                    properties:\n                      requestsPerSecond:\n                        format: int64\n                        type: integer\n                      clientRefs:\n                        type: array\n                        items:\n                          type: object\n                          required: [kind, name]\n                          properties:\n                            group:\n                              description: >-\n                                Group is the group of the referent. When empty, the\n                                Kubernetes core API group is inferred.\n                              maxLength: 253\n                              pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                              type: string\n                            kind:\n                              description: Kind is the kind of the referent.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                              type: string\n                            namespace:\n                              description: >-\n                                  Namespace is the namespace of the referent.\n                                  When unspecified (or empty string), this refers to the\n                                  local namespace of the Policy.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                              type: string\n                            name:\n                              description: Name is the name of the referent.\n                              maxLength: 253\n                              minLength: 1\n                              type: string\n            status:\n              type: object\n              properties:\n                conditions:\n                  type: array\n                  items:\n                    type: object\n                    properties:\n                      lastTransitionTime:\n                        description: lastTransitionTime is the last time the\n                          condition transitioned from one status to another.\n                        format: date-time\n                        type: string\n                      status:\n                        description: status of the condition (one of True, False, Unknown)\n                        enum:\n                        - \"True\"\n                        - \"False\"\n                        - Unknown\n                        type: string\n                      type:\n                        description: type of the condition in CamelCase or in\n                          foo.example.com/CamelCase.\n                        maxLength: 316\n                        pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                        type: string\n                      reason:\n                        description: reason contains a programmatic identifier\n                          indicating the reason for the condition's last\n                          transition. Producers of specific condition types may\n                          define expected values and meanings for this field, and\n                          whether the values are considered a guaranteed API. The\n                          value should be a CamelCase string. This field may not\n                          be empty.\n                        maxLength: 1024\n                        minLength: 1\n                        pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                        type: string\n                      message:\n                        description: message is a human readable message\n                          indicating details about the transition. This may be an\n                          empty string.\n                        maxLength: 32768\n                        type: string\n                  required:\n                  - status\n                  - type\n                targetRef:\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Server\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                  required:\n                  - name\n                  type: object\n              required:\n              - targetRef\n      additionalPrinterColumns:\n      - name: Target_kind\n        description: The resource kind to which the rate-limit applies\n        type: string\n        jsonPath: .spec.targetRef.kind\n      - name: Target_name\n        type: string\n        description: The resource name to which the rate-limit applies\n        jsonPath: .spec.targetRef.name\n      - name: Total_RPS\n        description: The overall rate-limit\n        type: integer\n        format: int32\n        jsonPath: .spec.total.requestsPerSecond\n      - name: Identity_RPS\n        description: The rate-limit per identity\n        type: integer\n        format: int32\n        jsonPath: .spec.identity.requestsPerSecond\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httproutes.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPRoute\n    listKind: HTTPRouteList\n    plural: httproutes\n    singular: httproute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\n                              All   implementations must support core filters. \\n\\n \"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta2\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta3\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                    timeouts:\n                      description: \"Timeouts defines the timeouts that can be configured\n                        for an HTTP request. \\n Support: Core \\n <gateway:experimental>\"\n                      properties:\n                        backendRequest:\n                          description: \"BackendRequest specifies a timeout for an\n                            individual request from the gateway to a backend service.\n                            Typically used in conjunction with automatic retries,\n                            if supported by an implementation. Default is the value\n                            of Request timeout. \\n Support: Extended\"\n                          format: duration\n                          type: string\n                        request:\n                          description: \"Request specifies a timeout for responding\n                            to client HTTP requests, disabled by default. \\n For example,\n                            the following rule will timeout if a client request is\n                            taking longer than 10 seconds to complete: \\n ``` rules:\n                            - timeouts: request: 10s backendRefs: ... ``` \\n Support:\n                            Core\"\n                          format: duration\n                          type: string\n                      type: object\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: meshtlsauthentications.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: MeshTLSAuthentication\n    plural: meshtlsauthentications\n    singular: meshtlsauthentication\n    shortNames: [meshtlsauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                MeshTLSAuthentication defines a list of authenticated client IDs\n                to be referenced by an `AuthorizationPolicy`. If a client\n                connection has the mutually-authenticated identity that matches\n                ANY of the of the provided identities, the connection is\n                considered authenticated.\n              type: object\n              oneOf:\n                - required: [identities]\n                - required: [identityRefs]\n              properties:\n                identities:\n                  description: >-\n                    Authorizes clients with the provided proxy identity strings\n                    (as provided via MTLS)\n\n                    The `*` prefix can be used to match all identities in\n                    a domain. An identity string of `*` indicates that\n                    all authentication clients are authorized.\n                  type: array\n                  minItems: 1\n                  items:\n                    type: string\n                identityRefs:\n                  type: array\n                  minItems: 1\n                  items:\n                    type: object\n                    required:\n                      - kind\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this refers to all resources of the specified Group\n                          and Kind in the specified namespace.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: networkauthentications.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: NetworkAuthentication\n    plural: networkauthentications\n    singular: networkauthentication\n    shortNames: [netauthn, networkauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                NetworkAuthentication defines a list of authenticated client\n                networks to be referenced by an `AuthorizationPolicy`. If a\n                client connection originates from ANY of the of the provided\n                networks, the connection is considered authenticated.\n              type: object\n              required: [networks]\n              properties:\n                networks:\n                  type: array\n                  items:\n                    type: object\n                    required: [cidr]\n                    properties:\n                      cidr:\n                        description: >-\n                          The CIDR of the network to be authorized.\n                        type: string\n                      except:\n                        description: >-\n                          A list of IP networks/addresses not to be included in\n                          the above `cidr`.\n                        type: array\n                        items:\n                          type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serverauthorizations.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: ServerAuthorization\n    plural: serverauthorizations\n    singular: serverauthorization\n    shortNames: [saz, serverauthz, srvauthz]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 ServerAuthorization is deprecated; use policy.linkerd.io/v1beta1 ServerAuthorization\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n    - name: v1beta1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n      additionalPrinterColumns:\n      - name: Server\n        type: string\n        description: The server that this grants access to\n        jsonPath: .spec.server.name\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: servers.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: Server\n    plural: servers\n    singular: server\n    shortNames: [srv]\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 Server is deprecated; use policy.linkerd.io/v1beta1 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n                  oneOf:\n                    - required: [matchExpressions]\n                    - required: [matchLabels]\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n    - name: v1beta1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1beta1 Server is deprecated; use policy.linkerd.io/v1beta3 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta2\n      served: true\n      storage: false\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta3\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                accessPolicy:\n                  type: string\n                  default: deny\n                  description: >-\n                    Default access policy to apply when the traffic doesn't match any of the policy rules.\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n      - name: Access Policy\n        type: string\n        description: The default access policy applied when the traffic doesn't match any of the policy rules\n        jsonPath: .spec.accessPolicy\n---\n###\n### Service Profile CRD\n###\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serviceprofiles.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: linkerd.io\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            required:\n            - routes\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  - name: v1alpha2\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  scope: Namespaced\n  names:\n    plural: serviceprofiles\n    singular: serviceprofile\n    kind: ServiceProfile\n    shortNames:\n    - sp\n\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997\n    gateway.networking.k8s.io/bundle-version: v1.1.1\n    gateway.networking.k8s.io/channel: experimental\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    helm.sh/resource-policy: keep\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\n  creationTimestamp: null\n  name: httproutes.gateway.networking.k8s.io\nspec:\n  group: gateway.networking.k8s.io\n  names:\n    categories:\n    - gateway-api\n    kind: HTTPRoute\n    listKind: HTTPRouteList\n    plural: httproutes\n    singular: httproute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1\n    schema:\n      openAPIV3Schema:\n        description: |-\n          HTTPRoute provides a way to route HTTP requests. This includes the capability\n          to match requests by hostname, path, header, or query param. Filters can be\n          used to specify additional processing steps. Backends specify where matching\n          requests should be routed.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: |-\n                  Hostnames defines a set of hostnames that should match against the HTTP Host\n                  header to select a HTTPRoute used to process the request. Implementations\n                  MUST ignore any port value specified in the HTTP Host header while\n                  performing a match and (absent of any applicable header modification\n                  configuration) MUST forward this header unmodified to the backend.\n\n\n                  Valid values for Hostnames are determined by RFC 1123 definition of a\n                  hostname with 2 notable exceptions:\n\n\n                  1. IPs are not allowed.\n                  2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                     label must appear by itself as the first label.\n\n\n                  If a hostname is specified by both the Listener and HTTPRoute, there\n                  must be at least one intersecting hostname for the HTTPRoute to be\n                  attached to the Listener. For example:\n\n\n                  * A Listener with `test.example.com` as the hostname matches HTTPRoutes\n                    that have either not specified any hostnames, or have specified at\n                    least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                    that have either not specified any hostnames or have specified at least\n                    one hostname that matches the Listener hostname. For example,\n                    `*.example.com`, `test.example.com`, and `foo.test.example.com` would\n                    all match. On the other hand, `example.com` and `test.example.net` would\n                    not match.\n\n\n                  Hostnames that are prefixed with a wildcard label (`*.`) are interpreted\n                  as a suffix match. That means that a match for `*.example.com` would match\n                  both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\n\n                  If both the Listener and HTTPRoute have specified hostnames, any\n                  HTTPRoute hostnames that do not match the Listener hostname MUST be\n                  ignored. For example, if a Listener specified `*.example.com`, and the\n                  HTTPRoute specified `test.example.com` and `test.example.net`,\n                  `test.example.net` must not be considered for a match.\n\n\n                  If both the Listener and HTTPRoute have specified hostnames, and none\n                  match with the criteria above, then the HTTPRoute is not accepted. The\n                  implementation must raise an 'Accepted' Condition with a status of\n                  `False` in the corresponding RouteParentStatus.\n\n\n                  In the event that multiple HTTPRoutes specify intersecting hostnames (e.g.\n                  overlapping wildcard matching and exact matching hostnames), precedence must\n                  be given to rules from the HTTPRoute with the largest number of:\n\n\n                  * Characters in a matching non-wildcard hostname.\n                  * Characters in a matching hostname.\n\n\n                  If ties exist across multiple Routes, the matching precedence rules for\n                  HTTPRouteMatches takes over.\n\n\n                  Support: Core\n                items:\n                  description: |-\n                    Hostname is the fully qualified domain name of a network host. This matches\n                    the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                     1. IPs are not allowed.\n                     2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                        label must appear by itself as the first label.\n\n\n                    Hostname can be \"precise\" which is a domain name without the terminating\n                    dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a\n                    domain name prefixed with a single wildcard label (e.g. `*.example.com`).\n\n\n                    Note that as per RFC1035 and RFC1123, a *label* must consist of lower case\n                    alphanumeric characters or '-', and must start and end with an alphanumeric\n                    character. No other punctuation is allowed.\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: |-\n                    HTTPRouteRule defines semantics for matching an HTTP request based on\n                    conditions (matches), processing it (filters), and forwarding the request to\n                    an API object (backendRefs).\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent.\n\n\n                        Failure behavior here depends on how many BackendRefs are specified and\n                        how many are invalid.\n\n\n                        If *all* entries in BackendRefs are invalid, and there are also no filters\n                        specified in this route rule, *all* traffic which matches this rule MUST\n                        receive a 500 status code.\n\n\n                        See the HTTPBackendRef definition for the rules about what makes a single\n                        HTTPBackendRef invalid.\n\n\n                        When a HTTPBackendRef is invalid, 500 status codes MUST be returned for\n                        requests that would have otherwise been routed to an invalid backend. If\n                        multiple backends are specified, and some are invalid, the proportion of\n                        requests that would otherwise have been routed to an invalid backend\n                        MUST receive a 500 status code.\n\n\n                        For example, if two backends are specified with equal weights, and one is\n                        invalid, 50 percent of traffic must receive a 500. Implementations may\n                        choose how that 50 percent is determined.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Extended for Kubernetes ServiceImport\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Core\n                      items:\n                        description: |-\n                          HTTPBackendRef defines how a HTTPRoute forwards a HTTP request.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n                        properties:\n                          filters:\n                            description: |-\n                              Filters defined at this level should be executed if and only if the\n                              request is being forwarded to the backend defined here.\n\n\n                              Support: Implementation-specific (For broader support of filters, use the\n                              Filters field in HTTPRouteRule.)\n                            items:\n                              description: |-\n                                HTTPRouteFilter defines processing steps that must be completed during the\n                                request or response lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway implementations. Some\n                                examples include request or response modification, implementing\n                                authentication strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type of the filter.\n                              properties:\n                                extensionRef:\n                                  description: |-\n                                    ExtensionRef is an optional, implementation-specific extension to the\n                                    \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                                    \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                                    extended filters.\n\n\n                                    This filter can be used multiple times within the same rule.\n\n\n                                    Support: Implementation-specific\n                                  properties:\n                                    group:\n                                      description: |-\n                                        Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                        When unspecified or empty string, core API group is inferred.\n                                      maxLength: 253\n                                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    kind:\n                                      description: Kind is kind of the referent. For\n                                        example \"HTTPRoute\" or \"Service\".\n                                      maxLength: 63\n                                      minLength: 1\n                                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                      type: string\n                                    name:\n                                      description: Name is the name of the referent.\n                                      maxLength: 253\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - group\n                                  - kind\n                                  - name\n                                  type: object\n                                requestHeaderModifier:\n                                  description: |-\n                                    RequestHeaderModifier defines a schema for a filter that modifies request\n                                    headers.\n\n\n                                    Support: Core\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestMirror:\n                                  description: |-\n                                    RequestMirror defines a schema for a filter that mirrors requests.\n                                    Requests are sent to the specified destination, but responses from\n                                    that destination are ignored.\n\n\n                                    This filter can be used multiple times within the same rule. Note that\n                                    not all implementations will be able to support mirroring to multiple\n                                    backends.\n\n\n                                    Support: Extended\n                                  properties:\n                                    backendRef:\n                                      description: |-\n                                        BackendRef references a resource where mirrored requests are sent.\n\n\n                                        Mirrored requests must be sent only to a single destination endpoint\n                                        within this BackendRef, irrespective of how many endpoints are present\n                                        within this BackendRef.\n\n\n                                        If the referent cannot be found, this BackendRef is invalid and must be\n                                        dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                        condition on the Route status is set to `status: False` and not configure\n                                        this backend in the underlying implementation.\n\n\n                                        If there is a cross-namespace reference to an *existing* object\n                                        that is not allowed by a ReferenceGrant, the controller must ensure the\n                                        \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                        with the \"RefNotPermitted\" reason and not configure this backend in the\n                                        underlying implementation.\n\n\n                                        In either error case, the Message of the `ResolvedRefs` Condition\n                                        should be used to provide more detail about the problem.\n\n\n                                        Support: Extended for Kubernetes Service\n\n\n                                        Support: Implementation-specific for any other resource\n                                      properties:\n                                        group:\n                                          default: \"\"\n                                          description: |-\n                                            Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                            When unspecified or empty string, core API group is inferred.\n                                          maxLength: 253\n                                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                          type: string\n                                        kind:\n                                          default: Service\n                                          description: |-\n                                            Kind is the Kubernetes resource kind of the referent. For example\n                                            \"Service\".\n\n\n                                            Defaults to \"Service\" when not specified.\n\n\n                                            ExternalName services can refer to CNAME DNS records that may live\n                                            outside of the cluster and as such are difficult to reason about in\n                                            terms of conformance. They also may not be safe to forward to (see\n                                            CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                            support ExternalName Services.\n\n\n                                            Support: Core (Services with a type other than ExternalName)\n\n\n                                            Support: Implementation-specific (Services with type ExternalName)\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                          type: string\n                                        name:\n                                          description: Name is the name of the referent.\n                                          maxLength: 253\n                                          minLength: 1\n                                          type: string\n                                        namespace:\n                                          description: |-\n                                            Namespace is the namespace of the backend. When unspecified, the local\n                                            namespace is inferred.\n\n\n                                            Note that when a namespace different than the local namespace is specified,\n                                            a ReferenceGrant object is required in the referent namespace to allow that\n                                            namespace's owner to accept the reference. See the ReferenceGrant\n                                            documentation for details.\n\n\n                                            Support: Core\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                          type: string\n                                        port:\n                                          description: |-\n                                            Port specifies the destination port number to use for this resource.\n                                            Port is required when the referent is a Kubernetes Service. In this\n                                            case, the port number is the service port number, not the target port.\n                                            For other resources, destination port might be derived from the referent\n                                            resource or this field.\n                                          format: int32\n                                          maximum: 65535\n                                          minimum: 1\n                                          type: integer\n                                      required:\n                                      - name\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: Must have port for Service reference\n                                        rule: '(size(self.group) == 0 && self.kind\n                                          == ''Service'') ? has(self.port) : true'\n                                  required:\n                                  - backendRef\n                                  type: object\n                                requestRedirect:\n                                  description: |-\n                                    RequestRedirect defines a schema for a filter that responds to the\n                                    request with an HTTP redirection.\n\n\n                                    Support: Core\n                                  properties:\n                                    hostname:\n                                      description: |-\n                                        Hostname is the hostname to be used in the value of the `Location`\n                                        header in the response.\n                                        When empty, the hostname in the `Host` header of the request is used.\n\n\n                                        Support: Core\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: |-\n                                        Path defines parameters used to modify the path of the incoming request.\n                                        The modified path is then used to construct the `Location` header. When\n                                        empty, the request path is used as-is.\n\n\n                                        Support: Extended\n                                      properties:\n                                        replaceFullPath:\n                                          description: |-\n                                            ReplaceFullPath specifies the value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: |-\n                                            ReplacePrefixMatch specifies the value with which to replace the prefix\n                                            match of a request during a rewrite or redirect. For example, a request\n                                            to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                            of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                            Note that this matches the behavior of the PathPrefix match type. This\n                                            matches full path elements. A path element refers to the list of labels\n                                            in the path split by the `/` separator. When specified, a trailing `/` is\n                                            ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                            match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                            ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                            Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                            the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                            Request Path | Prefix Match | Replace Prefix | Modified Path\n                                            -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                            /foo         | /foo         | /xyz           | /xyz\n                                            /foo/        | /foo         | /xyz           | /xyz/\n                                            /foo/bar     | /foo         | <empty string> | /bar\n                                            /foo/        | /foo         | <empty string> | /\n                                            /foo         | /foo         | <empty string> | /\n                                            /foo/        | /foo         | /              | /\n                                            /foo         | /foo         | /              | /\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: |-\n                                            Type defines the type of path modifier. Additional types may be\n                                            added in a future release of the API.\n\n\n                                            Note that values may be added to this enum, implementations\n                                            must ensure that unknown values will not cause a crash.\n\n\n                                            Unknown values here must result in the implementation setting the\n                                            Accepted Condition for the Route to `status: False`, with a\n                                            Reason of `UnsupportedValue`.\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: replaceFullPath must be specified\n                                          when type is set to 'ReplaceFullPath'\n                                        rule: 'self.type == ''ReplaceFullPath'' ?\n                                          has(self.replaceFullPath) : true'\n                                      - message: type must be 'ReplaceFullPath' when\n                                          replaceFullPath is set\n                                        rule: 'has(self.replaceFullPath) ? self.type\n                                          == ''ReplaceFullPath'' : true'\n                                      - message: replacePrefixMatch must be specified\n                                          when type is set to 'ReplacePrefixMatch'\n                                        rule: 'self.type == ''ReplacePrefixMatch''\n                                          ? has(self.replacePrefixMatch) : true'\n                                      - message: type must be 'ReplacePrefixMatch'\n                                          when replacePrefixMatch is set\n                                        rule: 'has(self.replacePrefixMatch) ? self.type\n                                          == ''ReplacePrefixMatch'' : true'\n                                    port:\n                                      description: |-\n                                        Port is the port to be used in the value of the `Location`\n                                        header in the response.\n\n\n                                        If no port is specified, the redirect port MUST be derived using the\n                                        following rules:\n\n\n                                        * If redirect scheme is not-empty, the redirect port MUST be the well-known\n                                          port associated with the redirect scheme. Specifically \"http\" to port 80\n                                          and \"https\" to port 443. If the redirect scheme does not have a\n                                          well-known port, the listener port of the Gateway SHOULD be used.\n                                        * If redirect scheme is empty, the redirect port MUST be the Gateway\n                                          Listener port.\n\n\n                                        Implementations SHOULD NOT add the port number in the 'Location'\n                                        header in the following cases:\n\n\n                                        * A Location header that will use HTTP (whether that is determined via\n                                          the Listener protocol or the Scheme field) _and_ use port 80.\n                                        * A Location header that will use HTTPS (whether that is determined via\n                                          the Listener protocol or the Scheme field) _and_ use port 443.\n\n\n                                        Support: Extended\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: |-\n                                        Scheme is the scheme to be used in the value of the `Location` header in\n                                        the response. When empty, the scheme of the request is used.\n\n\n                                        Scheme redirects can affect the port of the redirect, for more information,\n                                        refer to the documentation for the port field of this filter.\n\n\n                                        Note that values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause a crash.\n\n\n                                        Unknown values here must result in the implementation setting the\n                                        Accepted Condition for the Route to `status: False`, with a\n                                        Reason of `UnsupportedValue`.\n\n\n                                        Support: Extended\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: |-\n                                        StatusCode is the HTTP status code to be used in response.\n\n\n                                        Note that values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause a crash.\n\n\n                                        Unknown values here must result in the implementation setting the\n                                        Accepted Condition for the Route to `status: False`, with a\n                                        Reason of `UnsupportedValue`.\n\n\n                                        Support: Core\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: |-\n                                    ResponseHeaderModifier defines a schema for a filter that modifies response\n                                    headers.\n\n\n                                    Support: Extended\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: |-\n                                    Type identifies the type of filter to apply. As with other API fields,\n                                    types are classified into three conformance levels:\n\n\n                                    - Core: Filter types and their corresponding configuration defined by\n                                      \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                      implementations must support core filters.\n\n\n                                    - Extended: Filter types and their corresponding configuration defined by\n                                      \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                      are encouraged to support extended filters.\n\n\n                                    - Implementation-specific: Filters that are defined and supported by\n                                      specific vendors.\n                                      In the future, filters showing convergence in behavior across multiple\n                                      implementations will be considered for inclusion in extended or core\n                                      conformance levels. Filter-specific configuration for such filters\n                                      is specified using the ExtensionRef field. `Type` should be set to\n                                      \"ExtensionRef\" for custom filters.\n\n\n                                    Implementers are encouraged to define custom implementation types to\n                                    extend the core API with implementation-specific behavior.\n\n\n                                    If a reference to a custom filter type cannot be resolved, the filter\n                                    MUST NOT be skipped. Instead, requests that would have been processed by\n                                    that filter MUST receive a HTTP error response.\n\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause a crash.\n\n\n                                    Unknown values here must result in the implementation setting the\n                                    Accepted Condition for the Route to `status: False`, with a\n                                    Reason of `UnsupportedValue`.\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestMirror\n                                  - RequestRedirect\n                                  - URLRewrite\n                                  - ExtensionRef\n                                  type: string\n                                urlRewrite:\n                                  description: |-\n                                    URLRewrite defines a schema for a filter that modifies a request during forwarding.\n\n\n                                    Support: Extended\n                                  properties:\n                                    hostname:\n                                      description: |-\n                                        Hostname is the value to be used to replace the Host header value during\n                                        forwarding.\n\n\n                                        Support: Extended\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: |-\n                                        Path defines a path rewrite.\n\n\n                                        Support: Extended\n                                      properties:\n                                        replaceFullPath:\n                                          description: |-\n                                            ReplaceFullPath specifies the value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: |-\n                                            ReplacePrefixMatch specifies the value with which to replace the prefix\n                                            match of a request during a rewrite or redirect. For example, a request\n                                            to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                            of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                            Note that this matches the behavior of the PathPrefix match type. This\n                                            matches full path elements. A path element refers to the list of labels\n                                            in the path split by the `/` separator. When specified, a trailing `/` is\n                                            ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                            match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                            ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                            Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                            the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                            Request Path | Prefix Match | Replace Prefix | Modified Path\n                                            -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                            /foo         | /foo         | /xyz           | /xyz\n                                            /foo/        | /foo         | /xyz           | /xyz/\n                                            /foo/bar     | /foo         | <empty string> | /bar\n                                            /foo/        | /foo         | <empty string> | /\n                                            /foo         | /foo         | <empty string> | /\n                                            /foo/        | /foo         | /              | /\n                                            /foo         | /foo         | /              | /\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: |-\n                                            Type defines the type of path modifier. Additional types may be\n                                            added in a future release of the API.\n\n\n                                            Note that values may be added to this enum, implementations\n                                            must ensure that unknown values will not cause a crash.\n\n\n                                            Unknown values here must result in the implementation setting the\n                                            Accepted Condition for the Route to `status: False`, with a\n                                            Reason of `UnsupportedValue`.\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: replaceFullPath must be specified\n                                          when type is set to 'ReplaceFullPath'\n                                        rule: 'self.type == ''ReplaceFullPath'' ?\n                                          has(self.replaceFullPath) : true'\n                                      - message: type must be 'ReplaceFullPath' when\n                                          replaceFullPath is set\n                                        rule: 'has(self.replaceFullPath) ? self.type\n                                          == ''ReplaceFullPath'' : true'\n                                      - message: replacePrefixMatch must be specified\n                                          when type is set to 'ReplacePrefixMatch'\n                                        rule: 'self.type == ''ReplacePrefixMatch''\n                                          ? has(self.replacePrefixMatch) : true'\n                                      - message: type must be 'ReplacePrefixMatch'\n                                          when replacePrefixMatch is set\n                                        rule: 'has(self.replacePrefixMatch) ? self.type\n                                          == ''ReplacePrefixMatch'' : true'\n                                  type: object\n                              required:\n                              - type\n                              type: object\n                              x-kubernetes-validations:\n                              - message: filter.requestHeaderModifier must be nil\n                                  if the filter.type is not RequestHeaderModifier\n                                rule: '!(has(self.requestHeaderModifier) && self.type\n                                  != ''RequestHeaderModifier'')'\n                              - message: filter.requestHeaderModifier must be specified\n                                  for RequestHeaderModifier filter.type\n                                rule: '!(!has(self.requestHeaderModifier) && self.type\n                                  == ''RequestHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be nil\n                                  if the filter.type is not ResponseHeaderModifier\n                                rule: '!(has(self.responseHeaderModifier) && self.type\n                                  != ''ResponseHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be specified\n                                  for ResponseHeaderModifier filter.type\n                                rule: '!(!has(self.responseHeaderModifier) && self.type\n                                  == ''ResponseHeaderModifier'')'\n                              - message: filter.requestMirror must be nil if the filter.type\n                                  is not RequestMirror\n                                rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                              - message: filter.requestMirror must be specified for\n                                  RequestMirror filter.type\n                                rule: '!(!has(self.requestMirror) && self.type ==\n                                  ''RequestMirror'')'\n                              - message: filter.requestRedirect must be nil if the\n                                  filter.type is not RequestRedirect\n                                rule: '!(has(self.requestRedirect) && self.type !=\n                                  ''RequestRedirect'')'\n                              - message: filter.requestRedirect must be specified\n                                  for RequestRedirect filter.type\n                                rule: '!(!has(self.requestRedirect) && self.type ==\n                                  ''RequestRedirect'')'\n                              - message: filter.urlRewrite must be nil if the filter.type\n                                  is not URLRewrite\n                                rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')'\n                              - message: filter.urlRewrite must be specified for URLRewrite\n                                  filter.type\n                                rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')'\n                              - message: filter.extensionRef must be nil if the filter.type\n                                  is not ExtensionRef\n                                rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                              - message: filter.extensionRef must be specified for\n                                  ExtensionRef filter.type\n                                rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-validations:\n                            - message: May specify either httpRouteFilterRequestRedirect\n                                or httpRouteFilterRequestRewrite, but not both\n                              rule: '!(self.exists(f, f.type == ''RequestRedirect'')\n                                && self.exists(f, f.type == ''URLRewrite''))'\n                            - message: May specify either httpRouteFilterRequestRedirect\n                                or httpRouteFilterRequestRewrite, but not both\n                              rule: '!(self.exists(f, f.type == ''RequestRedirect'')\n                                && self.exists(f, f.type == ''URLRewrite''))'\n                            - message: RequestHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                                <= 1\n                            - message: ResponseHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                                <= 1\n                            - message: RequestRedirect filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestRedirect').size()\n                                <= 1\n                            - message: URLRewrite filter cannot be repeated\n                              rule: self.filter(f, f.type == 'URLRewrite').size()\n                                <= 1\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: |-\n                        Filters define the filters that are applied to requests that match\n                        this rule.\n\n\n                        Wherever possible, implementations SHOULD implement filters in the order\n                        they are specified.\n\n\n                        Implementations MAY choose to implement this ordering strictly, rejecting\n                        any combination or order of filters that can not be supported. If implementations\n                        choose a strict interpretation of filter ordering, they MUST clearly document\n                        that behavior.\n\n\n                        To reject an invalid combination or order of filters, implementations SHOULD\n                        consider the Route Rules with this configuration invalid. If all Route Rules\n                        in a Route are invalid, the entire Route would be considered invalid. If only\n                        a portion of Route Rules are invalid, implementations MUST set the\n                        \"PartiallyInvalid\" condition for the Route.\n\n\n                        Conformance-levels at this level are defined based on the type of filter:\n\n\n                        - ALL core filters MUST be supported by all implementations.\n                        - Implementers are encouraged to support extended filters.\n                        - Implementation-specific custom filters have no API guarantees across\n                          implementations.\n\n\n                        Specifying the same filter multiple times is not supported unless explicitly\n                        indicated in the filter.\n\n\n                        All filters are expected to be compatible with each other except for the\n                        URLRewrite and RequestRedirect filters, which may not be combined. If an\n                        implementation can not support other combinations of filters, they must clearly\n                        document that limitation. In cases where incompatible or unsupported\n                        filters are specified and cause the `Accepted` condition to be set to status\n                        `False`, implementations may use the `IncompatibleFilters` reason to specify\n                        this configuration error.\n\n\n                        Support: Core\n                      items:\n                        description: |-\n                          HTTPRouteFilter defines processing steps that must be completed during the\n                          request or response lifecycle. HTTPRouteFilters are meant as an extension\n                          point to express processing that may be done in Gateway implementations. Some\n                          examples include request or response modification, implementing\n                          authentication strategies, rate-limiting, and traffic shaping. API\n                          guarantee/conformance is defined based on the type of the filter.\n                        properties:\n                          extensionRef:\n                            description: |-\n                              ExtensionRef is an optional, implementation-specific extension to the\n                              \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                              \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                              extended filters.\n\n\n                              This filter can be used multiple times within the same rule.\n\n\n                              Support: Implementation-specific\n                            properties:\n                              group:\n                                description: |-\n                                  Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                  When unspecified or empty string, core API group is inferred.\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                description: Kind is kind of the referent. For example\n                                  \"HTTPRoute\" or \"Service\".\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: Name is the name of the referent.\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                            required:\n                            - group\n                            - kind\n                            - name\n                            type: object\n                          requestHeaderModifier:\n                            description: |-\n                              RequestHeaderModifier defines a schema for a filter that modifies request\n                              headers.\n\n\n                              Support: Core\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestMirror:\n                            description: |-\n                              RequestMirror defines a schema for a filter that mirrors requests.\n                              Requests are sent to the specified destination, but responses from\n                              that destination are ignored.\n\n\n                              This filter can be used multiple times within the same rule. Note that\n                              not all implementations will be able to support mirroring to multiple\n                              backends.\n\n\n                              Support: Extended\n                            properties:\n                              backendRef:\n                                description: |-\n                                  BackendRef references a resource where mirrored requests are sent.\n\n\n                                  Mirrored requests must be sent only to a single destination endpoint\n                                  within this BackendRef, irrespective of how many endpoints are present\n                                  within this BackendRef.\n\n\n                                  If the referent cannot be found, this BackendRef is invalid and must be\n                                  dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                  condition on the Route status is set to `status: False` and not configure\n                                  this backend in the underlying implementation.\n\n\n                                  If there is a cross-namespace reference to an *existing* object\n                                  that is not allowed by a ReferenceGrant, the controller must ensure the\n                                  \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                  with the \"RefNotPermitted\" reason and not configure this backend in the\n                                  underlying implementation.\n\n\n                                  In either error case, the Message of the `ResolvedRefs` Condition\n                                  should be used to provide more detail about the problem.\n\n\n                                  Support: Extended for Kubernetes Service\n\n\n                                  Support: Implementation-specific for any other resource\n                                properties:\n                                  group:\n                                    default: \"\"\n                                    description: |-\n                                      Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                      When unspecified or empty string, core API group is inferred.\n                                    maxLength: 253\n                                    pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                    type: string\n                                  kind:\n                                    default: Service\n                                    description: |-\n                                      Kind is the Kubernetes resource kind of the referent. For example\n                                      \"Service\".\n\n\n                                      Defaults to \"Service\" when not specified.\n\n\n                                      ExternalName services can refer to CNAME DNS records that may live\n                                      outside of the cluster and as such are difficult to reason about in\n                                      terms of conformance. They also may not be safe to forward to (see\n                                      CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                      support ExternalName Services.\n\n\n                                      Support: Core (Services with a type other than ExternalName)\n\n\n                                      Support: Implementation-specific (Services with type ExternalName)\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                    type: string\n                                  name:\n                                    description: Name is the name of the referent.\n                                    maxLength: 253\n                                    minLength: 1\n                                    type: string\n                                  namespace:\n                                    description: |-\n                                      Namespace is the namespace of the backend. When unspecified, the local\n                                      namespace is inferred.\n\n\n                                      Note that when a namespace different than the local namespace is specified,\n                                      a ReferenceGrant object is required in the referent namespace to allow that\n                                      namespace's owner to accept the reference. See the ReferenceGrant\n                                      documentation for details.\n\n\n                                      Support: Core\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                    type: string\n                                  port:\n                                    description: |-\n                                      Port specifies the destination port number to use for this resource.\n                                      Port is required when the referent is a Kubernetes Service. In this\n                                      case, the port number is the service port number, not the target port.\n                                      For other resources, destination port might be derived from the referent\n                                      resource or this field.\n                                    format: int32\n                                    maximum: 65535\n                                    minimum: 1\n                                    type: integer\n                                required:\n                                - name\n                                type: object\n                                x-kubernetes-validations:\n                                - message: Must have port for Service reference\n                                  rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                                    ? has(self.port) : true'\n                            required:\n                            - backendRef\n                            type: object\n                          requestRedirect:\n                            description: |-\n                              RequestRedirect defines a schema for a filter that responds to the\n                              request with an HTTP redirection.\n\n\n                              Support: Core\n                            properties:\n                              hostname:\n                                description: |-\n                                  Hostname is the hostname to be used in the value of the `Location`\n                                  header in the response.\n                                  When empty, the hostname in the `Host` header of the request is used.\n\n\n                                  Support: Core\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: |-\n                                  Path defines parameters used to modify the path of the incoming request.\n                                  The modified path is then used to construct the `Location` header. When\n                                  empty, the request path is used as-is.\n\n\n                                  Support: Extended\n                                properties:\n                                  replaceFullPath:\n                                    description: |-\n                                      ReplaceFullPath specifies the value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: |-\n                                      ReplacePrefixMatch specifies the value with which to replace the prefix\n                                      match of a request during a rewrite or redirect. For example, a request\n                                      to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                      of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                      Note that this matches the behavior of the PathPrefix match type. This\n                                      matches full path elements. A path element refers to the list of labels\n                                      in the path split by the `/` separator. When specified, a trailing `/` is\n                                      ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                      match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                      ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                      Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                      the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                      Request Path | Prefix Match | Replace Prefix | Modified Path\n                                      -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                      /foo         | /foo         | /xyz           | /xyz\n                                      /foo/        | /foo         | /xyz           | /xyz/\n                                      /foo/bar     | /foo         | <empty string> | /bar\n                                      /foo/        | /foo         | <empty string> | /\n                                      /foo         | /foo         | <empty string> | /\n                                      /foo/        | /foo         | /              | /\n                                      /foo         | /foo         | /              | /\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: |-\n                                      Type defines the type of path modifier. Additional types may be\n                                      added in a future release of the API.\n\n\n                                      Note that values may be added to this enum, implementations\n                                      must ensure that unknown values will not cause a crash.\n\n\n                                      Unknown values here must result in the implementation setting the\n                                      Accepted Condition for the Route to `status: False`, with a\n                                      Reason of `UnsupportedValue`.\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                                x-kubernetes-validations:\n                                - message: replaceFullPath must be specified when\n                                    type is set to 'ReplaceFullPath'\n                                  rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath)\n                                    : true'\n                                - message: type must be 'ReplaceFullPath' when replaceFullPath\n                                    is set\n                                  rule: 'has(self.replaceFullPath) ? self.type ==\n                                    ''ReplaceFullPath'' : true'\n                                - message: replacePrefixMatch must be specified when\n                                    type is set to 'ReplacePrefixMatch'\n                                  rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch)\n                                    : true'\n                                - message: type must be 'ReplacePrefixMatch' when\n                                    replacePrefixMatch is set\n                                  rule: 'has(self.replacePrefixMatch) ? self.type\n                                    == ''ReplacePrefixMatch'' : true'\n                              port:\n                                description: |-\n                                  Port is the port to be used in the value of the `Location`\n                                  header in the response.\n\n\n                                  If no port is specified, the redirect port MUST be derived using the\n                                  following rules:\n\n\n                                  * If redirect scheme is not-empty, the redirect port MUST be the well-known\n                                    port associated with the redirect scheme. Specifically \"http\" to port 80\n                                    and \"https\" to port 443. If the redirect scheme does not have a\n                                    well-known port, the listener port of the Gateway SHOULD be used.\n                                  * If redirect scheme is empty, the redirect port MUST be the Gateway\n                                    Listener port.\n\n\n                                  Implementations SHOULD NOT add the port number in the 'Location'\n                                  header in the following cases:\n\n\n                                  * A Location header that will use HTTP (whether that is determined via\n                                    the Listener protocol or the Scheme field) _and_ use port 80.\n                                  * A Location header that will use HTTPS (whether that is determined via\n                                    the Listener protocol or the Scheme field) _and_ use port 443.\n\n\n                                  Support: Extended\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: |-\n                                  Scheme is the scheme to be used in the value of the `Location` header in\n                                  the response. When empty, the scheme of the request is used.\n\n\n                                  Scheme redirects can affect the port of the redirect, for more information,\n                                  refer to the documentation for the port field of this filter.\n\n\n                                  Note that values may be added to this enum, implementations\n                                  must ensure that unknown values will not cause a crash.\n\n\n                                  Unknown values here must result in the implementation setting the\n                                  Accepted Condition for the Route to `status: False`, with a\n                                  Reason of `UnsupportedValue`.\n\n\n                                  Support: Extended\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: |-\n                                  StatusCode is the HTTP status code to be used in response.\n\n\n                                  Note that values may be added to this enum, implementations\n                                  must ensure that unknown values will not cause a crash.\n\n\n                                  Unknown values here must result in the implementation setting the\n                                  Accepted Condition for the Route to `status: False`, with a\n                                  Reason of `UnsupportedValue`.\n\n\n                                  Support: Core\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          responseHeaderModifier:\n                            description: |-\n                              ResponseHeaderModifier defines a schema for a filter that modifies response\n                              headers.\n\n\n                              Support: Extended\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          type:\n                            description: |-\n                              Type identifies the type of filter to apply. As with other API fields,\n                              types are classified into three conformance levels:\n\n\n                              - Core: Filter types and their corresponding configuration defined by\n                                \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                implementations must support core filters.\n\n\n                              - Extended: Filter types and their corresponding configuration defined by\n                                \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                are encouraged to support extended filters.\n\n\n                              - Implementation-specific: Filters that are defined and supported by\n                                specific vendors.\n                                In the future, filters showing convergence in behavior across multiple\n                                implementations will be considered for inclusion in extended or core\n                                conformance levels. Filter-specific configuration for such filters\n                                is specified using the ExtensionRef field. `Type` should be set to\n                                \"ExtensionRef\" for custom filters.\n\n\n                              Implementers are encouraged to define custom implementation types to\n                              extend the core API with implementation-specific behavior.\n\n\n                              If a reference to a custom filter type cannot be resolved, the filter\n                              MUST NOT be skipped. Instead, requests that would have been processed by\n                              that filter MUST receive a HTTP error response.\n\n\n                              Note that values may be added to this enum, implementations\n                              must ensure that unknown values will not cause a crash.\n\n\n                              Unknown values here must result in the implementation setting the\n                              Accepted Condition for the Route to `status: False`, with a\n                              Reason of `UnsupportedValue`.\n                            enum:\n                            - RequestHeaderModifier\n                            - ResponseHeaderModifier\n                            - RequestMirror\n                            - RequestRedirect\n                            - URLRewrite\n                            - ExtensionRef\n                            type: string\n                          urlRewrite:\n                            description: |-\n                              URLRewrite defines a schema for a filter that modifies a request during forwarding.\n\n\n                              Support: Extended\n                            properties:\n                              hostname:\n                                description: |-\n                                  Hostname is the value to be used to replace the Host header value during\n                                  forwarding.\n\n\n                                  Support: Extended\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: |-\n                                  Path defines a path rewrite.\n\n\n                                  Support: Extended\n                                properties:\n                                  replaceFullPath:\n                                    description: |-\n                                      ReplaceFullPath specifies the value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: |-\n                                      ReplacePrefixMatch specifies the value with which to replace the prefix\n                                      match of a request during a rewrite or redirect. For example, a request\n                                      to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                      of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                      Note that this matches the behavior of the PathPrefix match type. This\n                                      matches full path elements. A path element refers to the list of labels\n                                      in the path split by the `/` separator. When specified, a trailing `/` is\n                                      ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                      match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                      ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                      Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                      the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                      Request Path | Prefix Match | Replace Prefix | Modified Path\n                                      -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                      /foo         | /foo         | /xyz           | /xyz\n                                      /foo/        | /foo         | /xyz           | /xyz/\n                                      /foo/bar     | /foo         | <empty string> | /bar\n                                      /foo/        | /foo         | <empty string> | /\n                                      /foo         | /foo         | <empty string> | /\n                                      /foo/        | /foo         | /              | /\n                                      /foo         | /foo         | /              | /\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: |-\n                                      Type defines the type of path modifier. Additional types may be\n                                      added in a future release of the API.\n\n\n                                      Note that values may be added to this enum, implementations\n                                      must ensure that unknown values will not cause a crash.\n\n\n                                      Unknown values here must result in the implementation setting the\n                                      Accepted Condition for the Route to `status: False`, with a\n                                      Reason of `UnsupportedValue`.\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                                x-kubernetes-validations:\n                                - message: replaceFullPath must be specified when\n                                    type is set to 'ReplaceFullPath'\n                                  rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath)\n                                    : true'\n                                - message: type must be 'ReplaceFullPath' when replaceFullPath\n                                    is set\n                                  rule: 'has(self.replaceFullPath) ? self.type ==\n                                    ''ReplaceFullPath'' : true'\n                                - message: replacePrefixMatch must be specified when\n                                    type is set to 'ReplacePrefixMatch'\n                                  rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch)\n                                    : true'\n                                - message: type must be 'ReplacePrefixMatch' when\n                                    replacePrefixMatch is set\n                                  rule: 'has(self.replacePrefixMatch) ? self.type\n                                    == ''ReplacePrefixMatch'' : true'\n                            type: object\n                        required:\n                        - type\n                        type: object\n                        x-kubernetes-validations:\n                        - message: filter.requestHeaderModifier must be nil if the\n                            filter.type is not RequestHeaderModifier\n                          rule: '!(has(self.requestHeaderModifier) && self.type !=\n                            ''RequestHeaderModifier'')'\n                        - message: filter.requestHeaderModifier must be specified\n                            for RequestHeaderModifier filter.type\n                          rule: '!(!has(self.requestHeaderModifier) && self.type ==\n                            ''RequestHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be nil if the\n                            filter.type is not ResponseHeaderModifier\n                          rule: '!(has(self.responseHeaderModifier) && self.type !=\n                            ''ResponseHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be specified\n                            for ResponseHeaderModifier filter.type\n                          rule: '!(!has(self.responseHeaderModifier) && self.type\n                            == ''ResponseHeaderModifier'')'\n                        - message: filter.requestMirror must be nil if the filter.type\n                            is not RequestMirror\n                          rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                        - message: filter.requestMirror must be specified for RequestMirror\n                            filter.type\n                          rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')'\n                        - message: filter.requestRedirect must be nil if the filter.type\n                            is not RequestRedirect\n                          rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')'\n                        - message: filter.requestRedirect must be specified for RequestRedirect\n                            filter.type\n                          rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')'\n                        - message: filter.urlRewrite must be nil if the filter.type\n                            is not URLRewrite\n                          rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')'\n                        - message: filter.urlRewrite must be specified for URLRewrite\n                            filter.type\n                          rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')'\n                        - message: filter.extensionRef must be nil if the filter.type\n                            is not ExtensionRef\n                          rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                        - message: filter.extensionRef must be specified for ExtensionRef\n                            filter.type\n                          rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                      maxItems: 16\n                      type: array\n                      x-kubernetes-validations:\n                      - message: May specify either httpRouteFilterRequestRedirect\n                          or httpRouteFilterRequestRewrite, but not both\n                        rule: '!(self.exists(f, f.type == ''RequestRedirect'') &&\n                          self.exists(f, f.type == ''URLRewrite''))'\n                      - message: RequestHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                          <= 1\n                      - message: ResponseHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                          <= 1\n                      - message: RequestRedirect filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestRedirect').size() <=\n                          1\n                      - message: URLRewrite filter cannot be repeated\n                        rule: self.filter(f, f.type == 'URLRewrite').size() <= 1\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: |-\n                        Matches define conditions used for matching the rule against incoming\n                        HTTP requests. Each match is independent, i.e. this rule will be matched\n                        if **any** one of the matches is satisfied.\n\n\n                        For example, take the following matches configuration:\n\n\n                        ```\n                        matches:\n                        - path:\n                            value: \"/foo\"\n                          headers:\n                          - name: \"version\"\n                            value: \"v2\"\n                        - path:\n                            value: \"/v2/foo\"\n                        ```\n\n\n                        For a request to match against this rule, a request must satisfy\n                        EITHER of the two conditions:\n\n\n                        - path prefixed with `/foo` AND contains the header `version: v2`\n                        - path prefix of `/v2/foo`\n\n\n                        See the documentation for HTTPRouteMatch on how to specify multiple\n                        match conditions that should be ANDed together.\n\n\n                        If no matches are specified, the default is a prefix\n                        path match on \"/\", which has the effect of matching every\n                        HTTP request.\n\n\n                        Proxy or Load Balancer routing configuration generated from HTTPRoutes\n                        MUST prioritize matches based on the following criteria, continuing on\n                        ties. Across all rules specified on applicable Routes, precedence must be\n                        given to the match having:\n\n\n                        * \"Exact\" path match.\n                        * \"Prefix\" path match with largest number of characters.\n                        * Method match.\n                        * Largest number of header matches.\n                        * Largest number of query param matches.\n\n\n                        Note: The precedence of RegularExpression path matches are implementation-specific.\n\n\n                        If ties still exist across multiple Routes, matching precedence MUST be\n                        determined in order of the following criteria, continuing on ties:\n\n\n                        * The oldest Route based on creation timestamp.\n                        * The Route appearing first in alphabetical order by\n                          \"{namespace}/{name}\".\n\n\n                        If ties still exist within an HTTPRoute, matching precedence MUST be granted\n                        to the FIRST matching rule (in list order) with a match meeting the above\n                        criteria.\n\n\n                        When no rules matching a request have been successfully attached to the\n                        parent a request is coming from, a HTTP 404 status code MUST be returned.\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given\\naction. Multiple match types\n                          are ANDed together, i.e. the match will\\nevaluate to true\n                          only if all conditions are satisfied.\\n\\n\\nFor example,\n                          the match below will match a HTTP request only if its path\\nstarts\n                          with `/foo` AND it contains the `version: v1` header:\\n\\n\\n```\\nmatch:\\n\\n\\n\\tpath:\\n\\t\n                          \\ value: \\\"/foo\\\"\\n\\theaders:\\n\\t- name: \\\"version\\\"\\n\\t\n                          \\ value \\\"v1\\\"\\n\\n\\n```\"\n                        properties:\n                          headers:\n                            description: |-\n                              Headers specifies HTTP request header matchers. Multiple match values are\n                              ANDed together, meaning, a request must match all the specified headers\n                              to select the route.\n                            items:\n                              description: |-\n                                HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request\n                                headers.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                    case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                    If multiple entries specify equivalent header names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be ignored. Due to the\n                                    case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                    equivalent.\n\n\n                                    When a header is repeated in an HTTP request, it is\n                                    implementation-specific behavior as to how this is represented.\n                                    Generally, proxies should follow the guidance from the RFC:\n                                    https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding\n                                    processing a repeated header, with special handling for \"Set-Cookie\".\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: |-\n                                    Type specifies how to match against the value of the header.\n\n\n                                    Support: Core (Exact)\n\n\n                                    Support: Implementation-specific (RegularExpression)\n\n\n                                    Since RegularExpression HeaderMatchType has implementation-specific\n                                    conformance, implementations can support POSIX, PCRE or any other dialects\n                                    of regular expressions. Please read the implementation's documentation to\n                                    determine the supported dialect.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: |-\n                              Method specifies HTTP method matcher.\n                              When specified, this route will be matched only if the request has the\n                              specified method.\n\n\n                              Support: Extended\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: |-\n                              Path specifies a HTTP request path matcher. If this field is not\n                              specified, a default prefix match on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: |-\n                                  Type specifies how to match against the path Value.\n\n\n                                  Support: Core (Exact, PathPrefix)\n\n\n                                  Support: Implementation-specific (RegularExpression)\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                            x-kubernetes-validations:\n                            - message: value must be an absolute path and start with\n                                '/' when type one of ['Exact', 'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'')\n                                : true'\n                            - message: must not contain '//' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'')\n                                : true'\n                            - message: must not contain '/./' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'')\n                                : true'\n                            - message: must not contain '/../' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'')\n                                : true'\n                            - message: must not contain '%2f' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'')\n                                : true'\n                            - message: must not contain '%2F' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'')\n                                : true'\n                            - message: must not contain '#' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'')\n                                : true'\n                            - message: must not end with '/..' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'')\n                                : true'\n                            - message: must not end with '/.' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'')\n                                : true'\n                            - message: type must be one of ['Exact', 'PathPrefix',\n                                'RegularExpression']\n                              rule: self.type in ['Exact','PathPrefix'] || self.type\n                                == 'RegularExpression'\n                            - message: must only contain valid characters (matching\n                                ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$)\n                                for types ['Exact', 'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r\"\"\"^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$\"\"\")\n                                : true'\n                          queryParams:\n                            description: |-\n                              QueryParams specifies HTTP query parameter matchers. Multiple match\n                              values are ANDed together, meaning, a request must match all the\n                              specified query parameters to select the route.\n\n\n                              Support: Extended\n                            items:\n                              description: |-\n                                HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP\n                                query parameters.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the HTTP query param to be matched. This must be an\n                                    exact string match. (See\n                                    https://tools.ietf.org/html/rfc7230#section-2.7.3).\n\n\n                                    If multiple entries specify equivalent query param names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent query param name MUST be ignored.\n\n\n                                    If a query param is repeated in an HTTP request, the behavior is\n                                    purposely left undefined, since different data planes have different\n                                    capabilities. However, it is *recommended* that implementations should\n                                    match against the first value of the param if the data plane supports it,\n                                    as this behavior is expected in other load balancing contexts outside of\n                                    the Gateway API.\n\n\n                                    Users SHOULD NOT route traffic based on repeated query params to guard\n                                    themselves against potential differences in the implementations.\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: |-\n                                    Type specifies how to match against the value of the query parameter.\n\n\n                                    Support: Extended (Exact)\n\n\n                                    Support: Implementation-specific (RegularExpression)\n\n\n                                    Since RegularExpression QueryParamMatchType has Implementation-specific\n                                    conformance, implementations can support POSIX, PCRE or any other\n                                    dialects of regular expressions. Please read the implementation's\n                                    documentation to determine the supported dialect.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                    sessionPersistence:\n                      description: |+\n                        SessionPersistence defines and configures session persistence\n                        for the route rule.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        absoluteTimeout:\n                          description: |-\n                            AbsoluteTimeout defines the absolute timeout of the persistent\n                            session. Once the AbsoluteTimeout duration has elapsed, the\n                            session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        cookieConfig:\n                          description: |-\n                            CookieConfig provides configuration settings that are specific\n                            to cookie-based session persistence.\n\n\n                            Support: Core\n                          properties:\n                            lifetimeType:\n                              default: Session\n                              description: |-\n                                LifetimeType specifies whether the cookie has a permanent or\n                                session-based lifetime. A permanent cookie persists until its\n                                specified expiry time, defined by the Expires or Max-Age cookie\n                                attributes, while a session cookie is deleted when the current\n                                session ends.\n\n\n                                When set to \"Permanent\", AbsoluteTimeout indicates the\n                                cookie's lifetime via the Expires or Max-Age cookie attributes\n                                and is required.\n\n\n                                When set to \"Session\", AbsoluteTimeout indicates the\n                                absolute lifetime of the cookie tracked by the gateway and\n                                is optional.\n\n\n                                Support: Core for \"Session\" type\n\n\n                                Support: Extended for \"Permanent\" type\n                              enum:\n                              - Permanent\n                              - Session\n                              type: string\n                          type: object\n                        idleTimeout:\n                          description: |-\n                            IdleTimeout defines the idle timeout of the persistent session.\n                            Once the session has been idle for more than the specified\n                            IdleTimeout duration, the session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        sessionName:\n                          description: |-\n                            SessionName defines the name of the persistent session token\n                            which may be reflected in the cookie or the header. Users\n                            should avoid reusing session names to prevent unintended\n                            consequences, such as rejection or unpredictable behavior.\n\n\n                            Support: Implementation-specific\n                          maxLength: 128\n                          type: string\n                        type:\n                          default: Cookie\n                          description: |-\n                            Type defines the type of session persistence such as through\n                            the use a header or cookie. Defaults to cookie based session\n                            persistence.\n\n\n                            Support: Core for \"Cookie\" type\n\n\n                            Support: Extended for \"Header\" type\n                          enum:\n                          - Cookie\n                          - Header\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: AbsoluteTimeout must be specified when cookie lifetimeType\n                          is Permanent\n                        rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType)\n                          || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)'\n                    timeouts:\n                      description: |+\n                        Timeouts defines the timeouts that can be configured for an HTTP request.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        backendRequest:\n                          description: |-\n                            BackendRequest specifies a timeout for an individual request from the gateway\n                            to a backend. This covers the time from when the request first starts being\n                            sent from the gateway to when the full response has been received from the backend.\n\n\n                            Setting a timeout to the zero duration (e.g. \"0s\") SHOULD disable the timeout\n                            completely. Implementations that cannot completely disable the timeout MUST\n                            instead interpret the zero duration as the longest possible value to which\n                            the timeout can be set.\n\n\n                            An entire client HTTP transaction with a gateway, covered by the Request timeout,\n                            may result in more than one call from the gateway to the destination backend,\n                            for example, if automatic retries are supported.\n\n\n                            Because the Request timeout encompasses the BackendRequest timeout, the value of\n                            BackendRequest must be <= the value of Request timeout.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        request:\n                          description: |-\n                            Request specifies the maximum duration for a gateway to respond to an HTTP request.\n                            If the gateway has not been able to respond before this deadline is met, the gateway\n                            MUST return a timeout error.\n\n\n                            For example, setting the `rules.timeouts.request` field to the value `10s` in an\n                            `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds\n                            to complete.\n\n\n                            Setting a timeout to the zero duration (e.g. \"0s\") SHOULD disable the timeout\n                            completely. Implementations that cannot completely disable the timeout MUST\n                            instead interpret the zero duration as the longest possible value to which\n                            the timeout can be set.\n\n\n                            This timeout is intended to cover as close to the whole request-response transaction\n                            as possible although an implementation MAY choose to start the timeout after the entire\n                            request stream has been received instead of immediately after the transaction is\n                            initiated by the client.\n\n\n                            When this field is unspecified, request timeout behavior is implementation-specific.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: backendRequest timeout cannot be longer than request\n                          timeout\n                        rule: '!(has(self.request) && has(self.backendRequest) &&\n                          duration(self.request) != duration(''0s'') && duration(self.backendRequest)\n                          > duration(self.request))'\n                  type: object\n                  x-kubernetes-validations:\n                  - message: RequestRedirect filter must not be used together with\n                      backendRefs\n                    rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ?\n                      (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))):\n                      true'\n                  - message: When using RequestRedirect filter with path.replacePrefixMatch,\n                      exactly one PathPrefix match must be specified\n                    rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect)\n                      && has(f.requestRedirect.path) && f.requestRedirect.path.type\n                      == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch)))\n                      ? ((size(self.matches) != 1 || !has(self.matches[0].path) ||\n                      self.matches[0].path.type != ''PathPrefix'') ? false : true)\n                      : true'\n                  - message: When using URLRewrite filter with path.replacePrefixMatch,\n                      exactly one PathPrefix match must be specified\n                    rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite)\n                      && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch''\n                      && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches)\n                      != 1 || !has(self.matches[0].path) || self.matches[0].path.type\n                      != ''PathPrefix'') ? false : true) : true'\n                  - message: Within backendRefs, when using RequestRedirect filter\n                      with path.replacePrefixMatch, exactly one PathPrefix match must\n                      be specified\n                    rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b,\n                      (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect)\n                      && has(f.requestRedirect.path) && f.requestRedirect.path.type\n                      == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch)))\n                      )) ? ((size(self.matches) != 1 || !has(self.matches[0].path)\n                      || self.matches[0].path.type != ''PathPrefix'') ? false : true)\n                      : true'\n                  - message: Within backendRefs, When using URLRewrite filter with\n                      path.replacePrefixMatch, exactly one PathPrefix match must be\n                      specified\n                    rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b,\n                      (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite)\n                      && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch''\n                      && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches)\n                      != 1 || !has(self.matches[0].path) || self.matches[0].path.type\n                      != ''PathPrefix'') ? false : true) : true'\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: |-\n          HTTPRoute provides a way to route HTTP requests. This includes the capability\n          to match requests by hostname, path, header, or query param. Filters can be\n          used to specify additional processing steps. Backends specify where matching\n          requests should be routed.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: |-\n                  Hostnames defines a set of hostnames that should match against the HTTP Host\n                  header to select a HTTPRoute used to process the request. Implementations\n                  MUST ignore any port value specified in the HTTP Host header while\n                  performing a match and (absent of any applicable header modification\n                  configuration) MUST forward this header unmodified to the backend.\n\n\n                  Valid values for Hostnames are determined by RFC 1123 definition of a\n                  hostname with 2 notable exceptions:\n\n\n                  1. IPs are not allowed.\n                  2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                     label must appear by itself as the first label.\n\n\n                  If a hostname is specified by both the Listener and HTTPRoute, there\n                  must be at least one intersecting hostname for the HTTPRoute to be\n                  attached to the Listener. For example:\n\n\n                  * A Listener with `test.example.com` as the hostname matches HTTPRoutes\n                    that have either not specified any hostnames, or have specified at\n                    least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                    that have either not specified any hostnames or have specified at least\n                    one hostname that matches the Listener hostname. For example,\n                    `*.example.com`, `test.example.com`, and `foo.test.example.com` would\n                    all match. On the other hand, `example.com` and `test.example.net` would\n                    not match.\n\n\n                  Hostnames that are prefixed with a wildcard label (`*.`) are interpreted\n                  as a suffix match. That means that a match for `*.example.com` would match\n                  both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\n\n                  If both the Listener and HTTPRoute have specified hostnames, any\n                  HTTPRoute hostnames that do not match the Listener hostname MUST be\n                  ignored. For example, if a Listener specified `*.example.com`, and the\n                  HTTPRoute specified `test.example.com` and `test.example.net`,\n                  `test.example.net` must not be considered for a match.\n\n\n                  If both the Listener and HTTPRoute have specified hostnames, and none\n                  match with the criteria above, then the HTTPRoute is not accepted. The\n                  implementation must raise an 'Accepted' Condition with a status of\n                  `False` in the corresponding RouteParentStatus.\n\n\n                  In the event that multiple HTTPRoutes specify intersecting hostnames (e.g.\n                  overlapping wildcard matching and exact matching hostnames), precedence must\n                  be given to rules from the HTTPRoute with the largest number of:\n\n\n                  * Characters in a matching non-wildcard hostname.\n                  * Characters in a matching hostname.\n\n\n                  If ties exist across multiple Routes, the matching precedence rules for\n                  HTTPRouteMatches takes over.\n\n\n                  Support: Core\n                items:\n                  description: |-\n                    Hostname is the fully qualified domain name of a network host. This matches\n                    the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                     1. IPs are not allowed.\n                     2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                        label must appear by itself as the first label.\n\n\n                    Hostname can be \"precise\" which is a domain name without the terminating\n                    dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a\n                    domain name prefixed with a single wildcard label (e.g. `*.example.com`).\n\n\n                    Note that as per RFC1035 and RFC1123, a *label* must consist of lower case\n                    alphanumeric characters or '-', and must start and end with an alphanumeric\n                    character. No other punctuation is allowed.\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: |-\n                    HTTPRouteRule defines semantics for matching an HTTP request based on\n                    conditions (matches), processing it (filters), and forwarding the request to\n                    an API object (backendRefs).\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent.\n\n\n                        Failure behavior here depends on how many BackendRefs are specified and\n                        how many are invalid.\n\n\n                        If *all* entries in BackendRefs are invalid, and there are also no filters\n                        specified in this route rule, *all* traffic which matches this rule MUST\n                        receive a 500 status code.\n\n\n                        See the HTTPBackendRef definition for the rules about what makes a single\n                        HTTPBackendRef invalid.\n\n\n                        When a HTTPBackendRef is invalid, 500 status codes MUST be returned for\n                        requests that would have otherwise been routed to an invalid backend. If\n                        multiple backends are specified, and some are invalid, the proportion of\n                        requests that would otherwise have been routed to an invalid backend\n                        MUST receive a 500 status code.\n\n\n                        For example, if two backends are specified with equal weights, and one is\n                        invalid, 50 percent of traffic must receive a 500. Implementations may\n                        choose how that 50 percent is determined.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Extended for Kubernetes ServiceImport\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Core\n                      items:\n                        description: |-\n                          HTTPBackendRef defines how a HTTPRoute forwards a HTTP request.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n                        properties:\n                          filters:\n                            description: |-\n                              Filters defined at this level should be executed if and only if the\n                              request is being forwarded to the backend defined here.\n\n\n                              Support: Implementation-specific (For broader support of filters, use the\n                              Filters field in HTTPRouteRule.)\n                            items:\n                              description: |-\n                                HTTPRouteFilter defines processing steps that must be completed during the\n                                request or response lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway implementations. Some\n                                examples include request or response modification, implementing\n                                authentication strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type of the filter.\n                              properties:\n                                extensionRef:\n                                  description: |-\n                                    ExtensionRef is an optional, implementation-specific extension to the\n                                    \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                                    \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                                    extended filters.\n\n\n                                    This filter can be used multiple times within the same rule.\n\n\n                                    Support: Implementation-specific\n                                  properties:\n                                    group:\n                                      description: |-\n                                        Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                        When unspecified or empty string, core API group is inferred.\n                                      maxLength: 253\n                                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    kind:\n                                      description: Kind is kind of the referent. For\n                                        example \"HTTPRoute\" or \"Service\".\n                                      maxLength: 63\n                                      minLength: 1\n                                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                      type: string\n                                    name:\n                                      description: Name is the name of the referent.\n                                      maxLength: 253\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - group\n                                  - kind\n                                  - name\n                                  type: object\n                                requestHeaderModifier:\n                                  description: |-\n                                    RequestHeaderModifier defines a schema for a filter that modifies request\n                                    headers.\n\n\n                                    Support: Core\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestMirror:\n                                  description: |-\n                                    RequestMirror defines a schema for a filter that mirrors requests.\n                                    Requests are sent to the specified destination, but responses from\n                                    that destination are ignored.\n\n\n                                    This filter can be used multiple times within the same rule. Note that\n                                    not all implementations will be able to support mirroring to multiple\n                                    backends.\n\n\n                                    Support: Extended\n                                  properties:\n                                    backendRef:\n                                      description: |-\n                                        BackendRef references a resource where mirrored requests are sent.\n\n\n                                        Mirrored requests must be sent only to a single destination endpoint\n                                        within this BackendRef, irrespective of how many endpoints are present\n                                        within this BackendRef.\n\n\n                                        If the referent cannot be found, this BackendRef is invalid and must be\n                                        dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                        condition on the Route status is set to `status: False` and not configure\n                                        this backend in the underlying implementation.\n\n\n                                        If there is a cross-namespace reference to an *existing* object\n                                        that is not allowed by a ReferenceGrant, the controller must ensure the\n                                        \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                        with the \"RefNotPermitted\" reason and not configure this backend in the\n                                        underlying implementation.\n\n\n                                        In either error case, the Message of the `ResolvedRefs` Condition\n                                        should be used to provide more detail about the problem.\n\n\n                                        Support: Extended for Kubernetes Service\n\n\n                                        Support: Implementation-specific for any other resource\n                                      properties:\n                                        group:\n                                          default: \"\"\n                                          description: |-\n                                            Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                            When unspecified or empty string, core API group is inferred.\n                                          maxLength: 253\n                                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                          type: string\n                                        kind:\n                                          default: Service\n                                          description: |-\n                                            Kind is the Kubernetes resource kind of the referent. For example\n                                            \"Service\".\n\n\n                                            Defaults to \"Service\" when not specified.\n\n\n                                            ExternalName services can refer to CNAME DNS records that may live\n                                            outside of the cluster and as such are difficult to reason about in\n                                            terms of conformance. They also may not be safe to forward to (see\n                                            CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                            support ExternalName Services.\n\n\n                                            Support: Core (Services with a type other than ExternalName)\n\n\n                                            Support: Implementation-specific (Services with type ExternalName)\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                          type: string\n                                        name:\n                                          description: Name is the name of the referent.\n                                          maxLength: 253\n                                          minLength: 1\n                                          type: string\n                                        namespace:\n                                          description: |-\n                                            Namespace is the namespace of the backend. When unspecified, the local\n                                            namespace is inferred.\n\n\n                                            Note that when a namespace different than the local namespace is specified,\n                                            a ReferenceGrant object is required in the referent namespace to allow that\n                                            namespace's owner to accept the reference. See the ReferenceGrant\n                                            documentation for details.\n\n\n                                            Support: Core\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                          type: string\n                                        port:\n                                          description: |-\n                                            Port specifies the destination port number to use for this resource.\n                                            Port is required when the referent is a Kubernetes Service. In this\n                                            case, the port number is the service port number, not the target port.\n                                            For other resources, destination port might be derived from the referent\n                                            resource or this field.\n                                          format: int32\n                                          maximum: 65535\n                                          minimum: 1\n                                          type: integer\n                                      required:\n                                      - name\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: Must have port for Service reference\n                                        rule: '(size(self.group) == 0 && self.kind\n                                          == ''Service'') ? has(self.port) : true'\n                                  required:\n                                  - backendRef\n                                  type: object\n                                requestRedirect:\n                                  description: |-\n                                    RequestRedirect defines a schema for a filter that responds to the\n                                    request with an HTTP redirection.\n\n\n                                    Support: Core\n                                  properties:\n                                    hostname:\n                                      description: |-\n                                        Hostname is the hostname to be used in the value of the `Location`\n                                        header in the response.\n                                        When empty, the hostname in the `Host` header of the request is used.\n\n\n                                        Support: Core\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: |-\n                                        Path defines parameters used to modify the path of the incoming request.\n                                        The modified path is then used to construct the `Location` header. When\n                                        empty, the request path is used as-is.\n\n\n                                        Support: Extended\n                                      properties:\n                                        replaceFullPath:\n                                          description: |-\n                                            ReplaceFullPath specifies the value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: |-\n                                            ReplacePrefixMatch specifies the value with which to replace the prefix\n                                            match of a request during a rewrite or redirect. For example, a request\n                                            to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                            of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                            Note that this matches the behavior of the PathPrefix match type. This\n                                            matches full path elements. A path element refers to the list of labels\n                                            in the path split by the `/` separator. When specified, a trailing `/` is\n                                            ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                            match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                            ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                            Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                            the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                            Request Path | Prefix Match | Replace Prefix | Modified Path\n                                            -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                            /foo         | /foo         | /xyz           | /xyz\n                                            /foo/        | /foo         | /xyz           | /xyz/\n                                            /foo/bar     | /foo         | <empty string> | /bar\n                                            /foo/        | /foo         | <empty string> | /\n                                            /foo         | /foo         | <empty string> | /\n                                            /foo/        | /foo         | /              | /\n                                            /foo         | /foo         | /              | /\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: |-\n                                            Type defines the type of path modifier. Additional types may be\n                                            added in a future release of the API.\n\n\n                                            Note that values may be added to this enum, implementations\n                                            must ensure that unknown values will not cause a crash.\n\n\n                                            Unknown values here must result in the implementation setting the\n                                            Accepted Condition for the Route to `status: False`, with a\n                                            Reason of `UnsupportedValue`.\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: replaceFullPath must be specified\n                                          when type is set to 'ReplaceFullPath'\n                                        rule: 'self.type == ''ReplaceFullPath'' ?\n                                          has(self.replaceFullPath) : true'\n                                      - message: type must be 'ReplaceFullPath' when\n                                          replaceFullPath is set\n                                        rule: 'has(self.replaceFullPath) ? self.type\n                                          == ''ReplaceFullPath'' : true'\n                                      - message: replacePrefixMatch must be specified\n                                          when type is set to 'ReplacePrefixMatch'\n                                        rule: 'self.type == ''ReplacePrefixMatch''\n                                          ? has(self.replacePrefixMatch) : true'\n                                      - message: type must be 'ReplacePrefixMatch'\n                                          when replacePrefixMatch is set\n                                        rule: 'has(self.replacePrefixMatch) ? self.type\n                                          == ''ReplacePrefixMatch'' : true'\n                                    port:\n                                      description: |-\n                                        Port is the port to be used in the value of the `Location`\n                                        header in the response.\n\n\n                                        If no port is specified, the redirect port MUST be derived using the\n                                        following rules:\n\n\n                                        * If redirect scheme is not-empty, the redirect port MUST be the well-known\n                                          port associated with the redirect scheme. Specifically \"http\" to port 80\n                                          and \"https\" to port 443. If the redirect scheme does not have a\n                                          well-known port, the listener port of the Gateway SHOULD be used.\n                                        * If redirect scheme is empty, the redirect port MUST be the Gateway\n                                          Listener port.\n\n\n                                        Implementations SHOULD NOT add the port number in the 'Location'\n                                        header in the following cases:\n\n\n                                        * A Location header that will use HTTP (whether that is determined via\n                                          the Listener protocol or the Scheme field) _and_ use port 80.\n                                        * A Location header that will use HTTPS (whether that is determined via\n                                          the Listener protocol or the Scheme field) _and_ use port 443.\n\n\n                                        Support: Extended\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: |-\n                                        Scheme is the scheme to be used in the value of the `Location` header in\n                                        the response. When empty, the scheme of the request is used.\n\n\n                                        Scheme redirects can affect the port of the redirect, for more information,\n                                        refer to the documentation for the port field of this filter.\n\n\n                                        Note that values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause a crash.\n\n\n                                        Unknown values here must result in the implementation setting the\n                                        Accepted Condition for the Route to `status: False`, with a\n                                        Reason of `UnsupportedValue`.\n\n\n                                        Support: Extended\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: |-\n                                        StatusCode is the HTTP status code to be used in response.\n\n\n                                        Note that values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause a crash.\n\n\n                                        Unknown values here must result in the implementation setting the\n                                        Accepted Condition for the Route to `status: False`, with a\n                                        Reason of `UnsupportedValue`.\n\n\n                                        Support: Core\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: |-\n                                    ResponseHeaderModifier defines a schema for a filter that modifies response\n                                    headers.\n\n\n                                    Support: Extended\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: |-\n                                    Type identifies the type of filter to apply. As with other API fields,\n                                    types are classified into three conformance levels:\n\n\n                                    - Core: Filter types and their corresponding configuration defined by\n                                      \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                      implementations must support core filters.\n\n\n                                    - Extended: Filter types and their corresponding configuration defined by\n                                      \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                      are encouraged to support extended filters.\n\n\n                                    - Implementation-specific: Filters that are defined and supported by\n                                      specific vendors.\n                                      In the future, filters showing convergence in behavior across multiple\n                                      implementations will be considered for inclusion in extended or core\n                                      conformance levels. Filter-specific configuration for such filters\n                                      is specified using the ExtensionRef field. `Type` should be set to\n                                      \"ExtensionRef\" for custom filters.\n\n\n                                    Implementers are encouraged to define custom implementation types to\n                                    extend the core API with implementation-specific behavior.\n\n\n                                    If a reference to a custom filter type cannot be resolved, the filter\n                                    MUST NOT be skipped. Instead, requests that would have been processed by\n                                    that filter MUST receive a HTTP error response.\n\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause a crash.\n\n\n                                    Unknown values here must result in the implementation setting the\n                                    Accepted Condition for the Route to `status: False`, with a\n                                    Reason of `UnsupportedValue`.\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestMirror\n                                  - RequestRedirect\n                                  - URLRewrite\n                                  - ExtensionRef\n                                  type: string\n                                urlRewrite:\n                                  description: |-\n                                    URLRewrite defines a schema for a filter that modifies a request during forwarding.\n\n\n                                    Support: Extended\n                                  properties:\n                                    hostname:\n                                      description: |-\n                                        Hostname is the value to be used to replace the Host header value during\n                                        forwarding.\n\n\n                                        Support: Extended\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: |-\n                                        Path defines a path rewrite.\n\n\n                                        Support: Extended\n                                      properties:\n                                        replaceFullPath:\n                                          description: |-\n                                            ReplaceFullPath specifies the value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: |-\n                                            ReplacePrefixMatch specifies the value with which to replace the prefix\n                                            match of a request during a rewrite or redirect. For example, a request\n                                            to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                            of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                            Note that this matches the behavior of the PathPrefix match type. This\n                                            matches full path elements. A path element refers to the list of labels\n                                            in the path split by the `/` separator. When specified, a trailing `/` is\n                                            ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                            match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                            ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                            Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                            the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                            Request Path | Prefix Match | Replace Prefix | Modified Path\n                                            -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                            /foo         | /foo         | /xyz           | /xyz\n                                            /foo/        | /foo         | /xyz           | /xyz/\n                                            /foo/bar     | /foo         | <empty string> | /bar\n                                            /foo/        | /foo         | <empty string> | /\n                                            /foo         | /foo         | <empty string> | /\n                                            /foo/        | /foo         | /              | /\n                                            /foo         | /foo         | /              | /\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: |-\n                                            Type defines the type of path modifier. Additional types may be\n                                            added in a future release of the API.\n\n\n                                            Note that values may be added to this enum, implementations\n                                            must ensure that unknown values will not cause a crash.\n\n\n                                            Unknown values here must result in the implementation setting the\n                                            Accepted Condition for the Route to `status: False`, with a\n                                            Reason of `UnsupportedValue`.\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: replaceFullPath must be specified\n                                          when type is set to 'ReplaceFullPath'\n                                        rule: 'self.type == ''ReplaceFullPath'' ?\n                                          has(self.replaceFullPath) : true'\n                                      - message: type must be 'ReplaceFullPath' when\n                                          replaceFullPath is set\n                                        rule: 'has(self.replaceFullPath) ? self.type\n                                          == ''ReplaceFullPath'' : true'\n                                      - message: replacePrefixMatch must be specified\n                                          when type is set to 'ReplacePrefixMatch'\n                                        rule: 'self.type == ''ReplacePrefixMatch''\n                                          ? has(self.replacePrefixMatch) : true'\n                                      - message: type must be 'ReplacePrefixMatch'\n                                          when replacePrefixMatch is set\n                                        rule: 'has(self.replacePrefixMatch) ? self.type\n                                          == ''ReplacePrefixMatch'' : true'\n                                  type: object\n                              required:\n                              - type\n                              type: object\n                              x-kubernetes-validations:\n                              - message: filter.requestHeaderModifier must be nil\n                                  if the filter.type is not RequestHeaderModifier\n                                rule: '!(has(self.requestHeaderModifier) && self.type\n                                  != ''RequestHeaderModifier'')'\n                              - message: filter.requestHeaderModifier must be specified\n                                  for RequestHeaderModifier filter.type\n                                rule: '!(!has(self.requestHeaderModifier) && self.type\n                                  == ''RequestHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be nil\n                                  if the filter.type is not ResponseHeaderModifier\n                                rule: '!(has(self.responseHeaderModifier) && self.type\n                                  != ''ResponseHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be specified\n                                  for ResponseHeaderModifier filter.type\n                                rule: '!(!has(self.responseHeaderModifier) && self.type\n                                  == ''ResponseHeaderModifier'')'\n                              - message: filter.requestMirror must be nil if the filter.type\n                                  is not RequestMirror\n                                rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                              - message: filter.requestMirror must be specified for\n                                  RequestMirror filter.type\n                                rule: '!(!has(self.requestMirror) && self.type ==\n                                  ''RequestMirror'')'\n                              - message: filter.requestRedirect must be nil if the\n                                  filter.type is not RequestRedirect\n                                rule: '!(has(self.requestRedirect) && self.type !=\n                                  ''RequestRedirect'')'\n                              - message: filter.requestRedirect must be specified\n                                  for RequestRedirect filter.type\n                                rule: '!(!has(self.requestRedirect) && self.type ==\n                                  ''RequestRedirect'')'\n                              - message: filter.urlRewrite must be nil if the filter.type\n                                  is not URLRewrite\n                                rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')'\n                              - message: filter.urlRewrite must be specified for URLRewrite\n                                  filter.type\n                                rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')'\n                              - message: filter.extensionRef must be nil if the filter.type\n                                  is not ExtensionRef\n                                rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                              - message: filter.extensionRef must be specified for\n                                  ExtensionRef filter.type\n                                rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-validations:\n                            - message: May specify either httpRouteFilterRequestRedirect\n                                or httpRouteFilterRequestRewrite, but not both\n                              rule: '!(self.exists(f, f.type == ''RequestRedirect'')\n                                && self.exists(f, f.type == ''URLRewrite''))'\n                            - message: May specify either httpRouteFilterRequestRedirect\n                                or httpRouteFilterRequestRewrite, but not both\n                              rule: '!(self.exists(f, f.type == ''RequestRedirect'')\n                                && self.exists(f, f.type == ''URLRewrite''))'\n                            - message: RequestHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                                <= 1\n                            - message: ResponseHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                                <= 1\n                            - message: RequestRedirect filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestRedirect').size()\n                                <= 1\n                            - message: URLRewrite filter cannot be repeated\n                              rule: self.filter(f, f.type == 'URLRewrite').size()\n                                <= 1\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: |-\n                        Filters define the filters that are applied to requests that match\n                        this rule.\n\n\n                        Wherever possible, implementations SHOULD implement filters in the order\n                        they are specified.\n\n\n                        Implementations MAY choose to implement this ordering strictly, rejecting\n                        any combination or order of filters that can not be supported. If implementations\n                        choose a strict interpretation of filter ordering, they MUST clearly document\n                        that behavior.\n\n\n                        To reject an invalid combination or order of filters, implementations SHOULD\n                        consider the Route Rules with this configuration invalid. If all Route Rules\n                        in a Route are invalid, the entire Route would be considered invalid. If only\n                        a portion of Route Rules are invalid, implementations MUST set the\n                        \"PartiallyInvalid\" condition for the Route.\n\n\n                        Conformance-levels at this level are defined based on the type of filter:\n\n\n                        - ALL core filters MUST be supported by all implementations.\n                        - Implementers are encouraged to support extended filters.\n                        - Implementation-specific custom filters have no API guarantees across\n                          implementations.\n\n\n                        Specifying the same filter multiple times is not supported unless explicitly\n                        indicated in the filter.\n\n\n                        All filters are expected to be compatible with each other except for the\n                        URLRewrite and RequestRedirect filters, which may not be combined. If an\n                        implementation can not support other combinations of filters, they must clearly\n                        document that limitation. In cases where incompatible or unsupported\n                        filters are specified and cause the `Accepted` condition to be set to status\n                        `False`, implementations may use the `IncompatibleFilters` reason to specify\n                        this configuration error.\n\n\n                        Support: Core\n                      items:\n                        description: |-\n                          HTTPRouteFilter defines processing steps that must be completed during the\n                          request or response lifecycle. HTTPRouteFilters are meant as an extension\n                          point to express processing that may be done in Gateway implementations. Some\n                          examples include request or response modification, implementing\n                          authentication strategies, rate-limiting, and traffic shaping. API\n                          guarantee/conformance is defined based on the type of the filter.\n                        properties:\n                          extensionRef:\n                            description: |-\n                              ExtensionRef is an optional, implementation-specific extension to the\n                              \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                              \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                              extended filters.\n\n\n                              This filter can be used multiple times within the same rule.\n\n\n                              Support: Implementation-specific\n                            properties:\n                              group:\n                                description: |-\n                                  Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                  When unspecified or empty string, core API group is inferred.\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                description: Kind is kind of the referent. For example\n                                  \"HTTPRoute\" or \"Service\".\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: Name is the name of the referent.\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                            required:\n                            - group\n                            - kind\n                            - name\n                            type: object\n                          requestHeaderModifier:\n                            description: |-\n                              RequestHeaderModifier defines a schema for a filter that modifies request\n                              headers.\n\n\n                              Support: Core\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestMirror:\n                            description: |-\n                              RequestMirror defines a schema for a filter that mirrors requests.\n                              Requests are sent to the specified destination, but responses from\n                              that destination are ignored.\n\n\n                              This filter can be used multiple times within the same rule. Note that\n                              not all implementations will be able to support mirroring to multiple\n                              backends.\n\n\n                              Support: Extended\n                            properties:\n                              backendRef:\n                                description: |-\n                                  BackendRef references a resource where mirrored requests are sent.\n\n\n                                  Mirrored requests must be sent only to a single destination endpoint\n                                  within this BackendRef, irrespective of how many endpoints are present\n                                  within this BackendRef.\n\n\n                                  If the referent cannot be found, this BackendRef is invalid and must be\n                                  dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                  condition on the Route status is set to `status: False` and not configure\n                                  this backend in the underlying implementation.\n\n\n                                  If there is a cross-namespace reference to an *existing* object\n                                  that is not allowed by a ReferenceGrant, the controller must ensure the\n                                  \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                  with the \"RefNotPermitted\" reason and not configure this backend in the\n                                  underlying implementation.\n\n\n                                  In either error case, the Message of the `ResolvedRefs` Condition\n                                  should be used to provide more detail about the problem.\n\n\n                                  Support: Extended for Kubernetes Service\n\n\n                                  Support: Implementation-specific for any other resource\n                                properties:\n                                  group:\n                                    default: \"\"\n                                    description: |-\n                                      Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                      When unspecified or empty string, core API group is inferred.\n                                    maxLength: 253\n                                    pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                    type: string\n                                  kind:\n                                    default: Service\n                                    description: |-\n                                      Kind is the Kubernetes resource kind of the referent. For example\n                                      \"Service\".\n\n\n                                      Defaults to \"Service\" when not specified.\n\n\n                                      ExternalName services can refer to CNAME DNS records that may live\n                                      outside of the cluster and as such are difficult to reason about in\n                                      terms of conformance. They also may not be safe to forward to (see\n                                      CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                      support ExternalName Services.\n\n\n                                      Support: Core (Services with a type other than ExternalName)\n\n\n                                      Support: Implementation-specific (Services with type ExternalName)\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                    type: string\n                                  name:\n                                    description: Name is the name of the referent.\n                                    maxLength: 253\n                                    minLength: 1\n                                    type: string\n                                  namespace:\n                                    description: |-\n                                      Namespace is the namespace of the backend. When unspecified, the local\n                                      namespace is inferred.\n\n\n                                      Note that when a namespace different than the local namespace is specified,\n                                      a ReferenceGrant object is required in the referent namespace to allow that\n                                      namespace's owner to accept the reference. See the ReferenceGrant\n                                      documentation for details.\n\n\n                                      Support: Core\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                    type: string\n                                  port:\n                                    description: |-\n                                      Port specifies the destination port number to use for this resource.\n                                      Port is required when the referent is a Kubernetes Service. In this\n                                      case, the port number is the service port number, not the target port.\n                                      For other resources, destination port might be derived from the referent\n                                      resource or this field.\n                                    format: int32\n                                    maximum: 65535\n                                    minimum: 1\n                                    type: integer\n                                required:\n                                - name\n                                type: object\n                                x-kubernetes-validations:\n                                - message: Must have port for Service reference\n                                  rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                                    ? has(self.port) : true'\n                            required:\n                            - backendRef\n                            type: object\n                          requestRedirect:\n                            description: |-\n                              RequestRedirect defines a schema for a filter that responds to the\n                              request with an HTTP redirection.\n\n\n                              Support: Core\n                            properties:\n                              hostname:\n                                description: |-\n                                  Hostname is the hostname to be used in the value of the `Location`\n                                  header in the response.\n                                  When empty, the hostname in the `Host` header of the request is used.\n\n\n                                  Support: Core\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: |-\n                                  Path defines parameters used to modify the path of the incoming request.\n                                  The modified path is then used to construct the `Location` header. When\n                                  empty, the request path is used as-is.\n\n\n                                  Support: Extended\n                                properties:\n                                  replaceFullPath:\n                                    description: |-\n                                      ReplaceFullPath specifies the value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: |-\n                                      ReplacePrefixMatch specifies the value with which to replace the prefix\n                                      match of a request during a rewrite or redirect. For example, a request\n                                      to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                      of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                      Note that this matches the behavior of the PathPrefix match type. This\n                                      matches full path elements. A path element refers to the list of labels\n                                      in the path split by the `/` separator. When specified, a trailing `/` is\n                                      ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                      match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                      ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                      Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                      the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                      Request Path | Prefix Match | Replace Prefix | Modified Path\n                                      -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                      /foo         | /foo         | /xyz           | /xyz\n                                      /foo/        | /foo         | /xyz           | /xyz/\n                                      /foo/bar     | /foo         | <empty string> | /bar\n                                      /foo/        | /foo         | <empty string> | /\n                                      /foo         | /foo         | <empty string> | /\n                                      /foo/        | /foo         | /              | /\n                                      /foo         | /foo         | /              | /\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: |-\n                                      Type defines the type of path modifier. Additional types may be\n                                      added in a future release of the API.\n\n\n                                      Note that values may be added to this enum, implementations\n                                      must ensure that unknown values will not cause a crash.\n\n\n                                      Unknown values here must result in the implementation setting the\n                                      Accepted Condition for the Route to `status: False`, with a\n                                      Reason of `UnsupportedValue`.\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                                x-kubernetes-validations:\n                                - message: replaceFullPath must be specified when\n                                    type is set to 'ReplaceFullPath'\n                                  rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath)\n                                    : true'\n                                - message: type must be 'ReplaceFullPath' when replaceFullPath\n                                    is set\n                                  rule: 'has(self.replaceFullPath) ? self.type ==\n                                    ''ReplaceFullPath'' : true'\n                                - message: replacePrefixMatch must be specified when\n                                    type is set to 'ReplacePrefixMatch'\n                                  rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch)\n                                    : true'\n                                - message: type must be 'ReplacePrefixMatch' when\n                                    replacePrefixMatch is set\n                                  rule: 'has(self.replacePrefixMatch) ? self.type\n                                    == ''ReplacePrefixMatch'' : true'\n                              port:\n                                description: |-\n                                  Port is the port to be used in the value of the `Location`\n                                  header in the response.\n\n\n                                  If no port is specified, the redirect port MUST be derived using the\n                                  following rules:\n\n\n                                  * If redirect scheme is not-empty, the redirect port MUST be the well-known\n                                    port associated with the redirect scheme. Specifically \"http\" to port 80\n                                    and \"https\" to port 443. If the redirect scheme does not have a\n                                    well-known port, the listener port of the Gateway SHOULD be used.\n                                  * If redirect scheme is empty, the redirect port MUST be the Gateway\n                                    Listener port.\n\n\n                                  Implementations SHOULD NOT add the port number in the 'Location'\n                                  header in the following cases:\n\n\n                                  * A Location header that will use HTTP (whether that is determined via\n                                    the Listener protocol or the Scheme field) _and_ use port 80.\n                                  * A Location header that will use HTTPS (whether that is determined via\n                                    the Listener protocol or the Scheme field) _and_ use port 443.\n\n\n                                  Support: Extended\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: |-\n                                  Scheme is the scheme to be used in the value of the `Location` header in\n                                  the response. When empty, the scheme of the request is used.\n\n\n                                  Scheme redirects can affect the port of the redirect, for more information,\n                                  refer to the documentation for the port field of this filter.\n\n\n                                  Note that values may be added to this enum, implementations\n                                  must ensure that unknown values will not cause a crash.\n\n\n                                  Unknown values here must result in the implementation setting the\n                                  Accepted Condition for the Route to `status: False`, with a\n                                  Reason of `UnsupportedValue`.\n\n\n                                  Support: Extended\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: |-\n                                  StatusCode is the HTTP status code to be used in response.\n\n\n                                  Note that values may be added to this enum, implementations\n                                  must ensure that unknown values will not cause a crash.\n\n\n                                  Unknown values here must result in the implementation setting the\n                                  Accepted Condition for the Route to `status: False`, with a\n                                  Reason of `UnsupportedValue`.\n\n\n                                  Support: Core\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          responseHeaderModifier:\n                            description: |-\n                              ResponseHeaderModifier defines a schema for a filter that modifies response\n                              headers.\n\n\n                              Support: Extended\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          type:\n                            description: |-\n                              Type identifies the type of filter to apply. As with other API fields,\n                              types are classified into three conformance levels:\n\n\n                              - Core: Filter types and their corresponding configuration defined by\n                                \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                implementations must support core filters.\n\n\n                              - Extended: Filter types and their corresponding configuration defined by\n                                \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                are encouraged to support extended filters.\n\n\n                              - Implementation-specific: Filters that are defined and supported by\n                                specific vendors.\n                                In the future, filters showing convergence in behavior across multiple\n                                implementations will be considered for inclusion in extended or core\n                                conformance levels. Filter-specific configuration for such filters\n                                is specified using the ExtensionRef field. `Type` should be set to\n                                \"ExtensionRef\" for custom filters.\n\n\n                              Implementers are encouraged to define custom implementation types to\n                              extend the core API with implementation-specific behavior.\n\n\n                              If a reference to a custom filter type cannot be resolved, the filter\n                              MUST NOT be skipped. Instead, requests that would have been processed by\n                              that filter MUST receive a HTTP error response.\n\n\n                              Note that values may be added to this enum, implementations\n                              must ensure that unknown values will not cause a crash.\n\n\n                              Unknown values here must result in the implementation setting the\n                              Accepted Condition for the Route to `status: False`, with a\n                              Reason of `UnsupportedValue`.\n                            enum:\n                            - RequestHeaderModifier\n                            - ResponseHeaderModifier\n                            - RequestMirror\n                            - RequestRedirect\n                            - URLRewrite\n                            - ExtensionRef\n                            type: string\n                          urlRewrite:\n                            description: |-\n                              URLRewrite defines a schema for a filter that modifies a request during forwarding.\n\n\n                              Support: Extended\n                            properties:\n                              hostname:\n                                description: |-\n                                  Hostname is the value to be used to replace the Host header value during\n                                  forwarding.\n\n\n                                  Support: Extended\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: |-\n                                  Path defines a path rewrite.\n\n\n                                  Support: Extended\n                                properties:\n                                  replaceFullPath:\n                                    description: |-\n                                      ReplaceFullPath specifies the value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: |-\n                                      ReplacePrefixMatch specifies the value with which to replace the prefix\n                                      match of a request during a rewrite or redirect. For example, a request\n                                      to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                      of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                      Note that this matches the behavior of the PathPrefix match type. This\n                                      matches full path elements. A path element refers to the list of labels\n                                      in the path split by the `/` separator. When specified, a trailing `/` is\n                                      ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                      match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                      ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                      Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                      the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                      Request Path | Prefix Match | Replace Prefix | Modified Path\n                                      -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                      /foo         | /foo         | /xyz           | /xyz\n                                      /foo/        | /foo         | /xyz           | /xyz/\n                                      /foo/bar     | /foo         | <empty string> | /bar\n                                      /foo/        | /foo         | <empty string> | /\n                                      /foo         | /foo         | <empty string> | /\n                                      /foo/        | /foo         | /              | /\n                                      /foo         | /foo         | /              | /\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: |-\n                                      Type defines the type of path modifier. Additional types may be\n                                      added in a future release of the API.\n\n\n                                      Note that values may be added to this enum, implementations\n                                      must ensure that unknown values will not cause a crash.\n\n\n                                      Unknown values here must result in the implementation setting the\n                                      Accepted Condition for the Route to `status: False`, with a\n                                      Reason of `UnsupportedValue`.\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                                x-kubernetes-validations:\n                                - message: replaceFullPath must be specified when\n                                    type is set to 'ReplaceFullPath'\n                                  rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath)\n                                    : true'\n                                - message: type must be 'ReplaceFullPath' when replaceFullPath\n                                    is set\n                                  rule: 'has(self.replaceFullPath) ? self.type ==\n                                    ''ReplaceFullPath'' : true'\n                                - message: replacePrefixMatch must be specified when\n                                    type is set to 'ReplacePrefixMatch'\n                                  rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch)\n                                    : true'\n                                - message: type must be 'ReplacePrefixMatch' when\n                                    replacePrefixMatch is set\n                                  rule: 'has(self.replacePrefixMatch) ? self.type\n                                    == ''ReplacePrefixMatch'' : true'\n                            type: object\n                        required:\n                        - type\n                        type: object\n                        x-kubernetes-validations:\n                        - message: filter.requestHeaderModifier must be nil if the\n                            filter.type is not RequestHeaderModifier\n                          rule: '!(has(self.requestHeaderModifier) && self.type !=\n                            ''RequestHeaderModifier'')'\n                        - message: filter.requestHeaderModifier must be specified\n                            for RequestHeaderModifier filter.type\n                          rule: '!(!has(self.requestHeaderModifier) && self.type ==\n                            ''RequestHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be nil if the\n                            filter.type is not ResponseHeaderModifier\n                          rule: '!(has(self.responseHeaderModifier) && self.type !=\n                            ''ResponseHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be specified\n                            for ResponseHeaderModifier filter.type\n                          rule: '!(!has(self.responseHeaderModifier) && self.type\n                            == ''ResponseHeaderModifier'')'\n                        - message: filter.requestMirror must be nil if the filter.type\n                            is not RequestMirror\n                          rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                        - message: filter.requestMirror must be specified for RequestMirror\n                            filter.type\n                          rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')'\n                        - message: filter.requestRedirect must be nil if the filter.type\n                            is not RequestRedirect\n                          rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')'\n                        - message: filter.requestRedirect must be specified for RequestRedirect\n                            filter.type\n                          rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')'\n                        - message: filter.urlRewrite must be nil if the filter.type\n                            is not URLRewrite\n                          rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')'\n                        - message: filter.urlRewrite must be specified for URLRewrite\n                            filter.type\n                          rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')'\n                        - message: filter.extensionRef must be nil if the filter.type\n                            is not ExtensionRef\n                          rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                        - message: filter.extensionRef must be specified for ExtensionRef\n                            filter.type\n                          rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                      maxItems: 16\n                      type: array\n                      x-kubernetes-validations:\n                      - message: May specify either httpRouteFilterRequestRedirect\n                          or httpRouteFilterRequestRewrite, but not both\n                        rule: '!(self.exists(f, f.type == ''RequestRedirect'') &&\n                          self.exists(f, f.type == ''URLRewrite''))'\n                      - message: RequestHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                          <= 1\n                      - message: ResponseHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                          <= 1\n                      - message: RequestRedirect filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestRedirect').size() <=\n                          1\n                      - message: URLRewrite filter cannot be repeated\n                        rule: self.filter(f, f.type == 'URLRewrite').size() <= 1\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: |-\n                        Matches define conditions used for matching the rule against incoming\n                        HTTP requests. Each match is independent, i.e. this rule will be matched\n                        if **any** one of the matches is satisfied.\n\n\n                        For example, take the following matches configuration:\n\n\n                        ```\n                        matches:\n                        - path:\n                            value: \"/foo\"\n                          headers:\n                          - name: \"version\"\n                            value: \"v2\"\n                        - path:\n                            value: \"/v2/foo\"\n                        ```\n\n\n                        For a request to match against this rule, a request must satisfy\n                        EITHER of the two conditions:\n\n\n                        - path prefixed with `/foo` AND contains the header `version: v2`\n                        - path prefix of `/v2/foo`\n\n\n                        See the documentation for HTTPRouteMatch on how to specify multiple\n                        match conditions that should be ANDed together.\n\n\n                        If no matches are specified, the default is a prefix\n                        path match on \"/\", which has the effect of matching every\n                        HTTP request.\n\n\n                        Proxy or Load Balancer routing configuration generated from HTTPRoutes\n                        MUST prioritize matches based on the following criteria, continuing on\n                        ties. Across all rules specified on applicable Routes, precedence must be\n                        given to the match having:\n\n\n                        * \"Exact\" path match.\n                        * \"Prefix\" path match with largest number of characters.\n                        * Method match.\n                        * Largest number of header matches.\n                        * Largest number of query param matches.\n\n\n                        Note: The precedence of RegularExpression path matches are implementation-specific.\n\n\n                        If ties still exist across multiple Routes, matching precedence MUST be\n                        determined in order of the following criteria, continuing on ties:\n\n\n                        * The oldest Route based on creation timestamp.\n                        * The Route appearing first in alphabetical order by\n                          \"{namespace}/{name}\".\n\n\n                        If ties still exist within an HTTPRoute, matching precedence MUST be granted\n                        to the FIRST matching rule (in list order) with a match meeting the above\n                        criteria.\n\n\n                        When no rules matching a request have been successfully attached to the\n                        parent a request is coming from, a HTTP 404 status code MUST be returned.\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given\\naction. Multiple match types\n                          are ANDed together, i.e. the match will\\nevaluate to true\n                          only if all conditions are satisfied.\\n\\n\\nFor example,\n                          the match below will match a HTTP request only if its path\\nstarts\n                          with `/foo` AND it contains the `version: v1` header:\\n\\n\\n```\\nmatch:\\n\\n\\n\\tpath:\\n\\t\n                          \\ value: \\\"/foo\\\"\\n\\theaders:\\n\\t- name: \\\"version\\\"\\n\\t\n                          \\ value \\\"v1\\\"\\n\\n\\n```\"\n                        properties:\n                          headers:\n                            description: |-\n                              Headers specifies HTTP request header matchers. Multiple match values are\n                              ANDed together, meaning, a request must match all the specified headers\n                              to select the route.\n                            items:\n                              description: |-\n                                HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request\n                                headers.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                    case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                    If multiple entries specify equivalent header names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be ignored. Due to the\n                                    case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                    equivalent.\n\n\n                                    When a header is repeated in an HTTP request, it is\n                                    implementation-specific behavior as to how this is represented.\n                                    Generally, proxies should follow the guidance from the RFC:\n                                    https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding\n                                    processing a repeated header, with special handling for \"Set-Cookie\".\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: |-\n                                    Type specifies how to match against the value of the header.\n\n\n                                    Support: Core (Exact)\n\n\n                                    Support: Implementation-specific (RegularExpression)\n\n\n                                    Since RegularExpression HeaderMatchType has implementation-specific\n                                    conformance, implementations can support POSIX, PCRE or any other dialects\n                                    of regular expressions. Please read the implementation's documentation to\n                                    determine the supported dialect.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: |-\n                              Method specifies HTTP method matcher.\n                              When specified, this route will be matched only if the request has the\n                              specified method.\n\n\n                              Support: Extended\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: |-\n                              Path specifies a HTTP request path matcher. If this field is not\n                              specified, a default prefix match on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: |-\n                                  Type specifies how to match against the path Value.\n\n\n                                  Support: Core (Exact, PathPrefix)\n\n\n                                  Support: Implementation-specific (RegularExpression)\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                            x-kubernetes-validations:\n                            - message: value must be an absolute path and start with\n                                '/' when type one of ['Exact', 'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'')\n                                : true'\n                            - message: must not contain '//' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'')\n                                : true'\n                            - message: must not contain '/./' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'')\n                                : true'\n                            - message: must not contain '/../' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'')\n                                : true'\n                            - message: must not contain '%2f' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'')\n                                : true'\n                            - message: must not contain '%2F' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'')\n                                : true'\n                            - message: must not contain '#' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'')\n                                : true'\n                            - message: must not end with '/..' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'')\n                                : true'\n                            - message: must not end with '/.' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'')\n                                : true'\n                            - message: type must be one of ['Exact', 'PathPrefix',\n                                'RegularExpression']\n                              rule: self.type in ['Exact','PathPrefix'] || self.type\n                                == 'RegularExpression'\n                            - message: must only contain valid characters (matching\n                                ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$)\n                                for types ['Exact', 'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r\"\"\"^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$\"\"\")\n                                : true'\n                          queryParams:\n                            description: |-\n                              QueryParams specifies HTTP query parameter matchers. Multiple match\n                              values are ANDed together, meaning, a request must match all the\n                              specified query parameters to select the route.\n\n\n                              Support: Extended\n                            items:\n                              description: |-\n                                HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP\n                                query parameters.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the HTTP query param to be matched. This must be an\n                                    exact string match. (See\n                                    https://tools.ietf.org/html/rfc7230#section-2.7.3).\n\n\n                                    If multiple entries specify equivalent query param names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent query param name MUST be ignored.\n\n\n                                    If a query param is repeated in an HTTP request, the behavior is\n                                    purposely left undefined, since different data planes have different\n                                    capabilities. However, it is *recommended* that implementations should\n                                    match against the first value of the param if the data plane supports it,\n                                    as this behavior is expected in other load balancing contexts outside of\n                                    the Gateway API.\n\n\n                                    Users SHOULD NOT route traffic based on repeated query params to guard\n                                    themselves against potential differences in the implementations.\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: |-\n                                    Type specifies how to match against the value of the query parameter.\n\n\n                                    Support: Extended (Exact)\n\n\n                                    Support: Implementation-specific (RegularExpression)\n\n\n                                    Since RegularExpression QueryParamMatchType has Implementation-specific\n                                    conformance, implementations can support POSIX, PCRE or any other\n                                    dialects of regular expressions. Please read the implementation's\n                                    documentation to determine the supported dialect.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                    sessionPersistence:\n                      description: |+\n                        SessionPersistence defines and configures session persistence\n                        for the route rule.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        absoluteTimeout:\n                          description: |-\n                            AbsoluteTimeout defines the absolute timeout of the persistent\n                            session. Once the AbsoluteTimeout duration has elapsed, the\n                            session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        cookieConfig:\n                          description: |-\n                            CookieConfig provides configuration settings that are specific\n                            to cookie-based session persistence.\n\n\n                            Support: Core\n                          properties:\n                            lifetimeType:\n                              default: Session\n                              description: |-\n                                LifetimeType specifies whether the cookie has a permanent or\n                                session-based lifetime. A permanent cookie persists until its\n                                specified expiry time, defined by the Expires or Max-Age cookie\n                                attributes, while a session cookie is deleted when the current\n                                session ends.\n\n\n                                When set to \"Permanent\", AbsoluteTimeout indicates the\n                                cookie's lifetime via the Expires or Max-Age cookie attributes\n                                and is required.\n\n\n                                When set to \"Session\", AbsoluteTimeout indicates the\n                                absolute lifetime of the cookie tracked by the gateway and\n                                is optional.\n\n\n                                Support: Core for \"Session\" type\n\n\n                                Support: Extended for \"Permanent\" type\n                              enum:\n                              - Permanent\n                              - Session\n                              type: string\n                          type: object\n                        idleTimeout:\n                          description: |-\n                            IdleTimeout defines the idle timeout of the persistent session.\n                            Once the session has been idle for more than the specified\n                            IdleTimeout duration, the session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        sessionName:\n                          description: |-\n                            SessionName defines the name of the persistent session token\n                            which may be reflected in the cookie or the header. Users\n                            should avoid reusing session names to prevent unintended\n                            consequences, such as rejection or unpredictable behavior.\n\n\n                            Support: Implementation-specific\n                          maxLength: 128\n                          type: string\n                        type:\n                          default: Cookie\n                          description: |-\n                            Type defines the type of session persistence such as through\n                            the use a header or cookie. Defaults to cookie based session\n                            persistence.\n\n\n                            Support: Core for \"Cookie\" type\n\n\n                            Support: Extended for \"Header\" type\n                          enum:\n                          - Cookie\n                          - Header\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: AbsoluteTimeout must be specified when cookie lifetimeType\n                          is Permanent\n                        rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType)\n                          || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)'\n                    timeouts:\n                      description: |+\n                        Timeouts defines the timeouts that can be configured for an HTTP request.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        backendRequest:\n                          description: |-\n                            BackendRequest specifies a timeout for an individual request from the gateway\n                            to a backend. This covers the time from when the request first starts being\n                            sent from the gateway to when the full response has been received from the backend.\n\n\n                            Setting a timeout to the zero duration (e.g. \"0s\") SHOULD disable the timeout\n                            completely. Implementations that cannot completely disable the timeout MUST\n                            instead interpret the zero duration as the longest possible value to which\n                            the timeout can be set.\n\n\n                            An entire client HTTP transaction with a gateway, covered by the Request timeout,\n                            may result in more than one call from the gateway to the destination backend,\n                            for example, if automatic retries are supported.\n\n\n                            Because the Request timeout encompasses the BackendRequest timeout, the value of\n                            BackendRequest must be <= the value of Request timeout.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        request:\n                          description: |-\n                            Request specifies the maximum duration for a gateway to respond to an HTTP request.\n                            If the gateway has not been able to respond before this deadline is met, the gateway\n                            MUST return a timeout error.\n\n\n                            For example, setting the `rules.timeouts.request` field to the value `10s` in an\n                            `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds\n                            to complete.\n\n\n                            Setting a timeout to the zero duration (e.g. \"0s\") SHOULD disable the timeout\n                            completely. Implementations that cannot completely disable the timeout MUST\n                            instead interpret the zero duration as the longest possible value to which\n                            the timeout can be set.\n\n\n                            This timeout is intended to cover as close to the whole request-response transaction\n                            as possible although an implementation MAY choose to start the timeout after the entire\n                            request stream has been received instead of immediately after the transaction is\n                            initiated by the client.\n\n\n                            When this field is unspecified, request timeout behavior is implementation-specific.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: backendRequest timeout cannot be longer than request\n                          timeout\n                        rule: '!(has(self.request) && has(self.backendRequest) &&\n                          duration(self.request) != duration(''0s'') && duration(self.backendRequest)\n                          > duration(self.request))'\n                  type: object\n                  x-kubernetes-validations:\n                  - message: RequestRedirect filter must not be used together with\n                      backendRefs\n                    rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ?\n                      (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))):\n                      true'\n                  - message: When using RequestRedirect filter with path.replacePrefixMatch,\n                      exactly one PathPrefix match must be specified\n                    rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect)\n                      && has(f.requestRedirect.path) && f.requestRedirect.path.type\n                      == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch)))\n                      ? ((size(self.matches) != 1 || !has(self.matches[0].path) ||\n                      self.matches[0].path.type != ''PathPrefix'') ? false : true)\n                      : true'\n                  - message: When using URLRewrite filter with path.replacePrefixMatch,\n                      exactly one PathPrefix match must be specified\n                    rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite)\n                      && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch''\n                      && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches)\n                      != 1 || !has(self.matches[0].path) || self.matches[0].path.type\n                      != ''PathPrefix'') ? false : true) : true'\n                  - message: Within backendRefs, when using RequestRedirect filter\n                      with path.replacePrefixMatch, exactly one PathPrefix match must\n                      be specified\n                    rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b,\n                      (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect)\n                      && has(f.requestRedirect.path) && f.requestRedirect.path.type\n                      == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch)))\n                      )) ? ((size(self.matches) != 1 || !has(self.matches[0].path)\n                      || self.matches[0].path.type != ''PathPrefix'') ? false : true)\n                      : true'\n                  - message: Within backendRefs, When using URLRewrite filter with\n                      path.replacePrefixMatch, exactly one PathPrefix match must be\n                      specified\n                    rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b,\n                      (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite)\n                      && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch''\n                      && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches)\n                      != 1 || !has(self.matches[0].path) || self.matches[0].path.type\n                      != ''PathPrefix'') ? false : true) : true'\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: null\n  storedVersions: null\n\n\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997\n    gateway.networking.k8s.io/bundle-version: v1.1.1\n    gateway.networking.k8s.io/channel: experimental\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    helm.sh/resource-policy: keep\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\n  creationTimestamp: null\n  name: grpcroutes.gateway.networking.k8s.io\nspec:\n  group: gateway.networking.k8s.io\n  names:\n    categories:\n    - gateway-api\n    kind: GRPCRoute\n    listKind: GRPCRouteList\n    plural: grpcroutes\n    singular: grpcroute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1\n    schema:\n      openAPIV3Schema:\n        description: |-\n          GRPCRoute provides a way to route gRPC requests. This includes the capability\n          to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests will be routed.\n\n\n          GRPCRoute falls under extended support within the Gateway API. Within the\n          following specification, the word \"MUST\" indicates that an implementation\n          supporting GRPCRoute must conform to the indicated requirement, but an\n          implementation not supporting this route type need not follow the requirement\n          unless explicitly indicated.\n\n\n          Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST\n          accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via\n          ALPN. If the implementation does not support this, then it MUST set the\n          \"Accepted\" condition to \"False\" for the affected listener with a reason of\n          \"UnsupportedProtocol\".  Implementations MAY also accept HTTP/2 connections\n          with an upgrade from HTTP/1.\n\n\n          Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST\n          support HTTP/2 over cleartext TCP (h2c,\n          https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial\n          upgrade from HTTP/1.1, i.e. with prior knowledge\n          (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation\n          does not support this, then it MUST set the \"Accepted\" condition to \"False\"\n          for the affected listener with a reason of \"UnsupportedProtocol\".\n          Implementations MAY also accept HTTP/2 connections with an upgrade from\n          HTTP/1, i.e. without prior knowledge.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of GRPCRoute.\n            properties:\n              hostnames:\n                description: |-\n                  Hostnames defines a set of hostnames to match against the GRPC\n                  Host header to select a GRPCRoute to process the request. This matches\n                  the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                  1. IPs are not allowed.\n                  2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                     label MUST appear by itself as the first label.\n\n\n                  If a hostname is specified by both the Listener and GRPCRoute, there\n                  MUST be at least one intersecting hostname for the GRPCRoute to be\n                  attached to the Listener. For example:\n\n\n                  * A Listener with `test.example.com` as the hostname matches GRPCRoutes\n                    that have either not specified any hostnames, or have specified at\n                    least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches GRPCRoutes\n                    that have either not specified any hostnames or have specified at least\n                    one hostname that matches the Listener hostname. For example,\n                    `test.example.com` and `*.example.com` would both match. On the other\n                    hand, `example.com` and `test.example.net` would not match.\n\n\n                  Hostnames that are prefixed with a wildcard label (`*.`) are interpreted\n                  as a suffix match. That means that a match for `*.example.com` would match\n                  both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\n\n                  If both the Listener and GRPCRoute have specified hostnames, any\n                  GRPCRoute hostnames that do not match the Listener hostname MUST be\n                  ignored. For example, if a Listener specified `*.example.com`, and the\n                  GRPCRoute specified `test.example.com` and `test.example.net`,\n                  `test.example.net` MUST NOT be considered for a match.\n\n\n                  If both the Listener and GRPCRoute have specified hostnames, and none\n                  match with the criteria above, then the GRPCRoute MUST NOT be accepted by\n                  the implementation. The implementation MUST raise an 'Accepted' Condition\n                  with a status of `False` in the corresponding RouteParentStatus.\n\n\n                  If a Route (A) of type HTTPRoute or GRPCRoute is attached to a\n                  Listener and that listener already has another Route (B) of the other\n                  type attached and the intersection of the hostnames of A and B is\n                  non-empty, then the implementation MUST accept exactly one of these two\n                  routes, determined by the following criteria, in order:\n\n\n                  * The oldest Route based on creation timestamp.\n                  * The Route appearing first in alphabetical order by\n                    \"{namespace}/{name}\".\n\n\n                  The rejected Route MUST raise an 'Accepted' condition with a status of\n                  'False' in the corresponding RouteParentStatus.\n\n\n                  Support: Core\n                items:\n                  description: |-\n                    Hostname is the fully qualified domain name of a network host. This matches\n                    the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                     1. IPs are not allowed.\n                     2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                        label must appear by itself as the first label.\n\n\n                    Hostname can be \"precise\" which is a domain name without the terminating\n                    dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a\n                    domain name prefixed with a single wildcard label (e.g. `*.example.com`).\n\n\n                    Note that as per RFC1035 and RFC1123, a *label* must consist of lower case\n                    alphanumeric characters or '-', and must start and end with an alphanumeric\n                    character. No other punctuation is allowed.\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                description: Rules are a list of GRPC matchers, filters and actions.\n                items:\n                  description: |-\n                    GRPCRouteRule defines the semantics for matching a gRPC request based on\n                    conditions (matches), processing it (filters), and forwarding the request to\n                    an API object (backendRefs).\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent.\n\n\n                        Failure behavior here depends on how many BackendRefs are specified and\n                        how many are invalid.\n\n\n                        If *all* entries in BackendRefs are invalid, and there are also no filters\n                        specified in this route rule, *all* traffic which matches this rule MUST\n                        receive an `UNAVAILABLE` status.\n\n\n                        See the GRPCBackendRef definition for the rules about what makes a single\n                        GRPCBackendRef invalid.\n\n\n                        When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for\n                        requests that would have otherwise been routed to an invalid backend. If\n                        multiple backends are specified, and some are invalid, the proportion of\n                        requests that would otherwise have been routed to an invalid backend\n                        MUST receive an `UNAVAILABLE` status.\n\n\n                        For example, if two backends are specified with equal weights, and one is\n                        invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status.\n                        Implementations may choose how that 50 percent is determined.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Core\n                      items:\n                        description: |-\n                          GRPCBackendRef defines how a GRPCRoute forwards a gRPC request.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n                        properties:\n                          filters:\n                            description: |-\n                              Filters defined at this level MUST be executed if and only if the\n                              request is being forwarded to the backend defined here.\n\n\n                              Support: Implementation-specific (For broader support of filters, use the\n                              Filters field in GRPCRouteRule.)\n                            items:\n                              description: |-\n                                GRPCRouteFilter defines processing steps that must be completed during the\n                                request or response lifecycle. GRPCRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway implementations. Some\n                                examples include request or response modification, implementing\n                                authentication strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type of the filter.\n                              properties:\n                                extensionRef:\n                                  description: |-\n                                    ExtensionRef is an optional, implementation-specific extension to the\n                                    \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                                    \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                                    extended filters.\n\n\n                                    Support: Implementation-specific\n\n\n                                    This filter can be used multiple times within the same rule.\n                                  properties:\n                                    group:\n                                      description: |-\n                                        Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                        When unspecified or empty string, core API group is inferred.\n                                      maxLength: 253\n                                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    kind:\n                                      description: Kind is kind of the referent. For\n                                        example \"HTTPRoute\" or \"Service\".\n                                      maxLength: 63\n                                      minLength: 1\n                                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                      type: string\n                                    name:\n                                      description: Name is the name of the referent.\n                                      maxLength: 253\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - group\n                                  - kind\n                                  - name\n                                  type: object\n                                requestHeaderModifier:\n                                  description: |-\n                                    RequestHeaderModifier defines a schema for a filter that modifies request\n                                    headers.\n\n\n                                    Support: Core\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestMirror:\n                                  description: |-\n                                    RequestMirror defines a schema for a filter that mirrors requests.\n                                    Requests are sent to the specified destination, but responses from\n                                    that destination are ignored.\n\n\n                                    This filter can be used multiple times within the same rule. Note that\n                                    not all implementations will be able to support mirroring to multiple\n                                    backends.\n\n\n                                    Support: Extended\n                                  properties:\n                                    backendRef:\n                                      description: |-\n                                        BackendRef references a resource where mirrored requests are sent.\n\n\n                                        Mirrored requests must be sent only to a single destination endpoint\n                                        within this BackendRef, irrespective of how many endpoints are present\n                                        within this BackendRef.\n\n\n                                        If the referent cannot be found, this BackendRef is invalid and must be\n                                        dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                        condition on the Route status is set to `status: False` and not configure\n                                        this backend in the underlying implementation.\n\n\n                                        If there is a cross-namespace reference to an *existing* object\n                                        that is not allowed by a ReferenceGrant, the controller must ensure the\n                                        \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                        with the \"RefNotPermitted\" reason and not configure this backend in the\n                                        underlying implementation.\n\n\n                                        In either error case, the Message of the `ResolvedRefs` Condition\n                                        should be used to provide more detail about the problem.\n\n\n                                        Support: Extended for Kubernetes Service\n\n\n                                        Support: Implementation-specific for any other resource\n                                      properties:\n                                        group:\n                                          default: \"\"\n                                          description: |-\n                                            Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                            When unspecified or empty string, core API group is inferred.\n                                          maxLength: 253\n                                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                          type: string\n                                        kind:\n                                          default: Service\n                                          description: |-\n                                            Kind is the Kubernetes resource kind of the referent. For example\n                                            \"Service\".\n\n\n                                            Defaults to \"Service\" when not specified.\n\n\n                                            ExternalName services can refer to CNAME DNS records that may live\n                                            outside of the cluster and as such are difficult to reason about in\n                                            terms of conformance. They also may not be safe to forward to (see\n                                            CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                            support ExternalName Services.\n\n\n                                            Support: Core (Services with a type other than ExternalName)\n\n\n                                            Support: Implementation-specific (Services with type ExternalName)\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                          type: string\n                                        name:\n                                          description: Name is the name of the referent.\n                                          maxLength: 253\n                                          minLength: 1\n                                          type: string\n                                        namespace:\n                                          description: |-\n                                            Namespace is the namespace of the backend. When unspecified, the local\n                                            namespace is inferred.\n\n\n                                            Note that when a namespace different than the local namespace is specified,\n                                            a ReferenceGrant object is required in the referent namespace to allow that\n                                            namespace's owner to accept the reference. See the ReferenceGrant\n                                            documentation for details.\n\n\n                                            Support: Core\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                          type: string\n                                        port:\n                                          description: |-\n                                            Port specifies the destination port number to use for this resource.\n                                            Port is required when the referent is a Kubernetes Service. In this\n                                            case, the port number is the service port number, not the target port.\n                                            For other resources, destination port might be derived from the referent\n                                            resource or this field.\n                                          format: int32\n                                          maximum: 65535\n                                          minimum: 1\n                                          type: integer\n                                      required:\n                                      - name\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: Must have port for Service reference\n                                        rule: '(size(self.group) == 0 && self.kind\n                                          == ''Service'') ? has(self.port) : true'\n                                  required:\n                                  - backendRef\n                                  type: object\n                                responseHeaderModifier:\n                                  description: |-\n                                    ResponseHeaderModifier defines a schema for a filter that modifies response\n                                    headers.\n\n\n                                    Support: Extended\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: |+\n                                    Type identifies the type of filter to apply. As with other API fields,\n                                    types are classified into three conformance levels:\n\n\n                                    - Core: Filter types and their corresponding configuration defined by\n                                      \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                      implementations supporting GRPCRoute MUST support core filters.\n\n\n                                    - Extended: Filter types and their corresponding configuration defined by\n                                      \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                      are encouraged to support extended filters.\n\n\n                                    - Implementation-specific: Filters that are defined and supported by specific vendors.\n                                      In the future, filters showing convergence in behavior across multiple\n                                      implementations will be considered for inclusion in extended or core\n                                      conformance levels. Filter-specific configuration for such filters\n                                      is specified using the ExtensionRef field. `Type` MUST be set to\n                                      \"ExtensionRef\" for custom filters.\n\n\n                                    Implementers are encouraged to define custom implementation types to\n                                    extend the core API with implementation-specific behavior.\n\n\n                                    If a reference to a custom filter type cannot be resolved, the filter\n                                    MUST NOT be skipped. Instead, requests that would have been processed by\n                                    that filter MUST receive a HTTP error response.\n\n\n                                  enum:\n                                  - ResponseHeaderModifier\n                                  - RequestHeaderModifier\n                                  - RequestMirror\n                                  - ExtensionRef\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                              x-kubernetes-validations:\n                              - message: filter.requestHeaderModifier must be nil\n                                  if the filter.type is not RequestHeaderModifier\n                                rule: '!(has(self.requestHeaderModifier) && self.type\n                                  != ''RequestHeaderModifier'')'\n                              - message: filter.requestHeaderModifier must be specified\n                                  for RequestHeaderModifier filter.type\n                                rule: '!(!has(self.requestHeaderModifier) && self.type\n                                  == ''RequestHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be nil\n                                  if the filter.type is not ResponseHeaderModifier\n                                rule: '!(has(self.responseHeaderModifier) && self.type\n                                  != ''ResponseHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be specified\n                                  for ResponseHeaderModifier filter.type\n                                rule: '!(!has(self.responseHeaderModifier) && self.type\n                                  == ''ResponseHeaderModifier'')'\n                              - message: filter.requestMirror must be nil if the filter.type\n                                  is not RequestMirror\n                                rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                              - message: filter.requestMirror must be specified for\n                                  RequestMirror filter.type\n                                rule: '!(!has(self.requestMirror) && self.type ==\n                                  ''RequestMirror'')'\n                              - message: filter.extensionRef must be nil if the filter.type\n                                  is not ExtensionRef\n                                rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                              - message: filter.extensionRef must be specified for\n                                  ExtensionRef filter.type\n                                rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-validations:\n                            - message: RequestHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                                <= 1\n                            - message: ResponseHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                                <= 1\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: |-\n                        Filters define the filters that are applied to requests that match\n                        this rule.\n\n\n                        The effects of ordering of multiple behaviors are currently unspecified.\n                        This can change in the future based on feedback during the alpha stage.\n\n\n                        Conformance-levels at this level are defined based on the type of filter:\n\n\n                        - ALL core filters MUST be supported by all implementations that support\n                          GRPCRoute.\n                        - Implementers are encouraged to support extended filters.\n                        - Implementation-specific custom filters have no API guarantees across\n                          implementations.\n\n\n                        Specifying the same filter multiple times is not supported unless explicitly\n                        indicated in the filter.\n\n\n                        If an implementation can not support a combination of filters, it must clearly\n                        document that limitation. In cases where incompatible or unsupported\n                        filters are specified and cause the `Accepted` condition to be set to status\n                        `False`, implementations may use the `IncompatibleFilters` reason to specify\n                        this configuration error.\n\n\n                        Support: Core\n                      items:\n                        description: |-\n                          GRPCRouteFilter defines processing steps that must be completed during the\n                          request or response lifecycle. GRPCRouteFilters are meant as an extension\n                          point to express processing that may be done in Gateway implementations. Some\n                          examples include request or response modification, implementing\n                          authentication strategies, rate-limiting, and traffic shaping. API\n                          guarantee/conformance is defined based on the type of the filter.\n                        properties:\n                          extensionRef:\n                            description: |-\n                              ExtensionRef is an optional, implementation-specific extension to the\n                              \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                              \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                              extended filters.\n\n\n                              Support: Implementation-specific\n\n\n                              This filter can be used multiple times within the same rule.\n                            properties:\n                              group:\n                                description: |-\n                                  Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                  When unspecified or empty string, core API group is inferred.\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                description: Kind is kind of the referent. For example\n                                  \"HTTPRoute\" or \"Service\".\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: Name is the name of the referent.\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                            required:\n                            - group\n                            - kind\n                            - name\n                            type: object\n                          requestHeaderModifier:\n                            description: |-\n                              RequestHeaderModifier defines a schema for a filter that modifies request\n                              headers.\n\n\n                              Support: Core\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestMirror:\n                            description: |-\n                              RequestMirror defines a schema for a filter that mirrors requests.\n                              Requests are sent to the specified destination, but responses from\n                              that destination are ignored.\n\n\n                              This filter can be used multiple times within the same rule. Note that\n                              not all implementations will be able to support mirroring to multiple\n                              backends.\n\n\n                              Support: Extended\n                            properties:\n                              backendRef:\n                                description: |-\n                                  BackendRef references a resource where mirrored requests are sent.\n\n\n                                  Mirrored requests must be sent only to a single destination endpoint\n                                  within this BackendRef, irrespective of how many endpoints are present\n                                  within this BackendRef.\n\n\n                                  If the referent cannot be found, this BackendRef is invalid and must be\n                                  dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                  condition on the Route status is set to `status: False` and not configure\n                                  this backend in the underlying implementation.\n\n\n                                  If there is a cross-namespace reference to an *existing* object\n                                  that is not allowed by a ReferenceGrant, the controller must ensure the\n                                  \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                  with the \"RefNotPermitted\" reason and not configure this backend in the\n                                  underlying implementation.\n\n\n                                  In either error case, the Message of the `ResolvedRefs` Condition\n                                  should be used to provide more detail about the problem.\n\n\n                                  Support: Extended for Kubernetes Service\n\n\n                                  Support: Implementation-specific for any other resource\n                                properties:\n                                  group:\n                                    default: \"\"\n                                    description: |-\n                                      Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                      When unspecified or empty string, core API group is inferred.\n                                    maxLength: 253\n                                    pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                    type: string\n                                  kind:\n                                    default: Service\n                                    description: |-\n                                      Kind is the Kubernetes resource kind of the referent. For example\n                                      \"Service\".\n\n\n                                      Defaults to \"Service\" when not specified.\n\n\n                                      ExternalName services can refer to CNAME DNS records that may live\n                                      outside of the cluster and as such are difficult to reason about in\n                                      terms of conformance. They also may not be safe to forward to (see\n                                      CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                      support ExternalName Services.\n\n\n                                      Support: Core (Services with a type other than ExternalName)\n\n\n                                      Support: Implementation-specific (Services with type ExternalName)\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                    type: string\n                                  name:\n                                    description: Name is the name of the referent.\n                                    maxLength: 253\n                                    minLength: 1\n                                    type: string\n                                  namespace:\n                                    description: |-\n                                      Namespace is the namespace of the backend. When unspecified, the local\n                                      namespace is inferred.\n\n\n                                      Note that when a namespace different than the local namespace is specified,\n                                      a ReferenceGrant object is required in the referent namespace to allow that\n                                      namespace's owner to accept the reference. See the ReferenceGrant\n                                      documentation for details.\n\n\n                                      Support: Core\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                    type: string\n                                  port:\n                                    description: |-\n                                      Port specifies the destination port number to use for this resource.\n                                      Port is required when the referent is a Kubernetes Service. In this\n                                      case, the port number is the service port number, not the target port.\n                                      For other resources, destination port might be derived from the referent\n                                      resource or this field.\n                                    format: int32\n                                    maximum: 65535\n                                    minimum: 1\n                                    type: integer\n                                required:\n                                - name\n                                type: object\n                                x-kubernetes-validations:\n                                - message: Must have port for Service reference\n                                  rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                                    ? has(self.port) : true'\n                            required:\n                            - backendRef\n                            type: object\n                          responseHeaderModifier:\n                            description: |-\n                              ResponseHeaderModifier defines a schema for a filter that modifies response\n                              headers.\n\n\n                              Support: Extended\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          type:\n                            description: |+\n                              Type identifies the type of filter to apply. As with other API fields,\n                              types are classified into three conformance levels:\n\n\n                              - Core: Filter types and their corresponding configuration defined by\n                                \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                implementations supporting GRPCRoute MUST support core filters.\n\n\n                              - Extended: Filter types and their corresponding configuration defined by\n                                \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                are encouraged to support extended filters.\n\n\n                              - Implementation-specific: Filters that are defined and supported by specific vendors.\n                                In the future, filters showing convergence in behavior across multiple\n                                implementations will be considered for inclusion in extended or core\n                                conformance levels. Filter-specific configuration for such filters\n                                is specified using the ExtensionRef field. `Type` MUST be set to\n                                \"ExtensionRef\" for custom filters.\n\n\n                              Implementers are encouraged to define custom implementation types to\n                              extend the core API with implementation-specific behavior.\n\n\n                              If a reference to a custom filter type cannot be resolved, the filter\n                              MUST NOT be skipped. Instead, requests that would have been processed by\n                              that filter MUST receive a HTTP error response.\n\n\n                            enum:\n                            - ResponseHeaderModifier\n                            - RequestHeaderModifier\n                            - RequestMirror\n                            - ExtensionRef\n                            type: string\n                        required:\n                        - type\n                        type: object\n                        x-kubernetes-validations:\n                        - message: filter.requestHeaderModifier must be nil if the\n                            filter.type is not RequestHeaderModifier\n                          rule: '!(has(self.requestHeaderModifier) && self.type !=\n                            ''RequestHeaderModifier'')'\n                        - message: filter.requestHeaderModifier must be specified\n                            for RequestHeaderModifier filter.type\n                          rule: '!(!has(self.requestHeaderModifier) && self.type ==\n                            ''RequestHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be nil if the\n                            filter.type is not ResponseHeaderModifier\n                          rule: '!(has(self.responseHeaderModifier) && self.type !=\n                            ''ResponseHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be specified\n                            for ResponseHeaderModifier filter.type\n                          rule: '!(!has(self.responseHeaderModifier) && self.type\n                            == ''ResponseHeaderModifier'')'\n                        - message: filter.requestMirror must be nil if the filter.type\n                            is not RequestMirror\n                          rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                        - message: filter.requestMirror must be specified for RequestMirror\n                            filter.type\n                          rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')'\n                        - message: filter.extensionRef must be nil if the filter.type\n                            is not ExtensionRef\n                          rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                        - message: filter.extensionRef must be specified for ExtensionRef\n                            filter.type\n                          rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                      maxItems: 16\n                      type: array\n                      x-kubernetes-validations:\n                      - message: RequestHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                          <= 1\n                      - message: ResponseHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                          <= 1\n                    matches:\n                      description: |-\n                        Matches define conditions used for matching the rule against incoming\n                        gRPC requests. Each match is independent, i.e. this rule will be matched\n                        if **any** one of the matches is satisfied.\n\n\n                        For example, take the following matches configuration:\n\n\n                        ```\n                        matches:\n                        - method:\n                            service: foo.bar\n                          headers:\n                            values:\n                              version: 2\n                        - method:\n                            service: foo.bar.v2\n                        ```\n\n\n                        For a request to match against this rule, it MUST satisfy\n                        EITHER of the two conditions:\n\n\n                        - service of foo.bar AND contains the header `version: 2`\n                        - service of foo.bar.v2\n\n\n                        See the documentation for GRPCRouteMatch on how to specify multiple\n                        match conditions to be ANDed together.\n\n\n                        If no matches are specified, the implementation MUST match every gRPC request.\n\n\n                        Proxy or Load Balancer routing configuration generated from GRPCRoutes\n                        MUST prioritize rules based on the following criteria, continuing on\n                        ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes.\n                        Precedence MUST be given to the rule with the largest number of:\n\n\n                        * Characters in a matching non-wildcard hostname.\n                        * Characters in a matching hostname.\n                        * Characters in a matching service.\n                        * Characters in a matching method.\n                        * Header matches.\n\n\n                        If ties still exist across multiple Routes, matching precedence MUST be\n                        determined in order of the following criteria, continuing on ties:\n\n\n                        * The oldest Route based on creation timestamp.\n                        * The Route appearing first in alphabetical order by\n                          \"{namespace}/{name}\".\n\n\n                        If ties still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching rule meeting\n                        the above criteria.\n                      items:\n                        description: |-\n                          GRPCRouteMatch defines the predicate used to match requests to a given\n                          action. Multiple match types are ANDed together, i.e. the match will\n                          evaluate to true only if all conditions are satisfied.\n\n\n                          For example, the match below will match a gRPC request only if its service\n                          is `foo` AND it contains the `version: v1` header:\n\n\n                          ```\n                          matches:\n                            - method:\n                              type: Exact\n                              service: \"foo\"\n                              headers:\n                            - name: \"version\"\n                              value \"v1\"\n\n\n                          ```\n                        properties:\n                          headers:\n                            description: |-\n                              Headers specifies gRPC request header matchers. Multiple match values are\n                              ANDed together, meaning, a request MUST match all the specified headers\n                              to select the route.\n                            items:\n                              description: |-\n                                GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request\n                                headers.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the gRPC Header to be matched.\n\n\n                                    If multiple entries specify equivalent header names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be ignored. Due to the\n                                    case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                    equivalent.\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: Type specifies how to match against\n                                    the value of the header.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of the gRPC Header\n                                    to be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: |-\n                              Method specifies a gRPC request service/method matcher. If this field is\n                              not specified, all services and methods will match.\n                            properties:\n                              method:\n                                description: |-\n                                  Value of the method to match against. If left empty or omitted, will\n                                  match all services.\n\n\n                                  At least one of Service and Method MUST be a non-empty string.\n                                maxLength: 1024\n                                type: string\n                              service:\n                                description: |-\n                                  Value of the service to match against. If left empty or omitted, will\n                                  match any service.\n\n\n                                  At least one of Service and Method MUST be a non-empty string.\n                                maxLength: 1024\n                                type: string\n                              type:\n                                default: Exact\n                                description: |-\n                                  Type specifies how to match against the service and/or method.\n                                  Support: Core (Exact with service and method specified)\n\n\n                                  Support: Implementation-specific (Exact with method specified but no service specified)\n\n\n                                  Support: Implementation-specific (RegularExpression)\n                                enum:\n                                - Exact\n                                - RegularExpression\n                                type: string\n                            type: object\n                            x-kubernetes-validations:\n                            - message: One or both of 'service' or 'method' must be\n                                specified\n                              rule: 'has(self.type) ? has(self.service) || has(self.method)\n                                : true'\n                            - message: service must only contain valid characters\n                                (matching ^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$)\n                              rule: '(!has(self.type) || self.type == ''Exact'') &&\n                                has(self.service) ? self.service.matches(r\"\"\"^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$\"\"\"):\n                                true'\n                            - message: method must only contain valid characters (matching\n                                ^[A-Za-z_][A-Za-z_0-9]*$)\n                              rule: '(!has(self.type) || self.type == ''Exact'') &&\n                                has(self.method) ? self.method.matches(r\"\"\"^[A-Za-z_][A-Za-z_0-9]*$\"\"\"):\n                                true'\n                        type: object\n                      maxItems: 8\n                      type: array\n                    sessionPersistence:\n                      description: |+\n                        SessionPersistence defines and configures session persistence\n                        for the route rule.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        absoluteTimeout:\n                          description: |-\n                            AbsoluteTimeout defines the absolute timeout of the persistent\n                            session. Once the AbsoluteTimeout duration has elapsed, the\n                            session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        cookieConfig:\n                          description: |-\n                            CookieConfig provides configuration settings that are specific\n                            to cookie-based session persistence.\n\n\n                            Support: Core\n                          properties:\n                            lifetimeType:\n                              default: Session\n                              description: |-\n                                LifetimeType specifies whether the cookie has a permanent or\n                                session-based lifetime. A permanent cookie persists until its\n                                specified expiry time, defined by the Expires or Max-Age cookie\n                                attributes, while a session cookie is deleted when the current\n                                session ends.\n\n\n                                When set to \"Permanent\", AbsoluteTimeout indicates the\n                                cookie's lifetime via the Expires or Max-Age cookie attributes\n                                and is required.\n\n\n                                When set to \"Session\", AbsoluteTimeout indicates the\n                                absolute lifetime of the cookie tracked by the gateway and\n                                is optional.\n\n\n                                Support: Core for \"Session\" type\n\n\n                                Support: Extended for \"Permanent\" type\n                              enum:\n                              - Permanent\n                              - Session\n                              type: string\n                          type: object\n                        idleTimeout:\n                          description: |-\n                            IdleTimeout defines the idle timeout of the persistent session.\n                            Once the session has been idle for more than the specified\n                            IdleTimeout duration, the session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        sessionName:\n                          description: |-\n                            SessionName defines the name of the persistent session token\n                            which may be reflected in the cookie or the header. Users\n                            should avoid reusing session names to prevent unintended\n                            consequences, such as rejection or unpredictable behavior.\n\n\n                            Support: Implementation-specific\n                          maxLength: 128\n                          type: string\n                        type:\n                          default: Cookie\n                          description: |-\n                            Type defines the type of session persistence such as through\n                            the use a header or cookie. Defaults to cookie based session\n                            persistence.\n\n\n                            Support: Core for \"Cookie\" type\n\n\n                            Support: Extended for \"Header\" type\n                          enum:\n                          - Cookie\n                          - Header\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: AbsoluteTimeout must be specified when cookie lifetimeType\n                          is Permanent\n                        rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType)\n                          || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)'\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of GRPCRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    deprecated: true\n    deprecationWarning: The v1alpha2 version of GRPCRoute has been deprecated and\n      will be removed in a future release of the API. Please upgrade to v1.\n    name: v1alpha2\n    schema:\n      openAPIV3Schema:\n        description: |-\n          GRPCRoute provides a way to route gRPC requests. This includes the capability\n          to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests will be routed.\n\n\n          GRPCRoute falls under extended support within the Gateway API. Within the\n          following specification, the word \"MUST\" indicates that an implementation\n          supporting GRPCRoute must conform to the indicated requirement, but an\n          implementation not supporting this route type need not follow the requirement\n          unless explicitly indicated.\n\n\n          Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST\n          accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via\n          ALPN. If the implementation does not support this, then it MUST set the\n          \"Accepted\" condition to \"False\" for the affected listener with a reason of\n          \"UnsupportedProtocol\".  Implementations MAY also accept HTTP/2 connections\n          with an upgrade from HTTP/1.\n\n\n          Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST\n          support HTTP/2 over cleartext TCP (h2c,\n          https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial\n          upgrade from HTTP/1.1, i.e. with prior knowledge\n          (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation\n          does not support this, then it MUST set the \"Accepted\" condition to \"False\"\n          for the affected listener with a reason of \"UnsupportedProtocol\".\n          Implementations MAY also accept HTTP/2 connections with an upgrade from\n          HTTP/1, i.e. without prior knowledge.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of GRPCRoute.\n            properties:\n              hostnames:\n                description: |-\n                  Hostnames defines a set of hostnames to match against the GRPC\n                  Host header to select a GRPCRoute to process the request. This matches\n                  the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                  1. IPs are not allowed.\n                  2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                     label MUST appear by itself as the first label.\n\n\n                  If a hostname is specified by both the Listener and GRPCRoute, there\n                  MUST be at least one intersecting hostname for the GRPCRoute to be\n                  attached to the Listener. For example:\n\n\n                  * A Listener with `test.example.com` as the hostname matches GRPCRoutes\n                    that have either not specified any hostnames, or have specified at\n                    least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches GRPCRoutes\n                    that have either not specified any hostnames or have specified at least\n                    one hostname that matches the Listener hostname. For example,\n                    `test.example.com` and `*.example.com` would both match. On the other\n                    hand, `example.com` and `test.example.net` would not match.\n\n\n                  Hostnames that are prefixed with a wildcard label (`*.`) are interpreted\n                  as a suffix match. That means that a match for `*.example.com` would match\n                  both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\n\n                  If both the Listener and GRPCRoute have specified hostnames, any\n                  GRPCRoute hostnames that do not match the Listener hostname MUST be\n                  ignored. For example, if a Listener specified `*.example.com`, and the\n                  GRPCRoute specified `test.example.com` and `test.example.net`,\n                  `test.example.net` MUST NOT be considered for a match.\n\n\n                  If both the Listener and GRPCRoute have specified hostnames, and none\n                  match with the criteria above, then the GRPCRoute MUST NOT be accepted by\n                  the implementation. The implementation MUST raise an 'Accepted' Condition\n                  with a status of `False` in the corresponding RouteParentStatus.\n\n\n                  If a Route (A) of type HTTPRoute or GRPCRoute is attached to a\n                  Listener and that listener already has another Route (B) of the other\n                  type attached and the intersection of the hostnames of A and B is\n                  non-empty, then the implementation MUST accept exactly one of these two\n                  routes, determined by the following criteria, in order:\n\n\n                  * The oldest Route based on creation timestamp.\n                  * The Route appearing first in alphabetical order by\n                    \"{namespace}/{name}\".\n\n\n                  The rejected Route MUST raise an 'Accepted' condition with a status of\n                  'False' in the corresponding RouteParentStatus.\n\n\n                  Support: Core\n                items:\n                  description: |-\n                    Hostname is the fully qualified domain name of a network host. This matches\n                    the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                     1. IPs are not allowed.\n                     2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                        label must appear by itself as the first label.\n\n\n                    Hostname can be \"precise\" which is a domain name without the terminating\n                    dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a\n                    domain name prefixed with a single wildcard label (e.g. `*.example.com`).\n\n\n                    Note that as per RFC1035 and RFC1123, a *label* must consist of lower case\n                    alphanumeric characters or '-', and must start and end with an alphanumeric\n                    character. No other punctuation is allowed.\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                description: Rules are a list of GRPC matchers, filters and actions.\n                items:\n                  description: |-\n                    GRPCRouteRule defines the semantics for matching a gRPC request based on\n                    conditions (matches), processing it (filters), and forwarding the request to\n                    an API object (backendRefs).\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent.\n\n\n                        Failure behavior here depends on how many BackendRefs are specified and\n                        how many are invalid.\n\n\n                        If *all* entries in BackendRefs are invalid, and there are also no filters\n                        specified in this route rule, *all* traffic which matches this rule MUST\n                        receive an `UNAVAILABLE` status.\n\n\n                        See the GRPCBackendRef definition for the rules about what makes a single\n                        GRPCBackendRef invalid.\n\n\n                        When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for\n                        requests that would have otherwise been routed to an invalid backend. If\n                        multiple backends are specified, and some are invalid, the proportion of\n                        requests that would otherwise have been routed to an invalid backend\n                        MUST receive an `UNAVAILABLE` status.\n\n\n                        For example, if two backends are specified with equal weights, and one is\n                        invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status.\n                        Implementations may choose how that 50 percent is determined.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Core\n                      items:\n                        description: |-\n                          GRPCBackendRef defines how a GRPCRoute forwards a gRPC request.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n                        properties:\n                          filters:\n                            description: |-\n                              Filters defined at this level MUST be executed if and only if the\n                              request is being forwarded to the backend defined here.\n\n\n                              Support: Implementation-specific (For broader support of filters, use the\n                              Filters field in GRPCRouteRule.)\n                            items:\n                              description: |-\n                                GRPCRouteFilter defines processing steps that must be completed during the\n                                request or response lifecycle. GRPCRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway implementations. Some\n                                examples include request or response modification, implementing\n                                authentication strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type of the filter.\n                              properties:\n                                extensionRef:\n                                  description: |-\n                                    ExtensionRef is an optional, implementation-specific extension to the\n                                    \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                                    \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                                    extended filters.\n\n\n                                    Support: Implementation-specific\n\n\n                                    This filter can be used multiple times within the same rule.\n                                  properties:\n                                    group:\n                                      description: |-\n                                        Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                        When unspecified or empty string, core API group is inferred.\n                                      maxLength: 253\n                                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    kind:\n                                      description: Kind is kind of the referent. For\n                                        example \"HTTPRoute\" or \"Service\".\n                                      maxLength: 63\n                                      minLength: 1\n                                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                      type: string\n                                    name:\n                                      description: Name is the name of the referent.\n                                      maxLength: 253\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - group\n                                  - kind\n                                  - name\n                                  type: object\n                                requestHeaderModifier:\n                                  description: |-\n                                    RequestHeaderModifier defines a schema for a filter that modifies request\n                                    headers.\n\n\n                                    Support: Core\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestMirror:\n                                  description: |-\n                                    RequestMirror defines a schema for a filter that mirrors requests.\n                                    Requests are sent to the specified destination, but responses from\n                                    that destination are ignored.\n\n\n                                    This filter can be used multiple times within the same rule. Note that\n                                    not all implementations will be able to support mirroring to multiple\n                                    backends.\n\n\n                                    Support: Extended\n                                  properties:\n                                    backendRef:\n                                      description: |-\n                                        BackendRef references a resource where mirrored requests are sent.\n\n\n                                        Mirrored requests must be sent only to a single destination endpoint\n                                        within this BackendRef, irrespective of how many endpoints are present\n                                        within this BackendRef.\n\n\n                                        If the referent cannot be found, this BackendRef is invalid and must be\n                                        dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                        condition on the Route status is set to `status: False` and not configure\n                                        this backend in the underlying implementation.\n\n\n                                        If there is a cross-namespace reference to an *existing* object\n                                        that is not allowed by a ReferenceGrant, the controller must ensure the\n                                        \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                        with the \"RefNotPermitted\" reason and not configure this backend in the\n                                        underlying implementation.\n\n\n                                        In either error case, the Message of the `ResolvedRefs` Condition\n                                        should be used to provide more detail about the problem.\n\n\n                                        Support: Extended for Kubernetes Service\n\n\n                                        Support: Implementation-specific for any other resource\n                                      properties:\n                                        group:\n                                          default: \"\"\n                                          description: |-\n                                            Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                            When unspecified or empty string, core API group is inferred.\n                                          maxLength: 253\n                                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                          type: string\n                                        kind:\n                                          default: Service\n                                          description: |-\n                                            Kind is the Kubernetes resource kind of the referent. For example\n                                            \"Service\".\n\n\n                                            Defaults to \"Service\" when not specified.\n\n\n                                            ExternalName services can refer to CNAME DNS records that may live\n                                            outside of the cluster and as such are difficult to reason about in\n                                            terms of conformance. They also may not be safe to forward to (see\n                                            CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                            support ExternalName Services.\n\n\n                                            Support: Core (Services with a type other than ExternalName)\n\n\n                                            Support: Implementation-specific (Services with type ExternalName)\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                          type: string\n                                        name:\n                                          description: Name is the name of the referent.\n                                          maxLength: 253\n                                          minLength: 1\n                                          type: string\n                                        namespace:\n                                          description: |-\n                                            Namespace is the namespace of the backend. When unspecified, the local\n                                            namespace is inferred.\n\n\n                                            Note that when a namespace different than the local namespace is specified,\n                                            a ReferenceGrant object is required in the referent namespace to allow that\n                                            namespace's owner to accept the reference. See the ReferenceGrant\n                                            documentation for details.\n\n\n                                            Support: Core\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                          type: string\n                                        port:\n                                          description: |-\n                                            Port specifies the destination port number to use for this resource.\n                                            Port is required when the referent is a Kubernetes Service. In this\n                                            case, the port number is the service port number, not the target port.\n                                            For other resources, destination port might be derived from the referent\n                                            resource or this field.\n                                          format: int32\n                                          maximum: 65535\n                                          minimum: 1\n                                          type: integer\n                                      required:\n                                      - name\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: Must have port for Service reference\n                                        rule: '(size(self.group) == 0 && self.kind\n                                          == ''Service'') ? has(self.port) : true'\n                                  required:\n                                  - backendRef\n                                  type: object\n                                responseHeaderModifier:\n                                  description: |-\n                                    ResponseHeaderModifier defines a schema for a filter that modifies response\n                                    headers.\n\n\n                                    Support: Extended\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: |+\n                                    Type identifies the type of filter to apply. As with other API fields,\n                                    types are classified into three conformance levels:\n\n\n                                    - Core: Filter types and their corresponding configuration defined by\n                                      \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                      implementations supporting GRPCRoute MUST support core filters.\n\n\n                                    - Extended: Filter types and their corresponding configuration defined by\n                                      \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                      are encouraged to support extended filters.\n\n\n                                    - Implementation-specific: Filters that are defined and supported by specific vendors.\n                                      In the future, filters showing convergence in behavior across multiple\n                                      implementations will be considered for inclusion in extended or core\n                                      conformance levels. Filter-specific configuration for such filters\n                                      is specified using the ExtensionRef field. `Type` MUST be set to\n                                      \"ExtensionRef\" for custom filters.\n\n\n                                    Implementers are encouraged to define custom implementation types to\n                                    extend the core API with implementation-specific behavior.\n\n\n                                    If a reference to a custom filter type cannot be resolved, the filter\n                                    MUST NOT be skipped. Instead, requests that would have been processed by\n                                    that filter MUST receive a HTTP error response.\n\n\n                                  enum:\n                                  - ResponseHeaderModifier\n                                  - RequestHeaderModifier\n                                  - RequestMirror\n                                  - ExtensionRef\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                              x-kubernetes-validations:\n                              - message: filter.requestHeaderModifier must be nil\n                                  if the filter.type is not RequestHeaderModifier\n                                rule: '!(has(self.requestHeaderModifier) && self.type\n                                  != ''RequestHeaderModifier'')'\n                              - message: filter.requestHeaderModifier must be specified\n                                  for RequestHeaderModifier filter.type\n                                rule: '!(!has(self.requestHeaderModifier) && self.type\n                                  == ''RequestHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be nil\n                                  if the filter.type is not ResponseHeaderModifier\n                                rule: '!(has(self.responseHeaderModifier) && self.type\n                                  != ''ResponseHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be specified\n                                  for ResponseHeaderModifier filter.type\n                                rule: '!(!has(self.responseHeaderModifier) && self.type\n                                  == ''ResponseHeaderModifier'')'\n                              - message: filter.requestMirror must be nil if the filter.type\n                                  is not RequestMirror\n                                rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                              - message: filter.requestMirror must be specified for\n                                  RequestMirror filter.type\n                                rule: '!(!has(self.requestMirror) && self.type ==\n                                  ''RequestMirror'')'\n                              - message: filter.extensionRef must be nil if the filter.type\n                                  is not ExtensionRef\n                                rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                              - message: filter.extensionRef must be specified for\n                                  ExtensionRef filter.type\n                                rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-validations:\n                            - message: RequestHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                                <= 1\n                            - message: ResponseHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                                <= 1\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: |-\n                        Filters define the filters that are applied to requests that match\n                        this rule.\n\n\n                        The effects of ordering of multiple behaviors are currently unspecified.\n                        This can change in the future based on feedback during the alpha stage.\n\n\n                        Conformance-levels at this level are defined based on the type of filter:\n\n\n                        - ALL core filters MUST be supported by all implementations that support\n                          GRPCRoute.\n                        - Implementers are encouraged to support extended filters.\n                        - Implementation-specific custom filters have no API guarantees across\n                          implementations.\n\n\n                        Specifying the same filter multiple times is not supported unless explicitly\n                        indicated in the filter.\n\n\n                        If an implementation can not support a combination of filters, it must clearly\n                        document that limitation. In cases where incompatible or unsupported\n                        filters are specified and cause the `Accepted` condition to be set to status\n                        `False`, implementations may use the `IncompatibleFilters` reason to specify\n                        this configuration error.\n\n\n                        Support: Core\n                      items:\n                        description: |-\n                          GRPCRouteFilter defines processing steps that must be completed during the\n                          request or response lifecycle. GRPCRouteFilters are meant as an extension\n                          point to express processing that may be done in Gateway implementations. Some\n                          examples include request or response modification, implementing\n                          authentication strategies, rate-limiting, and traffic shaping. API\n                          guarantee/conformance is defined based on the type of the filter.\n                        properties:\n                          extensionRef:\n                            description: |-\n                              ExtensionRef is an optional, implementation-specific extension to the\n                              \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                              \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                              extended filters.\n\n\n                              Support: Implementation-specific\n\n\n                              This filter can be used multiple times within the same rule.\n                            properties:\n                              group:\n                                description: |-\n                                  Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                  When unspecified or empty string, core API group is inferred.\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                description: Kind is kind of the referent. For example\n                                  \"HTTPRoute\" or \"Service\".\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: Name is the name of the referent.\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                            required:\n                            - group\n                            - kind\n                            - name\n                            type: object\n                          requestHeaderModifier:\n                            description: |-\n                              RequestHeaderModifier defines a schema for a filter that modifies request\n                              headers.\n\n\n                              Support: Core\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestMirror:\n                            description: |-\n                              RequestMirror defines a schema for a filter that mirrors requests.\n                              Requests are sent to the specified destination, but responses from\n                              that destination are ignored.\n\n\n                              This filter can be used multiple times within the same rule. Note that\n                              not all implementations will be able to support mirroring to multiple\n                              backends.\n\n\n                              Support: Extended\n                            properties:\n                              backendRef:\n                                description: |-\n                                  BackendRef references a resource where mirrored requests are sent.\n\n\n                                  Mirrored requests must be sent only to a single destination endpoint\n                                  within this BackendRef, irrespective of how many endpoints are present\n                                  within this BackendRef.\n\n\n                                  If the referent cannot be found, this BackendRef is invalid and must be\n                                  dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                  condition on the Route status is set to `status: False` and not configure\n                                  this backend in the underlying implementation.\n\n\n                                  If there is a cross-namespace reference to an *existing* object\n                                  that is not allowed by a ReferenceGrant, the controller must ensure the\n                                  \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                  with the \"RefNotPermitted\" reason and not configure this backend in the\n                                  underlying implementation.\n\n\n                                  In either error case, the Message of the `ResolvedRefs` Condition\n                                  should be used to provide more detail about the problem.\n\n\n                                  Support: Extended for Kubernetes Service\n\n\n                                  Support: Implementation-specific for any other resource\n                                properties:\n                                  group:\n                                    default: \"\"\n                                    description: |-\n                                      Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                      When unspecified or empty string, core API group is inferred.\n                                    maxLength: 253\n                                    pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                    type: string\n                                  kind:\n                                    default: Service\n                                    description: |-\n                                      Kind is the Kubernetes resource kind of the referent. For example\n                                      \"Service\".\n\n\n                                      Defaults to \"Service\" when not specified.\n\n\n                                      ExternalName services can refer to CNAME DNS records that may live\n                                      outside of the cluster and as such are difficult to reason about in\n                                      terms of conformance. They also may not be safe to forward to (see\n                                      CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                      support ExternalName Services.\n\n\n                                      Support: Core (Services with a type other than ExternalName)\n\n\n                                      Support: Implementation-specific (Services with type ExternalName)\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                    type: string\n                                  name:\n                                    description: Name is the name of the referent.\n                                    maxLength: 253\n                                    minLength: 1\n                                    type: string\n                                  namespace:\n                                    description: |-\n                                      Namespace is the namespace of the backend. When unspecified, the local\n                                      namespace is inferred.\n\n\n                                      Note that when a namespace different than the local namespace is specified,\n                                      a ReferenceGrant object is required in the referent namespace to allow that\n                                      namespace's owner to accept the reference. See the ReferenceGrant\n                                      documentation for details.\n\n\n                                      Support: Core\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                    type: string\n                                  port:\n                                    description: |-\n                                      Port specifies the destination port number to use for this resource.\n                                      Port is required when the referent is a Kubernetes Service. In this\n                                      case, the port number is the service port number, not the target port.\n                                      For other resources, destination port might be derived from the referent\n                                      resource or this field.\n                                    format: int32\n                                    maximum: 65535\n                                    minimum: 1\n                                    type: integer\n                                required:\n                                - name\n                                type: object\n                                x-kubernetes-validations:\n                                - message: Must have port for Service reference\n                                  rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                                    ? has(self.port) : true'\n                            required:\n                            - backendRef\n                            type: object\n                          responseHeaderModifier:\n                            description: |-\n                              ResponseHeaderModifier defines a schema for a filter that modifies response\n                              headers.\n\n\n                              Support: Extended\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          type:\n                            description: |+\n                              Type identifies the type of filter to apply. As with other API fields,\n                              types are classified into three conformance levels:\n\n\n                              - Core: Filter types and their corresponding configuration defined by\n                                \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                implementations supporting GRPCRoute MUST support core filters.\n\n\n                              - Extended: Filter types and their corresponding configuration defined by\n                                \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                are encouraged to support extended filters.\n\n\n                              - Implementation-specific: Filters that are defined and supported by specific vendors.\n                                In the future, filters showing convergence in behavior across multiple\n                                implementations will be considered for inclusion in extended or core\n                                conformance levels. Filter-specific configuration for such filters\n                                is specified using the ExtensionRef field. `Type` MUST be set to\n                                \"ExtensionRef\" for custom filters.\n\n\n                              Implementers are encouraged to define custom implementation types to\n                              extend the core API with implementation-specific behavior.\n\n\n                              If a reference to a custom filter type cannot be resolved, the filter\n                              MUST NOT be skipped. Instead, requests that would have been processed by\n                              that filter MUST receive a HTTP error response.\n\n\n                            enum:\n                            - ResponseHeaderModifier\n                            - RequestHeaderModifier\n                            - RequestMirror\n                            - ExtensionRef\n                            type: string\n                        required:\n                        - type\n                        type: object\n                        x-kubernetes-validations:\n                        - message: filter.requestHeaderModifier must be nil if the\n                            filter.type is not RequestHeaderModifier\n                          rule: '!(has(self.requestHeaderModifier) && self.type !=\n                            ''RequestHeaderModifier'')'\n                        - message: filter.requestHeaderModifier must be specified\n                            for RequestHeaderModifier filter.type\n                          rule: '!(!has(self.requestHeaderModifier) && self.type ==\n                            ''RequestHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be nil if the\n                            filter.type is not ResponseHeaderModifier\n                          rule: '!(has(self.responseHeaderModifier) && self.type !=\n                            ''ResponseHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be specified\n                            for ResponseHeaderModifier filter.type\n                          rule: '!(!has(self.responseHeaderModifier) && self.type\n                            == ''ResponseHeaderModifier'')'\n                        - message: filter.requestMirror must be nil if the filter.type\n                            is not RequestMirror\n                          rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                        - message: filter.requestMirror must be specified for RequestMirror\n                            filter.type\n                          rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')'\n                        - message: filter.extensionRef must be nil if the filter.type\n                            is not ExtensionRef\n                          rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                        - message: filter.extensionRef must be specified for ExtensionRef\n                            filter.type\n                          rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                      maxItems: 16\n                      type: array\n                      x-kubernetes-validations:\n                      - message: RequestHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                          <= 1\n                      - message: ResponseHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                          <= 1\n                    matches:\n                      description: |-\n                        Matches define conditions used for matching the rule against incoming\n                        gRPC requests. Each match is independent, i.e. this rule will be matched\n                        if **any** one of the matches is satisfied.\n\n\n                        For example, take the following matches configuration:\n\n\n                        ```\n                        matches:\n                        - method:\n                            service: foo.bar\n                          headers:\n                            values:\n                              version: 2\n                        - method:\n                            service: foo.bar.v2\n                        ```\n\n\n                        For a request to match against this rule, it MUST satisfy\n                        EITHER of the two conditions:\n\n\n                        - service of foo.bar AND contains the header `version: 2`\n                        - service of foo.bar.v2\n\n\n                        See the documentation for GRPCRouteMatch on how to specify multiple\n                        match conditions to be ANDed together.\n\n\n                        If no matches are specified, the implementation MUST match every gRPC request.\n\n\n                        Proxy or Load Balancer routing configuration generated from GRPCRoutes\n                        MUST prioritize rules based on the following criteria, continuing on\n                        ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes.\n                        Precedence MUST be given to the rule with the largest number of:\n\n\n                        * Characters in a matching non-wildcard hostname.\n                        * Characters in a matching hostname.\n                        * Characters in a matching service.\n                        * Characters in a matching method.\n                        * Header matches.\n\n\n                        If ties still exist across multiple Routes, matching precedence MUST be\n                        determined in order of the following criteria, continuing on ties:\n\n\n                        * The oldest Route based on creation timestamp.\n                        * The Route appearing first in alphabetical order by\n                          \"{namespace}/{name}\".\n\n\n                        If ties still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching rule meeting\n                        the above criteria.\n                      items:\n                        description: |-\n                          GRPCRouteMatch defines the predicate used to match requests to a given\n                          action. Multiple match types are ANDed together, i.e. the match will\n                          evaluate to true only if all conditions are satisfied.\n\n\n                          For example, the match below will match a gRPC request only if its service\n                          is `foo` AND it contains the `version: v1` header:\n\n\n                          ```\n                          matches:\n                            - method:\n                              type: Exact\n                              service: \"foo\"\n                              headers:\n                            - name: \"version\"\n                              value \"v1\"\n\n\n                          ```\n                        properties:\n                          headers:\n                            description: |-\n                              Headers specifies gRPC request header matchers. Multiple match values are\n                              ANDed together, meaning, a request MUST match all the specified headers\n                              to select the route.\n                            items:\n                              description: |-\n                                GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request\n                                headers.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the gRPC Header to be matched.\n\n\n                                    If multiple entries specify equivalent header names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be ignored. Due to the\n                                    case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                    equivalent.\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: Type specifies how to match against\n                                    the value of the header.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of the gRPC Header\n                                    to be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: |-\n                              Method specifies a gRPC request service/method matcher. If this field is\n                              not specified, all services and methods will match.\n                            properties:\n                              method:\n                                description: |-\n                                  Value of the method to match against. If left empty or omitted, will\n                                  match all services.\n\n\n                                  At least one of Service and Method MUST be a non-empty string.\n                                maxLength: 1024\n                                type: string\n                              service:\n                                description: |-\n                                  Value of the service to match against. If left empty or omitted, will\n                                  match any service.\n\n\n                                  At least one of Service and Method MUST be a non-empty string.\n                                maxLength: 1024\n                                type: string\n                              type:\n                                default: Exact\n                                description: |-\n                                  Type specifies how to match against the service and/or method.\n                                  Support: Core (Exact with service and method specified)\n\n\n                                  Support: Implementation-specific (Exact with method specified but no service specified)\n\n\n                                  Support: Implementation-specific (RegularExpression)\n                                enum:\n                                - Exact\n                                - RegularExpression\n                                type: string\n                            type: object\n                            x-kubernetes-validations:\n                            - message: One or both of 'service' or 'method' must be\n                                specified\n                              rule: 'has(self.type) ? has(self.service) || has(self.method)\n                                : true'\n                            - message: service must only contain valid characters\n                                (matching ^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$)\n                              rule: '(!has(self.type) || self.type == ''Exact'') &&\n                                has(self.service) ? self.service.matches(r\"\"\"^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$\"\"\"):\n                                true'\n                            - message: method must only contain valid characters (matching\n                                ^[A-Za-z_][A-Za-z_0-9]*$)\n                              rule: '(!has(self.type) || self.type == ''Exact'') &&\n                                has(self.method) ? self.method.matches(r\"\"\"^[A-Za-z_][A-Za-z_0-9]*$\"\"\"):\n                                true'\n                        type: object\n                      maxItems: 8\n                      type: array\n                    sessionPersistence:\n                      description: |+\n                        SessionPersistence defines and configures session persistence\n                        for the route rule.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        absoluteTimeout:\n                          description: |-\n                            AbsoluteTimeout defines the absolute timeout of the persistent\n                            session. Once the AbsoluteTimeout duration has elapsed, the\n                            session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        cookieConfig:\n                          description: |-\n                            CookieConfig provides configuration settings that are specific\n                            to cookie-based session persistence.\n\n\n                            Support: Core\n                          properties:\n                            lifetimeType:\n                              default: Session\n                              description: |-\n                                LifetimeType specifies whether the cookie has a permanent or\n                                session-based lifetime. A permanent cookie persists until its\n                                specified expiry time, defined by the Expires or Max-Age cookie\n                                attributes, while a session cookie is deleted when the current\n                                session ends.\n\n\n                                When set to \"Permanent\", AbsoluteTimeout indicates the\n                                cookie's lifetime via the Expires or Max-Age cookie attributes\n                                and is required.\n\n\n                                When set to \"Session\", AbsoluteTimeout indicates the\n                                absolute lifetime of the cookie tracked by the gateway and\n                                is optional.\n\n\n                                Support: Core for \"Session\" type\n\n\n                                Support: Extended for \"Permanent\" type\n                              enum:\n                              - Permanent\n                              - Session\n                              type: string\n                          type: object\n                        idleTimeout:\n                          description: |-\n                            IdleTimeout defines the idle timeout of the persistent session.\n                            Once the session has been idle for more than the specified\n                            IdleTimeout duration, the session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        sessionName:\n                          description: |-\n                            SessionName defines the name of the persistent session token\n                            which may be reflected in the cookie or the header. Users\n                            should avoid reusing session names to prevent unintended\n                            consequences, such as rejection or unpredictable behavior.\n\n\n                            Support: Implementation-specific\n                          maxLength: 128\n                          type: string\n                        type:\n                          default: Cookie\n                          description: |-\n                            Type defines the type of session persistence such as through\n                            the use a header or cookie. Defaults to cookie based session\n                            persistence.\n\n\n                            Support: Core for \"Cookie\" type\n\n\n                            Support: Extended for \"Header\" type\n                          enum:\n                          - Cookie\n                          - Header\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: AbsoluteTimeout must be specified when cookie lifetimeType\n                          is Permanent\n                        rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType)\n                          || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)'\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of GRPCRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: null\n  storedVersions: null\n\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997\n    gateway.networking.k8s.io/bundle-version: v1.1.1\n    gateway.networking.k8s.io/channel: experimental\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    helm.sh/resource-policy: keep\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\n  creationTimestamp: null\n  name: tlsroutes.gateway.networking.k8s.io\nspec:\n  group: gateway.networking.k8s.io\n  names:\n    categories:\n    - gateway-api\n    kind: TLSRoute\n    listKind: TLSRouteList\n    plural: tlsroutes\n    singular: tlsroute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1alpha2\n    schema:\n      openAPIV3Schema:\n        description: |-\n          The TLSRoute resource is similar to TCPRoute, but can be configured\n          to match against TLS-specific metadata. This allows more flexibility\n          in matching streams for a given TLS listener.\n\n\n          If you need to forward traffic to a single target for a TLS listener, you\n          could choose to use a TCPRoute with a TLS listener.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of TLSRoute.\n            properties:\n              hostnames:\n                description: |-\n                  Hostnames defines a set of SNI names that should match against the\n                  SNI attribute of TLS ClientHello message in TLS handshake. This matches\n                  the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                  1. IPs are not allowed in SNI names per RFC 6066.\n                  2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                     label must appear by itself as the first label.\n\n\n                  If a hostname is specified by both the Listener and TLSRoute, there\n                  must be at least one intersecting hostname for the TLSRoute to be\n                  attached to the Listener. For example:\n\n\n                  * A Listener with `test.example.com` as the hostname matches TLSRoutes\n                    that have either not specified any hostnames, or have specified at\n                    least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches TLSRoutes\n                    that have either not specified any hostnames or have specified at least\n                    one hostname that matches the Listener hostname. For example,\n                    `test.example.com` and `*.example.com` would both match. On the other\n                    hand, `example.com` and `test.example.net` would not match.\n\n\n                  If both the Listener and TLSRoute have specified hostnames, any\n                  TLSRoute hostnames that do not match the Listener hostname MUST be\n                  ignored. For example, if a Listener specified `*.example.com`, and the\n                  TLSRoute specified `test.example.com` and `test.example.net`,\n                  `test.example.net` must not be considered for a match.\n\n\n                  If both the Listener and TLSRoute have specified hostnames, and none\n                  match with the criteria above, then the TLSRoute is not accepted. The\n                  implementation must raise an 'Accepted' Condition with a status of\n                  `False` in the corresponding RouteParentStatus.\n\n\n                  Support: Core\n                items:\n                  description: |-\n                    Hostname is the fully qualified domain name of a network host. This matches\n                    the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                     1. IPs are not allowed.\n                     2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                        label must appear by itself as the first label.\n\n\n                    Hostname can be \"precise\" which is a domain name without the terminating\n                    dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a\n                    domain name prefixed with a single wildcard label (e.g. `*.example.com`).\n\n\n                    Note that as per RFC1035 and RFC1123, a *label* must consist of lower case\n                    alphanumeric characters or '-', and must start and end with an alphanumeric\n                    character. No other punctuation is allowed.\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                description: Rules are a list of TLS matchers and actions.\n                items:\n                  description: TLSRouteRule is the configuration for a given rule.\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent. If unspecified or invalid (refers to a non-existent resource or\n                        a Service with no endpoints), the rule performs no forwarding; if no\n                        filters are specified that would result in a response being sent, the\n                        underlying implementation must actively reject request attempts to this\n                        backend, by rejecting the connection or returning a 500 status code.\n                        Request rejections must respect weight; if an invalid backend is\n                        requested to have 80% of requests, then 80% of requests must be rejected\n                        instead.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Extended for Kubernetes ServiceImport\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Extended\n                      items:\n                        description: |-\n                          BackendRef defines how a Route should forward a request to a Kubernetes\n                          resource.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n\n\n                          Note that when the BackendTLSPolicy object is enabled by the implementation,\n                          there are some extra rules about validity to consider here. See the fields\n                          where this struct is used for more information about the exact behavior.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      minItems: 1\n                      type: array\n                  type: object\n                maxItems: 16\n                minItems: 1\n                type: array\n            required:\n            - rules\n            type: object\n          status:\n            description: Status defines the current state of TLSRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: null\n  storedVersions: null\n\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997\n    gateway.networking.k8s.io/bundle-version: v1.1.1\n    gateway.networking.k8s.io/channel: experimental\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    helm.sh/resource-policy: keep\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\n  creationTimestamp: null\n  name: tcproutes.gateway.networking.k8s.io\nspec:\n  group: gateway.networking.k8s.io\n  names:\n    categories:\n    - gateway-api\n    kind: TCPRoute\n    listKind: TCPRouteList\n    plural: tcproutes\n    singular: tcproute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1alpha2\n    schema:\n      openAPIV3Schema:\n        description: |-\n          TCPRoute provides a way to route TCP requests. When combined with a Gateway\n          listener, it can be used to forward connections on the port specified by the\n          listener to a set of backends specified by the TCPRoute.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of TCPRoute.\n            properties:\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                description: Rules are a list of TCP matchers and actions.\n                items:\n                  description: TCPRouteRule is the configuration for a given rule.\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent. If unspecified or invalid (refers to a non-existent resource or a\n                        Service with no endpoints), the underlying implementation MUST actively\n                        reject connection attempts to this backend. Connection rejections must\n                        respect weight; if an invalid backend is requested to have 80% of\n                        connections, then 80% of connections must be rejected instead.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Extended for Kubernetes ServiceImport\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Extended\n                      items:\n                        description: |-\n                          BackendRef defines how a Route should forward a request to a Kubernetes\n                          resource.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n\n\n                          Note that when the BackendTLSPolicy object is enabled by the implementation,\n                          there are some extra rules about validity to consider here. See the fields\n                          where this struct is used for more information about the exact behavior.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      minItems: 1\n                      type: array\n                  type: object\n                maxItems: 16\n                minItems: 1\n                type: array\n            required:\n            - rules\n            type: object\n          status:\n            description: Status defines the current state of TCPRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: null\n  storedVersions: null\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: externalworkloads.workload.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: workload.linkerd.io\n  names:\n    categories:\n    - external\n    kind: ExternalWorkload\n    listKind: ExternalWorkloadList\n    plural: externalworkloads\n    singular: externalworkload\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTls:\n                description: meshTls describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                # TODO: relax this in the future when ipv6 is supported\n                # an external workload (like a pod) should only\n                # support 2 interfaces\n                maxItems: 1\n            type: object\n            required:\n            - meshTls\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTls.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n  - name: v1beta1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTLS:\n                description: meshTLS describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload. This field may\n                  hold a maximum of two entries. If one entry, it can be an\n                  IPv4 or IPv6 address; if two entries it should contain one\n                  IPv4 address and one IPv6 address.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                maxItems: 2\n            type: object\n            required:\n            - meshTLS\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTLS.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n"
  },
  {
    "path": "cli/cmd/testdata/install_custom_domain.golden",
    "content": "---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: linkerd\n    pod-security.kubernetes.io/enforce: privileged\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGU=\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: linkerd/cli dev-undefined\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 1\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: install-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 25%\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    enablePodDisruptionBudget: false\n    heartbeat: null\n    heartbeatResources: null\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: false\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\n            MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\n            ODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\n            a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\n            fcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n            2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\n            Af8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\n            HmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\n            AiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n            51tdrmkHEZRr0qlLSJdHYgEfMzk=\n            -----END CERTIFICATE-----\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources: null\n    identityTrustAnchorsPEM: |\n      -----BEGIN CERTIFICATE-----\n      MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n      JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n      MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n      ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n      l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n      uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n      /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n      aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n      IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n      vgUC0d2/9FMueIVMb+46WTCOjsqr\n      -----END CERTIFICATE-----\n    identityTrustDomain: cluster.local\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: []\n    linkerdVersion: install-control-plane-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: policy validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: profile validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: install-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: 4567,4568\n      ignoreOutboundPorts: 4567,4568\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: proxy injector CA bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tolerations: null\n    webhookFailurePolicy: Ignore\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQpPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CmEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQoyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCkFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CjUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==\n  key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgpkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  ca-bundle.crt: |-\n    -----BEGIN CERTIFICATE-----\n    MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n    JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n    MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n    ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n    l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n    uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n    /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n    aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n    IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n    vgUC0d2/9FMueIVMb+46WTCOjsqr\n    -----END CERTIFICATE-----\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-identity\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd\n        - -identity-trust-domain=cluster.local\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-destination\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: a39770e2a1ac1f4bb452509e52a4ffd176bcb034f1ce906491f2649e28c67319\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=cluster.local\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=cluster.local\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd\n          annotations:\n            linkerd.io/created-by: linkerd/cli dev-undefined\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:install-control-plane-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-proxy-injector\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: cd0cf730780be444ab96a4a835a244033ffb7c8cf4a8796d0e6ae5c72aa9ff31\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\napiVersion: v1\ndata:\n  linkerd-config-overrides: ZGVidWdDb250YWluZXI6CiAgaW1hZ2U6CiAgICB2ZXJzaW9uOiBpbnN0YWxsLWRlYnVnLXZlcnNpb24KaGVhcnRiZWF0U2NoZWR1bGU6IDEgMiAzIDQgNQppZGVudGl0eToKICBpc3N1ZXI6CiAgICB0bHM6CiAgICAgIGNydFBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogICAgICAgIE1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KICAgICAgICBNQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUI0WERUSXdNRGd5CiAgICAgICAgT0RBM01UTTBOMW9YRFRNd01EZ3lOakEzTVRNME4xb3dLVEVuTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdQogICAgICAgIGEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKICAgICAgICBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzJkUXZSYVlhbnV4RDM2RHQxCiAgICAgICAgMi9KeHlpU2d4S1dSZG9heSthTndNRzR3RGdZRFZSMFBBUUgvQkFRREFnRUdNQklHQTFVZEV3RUIvd1FJTUFZQgogICAgICAgIEFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKICAgICAgICBIbWxrWlc1MGFYUjVMbXhwYm10bGNtUXVZMngxYzNSbGNpNXNiMk5oYkRBS0JnZ3Foa2pPUFFRREFnTkhBREJFCiAgICAgICAgQWlBdHVvSTVYdUN0ckdWUnpTbVJUbDJyYTI4YVY5TXlUVTdkNXFuVEFGSEtTZ0lnUktDdmx1T1NnQTVPMjFwNQogICAgICAgIDUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KICAgICAgICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCiAgICAgIGtleVBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gRUMgUFJJVkFURSBLRVktLS0tLQogICAgICAgIE1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKICAgICAgICBBd0VIb1VRRFFnQUUxL0ZwZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyCiAgICAgICAgZFF2UmFZYW51eEQzNkR0MTIvSnh5aVNneEtXUmRvYXkrUT09CiAgICAgICAgLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQppZGVudGl0eVRydXN0QW5jaG9yc1BFTTogfAogIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogIE1JSUJ3VENDQVdhZ0F3SUJBZ0lRZURacDVsRGFJeWdRNVVmTUtackZBVEFLQmdncWhrak9QUVFEQWpBcE1TY3cKICBKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3SGhjTk1qQXdPREk0CiAgTURjeE1qUTNXaGNOTXpBd09ESTJNRGN4TWpRM1dqQXBNU2N3SlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1cgogIFpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFScWM3MFoKICBsMXZndzc5cmpCNXVTSVRJQ1VBNkd5ZnZTRmZjdUlpczdCL1hGU2trd0FIVTVTL3MxQUFQK1IwVFg3SEJXVUM0CiAgdWFHNFdXc2l3SktObjdtZ28zQXdiakFPQmdOVkhROEJBZjhFQkFNQ0FRWXdFZ1lEVlIwVEFRSC9CQWd3QmdFQgogIC93SUJBVEFkQmdOVkhRNEVGZ1FVNVl0alZWUGZkN0k3TkxIc24yQzI2RUJ5R1Ywd0tRWURWUjBSQkNJd0lJSWUKICBhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUFvR0NDcUdTTTQ5QkFNQ0Ewa0FNRVlDCiAgSVFDTjdsQkZMRER2ang2VjArWGtqcEtFUlJzSllmNWFkTXZubG9GbDQ4aWxKZ0loQU50eGhuZGNyK1FKUHVDOAogIHZnVUMwZDIvOUZNdWVJVk1iKzQ2V1RDT2pzcXIKICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCmxpbmtlcmRWZXJzaW9uOiBpbnN0YWxsLWNvbnRyb2wtcGxhbmUtdmVyc2lvbgpwb2xpY3lWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHBvbGljeSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJvZmlsZVZhbGlkYXRvcjoKICBjYUJ1bmRsZTogcHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJveHk6CiAgaW1hZ2U6CiAgICB2ZXJzaW9uOiBpbnN0YWxsLXByb3h5LXZlcnNpb24KcHJveHlJbmplY3RvcjoKICBjYUJ1bmRsZTogcHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUK\nkind: Secret\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-config-overrides\n  namespace: linkerd\n"
  },
  {
    "path": "cli/cmd/testdata/install_custom_registry.golden",
    "content": "---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: linkerd\n    pod-security.kubernetes.io/enforce: privileged\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGU=\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: linkerd/cli dev-undefined\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: my.custom.registry/linkerd-io/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 1\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: my.custom.registry/linkerd-io/debug\n        pullPolicy: \"\"\n        version: install-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 25%\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    enablePodDisruptionBudget: false\n    heartbeat: null\n    heartbeatResources: null\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: false\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\n            MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\n            ODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\n            a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\n            fcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n            2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\n            Af8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\n            HmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\n            AiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n            51tdrmkHEZRr0qlLSJdHYgEfMzk=\n            -----END CERTIFICATE-----\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources: null\n    identityTrustAnchorsPEM: |\n      -----BEGIN CERTIFICATE-----\n      MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n      JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n      MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n      ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n      l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n      uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n      /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n      aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n      IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n      vgUC0d2/9FMueIVMb+46WTCOjsqr\n      -----END CERTIFICATE-----\n    identityTrustDomain: cluster.local\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: []\n    linkerdVersion: install-control-plane-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: policy validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: profile validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: my.custom.registry/linkerd-io/proxy\n        pullPolicy: \"\"\n        version: install-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: 4567,4568\n      ignoreOutboundPorts: 4567,4568\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: proxy injector CA bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tolerations: null\n    webhookFailurePolicy: Ignore\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQpPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CmEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQoyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCkFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CjUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==\n  key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgpkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  ca-bundle.crt: |-\n    -----BEGIN CERTIFICATE-----\n    MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n    JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n    MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n    ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n    l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n    uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n    /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n    aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n    IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n    vgUC0d2/9FMueIVMb+46WTCOjsqr\n    -----END CERTIFICATE-----\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-identity\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd\n        - -identity-trust-domain=cluster.local\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: my.custom.registry/linkerd-io/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: my.custom.registry/linkerd-io/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: my.custom.registry/linkerd-io/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-destination\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: a39770e2a1ac1f4bb452509e52a4ffd176bcb034f1ce906491f2649e28c67319\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: my.custom.registry/linkerd-io/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=cluster.local\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: my.custom.registry/linkerd-io/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: my.custom.registry/linkerd-io/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=cluster.local\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: my.custom.registry/linkerd-io/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: my.custom.registry/linkerd-io/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd\n          annotations:\n            linkerd.io/created-by: linkerd/cli dev-undefined\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: my.custom.registry/linkerd-io/controller:install-control-plane-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-proxy-injector\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: cd0cf730780be444ab96a4a835a244033ffb7c8cf4a8796d0e6ae5c72aa9ff31\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: my.custom.registry/linkerd-io/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd\n        - -enable-pprof=false\n        image: my.custom.registry/linkerd-io/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: my.custom.registry/linkerd-io/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\napiVersion: v1\ndata:\n  linkerd-config-overrides: Y29udHJvbGxlckltYWdlOiBteS5jdXN0b20ucmVnaXN0cnkvbGlua2VyZC1pby9jb250cm9sbGVyCmRlYnVnQ29udGFpbmVyOgogIGltYWdlOgogICAgbmFtZTogbXkuY3VzdG9tLnJlZ2lzdHJ5L2xpbmtlcmQtaW8vZGVidWcKICAgIHZlcnNpb246IGluc3RhbGwtZGVidWctdmVyc2lvbgpoZWFydGJlYXRTY2hlZHVsZTogMSAyIDMgNCA1CmlkZW50aXR5OgogIGlzc3VlcjoKICAgIHRsczoKICAgICAgY3J0UEVNOiB8CiAgICAgICAgLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCiAgICAgICAgTUlJQndEQ0NBV2VnQXdJQkFnSVJBSlJJZ1o4UnRPOEV3ZzFYZXBmOFQ0NHdDZ1lJS29aSXpqMEVBd0l3S1RFbgogICAgICAgIE1DVUdBMVVFQXhNZWFXUmxiblJwZEhrdWJHbHVhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJd01EZ3kKICAgICAgICBPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CiAgICAgICAgYTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFMS9GcAogICAgICAgIGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MmRRdlJhWWFudXhEMzZEdDEKICAgICAgICAyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCiAgICAgICAgQWY4Q0FRQXdIUVlEVlIwT0JCWUVGSTFXbnJxTVlLYUhIT28renB5aWlEcTJwTzBLTUNrR0ExVWRFUVFpTUNDQwogICAgICAgIEhtbGtaVzUwYVhSNUxteHBibXRsY21RdVkyeDFjM1JsY2k1c2IyTmhiREFLQmdncWhrak9QUVFEQWdOSEFEQkUKICAgICAgICBBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CiAgICAgICAgNTF0ZHJta0hFWlJyMHFsTFNKZEhZZ0VmTXprPQogICAgICAgIC0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KICAgICAga2V5UEVNOiB8CiAgICAgICAgLS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCiAgICAgICAgTUhjQ0FRRUVJQUFlOG5mYnpadTljL09CMis4eEpNMEZ6N05Vd1RRYXp1bGtGTnM0VEk1K29Bb0dDQ3FHU000OQogICAgICAgIEF3RUhvVVFEUWdBRTEvRnBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzIKICAgICAgICBkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KICAgICAgICAtLS0tLUVORCBFQyBQUklWQVRFIEtFWS0tLS0tCmlkZW50aXR5VHJ1c3RBbmNob3JzUEVNOiB8CiAgLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCiAgTUlJQndUQ0NBV2FnQXdJQkFnSVFlRFpwNWxEYUl5Z1E1VWZNS1pyRkFUQUtCZ2dxaGtqT1BRUURBakFwTVNjdwogIEpRWURWUVFERXg1cFpHVnVkR2wwZVM1c2FXNXJaWEprTG1Oc2RYTjBaWEl1Ykc5allXd3dIaGNOTWpBd09ESTQKICBNRGN4TWpRM1doY05NekF3T0RJMk1EY3hNalEzV2pBcE1TY3dKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyCiAgWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVJxYzcwWgogIGwxdmd3NzlyakI1dVNJVElDVUE2R3lmdlNGZmN1SWlzN0IvWEZTa2t3QUhVNVMvczFBQVArUjBUWDdIQldVQzQKICB1YUc0V1dzaXdKS05uN21nbzNBd2JqQU9CZ05WSFE4QkFmOEVCQU1DQVFZd0VnWURWUjBUQVFIL0JBZ3dCZ0VCCiAgL3dJQkFUQWRCZ05WSFE0RUZnUVU1WXRqVlZQZmQ3STdOTEhzbjJDMjZFQnlHVjB3S1FZRFZSMFJCQ0l3SUlJZQogIGFXUmxiblJwZEhrdWJHbHVhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNQW9HQ0NxR1NNNDlCQU1DQTBrQU1FWUMKICBJUUNON2xCRkxERHZqeDZWMCtYa2pwS0VSUnNKWWY1YWRNdm5sb0ZsNDhpbEpnSWhBTnR4aG5kY3IrUUpQdUM4CiAgdmdVQzBkMi85Rk11ZUlWTWIrNDZXVENPanNxcgogIC0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KbGlua2VyZFZlcnNpb246IGluc3RhbGwtY29udHJvbC1wbGFuZS12ZXJzaW9uCnBvbGljeVZhbGlkYXRvcjoKICBjYUJ1bmRsZTogcG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQpwcm9maWxlVmFsaWRhdG9yOgogIGNhQnVuZGxlOiBwcm9maWxlIHZhbGlkYXRvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQpwcm94eToKICBpbWFnZToKICAgIG5hbWU6IG15LmN1c3RvbS5yZWdpc3RyeS9saW5rZXJkLWlvL3Byb3h5CiAgICB2ZXJzaW9uOiBpbnN0YWxsLXByb3h5LXZlcnNpb24KcHJveHlJbmplY3RvcjoKICBjYUJ1bmRsZTogcHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUK\nkind: Secret\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-config-overrides\n  namespace: linkerd\n"
  },
  {
    "path": "cli/cmd/testdata/install_default.golden",
    "content": "---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: linkerd\n    pod-security.kubernetes.io/enforce: privileged\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGU=\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: linkerd/cli dev-undefined\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 1\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: install-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 25%\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    enablePodDisruptionBudget: false\n    heartbeat: null\n    heartbeatResources: null\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: false\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\n            MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\n            ODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\n            a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\n            fcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n            2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\n            Af8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\n            HmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\n            AiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n            51tdrmkHEZRr0qlLSJdHYgEfMzk=\n            -----END CERTIFICATE-----\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources: null\n    identityTrustAnchorsPEM: |\n      -----BEGIN CERTIFICATE-----\n      MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n      JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n      MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n      ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n      l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n      uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n      /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n      aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n      IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n      vgUC0d2/9FMueIVMb+46WTCOjsqr\n      -----END CERTIFICATE-----\n    identityTrustDomain: cluster.local\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: []\n    linkerdVersion: install-control-plane-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: policy validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: profile validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: install-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: 4567,4568\n      ignoreOutboundPorts: 4567,4568\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: proxy injector CA bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tolerations: null\n    webhookFailurePolicy: Ignore\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQpPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CmEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQoyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCkFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CjUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==\n  key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgpkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  ca-bundle.crt: |-\n    -----BEGIN CERTIFICATE-----\n    MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n    JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n    MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n    ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n    l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n    uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n    /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n    aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n    IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n    vgUC0d2/9FMueIVMb+46WTCOjsqr\n    -----END CERTIFICATE-----\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-identity\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd\n        - -identity-trust-domain=cluster.local\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-destination\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: a39770e2a1ac1f4bb452509e52a4ffd176bcb034f1ce906491f2649e28c67319\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=cluster.local\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=cluster.local\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd\n          annotations:\n            linkerd.io/created-by: linkerd/cli dev-undefined\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:install-control-plane-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-proxy-injector\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: cd0cf730780be444ab96a4a835a244033ffb7c8cf4a8796d0e6ae5c72aa9ff31\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\napiVersion: v1\ndata:\n  linkerd-config-overrides: ZGVidWdDb250YWluZXI6CiAgaW1hZ2U6CiAgICB2ZXJzaW9uOiBpbnN0YWxsLWRlYnVnLXZlcnNpb24KaGVhcnRiZWF0U2NoZWR1bGU6IDEgMiAzIDQgNQppZGVudGl0eToKICBpc3N1ZXI6CiAgICB0bHM6CiAgICAgIGNydFBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogICAgICAgIE1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KICAgICAgICBNQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUI0WERUSXdNRGd5CiAgICAgICAgT0RBM01UTTBOMW9YRFRNd01EZ3lOakEzTVRNME4xb3dLVEVuTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdQogICAgICAgIGEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKICAgICAgICBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzJkUXZSYVlhbnV4RDM2RHQxCiAgICAgICAgMi9KeHlpU2d4S1dSZG9heSthTndNRzR3RGdZRFZSMFBBUUgvQkFRREFnRUdNQklHQTFVZEV3RUIvd1FJTUFZQgogICAgICAgIEFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKICAgICAgICBIbWxrWlc1MGFYUjVMbXhwYm10bGNtUXVZMngxYzNSbGNpNXNiMk5oYkRBS0JnZ3Foa2pPUFFRREFnTkhBREJFCiAgICAgICAgQWlBdHVvSTVYdUN0ckdWUnpTbVJUbDJyYTI4YVY5TXlUVTdkNXFuVEFGSEtTZ0lnUktDdmx1T1NnQTVPMjFwNQogICAgICAgIDUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KICAgICAgICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCiAgICAgIGtleVBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gRUMgUFJJVkFURSBLRVktLS0tLQogICAgICAgIE1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKICAgICAgICBBd0VIb1VRRFFnQUUxL0ZwZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyCiAgICAgICAgZFF2UmFZYW51eEQzNkR0MTIvSnh5aVNneEtXUmRvYXkrUT09CiAgICAgICAgLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQppZGVudGl0eVRydXN0QW5jaG9yc1BFTTogfAogIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogIE1JSUJ3VENDQVdhZ0F3SUJBZ0lRZURacDVsRGFJeWdRNVVmTUtackZBVEFLQmdncWhrak9QUVFEQWpBcE1TY3cKICBKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3SGhjTk1qQXdPREk0CiAgTURjeE1qUTNXaGNOTXpBd09ESTJNRGN4TWpRM1dqQXBNU2N3SlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1cgogIFpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFScWM3MFoKICBsMXZndzc5cmpCNXVTSVRJQ1VBNkd5ZnZTRmZjdUlpczdCL1hGU2trd0FIVTVTL3MxQUFQK1IwVFg3SEJXVUM0CiAgdWFHNFdXc2l3SktObjdtZ28zQXdiakFPQmdOVkhROEJBZjhFQkFNQ0FRWXdFZ1lEVlIwVEFRSC9CQWd3QmdFQgogIC93SUJBVEFkQmdOVkhRNEVGZ1FVNVl0alZWUGZkN0k3TkxIc24yQzI2RUJ5R1Ywd0tRWURWUjBSQkNJd0lJSWUKICBhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUFvR0NDcUdTTTQ5QkFNQ0Ewa0FNRVlDCiAgSVFDTjdsQkZMRER2ang2VjArWGtqcEtFUlJzSllmNWFkTXZubG9GbDQ4aWxKZ0loQU50eGhuZGNyK1FKUHVDOAogIHZnVUMwZDIvOUZNdWVJVk1iKzQ2V1RDT2pzcXIKICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCmxpbmtlcmRWZXJzaW9uOiBpbnN0YWxsLWNvbnRyb2wtcGxhbmUtdmVyc2lvbgpwb2xpY3lWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHBvbGljeSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJvZmlsZVZhbGlkYXRvcjoKICBjYUJ1bmRsZTogcHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJveHk6CiAgaW1hZ2U6CiAgICB2ZXJzaW9uOiBpbnN0YWxsLXByb3h5LXZlcnNpb24KcHJveHlJbmplY3RvcjoKICBjYUJ1bmRsZTogcHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUK\nkind: Secret\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-config-overrides\n  namespace: linkerd\n"
  },
  {
    "path": "cli/cmd/testdata/install_default_override_dst_get_nets.golden",
    "content": "---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: linkerd\n    pod-security.kubernetes.io/enforce: privileged\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGU=\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: linkerd/cli dev-undefined\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.0.0.0/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 1\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: install-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 25%\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    enablePodDisruptionBudget: false\n    heartbeat: null\n    heartbeatResources: null\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: false\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\n            MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\n            ODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\n            a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\n            fcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n            2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\n            Af8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\n            HmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\n            AiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n            51tdrmkHEZRr0qlLSJdHYgEfMzk=\n            -----END CERTIFICATE-----\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources: null\n    identityTrustAnchorsPEM: |\n      -----BEGIN CERTIFICATE-----\n      MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n      JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n      MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n      ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n      l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n      uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n      /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n      aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n      IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n      vgUC0d2/9FMueIVMb+46WTCOjsqr\n      -----END CERTIFICATE-----\n    identityTrustDomain: cluster.local\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: []\n    linkerdVersion: install-control-plane-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: policy validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: profile validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: install-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: 4567,4568\n      ignoreOutboundPorts: 4567,4568\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: proxy injector CA bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tolerations: null\n    webhookFailurePolicy: Ignore\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQpPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CmEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQoyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCkFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CjUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==\n  key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgpkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  ca-bundle.crt: |-\n    -----BEGIN CERTIFICATE-----\n    MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n    JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n    MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n    ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n    l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n    uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n    /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n    aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n    IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n    vgUC0d2/9FMueIVMb+46WTCOjsqr\n    -----END CERTIFICATE-----\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-identity\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd\n        - -identity-trust-domain=cluster.local\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.0.0.0/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.0.0.0/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-destination\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: a39770e2a1ac1f4bb452509e52a4ffd176bcb034f1ce906491f2649e28c67319\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.0.0.0/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.0.0.0/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=cluster.local\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.0.0.0/8\n        - --identity-domain=cluster.local\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd\n          annotations:\n            linkerd.io/created-by: linkerd/cli dev-undefined\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:install-control-plane-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-proxy-injector\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: cd0cf730780be444ab96a4a835a244033ffb7c8cf4a8796d0e6ae5c72aa9ff31\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.0.0.0/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.0.0.0/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\napiVersion: v1\ndata:\n  linkerd-config-overrides: Y2x1c3Rlck5ldHdvcmtzOiAxMC4wLjAuMC84LDEwMC42NC4wLjAvMTAsMTcyLjAuMC4wLzgKZGVidWdDb250YWluZXI6CiAgaW1hZ2U6CiAgICB2ZXJzaW9uOiBpbnN0YWxsLWRlYnVnLXZlcnNpb24KaGVhcnRiZWF0U2NoZWR1bGU6IDEgMiAzIDQgNQppZGVudGl0eToKICBpc3N1ZXI6CiAgICB0bHM6CiAgICAgIGNydFBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogICAgICAgIE1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KICAgICAgICBNQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUI0WERUSXdNRGd5CiAgICAgICAgT0RBM01UTTBOMW9YRFRNd01EZ3lOakEzTVRNME4xb3dLVEVuTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdQogICAgICAgIGEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKICAgICAgICBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzJkUXZSYVlhbnV4RDM2RHQxCiAgICAgICAgMi9KeHlpU2d4S1dSZG9heSthTndNRzR3RGdZRFZSMFBBUUgvQkFRREFnRUdNQklHQTFVZEV3RUIvd1FJTUFZQgogICAgICAgIEFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKICAgICAgICBIbWxrWlc1MGFYUjVMbXhwYm10bGNtUXVZMngxYzNSbGNpNXNiMk5oYkRBS0JnZ3Foa2pPUFFRREFnTkhBREJFCiAgICAgICAgQWlBdHVvSTVYdUN0ckdWUnpTbVJUbDJyYTI4YVY5TXlUVTdkNXFuVEFGSEtTZ0lnUktDdmx1T1NnQTVPMjFwNQogICAgICAgIDUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KICAgICAgICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCiAgICAgIGtleVBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gRUMgUFJJVkFURSBLRVktLS0tLQogICAgICAgIE1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKICAgICAgICBBd0VIb1VRRFFnQUUxL0ZwZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyCiAgICAgICAgZFF2UmFZYW51eEQzNkR0MTIvSnh5aVNneEtXUmRvYXkrUT09CiAgICAgICAgLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQppZGVudGl0eVRydXN0QW5jaG9yc1BFTTogfAogIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogIE1JSUJ3VENDQVdhZ0F3SUJBZ0lRZURacDVsRGFJeWdRNVVmTUtackZBVEFLQmdncWhrak9QUVFEQWpBcE1TY3cKICBKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3SGhjTk1qQXdPREk0CiAgTURjeE1qUTNXaGNOTXpBd09ESTJNRGN4TWpRM1dqQXBNU2N3SlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1cgogIFpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFScWM3MFoKICBsMXZndzc5cmpCNXVTSVRJQ1VBNkd5ZnZTRmZjdUlpczdCL1hGU2trd0FIVTVTL3MxQUFQK1IwVFg3SEJXVUM0CiAgdWFHNFdXc2l3SktObjdtZ28zQXdiakFPQmdOVkhROEJBZjhFQkFNQ0FRWXdFZ1lEVlIwVEFRSC9CQWd3QmdFQgogIC93SUJBVEFkQmdOVkhRNEVGZ1FVNVl0alZWUGZkN0k3TkxIc24yQzI2RUJ5R1Ywd0tRWURWUjBSQkNJd0lJSWUKICBhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUFvR0NDcUdTTTQ5QkFNQ0Ewa0FNRVlDCiAgSVFDTjdsQkZMRER2ang2VjArWGtqcEtFUlJzSllmNWFkTXZubG9GbDQ4aWxKZ0loQU50eGhuZGNyK1FKUHVDOAogIHZnVUMwZDIvOUZNdWVJVk1iKzQ2V1RDT2pzcXIKICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCmxpbmtlcmRWZXJzaW9uOiBpbnN0YWxsLWNvbnRyb2wtcGxhbmUtdmVyc2lvbgpwb2xpY3lWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHBvbGljeSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJvZmlsZVZhbGlkYXRvcjoKICBjYUJ1bmRsZTogcHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJveHk6CiAgaW1hZ2U6CiAgICB2ZXJzaW9uOiBpbnN0YWxsLXByb3h5LXZlcnNpb24KcHJveHlJbmplY3RvcjoKICBjYUJ1bmRsZTogcHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUK\nkind: Secret\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-config-overrides\n  namespace: linkerd\n"
  },
  {
    "path": "cli/cmd/testdata/install_default_token.golden",
    "content": "---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: linkerd\n    pod-security.kubernetes.io/enforce: privileged\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGU=\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: linkerd/cli dev-undefined\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 1\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: install-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 25%\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    enablePodDisruptionBudget: false\n    heartbeat: null\n    heartbeatResources: null\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: false\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\n            MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\n            ODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\n            a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\n            fcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n            2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\n            Af8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\n            HmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\n            AiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n            51tdrmkHEZRr0qlLSJdHYgEfMzk=\n            -----END CERTIFICATE-----\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: false\n    identityProxyResources: null\n    identityResources: null\n    identityTrustAnchorsPEM: |\n      -----BEGIN CERTIFICATE-----\n      MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n      JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n      MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n      ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n      l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n      uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n      /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n      aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n      IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n      vgUC0d2/9FMueIVMb+46WTCOjsqr\n      -----END CERTIFICATE-----\n    identityTrustDomain: cluster.local\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: []\n    linkerdVersion: install-control-plane-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: policy validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: profile validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: install-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: 4567,4568\n      ignoreOutboundPorts: 4567,4568\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: proxy injector CA bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tolerations: null\n    webhookFailurePolicy: Ignore\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQpPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CmEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQoyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCkFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CjUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==\n  key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgpkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  ca-bundle.crt: |-\n    -----BEGIN CERTIFICATE-----\n    MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n    JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n    MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n    ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n    l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n    uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n    /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n    aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n    IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n    vgUC0d2/9FMueIVMb+46WTCOjsqr\n    -----END CERTIFICATE-----\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-identity\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd\n        - -identity-trust-domain=cluster.local\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/kubernetes.io/serviceaccount/token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-destination\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: a39770e2a1ac1f4bb452509e52a4ffd176bcb034f1ce906491f2649e28c67319\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/kubernetes.io/serviceaccount/token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=cluster.local\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=cluster.local\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd\n          annotations:\n            linkerd.io/created-by: linkerd/cli dev-undefined\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:install-control-plane-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-proxy-injector\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: cd0cf730780be444ab96a4a835a244033ffb7c8cf4a8796d0e6ae5c72aa9ff31\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/kubernetes.io/serviceaccount/token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\napiVersion: v1\ndata:\n  linkerd-config-overrides: ZGVidWdDb250YWluZXI6CiAgaW1hZ2U6CiAgICB2ZXJzaW9uOiBpbnN0YWxsLWRlYnVnLXZlcnNpb24KaGVhcnRiZWF0U2NoZWR1bGU6IDEgMiAzIDQgNQppZGVudGl0eToKICBpc3N1ZXI6CiAgICB0bHM6CiAgICAgIGNydFBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogICAgICAgIE1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KICAgICAgICBNQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUI0WERUSXdNRGd5CiAgICAgICAgT0RBM01UTTBOMW9YRFRNd01EZ3lOakEzTVRNME4xb3dLVEVuTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdQogICAgICAgIGEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKICAgICAgICBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzJkUXZSYVlhbnV4RDM2RHQxCiAgICAgICAgMi9KeHlpU2d4S1dSZG9heSthTndNRzR3RGdZRFZSMFBBUUgvQkFRREFnRUdNQklHQTFVZEV3RUIvd1FJTUFZQgogICAgICAgIEFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKICAgICAgICBIbWxrWlc1MGFYUjVMbXhwYm10bGNtUXVZMngxYzNSbGNpNXNiMk5oYkRBS0JnZ3Foa2pPUFFRREFnTkhBREJFCiAgICAgICAgQWlBdHVvSTVYdUN0ckdWUnpTbVJUbDJyYTI4YVY5TXlUVTdkNXFuVEFGSEtTZ0lnUktDdmx1T1NnQTVPMjFwNQogICAgICAgIDUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KICAgICAgICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCiAgICAgIGtleVBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gRUMgUFJJVkFURSBLRVktLS0tLQogICAgICAgIE1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKICAgICAgICBBd0VIb1VRRFFnQUUxL0ZwZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyCiAgICAgICAgZFF2UmFZYW51eEQzNkR0MTIvSnh5aVNneEtXUmRvYXkrUT09CiAgICAgICAgLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQogIHNlcnZpY2VBY2NvdW50VG9rZW5Qcm9qZWN0aW9uOiBmYWxzZQppZGVudGl0eVRydXN0QW5jaG9yc1BFTTogfAogIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogIE1JSUJ3VENDQVdhZ0F3SUJBZ0lRZURacDVsRGFJeWdRNVVmTUtackZBVEFLQmdncWhrak9QUVFEQWpBcE1TY3cKICBKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3SGhjTk1qQXdPREk0CiAgTURjeE1qUTNXaGNOTXpBd09ESTJNRGN4TWpRM1dqQXBNU2N3SlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1cgogIFpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFScWM3MFoKICBsMXZndzc5cmpCNXVTSVRJQ1VBNkd5ZnZTRmZjdUlpczdCL1hGU2trd0FIVTVTL3MxQUFQK1IwVFg3SEJXVUM0CiAgdWFHNFdXc2l3SktObjdtZ28zQXdiakFPQmdOVkhROEJBZjhFQkFNQ0FRWXdFZ1lEVlIwVEFRSC9CQWd3QmdFQgogIC93SUJBVEFkQmdOVkhRNEVGZ1FVNVl0alZWUGZkN0k3TkxIc24yQzI2RUJ5R1Ywd0tRWURWUjBSQkNJd0lJSWUKICBhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUFvR0NDcUdTTTQ5QkFNQ0Ewa0FNRVlDCiAgSVFDTjdsQkZMRER2ang2VjArWGtqcEtFUlJzSllmNWFkTXZubG9GbDQ4aWxKZ0loQU50eGhuZGNyK1FKUHVDOAogIHZnVUMwZDIvOUZNdWVJVk1iKzQ2V1RDT2pzcXIKICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCmxpbmtlcmRWZXJzaW9uOiBpbnN0YWxsLWNvbnRyb2wtcGxhbmUtdmVyc2lvbgpwb2xpY3lWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHBvbGljeSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJvZmlsZVZhbGlkYXRvcjoKICBjYUJ1bmRsZTogcHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJveHk6CiAgaW1hZ2U6CiAgICB2ZXJzaW9uOiBpbnN0YWxsLXByb3h5LXZlcnNpb24KcHJveHlJbmplY3RvcjoKICBjYUJ1bmRsZTogcHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUK\nkind: Secret\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-config-overrides\n  namespace: linkerd\n"
  },
  {
    "path": "cli/cmd/testdata/install_gid_output.golden",
    "content": "---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: linkerd\n    pod-security.kubernetes.io/enforce: privileged\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGU=\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: linkerd/cli dev-undefined\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: 1234\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 1\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: install-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 25%\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    enablePodDisruptionBudget: false\n    heartbeat: null\n    heartbeatResources: null\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: false\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\n            MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\n            ODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\n            a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\n            fcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n            2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\n            Af8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\n            HmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\n            AiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n            51tdrmkHEZRr0qlLSJdHYgEfMzk=\n            -----END CERTIFICATE-----\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources: null\n    identityTrustAnchorsPEM: |\n      -----BEGIN CERTIFICATE-----\n      MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n      JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n      MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n      ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n      l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n      uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n      /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n      aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n      IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n      vgUC0d2/9FMueIVMb+46WTCOjsqr\n      -----END CERTIFICATE-----\n    identityTrustDomain: cluster.local\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: []\n    linkerdVersion: install-control-plane-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: policy validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: profile validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: 4321\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: install-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: 4567,4568\n      ignoreOutboundPorts: 4567,4568\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: proxy injector CA bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tolerations: null\n    webhookFailurePolicy: Ignore\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQpPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CmEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQoyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCkFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CjUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==\n  key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgpkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  ca-bundle.crt: |-\n    -----BEGIN CERTIFICATE-----\n    MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n    JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n    MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n    ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n    l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n    uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n    /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n    aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n    IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n    vgUC0d2/9FMueIVMb+46WTCOjsqr\n    -----END CERTIFICATE-----\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-identity\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd\n        - -identity-trust-domain=cluster.local\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 1234\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          runAsGroup: 4321\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --proxy-gid\n        - \"4321\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-destination\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: a39770e2a1ac1f4bb452509e52a4ffd176bcb034f1ce906491f2649e28c67319\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          runAsGroup: 4321\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=cluster.local\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 1234\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 1234\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=cluster.local\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 1234\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --proxy-gid\n        - \"4321\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd\n          annotations:\n            linkerd.io/created-by: linkerd/cli dev-undefined\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:install-control-plane-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              runAsGroup: 1234\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-proxy-injector\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: cd0cf730780be444ab96a4a835a244033ffb7c8cf4a8796d0e6ae5c72aa9ff31\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          runAsGroup: 4321\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 1234\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --proxy-gid\n        - \"4321\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\napiVersion: v1\ndata:\n  linkerd-config-overrides: Y29udHJvbGxlckdJRDogMTIzNApkZWJ1Z0NvbnRhaW5lcjoKICBpbWFnZToKICAgIHZlcnNpb246IGluc3RhbGwtZGVidWctdmVyc2lvbgpoZWFydGJlYXRTY2hlZHVsZTogMSAyIDMgNCA1CmlkZW50aXR5OgogIGlzc3VlcjoKICAgIHRsczoKICAgICAgY3J0UEVNOiB8CiAgICAgICAgLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCiAgICAgICAgTUlJQndEQ0NBV2VnQXdJQkFnSVJBSlJJZ1o4UnRPOEV3ZzFYZXBmOFQ0NHdDZ1lJS29aSXpqMEVBd0l3S1RFbgogICAgICAgIE1DVUdBMVVFQXhNZWFXUmxiblJwZEhrdWJHbHVhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJd01EZ3kKICAgICAgICBPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CiAgICAgICAgYTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFMS9GcAogICAgICAgIGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MmRRdlJhWWFudXhEMzZEdDEKICAgICAgICAyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCiAgICAgICAgQWY4Q0FRQXdIUVlEVlIwT0JCWUVGSTFXbnJxTVlLYUhIT28renB5aWlEcTJwTzBLTUNrR0ExVWRFUVFpTUNDQwogICAgICAgIEhtbGtaVzUwYVhSNUxteHBibXRsY21RdVkyeDFjM1JsY2k1c2IyTmhiREFLQmdncWhrak9QUVFEQWdOSEFEQkUKICAgICAgICBBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CiAgICAgICAgNTF0ZHJta0hFWlJyMHFsTFNKZEhZZ0VmTXprPQogICAgICAgIC0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KICAgICAga2V5UEVNOiB8CiAgICAgICAgLS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCiAgICAgICAgTUhjQ0FRRUVJQUFlOG5mYnpadTljL09CMis4eEpNMEZ6N05Vd1RRYXp1bGtGTnM0VEk1K29Bb0dDQ3FHU000OQogICAgICAgIEF3RUhvVVFEUWdBRTEvRnBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzIKICAgICAgICBkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KICAgICAgICAtLS0tLUVORCBFQyBQUklWQVRFIEtFWS0tLS0tCmlkZW50aXR5VHJ1c3RBbmNob3JzUEVNOiB8CiAgLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCiAgTUlJQndUQ0NBV2FnQXdJQkFnSVFlRFpwNWxEYUl5Z1E1VWZNS1pyRkFUQUtCZ2dxaGtqT1BRUURBakFwTVNjdwogIEpRWURWUVFERXg1cFpHVnVkR2wwZVM1c2FXNXJaWEprTG1Oc2RYTjBaWEl1Ykc5allXd3dIaGNOTWpBd09ESTQKICBNRGN4TWpRM1doY05NekF3T0RJMk1EY3hNalEzV2pBcE1TY3dKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyCiAgWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVJxYzcwWgogIGwxdmd3NzlyakI1dVNJVElDVUE2R3lmdlNGZmN1SWlzN0IvWEZTa2t3QUhVNVMvczFBQVArUjBUWDdIQldVQzQKICB1YUc0V1dzaXdKS05uN21nbzNBd2JqQU9CZ05WSFE4QkFmOEVCQU1DQVFZd0VnWURWUjBUQVFIL0JBZ3dCZ0VCCiAgL3dJQkFUQWRCZ05WSFE0RUZnUVU1WXRqVlZQZmQ3STdOTEhzbjJDMjZFQnlHVjB3S1FZRFZSMFJCQ0l3SUlJZQogIGFXUmxiblJwZEhrdWJHbHVhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNQW9HQ0NxR1NNNDlCQU1DQTBrQU1FWUMKICBJUUNON2xCRkxERHZqeDZWMCtYa2pwS0VSUnNKWWY1YWRNdm5sb0ZsNDhpbEpnSWhBTnR4aG5kY3IrUUpQdUM4CiAgdmdVQzBkMi85Rk11ZUlWTWIrNDZXVENPanNxcgogIC0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KbGlua2VyZFZlcnNpb246IGluc3RhbGwtY29udHJvbC1wbGFuZS12ZXJzaW9uCnBvbGljeVZhbGlkYXRvcjoKICBjYUJ1bmRsZTogcG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQpwcm9maWxlVmFsaWRhdG9yOgogIGNhQnVuZGxlOiBwcm9maWxlIHZhbGlkYXRvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQpwcm94eToKICBnaWQ6IDQzMjEKICBpbWFnZToKICAgIHZlcnNpb246IGluc3RhbGwtcHJveHktdmVyc2lvbgpwcm94eUluamVjdG9yOgogIGNhQnVuZGxlOiBwcm94eSBpbmplY3RvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQo=\nkind: Secret\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-config-overrides\n  namespace: linkerd\n"
  },
  {
    "path": "cli/cmd/testdata/install_ha_output.golden",
    "content": "---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: linkerd\n    pod-security.kubernetes.io/enforce: privileged\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxl\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGU=\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: linkerd/cli dev-undefined\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 3\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: install-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 1\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: true\n    enablePodDisruptionBudget: true\n    heartbeat: null\n    heartbeatResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: true\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\n            MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\n            ODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\n            a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\n            fcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n            2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\n            Af8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\n            HmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\n            AiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n            51tdrmkHEZRr0qlLSJdHYgEfMzk=\n            -----END CERTIFICATE-----\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 10Mi\n    identityTrustAnchorsPEM: |\n      -----BEGIN CERTIFICATE-----\n      MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n      JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n      MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n      ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n      l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n      uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n      /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n      aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n      IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n      vgUC0d2/9FMueIVMb+46WTCOjsqr\n      -----END CERTIFICATE-----\n    identityTrustDomain: cluster.local\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: null\n    linkerdVersion: install-control-plane-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: policy validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: profile validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: install-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: 100m\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: 250Mi\n          request: 20Mi\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: 4567,4568\n      ignoreOutboundPorts: 4567,4568\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: proxy injector CA bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tolerations: null\n    webhookFailurePolicy: Fail\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQpPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CmEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQoyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCkFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CjUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==\n  key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgpkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  ca-bundle.crt: |-\n    -----BEGIN CERTIFICATE-----\n    MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n    JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n    MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n    ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n    l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n    uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n    /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n    aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n    IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n    vgUC0d2/9FMueIVMb+46WTCOjsqr\n    -----END CERTIFICATE-----\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-identity\n  namespace: linkerd\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - identity\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - identity\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd\n        - -identity-trust-domain=cluster.local\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"10Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-destination\n  namespace: linkerd\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        checksum/config: 33fbe1b09d768bf7ac30457535bac171e47cd5f078b7bda7c15f8e1de2943659\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - destination\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - destination\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=cluster.local\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"50Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=cluster.local\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd\n          annotations:\n            linkerd.io/created-by: linkerd/cli dev-undefined\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:install-control-plane-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            resources:\n              limits:\n                memory: \"250Mi\"\n              requests:\n                cpu: \"100m\"\n                memory: \"50Mi\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-proxy-injector\n  namespace: linkerd\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        checksum/config: 3ac2189b3e87d5a1ef27d6fe1783bb6f8d53c91b470c4ff42e395ba46cd70d4b\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - proxy-injector\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - proxy-injector\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"50Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n---\napiVersion: v1\ndata:\n  linkerd-config-overrides: Y29udHJvbGxlclJlcGxpY2FzOiAzCmRlYnVnQ29udGFpbmVyOgogIGltYWdlOgogICAgdmVyc2lvbjogaW5zdGFsbC1kZWJ1Zy12ZXJzaW9uCmRlcGxveW1lbnRTdHJhdGVneToKICByb2xsaW5nVXBkYXRlOgogICAgbWF4VW5hdmFpbGFibGU6IDEKZGVzdGluYXRpb25SZXNvdXJjZXM6CiAgY3B1OgogICAgbGltaXQ6ICIiCiAgICByZXF1ZXN0OiAxMDBtCiAgZXBoZW1lcmFsLXN0b3JhZ2U6CiAgICBsaW1pdDogIiIKICAgIHJlcXVlc3Q6ICIiCiAgbWVtb3J5OgogICAgbGltaXQ6IDI1ME1pCiAgICByZXF1ZXN0OiA1ME1pCmVuYWJsZVBvZEFudGlBZmZpbml0eTogdHJ1ZQplbmFibGVQb2REaXNydXB0aW9uQnVkZ2V0OiB0cnVlCmhlYXJ0YmVhdFJlc291cmNlczoKICBjcHU6CiAgICBsaW1pdDogIiIKICAgIHJlcXVlc3Q6IDEwMG0KICBlcGhlbWVyYWwtc3RvcmFnZToKICAgIGxpbWl0OiAiIgogICAgcmVxdWVzdDogIiIKICBtZW1vcnk6CiAgICBsaW1pdDogMjUwTWkKICAgIHJlcXVlc3Q6IDUwTWkKaGVhcnRiZWF0U2NoZWR1bGU6IDEgMiAzIDQgNQpoaWdoQXZhaWxhYmlsaXR5OiB0cnVlCmlkZW50aXR5OgogIGlzc3VlcjoKICAgIHRsczoKICAgICAgY3J0UEVNOiB8CiAgICAgICAgLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCiAgICAgICAgTUlJQndEQ0NBV2VnQXdJQkFnSVJBSlJJZ1o4UnRPOEV3ZzFYZXBmOFQ0NHdDZ1lJS29aSXpqMEVBd0l3S1RFbgogICAgICAgIE1DVUdBMVVFQXhNZWFXUmxiblJwZEhrdWJHbHVhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJd01EZ3kKICAgICAgICBPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CiAgICAgICAgYTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFMS9GcAogICAgICAgIGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MmRRdlJhWWFudXhEMzZEdDEKICAgICAgICAyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCiAgICAgICAgQWY4Q0FRQXdIUVlEVlIwT0JCWUVGSTFXbnJxTVlLYUhIT28renB5aWlEcTJwTzBLTUNrR0ExVWRFUVFpTUNDQwogICAgICAgIEhtbGtaVzUwYVhSNUxteHBibXRsY21RdVkyeDFjM1JsY2k1c2IyTmhiREFLQmdncWhrak9QUVFEQWdOSEFEQkUKICAgICAgICBBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CiAgICAgICAgNTF0ZHJta0hFWlJyMHFsTFNKZEhZZ0VmTXprPQogICAgICAgIC0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KICAgICAga2V5UEVNOiB8CiAgICAgICAgLS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCiAgICAgICAgTUhjQ0FRRUVJQUFlOG5mYnpadTljL09CMis4eEpNMEZ6N05Vd1RRYXp1bGtGTnM0VEk1K29Bb0dDQ3FHU000OQogICAgICAgIEF3RUhvVVFEUWdBRTEvRnBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzIKICAgICAgICBkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KICAgICAgICAtLS0tLUVORCBFQyBQUklWQVRFIEtFWS0tLS0tCmlkZW50aXR5UmVzb3VyY2VzOgogIGNwdToKICAgIGxpbWl0OiAiIgogICAgcmVxdWVzdDogMTAwbQogIGVwaGVtZXJhbC1zdG9yYWdlOgogICAgbGltaXQ6ICIiCiAgICByZXF1ZXN0OiAiIgogIG1lbW9yeToKICAgIGxpbWl0OiAyNTBNaQogICAgcmVxdWVzdDogMTBNaQppZGVudGl0eVRydXN0QW5jaG9yc1BFTTogfAogIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogIE1JSUJ3VENDQVdhZ0F3SUJBZ0lRZURacDVsRGFJeWdRNVVmTUtackZBVEFLQmdncWhrak9QUVFEQWpBcE1TY3cKICBKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3SGhjTk1qQXdPREk0CiAgTURjeE1qUTNXaGNOTXpBd09ESTJNRGN4TWpRM1dqQXBNU2N3SlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1cgogIFpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFScWM3MFoKICBsMXZndzc5cmpCNXVTSVRJQ1VBNkd5ZnZTRmZjdUlpczdCL1hGU2trd0FIVTVTL3MxQUFQK1IwVFg3SEJXVUM0CiAgdWFHNFdXc2l3SktObjdtZ28zQXdiakFPQmdOVkhROEJBZjhFQkFNQ0FRWXdFZ1lEVlIwVEFRSC9CQWd3QmdFQgogIC93SUJBVEFkQmdOVkhRNEVGZ1FVNVl0alZWUGZkN0k3TkxIc24yQzI2RUJ5R1Ywd0tRWURWUjBSQkNJd0lJSWUKICBhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUFvR0NDcUdTTTQ5QkFNQ0Ewa0FNRVlDCiAgSVFDTjdsQkZMRER2ang2VjArWGtqcEtFUlJzSllmNWFkTXZubG9GbDQ4aWxKZ0loQU50eGhuZGNyK1FKUHVDOAogIHZnVUMwZDIvOUZNdWVJVk1iKzQ2V1RDT2pzcXIKICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCmltYWdlUHVsbFNlY3JldHM6IG51bGwKbGlua2VyZFZlcnNpb246IGluc3RhbGwtY29udHJvbC1wbGFuZS12ZXJzaW9uCnBvbGljeVZhbGlkYXRvcjoKICBjYUJ1bmRsZTogcG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQpwcm9maWxlVmFsaWRhdG9yOgogIGNhQnVuZGxlOiBwcm9maWxlIHZhbGlkYXRvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQpwcm94eToKICBpbWFnZToKICAgIHZlcnNpb246IGluc3RhbGwtcHJveHktdmVyc2lvbgogIHJlc291cmNlczoKICAgIGNwdToKICAgICAgcmVxdWVzdDogMTAwbQogICAgbWVtb3J5OgogICAgICBsaW1pdDogMjUwTWkKICAgICAgcmVxdWVzdDogMjBNaQpwcm94eUluamVjdG9yOgogIGNhQnVuZGxlOiBwcm94eSBpbmplY3RvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQpwcm94eUluamVjdG9yUmVzb3VyY2VzOgogIGNwdToKICAgIGxpbWl0OiAiIgogICAgcmVxdWVzdDogMTAwbQogIGVwaGVtZXJhbC1zdG9yYWdlOgogICAgbGltaXQ6ICIiCiAgICByZXF1ZXN0OiAiIgogIG1lbW9yeToKICAgIGxpbWl0OiAyNTBNaQogICAgcmVxdWVzdDogNTBNaQp3ZWJob29rRmFpbHVyZVBvbGljeTogRmFpbAo=\nkind: Secret\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-config-overrides\n  namespace: linkerd\n"
  },
  {
    "path": "cli/cmd/testdata/install_ha_with_overrides_output.golden",
    "content": "---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: linkerd\n    pod-security.kubernetes.io/enforce: privileged\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxl\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGU=\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: linkerd/cli dev-undefined\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 2\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: install-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 1\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: true\n    enablePodDisruptionBudget: true\n    heartbeat: null\n    heartbeatResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: true\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\n            MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\n            ODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\n            a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\n            fcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n            2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\n            Af8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\n            HmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\n            AiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n            51tdrmkHEZRr0qlLSJdHYgEfMzk=\n            -----END CERTIFICATE-----\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 10Mi\n    identityTrustAnchorsPEM: |\n      -----BEGIN CERTIFICATE-----\n      MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n      JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n      MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n      ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n      l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n      uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n      /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n      aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n      IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n      vgUC0d2/9FMueIVMb+46WTCOjsqr\n      -----END CERTIFICATE-----\n    identityTrustDomain: cluster.local\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: null\n    linkerdVersion: install-control-plane-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: policy validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: profile validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: install-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: 400m\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: 250Mi\n          request: 300Mi\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: 4567,4568\n      ignoreOutboundPorts: 4567,4568\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: proxy injector CA bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tolerations: null\n    webhookFailurePolicy: Fail\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQpPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CmEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQoyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCkFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CjUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==\n  key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgpkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  ca-bundle.crt: |-\n    -----BEGIN CERTIFICATE-----\n    MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n    JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n    MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n    ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n    l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n    uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n    /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n    aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n    IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n    vgUC0d2/9FMueIVMb+46WTCOjsqr\n    -----END CERTIFICATE-----\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-identity\n  namespace: linkerd\nspec:\n  replicas: 2\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - identity\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - identity\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd\n        - -identity-trust-domain=cluster.local\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"10Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"400m\"\n            memory: \"300Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"400m\"\n            memory: \"300Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-destination\n  namespace: linkerd\nspec:\n  replicas: 2\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        checksum/config: 33fbe1b09d768bf7ac30457535bac171e47cd5f078b7bda7c15f8e1de2943659\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - destination\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - destination\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"400m\"\n            memory: \"300Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=cluster.local\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"50Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=cluster.local\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"400m\"\n            memory: \"300Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd\n          annotations:\n            linkerd.io/created-by: linkerd/cli dev-undefined\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:install-control-plane-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            resources:\n              limits:\n                memory: \"250Mi\"\n              requests:\n                cpu: \"100m\"\n                memory: \"50Mi\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-proxy-injector\n  namespace: linkerd\nspec:\n  replicas: 2\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        checksum/config: 3ac2189b3e87d5a1ef27d6fe1783bb6f8d53c91b470c4ff42e395ba46cd70d4b\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - proxy-injector\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - proxy-injector\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"400m\"\n            memory: \"300Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"50Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"400m\"\n            memory: \"300Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n---\napiVersion: v1\ndata:\n  linkerd-config-overrides: Y29udHJvbGxlclJlcGxpY2FzOiAyCmRlYnVnQ29udGFpbmVyOgogIGltYWdlOgogICAgdmVyc2lvbjogaW5zdGFsbC1kZWJ1Zy12ZXJzaW9uCmRlcGxveW1lbnRTdHJhdGVneToKICByb2xsaW5nVXBkYXRlOgogICAgbWF4VW5hdmFpbGFibGU6IDEKZGVzdGluYXRpb25SZXNvdXJjZXM6CiAgY3B1OgogICAgbGltaXQ6ICIiCiAgICByZXF1ZXN0OiAxMDBtCiAgZXBoZW1lcmFsLXN0b3JhZ2U6CiAgICBsaW1pdDogIiIKICAgIHJlcXVlc3Q6ICIiCiAgbWVtb3J5OgogICAgbGltaXQ6IDI1ME1pCiAgICByZXF1ZXN0OiA1ME1pCmVuYWJsZVBvZEFudGlBZmZpbml0eTogdHJ1ZQplbmFibGVQb2REaXNydXB0aW9uQnVkZ2V0OiB0cnVlCmhlYXJ0YmVhdFJlc291cmNlczoKICBjcHU6CiAgICBsaW1pdDogIiIKICAgIHJlcXVlc3Q6IDEwMG0KICBlcGhlbWVyYWwtc3RvcmFnZToKICAgIGxpbWl0OiAiIgogICAgcmVxdWVzdDogIiIKICBtZW1vcnk6CiAgICBsaW1pdDogMjUwTWkKICAgIHJlcXVlc3Q6IDUwTWkKaGVhcnRiZWF0U2NoZWR1bGU6IDEgMiAzIDQgNQpoaWdoQXZhaWxhYmlsaXR5OiB0cnVlCmlkZW50aXR5OgogIGlzc3VlcjoKICAgIHRsczoKICAgICAgY3J0UEVNOiB8CiAgICAgICAgLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCiAgICAgICAgTUlJQndEQ0NBV2VnQXdJQkFnSVJBSlJJZ1o4UnRPOEV3ZzFYZXBmOFQ0NHdDZ1lJS29aSXpqMEVBd0l3S1RFbgogICAgICAgIE1DVUdBMVVFQXhNZWFXUmxiblJwZEhrdWJHbHVhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJd01EZ3kKICAgICAgICBPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CiAgICAgICAgYTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFMS9GcAogICAgICAgIGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MmRRdlJhWWFudXhEMzZEdDEKICAgICAgICAyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCiAgICAgICAgQWY4Q0FRQXdIUVlEVlIwT0JCWUVGSTFXbnJxTVlLYUhIT28renB5aWlEcTJwTzBLTUNrR0ExVWRFUVFpTUNDQwogICAgICAgIEhtbGtaVzUwYVhSNUxteHBibXRsY21RdVkyeDFjM1JsY2k1c2IyTmhiREFLQmdncWhrak9QUVFEQWdOSEFEQkUKICAgICAgICBBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CiAgICAgICAgNTF0ZHJta0hFWlJyMHFsTFNKZEhZZ0VmTXprPQogICAgICAgIC0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KICAgICAga2V5UEVNOiB8CiAgICAgICAgLS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCiAgICAgICAgTUhjQ0FRRUVJQUFlOG5mYnpadTljL09CMis4eEpNMEZ6N05Vd1RRYXp1bGtGTnM0VEk1K29Bb0dDQ3FHU000OQogICAgICAgIEF3RUhvVVFEUWdBRTEvRnBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzIKICAgICAgICBkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KICAgICAgICAtLS0tLUVORCBFQyBQUklWQVRFIEtFWS0tLS0tCmlkZW50aXR5UmVzb3VyY2VzOgogIGNwdToKICAgIGxpbWl0OiAiIgogICAgcmVxdWVzdDogMTAwbQogIGVwaGVtZXJhbC1zdG9yYWdlOgogICAgbGltaXQ6ICIiCiAgICByZXF1ZXN0OiAiIgogIG1lbW9yeToKICAgIGxpbWl0OiAyNTBNaQogICAgcmVxdWVzdDogMTBNaQppZGVudGl0eVRydXN0QW5jaG9yc1BFTTogfAogIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogIE1JSUJ3VENDQVdhZ0F3SUJBZ0lRZURacDVsRGFJeWdRNVVmTUtackZBVEFLQmdncWhrak9QUVFEQWpBcE1TY3cKICBKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3SGhjTk1qQXdPREk0CiAgTURjeE1qUTNXaGNOTXpBd09ESTJNRGN4TWpRM1dqQXBNU2N3SlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1cgogIFpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFScWM3MFoKICBsMXZndzc5cmpCNXVTSVRJQ1VBNkd5ZnZTRmZjdUlpczdCL1hGU2trd0FIVTVTL3MxQUFQK1IwVFg3SEJXVUM0CiAgdWFHNFdXc2l3SktObjdtZ28zQXdiakFPQmdOVkhROEJBZjhFQkFNQ0FRWXdFZ1lEVlIwVEFRSC9CQWd3QmdFQgogIC93SUJBVEFkQmdOVkhRNEVGZ1FVNVl0alZWUGZkN0k3TkxIc24yQzI2RUJ5R1Ywd0tRWURWUjBSQkNJd0lJSWUKICBhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUFvR0NDcUdTTTQ5QkFNQ0Ewa0FNRVlDCiAgSVFDTjdsQkZMRER2ang2VjArWGtqcEtFUlJzSllmNWFkTXZubG9GbDQ4aWxKZ0loQU50eGhuZGNyK1FKUHVDOAogIHZnVUMwZDIvOUZNdWVJVk1iKzQ2V1RDT2pzcXIKICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCmltYWdlUHVsbFNlY3JldHM6IG51bGwKbGlua2VyZFZlcnNpb246IGluc3RhbGwtY29udHJvbC1wbGFuZS12ZXJzaW9uCnBvbGljeVZhbGlkYXRvcjoKICBjYUJ1bmRsZTogcG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQpwcm9maWxlVmFsaWRhdG9yOgogIGNhQnVuZGxlOiBwcm9maWxlIHZhbGlkYXRvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQpwcm94eToKICBpbWFnZToKICAgIHZlcnNpb246IGluc3RhbGwtcHJveHktdmVyc2lvbgogIHJlc291cmNlczoKICAgIGNwdToKICAgICAgcmVxdWVzdDogNDAwbQogICAgbWVtb3J5OgogICAgICBsaW1pdDogMjUwTWkKICAgICAgcmVxdWVzdDogMzAwTWkKcHJveHlJbmplY3RvcjoKICBjYUJ1bmRsZTogcHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJveHlJbmplY3RvclJlc291cmNlczoKICBjcHU6CiAgICBsaW1pdDogIiIKICAgIHJlcXVlc3Q6IDEwMG0KICBlcGhlbWVyYWwtc3RvcmFnZToKICAgIGxpbWl0OiAiIgogICAgcmVxdWVzdDogIiIKICBtZW1vcnk6CiAgICBsaW1pdDogMjUwTWkKICAgIHJlcXVlc3Q6IDUwTWkKd2ViaG9va0ZhaWx1cmVQb2xpY3k6IEZhaWwK\nkind: Secret\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-config-overrides\n  namespace: linkerd\n"
  },
  {
    "path": "cli/cmd/testdata/install_heartbeat_disabled_output.golden",
    "content": "---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: linkerd\n    pod-security.kubernetes.io/enforce: privileged\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGU=\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n\n\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: linkerd/cli dev-undefined\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 1\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: install-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 25%\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: true\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    enablePodDisruptionBudget: false\n    heartbeat: null\n    heartbeatResources: null\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: false\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\n            MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\n            ODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\n            a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\n            fcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n            2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\n            Af8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\n            HmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\n            AiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n            51tdrmkHEZRr0qlLSJdHYgEfMzk=\n            -----END CERTIFICATE-----\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources: null\n    identityTrustAnchorsPEM: |\n      -----BEGIN CERTIFICATE-----\n      MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n      JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n      MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n      ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n      l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n      uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n      /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n      aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n      IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n      vgUC0d2/9FMueIVMb+46WTCOjsqr\n      -----END CERTIFICATE-----\n    identityTrustDomain: cluster.local\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: []\n    linkerdVersion: install-control-plane-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: policy validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: profile validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: install-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: 4567,4568\n      ignoreOutboundPorts: 4567,4568\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: proxy injector CA bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tolerations: null\n    webhookFailurePolicy: Ignore\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQpPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CmEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQoyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCkFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CjUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==\n  key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgpkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  ca-bundle.crt: |-\n    -----BEGIN CERTIFICATE-----\n    MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n    JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n    MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n    ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n    l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n    uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n    /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n    aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n    IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n    vgUC0d2/9FMueIVMb+46WTCOjsqr\n    -----END CERTIFICATE-----\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-identity\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd\n        - -identity-trust-domain=cluster.local\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-destination\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: a39770e2a1ac1f4bb452509e52a4ffd176bcb034f1ce906491f2649e28c67319\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=cluster.local\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=cluster.local\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-proxy-injector\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: cd0cf730780be444ab96a4a835a244033ffb7c8cf4a8796d0e6ae5c72aa9ff31\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\napiVersion: v1\ndata:\n  linkerd-config-overrides: ZGVidWdDb250YWluZXI6CiAgaW1hZ2U6CiAgICB2ZXJzaW9uOiBpbnN0YWxsLWRlYnVnLXZlcnNpb24KZGlzYWJsZUhlYXJ0QmVhdDogdHJ1ZQpoZWFydGJlYXRTY2hlZHVsZTogMSAyIDMgNCA1CmlkZW50aXR5OgogIGlzc3VlcjoKICAgIHRsczoKICAgICAgY3J0UEVNOiB8CiAgICAgICAgLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCiAgICAgICAgTUlJQndEQ0NBV2VnQXdJQkFnSVJBSlJJZ1o4UnRPOEV3ZzFYZXBmOFQ0NHdDZ1lJS29aSXpqMEVBd0l3S1RFbgogICAgICAgIE1DVUdBMVVFQXhNZWFXUmxiblJwZEhrdWJHbHVhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJd01EZ3kKICAgICAgICBPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CiAgICAgICAgYTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFMS9GcAogICAgICAgIGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MmRRdlJhWWFudXhEMzZEdDEKICAgICAgICAyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCiAgICAgICAgQWY4Q0FRQXdIUVlEVlIwT0JCWUVGSTFXbnJxTVlLYUhIT28renB5aWlEcTJwTzBLTUNrR0ExVWRFUVFpTUNDQwogICAgICAgIEhtbGtaVzUwYVhSNUxteHBibXRsY21RdVkyeDFjM1JsY2k1c2IyTmhiREFLQmdncWhrak9QUVFEQWdOSEFEQkUKICAgICAgICBBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CiAgICAgICAgNTF0ZHJta0hFWlJyMHFsTFNKZEhZZ0VmTXprPQogICAgICAgIC0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KICAgICAga2V5UEVNOiB8CiAgICAgICAgLS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCiAgICAgICAgTUhjQ0FRRUVJQUFlOG5mYnpadTljL09CMis4eEpNMEZ6N05Vd1RRYXp1bGtGTnM0VEk1K29Bb0dDQ3FHU000OQogICAgICAgIEF3RUhvVVFEUWdBRTEvRnBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzIKICAgICAgICBkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KICAgICAgICAtLS0tLUVORCBFQyBQUklWQVRFIEtFWS0tLS0tCmlkZW50aXR5VHJ1c3RBbmNob3JzUEVNOiB8CiAgLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCiAgTUlJQndUQ0NBV2FnQXdJQkFnSVFlRFpwNWxEYUl5Z1E1VWZNS1pyRkFUQUtCZ2dxaGtqT1BRUURBakFwTVNjdwogIEpRWURWUVFERXg1cFpHVnVkR2wwZVM1c2FXNXJaWEprTG1Oc2RYTjBaWEl1Ykc5allXd3dIaGNOTWpBd09ESTQKICBNRGN4TWpRM1doY05NekF3T0RJMk1EY3hNalEzV2pBcE1TY3dKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyCiAgWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVJxYzcwWgogIGwxdmd3NzlyakI1dVNJVElDVUE2R3lmdlNGZmN1SWlzN0IvWEZTa2t3QUhVNVMvczFBQVArUjBUWDdIQldVQzQKICB1YUc0V1dzaXdKS05uN21nbzNBd2JqQU9CZ05WSFE4QkFmOEVCQU1DQVFZd0VnWURWUjBUQVFIL0JBZ3dCZ0VCCiAgL3dJQkFUQWRCZ05WSFE0RUZnUVU1WXRqVlZQZmQ3STdOTEhzbjJDMjZFQnlHVjB3S1FZRFZSMFJCQ0l3SUlJZQogIGFXUmxiblJwZEhrdWJHbHVhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNQW9HQ0NxR1NNNDlCQU1DQTBrQU1FWUMKICBJUUNON2xCRkxERHZqeDZWMCtYa2pwS0VSUnNKWWY1YWRNdm5sb0ZsNDhpbEpnSWhBTnR4aG5kY3IrUUpQdUM4CiAgdmdVQzBkMi85Rk11ZUlWTWIrNDZXVENPanNxcgogIC0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KbGlua2VyZFZlcnNpb246IGluc3RhbGwtY29udHJvbC1wbGFuZS12ZXJzaW9uCnBvbGljeVZhbGlkYXRvcjoKICBjYUJ1bmRsZTogcG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQpwcm9maWxlVmFsaWRhdG9yOgogIGNhQnVuZGxlOiBwcm9maWxlIHZhbGlkYXRvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQpwcm94eToKICBpbWFnZToKICAgIHZlcnNpb246IGluc3RhbGwtcHJveHktdmVyc2lvbgpwcm94eUluamVjdG9yOgogIGNhQnVuZGxlOiBwcm94eSBpbmplY3RvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQo=\nkind: Secret\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-config-overrides\n  namespace: linkerd\n"
  },
  {
    "path": "cli/cmd/testdata/install_helm_control_plane_output.golden",
    "content": "---\n# Source: linkerd-control-plane/templates/namespace.yaml\n---\n# Source: linkerd-control-plane/templates/identity-rbac.yaml\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-dev-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd-dev\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n---\n# Source: linkerd-control-plane/templates/destination-rbac.yaml\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-dev-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd-dev\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd-dev\n      path: \"/\"\n    caBundle: dGVzdC1wcm9maWxlLXZhbGlkYXRvci1jYS1idW5kbGU=\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd-dev\n      path: \"/\"\n    caBundle: dGVzdC1wcm9maWxlLXZhbGlkYXRvci1jYS1idW5kbGU=\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd-dev\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd-dev\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd-dev\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd-dev\n---\n# Source: linkerd-control-plane/templates/heartbeat-rbac.yaml\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd-dev\n---\n# Source: linkerd-control-plane/templates/podmonitor.yaml\n\n---\n# Source: linkerd-control-plane/templates/proxy-injector-rbac.yaml\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-dev-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd-dev\n      path: \"/\"\n    caBundle: dGVzdC1wcm94eS1pbmplY3Rvci1jYS1idW5kbGU=\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\n# Source: linkerd-control-plane/templates/psp.yaml\n---\n# Source: linkerd-control-plane/templates/config.yaml\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: \"\"\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 1\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: test-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 25%\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    enablePodDisruptionBudget: false\n    heartbeat: null\n    heartbeatResources: null\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: false\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: test-crt-pem\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources: null\n    identityTrustAnchorsPEM: test-trust-anchor\n    identityTrustDomain: test.trust.domain\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: []\n    linkerdVersion: linkerd-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: test-profile-validator-ca-bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: test-profile-validator-ca-bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: test-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: \"222\"\n      ignoreOutboundPorts: \"111\"\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: test-proxy-injector-ca-bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tap:\n      caBundle: test-tap-ca-bundle\n      externalSecret: true\n    tolerations: null\n    webhookFailurePolicy: Ignore\n---\n# Source: linkerd-control-plane/templates/config-rbac.yaml\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n# Source: linkerd-control-plane/templates/identity.yaml\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\ndata:\n  crt.pem: dGVzdC1jcnQtcGVt\n  key.pem: dGVzdC1rZXktcGVt\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\ndata:\n  ca-bundle.crt: |-\n    test-trust-anchor\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  name: linkerd-identity\n  namespace: linkerd-dev\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd-dev\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/helm linkerd-version\n        linkerd.io/proxy-version: test-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: f8ebf807fa1cf5bf3b40e94680a5fc91593782385f28c96eae7bb6672dba375e\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd-dev\n        linkerd.io/workload-ns: linkerd-dev\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd-dev\n        - -identity-trust-domain=test.trust.domain\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd-dev.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd-dev.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd-dev\n        - name: _l5d_trustdomain\n          value: test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,222\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n# Source: linkerd-control-plane/templates/destination.yaml\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  name: linkerd-destination\n  namespace: linkerd-dev\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd-dev\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: 024027c6115d84206f869e9becc360639cf77cf24b0e9c6c8920138f7ea33fd2\n        linkerd.io/created-by: linkerd/helm linkerd-version\n        linkerd.io/proxy-version: test-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: f8ebf807fa1cf5bf3b40e94680a5fc91593782385f28c96eae7bb6672dba375e\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd-dev\n        linkerd.io/workload-ns: linkerd-dev\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd-dev\n        - name: _l5d_trustdomain\n          value: test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd-dev.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd-dev\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=test.trust.domain\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd-dev\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=test.trust.domain\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,222\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n# Source: linkerd-control-plane/templates/heartbeat.yaml\n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd-dev\n          annotations:\n            linkerd.io/created-by: linkerd/helm linkerd-version\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:linkerd-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd-dev\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n# Source: linkerd-control-plane/templates/proxy-injector.yaml\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: a03c5a5d4ed8cae24c45d89569246c3e44eded6915cbdc71698e8008d3587d59\n        linkerd.io/created-by: linkerd/helm linkerd-version\n        linkerd.io/proxy-version: test-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: f8ebf807fa1cf5bf3b40e94680a5fc91593782385f28c96eae7bb6672dba375e\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd-dev\n        linkerd.io/workload-ns: linkerd-dev\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd-dev.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd-dev.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd-dev\n        - name: _l5d_trustdomain\n          value: test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd-dev.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd-dev\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,222\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n"
  },
  {
    "path": "cli/cmd/testdata/install_helm_control_plane_output_ha.golden",
    "content": "---\n# Source: linkerd-control-plane/templates/namespace.yaml\n---\n# Source: linkerd-control-plane/templates/identity-rbac.yaml\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-dev-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd-dev\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n---\n# Source: linkerd-control-plane/templates/destination-rbac.yaml\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-dev-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd-dev\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd-dev\n      path: \"/\"\n    caBundle: dGVzdC1wcm9maWxlLXZhbGlkYXRvci1jYS1idW5kbGU=\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd-dev\n      path: \"/\"\n    caBundle: dGVzdC1wcm9maWxlLXZhbGlkYXRvci1jYS1idW5kbGU=\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd-dev\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd-dev\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd-dev\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd-dev\n---\n# Source: linkerd-control-plane/templates/heartbeat-rbac.yaml\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd-dev\n---\n# Source: linkerd-control-plane/templates/podmonitor.yaml\n\n---\n# Source: linkerd-control-plane/templates/proxy-injector-rbac.yaml\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-dev-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd-dev\n      path: \"/\"\n    caBundle: dGVzdC1wcm94eS1pbmplY3Rvci1jYS1idW5kbGU=\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\n# Source: linkerd-control-plane/templates/psp.yaml\n---\n# Source: linkerd-control-plane/templates/config.yaml\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: \"\"\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 3\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: test-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 1\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: true\n    enablePodDisruptionBudget: true\n    heartbeat: null\n    heartbeatResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: true\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: test-crt-pem\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 10Mi\n    identityTrustAnchorsPEM: test-trust-anchor\n    identityTrustDomain: test.trust.domain\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: null\n    linkerdVersion: linkerd-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: test-profile-validator-ca-bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: test-profile-validator-ca-bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: test-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: 100m\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: 250Mi\n          request: 20Mi\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: \"222\"\n      ignoreOutboundPorts: \"111\"\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: test-proxy-injector-ca-bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tap:\n      caBundle: test-tap-ca-bundle\n      externalSecret: true\n    tolerations: null\n    webhookFailurePolicy: Fail\n---\n# Source: linkerd-control-plane/templates/config-rbac.yaml\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n# Source: linkerd-control-plane/templates/identity.yaml\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\ndata:\n  crt.pem: dGVzdC1jcnQtcGVt\n  key.pem: dGVzdC1rZXktcGVt\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\ndata:\n  ca-bundle.crt: |-\n    test-trust-anchor\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  name: linkerd-identity\n  namespace: linkerd-dev\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd-dev\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/helm linkerd-version\n        linkerd.io/proxy-version: test-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: f8ebf807fa1cf5bf3b40e94680a5fc91593782385f28c96eae7bb6672dba375e\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd-dev\n        linkerd.io/workload-ns: linkerd-dev\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - identity\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - identity\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd-dev\n        - -identity-trust-domain=test.trust.domain\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"10Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd-dev.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd-dev.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd-dev\n        - name: _l5d_trustdomain\n          value: test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,222\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n# Source: linkerd-control-plane/templates/destination.yaml\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  name: linkerd-destination\n  namespace: linkerd-dev\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd-dev\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        checksum/config: fd8add3a34efa33eae90f8e70a5e0925728e8cf48d5607c4c03adb27ed005fe1\n        linkerd.io/created-by: linkerd/helm linkerd-version\n        linkerd.io/proxy-version: test-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: f8ebf807fa1cf5bf3b40e94680a5fc91593782385f28c96eae7bb6672dba375e\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd-dev\n        linkerd.io/workload-ns: linkerd-dev\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - destination\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - destination\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd-dev\n        - name: _l5d_trustdomain\n          value: test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd-dev.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd-dev\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=test.trust.domain\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"50Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd-dev\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=test.trust.domain\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,222\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n# Source: linkerd-control-plane/templates/heartbeat.yaml\n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd-dev\n          annotations:\n            linkerd.io/created-by: linkerd/helm linkerd-version\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:linkerd-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd-dev\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            resources:\n              limits:\n                memory: \"250Mi\"\n              requests:\n                cpu: \"100m\"\n                memory: \"50Mi\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n# Source: linkerd-control-plane/templates/proxy-injector.yaml\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        checksum/config: fd3a1b10afd0c6c39c7c63f51aece4a849b0e47ba992a6612a1a5fa99211b084\n        linkerd.io/created-by: linkerd/helm linkerd-version\n        linkerd.io/proxy-version: test-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: f8ebf807fa1cf5bf3b40e94680a5fc91593782385f28c96eae7bb6672dba375e\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd-dev\n        linkerd.io/workload-ns: linkerd-dev\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - proxy-injector\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - proxy-injector\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd-dev.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd-dev.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd-dev\n        - name: _l5d_trustdomain\n          value: test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd-dev.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd-dev\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"50Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,222\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n"
  },
  {
    "path": "cli/cmd/testdata/install_helm_control_plane_output_ha_with_gid.golden",
    "content": "---\n# Source: linkerd-control-plane/templates/namespace.yaml\n---\n# Source: linkerd-control-plane/templates/identity-rbac.yaml\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-dev-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd-dev\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n---\n# Source: linkerd-control-plane/templates/destination-rbac.yaml\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-dev-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd-dev\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd-dev\n      path: \"/\"\n    caBundle: dGVzdC1wcm9maWxlLXZhbGlkYXRvci1jYS1idW5kbGU=\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd-dev\n      path: \"/\"\n    caBundle: dGVzdC1wcm9maWxlLXZhbGlkYXRvci1jYS1idW5kbGU=\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd-dev\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd-dev\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd-dev\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd-dev\n---\n# Source: linkerd-control-plane/templates/heartbeat-rbac.yaml\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd-dev\n---\n# Source: linkerd-control-plane/templates/podmonitor.yaml\n\n---\n# Source: linkerd-control-plane/templates/proxy-injector-rbac.yaml\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-dev-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd-dev\n      path: \"/\"\n    caBundle: dGVzdC1wcm94eS1pbmplY3Rvci1jYS1idW5kbGU=\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\n# Source: linkerd-control-plane/templates/psp.yaml\n---\n# Source: linkerd-control-plane/templates/config.yaml\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: \"\"\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: 1324\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 3\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: test-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 1\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: true\n    enablePodDisruptionBudget: true\n    heartbeat: null\n    heartbeatResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: true\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: test-crt-pem\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 10Mi\n    identityTrustAnchorsPEM: test-trust-anchor\n    identityTrustDomain: test.trust.domain\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: null\n    linkerdVersion: linkerd-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: test-profile-validator-ca-bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: test-profile-validator-ca-bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: 4231\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: test-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: 100m\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: 250Mi\n          request: 20Mi\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: \"222\"\n      ignoreOutboundPorts: \"111\"\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: test-proxy-injector-ca-bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tap:\n      caBundle: test-tap-ca-bundle\n      externalSecret: true\n    tolerations: null\n    webhookFailurePolicy: Fail\n---\n# Source: linkerd-control-plane/templates/config-rbac.yaml\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n# Source: linkerd-control-plane/templates/identity.yaml\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\ndata:\n  crt.pem: dGVzdC1jcnQtcGVt\n  key.pem: dGVzdC1rZXktcGVt\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\ndata:\n  ca-bundle.crt: |-\n    test-trust-anchor\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  name: linkerd-identity\n  namespace: linkerd-dev\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd-dev\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/helm linkerd-version\n        linkerd.io/proxy-version: test-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: f8ebf807fa1cf5bf3b40e94680a5fc91593782385f28c96eae7bb6672dba375e\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd-dev\n        linkerd.io/workload-ns: linkerd-dev\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - identity\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - identity\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd-dev\n        - -identity-trust-domain=test.trust.domain\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"10Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 1324\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd-dev.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd-dev.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd-dev\n        - name: _l5d_trustdomain\n          value: test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          runAsGroup: 4231\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --proxy-gid\n        - \"4231\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,222\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n# Source: linkerd-control-plane/templates/destination.yaml\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  name: linkerd-destination\n  namespace: linkerd-dev\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd-dev\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        checksum/config: fd8add3a34efa33eae90f8e70a5e0925728e8cf48d5607c4c03adb27ed005fe1\n        linkerd.io/created-by: linkerd/helm linkerd-version\n        linkerd.io/proxy-version: test-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: f8ebf807fa1cf5bf3b40e94680a5fc91593782385f28c96eae7bb6672dba375e\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd-dev\n        linkerd.io/workload-ns: linkerd-dev\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - destination\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - destination\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd-dev\n        - name: _l5d_trustdomain\n          value: test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd-dev.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          runAsGroup: 4231\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd-dev\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=test.trust.domain\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"50Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 1324\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 1324\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd-dev\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=test.trust.domain\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 1324\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --proxy-gid\n        - \"4231\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,222\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n# Source: linkerd-control-plane/templates/heartbeat.yaml\n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd-dev\n          annotations:\n            linkerd.io/created-by: linkerd/helm linkerd-version\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:linkerd-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd-dev\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            resources:\n              limits:\n                memory: \"250Mi\"\n              requests:\n                cpu: \"100m\"\n                memory: \"50Mi\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              runAsGroup: 1324\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n# Source: linkerd-control-plane/templates/proxy-injector.yaml\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        checksum/config: fd3a1b10afd0c6c39c7c63f51aece4a849b0e47ba992a6612a1a5fa99211b084\n        linkerd.io/created-by: linkerd/helm linkerd-version\n        linkerd.io/proxy-version: test-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: f8ebf807fa1cf5bf3b40e94680a5fc91593782385f28c96eae7bb6672dba375e\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd-dev\n        linkerd.io/workload-ns: linkerd-dev\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - proxy-injector\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - proxy-injector\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd-dev.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd-dev.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd-dev\n        - name: _l5d_trustdomain\n          value: test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd-dev.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          runAsGroup: 4231\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd-dev\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"50Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 1324\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --proxy-gid\n        - \"4231\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,222\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n"
  },
  {
    "path": "cli/cmd/testdata/install_helm_crds_output.golden",
    "content": "---\n# Source: linkerd-crds/templates/policy/authorization-policy.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: authorizationpolicies.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: AuthorizationPolicy\n    plural: authorizationpolicies\n    singular: authorizationpolicy\n    shortNames: [authzpolicy]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied server\n                resources.\n              type: object\n              required: [targetRef, requiredAuthenticationRefs]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the authorization\n                    policy applies.\n                  type: object\n                  required: [kind, name]\n                  # Modified from the gateway API.\n                  # Copyright 2020 The Kubernetes Authors\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: >-\n                        Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                requiredAuthenticationRefs:\n                  description: >-\n                    RequiredAuthenticationRefs enumerates a set of required\n                    authentications. ALL authentications must be satisfied for\n                    the authorization to apply. If any of the referred objects\n                    cannot be found, the authorization will be ignored.\n                  type: array\n                  items:\n                    type: object\n                    required: [kind, name]\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n---\n# Source: linkerd-crds/templates/policy/egress-network.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: egressnetworks.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  names:\n    categories:\n    - policy\n    kind: EgressNetwork\n    listKind: EgressNetworkList\n    plural: egressnetworks\n    singular: egressnetwork\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An EgressNetwork captures traffic to egress destinations\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              trafficPolicy:\n                description: >-\n                  This field controls the traffic policy enforced upon traffic\n                  that does not match any explicit route resources associated\n                  with an instance of this object. The values that are allowed\n                  currently are:\n                   - Allow - permits all traffic, even if it has not been\n                                explicitly described via attaching an xRoute\n                                resources.\n                   - Deny -  blocks all traffic that has not been described via\n                                attaching an xRoute resource.\n                type: string\n                enum:\n                - Allow\n                - Deny\n              networks:\n                type: array\n                items:\n                  type: object\n                  required: [cidr]\n                  properties:\n                    cidr:\n                      description: >-\n                        The CIDR of the network to be authorized.\n                      type: string\n                    except:\n                      description: >-\n                        A list of IP networks/addresses not to be included in\n                        the above `cidr`.\n                      type: array\n                      items:\n                        type: string\n            type: object\n            required:\n            - trafficPolicy\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n---\n# Source: linkerd-crds/templates/policy/http-local-ratelimit-policy.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httplocalratelimitpolicies.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPLocalRateLimitPolicy\n    listKind: HTTPLocalRateLimitPolicyList\n    plural: httplocalratelimitpolicies\n    singular: httplocalratelimitpolicy\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      subresources:\n        status: {}\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required: [targetRef]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the rate limit\n                    policy applies. Only Server is allowed.\n                  type: object\n                  required: [kind, name]\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                total:\n                  description: >-\n                    Overall rate-limit, which all traffic coming to this\n                    target should abide.\n                    If unset no overall limit is applied.\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                identity:\n                  description: >-\n                    Fairness for individual identities; each separate client,\n                    grouped by identity, will have this rate-limit. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                overrides:\n                  description: >-\n                    Overrides for traffic from a specific client. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: array\n                  items:\n                    type: object\n                    required: [requestsPerSecond, clientRefs]\n                    properties:\n                      requestsPerSecond:\n                        format: int64\n                        type: integer\n                      clientRefs:\n                        type: array\n                        items:\n                          type: object\n                          required: [kind, name]\n                          properties:\n                            group:\n                              description: >-\n                                Group is the group of the referent. When empty, the\n                                Kubernetes core API group is inferred.\n                              maxLength: 253\n                              pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                              type: string\n                            kind:\n                              description: Kind is the kind of the referent.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                              type: string\n                            namespace:\n                              description: >-\n                                  Namespace is the namespace of the referent.\n                                  When unspecified (or empty string), this refers to the\n                                  local namespace of the Policy.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                              type: string\n                            name:\n                              description: Name is the name of the referent.\n                              maxLength: 253\n                              minLength: 1\n                              type: string\n            status:\n              type: object\n              properties:\n                conditions:\n                  type: array\n                  items:\n                    type: object\n                    properties:\n                      lastTransitionTime:\n                        description: lastTransitionTime is the last time the\n                          condition transitioned from one status to another.\n                        format: date-time\n                        type: string\n                      status:\n                        description: status of the condition (one of True, False, Unknown)\n                        enum:\n                        - \"True\"\n                        - \"False\"\n                        - Unknown\n                        type: string\n                      type:\n                        description: type of the condition in CamelCase or in\n                          foo.example.com/CamelCase.\n                        maxLength: 316\n                        pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                        type: string\n                      reason:\n                        description: reason contains a programmatic identifier\n                          indicating the reason for the condition's last\n                          transition. Producers of specific condition types may\n                          define expected values and meanings for this field, and\n                          whether the values are considered a guaranteed API. The\n                          value should be a CamelCase string. This field may not\n                          be empty.\n                        maxLength: 1024\n                        minLength: 1\n                        pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                        type: string\n                      message:\n                        description: message is a human readable message\n                          indicating details about the transition. This may be an\n                          empty string.\n                        maxLength: 32768\n                        type: string\n                  required:\n                  - status\n                  - type\n                targetRef:\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Server\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                  required:\n                  - name\n                  type: object\n              required:\n              - targetRef\n      additionalPrinterColumns:\n      - name: Target_kind\n        description: The resource kind to which the rate-limit applies\n        type: string\n        jsonPath: .spec.targetRef.kind\n      - name: Target_name\n        type: string\n        description: The resource name to which the rate-limit applies\n        jsonPath: .spec.targetRef.name\n      - name: Total_RPS\n        description: The overall rate-limit\n        type: integer\n        format: int32\n        jsonPath: .spec.total.requestsPerSecond\n      - name: Identity_RPS\n        description: The rate-limit per identity\n        type: integer\n        format: int32\n        jsonPath: .spec.identity.requestsPerSecond\n---\n# Source: linkerd-crds/templates/policy/httproute.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httproutes.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPRoute\n    listKind: HTTPRouteList\n    plural: httproutes\n    singular: httproute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\n                              All   implementations must support core filters. \\n\\n \"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta2\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta3\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                    timeouts:\n                      description: \"Timeouts defines the timeouts that can be configured\n                        for an HTTP request. \\n Support: Core \\n <gateway:experimental>\"\n                      properties:\n                        backendRequest:\n                          description: \"BackendRequest specifies a timeout for an\n                            individual request from the gateway to a backend service.\n                            Typically used in conjunction with automatic retries,\n                            if supported by an implementation. Default is the value\n                            of Request timeout. \\n Support: Extended\"\n                          format: duration\n                          type: string\n                        request:\n                          description: \"Request specifies a timeout for responding\n                            to client HTTP requests, disabled by default. \\n For example,\n                            the following rule will timeout if a client request is\n                            taking longer than 10 seconds to complete: \\n ``` rules:\n                            - timeouts: request: 10s backendRefs: ... ``` \\n Support:\n                            Core\"\n                          format: duration\n                          type: string\n                      type: object\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: linkerd-crds/templates/policy/meshtls-authentication.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: meshtlsauthentications.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: MeshTLSAuthentication\n    plural: meshtlsauthentications\n    singular: meshtlsauthentication\n    shortNames: [meshtlsauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                MeshTLSAuthentication defines a list of authenticated client IDs\n                to be referenced by an `AuthorizationPolicy`. If a client\n                connection has the mutually-authenticated identity that matches\n                ANY of the of the provided identities, the connection is\n                considered authenticated.\n              type: object\n              oneOf:\n                - required: [identities]\n                - required: [identityRefs]\n              properties:\n                identities:\n                  description: >-\n                    Authorizes clients with the provided proxy identity strings\n                    (as provided via MTLS)\n\n                    The `*` prefix can be used to match all identities in\n                    a domain. An identity string of `*` indicates that\n                    all authentication clients are authorized.\n                  type: array\n                  minItems: 1\n                  items:\n                    type: string\n                identityRefs:\n                  type: array\n                  minItems: 1\n                  items:\n                    type: object\n                    required:\n                      - kind\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this refers to all resources of the specified Group\n                          and Kind in the specified namespace.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n---\n# Source: linkerd-crds/templates/policy/network-authentication.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: networkauthentications.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: NetworkAuthentication\n    plural: networkauthentications\n    singular: networkauthentication\n    shortNames: [netauthn, networkauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                NetworkAuthentication defines a list of authenticated client\n                networks to be referenced by an `AuthorizationPolicy`. If a\n                client connection originates from ANY of the of the provided\n                networks, the connection is considered authenticated.\n              type: object\n              required: [networks]\n              properties:\n                networks:\n                  type: array\n                  items:\n                    type: object\n                    required: [cidr]\n                    properties:\n                      cidr:\n                        description: >-\n                          The CIDR of the network to be authorized.\n                        type: string\n                      except:\n                        description: >-\n                          A list of IP networks/addresses not to be included in\n                          the above `cidr`.\n                        type: array\n                        items:\n                          type: string\n---\n# Source: linkerd-crds/templates/policy/server-authorization.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serverauthorizations.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: ServerAuthorization\n    plural: serverauthorizations\n    singular: serverauthorization\n    shortNames: [saz, serverauthz, srvauthz]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 ServerAuthorization is deprecated; use policy.linkerd.io/v1beta1 ServerAuthorization\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n    - name: v1beta1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n      additionalPrinterColumns:\n      - name: Server\n        type: string\n        description: The server that this grants access to\n        jsonPath: .spec.server.name\n---\n# Source: linkerd-crds/templates/policy/server.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: servers.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: Server\n    plural: servers\n    singular: server\n    shortNames: [srv]\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 Server is deprecated; use policy.linkerd.io/v1beta1 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n                  oneOf:\n                    - required: [matchExpressions]\n                    - required: [matchLabels]\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n    - name: v1beta1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1beta1 Server is deprecated; use policy.linkerd.io/v1beta3 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta2\n      served: true\n      storage: false\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta3\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                accessPolicy:\n                  type: string\n                  default: deny\n                  description: >-\n                    Default access policy to apply when the traffic doesn't match any of the policy rules.\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n      - name: Access Policy\n        type: string\n        description: The default access policy applied when the traffic doesn't match any of the policy rules\n        jsonPath: .spec.accessPolicy\n---\n# Source: linkerd-crds/templates/serviceprofile.yaml\n---\n###\n### Service Profile CRD\n###\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serviceprofiles.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: linkerd.io\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            required:\n            - routes\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  - name: v1alpha2\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  scope: Namespaced\n  names:\n    plural: serviceprofiles\n    singular: serviceprofile\n    kind: ServiceProfile\n    shortNames:\n    - sp\n---\n# Source: linkerd-crds/templates/gateway.networking.k8s.io_httproutes.yaml\n\n\n---\n# Source: linkerd-crds/templates/gateway.networking.k8s.io_grpcroutes.yaml\n\n---\n# Source: linkerd-crds/templates/gateway.networking.k8s.io_tlsroutes.yaml\n\n---\n# Source: linkerd-crds/templates/gateway.networking.k8s.io_tcproutes.yaml\n\n---\n# Source: linkerd-crds/templates/workload/external-workload.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: externalworkloads.workload.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: workload.linkerd.io\n  names:\n    categories:\n    - external\n    kind: ExternalWorkload\n    listKind: ExternalWorkloadList\n    plural: externalworkloads\n    singular: externalworkload\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTls:\n                description: meshTls describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                # TODO: relax this in the future when ipv6 is supported\n                # an external workload (like a pod) should only\n                # support 2 interfaces\n                maxItems: 1\n            type: object\n            required:\n            - meshTls\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTls.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n  - name: v1beta1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTLS:\n                description: meshTLS describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload. This field may\n                  hold a maximum of two entries. If one entry, it can be an\n                  IPv4 or IPv6 address; if two entries it should contain one\n                  IPv4 address and one IPv6 address.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                maxItems: 2\n            type: object\n            required:\n            - meshTLS\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTLS.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n"
  },
  {
    "path": "cli/cmd/testdata/install_helm_crds_output_ha.golden",
    "content": "---\n# Source: linkerd-crds/templates/policy/authorization-policy.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: authorizationpolicies.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: AuthorizationPolicy\n    plural: authorizationpolicies\n    singular: authorizationpolicy\n    shortNames: [authzpolicy]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied server\n                resources.\n              type: object\n              required: [targetRef, requiredAuthenticationRefs]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the authorization\n                    policy applies.\n                  type: object\n                  required: [kind, name]\n                  # Modified from the gateway API.\n                  # Copyright 2020 The Kubernetes Authors\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: >-\n                        Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                requiredAuthenticationRefs:\n                  description: >-\n                    RequiredAuthenticationRefs enumerates a set of required\n                    authentications. ALL authentications must be satisfied for\n                    the authorization to apply. If any of the referred objects\n                    cannot be found, the authorization will be ignored.\n                  type: array\n                  items:\n                    type: object\n                    required: [kind, name]\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n---\n# Source: linkerd-crds/templates/policy/egress-network.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: egressnetworks.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  names:\n    categories:\n    - policy\n    kind: EgressNetwork\n    listKind: EgressNetworkList\n    plural: egressnetworks\n    singular: egressnetwork\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An EgressNetwork captures traffic to egress destinations\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              trafficPolicy:\n                description: >-\n                  This field controls the traffic policy enforced upon traffic\n                  that does not match any explicit route resources associated\n                  with an instance of this object. The values that are allowed\n                  currently are:\n                   - Allow - permits all traffic, even if it has not been\n                                explicitly described via attaching an xRoute\n                                resources.\n                   - Deny -  blocks all traffic that has not been described via\n                                attaching an xRoute resource.\n                type: string\n                enum:\n                - Allow\n                - Deny\n              networks:\n                type: array\n                items:\n                  type: object\n                  required: [cidr]\n                  properties:\n                    cidr:\n                      description: >-\n                        The CIDR of the network to be authorized.\n                      type: string\n                    except:\n                      description: >-\n                        A list of IP networks/addresses not to be included in\n                        the above `cidr`.\n                      type: array\n                      items:\n                        type: string\n            type: object\n            required:\n            - trafficPolicy\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n---\n# Source: linkerd-crds/templates/policy/http-local-ratelimit-policy.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httplocalratelimitpolicies.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPLocalRateLimitPolicy\n    listKind: HTTPLocalRateLimitPolicyList\n    plural: httplocalratelimitpolicies\n    singular: httplocalratelimitpolicy\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      subresources:\n        status: {}\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required: [targetRef]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the rate limit\n                    policy applies. Only Server is allowed.\n                  type: object\n                  required: [kind, name]\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                total:\n                  description: >-\n                    Overall rate-limit, which all traffic coming to this\n                    target should abide.\n                    If unset no overall limit is applied.\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                identity:\n                  description: >-\n                    Fairness for individual identities; each separate client,\n                    grouped by identity, will have this rate-limit. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                overrides:\n                  description: >-\n                    Overrides for traffic from a specific client. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: array\n                  items:\n                    type: object\n                    required: [requestsPerSecond, clientRefs]\n                    properties:\n                      requestsPerSecond:\n                        format: int64\n                        type: integer\n                      clientRefs:\n                        type: array\n                        items:\n                          type: object\n                          required: [kind, name]\n                          properties:\n                            group:\n                              description: >-\n                                Group is the group of the referent. When empty, the\n                                Kubernetes core API group is inferred.\n                              maxLength: 253\n                              pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                              type: string\n                            kind:\n                              description: Kind is the kind of the referent.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                              type: string\n                            namespace:\n                              description: >-\n                                  Namespace is the namespace of the referent.\n                                  When unspecified (or empty string), this refers to the\n                                  local namespace of the Policy.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                              type: string\n                            name:\n                              description: Name is the name of the referent.\n                              maxLength: 253\n                              minLength: 1\n                              type: string\n            status:\n              type: object\n              properties:\n                conditions:\n                  type: array\n                  items:\n                    type: object\n                    properties:\n                      lastTransitionTime:\n                        description: lastTransitionTime is the last time the\n                          condition transitioned from one status to another.\n                        format: date-time\n                        type: string\n                      status:\n                        description: status of the condition (one of True, False, Unknown)\n                        enum:\n                        - \"True\"\n                        - \"False\"\n                        - Unknown\n                        type: string\n                      type:\n                        description: type of the condition in CamelCase or in\n                          foo.example.com/CamelCase.\n                        maxLength: 316\n                        pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                        type: string\n                      reason:\n                        description: reason contains a programmatic identifier\n                          indicating the reason for the condition's last\n                          transition. Producers of specific condition types may\n                          define expected values and meanings for this field, and\n                          whether the values are considered a guaranteed API. The\n                          value should be a CamelCase string. This field may not\n                          be empty.\n                        maxLength: 1024\n                        minLength: 1\n                        pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                        type: string\n                      message:\n                        description: message is a human readable message\n                          indicating details about the transition. This may be an\n                          empty string.\n                        maxLength: 32768\n                        type: string\n                  required:\n                  - status\n                  - type\n                targetRef:\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Server\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                  required:\n                  - name\n                  type: object\n              required:\n              - targetRef\n      additionalPrinterColumns:\n      - name: Target_kind\n        description: The resource kind to which the rate-limit applies\n        type: string\n        jsonPath: .spec.targetRef.kind\n      - name: Target_name\n        type: string\n        description: The resource name to which the rate-limit applies\n        jsonPath: .spec.targetRef.name\n      - name: Total_RPS\n        description: The overall rate-limit\n        type: integer\n        format: int32\n        jsonPath: .spec.total.requestsPerSecond\n      - name: Identity_RPS\n        description: The rate-limit per identity\n        type: integer\n        format: int32\n        jsonPath: .spec.identity.requestsPerSecond\n---\n# Source: linkerd-crds/templates/policy/httproute.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httproutes.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPRoute\n    listKind: HTTPRouteList\n    plural: httproutes\n    singular: httproute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\n                              All   implementations must support core filters. \\n\\n \"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta2\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta3\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                    timeouts:\n                      description: \"Timeouts defines the timeouts that can be configured\n                        for an HTTP request. \\n Support: Core \\n <gateway:experimental>\"\n                      properties:\n                        backendRequest:\n                          description: \"BackendRequest specifies a timeout for an\n                            individual request from the gateway to a backend service.\n                            Typically used in conjunction with automatic retries,\n                            if supported by an implementation. Default is the value\n                            of Request timeout. \\n Support: Extended\"\n                          format: duration\n                          type: string\n                        request:\n                          description: \"Request specifies a timeout for responding\n                            to client HTTP requests, disabled by default. \\n For example,\n                            the following rule will timeout if a client request is\n                            taking longer than 10 seconds to complete: \\n ``` rules:\n                            - timeouts: request: 10s backendRefs: ... ``` \\n Support:\n                            Core\"\n                          format: duration\n                          type: string\n                      type: object\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: linkerd-crds/templates/policy/meshtls-authentication.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: meshtlsauthentications.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: MeshTLSAuthentication\n    plural: meshtlsauthentications\n    singular: meshtlsauthentication\n    shortNames: [meshtlsauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                MeshTLSAuthentication defines a list of authenticated client IDs\n                to be referenced by an `AuthorizationPolicy`. If a client\n                connection has the mutually-authenticated identity that matches\n                ANY of the of the provided identities, the connection is\n                considered authenticated.\n              type: object\n              oneOf:\n                - required: [identities]\n                - required: [identityRefs]\n              properties:\n                identities:\n                  description: >-\n                    Authorizes clients with the provided proxy identity strings\n                    (as provided via MTLS)\n\n                    The `*` prefix can be used to match all identities in\n                    a domain. An identity string of `*` indicates that\n                    all authentication clients are authorized.\n                  type: array\n                  minItems: 1\n                  items:\n                    type: string\n                identityRefs:\n                  type: array\n                  minItems: 1\n                  items:\n                    type: object\n                    required:\n                      - kind\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this refers to all resources of the specified Group\n                          and Kind in the specified namespace.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n---\n# Source: linkerd-crds/templates/policy/network-authentication.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: networkauthentications.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: NetworkAuthentication\n    plural: networkauthentications\n    singular: networkauthentication\n    shortNames: [netauthn, networkauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                NetworkAuthentication defines a list of authenticated client\n                networks to be referenced by an `AuthorizationPolicy`. If a\n                client connection originates from ANY of the of the provided\n                networks, the connection is considered authenticated.\n              type: object\n              required: [networks]\n              properties:\n                networks:\n                  type: array\n                  items:\n                    type: object\n                    required: [cidr]\n                    properties:\n                      cidr:\n                        description: >-\n                          The CIDR of the network to be authorized.\n                        type: string\n                      except:\n                        description: >-\n                          A list of IP networks/addresses not to be included in\n                          the above `cidr`.\n                        type: array\n                        items:\n                          type: string\n---\n# Source: linkerd-crds/templates/policy/server-authorization.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serverauthorizations.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: ServerAuthorization\n    plural: serverauthorizations\n    singular: serverauthorization\n    shortNames: [saz, serverauthz, srvauthz]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 ServerAuthorization is deprecated; use policy.linkerd.io/v1beta1 ServerAuthorization\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n    - name: v1beta1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n      additionalPrinterColumns:\n      - name: Server\n        type: string\n        description: The server that this grants access to\n        jsonPath: .spec.server.name\n---\n# Source: linkerd-crds/templates/policy/server.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: servers.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: Server\n    plural: servers\n    singular: server\n    shortNames: [srv]\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 Server is deprecated; use policy.linkerd.io/v1beta1 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n                  oneOf:\n                    - required: [matchExpressions]\n                    - required: [matchLabels]\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n    - name: v1beta1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1beta1 Server is deprecated; use policy.linkerd.io/v1beta3 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta2\n      served: true\n      storage: false\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta3\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                accessPolicy:\n                  type: string\n                  default: deny\n                  description: >-\n                    Default access policy to apply when the traffic doesn't match any of the policy rules.\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n      - name: Access Policy\n        type: string\n        description: The default access policy applied when the traffic doesn't match any of the policy rules\n        jsonPath: .spec.accessPolicy\n---\n# Source: linkerd-crds/templates/serviceprofile.yaml\n---\n###\n### Service Profile CRD\n###\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serviceprofiles.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: linkerd.io\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            required:\n            - routes\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  - name: v1alpha2\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  scope: Namespaced\n  names:\n    plural: serviceprofiles\n    singular: serviceprofile\n    kind: ServiceProfile\n    shortNames:\n    - sp\n---\n# Source: linkerd-crds/templates/gateway.networking.k8s.io_httproutes.yaml\n\n\n---\n# Source: linkerd-crds/templates/gateway.networking.k8s.io_grpcroutes.yaml\n\n---\n# Source: linkerd-crds/templates/gateway.networking.k8s.io_tlsroutes.yaml\n\n---\n# Source: linkerd-crds/templates/gateway.networking.k8s.io_tcproutes.yaml\n\n---\n# Source: linkerd-crds/templates/workload/external-workload.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: externalworkloads.workload.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: workload.linkerd.io\n  names:\n    categories:\n    - external\n    kind: ExternalWorkload\n    listKind: ExternalWorkloadList\n    plural: externalworkloads\n    singular: externalworkload\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTls:\n                description: meshTls describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                # TODO: relax this in the future when ipv6 is supported\n                # an external workload (like a pod) should only\n                # support 2 interfaces\n                maxItems: 1\n            type: object\n            required:\n            - meshTls\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTls.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n  - name: v1beta1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTLS:\n                description: meshTLS describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload. This field may\n                  hold a maximum of two entries. If one entry, it can be an\n                  IPv4 or IPv6 address; if two entries it should contain one\n                  IPv4 address and one IPv6 address.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                maxItems: 2\n            type: object\n            required:\n            - meshTLS\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTLS.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n"
  },
  {
    "path": "cli/cmd/testdata/install_helm_crds_without_gateway_output.golden",
    "content": "---\n# Source: linkerd-crds/templates/policy/authorization-policy.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: authorizationpolicies.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: AuthorizationPolicy\n    plural: authorizationpolicies\n    singular: authorizationpolicy\n    shortNames: [authzpolicy]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied server\n                resources.\n              type: object\n              required: [targetRef, requiredAuthenticationRefs]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the authorization\n                    policy applies.\n                  type: object\n                  required: [kind, name]\n                  # Modified from the gateway API.\n                  # Copyright 2020 The Kubernetes Authors\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: >-\n                        Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                requiredAuthenticationRefs:\n                  description: >-\n                    RequiredAuthenticationRefs enumerates a set of required\n                    authentications. ALL authentications must be satisfied for\n                    the authorization to apply. If any of the referred objects\n                    cannot be found, the authorization will be ignored.\n                  type: array\n                  items:\n                    type: object\n                    required: [kind, name]\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n---\n# Source: linkerd-crds/templates/policy/egress-network.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: egressnetworks.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  names:\n    categories:\n    - policy\n    kind: EgressNetwork\n    listKind: EgressNetworkList\n    plural: egressnetworks\n    singular: egressnetwork\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An EgressNetwork captures traffic to egress destinations\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              trafficPolicy:\n                description: >-\n                  This field controls the traffic policy enforced upon traffic\n                  that does not match any explicit route resources associated\n                  with an instance of this object. The values that are allowed\n                  currently are:\n                   - Allow - permits all traffic, even if it has not been\n                                explicitly described via attaching an xRoute\n                                resources.\n                   - Deny -  blocks all traffic that has not been described via\n                                attaching an xRoute resource.\n                type: string\n                enum:\n                - Allow\n                - Deny\n              networks:\n                type: array\n                items:\n                  type: object\n                  required: [cidr]\n                  properties:\n                    cidr:\n                      description: >-\n                        The CIDR of the network to be authorized.\n                      type: string\n                    except:\n                      description: >-\n                        A list of IP networks/addresses not to be included in\n                        the above `cidr`.\n                      type: array\n                      items:\n                        type: string\n            type: object\n            required:\n            - trafficPolicy\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n---\n# Source: linkerd-crds/templates/policy/http-local-ratelimit-policy.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httplocalratelimitpolicies.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPLocalRateLimitPolicy\n    listKind: HTTPLocalRateLimitPolicyList\n    plural: httplocalratelimitpolicies\n    singular: httplocalratelimitpolicy\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      subresources:\n        status: {}\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required: [targetRef]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the rate limit\n                    policy applies. Only Server is allowed.\n                  type: object\n                  required: [kind, name]\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                total:\n                  description: >-\n                    Overall rate-limit, which all traffic coming to this\n                    target should abide.\n                    If unset no overall limit is applied.\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                identity:\n                  description: >-\n                    Fairness for individual identities; each separate client,\n                    grouped by identity, will have this rate-limit. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                overrides:\n                  description: >-\n                    Overrides for traffic from a specific client. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: array\n                  items:\n                    type: object\n                    required: [requestsPerSecond, clientRefs]\n                    properties:\n                      requestsPerSecond:\n                        format: int64\n                        type: integer\n                      clientRefs:\n                        type: array\n                        items:\n                          type: object\n                          required: [kind, name]\n                          properties:\n                            group:\n                              description: >-\n                                Group is the group of the referent. When empty, the\n                                Kubernetes core API group is inferred.\n                              maxLength: 253\n                              pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                              type: string\n                            kind:\n                              description: Kind is the kind of the referent.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                              type: string\n                            namespace:\n                              description: >-\n                                  Namespace is the namespace of the referent.\n                                  When unspecified (or empty string), this refers to the\n                                  local namespace of the Policy.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                              type: string\n                            name:\n                              description: Name is the name of the referent.\n                              maxLength: 253\n                              minLength: 1\n                              type: string\n            status:\n              type: object\n              properties:\n                conditions:\n                  type: array\n                  items:\n                    type: object\n                    properties:\n                      lastTransitionTime:\n                        description: lastTransitionTime is the last time the\n                          condition transitioned from one status to another.\n                        format: date-time\n                        type: string\n                      status:\n                        description: status of the condition (one of True, False, Unknown)\n                        enum:\n                        - \"True\"\n                        - \"False\"\n                        - Unknown\n                        type: string\n                      type:\n                        description: type of the condition in CamelCase or in\n                          foo.example.com/CamelCase.\n                        maxLength: 316\n                        pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                        type: string\n                      reason:\n                        description: reason contains a programmatic identifier\n                          indicating the reason for the condition's last\n                          transition. Producers of specific condition types may\n                          define expected values and meanings for this field, and\n                          whether the values are considered a guaranteed API. The\n                          value should be a CamelCase string. This field may not\n                          be empty.\n                        maxLength: 1024\n                        minLength: 1\n                        pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                        type: string\n                      message:\n                        description: message is a human readable message\n                          indicating details about the transition. This may be an\n                          empty string.\n                        maxLength: 32768\n                        type: string\n                  required:\n                  - status\n                  - type\n                targetRef:\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Server\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                  required:\n                  - name\n                  type: object\n              required:\n              - targetRef\n      additionalPrinterColumns:\n      - name: Target_kind\n        description: The resource kind to which the rate-limit applies\n        type: string\n        jsonPath: .spec.targetRef.kind\n      - name: Target_name\n        type: string\n        description: The resource name to which the rate-limit applies\n        jsonPath: .spec.targetRef.name\n      - name: Total_RPS\n        description: The overall rate-limit\n        type: integer\n        format: int32\n        jsonPath: .spec.total.requestsPerSecond\n      - name: Identity_RPS\n        description: The rate-limit per identity\n        type: integer\n        format: int32\n        jsonPath: .spec.identity.requestsPerSecond\n---\n# Source: linkerd-crds/templates/policy/httproute.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httproutes.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPRoute\n    listKind: HTTPRouteList\n    plural: httproutes\n    singular: httproute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\n                              All   implementations must support core filters. \\n\\n \"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta2\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta3\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                    timeouts:\n                      description: \"Timeouts defines the timeouts that can be configured\n                        for an HTTP request. \\n Support: Core \\n <gateway:experimental>\"\n                      properties:\n                        backendRequest:\n                          description: \"BackendRequest specifies a timeout for an\n                            individual request from the gateway to a backend service.\n                            Typically used in conjunction with automatic retries,\n                            if supported by an implementation. Default is the value\n                            of Request timeout. \\n Support: Extended\"\n                          format: duration\n                          type: string\n                        request:\n                          description: \"Request specifies a timeout for responding\n                            to client HTTP requests, disabled by default. \\n For example,\n                            the following rule will timeout if a client request is\n                            taking longer than 10 seconds to complete: \\n ``` rules:\n                            - timeouts: request: 10s backendRefs: ... ``` \\n Support:\n                            Core\"\n                          format: duration\n                          type: string\n                      type: object\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\n# Source: linkerd-crds/templates/policy/meshtls-authentication.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: meshtlsauthentications.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: MeshTLSAuthentication\n    plural: meshtlsauthentications\n    singular: meshtlsauthentication\n    shortNames: [meshtlsauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                MeshTLSAuthentication defines a list of authenticated client IDs\n                to be referenced by an `AuthorizationPolicy`. If a client\n                connection has the mutually-authenticated identity that matches\n                ANY of the of the provided identities, the connection is\n                considered authenticated.\n              type: object\n              oneOf:\n                - required: [identities]\n                - required: [identityRefs]\n              properties:\n                identities:\n                  description: >-\n                    Authorizes clients with the provided proxy identity strings\n                    (as provided via MTLS)\n\n                    The `*` prefix can be used to match all identities in\n                    a domain. An identity string of `*` indicates that\n                    all authentication clients are authorized.\n                  type: array\n                  minItems: 1\n                  items:\n                    type: string\n                identityRefs:\n                  type: array\n                  minItems: 1\n                  items:\n                    type: object\n                    required:\n                      - kind\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this refers to all resources of the specified Group\n                          and Kind in the specified namespace.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n---\n# Source: linkerd-crds/templates/policy/network-authentication.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: networkauthentications.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: NetworkAuthentication\n    plural: networkauthentications\n    singular: networkauthentication\n    shortNames: [netauthn, networkauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                NetworkAuthentication defines a list of authenticated client\n                networks to be referenced by an `AuthorizationPolicy`. If a\n                client connection originates from ANY of the of the provided\n                networks, the connection is considered authenticated.\n              type: object\n              required: [networks]\n              properties:\n                networks:\n                  type: array\n                  items:\n                    type: object\n                    required: [cidr]\n                    properties:\n                      cidr:\n                        description: >-\n                          The CIDR of the network to be authorized.\n                        type: string\n                      except:\n                        description: >-\n                          A list of IP networks/addresses not to be included in\n                          the above `cidr`.\n                        type: array\n                        items:\n                          type: string\n---\n# Source: linkerd-crds/templates/policy/server-authorization.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serverauthorizations.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: ServerAuthorization\n    plural: serverauthorizations\n    singular: serverauthorization\n    shortNames: [saz, serverauthz, srvauthz]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 ServerAuthorization is deprecated; use policy.linkerd.io/v1beta1 ServerAuthorization\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n    - name: v1beta1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n      additionalPrinterColumns:\n      - name: Server\n        type: string\n        description: The server that this grants access to\n        jsonPath: .spec.server.name\n---\n# Source: linkerd-crds/templates/policy/server.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: servers.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: Server\n    plural: servers\n    singular: server\n    shortNames: [srv]\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 Server is deprecated; use policy.linkerd.io/v1beta1 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n                  oneOf:\n                    - required: [matchExpressions]\n                    - required: [matchLabels]\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n    - name: v1beta1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1beta1 Server is deprecated; use policy.linkerd.io/v1beta3 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta2\n      served: true\n      storage: false\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta3\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                accessPolicy:\n                  type: string\n                  default: deny\n                  description: >-\n                    Default access policy to apply when the traffic doesn't match any of the policy rules.\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n      - name: Access Policy\n        type: string\n        description: The default access policy applied when the traffic doesn't match any of the policy rules\n        jsonPath: .spec.accessPolicy\n---\n# Source: linkerd-crds/templates/serviceprofile.yaml\n---\n###\n### Service Profile CRD\n###\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serviceprofiles.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: linkerd.io\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            required:\n            - routes\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  - name: v1alpha2\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  scope: Namespaced\n  names:\n    plural: serviceprofiles\n    singular: serviceprofile\n    kind: ServiceProfile\n    shortNames:\n    - sp\n---\n# Source: linkerd-crds/templates/gateway.networking.k8s.io_httproutes.yaml\n\n\n---\n# Source: linkerd-crds/templates/gateway.networking.k8s.io_grpcroutes.yaml\n\n---\n# Source: linkerd-crds/templates/gateway.networking.k8s.io_tlsroutes.yaml\n\n---\n# Source: linkerd-crds/templates/gateway.networking.k8s.io_tcproutes.yaml\n\n---\n# Source: linkerd-crds/templates/workload/external-workload.yaml\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: externalworkloads.workload.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    helm.sh/chart: linkerd-crds-\n    linkerd.io/control-plane-ns: linkerd-dev\nspec:\n  group: workload.linkerd.io\n  names:\n    categories:\n    - external\n    kind: ExternalWorkload\n    listKind: ExternalWorkloadList\n    plural: externalworkloads\n    singular: externalworkload\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTls:\n                description: meshTls describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                # TODO: relax this in the future when ipv6 is supported\n                # an external workload (like a pod) should only\n                # support 2 interfaces\n                maxItems: 1\n            type: object\n            required:\n            - meshTls\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTls.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n  - name: v1beta1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTLS:\n                description: meshTLS describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload. This field may\n                  hold a maximum of two entries. If one entry, it can be an\n                  IPv4 or IPv6 address; if two entries it should contain one\n                  IPv4 address and one IPv6 address.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                maxItems: 2\n            type: object\n            required:\n            - meshTLS\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTLS.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n"
  },
  {
    "path": "cli/cmd/testdata/install_helm_output_ha_labels.golden",
    "content": "---\n# Source: linkerd-control-plane/templates/namespace.yaml\n---\n# Source: linkerd-control-plane/templates/identity-rbac.yaml\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-dev-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd-dev\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n---\n# Source: linkerd-control-plane/templates/destination-rbac.yaml\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-dev-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd-dev\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd-dev\n      path: \"/\"\n    caBundle: dGVzdC1wcm9maWxlLXZhbGlkYXRvci1jYS1idW5kbGU=\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd-dev\n      path: \"/\"\n    caBundle: dGVzdC1wcm9maWxlLXZhbGlkYXRvci1jYS1idW5kbGU=\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd-dev\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd-dev\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd-dev\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd-dev\n---\n# Source: linkerd-control-plane/templates/heartbeat-rbac.yaml\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd-dev\n---\n# Source: linkerd-control-plane/templates/podmonitor.yaml\n\n---\n# Source: linkerd-control-plane/templates/proxy-injector-rbac.yaml\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-dev-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd-dev\n      path: \"/\"\n    caBundle: dGVzdC1wcm94eS1pbmplY3Rvci1jYS1idW5kbGU=\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\n# Source: linkerd-control-plane/templates/psp.yaml\n---\n# Source: linkerd-control-plane/templates/config.yaml\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: \"\"\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 3\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: test-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 1\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: true\n    enablePodDisruptionBudget: true\n    heartbeat: null\n    heartbeatResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: true\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: test-crt-pem\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 10Mi\n    identityTrustAnchorsPEM: test-trust-anchor\n    identityTrustDomain: test.trust.domain\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: null\n    linkerdVersion: linkerd-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations:\n      asda: fasda\n      bingo: bongo\n    podLabels:\n      fiz: buz\n      foo: bar\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: test-profile-validator-ca-bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: test-profile-validator-ca-bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: test-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: 100m\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: 250Mi\n          request: 20Mi\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: \"444\"\n      ignoreOutboundPorts: \"333\"\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: test-proxy-injector-ca-bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tap:\n      caBundle: test-tap-ca-bundle\n      externalSecret: true\n    tolerations: null\n    webhookFailurePolicy: Fail\n---\n# Source: linkerd-control-plane/templates/config-rbac.yaml\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n# Source: linkerd-control-plane/templates/identity.yaml\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\ndata:\n  crt.pem: dGVzdC1jcnQtcGVt\n  key.pem: dGVzdC1rZXktcGVt\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\ndata:\n  ca-bundle.crt: |-\n    test-trust-anchor\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  name: linkerd-identity\n  namespace: linkerd-dev\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd-dev\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/helm linkerd-version\n        linkerd.io/proxy-version: test-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: f8ebf807fa1cf5bf3b40e94680a5fc91593782385f28c96eae7bb6672dba375e\n        asda: fasda\n        bingo: bongo\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd-dev\n        linkerd.io/workload-ns: linkerd-dev\n        linkerd.io/proxy-deployment: linkerd-identity\n        fiz: buz\n        foo: bar\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - identity\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - identity\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd-dev\n        - -identity-trust-domain=test.trust.domain\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"10Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd-dev.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd-dev.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd-dev\n        - name: _l5d_trustdomain\n          value: test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,444\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n# Source: linkerd-control-plane/templates/destination.yaml\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  name: linkerd-destination\n  namespace: linkerd-dev\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd-dev\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        checksum/config: fd8add3a34efa33eae90f8e70a5e0925728e8cf48d5607c4c03adb27ed005fe1\n        linkerd.io/created-by: linkerd/helm linkerd-version\n        linkerd.io/proxy-version: test-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: f8ebf807fa1cf5bf3b40e94680a5fc91593782385f28c96eae7bb6672dba375e\n        asda: fasda\n        bingo: bongo\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd-dev\n        linkerd.io/workload-ns: linkerd-dev\n        linkerd.io/proxy-deployment: linkerd-destination\n        fiz: buz\n        foo: bar\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - destination\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - destination\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd-dev\n        - name: _l5d_trustdomain\n          value: test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd-dev.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd-dev\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=test.trust.domain\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"50Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd-dev\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=test.trust.domain\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,444\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n# Source: linkerd-control-plane/templates/heartbeat.yaml\n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd-dev\n            fiz: buz\n            foo: bar\n          annotations:\n            linkerd.io/created-by: linkerd/helm linkerd-version\n            asda: fasda\n            bingo: bongo\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:linkerd-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd-dev\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            resources:\n              limits:\n                memory: \"250Mi\"\n              requests:\n                cpu: \"100m\"\n                memory: \"50Mi\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n# Source: linkerd-control-plane/templates/proxy-injector.yaml\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        checksum/config: fd3a1b10afd0c6c39c7c63f51aece4a849b0e47ba992a6612a1a5fa99211b084\n        linkerd.io/created-by: linkerd/helm linkerd-version\n        linkerd.io/proxy-version: test-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: f8ebf807fa1cf5bf3b40e94680a5fc91593782385f28c96eae7bb6672dba375e\n        asda: fasda\n        bingo: bongo\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd-dev\n        linkerd.io/workload-ns: linkerd-dev\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n        fiz: buz\n        foo: bar\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - proxy-injector\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - proxy-injector\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd-dev.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd-dev.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd-dev\n        - name: _l5d_trustdomain\n          value: test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd-dev.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd-dev\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"50Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,444\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n"
  },
  {
    "path": "cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden",
    "content": "---\n# Source: linkerd-control-plane/templates/namespace.yaml\n---\n# Source: linkerd-control-plane/templates/identity-rbac.yaml\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-dev-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd-dev\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n---\n# Source: linkerd-control-plane/templates/destination-rbac.yaml\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-dev-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd-dev\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: In\n      values:\n      - enabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd-dev\n      path: \"/\"\n    caBundle: dGVzdC1wcm9maWxlLXZhbGlkYXRvci1jYS1idW5kbGU=\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd-dev\n      path: \"/\"\n    caBundle: dGVzdC1wcm9maWxlLXZhbGlkYXRvci1jYS1idW5kbGU=\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd-dev\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd-dev\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd-dev\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd-dev\n---\n# Source: linkerd-control-plane/templates/heartbeat-rbac.yaml\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd-dev\n---\n# Source: linkerd-control-plane/templates/podmonitor.yaml\n\n---\n# Source: linkerd-control-plane/templates/proxy-injector-rbac.yaml\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-dev-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-dev-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: In\n      values:\n      - enabled\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd-dev\n      path: \"/\"\n    caBundle: dGVzdC1wcm94eS1pbmplY3Rvci1jYS1idW5kbGU=\n  failurePolicy: Fail\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\n# Source: linkerd-control-plane/templates/psp.yaml\n---\n# Source: linkerd-control-plane/templates/config.yaml\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: \"\"\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 3\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: test-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 1\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: true\n    enablePodDisruptionBudget: true\n    heartbeat: null\n    heartbeatResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: true\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: test-crt-pem\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 10Mi\n    identityTrustAnchorsPEM: test-trust-anchor\n    identityTrustDomain: test.trust.domain\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: null\n    linkerdVersion: linkerd-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: test-profile-validator-ca-bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: test-profile-validator-ca-bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: In\n          values:\n          - enabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: test-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: 100m\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: 250Mi\n          request: 20Mi\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: \"222\"\n      ignoreOutboundPorts: \"111\"\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: test-proxy-injector-ca-bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: In\n          values:\n          - enabled\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources:\n      cpu:\n        limit: \"\"\n        request: 100m\n      ephemeral-storage:\n        limit: \"\"\n        request: \"\"\n      memory:\n        limit: 250Mi\n        request: 50Mi\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tap:\n      caBundle: test-tap-ca-bundle\n      externalSecret: true\n    tolerations: null\n    webhookFailurePolicy: Fail\n---\n# Source: linkerd-control-plane/templates/config-rbac.yaml\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd-dev\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n# Source: linkerd-control-plane/templates/identity.yaml\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\ndata:\n  crt.pem: dGVzdC1jcnQtcGVt\n  key.pem: dGVzdC1rZXktcGVt\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\ndata:\n  ca-bundle.crt: |-\n    test-trust-anchor\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd-dev\n  name: linkerd-identity\n  namespace: linkerd-dev\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd-dev\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/helm linkerd-version\n        linkerd.io/proxy-version: test-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: f8ebf807fa1cf5bf3b40e94680a5fc91593782385f28c96eae7bb6672dba375e\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd-dev\n        linkerd.io/workload-ns: linkerd-dev\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - identity\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - identity\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd-dev\n        - -identity-trust-domain=test.trust.domain\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"10Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd-dev.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd-dev.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd-dev\n        - name: _l5d_trustdomain\n          value: test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,222\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n# Source: linkerd-control-plane/templates/destination.yaml\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd-dev\n  name: linkerd-destination\n  namespace: linkerd-dev\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd-dev\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        checksum/config: 882b51dcfef64f0e2418f2865db47c80b988a2dc0132c3e927a0b92ce3df8ad1\n        linkerd.io/created-by: linkerd/helm linkerd-version\n        linkerd.io/proxy-version: test-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: f8ebf807fa1cf5bf3b40e94680a5fc91593782385f28c96eae7bb6672dba375e\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd-dev\n        linkerd.io/workload-ns: linkerd-dev\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - destination\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - destination\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd-dev\n        - name: _l5d_trustdomain\n          value: test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd-dev.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd-dev\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=test.trust.domain\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"50Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd-dev\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=test.trust.domain\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,222\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n# Source: linkerd-control-plane/templates/heartbeat.yaml\n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd-dev\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd-dev\n          annotations:\n            linkerd.io/created-by: linkerd/helm linkerd-version\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:linkerd-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd-dev\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            resources:\n              limits:\n                memory: \"250Mi\"\n              requests:\n                cpu: \"100m\"\n                memory: \"50Mi\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n# Source: linkerd-control-plane/templates/proxy-injector.yaml\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerd-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        checksum/config: a6eb8c6e7cbd674a1b0e776a942c6c72fe93c478344911e8326d5bf739560a5e\n        linkerd.io/created-by: linkerd/helm linkerd-version\n        linkerd.io/proxy-version: test-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: f8ebf807fa1cf5bf3b40e94680a5fc91593782385f28c96eae7bb6672dba375e\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd-dev\n        linkerd.io/workload-ns: linkerd-dev\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: linkerd.io/control-plane-component\n                  operator: In\n                  values:\n                  - proxy-injector\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: linkerd.io/control-plane-component\n                operator: In\n                values:\n                - proxy-injector\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd-dev.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd-dev.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd-dev\n        - name: _l5d_trustdomain\n          value: test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd-dev.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd-dev.serviceaccount.identity.linkerd-dev.test.trust.domain\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd-dev\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:linkerd-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"50Mi\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,222\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:test-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n          limits:\n            memory: \"250Mi\"\n          requests:\n            cpu: \"100m\"\n            memory: \"20Mi\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd-dev\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd-dev\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerd-version\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n"
  },
  {
    "path": "cli/cmd/testdata/install_no_init_container.golden",
    "content": "---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: linkerd\n    pod-security.kubernetes.io/enforce: restricted\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGU=\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: linkerd/cli dev-undefined\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: true\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 1\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: install-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 25%\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    enablePodDisruptionBudget: false\n    heartbeat: null\n    heartbeatResources: null\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: false\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\n            MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\n            ODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\n            a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\n            fcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n            2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\n            Af8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\n            HmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\n            AiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n            51tdrmkHEZRr0qlLSJdHYgEfMzk=\n            -----END CERTIFICATE-----\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources: null\n    identityTrustAnchorsPEM: |\n      -----BEGIN CERTIFICATE-----\n      MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n      JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n      MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n      ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n      l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n      uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n      /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n      aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n      IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n      vgUC0d2/9FMueIVMb+46WTCOjsqr\n      -----END CERTIFICATE-----\n    identityTrustDomain: cluster.local\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: []\n    linkerdVersion: install-control-plane-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: policy validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: profile validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: install-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: 4567,4568\n      ignoreOutboundPorts: 4567,4568\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: proxy injector CA bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tolerations: null\n    webhookFailurePolicy: Ignore\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQpPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CmEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQoyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCkFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CjUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==\n  key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgpkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  ca-bundle.crt: |-\n    -----BEGIN CERTIFICATE-----\n    MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n    JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n    MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n    ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n    l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n    uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n    /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n    aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n    IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n    vgUC0d2/9FMueIVMb+46WTCOjsqr\n    -----END CERTIFICATE-----\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-identity\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd\n        - -identity-trust-domain=cluster.local\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - name: linkerd-network-validator\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        command:\n          - /usr/lib/linkerd/linkerd2-network-validator\n        args:\n          - --log-format\n          - plain\n          - --log-level\n          - debug\n          - --connect-addr\n          - \"1.1.1.1:20001\"\n          - --listen-addr\n          - \"0.0.0.0:4140\"\n          - --timeout\n          - 10s\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-destination\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: a39770e2a1ac1f4bb452509e52a4ffd176bcb034f1ce906491f2649e28c67319\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=cluster.local\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=cluster.local\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - name: linkerd-network-validator\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        command:\n          - /usr/lib/linkerd/linkerd2-network-validator\n        args:\n          - --log-format\n          - plain\n          - --log-level\n          - debug\n          - --connect-addr\n          - \"1.1.1.1:20001\"\n          - --listen-addr\n          - \"0.0.0.0:4140\"\n          - --timeout\n          - 10s\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd\n          annotations:\n            linkerd.io/created-by: linkerd/cli dev-undefined\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:install-control-plane-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-proxy-injector\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: cd0cf730780be444ab96a4a835a244033ffb7c8cf4a8796d0e6ae5c72aa9ff31\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - name: linkerd-network-validator\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        command:\n          - /usr/lib/linkerd/linkerd2-network-validator\n        args:\n          - --log-format\n          - plain\n          - --log-level\n          - debug\n          - --connect-addr\n          - \"1.1.1.1:20001\"\n          - --listen-addr\n          - \"0.0.0.0:4140\"\n          - --timeout\n          - 10s\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\napiVersion: v1\ndata:\n  linkerd-config-overrides: Y25pRW5hYmxlZDogdHJ1ZQpkZWJ1Z0NvbnRhaW5lcjoKICBpbWFnZToKICAgIHZlcnNpb246IGluc3RhbGwtZGVidWctdmVyc2lvbgpoZWFydGJlYXRTY2hlZHVsZTogMSAyIDMgNCA1CmlkZW50aXR5OgogIGlzc3VlcjoKICAgIHRsczoKICAgICAgY3J0UEVNOiB8CiAgICAgICAgLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCiAgICAgICAgTUlJQndEQ0NBV2VnQXdJQkFnSVJBSlJJZ1o4UnRPOEV3ZzFYZXBmOFQ0NHdDZ1lJS29aSXpqMEVBd0l3S1RFbgogICAgICAgIE1DVUdBMVVFQXhNZWFXUmxiblJwZEhrdWJHbHVhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJd01EZ3kKICAgICAgICBPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CiAgICAgICAgYTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFMS9GcAogICAgICAgIGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MmRRdlJhWWFudXhEMzZEdDEKICAgICAgICAyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCiAgICAgICAgQWY4Q0FRQXdIUVlEVlIwT0JCWUVGSTFXbnJxTVlLYUhIT28renB5aWlEcTJwTzBLTUNrR0ExVWRFUVFpTUNDQwogICAgICAgIEhtbGtaVzUwYVhSNUxteHBibXRsY21RdVkyeDFjM1JsY2k1c2IyTmhiREFLQmdncWhrak9QUVFEQWdOSEFEQkUKICAgICAgICBBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CiAgICAgICAgNTF0ZHJta0hFWlJyMHFsTFNKZEhZZ0VmTXprPQogICAgICAgIC0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KICAgICAga2V5UEVNOiB8CiAgICAgICAgLS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCiAgICAgICAgTUhjQ0FRRUVJQUFlOG5mYnpadTljL09CMis4eEpNMEZ6N05Vd1RRYXp1bGtGTnM0VEk1K29Bb0dDQ3FHU000OQogICAgICAgIEF3RUhvVVFEUWdBRTEvRnBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzIKICAgICAgICBkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KICAgICAgICAtLS0tLUVORCBFQyBQUklWQVRFIEtFWS0tLS0tCmlkZW50aXR5VHJ1c3RBbmNob3JzUEVNOiB8CiAgLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCiAgTUlJQndUQ0NBV2FnQXdJQkFnSVFlRFpwNWxEYUl5Z1E1VWZNS1pyRkFUQUtCZ2dxaGtqT1BRUURBakFwTVNjdwogIEpRWURWUVFERXg1cFpHVnVkR2wwZVM1c2FXNXJaWEprTG1Oc2RYTjBaWEl1Ykc5allXd3dIaGNOTWpBd09ESTQKICBNRGN4TWpRM1doY05NekF3T0RJMk1EY3hNalEzV2pBcE1TY3dKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyCiAgWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVJxYzcwWgogIGwxdmd3NzlyakI1dVNJVElDVUE2R3lmdlNGZmN1SWlzN0IvWEZTa2t3QUhVNVMvczFBQVArUjBUWDdIQldVQzQKICB1YUc0V1dzaXdKS05uN21nbzNBd2JqQU9CZ05WSFE4QkFmOEVCQU1DQVFZd0VnWURWUjBUQVFIL0JBZ3dCZ0VCCiAgL3dJQkFUQWRCZ05WSFE0RUZnUVU1WXRqVlZQZmQ3STdOTEhzbjJDMjZFQnlHVjB3S1FZRFZSMFJCQ0l3SUlJZQogIGFXUmxiblJwZEhrdWJHbHVhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNQW9HQ0NxR1NNNDlCQU1DQTBrQU1FWUMKICBJUUNON2xCRkxERHZqeDZWMCtYa2pwS0VSUnNKWWY1YWRNdm5sb0ZsNDhpbEpnSWhBTnR4aG5kY3IrUUpQdUM4CiAgdmdVQzBkMi85Rk11ZUlWTWIrNDZXVENPanNxcgogIC0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KbGlua2VyZFZlcnNpb246IGluc3RhbGwtY29udHJvbC1wbGFuZS12ZXJzaW9uCnBvbGljeVZhbGlkYXRvcjoKICBjYUJ1bmRsZTogcG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQpwcm9maWxlVmFsaWRhdG9yOgogIGNhQnVuZGxlOiBwcm9maWxlIHZhbGlkYXRvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQpwcm94eToKICBpbWFnZToKICAgIHZlcnNpb246IGluc3RhbGwtcHJveHktdmVyc2lvbgpwcm94eUluamVjdG9yOgogIGNhQnVuZGxlOiBwcm94eSBpbmplY3RvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQo=\nkind: Secret\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-config-overrides\n  namespace: linkerd\n"
  },
  {
    "path": "cli/cmd/testdata/install_output.golden",
    "content": "---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: linkerd\n    pod-security.kubernetes.io/enforce: privileged\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxl\n  failurePolicy: WebhookFailurePolicy\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGU=\n  failurePolicy: WebhookFailurePolicy\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl\n  failurePolicy: WebhookFailurePolicy\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: CliVersion\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: CliVersion\n    clusterDomain: cluster.local\n    clusterNetworks: ClusterNetworks\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: 2103\n    controllerImage: ControllerImage\n    controllerLogFormat: ControllerLogFormat\n    controllerLogLevel: ControllerLogLevel\n    controllerReplicas: 1\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: DebugImageName\n        pullPolicy: DebugImagePullPolicy\n        version: DebugVersion\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: false\n    disableIPv6: false\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: false\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    enablePodDisruptionBudget: false\n    heartbeat: null\n    heartbeatResources: null\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: false\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\n            MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\n            ODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\n            a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\n            fcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n            2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\n            Af8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\n            HmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\n            AiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n            51tdrmkHEZRr0qlLSJdHYgEfMzk=\n            -----END CERTIFICATE-----\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources: null\n    identityTrustAnchorsPEM: |\n      -----BEGIN CERTIFICATE-----\n      MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n      JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n      MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n      ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n      l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n      uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n      /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n      aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n      IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n      vgUC0d2/9FMueIVMb+46WTCOjsqr\n      -----END CERTIFICATE-----\n    identityTrustDomain: cluster.local\n    imagePullPolicy: ImagePullPolicy\n    imagePullSecrets: null\n    linkerdVersion: LinkerdVersion\n    networkValidator:\n      connectAddr: 1.1.1.1:20001\n      listenAddr: '[::]:4140'\n      logFormat: plain\n      logLevel: debug\n      securityContext: null\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor: null\n    policyController:\n      logLevel: log-level\n      probeNetworks:\n      - 1.0.0.0/0\n      - 2.0.0.0/0\n      resources:\n        cpu:\n          limit: cpu-limit\n          request: cpu-request\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: memory-limit\n          request: memory-request\n    policyValidator:\n      caBundle: policy validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: PriorityClassName\n    profileValidator:\n      caBundle: profile validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control: null\n      defaultInboundPolicy: default-allow-policy\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: 2102\n      image:\n        name: ProxyImageName\n        pullPolicy: ImagePullPolicy\n        version: ProxyVersion\n      inboundConnectTimeout: \"\"\n      inboundDiscoveryCacheUnusedTimeout: \"\"\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,443,587,3306,5432,11211\n      outboundConnectTimeout: \"\"\n      outboundDiscoveryCacheUnusedTimeout: \"\"\n      outboundTransportMode: \"\"\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: cpu-limit\n          request: cpu-request\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: memory-limit\n          request: memory-request\n      runtime:\n        workers: {}\n      saMountPath: null\n      securityContext: null\n      shutdownGracePeriod: \"\"\n      startupProbe: null\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: ProxyContainerName\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: \"\"\n      ignoreOutboundPorts: \"443\"\n      iptablesMode: legacy\n      kubeAPIServerPorts: \"\"\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: proxy injector CA bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    revisionHistoryLimit: 10\n    spValidator: null\n    tolerations: null\n    webhookFailurePolicy: WebhookFailurePolicy\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: CliVersion\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: CliVersion\ndata:\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQpPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CmEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQoyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCkFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CjUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==\n  key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgpkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: CliVersion\ndata:\n  ca-bundle.crt: |-\n    -----BEGIN CERTIFICATE-----\n    MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n    JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n    MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n    ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n    l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n    uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n    /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n    aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n    IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n    vgUC0d2/9FMueIVMb+46WTCOjsqr\n    -----END CERTIFICATE-----\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: CliVersion\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: CliVersion\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: CliVersion\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: LinkerdVersion\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-identity\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-identity\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: CliVersion\n        linkerd.io/proxy-version: ProxyVersion\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=ControllerLogLevel\n        - -log-format=ControllerLogFormat\n        - -controller-namespace=linkerd\n        - -identity-trust-domain=cluster.local\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: ControllerImage:LinkerdVersion\n        imagePullPolicy: ImagePullPolicy\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"ClusterNetworks\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"ClusterNetworks\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"[::]:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"[::]:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140,[::1]:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"[::]:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,443,587,3306,5432,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: ProxyImageName:ProxyVersion\n        imagePullPolicy: ImagePullPolicy\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            cpu: \"cpu-limit\"\n            memory: \"memory-limit\"\n          requests:\n            cpu: \"cpu-request\"\n            memory: \"memory-request\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          runAsGroup: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --proxy-gid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191\"\n        image: ProxyImageName:ProxyVersion\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: ImagePullPolicy\n        name: linkerd-init\n        resources:\n          limits:\n            cpu: \"cpu-limit\"\n            memory: \"memory-limit\"\n          requests:\n            cpu: \"cpu-request\"\n            memory: \"memory-request\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      priorityClassName: PriorityClassName\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: CliVersion\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: CliVersion\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: CliVersion\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: CliVersion\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: CliVersion\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: CliVersion\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: LinkerdVersion\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-destination\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-destination\n  template:\n    metadata:\n      annotations:\n        checksum/config: 036fb1916c0896bb3c88168f51e2fc8846b00678b206ea8a14361ad4df1ae1bc\n        linkerd.io/created-by: CliVersion\n        linkerd.io/proxy-version: ProxyVersion\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"ClusterNetworks\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"ClusterNetworks\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"[::]:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"[::]:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140,[::1]:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"[::]:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,443,587,3306,5432,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: ProxyImageName:ProxyVersion\n        imagePullPolicy: ImagePullPolicy\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            cpu: \"cpu-limit\"\n            memory: \"memory-limit\"\n          requests:\n            cpu: \"cpu-request\"\n            memory: \"memory-request\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          runAsGroup: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd\n        - -outbound-transport-mode=\n        - -enable-h2-upgrade=true\n        - -log-level=ControllerLogLevel\n        - -log-format=ControllerLogFormat\n        - -enable-endpoint-slices=false\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=cluster.local\n        - -default-opaque-ports=25,443,587,3306,5432,11211\n        - -enable-ipv6=true\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: ControllerImage:LinkerdVersion\n        imagePullPolicy: ImagePullPolicy\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=ControllerLogLevel\n        - -log-format=ControllerLogFormat\n        - -enable-pprof=false\n        image: ControllerImage:LinkerdVersion\n        imagePullPolicy: ImagePullPolicy\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=[::]:9990\n        - --control-plane-namespace=linkerd\n        - --grpc-addr=[::]:8090\n        - --server-addr=[::]:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=ClusterNetworks\n        - --identity-domain=cluster.local\n        - --cluster-domain=cluster.local\n        - --default-policy=default-allow-policy\n        - --log-level=log-level\n        - --log-format=ControllerLogFormat\n        - --default-opaque-ports=25,443,587,3306,5432,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=1.0.0.0/0,2.0.0.0/0\n        image: ControllerImage:LinkerdVersion\n        imagePullPolicy: ImagePullPolicy\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n          limits:\n            cpu: \"cpu-limit\"\n            memory: \"memory-limit\"\n          requests:\n            cpu: \"cpu-request\"\n            memory: \"memory-request\"\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --proxy-gid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191\"\n        image: ProxyImageName:ProxyVersion\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: ImagePullPolicy\n        name: linkerd-init\n        resources:\n          limits:\n            cpu: \"cpu-limit\"\n            memory: \"memory-limit\"\n          requests:\n            cpu: \"cpu-request\"\n            memory: \"memory-request\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      priorityClassName: PriorityClassName\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: LinkerdVersion\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: CliVersion\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd\n          annotations:\n            linkerd.io/created-by: CliVersion\n        spec:\n          priorityClassName: PriorityClassName\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: ControllerImage:LinkerdVersion\n            imagePullPolicy: ImagePullPolicy\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd\"\n            - \"-log-level=ControllerLogLevel\"\n            - \"-log-format=ControllerLogFormat\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              runAsGroup: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: CliVersion\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: LinkerdVersion\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-proxy-injector\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  template:\n    metadata:\n      annotations:\n        checksum/config: fd791d1adb869c6aa7de66e366ec110a2ccbacf37a750723b111d98636c5ae00\n        linkerd.io/created-by: CliVersion\n        linkerd.io/proxy-version: ProxyVersion\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"ClusterNetworks\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"ClusterNetworks\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"[::]:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"[::]:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140,[::1]:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"[::]:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,443,587,3306,5432,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: ProxyImageName:ProxyVersion\n        imagePullPolicy: ImagePullPolicy\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n          limits:\n            cpu: \"cpu-limit\"\n            memory: \"memory-limit\"\n          requests:\n            cpu: \"cpu-request\"\n            memory: \"memory-request\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          runAsGroup: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=ControllerLogLevel\n        - -log-format=ControllerLogFormat\n        - -linkerd-namespace=linkerd\n        - -enable-pprof=false\n        image: ControllerImage:LinkerdVersion\n        imagePullPolicy: ImagePullPolicy\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --proxy-gid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191\"\n        image: ProxyImageName:ProxyVersion\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: ImagePullPolicy\n        name: linkerd-init\n        resources:\n          limits:\n            cpu: \"cpu-limit\"\n            memory: \"memory-limit\"\n          requests:\n            cpu: \"cpu-request\"\n            memory: \"memory-request\"\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      priorityClassName: PriorityClassName\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: CliVersion\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\napiVersion: v1\ndata:\n  linkerd-config-overrides: Y2xpVmVyc2lvbjogQ2xpVmVyc2lvbgpjbHVzdGVyTmV0d29ya3M6IENsdXN0ZXJOZXR3b3Jrcwpjb250cm9sbGVyR0lEOiAyMTAzCmNvbnRyb2xsZXJJbWFnZTogQ29udHJvbGxlckltYWdlCmNvbnRyb2xsZXJMb2dGb3JtYXQ6IENvbnRyb2xsZXJMb2dGb3JtYXQKY29udHJvbGxlckxvZ0xldmVsOiBDb250cm9sbGVyTG9nTGV2ZWwKZGVidWdDb250YWluZXI6CiAgaW1hZ2U6CiAgICBuYW1lOiBEZWJ1Z0ltYWdlTmFtZQogICAgcHVsbFBvbGljeTogRGVidWdJbWFnZVB1bGxQb2xpY3kKICAgIHZlcnNpb246IERlYnVnVmVyc2lvbgpkaXNhYmxlSVB2NjogZmFsc2UKZW5hYmxlRW5kcG9pbnRTbGljZXM6IGZhbHNlCmhlYXJ0YmVhdFNjaGVkdWxlOiAxIDIgMyA0IDUKaWRlbnRpdHk6CiAgaXNzdWVyOgogICAgdGxzOgogICAgICBjcnRQRU06IHwKICAgICAgICAtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KICAgICAgICBNSUlCd0RDQ0FXZWdBd0lCQWdJUkFKUklnWjhSdE84RXdnMVhlcGY4VDQ0d0NnWUlLb1pJemowRUF3SXdLVEVuCiAgICAgICAgTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQogICAgICAgIE9EQTNNVE0wTjFvWERUTXdNRGd5TmpBM01UTTBOMW93S1RFbk1DVUdBMVVFQXhNZWFXUmxiblJwZEhrdWJHbHUKICAgICAgICBhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUxL0ZwCiAgICAgICAgZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQogICAgICAgIDIvSnh5aVNneEtXUmRvYXkrYU53TUc0d0RnWURWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUIKICAgICAgICBBZjhDQVFBd0hRWURWUjBPQkJZRUZJMVducnFNWUthSEhPbyt6cHlpaURxMnBPMEtNQ2tHQTFVZEVRUWlNQ0NDCiAgICAgICAgSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQogICAgICAgIEFpQXR1b0k1WHVDdHJHVlJ6U21SVGwycmEyOGFWOU15VFU3ZDVxblRBRkhLU2dJZ1JLQ3ZsdU9TZ0E1TzIxcDUKICAgICAgICA1MXRkcm1rSEVaUnIwcWxMU0pkSFlnRWZNems9CiAgICAgICAgLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQogICAgICBrZXlQRU06IHwKICAgICAgICAtLS0tLUJFR0lOIEVDIFBSSVZBVEUgS0VZLS0tLS0KICAgICAgICBNSGNDQVFFRUlBQWU4bmZielp1OWMvT0IyKzh4Sk0wRno3TlV3VFFhenVsa0ZOczRUSTUrb0FvR0NDcUdTTTQ5CiAgICAgICAgQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgogICAgICAgIGRRdlJhWWFudXhEMzZEdDEyL0p4eWlTZ3hLV1Jkb2F5K1E9PQogICAgICAgIC0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0KaWRlbnRpdHlUcnVzdEFuY2hvcnNQRU06IHwKICAtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KICBNSUlCd1RDQ0FXYWdBd0lCQWdJUWVEWnA1bERhSXlnUTVVZk1LWnJGQVRBS0JnZ3Foa2pPUFFRREFqQXBNU2N3CiAgSlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1clpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d0hoY05NakF3T0RJNAogIE1EY3hNalEzV2hjTk16QXdPREkyTURjeE1qUTNXakFwTVNjd0pRWURWUVFERXg1cFpHVnVkR2wwZVM1c2FXNXIKICBaWEprTG1Oc2RYTjBaWEl1Ykc5allXd3dXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBUnFjNzBaCiAgbDF2Z3c3OXJqQjV1U0lUSUNVQTZHeWZ2U0ZmY3VJaXM3Qi9YRlNra3dBSFU1Uy9zMUFBUCtSMFRYN0hCV1VDNAogIHVhRzRXV3Npd0pLTm43bWdvM0F3YmpBT0JnTlZIUThCQWY4RUJBTUNBUVl3RWdZRFZSMFRBUUgvQkFnd0JnRUIKICAvd0lCQVRBZEJnTlZIUTRFRmdRVTVZdGpWVlBmZDdJN05MSHNuMkMyNkVCeUdWMHdLUVlEVlIwUkJDSXdJSUllCiAgYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Bb0dDQ3FHU000OUJBTUNBMGtBTUVZQwogIElRQ043bEJGTEREdmp4NlYwK1hranBLRVJSc0pZZjVhZE12bmxvRmw0OGlsSmdJaEFOdHhobmRjcitRSlB1QzgKICB2Z1VDMGQyLzlGTXVlSVZNYis0NldUQ09qc3FyCiAgLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQppbWFnZVB1bGxQb2xpY3k6IEltYWdlUHVsbFBvbGljeQppbWFnZVB1bGxTZWNyZXRzOiBudWxsCmxpbmtlcmRWZXJzaW9uOiBMaW5rZXJkVmVyc2lvbgpuZXR3b3JrVmFsaWRhdG9yOgogIGNvbm5lY3RBZGRyOiAxLjEuMS4xOjIwMDAxCiAgbGlzdGVuQWRkcjogJ1s6Ol06NDE0MCcKICBzZWN1cml0eUNvbnRleHQ6IG51bGwKcG9kTW9uaXRvcjogbnVsbApwb2xpY3lDb250cm9sbGVyOgogIGxvZ0xldmVsOiBsb2ctbGV2ZWwKICByZXNvdXJjZXM6CiAgICBjcHU6CiAgICAgIGxpbWl0OiBjcHUtbGltaXQKICAgICAgcmVxdWVzdDogY3B1LXJlcXVlc3QKICAgIG1lbW9yeToKICAgICAgbGltaXQ6IG1lbW9yeS1saW1pdAogICAgICByZXF1ZXN0OiBtZW1vcnktcmVxdWVzdApwb2xpY3lWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHBvbGljeSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJpb3JpdHlDbGFzc05hbWU6IFByaW9yaXR5Q2xhc3NOYW1lCnByb2ZpbGVWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHByb2ZpbGUgdmFsaWRhdG9yIENBIGJ1bmRsZQogIGV4dGVybmFsU2VjcmV0OiB0cnVlCnByb3h5OgogIGNvbnRyb2w6IG51bGwKICBkZWZhdWx0SW5ib3VuZFBvbGljeTogZGVmYXVsdC1hbGxvdy1wb2xpY3kKICBnaWQ6IDIxMDIKICBpbWFnZToKICAgIG5hbWU6IFByb3h5SW1hZ2VOYW1lCiAgICBwdWxsUG9saWN5OiBJbWFnZVB1bGxQb2xpY3kKICAgIHZlcnNpb246IFByb3h5VmVyc2lvbgogIGluYm91bmRDb25uZWN0VGltZW91dDogIiIKICBpbmJvdW5kRGlzY292ZXJ5Q2FjaGVVbnVzZWRUaW1lb3V0OiAiIgogIGxvZ0xldmVsOiB3YXJuLGxpbmtlcmQ9aW5mbwogIG9wYXF1ZVBvcnRzOiAyNSw0NDMsNTg3LDMzMDYsNTQzMiwxMTIxMQogIG91dGJvdW5kQ29ubmVjdFRpbWVvdXQ6ICIiCiAgb3V0Ym91bmREaXNjb3ZlcnlDYWNoZVVudXNlZFRpbWVvdXQ6ICIiCiAgb3V0Ym91bmRUcmFuc3BvcnRNb2RlOiAiIgogIHJlc291cmNlczoKICAgIGNwdToKICAgICAgbGltaXQ6IGNwdS1saW1pdAogICAgICByZXF1ZXN0OiBjcHUtcmVxdWVzdAogICAgbWVtb3J5OgogICAgICBsaW1pdDogbWVtb3J5LWxpbWl0CiAgICAgIHJlcXVlc3Q6IG1lbW9yeS1yZXF1ZXN0CiAgc2VjdXJpdHlDb250ZXh0OiBudWxsCiAgc3RhcnR1cFByb2JlOiBudWxsCnByb3h5Q29udGFpbmVyTmFtZTogUHJveHlDb250YWluZXJOYW1lCnByb3h5SW5pdDoKICBpZ25vcmVJbmJvdW5kUG9ydHM6ICIiCiAgaWdub3JlT3V0Ym91bmRQb3J0czogIjQ0MyIKICBpcHRhYmxlc01vZGU6IGxlZ2FjeQogIGt1YmVBUElTZXJ2ZXJQb3J0czogIiIKcHJveHlJbmplY3RvcjoKICBjYUJ1bmRsZTogcHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKc3BWYWxpZGF0b3I6IG51bGwKd2ViaG9va0ZhaWx1cmVQb2xpY3k6IFdlYmhvb2tGYWlsdXJlUG9saWN5Cg==\nkind: Secret\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-config-overrides\n  namespace: linkerd\n"
  },
  {
    "path": "cli/cmd/testdata/install_proxy_ignores.golden",
    "content": "---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: linkerd\n    pod-security.kubernetes.io/enforce: privileged\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGU=\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: linkerd/cli dev-undefined\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 1\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: install-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 25%\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    enablePodDisruptionBudget: false\n    heartbeat: null\n    heartbeatResources: null\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: false\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\n            MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\n            ODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\n            a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\n            fcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n            2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\n            Af8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\n            HmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\n            AiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n            51tdrmkHEZRr0qlLSJdHYgEfMzk=\n            -----END CERTIFICATE-----\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources: null\n    identityTrustAnchorsPEM: |\n      -----BEGIN CERTIFICATE-----\n      MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n      JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n      MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n      ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n      l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n      uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n      /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n      aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n      IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n      vgUC0d2/9FMueIVMb+46WTCOjsqr\n      -----END CERTIFICATE-----\n    identityTrustDomain: cluster.local\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: []\n    linkerdVersion: install-control-plane-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: policy validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: profile validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: install-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: 22,8100-8102\n      ignoreOutboundPorts: \"5432\"\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: proxy injector CA bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tolerations: null\n    webhookFailurePolicy: Ignore\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQpPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CmEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQoyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCkFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CjUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==\n  key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgpkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  ca-bundle.crt: |-\n    -----BEGIN CERTIFICATE-----\n    MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n    JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n    MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n    ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n    l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n    uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n    /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n    aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n    IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n    vgUC0d2/9FMueIVMb+46WTCOjsqr\n    -----END CERTIFICATE-----\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-identity\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd\n        - -identity-trust-domain=cluster.local\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,22,8100-8102\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-destination\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: a39770e2a1ac1f4bb452509e52a4ffd176bcb034f1ce906491f2649e28c67319\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=cluster.local\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=cluster.local\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,22,8100-8102\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd\n          annotations:\n            linkerd.io/created-by: linkerd/cli dev-undefined\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:install-control-plane-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-proxy-injector\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: cd0cf730780be444ab96a4a835a244033ffb7c8cf4a8796d0e6ae5c72aa9ff31\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,22,8100-8102\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\napiVersion: v1\ndata:\n  linkerd-config-overrides: ZGVidWdDb250YWluZXI6CiAgaW1hZ2U6CiAgICB2ZXJzaW9uOiBpbnN0YWxsLWRlYnVnLXZlcnNpb24KaGVhcnRiZWF0U2NoZWR1bGU6IDEgMiAzIDQgNQppZGVudGl0eToKICBpc3N1ZXI6CiAgICB0bHM6CiAgICAgIGNydFBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogICAgICAgIE1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KICAgICAgICBNQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUI0WERUSXdNRGd5CiAgICAgICAgT0RBM01UTTBOMW9YRFRNd01EZ3lOakEzTVRNME4xb3dLVEVuTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdQogICAgICAgIGEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKICAgICAgICBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzJkUXZSYVlhbnV4RDM2RHQxCiAgICAgICAgMi9KeHlpU2d4S1dSZG9heSthTndNRzR3RGdZRFZSMFBBUUgvQkFRREFnRUdNQklHQTFVZEV3RUIvd1FJTUFZQgogICAgICAgIEFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKICAgICAgICBIbWxrWlc1MGFYUjVMbXhwYm10bGNtUXVZMngxYzNSbGNpNXNiMk5oYkRBS0JnZ3Foa2pPUFFRREFnTkhBREJFCiAgICAgICAgQWlBdHVvSTVYdUN0ckdWUnpTbVJUbDJyYTI4YVY5TXlUVTdkNXFuVEFGSEtTZ0lnUktDdmx1T1NnQTVPMjFwNQogICAgICAgIDUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KICAgICAgICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCiAgICAgIGtleVBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gRUMgUFJJVkFURSBLRVktLS0tLQogICAgICAgIE1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKICAgICAgICBBd0VIb1VRRFFnQUUxL0ZwZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyCiAgICAgICAgZFF2UmFZYW51eEQzNkR0MTIvSnh5aVNneEtXUmRvYXkrUT09CiAgICAgICAgLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQppZGVudGl0eVRydXN0QW5jaG9yc1BFTTogfAogIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogIE1JSUJ3VENDQVdhZ0F3SUJBZ0lRZURacDVsRGFJeWdRNVVmTUtackZBVEFLQmdncWhrak9QUVFEQWpBcE1TY3cKICBKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3SGhjTk1qQXdPREk0CiAgTURjeE1qUTNXaGNOTXpBd09ESTJNRGN4TWpRM1dqQXBNU2N3SlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1cgogIFpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFScWM3MFoKICBsMXZndzc5cmpCNXVTSVRJQ1VBNkd5ZnZTRmZjdUlpczdCL1hGU2trd0FIVTVTL3MxQUFQK1IwVFg3SEJXVUM0CiAgdWFHNFdXc2l3SktObjdtZ28zQXdiakFPQmdOVkhROEJBZjhFQkFNQ0FRWXdFZ1lEVlIwVEFRSC9CQWd3QmdFQgogIC93SUJBVEFkQmdOVkhRNEVGZ1FVNVl0alZWUGZkN0k3TkxIc24yQzI2RUJ5R1Ywd0tRWURWUjBSQkNJd0lJSWUKICBhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUFvR0NDcUdTTTQ5QkFNQ0Ewa0FNRVlDCiAgSVFDTjdsQkZMRER2ang2VjArWGtqcEtFUlJzSllmNWFkTXZubG9GbDQ4aWxKZ0loQU50eGhuZGNyK1FKUHVDOAogIHZnVUMwZDIvOUZNdWVJVk1iKzQ2V1RDT2pzcXIKICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCmxpbmtlcmRWZXJzaW9uOiBpbnN0YWxsLWNvbnRyb2wtcGxhbmUtdmVyc2lvbgpwb2xpY3lWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHBvbGljeSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJvZmlsZVZhbGlkYXRvcjoKICBjYUJ1bmRsZTogcHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJveHk6CiAgaW1hZ2U6CiAgICB2ZXJzaW9uOiBpbnN0YWxsLXByb3h5LXZlcnNpb24KcHJveHlJbml0OgogIGlnbm9yZUluYm91bmRQb3J0czogMjIsODEwMC04MTAyCiAgaWdub3JlT3V0Ym91bmRQb3J0czogIjU0MzIiCnByb3h5SW5qZWN0b3I6CiAgY2FCdW5kbGU6IHByb3h5IGluamVjdG9yIENBIGJ1bmRsZQogIGV4dGVybmFsU2VjcmV0OiB0cnVlCg==\nkind: Secret\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-config-overrides\n  namespace: linkerd\n"
  },
  {
    "path": "cli/cmd/testdata/install_tracing.golden",
    "content": "---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: linkerd\n    pod-security.kubernetes.io/enforce: privileged\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGU=\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: linkerd/cli dev-undefined\n    clusterDomain: cluster.local\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 1\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: install-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 25%\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    enablePodDisruptionBudget: false\n    heartbeat: null\n    heartbeatResources: null\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: false\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\n            MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\n            ODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\n            a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\n            fcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n            2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\n            Af8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\n            HmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\n            AiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n            51tdrmkHEZRr0qlLSJdHYgEfMzk=\n            -----END CERTIFICATE-----\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources: null\n    identityTrustAnchorsPEM: |\n      -----BEGIN CERTIFICATE-----\n      MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n      JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n      MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n      ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n      l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n      uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n      /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n      aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n      IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n      vgUC0d2/9FMueIVMb+46WTCOjsqr\n      -----END CERTIFICATE-----\n    identityTrustDomain: cluster.local\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: []\n    linkerdVersion: install-control-plane-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: policy validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: profile validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: install-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: tracing.foo:4317\n          meshIdentity:\n            namespace: foo\n            serviceAccountName: default\n        enabled: true\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: 4567,4568\n      ignoreOutboundPorts: 4567,4568\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: proxy injector CA bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tolerations: null\n    webhookFailurePolicy: Ignore\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQpPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CmEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQoyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCkFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CjUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==\n  key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgpkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  ca-bundle.crt: |-\n    -----BEGIN CERTIFICATE-----\n    MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n    JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n    MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n    ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n    l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n    uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n    /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n    aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n    IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n    vgUC0d2/9FMueIVMb+46WTCOjsqr\n    -----END CERTIFICATE-----\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-identity\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd\n        - -identity-trust-domain=cluster.local\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_TRACE_ATTRIBUTES_PATH\n          value: /var/run/linkerd/podinfo/labels\n        - name: LINKERD2_PROXY_TRACE_ANNOTATIONS_PATH\n          value: /var/run/linkerd/podinfo/annotations\n        - name: LINKERD2_PROXY_TRACE_PROTOCOL\n          value: opentelemetry\n        - name: LINKERD2_PROXY_TRACE_SERVICE_NAME\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_TRACE_COLLECTOR_SVC_ADDR\n          value: tracing.foo:4317\n        - name: LINKERD2_PROXY_TRACE_COLLECTOR_SVC_NAME\n          value: default.foo.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_TRACE_EXTRA_ATTRIBUTES\n          value: |\n            k8s.container.name=$(_pod_containerName)\n            k8s.pod.ip=$(_pod_ip)\n            k8s.pod.uid=$(_pod_uid)\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/linkerd/podinfo\n          name: linkerd-podinfo\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-podinfo\n        downwardAPI:\n          items:\n            - path: labels\n              fieldRef:\n                fieldPath: metadata.labels\n            - path: annotations\n              fieldRef:\n                fieldPath: metadata.annotations\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-destination\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: a39770e2a1ac1f4bb452509e52a4ffd176bcb034f1ce906491f2649e28c67319\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_TRACE_ATTRIBUTES_PATH\n          value: /var/run/linkerd/podinfo/labels\n        - name: LINKERD2_PROXY_TRACE_ANNOTATIONS_PATH\n          value: /var/run/linkerd/podinfo/annotations\n        - name: LINKERD2_PROXY_TRACE_PROTOCOL\n          value: opentelemetry\n        - name: LINKERD2_PROXY_TRACE_SERVICE_NAME\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_TRACE_COLLECTOR_SVC_ADDR\n          value: tracing.foo:4317\n        - name: LINKERD2_PROXY_TRACE_COLLECTOR_SVC_NAME\n          value: default.foo.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_TRACE_EXTRA_ATTRIBUTES\n          value: |\n            k8s.container.name=$(_pod_containerName)\n            k8s.pod.ip=$(_pod_ip)\n            k8s.pod.uid=$(_pod_uid)\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/linkerd/podinfo\n          name: linkerd-podinfo\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=cluster.local\n        - -identity-trust-domain=cluster.local\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=cluster.local\n        - --cluster-domain=cluster.local\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-podinfo\n        downwardAPI:\n          items:\n            - path: labels\n              fieldRef:\n                fieldPath: metadata.labels\n            - path: annotations\n              fieldRef:\n                fieldPath: metadata.annotations\n      \n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd\n          annotations:\n            linkerd.io/created-by: linkerd/cli dev-undefined\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:install-control-plane-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-proxy-injector\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: cd0cf730780be444ab96a4a835a244033ffb7c8cf4a8796d0e6ae5c72aa9ff31\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.cluster.local.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.cluster.local.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.cluster.local.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_TRACE_ATTRIBUTES_PATH\n          value: /var/run/linkerd/podinfo/labels\n        - name: LINKERD2_PROXY_TRACE_ANNOTATIONS_PATH\n          value: /var/run/linkerd/podinfo/annotations\n        - name: LINKERD2_PROXY_TRACE_PROTOCOL\n          value: opentelemetry\n        - name: LINKERD2_PROXY_TRACE_SERVICE_NAME\n          value: linkerd-proxy\n        - name: LINKERD2_PROXY_TRACE_COLLECTOR_SVC_ADDR\n          value: tracing.foo:4317\n        - name: LINKERD2_PROXY_TRACE_COLLECTOR_SVC_NAME\n          value: default.foo.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_TRACE_EXTRA_ATTRIBUTES\n          value: |\n            k8s.container.name=$(_pod_containerName)\n            k8s.pod.ip=$(_pod_ip)\n            k8s.pod.uid=$(_pod_uid)\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.cluster.local.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/linkerd/podinfo\n          name: linkerd-podinfo\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      - name: linkerd-podinfo\n        downwardAPI:\n          items:\n            - path: labels\n              fieldRef:\n                fieldPath: metadata.labels\n            - path: annotations\n              fieldRef:\n                fieldPath: metadata.annotations\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\napiVersion: v1\ndata:\n  linkerd-config-overrides: ZGVidWdDb250YWluZXI6CiAgaW1hZ2U6CiAgICB2ZXJzaW9uOiBpbnN0YWxsLWRlYnVnLXZlcnNpb24KaGVhcnRiZWF0U2NoZWR1bGU6IDEgMiAzIDQgNQppZGVudGl0eToKICBpc3N1ZXI6CiAgICB0bHM6CiAgICAgIGNydFBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogICAgICAgIE1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KICAgICAgICBNQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUI0WERUSXdNRGd5CiAgICAgICAgT0RBM01UTTBOMW9YRFRNd01EZ3lOakEzTVRNME4xb3dLVEVuTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdQogICAgICAgIGEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKICAgICAgICBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzJkUXZSYVlhbnV4RDM2RHQxCiAgICAgICAgMi9KeHlpU2d4S1dSZG9heSthTndNRzR3RGdZRFZSMFBBUUgvQkFRREFnRUdNQklHQTFVZEV3RUIvd1FJTUFZQgogICAgICAgIEFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKICAgICAgICBIbWxrWlc1MGFYUjVMbXhwYm10bGNtUXVZMngxYzNSbGNpNXNiMk5oYkRBS0JnZ3Foa2pPUFFRREFnTkhBREJFCiAgICAgICAgQWlBdHVvSTVYdUN0ckdWUnpTbVJUbDJyYTI4YVY5TXlUVTdkNXFuVEFGSEtTZ0lnUktDdmx1T1NnQTVPMjFwNQogICAgICAgIDUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KICAgICAgICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCiAgICAgIGtleVBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gRUMgUFJJVkFURSBLRVktLS0tLQogICAgICAgIE1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKICAgICAgICBBd0VIb1VRRFFnQUUxL0ZwZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyCiAgICAgICAgZFF2UmFZYW51eEQzNkR0MTIvSnh5aVNneEtXUmRvYXkrUT09CiAgICAgICAgLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQppZGVudGl0eVRydXN0QW5jaG9yc1BFTTogfAogIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogIE1JSUJ3VENDQVdhZ0F3SUJBZ0lRZURacDVsRGFJeWdRNVVmTUtackZBVEFLQmdncWhrak9QUVFEQWpBcE1TY3cKICBKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3SGhjTk1qQXdPREk0CiAgTURjeE1qUTNXaGNOTXpBd09ESTJNRGN4TWpRM1dqQXBNU2N3SlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1cgogIFpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFScWM3MFoKICBsMXZndzc5cmpCNXVTSVRJQ1VBNkd5ZnZTRmZjdUlpczdCL1hGU2trd0FIVTVTL3MxQUFQK1IwVFg3SEJXVUM0CiAgdWFHNFdXc2l3SktObjdtZ28zQXdiakFPQmdOVkhROEJBZjhFQkFNQ0FRWXdFZ1lEVlIwVEFRSC9CQWd3QmdFQgogIC93SUJBVEFkQmdOVkhRNEVGZ1FVNVl0alZWUGZkN0k3TkxIc24yQzI2RUJ5R1Ywd0tRWURWUjBSQkNJd0lJSWUKICBhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUFvR0NDcUdTTTQ5QkFNQ0Ewa0FNRVlDCiAgSVFDTjdsQkZMRER2ang2VjArWGtqcEtFUlJzSllmNWFkTXZubG9GbDQ4aWxKZ0loQU50eGhuZGNyK1FKUHVDOAogIHZnVUMwZDIvOUZNdWVJVk1iKzQ2V1RDT2pzcXIKICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCmxpbmtlcmRWZXJzaW9uOiBpbnN0YWxsLWNvbnRyb2wtcGxhbmUtdmVyc2lvbgpwb2xpY3lWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHBvbGljeSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJvZmlsZVZhbGlkYXRvcjoKICBjYUJ1bmRsZTogcHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJveHk6CiAgaW1hZ2U6CiAgICB2ZXJzaW9uOiBpbnN0YWxsLXByb3h5LXZlcnNpb24KICB0cmFjaW5nOgogICAgY29sbGVjdG9yOgogICAgICBlbmRwb2ludDogdHJhY2luZy5mb286NDMxNwogICAgICBtZXNoSWRlbnRpdHk6CiAgICAgICAgbmFtZXNwYWNlOiBmb28KICAgICAgICBzZXJ2aWNlQWNjb3VudE5hbWU6IGRlZmF1bHQKICAgIGVuYWJsZWQ6IHRydWUKcHJveHlJbmplY3RvcjoKICBjYUJ1bmRsZTogcHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUK\nkind: Secret\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-config-overrides\n  namespace: linkerd\n"
  },
  {
    "path": "cli/cmd/testdata/install_values_file.golden",
    "content": "---\n###\n### Linkerd Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd\n  annotations:\n    linkerd.io/inject: disabled\n  labels:\n    linkerd.io/is-control-plane: \"true\"\n    config.linkerd.io/admission-webhooks: disabled\n    linkerd.io/control-plane-ns: linkerd\n    pod-security.kubernetes.io/enforce: privileged\n---\n###\n### Identity Controller Service RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources: [\"tokenreviews\"]\n  verbs: [\"create\"]\n# TODO(ver) Restrict this to the Linkerd namespace. See\n# https://github.com/linkerd/linkerd2/issues/9367\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-identity\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-identity\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-identity\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n---\n###\n### Destination Controller Service\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"workload.linkerd.io\"]\n  resources: [\"externalworkloads\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-destination\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-destination\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-destination\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-sp-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-sp-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"linkerd.io\"]\n    apiVersions: [\"v1alpha1\", \"v1alpha2\"]\n    resources: [\"serviceprofiles\"]\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-policy-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-policy-validator.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n  clientConfig:\n    service:\n      name: linkerd-policy-validator\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cG9saWN5IHZhbGlkYXRvciBDQSBidW5kbGU=\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"policy.linkerd.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - authorizationpolicies\n    - httplocalratelimitpolicies\n    - httproutes\n    - networkauthentications\n    - meshtlsauthentications\n    - serverauthorizations\n    - servers\n    - egressnetworks\n  - operations: [\"CREATE\", \"UPDATE\"]\n    apiGroups: [\"gateway.networking.k8s.io\"]\n    apiVersions: [\"*\"]\n    resources:\n    - httproutes\n    - grpcroutes\n    - tlsroutes\n    - tcproutes\n  sideEffects: None\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - pods\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - get\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - authorizationpolicies\n      - httplocalratelimitpolicies\n      - httproutes\n      - meshtlsauthentications\n      - networkauthentications\n      - servers\n      - serverauthorizations\n      - egressnetworks\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes\n      - grpcroutes\n      - tlsroutes\n      - tcproutes\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - policy.linkerd.io\n    resources:\n      - httproutes/status\n      - httplocalratelimitpolicies/status\n      - egressnetworks/status\n    verbs:\n      - patch\n  - apiGroups:\n      - gateway.networking.k8s.io\n    resources:\n      - httproutes/status\n      - grpcroutes/status\n      - tlsroutes/status\n      - tcproutes/status\n    verbs:\n      - patch\n  - apiGroups:\n      - workload.linkerd.io\n    resources:\n      - externalworkloads\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - coordination.k8s.io\n    resources:\n      - leases\n    verbs:\n      - create\n      - get\n      - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-destination-policy\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-policy\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-destination-remote-discovery\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/part-of: Linkerd\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: remote-discovery\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-destination\n    namespace: linkerd\n---\n###\n### Heartbeat RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: Role\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-heartbeat\n  labels:\n    linkerd.io/control-plane-ns: linkerd\nroleRef:\n  kind: ClusterRole\n  name: linkerd-heartbeat\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-heartbeat\n  namespace: linkerd\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n\n---\n###\n### Proxy Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"replicationcontrollers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"deployments\", \"replicasets\", \"daemonsets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-proxy-injector\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  apiGroup: \"\"\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-proxy-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\nwebhooks:\n- name: linkerd-proxy-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: config.linkerd.io/admission-webhooks\n      operator: NotIn\n      values:\n      - disabled\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n      - cert-manager\n  objectSelector:\n    null\n  clientConfig:\n    service:\n      name: linkerd-proxy-injector\n      namespace: linkerd\n      path: \"/\"\n    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\", \"services\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n  timeoutSeconds: 10\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: controller\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  linkerd-crds-chart-version: linkerd-crds-1.0.0-edge\n  values: |\n    cliVersion: linkerd/cli dev-undefined\n    clusterDomain: example.com\n    clusterNetworks: 10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n    cniEnabled: false\n    controller:\n      podDisruptionBudget:\n        maxUnavailable: \"1\"\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity: null\n        enabled: false\n        labels: null\n        protocol: \"\"\n        traceServiceName: \"\"\n    controllerGID: -1\n    controllerImage: cr.l5d.io/linkerd/controller\n    controllerLogFormat: plain\n    controllerLogLevel: info\n    controllerReplicas: 1\n    controllerUID: 2103\n    debugContainer:\n      image:\n        name: cr.l5d.io/linkerd/debug\n        pullPolicy: \"\"\n        version: install-debug-version\n    deploymentStrategy:\n      rollingUpdate:\n        maxSurge: 25%\n        maxUnavailable: 25%\n    destinationController:\n      meshedHttp2ClientProtobuf:\n        keep_alive:\n          interval:\n            seconds: 10\n          timeout:\n            seconds: 3\n          while_idle: true\n      podAnnotations: {}\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: false\n    disableIPv6: true\n    egress:\n      globalEgressNetworkNamespace: linkerd-egress\n    enableEndpointSlices: true\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    enablePodDisruptionBudget: false\n    heartbeat: null\n    heartbeatResources: null\n    heartbeatSchedule: 1 2 3 4 5\n    highAvailability: false\n    identity:\n      additionalEnv: null\n      experimentalEnv: null\n      externalCA: false\n      issuer:\n        clockSkewAllowance: 20s\n        issuanceLifetime: 24h0m0s\n        scheme: linkerd.io/tls\n        tls:\n          crtPEM: |\n            -----BEGIN CERTIFICATE-----\n            MIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\n            MCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\n            ODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\n            a2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\n            fcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n            2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\n            Af8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\n            HmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\n            AiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n            51tdrmkHEZRr0qlLSJdHYgEfMzk=\n            -----END CERTIFICATE-----\n      kubeAPI:\n        clientBurst: 200\n        clientQPS: 100\n      podAnnotations: {}\n      serviceAccountTokenProjection: true\n    identityProxyResources: null\n    identityResources: null\n    identityTrustAnchorsPEM: |\n      -----BEGIN CERTIFICATE-----\n      MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n      JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n      MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n      ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n      l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n      uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n      /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n      aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n      IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n      vgUC0d2/9FMueIVMb+46WTCOjsqr\n      -----END CERTIFICATE-----\n    identityTrustDomain: example.com\n    imagePullPolicy: IfNotPresent\n    imagePullSecrets: []\n    linkerdVersion: install-control-plane-version\n    networkValidator:\n      connectAddr: \"\"\n      listenAddr: \"\"\n      logFormat: plain\n      logLevel: debug\n      securityContext:\n        allowPrivilegeEscalation: false\n        capabilities:\n          drop:\n          - ALL\n        readOnlyRootFilesystem: true\n        runAsGroup: 65534\n        runAsNonRoot: true\n        runAsUser: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      timeout: 10s\n    nodeAffinity: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    podAnnotations: {}\n    podLabels: {}\n    podMonitor:\n      controller:\n        enabled: true\n        namespaceSelector: |\n          matchNames:\n            - {{ .Release.Namespace }}\n            - linkerd-viz\n      enabled: false\n      proxy:\n        enabled: true\n      scrapeInterval: 10s\n      scrapeTimeout: 10s\n      serviceMirror:\n        enabled: true\n    policyController:\n      logLevel: info\n      probeNetworks:\n      - 0.0.0.0/0\n      - ::/0\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n    policyValidator:\n      caBundle: policy validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    priorityClassName: \"\"\n    profileValidator:\n      caBundle: profile validator CA bundle\n      crtPEM: \"\"\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n    prometheusUrl: \"\"\n    proxy:\n      accessLog: \"\"\n      additionalEnv: null\n      await: true\n      capabilities: null\n      control:\n        streams:\n          idleTimeout: 5m\n          initialTimeout: 3s\n          lifetime: 1h\n      defaultInboundPolicy: all-unauthenticated\n      disableInboundProtocolDetectTimeout: false\n      disableOutboundProtocolDetectTimeout: false\n      enableExternalProfiles: false\n      enableShutdownEndpoint: false\n      experimentalEnv: null\n      gid: -1\n      image:\n        name: cr.l5d.io/linkerd/proxy\n        pullPolicy: \"\"\n        version: install-proxy-version\n      inbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      inboundConnectTimeout: 100ms\n      inboundDiscoveryCacheUnusedTimeout: 90s\n      isGateway: false\n      isIngress: false\n      livenessProbe:\n        initialDelaySeconds: 10\n        timeoutSeconds: 1\n      logFormat: plain\n      logHTTPHeaders: \"off\"\n      logLevel: warn,linkerd=info,hickory=error\n      metrics:\n        hostnameLabels: false\n      nativeSidecar: false\n      opaquePorts: 25,587,3306,4444,5432,6379,9300,11211\n      outbound:\n        server:\n          http2:\n            keepAliveInterval: 10s\n            keepAliveTimeout: 3s\n      outboundConnectTimeout: 1000ms\n      outboundDiscoveryCacheUnusedTimeout: 5s\n      outboundTransportMode: transport-header\n      podInboundPorts: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      readinessProbe:\n        initialDelaySeconds: 2\n        timeoutSeconds: 1\n      requireIdentityOnInboundPorts: \"\"\n      resources:\n        cpu:\n          limit: \"\"\n          request: \"\"\n        ephemeral-storage:\n          limit: \"\"\n          request: \"\"\n        memory:\n          limit: \"\"\n          request: \"\"\n      runtime:\n        workers:\n          minimum: 1\n      saMountPath: null\n      securityContext: {}\n      shutdownGracePeriod: \"\"\n      startupProbe:\n        failureThreshold: 120\n        initialDelaySeconds: 0\n        periodSeconds: 1\n      tracing:\n        collector:\n          endpoint: \"\"\n          meshIdentity:\n            namespace: \"\"\n            serviceAccountName: \"\"\n        enabled: false\n        labels:\n          k8s.container.name: $(_pod_containerName)\n          k8s.pod.ip: $(_pod_ip)\n          k8s.pod.uid: $(_pod_uid)\n        protocol: \"\"\n        traceServiceName: linkerd-proxy\n      uid: 2102\n      waitBeforeExitSeconds: 0\n    proxyContainerName: linkerd-proxy\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: 4567,4568\n      ignoreOutboundPorts: 4567,4568\n      iptablesMode: nft\n      kubeAPIServerPorts: 443,6443\n      logFormat: \"\"\n      logLevel: \"\"\n      privileged: false\n      resources: null\n      runAsGroup: 65534\n      runAsRoot: false\n      runAsUser: 65534\n      saMountPath: null\n      skipSubnets: \"\"\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    proxyInjector:\n      additionalEnv: null\n      caBundle: proxy injector CA bundle\n      crtPEM: \"\"\n      experimentalEnv: null\n      externalSecret: true\n      injectCaFrom: \"\"\n      injectCaFromSecret: \"\"\n      namespaceSelector:\n        matchExpressions:\n        - key: config.linkerd.io/admission-webhooks\n          operator: NotIn\n          values:\n          - disabled\n        - key: kubernetes.io/metadata.name\n          operator: NotIn\n          values:\n          - kube-system\n          - cert-manager\n      podAnnotations: {}\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    revisionHistoryLimit: 10\n    spValidator:\n      livenessProbe:\n        timeoutSeconds: 1\n      readinessProbe:\n        timeoutSeconds: 1\n    tolerations: null\n    webhookFailurePolicy: Ignore\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  name: ext-namespace-metadata-linkerd-config\n  namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n---\n###\n### Identity Controller Service\n###\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQpPREEzTVRNME4xb1hEVE13TURneU5qQTNNVE0wTjFvd0tURW5NQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1CmEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQoyL0p4eWlTZ3hLV1Jkb2F5K2FOd01HNHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCCkFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF0dW9JNVh1Q3RyR1ZSelNtUlRsMnJhMjhhVjlNeVRVN2Q1cW5UQUZIS1NnSWdSS0N2bHVPU2dBNU8yMXA1CjUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==\n  key.pem: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgpkUXZSYVlhbnV4RDM2RHQxMi9KeHlpU2d4S1dSZG9heStRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-identity-trust-roots\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  ca-bundle.crt: |-\n    -----BEGIN CERTIFICATE-----\n    MIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\n    JQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\n    MDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\n    ZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\n    l1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\n    uaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n    /wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\n    aWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\n    IQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\n    vgUC0d2/9FMueIVMb+46WTCOjsqr\n    -----END CERTIFICATE-----\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-identity-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: identity\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: identity\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: identity\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-identity\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: identity\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-identity\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: identity\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-identity\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - identity\n        - -log-level=info\n        - -log-format=plain\n        - -controller-namespace=linkerd\n        - -identity-trust-domain=example.com\n        - -identity-issuance-lifetime=24h0m0s\n        - -identity-clock-skew-allowance=20s\n        - -identity-scheme=linkerd.io/tls\n        - -enable-pprof=false\n        - -kube-apiclient-qps=100\n        - -kube-apiclient-burst=200\n        env:\n        - name: LINKERD_DISABLED\n          value: \"linkerd-await cannot block the identity controller\"\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9990\n          initialDelaySeconds: 10\n        name: identity\n        ports:\n        - containerPort: 8080\n          name: ident-grpc\n        - containerPort: 9990\n          name: ident-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9990\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/issuer\n          name: identity-issuer\n        - mountPath: /var/run/linkerd/identity/trust-roots/\n          name: trust-roots\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_TLS\n          value: \"8080\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.example.com.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.example.com.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8080,9990\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.example.com.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: example.com\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: localhost.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.example.com\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.example.com\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.example.com\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.example.com\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-identity\n      volumes:\n      - name: identity-issuer\n        secret:\n          secretName: linkerd-identity-issuer\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n---\n###\n### Destination Controller Service\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-dst-headless\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8086\n    targetPort: 8086\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-sp-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: sp-validator\n    port: 443\n    targetPort: sp-validator\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  clusterIP: None\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: grpc\n    port: 8090\n    targetPort: 8090\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-policy-validator\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: destination\n  ports:\n  - name: policy-https\n    port: 443\n    targetPort: policy-https\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: destination\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: destination\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-destination\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: destination\n      linkerd.io/control-plane-ns: linkerd\n      linkerd.io/proxy-deployment: linkerd-destination\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: a39770e2a1ac1f4bb452509e52a4ffd176bcb034f1ce906491f2649e28c67319\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: destination\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-destination\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: localhost.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: localhost.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8086,8090,8443,9443,9990,9996,9997\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.example.com.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: example.com\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.example.com.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.example.com\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.example.com\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.example.com\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.example.com\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - destination\n        - -addr=:8086\n        - -controller-namespace=linkerd\n        - -outbound-transport-mode=transport-header\n        - -enable-h2-upgrade=true\n        - -log-level=info\n        - -log-format=plain\n        - -enable-endpoint-slices=true\n        - -cluster-domain=example.com\n        - -identity-trust-domain=example.com\n        - -default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - -enable-ipv6=false\n        - -enable-pprof=false\n        - --meshed-http2-client-params={\"keep_alive\":{\"interval\":{\"seconds\":10},\"timeout\":{\"seconds\":3},\"while_idle\":true}}\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9996\n          initialDelaySeconds: 10\n        name: destination\n        ports:\n        - containerPort: 8086\n          name: dest-grpc\n        - containerPort: 9996\n          name: dest-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9996\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - args:\n        - sp-validator\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9997\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: sp-validator\n        ports:\n        - containerPort: 8443\n          name: sp-validator\n        - containerPort: 9997\n          name: spval-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9997\n          timeoutSeconds: 1\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: sp-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      - command: [\"/linkerd-policy-controller\"]\n        args:\n        - --admin-addr=0.0.0.0:9990\n        - --control-plane-namespace=linkerd\n        - --grpc-addr=0.0.0.0:8090\n        - --server-addr=0.0.0.0:9443\n        - --server-tls-key=/var/run/linkerd/tls/tls.key\n        - --server-tls-certs=/var/run/linkerd/tls/tls.crt\n        - --cluster-networks=10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\n        - --identity-domain=example.com\n        - --cluster-domain=example.com\n        - --default-policy=all-unauthenticated\n        - --log-level=info\n        - --log-format=plain\n        - --default-opaque-ports=25,587,3306,4444,5432,6379,9300,11211\n        - --global-egress-network-namespace=linkerd-egress\n        - --probe-networks=0.0.0.0/0,::/0\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: policy-admin\n          initialDelaySeconds: 10\n        name: policy\n        ports:\n        - containerPort: 8090\n          name: policy-grpc\n        - containerPort: 9990\n          name: policy-admin\n        - containerPort: 9443\n          name: policy-https\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: policy-admin\n        resources:\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: policy-tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-destination\n      volumes:\n      - name: sp-tls\n        secret:\n          secretName: linkerd-sp-validator-k8s-tls\n      - name: policy-tls\n        secret:\n          secretName: linkerd-policy-validator-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\n###\n### Heartbeat\n###\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: linkerd-heartbeat\n  namespace: linkerd\n  labels:\n    app.kubernetes.io/name: heartbeat\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: heartbeat\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  concurrencyPolicy: Replace\n  schedule: \"1 2 3 4 5\"\n  successfulJobsHistoryLimit: 0\n  jobTemplate:\n    spec:\n      template:\n        metadata:\n          labels:\n            linkerd.io/control-plane-component: heartbeat\n            linkerd.io/workload-ns: linkerd\n          annotations:\n            linkerd.io/created-by: linkerd/cli dev-undefined\n        spec:\n          nodeSelector:\n            kubernetes.io/os: linux\n          securityContext:\n            seccompProfile:\n              type: RuntimeDefault\n          serviceAccountName: linkerd-heartbeat\n          restartPolicy: Never\n          automountServiceAccountToken: false\n          containers:\n          - name: heartbeat\n            image: cr.l5d.io/linkerd/controller:install-control-plane-version\n            imagePullPolicy: IfNotPresent\n            env:\n            - name: LINKERD_DISABLED\n              value: \"the heartbeat controller does not use the proxy\"\n            args:\n            - \"heartbeat\"\n            - \"-controller-namespace=linkerd\"\n            - \"-log-level=info\"\n            - \"-log-format=plain\"\n            - \"-prometheus-url=http://prometheus.linkerd-viz.svc.example.com:9090\"\n            securityContext:\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: true\n              runAsNonRoot: true\n              runAsUser: 2103\n              allowPrivilegeEscalation: false\n              seccompProfile:\n                type: RuntimeDefault\n            volumeMounts:\n            - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n              name: kube-api-access\n              readOnly: true\n          volumes:\n          - name: kube-api-access\n            projected:\n              defaultMode: 420\n              sources:\n              - serviceAccountToken:\n                  expirationSeconds: 3607\n                  path: token\n              - configMap:\n                  items:\n                  - key: ca.crt\n                    path: ca.crt\n                  name: kube-root-ca.crt\n              - downwardAPI:\n                  items:\n                  - fieldRef:\n                      apiVersion: v1\n                      fieldPath: metadata.namespace\n                    path: namespace\n---\n###\n### Proxy Injector\n###\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    app.kubernetes.io/name: proxy-injector\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: install-control-plane-version\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-proxy-injector\n  namespace: linkerd\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/control-plane-component: proxy-injector\n  strategy:\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 25%\n  template:\n    metadata:\n      annotations:\n        checksum/config: cd0cf730780be444ab96a4a835a244033ffb7c8cf4a8796d0e6ae5c72aa9ff31\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/proxy-version: install-proxy-version\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        linkerd.io/trust-root-sha256: 8dc603abd4e755c25c94da05abbf29b9b283a784733651020d72f97ca8ab98e4\n        config.linkerd.io/opaque-ports: \"8443\"\n        config.linkerd.io/default-inbound-policy: \"all-unauthenticated\"\n      labels:\n        linkerd.io/control-plane-component: proxy-injector\n        linkerd.io/control-plane-ns: linkerd\n        linkerd.io/workload-ns: linkerd\n        linkerd.io/proxy-deployment: linkerd-proxy-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - env:\n        - name: _pod_name\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.name\n        - name: _pod_ns\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        - name: _pod_uid\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.uid\n        - name: _pod_ip\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIP\n        - name: _pod_nodeName\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.nodeName\n        - name: _pod_containerName\n          value: &containerName linkerd-proxy\n        - name: LINKERD2_PROXY_CORES\n          value: \"1\"\n        - name: LINKERD2_PROXY_CORES_MIN\n          value: \"1\"\n        - name: LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\n          value: \"false\"\n        - name: LINKERD2_PROXY_LOG\n          value: \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        - name: LINKERD2_PROXY_LOG_FORMAT\n          value: \"plain\"\n        - name: LINKERD2_PROXY_DESTINATION_SVC_ADDR\n          value: linkerd-dst-headless.linkerd.svc.example.com.:8086\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_POLICY_SVC_ADDR\n          value: linkerd-policy.linkerd.svc.example.com.:8090\n        - name: LINKERD2_PROXY_POLICY_WORKLOAD\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"pod\":\"$(_pod_name)\"}\n        - name: LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\n          value: all-unauthenticated\n        - name: LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\n          value: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\n          value: \"5m\"\n        - name: LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\n          value: \"1h\"\n        - name: LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\n          value: \"100ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\n          value: \"1000ms\"\n        - name: LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"5s\"\n        - name: LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\n          value: \"90s\"\n        - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n          value: \"0.0.0.0:4190\"\n        - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n          value: \"0.0.0.0:4191\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\n          value: \"127.0.0.1:4140\"\n        - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n          value: \"0.0.0.0:4143\"\n        - name: LINKERD2_PROXY_INBOUND_IPS\n          valueFrom:\n            fieldRef:\n              fieldPath: status.podIPs\n        - name: LINKERD2_PROXY_INBOUND_PORTS\n          value: \"8443,9995\"\n        - name: LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\n          value: svc.example.com.\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\n          value: 10000ms\n        - name: LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\n          value: 30s\n        - name: LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\n          value: \"false\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\n          value: \"10s\"\n        - name: LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\n          value: \"3s\"\n        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\n          value: \"25,587,3306,4444,5432,6379,9300,11211\"\n        - name: LINKERD2_PROXY_DESTINATION_CONTEXT\n          value: |\n            {\"ns\":\"$(_pod_ns)\", \"nodeName\":\"$(_pod_nodeName)\", \"pod\":\"$(_pod_name)\"}\n        - name: _pod_sa\n          valueFrom:\n            fieldRef:\n              fieldPath: spec.serviceAccountName\n        - name: _l5d_ns\n          value: linkerd\n        - name: _l5d_trustdomain\n          value: example.com\n        - name: LINKERD2_PROXY_IDENTITY_DIR\n          value: /var/run/linkerd/identity/end-entity\n        - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-identity-trust-roots\n              key: ca-bundle.crt\n        - name: LINKERD2_PROXY_IDENTITY_TOKEN_FILE\n          value: /var/run/secrets/tokens/linkerd-identity-token\n        - name: LINKERD2_PROXY_IDENTITY_SVC_ADDR\n          value: linkerd-identity-headless.linkerd.svc.example.com.:8080\n        - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n          value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.example.com\n        - name: LINKERD2_PROXY_IDENTITY_SVC_NAME\n          value: linkerd-identity.linkerd.serviceaccount.identity.linkerd.example.com\n        - name: LINKERD2_PROXY_DESTINATION_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.example.com\n        - name: LINKERD2_PROXY_POLICY_SVC_NAME\n          value: linkerd-destination.linkerd.serviceaccount.identity.linkerd.example.com\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /live\n            port: 4191\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: *containerName\n        ports:\n        - containerPort: 4143\n          name: linkerd-proxy\n        - containerPort: 4191\n          name: linkerd-admin\n        readinessProbe:\n          httpGet:\n            path: /ready\n            port: 4191\n          initialDelaySeconds: 2\n          timeoutSeconds: 1\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n              - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2102\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        lifecycle:\n          postStart:\n            exec:\n              command:\n                - /usr/lib/linkerd/linkerd-await\n                - --timeout=2m\n                - --port=4191\n        volumeMounts:\n        - mountPath: /var/run/linkerd/identity/end-entity\n          name: linkerd-identity-end-entity\n        - mountPath: /var/run/secrets/tokens\n          name: linkerd-identity-token\n      - args:\n        - proxy-injector\n        - -log-level=info\n        - -log-format=plain\n        - -linkerd-namespace=linkerd\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/controller:install-control-plane-version\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: proxy-injector\n        ports:\n        - containerPort: 8443\n          name: proxy-injector\n        - containerPort: 9995\n          name: injector-admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        securityContext:\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          allowPrivilegeEscalation: false\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/config\n          name: config\n        - mountPath: /var/run/linkerd/identity/trust-roots\n          name: trust-roots\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      initContainers:\n      - args:\n        - --firewall-bin-path\n        - \"iptables-nft\"\n        - --firewall-save-bin-path\n        - \"iptables-nft-save\"\n        - --ipv6=false\n        - --incoming-proxy-port\n        - \"4143\"\n        - --outgoing-proxy-port\n        - \"4140\"\n        - --proxy-uid\n        - \"2102\"\n        - --inbound-ports-to-ignore\n        - \"4190,4191,4567,4568\"\n        - --outbound-ports-to-ignore\n        - \"443,6443\"\n        image: cr.l5d.io/linkerd/proxy:install-proxy-version\n        command: [\"/usr/lib/linkerd/linkerd2-proxy-init\"]\n        imagePullPolicy: IfNotPresent\n        name: linkerd-init\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            add:\n            - NET_ADMIN\n            - NET_RAW\n          privileged: false\n          runAsNonRoot: true\n          runAsUser: 65534\n          runAsGroup: 65534\n          readOnlyRootFilesystem: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePolicy: FallbackToLogsOnError\n        volumeMounts:\n        - mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-proxy-injector\n      volumes:\n      - configMap:\n          name: linkerd-config\n        name: config\n      - configMap:\n          name: linkerd-identity-trust-roots\n        name: trust-roots\n      - name: tls\n        secret:\n          secretName: linkerd-proxy-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n      - emptyDir: {}\n        name: linkerd-proxy-init-xtables-lock\n      - name: linkerd-identity-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: linkerd-identity-token\n              expirationSeconds: 86400\n              audience: identity.l5d.io\n      - emptyDir:\n          medium: Memory\n        name: linkerd-identity-end-entity\n      \n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: linkerd\n  labels:\n    linkerd.io/control-plane-component: proxy-injector\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    config.linkerd.io/opaque-ports: \"443\"\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/control-plane-component: proxy-injector\n  ports:\n  - name: proxy-injector\n    port: 443\n    targetPort: proxy-injector\n---\napiVersion: v1\ndata:\n  linkerd-config-overrides: Y2x1c3RlckRvbWFpbjogZXhhbXBsZS5jb20KZGVidWdDb250YWluZXI6CiAgaW1hZ2U6CiAgICB2ZXJzaW9uOiBpbnN0YWxsLWRlYnVnLXZlcnNpb24KaGVhcnRiZWF0U2NoZWR1bGU6IDEgMiAzIDQgNQppZGVudGl0eToKICBpc3N1ZXI6CiAgICB0bHM6CiAgICAgIGNydFBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogICAgICAgIE1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KICAgICAgICBNQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUI0WERUSXdNRGd5CiAgICAgICAgT0RBM01UTTBOMW9YRFRNd01EZ3lOakEzTVRNME4xb3dLVEVuTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdQogICAgICAgIGEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKICAgICAgICBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzJkUXZSYVlhbnV4RDM2RHQxCiAgICAgICAgMi9KeHlpU2d4S1dSZG9heSthTndNRzR3RGdZRFZSMFBBUUgvQkFRREFnRUdNQklHQTFVZEV3RUIvd1FJTUFZQgogICAgICAgIEFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKICAgICAgICBIbWxrWlc1MGFYUjVMbXhwYm10bGNtUXVZMngxYzNSbGNpNXNiMk5oYkRBS0JnZ3Foa2pPUFFRREFnTkhBREJFCiAgICAgICAgQWlBdHVvSTVYdUN0ckdWUnpTbVJUbDJyYTI4YVY5TXlUVTdkNXFuVEFGSEtTZ0lnUktDdmx1T1NnQTVPMjFwNQogICAgICAgIDUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KICAgICAgICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCiAgICAgIGtleVBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gRUMgUFJJVkFURSBLRVktLS0tLQogICAgICAgIE1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKICAgICAgICBBd0VIb1VRRFFnQUUxL0ZwZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyCiAgICAgICAgZFF2UmFZYW51eEQzNkR0MTIvSnh5aVNneEtXUmRvYXkrUT09CiAgICAgICAgLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQppZGVudGl0eVRydXN0QW5jaG9yc1BFTTogfAogIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogIE1JSUJ3VENDQVdhZ0F3SUJBZ0lRZURacDVsRGFJeWdRNVVmTUtackZBVEFLQmdncWhrak9QUVFEQWpBcE1TY3cKICBKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3SGhjTk1qQXdPREk0CiAgTURjeE1qUTNXaGNOTXpBd09ESTJNRGN4TWpRM1dqQXBNU2N3SlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1cgogIFpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFScWM3MFoKICBsMXZndzc5cmpCNXVTSVRJQ1VBNkd5ZnZTRmZjdUlpczdCL1hGU2trd0FIVTVTL3MxQUFQK1IwVFg3SEJXVUM0CiAgdWFHNFdXc2l3SktObjdtZ28zQXdiakFPQmdOVkhROEJBZjhFQkFNQ0FRWXdFZ1lEVlIwVEFRSC9CQWd3QmdFQgogIC93SUJBVEFkQmdOVkhRNEVGZ1FVNVl0alZWUGZkN0k3TkxIc24yQzI2RUJ5R1Ywd0tRWURWUjBSQkNJd0lJSWUKICBhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUFvR0NDcUdTTTQ5QkFNQ0Ewa0FNRVlDCiAgSVFDTjdsQkZMRER2ang2VjArWGtqcEtFUlJzSllmNWFkTXZubG9GbDQ4aWxKZ0loQU50eGhuZGNyK1FKUHVDOAogIHZnVUMwZDIvOUZNdWVJVk1iKzQ2V1RDT2pzcXIKICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCmxpbmtlcmRWZXJzaW9uOiBpbnN0YWxsLWNvbnRyb2wtcGxhbmUtdmVyc2lvbgpwb2xpY3lWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHBvbGljeSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJvZmlsZVZhbGlkYXRvcjoKICBjYUJ1bmRsZTogcHJvZmlsZSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJveHk6CiAgaW1hZ2U6CiAgICB2ZXJzaW9uOiBpbnN0YWxsLXByb3h5LXZlcnNpb24KcHJveHlJbmplY3RvcjoKICBjYUJ1bmRsZTogcHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUK\nkind: Secret\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-config-overrides\n  namespace: linkerd\n"
  },
  {
    "path": "cli/cmd/testdata/not-valid-yet-crt.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIBhzCCAS2gAwIBAgIBATAKBggqhkjOPQQDAjApMScwJQYDVQQDEx5pZGVudGl0\neS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwIhgPMjEwMDAxMDEwMTAwNTFaGA8yMTAx\nMDEwMTAxMDExMVowKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVy\nLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpBRVetcK6fZX+epoTMnG\nGPAqBxsgTchgdhEJuQ3bsPpytNTljhkyAHtLawkDCY8HRnjvfewVskCC5ss5OLKm\nLqNCMEAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF\nBQcDAjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIQC1gdlPiQcj\nhhLgyCc4nSL6elbtbm7OBDG25huN48iz8wIgWk2OtxQaY8ybfsP2vtmFRHZpE/Ic\nEdsZC90n2PnTcsI=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cli/cmd/testdata/not-valid-yet-key.pem",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIOz9vNj0nut2KDnptSeTV4x7IIK1t3jwRsBU7Fl9Cj2voAoGCCqGSM49\nAwEHoUQDQgAEpBRVetcK6fZX+epoTMnGGPAqBxsgTchgdhEJuQ3bsPpytNTljhky\nAHtLawkDCY8HRnjvfewVskCC5ss5OLKmLg==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "cli/cmd/testdata/not-valid-yet-trust-anchors.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIBhzCCAS2gAwIBAgIBATAKBggqhkjOPQQDAjApMScwJQYDVQQDEx5pZGVudGl0\neS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwIhgPMjEwMDAxMDEwMTAwNTFaGA8yMTAx\nMDEwMTAxMDExMVowKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVy\nLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpBRVetcK6fZX+epoTMnG\nGPAqBxsgTchgdhEJuQ3bsPpytNTljhkyAHtLawkDCY8HRnjvfewVskCC5ss5OLKm\nLqNCMEAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF\nBQcDAjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIQC1gdlPiQcj\nhhLgyCc4nSL6elbtbm7OBDG25huN48iz8wIgWk2OtxQaY8ybfsP2vtmFRHZpE/Ic\nEdsZC90n2PnTcsI=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cli/cmd/testdata/obfuscate-diagnostics-proxy-metrics.golden",
    "content": "# HELP inbound_http_authz_allow_total The total number of inbound HTTP requests that were authorized\n# TYPE inbound_http_authz_allow_total counter\ninbound_http_authz_allow_total{target_addr=\"f21fd977\",target_ip=\"0.0.0.0\",target_port=\"4191\",srv_name=\"default:all-unauthenticated\",saz_name=\"default:all-unauthenticated\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\"} 1319\ninbound_http_authz_allow_total{target_addr=\"d5b2560e\",target_ip=\"10.42.0.63\",target_port=\"8080\",srv_name=\"default:all-unauthenticated\",saz_name=\"default:all-unauthenticated\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\"} 7\ninbound_http_authz_allow_total{target_addr=\"541ef355\",target_ip=\"10.42.0.63\",target_port=\"8090\",srv_name=\"default:all-unauthenticated\",saz_name=\"default:all-unauthenticated\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\"} 1\ninbound_http_authz_allow_total{target_addr=\"541ef355\",target_ip=\"10.42.0.63\",target_port=\"8090\",srv_name=\"default:all-unauthenticated\",saz_name=\"default:all-unauthenticated\",tls=\"true\",client_id=\"b1da0aee\"} 3147\n"
  },
  {
    "path": "cli/cmd/testdata/obfuscate-diagnostics-proxy-metrics.input",
    "content": "# HELP inbound_http_authz_allow_total The total number of inbound HTTP requests that were authorized\n# TYPE inbound_http_authz_allow_total counter\ninbound_http_authz_allow_total{target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",srv_name=\"default:all-unauthenticated\",saz_name=\"default:all-unauthenticated\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\"} 1319\ninbound_http_authz_allow_total{target_addr=\"10.42.0.63:8080\",target_ip=\"10.42.0.63\",target_port=\"8080\",srv_name=\"default:all-unauthenticated\",saz_name=\"default:all-unauthenticated\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\"} 7\ninbound_http_authz_allow_total{target_addr=\"10.42.0.63:8090\",target_ip=\"10.42.0.63\",target_port=\"8090\",srv_name=\"default:all-unauthenticated\",saz_name=\"default:all-unauthenticated\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\"} 1\ninbound_http_authz_allow_total{target_addr=\"10.42.0.63:8090\",target_ip=\"10.42.0.63\",target_port=\"8090\",srv_name=\"default:all-unauthenticated\",saz_name=\"default:all-unauthenticated\",tls=\"true\",client_id=\"default.default.serviceaccount.identity.linkerd.cluster.local\"} 3147\n"
  },
  {
    "path": "cli/cmd/testdata/prom-config.yaml",
    "content": "prometheus:\n  image: linkedin.io/prom\n  args:\n    log.format: json\n  globalConfig:\n    evaluation_interval: 2m\n    external_labels:\n      cluster: cluster-1\n\n  scrapeConfigs:\n  - job_name: 'kubernetes-nodes'\n    scheme: https\n    tls_config:\n      ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n    bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token\n    kubernetes_sd_configs:\n    - role: node\n    relabel_configs:\n    - action: labelmap\n      regex: __meta_kubernetes_node_label_(.+)\n  alertmanagers:\n  - scheme: http\n    static_configs:\n    - targets:\n      - \"alertmanager.linkerd.svc:9093\"\n  alertRelabelConfigs:\n  - action: labeldrop\n    regex: prometheus_replica\n  ruleConfigMapMounts:\n  - name: alerting-rules\n    subPath: alerting_rules.yml\n    configMap: linkerd-prometheus-rules\n  - name: recording-rules\n    subPath: recording_rules.yml\n    configMap: linkerd-prometheus-rules\n  remoteWrite:\n  - url: http://cortex-service.default:9009/api/prom/push\n  sidecarContainers:\n  - name: sidecar\n    lifecycle:\n      type: Sidecar\n    imagePullPolicy: always\n    command:\n    - /bin/sh\n    - -c\n    - |\n      exec /bin/stackdriver-prometheus-sidecar \\\n        --stackdriver.project-id=myproject \\\n        --stackdriver.kubernetes.location=us-central1 \\\n        --stackdriver.kubernetes.cluster-name=mycluster \\\n        --prometheus.wal-directory=/data/wal \\\n        --log.level=info\n      volumeMounts:\n      - mountPath: /data\n        name: data\n    ports:\n    - name: foo\n      containerPort: 9091\n      protocol: TCP\n"
  },
  {
    "path": "cli/cmd/testdata/upgrade_crds_with_gateway_api.golden",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: authorizationpolicies.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: AuthorizationPolicy\n    plural: authorizationpolicies\n    singular: authorizationpolicy\n    shortNames: [authzpolicy]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied server\n                resources.\n              type: object\n              required: [targetRef, requiredAuthenticationRefs]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the authorization\n                    policy applies.\n                  type: object\n                  required: [kind, name]\n                  # Modified from the gateway API.\n                  # Copyright 2020 The Kubernetes Authors\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: >-\n                        Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                requiredAuthenticationRefs:\n                  description: >-\n                    RequiredAuthenticationRefs enumerates a set of required\n                    authentications. ALL authentications must be satisfied for\n                    the authorization to apply. If any of the referred objects\n                    cannot be found, the authorization will be ignored.\n                  type: array\n                  items:\n                    type: object\n                    required: [kind, name]\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: egressnetworks.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    categories:\n    - policy\n    kind: EgressNetwork\n    listKind: EgressNetworkList\n    plural: egressnetworks\n    singular: egressnetwork\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An EgressNetwork captures traffic to egress destinations\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              trafficPolicy:\n                description: >-\n                  This field controls the traffic policy enforced upon traffic\n                  that does not match any explicit route resources associated\n                  with an instance of this object. The values that are allowed\n                  currently are:\n                   - Allow - permits all traffic, even if it has not been\n                                explicitly described via attaching an xRoute\n                                resources.\n                   - Deny -  blocks all traffic that has not been described via\n                                attaching an xRoute resource.\n                type: string\n                enum:\n                - Allow\n                - Deny\n              networks:\n                type: array\n                items:\n                  type: object\n                  required: [cidr]\n                  properties:\n                    cidr:\n                      description: >-\n                        The CIDR of the network to be authorized.\n                      type: string\n                    except:\n                      description: >-\n                        A list of IP networks/addresses not to be included in\n                        the above `cidr`.\n                      type: array\n                      items:\n                        type: string\n            type: object\n            required:\n            - trafficPolicy\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httplocalratelimitpolicies.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPLocalRateLimitPolicy\n    listKind: HTTPLocalRateLimitPolicyList\n    plural: httplocalratelimitpolicies\n    singular: httplocalratelimitpolicy\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      subresources:\n        status: {}\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required: [targetRef]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the rate limit\n                    policy applies. Only Server is allowed.\n                  type: object\n                  required: [kind, name]\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                total:\n                  description: >-\n                    Overall rate-limit, which all traffic coming to this\n                    target should abide.\n                    If unset no overall limit is applied.\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                identity:\n                  description: >-\n                    Fairness for individual identities; each separate client,\n                    grouped by identity, will have this rate-limit. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                overrides:\n                  description: >-\n                    Overrides for traffic from a specific client. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: array\n                  items:\n                    type: object\n                    required: [requestsPerSecond, clientRefs]\n                    properties:\n                      requestsPerSecond:\n                        format: int64\n                        type: integer\n                      clientRefs:\n                        type: array\n                        items:\n                          type: object\n                          required: [kind, name]\n                          properties:\n                            group:\n                              description: >-\n                                Group is the group of the referent. When empty, the\n                                Kubernetes core API group is inferred.\n                              maxLength: 253\n                              pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                              type: string\n                            kind:\n                              description: Kind is the kind of the referent.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                              type: string\n                            namespace:\n                              description: >-\n                                  Namespace is the namespace of the referent.\n                                  When unspecified (or empty string), this refers to the\n                                  local namespace of the Policy.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                              type: string\n                            name:\n                              description: Name is the name of the referent.\n                              maxLength: 253\n                              minLength: 1\n                              type: string\n            status:\n              type: object\n              properties:\n                conditions:\n                  type: array\n                  items:\n                    type: object\n                    properties:\n                      lastTransitionTime:\n                        description: lastTransitionTime is the last time the\n                          condition transitioned from one status to another.\n                        format: date-time\n                        type: string\n                      status:\n                        description: status of the condition (one of True, False, Unknown)\n                        enum:\n                        - \"True\"\n                        - \"False\"\n                        - Unknown\n                        type: string\n                      type:\n                        description: type of the condition in CamelCase or in\n                          foo.example.com/CamelCase.\n                        maxLength: 316\n                        pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                        type: string\n                      reason:\n                        description: reason contains a programmatic identifier\n                          indicating the reason for the condition's last\n                          transition. Producers of specific condition types may\n                          define expected values and meanings for this field, and\n                          whether the values are considered a guaranteed API. The\n                          value should be a CamelCase string. This field may not\n                          be empty.\n                        maxLength: 1024\n                        minLength: 1\n                        pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                        type: string\n                      message:\n                        description: message is a human readable message\n                          indicating details about the transition. This may be an\n                          empty string.\n                        maxLength: 32768\n                        type: string\n                  required:\n                  - status\n                  - type\n                targetRef:\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Server\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                  required:\n                  - name\n                  type: object\n              required:\n              - targetRef\n      additionalPrinterColumns:\n      - name: Target_kind\n        description: The resource kind to which the rate-limit applies\n        type: string\n        jsonPath: .spec.targetRef.kind\n      - name: Target_name\n        type: string\n        description: The resource name to which the rate-limit applies\n        jsonPath: .spec.targetRef.name\n      - name: Total_RPS\n        description: The overall rate-limit\n        type: integer\n        format: int32\n        jsonPath: .spec.total.requestsPerSecond\n      - name: Identity_RPS\n        description: The rate-limit per identity\n        type: integer\n        format: int32\n        jsonPath: .spec.identity.requestsPerSecond\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httproutes.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPRoute\n    listKind: HTTPRouteList\n    plural: httproutes\n    singular: httproute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\n                              All   implementations must support core filters. \\n\\n \"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta2\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta3\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                    timeouts:\n                      description: \"Timeouts defines the timeouts that can be configured\n                        for an HTTP request. \\n Support: Core \\n <gateway:experimental>\"\n                      properties:\n                        backendRequest:\n                          description: \"BackendRequest specifies a timeout for an\n                            individual request from the gateway to a backend service.\n                            Typically used in conjunction with automatic retries,\n                            if supported by an implementation. Default is the value\n                            of Request timeout. \\n Support: Extended\"\n                          format: duration\n                          type: string\n                        request:\n                          description: \"Request specifies a timeout for responding\n                            to client HTTP requests, disabled by default. \\n For example,\n                            the following rule will timeout if a client request is\n                            taking longer than 10 seconds to complete: \\n ``` rules:\n                            - timeouts: request: 10s backendRefs: ... ``` \\n Support:\n                            Core\"\n                          format: duration\n                          type: string\n                      type: object\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: meshtlsauthentications.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: MeshTLSAuthentication\n    plural: meshtlsauthentications\n    singular: meshtlsauthentication\n    shortNames: [meshtlsauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                MeshTLSAuthentication defines a list of authenticated client IDs\n                to be referenced by an `AuthorizationPolicy`. If a client\n                connection has the mutually-authenticated identity that matches\n                ANY of the of the provided identities, the connection is\n                considered authenticated.\n              type: object\n              oneOf:\n                - required: [identities]\n                - required: [identityRefs]\n              properties:\n                identities:\n                  description: >-\n                    Authorizes clients with the provided proxy identity strings\n                    (as provided via MTLS)\n\n                    The `*` prefix can be used to match all identities in\n                    a domain. An identity string of `*` indicates that\n                    all authentication clients are authorized.\n                  type: array\n                  minItems: 1\n                  items:\n                    type: string\n                identityRefs:\n                  type: array\n                  minItems: 1\n                  items:\n                    type: object\n                    required:\n                      - kind\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this refers to all resources of the specified Group\n                          and Kind in the specified namespace.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: networkauthentications.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: NetworkAuthentication\n    plural: networkauthentications\n    singular: networkauthentication\n    shortNames: [netauthn, networkauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                NetworkAuthentication defines a list of authenticated client\n                networks to be referenced by an `AuthorizationPolicy`. If a\n                client connection originates from ANY of the of the provided\n                networks, the connection is considered authenticated.\n              type: object\n              required: [networks]\n              properties:\n                networks:\n                  type: array\n                  items:\n                    type: object\n                    required: [cidr]\n                    properties:\n                      cidr:\n                        description: >-\n                          The CIDR of the network to be authorized.\n                        type: string\n                      except:\n                        description: >-\n                          A list of IP networks/addresses not to be included in\n                          the above `cidr`.\n                        type: array\n                        items:\n                          type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serverauthorizations.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: ServerAuthorization\n    plural: serverauthorizations\n    singular: serverauthorization\n    shortNames: [saz, serverauthz, srvauthz]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 ServerAuthorization is deprecated; use policy.linkerd.io/v1beta1 ServerAuthorization\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n    - name: v1beta1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n      additionalPrinterColumns:\n      - name: Server\n        type: string\n        description: The server that this grants access to\n        jsonPath: .spec.server.name\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: servers.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: Server\n    plural: servers\n    singular: server\n    shortNames: [srv]\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 Server is deprecated; use policy.linkerd.io/v1beta1 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n                  oneOf:\n                    - required: [matchExpressions]\n                    - required: [matchLabels]\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n    - name: v1beta1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1beta1 Server is deprecated; use policy.linkerd.io/v1beta3 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta2\n      served: true\n      storage: false\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta3\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                accessPolicy:\n                  type: string\n                  default: deny\n                  description: >-\n                    Default access policy to apply when the traffic doesn't match any of the policy rules.\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n      - name: Access Policy\n        type: string\n        description: The default access policy applied when the traffic doesn't match any of the policy rules\n        jsonPath: .spec.accessPolicy\n---\n###\n### Service Profile CRD\n###\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serviceprofiles.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: linkerd.io\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            required:\n            - routes\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  - name: v1alpha2\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  scope: Namespaced\n  names:\n    plural: serviceprofiles\n    singular: serviceprofile\n    kind: ServiceProfile\n    shortNames:\n    - sp\n\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997\n    gateway.networking.k8s.io/bundle-version: v1.1.1\n    gateway.networking.k8s.io/channel: experimental\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    helm.sh/resource-policy: keep\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\n  creationTimestamp: null\n  name: httproutes.gateway.networking.k8s.io\nspec:\n  group: gateway.networking.k8s.io\n  names:\n    categories:\n    - gateway-api\n    kind: HTTPRoute\n    listKind: HTTPRouteList\n    plural: httproutes\n    singular: httproute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1\n    schema:\n      openAPIV3Schema:\n        description: |-\n          HTTPRoute provides a way to route HTTP requests. This includes the capability\n          to match requests by hostname, path, header, or query param. Filters can be\n          used to specify additional processing steps. Backends specify where matching\n          requests should be routed.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: |-\n                  Hostnames defines a set of hostnames that should match against the HTTP Host\n                  header to select a HTTPRoute used to process the request. Implementations\n                  MUST ignore any port value specified in the HTTP Host header while\n                  performing a match and (absent of any applicable header modification\n                  configuration) MUST forward this header unmodified to the backend.\n\n\n                  Valid values for Hostnames are determined by RFC 1123 definition of a\n                  hostname with 2 notable exceptions:\n\n\n                  1. IPs are not allowed.\n                  2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                     label must appear by itself as the first label.\n\n\n                  If a hostname is specified by both the Listener and HTTPRoute, there\n                  must be at least one intersecting hostname for the HTTPRoute to be\n                  attached to the Listener. For example:\n\n\n                  * A Listener with `test.example.com` as the hostname matches HTTPRoutes\n                    that have either not specified any hostnames, or have specified at\n                    least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                    that have either not specified any hostnames or have specified at least\n                    one hostname that matches the Listener hostname. For example,\n                    `*.example.com`, `test.example.com`, and `foo.test.example.com` would\n                    all match. On the other hand, `example.com` and `test.example.net` would\n                    not match.\n\n\n                  Hostnames that are prefixed with a wildcard label (`*.`) are interpreted\n                  as a suffix match. That means that a match for `*.example.com` would match\n                  both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\n\n                  If both the Listener and HTTPRoute have specified hostnames, any\n                  HTTPRoute hostnames that do not match the Listener hostname MUST be\n                  ignored. For example, if a Listener specified `*.example.com`, and the\n                  HTTPRoute specified `test.example.com` and `test.example.net`,\n                  `test.example.net` must not be considered for a match.\n\n\n                  If both the Listener and HTTPRoute have specified hostnames, and none\n                  match with the criteria above, then the HTTPRoute is not accepted. The\n                  implementation must raise an 'Accepted' Condition with a status of\n                  `False` in the corresponding RouteParentStatus.\n\n\n                  In the event that multiple HTTPRoutes specify intersecting hostnames (e.g.\n                  overlapping wildcard matching and exact matching hostnames), precedence must\n                  be given to rules from the HTTPRoute with the largest number of:\n\n\n                  * Characters in a matching non-wildcard hostname.\n                  * Characters in a matching hostname.\n\n\n                  If ties exist across multiple Routes, the matching precedence rules for\n                  HTTPRouteMatches takes over.\n\n\n                  Support: Core\n                items:\n                  description: |-\n                    Hostname is the fully qualified domain name of a network host. This matches\n                    the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                     1. IPs are not allowed.\n                     2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                        label must appear by itself as the first label.\n\n\n                    Hostname can be \"precise\" which is a domain name without the terminating\n                    dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a\n                    domain name prefixed with a single wildcard label (e.g. `*.example.com`).\n\n\n                    Note that as per RFC1035 and RFC1123, a *label* must consist of lower case\n                    alphanumeric characters or '-', and must start and end with an alphanumeric\n                    character. No other punctuation is allowed.\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: |-\n                    HTTPRouteRule defines semantics for matching an HTTP request based on\n                    conditions (matches), processing it (filters), and forwarding the request to\n                    an API object (backendRefs).\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent.\n\n\n                        Failure behavior here depends on how many BackendRefs are specified and\n                        how many are invalid.\n\n\n                        If *all* entries in BackendRefs are invalid, and there are also no filters\n                        specified in this route rule, *all* traffic which matches this rule MUST\n                        receive a 500 status code.\n\n\n                        See the HTTPBackendRef definition for the rules about what makes a single\n                        HTTPBackendRef invalid.\n\n\n                        When a HTTPBackendRef is invalid, 500 status codes MUST be returned for\n                        requests that would have otherwise been routed to an invalid backend. If\n                        multiple backends are specified, and some are invalid, the proportion of\n                        requests that would otherwise have been routed to an invalid backend\n                        MUST receive a 500 status code.\n\n\n                        For example, if two backends are specified with equal weights, and one is\n                        invalid, 50 percent of traffic must receive a 500. Implementations may\n                        choose how that 50 percent is determined.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Extended for Kubernetes ServiceImport\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Core\n                      items:\n                        description: |-\n                          HTTPBackendRef defines how a HTTPRoute forwards a HTTP request.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n                        properties:\n                          filters:\n                            description: |-\n                              Filters defined at this level should be executed if and only if the\n                              request is being forwarded to the backend defined here.\n\n\n                              Support: Implementation-specific (For broader support of filters, use the\n                              Filters field in HTTPRouteRule.)\n                            items:\n                              description: |-\n                                HTTPRouteFilter defines processing steps that must be completed during the\n                                request or response lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway implementations. Some\n                                examples include request or response modification, implementing\n                                authentication strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type of the filter.\n                              properties:\n                                extensionRef:\n                                  description: |-\n                                    ExtensionRef is an optional, implementation-specific extension to the\n                                    \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                                    \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                                    extended filters.\n\n\n                                    This filter can be used multiple times within the same rule.\n\n\n                                    Support: Implementation-specific\n                                  properties:\n                                    group:\n                                      description: |-\n                                        Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                        When unspecified or empty string, core API group is inferred.\n                                      maxLength: 253\n                                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    kind:\n                                      description: Kind is kind of the referent. For\n                                        example \"HTTPRoute\" or \"Service\".\n                                      maxLength: 63\n                                      minLength: 1\n                                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                      type: string\n                                    name:\n                                      description: Name is the name of the referent.\n                                      maxLength: 253\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - group\n                                  - kind\n                                  - name\n                                  type: object\n                                requestHeaderModifier:\n                                  description: |-\n                                    RequestHeaderModifier defines a schema for a filter that modifies request\n                                    headers.\n\n\n                                    Support: Core\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestMirror:\n                                  description: |-\n                                    RequestMirror defines a schema for a filter that mirrors requests.\n                                    Requests are sent to the specified destination, but responses from\n                                    that destination are ignored.\n\n\n                                    This filter can be used multiple times within the same rule. Note that\n                                    not all implementations will be able to support mirroring to multiple\n                                    backends.\n\n\n                                    Support: Extended\n                                  properties:\n                                    backendRef:\n                                      description: |-\n                                        BackendRef references a resource where mirrored requests are sent.\n\n\n                                        Mirrored requests must be sent only to a single destination endpoint\n                                        within this BackendRef, irrespective of how many endpoints are present\n                                        within this BackendRef.\n\n\n                                        If the referent cannot be found, this BackendRef is invalid and must be\n                                        dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                        condition on the Route status is set to `status: False` and not configure\n                                        this backend in the underlying implementation.\n\n\n                                        If there is a cross-namespace reference to an *existing* object\n                                        that is not allowed by a ReferenceGrant, the controller must ensure the\n                                        \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                        with the \"RefNotPermitted\" reason and not configure this backend in the\n                                        underlying implementation.\n\n\n                                        In either error case, the Message of the `ResolvedRefs` Condition\n                                        should be used to provide more detail about the problem.\n\n\n                                        Support: Extended for Kubernetes Service\n\n\n                                        Support: Implementation-specific for any other resource\n                                      properties:\n                                        group:\n                                          default: \"\"\n                                          description: |-\n                                            Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                            When unspecified or empty string, core API group is inferred.\n                                          maxLength: 253\n                                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                          type: string\n                                        kind:\n                                          default: Service\n                                          description: |-\n                                            Kind is the Kubernetes resource kind of the referent. For example\n                                            \"Service\".\n\n\n                                            Defaults to \"Service\" when not specified.\n\n\n                                            ExternalName services can refer to CNAME DNS records that may live\n                                            outside of the cluster and as such are difficult to reason about in\n                                            terms of conformance. They also may not be safe to forward to (see\n                                            CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                            support ExternalName Services.\n\n\n                                            Support: Core (Services with a type other than ExternalName)\n\n\n                                            Support: Implementation-specific (Services with type ExternalName)\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                          type: string\n                                        name:\n                                          description: Name is the name of the referent.\n                                          maxLength: 253\n                                          minLength: 1\n                                          type: string\n                                        namespace:\n                                          description: |-\n                                            Namespace is the namespace of the backend. When unspecified, the local\n                                            namespace is inferred.\n\n\n                                            Note that when a namespace different than the local namespace is specified,\n                                            a ReferenceGrant object is required in the referent namespace to allow that\n                                            namespace's owner to accept the reference. See the ReferenceGrant\n                                            documentation for details.\n\n\n                                            Support: Core\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                          type: string\n                                        port:\n                                          description: |-\n                                            Port specifies the destination port number to use for this resource.\n                                            Port is required when the referent is a Kubernetes Service. In this\n                                            case, the port number is the service port number, not the target port.\n                                            For other resources, destination port might be derived from the referent\n                                            resource or this field.\n                                          format: int32\n                                          maximum: 65535\n                                          minimum: 1\n                                          type: integer\n                                      required:\n                                      - name\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: Must have port for Service reference\n                                        rule: '(size(self.group) == 0 && self.kind\n                                          == ''Service'') ? has(self.port) : true'\n                                  required:\n                                  - backendRef\n                                  type: object\n                                requestRedirect:\n                                  description: |-\n                                    RequestRedirect defines a schema for a filter that responds to the\n                                    request with an HTTP redirection.\n\n\n                                    Support: Core\n                                  properties:\n                                    hostname:\n                                      description: |-\n                                        Hostname is the hostname to be used in the value of the `Location`\n                                        header in the response.\n                                        When empty, the hostname in the `Host` header of the request is used.\n\n\n                                        Support: Core\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: |-\n                                        Path defines parameters used to modify the path of the incoming request.\n                                        The modified path is then used to construct the `Location` header. When\n                                        empty, the request path is used as-is.\n\n\n                                        Support: Extended\n                                      properties:\n                                        replaceFullPath:\n                                          description: |-\n                                            ReplaceFullPath specifies the value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: |-\n                                            ReplacePrefixMatch specifies the value with which to replace the prefix\n                                            match of a request during a rewrite or redirect. For example, a request\n                                            to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                            of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                            Note that this matches the behavior of the PathPrefix match type. This\n                                            matches full path elements. A path element refers to the list of labels\n                                            in the path split by the `/` separator. When specified, a trailing `/` is\n                                            ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                            match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                            ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                            Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                            the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                            Request Path | Prefix Match | Replace Prefix | Modified Path\n                                            -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                            /foo         | /foo         | /xyz           | /xyz\n                                            /foo/        | /foo         | /xyz           | /xyz/\n                                            /foo/bar     | /foo         | <empty string> | /bar\n                                            /foo/        | /foo         | <empty string> | /\n                                            /foo         | /foo         | <empty string> | /\n                                            /foo/        | /foo         | /              | /\n                                            /foo         | /foo         | /              | /\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: |-\n                                            Type defines the type of path modifier. Additional types may be\n                                            added in a future release of the API.\n\n\n                                            Note that values may be added to this enum, implementations\n                                            must ensure that unknown values will not cause a crash.\n\n\n                                            Unknown values here must result in the implementation setting the\n                                            Accepted Condition for the Route to `status: False`, with a\n                                            Reason of `UnsupportedValue`.\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: replaceFullPath must be specified\n                                          when type is set to 'ReplaceFullPath'\n                                        rule: 'self.type == ''ReplaceFullPath'' ?\n                                          has(self.replaceFullPath) : true'\n                                      - message: type must be 'ReplaceFullPath' when\n                                          replaceFullPath is set\n                                        rule: 'has(self.replaceFullPath) ? self.type\n                                          == ''ReplaceFullPath'' : true'\n                                      - message: replacePrefixMatch must be specified\n                                          when type is set to 'ReplacePrefixMatch'\n                                        rule: 'self.type == ''ReplacePrefixMatch''\n                                          ? has(self.replacePrefixMatch) : true'\n                                      - message: type must be 'ReplacePrefixMatch'\n                                          when replacePrefixMatch is set\n                                        rule: 'has(self.replacePrefixMatch) ? self.type\n                                          == ''ReplacePrefixMatch'' : true'\n                                    port:\n                                      description: |-\n                                        Port is the port to be used in the value of the `Location`\n                                        header in the response.\n\n\n                                        If no port is specified, the redirect port MUST be derived using the\n                                        following rules:\n\n\n                                        * If redirect scheme is not-empty, the redirect port MUST be the well-known\n                                          port associated with the redirect scheme. Specifically \"http\" to port 80\n                                          and \"https\" to port 443. If the redirect scheme does not have a\n                                          well-known port, the listener port of the Gateway SHOULD be used.\n                                        * If redirect scheme is empty, the redirect port MUST be the Gateway\n                                          Listener port.\n\n\n                                        Implementations SHOULD NOT add the port number in the 'Location'\n                                        header in the following cases:\n\n\n                                        * A Location header that will use HTTP (whether that is determined via\n                                          the Listener protocol or the Scheme field) _and_ use port 80.\n                                        * A Location header that will use HTTPS (whether that is determined via\n                                          the Listener protocol or the Scheme field) _and_ use port 443.\n\n\n                                        Support: Extended\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: |-\n                                        Scheme is the scheme to be used in the value of the `Location` header in\n                                        the response. When empty, the scheme of the request is used.\n\n\n                                        Scheme redirects can affect the port of the redirect, for more information,\n                                        refer to the documentation for the port field of this filter.\n\n\n                                        Note that values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause a crash.\n\n\n                                        Unknown values here must result in the implementation setting the\n                                        Accepted Condition for the Route to `status: False`, with a\n                                        Reason of `UnsupportedValue`.\n\n\n                                        Support: Extended\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: |-\n                                        StatusCode is the HTTP status code to be used in response.\n\n\n                                        Note that values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause a crash.\n\n\n                                        Unknown values here must result in the implementation setting the\n                                        Accepted Condition for the Route to `status: False`, with a\n                                        Reason of `UnsupportedValue`.\n\n\n                                        Support: Core\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: |-\n                                    ResponseHeaderModifier defines a schema for a filter that modifies response\n                                    headers.\n\n\n                                    Support: Extended\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: |-\n                                    Type identifies the type of filter to apply. As with other API fields,\n                                    types are classified into three conformance levels:\n\n\n                                    - Core: Filter types and their corresponding configuration defined by\n                                      \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                      implementations must support core filters.\n\n\n                                    - Extended: Filter types and their corresponding configuration defined by\n                                      \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                      are encouraged to support extended filters.\n\n\n                                    - Implementation-specific: Filters that are defined and supported by\n                                      specific vendors.\n                                      In the future, filters showing convergence in behavior across multiple\n                                      implementations will be considered for inclusion in extended or core\n                                      conformance levels. Filter-specific configuration for such filters\n                                      is specified using the ExtensionRef field. `Type` should be set to\n                                      \"ExtensionRef\" for custom filters.\n\n\n                                    Implementers are encouraged to define custom implementation types to\n                                    extend the core API with implementation-specific behavior.\n\n\n                                    If a reference to a custom filter type cannot be resolved, the filter\n                                    MUST NOT be skipped. Instead, requests that would have been processed by\n                                    that filter MUST receive a HTTP error response.\n\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause a crash.\n\n\n                                    Unknown values here must result in the implementation setting the\n                                    Accepted Condition for the Route to `status: False`, with a\n                                    Reason of `UnsupportedValue`.\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestMirror\n                                  - RequestRedirect\n                                  - URLRewrite\n                                  - ExtensionRef\n                                  type: string\n                                urlRewrite:\n                                  description: |-\n                                    URLRewrite defines a schema for a filter that modifies a request during forwarding.\n\n\n                                    Support: Extended\n                                  properties:\n                                    hostname:\n                                      description: |-\n                                        Hostname is the value to be used to replace the Host header value during\n                                        forwarding.\n\n\n                                        Support: Extended\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: |-\n                                        Path defines a path rewrite.\n\n\n                                        Support: Extended\n                                      properties:\n                                        replaceFullPath:\n                                          description: |-\n                                            ReplaceFullPath specifies the value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: |-\n                                            ReplacePrefixMatch specifies the value with which to replace the prefix\n                                            match of a request during a rewrite or redirect. For example, a request\n                                            to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                            of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                            Note that this matches the behavior of the PathPrefix match type. This\n                                            matches full path elements. A path element refers to the list of labels\n                                            in the path split by the `/` separator. When specified, a trailing `/` is\n                                            ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                            match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                            ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                            Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                            the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                            Request Path | Prefix Match | Replace Prefix | Modified Path\n                                            -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                            /foo         | /foo         | /xyz           | /xyz\n                                            /foo/        | /foo         | /xyz           | /xyz/\n                                            /foo/bar     | /foo         | <empty string> | /bar\n                                            /foo/        | /foo         | <empty string> | /\n                                            /foo         | /foo         | <empty string> | /\n                                            /foo/        | /foo         | /              | /\n                                            /foo         | /foo         | /              | /\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: |-\n                                            Type defines the type of path modifier. Additional types may be\n                                            added in a future release of the API.\n\n\n                                            Note that values may be added to this enum, implementations\n                                            must ensure that unknown values will not cause a crash.\n\n\n                                            Unknown values here must result in the implementation setting the\n                                            Accepted Condition for the Route to `status: False`, with a\n                                            Reason of `UnsupportedValue`.\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: replaceFullPath must be specified\n                                          when type is set to 'ReplaceFullPath'\n                                        rule: 'self.type == ''ReplaceFullPath'' ?\n                                          has(self.replaceFullPath) : true'\n                                      - message: type must be 'ReplaceFullPath' when\n                                          replaceFullPath is set\n                                        rule: 'has(self.replaceFullPath) ? self.type\n                                          == ''ReplaceFullPath'' : true'\n                                      - message: replacePrefixMatch must be specified\n                                          when type is set to 'ReplacePrefixMatch'\n                                        rule: 'self.type == ''ReplacePrefixMatch''\n                                          ? has(self.replacePrefixMatch) : true'\n                                      - message: type must be 'ReplacePrefixMatch'\n                                          when replacePrefixMatch is set\n                                        rule: 'has(self.replacePrefixMatch) ? self.type\n                                          == ''ReplacePrefixMatch'' : true'\n                                  type: object\n                              required:\n                              - type\n                              type: object\n                              x-kubernetes-validations:\n                              - message: filter.requestHeaderModifier must be nil\n                                  if the filter.type is not RequestHeaderModifier\n                                rule: '!(has(self.requestHeaderModifier) && self.type\n                                  != ''RequestHeaderModifier'')'\n                              - message: filter.requestHeaderModifier must be specified\n                                  for RequestHeaderModifier filter.type\n                                rule: '!(!has(self.requestHeaderModifier) && self.type\n                                  == ''RequestHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be nil\n                                  if the filter.type is not ResponseHeaderModifier\n                                rule: '!(has(self.responseHeaderModifier) && self.type\n                                  != ''ResponseHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be specified\n                                  for ResponseHeaderModifier filter.type\n                                rule: '!(!has(self.responseHeaderModifier) && self.type\n                                  == ''ResponseHeaderModifier'')'\n                              - message: filter.requestMirror must be nil if the filter.type\n                                  is not RequestMirror\n                                rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                              - message: filter.requestMirror must be specified for\n                                  RequestMirror filter.type\n                                rule: '!(!has(self.requestMirror) && self.type ==\n                                  ''RequestMirror'')'\n                              - message: filter.requestRedirect must be nil if the\n                                  filter.type is not RequestRedirect\n                                rule: '!(has(self.requestRedirect) && self.type !=\n                                  ''RequestRedirect'')'\n                              - message: filter.requestRedirect must be specified\n                                  for RequestRedirect filter.type\n                                rule: '!(!has(self.requestRedirect) && self.type ==\n                                  ''RequestRedirect'')'\n                              - message: filter.urlRewrite must be nil if the filter.type\n                                  is not URLRewrite\n                                rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')'\n                              - message: filter.urlRewrite must be specified for URLRewrite\n                                  filter.type\n                                rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')'\n                              - message: filter.extensionRef must be nil if the filter.type\n                                  is not ExtensionRef\n                                rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                              - message: filter.extensionRef must be specified for\n                                  ExtensionRef filter.type\n                                rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-validations:\n                            - message: May specify either httpRouteFilterRequestRedirect\n                                or httpRouteFilterRequestRewrite, but not both\n                              rule: '!(self.exists(f, f.type == ''RequestRedirect'')\n                                && self.exists(f, f.type == ''URLRewrite''))'\n                            - message: May specify either httpRouteFilterRequestRedirect\n                                or httpRouteFilterRequestRewrite, but not both\n                              rule: '!(self.exists(f, f.type == ''RequestRedirect'')\n                                && self.exists(f, f.type == ''URLRewrite''))'\n                            - message: RequestHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                                <= 1\n                            - message: ResponseHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                                <= 1\n                            - message: RequestRedirect filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestRedirect').size()\n                                <= 1\n                            - message: URLRewrite filter cannot be repeated\n                              rule: self.filter(f, f.type == 'URLRewrite').size()\n                                <= 1\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: |-\n                        Filters define the filters that are applied to requests that match\n                        this rule.\n\n\n                        Wherever possible, implementations SHOULD implement filters in the order\n                        they are specified.\n\n\n                        Implementations MAY choose to implement this ordering strictly, rejecting\n                        any combination or order of filters that can not be supported. If implementations\n                        choose a strict interpretation of filter ordering, they MUST clearly document\n                        that behavior.\n\n\n                        To reject an invalid combination or order of filters, implementations SHOULD\n                        consider the Route Rules with this configuration invalid. If all Route Rules\n                        in a Route are invalid, the entire Route would be considered invalid. If only\n                        a portion of Route Rules are invalid, implementations MUST set the\n                        \"PartiallyInvalid\" condition for the Route.\n\n\n                        Conformance-levels at this level are defined based on the type of filter:\n\n\n                        - ALL core filters MUST be supported by all implementations.\n                        - Implementers are encouraged to support extended filters.\n                        - Implementation-specific custom filters have no API guarantees across\n                          implementations.\n\n\n                        Specifying the same filter multiple times is not supported unless explicitly\n                        indicated in the filter.\n\n\n                        All filters are expected to be compatible with each other except for the\n                        URLRewrite and RequestRedirect filters, which may not be combined. If an\n                        implementation can not support other combinations of filters, they must clearly\n                        document that limitation. In cases where incompatible or unsupported\n                        filters are specified and cause the `Accepted` condition to be set to status\n                        `False`, implementations may use the `IncompatibleFilters` reason to specify\n                        this configuration error.\n\n\n                        Support: Core\n                      items:\n                        description: |-\n                          HTTPRouteFilter defines processing steps that must be completed during the\n                          request or response lifecycle. HTTPRouteFilters are meant as an extension\n                          point to express processing that may be done in Gateway implementations. Some\n                          examples include request or response modification, implementing\n                          authentication strategies, rate-limiting, and traffic shaping. API\n                          guarantee/conformance is defined based on the type of the filter.\n                        properties:\n                          extensionRef:\n                            description: |-\n                              ExtensionRef is an optional, implementation-specific extension to the\n                              \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                              \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                              extended filters.\n\n\n                              This filter can be used multiple times within the same rule.\n\n\n                              Support: Implementation-specific\n                            properties:\n                              group:\n                                description: |-\n                                  Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                  When unspecified or empty string, core API group is inferred.\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                description: Kind is kind of the referent. For example\n                                  \"HTTPRoute\" or \"Service\".\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: Name is the name of the referent.\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                            required:\n                            - group\n                            - kind\n                            - name\n                            type: object\n                          requestHeaderModifier:\n                            description: |-\n                              RequestHeaderModifier defines a schema for a filter that modifies request\n                              headers.\n\n\n                              Support: Core\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestMirror:\n                            description: |-\n                              RequestMirror defines a schema for a filter that mirrors requests.\n                              Requests are sent to the specified destination, but responses from\n                              that destination are ignored.\n\n\n                              This filter can be used multiple times within the same rule. Note that\n                              not all implementations will be able to support mirroring to multiple\n                              backends.\n\n\n                              Support: Extended\n                            properties:\n                              backendRef:\n                                description: |-\n                                  BackendRef references a resource where mirrored requests are sent.\n\n\n                                  Mirrored requests must be sent only to a single destination endpoint\n                                  within this BackendRef, irrespective of how many endpoints are present\n                                  within this BackendRef.\n\n\n                                  If the referent cannot be found, this BackendRef is invalid and must be\n                                  dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                  condition on the Route status is set to `status: False` and not configure\n                                  this backend in the underlying implementation.\n\n\n                                  If there is a cross-namespace reference to an *existing* object\n                                  that is not allowed by a ReferenceGrant, the controller must ensure the\n                                  \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                  with the \"RefNotPermitted\" reason and not configure this backend in the\n                                  underlying implementation.\n\n\n                                  In either error case, the Message of the `ResolvedRefs` Condition\n                                  should be used to provide more detail about the problem.\n\n\n                                  Support: Extended for Kubernetes Service\n\n\n                                  Support: Implementation-specific for any other resource\n                                properties:\n                                  group:\n                                    default: \"\"\n                                    description: |-\n                                      Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                      When unspecified or empty string, core API group is inferred.\n                                    maxLength: 253\n                                    pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                    type: string\n                                  kind:\n                                    default: Service\n                                    description: |-\n                                      Kind is the Kubernetes resource kind of the referent. For example\n                                      \"Service\".\n\n\n                                      Defaults to \"Service\" when not specified.\n\n\n                                      ExternalName services can refer to CNAME DNS records that may live\n                                      outside of the cluster and as such are difficult to reason about in\n                                      terms of conformance. They also may not be safe to forward to (see\n                                      CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                      support ExternalName Services.\n\n\n                                      Support: Core (Services with a type other than ExternalName)\n\n\n                                      Support: Implementation-specific (Services with type ExternalName)\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                    type: string\n                                  name:\n                                    description: Name is the name of the referent.\n                                    maxLength: 253\n                                    minLength: 1\n                                    type: string\n                                  namespace:\n                                    description: |-\n                                      Namespace is the namespace of the backend. When unspecified, the local\n                                      namespace is inferred.\n\n\n                                      Note that when a namespace different than the local namespace is specified,\n                                      a ReferenceGrant object is required in the referent namespace to allow that\n                                      namespace's owner to accept the reference. See the ReferenceGrant\n                                      documentation for details.\n\n\n                                      Support: Core\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                    type: string\n                                  port:\n                                    description: |-\n                                      Port specifies the destination port number to use for this resource.\n                                      Port is required when the referent is a Kubernetes Service. In this\n                                      case, the port number is the service port number, not the target port.\n                                      For other resources, destination port might be derived from the referent\n                                      resource or this field.\n                                    format: int32\n                                    maximum: 65535\n                                    minimum: 1\n                                    type: integer\n                                required:\n                                - name\n                                type: object\n                                x-kubernetes-validations:\n                                - message: Must have port for Service reference\n                                  rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                                    ? has(self.port) : true'\n                            required:\n                            - backendRef\n                            type: object\n                          requestRedirect:\n                            description: |-\n                              RequestRedirect defines a schema for a filter that responds to the\n                              request with an HTTP redirection.\n\n\n                              Support: Core\n                            properties:\n                              hostname:\n                                description: |-\n                                  Hostname is the hostname to be used in the value of the `Location`\n                                  header in the response.\n                                  When empty, the hostname in the `Host` header of the request is used.\n\n\n                                  Support: Core\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: |-\n                                  Path defines parameters used to modify the path of the incoming request.\n                                  The modified path is then used to construct the `Location` header. When\n                                  empty, the request path is used as-is.\n\n\n                                  Support: Extended\n                                properties:\n                                  replaceFullPath:\n                                    description: |-\n                                      ReplaceFullPath specifies the value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: |-\n                                      ReplacePrefixMatch specifies the value with which to replace the prefix\n                                      match of a request during a rewrite or redirect. For example, a request\n                                      to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                      of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                      Note that this matches the behavior of the PathPrefix match type. This\n                                      matches full path elements. A path element refers to the list of labels\n                                      in the path split by the `/` separator. When specified, a trailing `/` is\n                                      ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                      match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                      ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                      Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                      the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                      Request Path | Prefix Match | Replace Prefix | Modified Path\n                                      -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                      /foo         | /foo         | /xyz           | /xyz\n                                      /foo/        | /foo         | /xyz           | /xyz/\n                                      /foo/bar     | /foo         | <empty string> | /bar\n                                      /foo/        | /foo         | <empty string> | /\n                                      /foo         | /foo         | <empty string> | /\n                                      /foo/        | /foo         | /              | /\n                                      /foo         | /foo         | /              | /\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: |-\n                                      Type defines the type of path modifier. Additional types may be\n                                      added in a future release of the API.\n\n\n                                      Note that values may be added to this enum, implementations\n                                      must ensure that unknown values will not cause a crash.\n\n\n                                      Unknown values here must result in the implementation setting the\n                                      Accepted Condition for the Route to `status: False`, with a\n                                      Reason of `UnsupportedValue`.\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                                x-kubernetes-validations:\n                                - message: replaceFullPath must be specified when\n                                    type is set to 'ReplaceFullPath'\n                                  rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath)\n                                    : true'\n                                - message: type must be 'ReplaceFullPath' when replaceFullPath\n                                    is set\n                                  rule: 'has(self.replaceFullPath) ? self.type ==\n                                    ''ReplaceFullPath'' : true'\n                                - message: replacePrefixMatch must be specified when\n                                    type is set to 'ReplacePrefixMatch'\n                                  rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch)\n                                    : true'\n                                - message: type must be 'ReplacePrefixMatch' when\n                                    replacePrefixMatch is set\n                                  rule: 'has(self.replacePrefixMatch) ? self.type\n                                    == ''ReplacePrefixMatch'' : true'\n                              port:\n                                description: |-\n                                  Port is the port to be used in the value of the `Location`\n                                  header in the response.\n\n\n                                  If no port is specified, the redirect port MUST be derived using the\n                                  following rules:\n\n\n                                  * If redirect scheme is not-empty, the redirect port MUST be the well-known\n                                    port associated with the redirect scheme. Specifically \"http\" to port 80\n                                    and \"https\" to port 443. If the redirect scheme does not have a\n                                    well-known port, the listener port of the Gateway SHOULD be used.\n                                  * If redirect scheme is empty, the redirect port MUST be the Gateway\n                                    Listener port.\n\n\n                                  Implementations SHOULD NOT add the port number in the 'Location'\n                                  header in the following cases:\n\n\n                                  * A Location header that will use HTTP (whether that is determined via\n                                    the Listener protocol or the Scheme field) _and_ use port 80.\n                                  * A Location header that will use HTTPS (whether that is determined via\n                                    the Listener protocol or the Scheme field) _and_ use port 443.\n\n\n                                  Support: Extended\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: |-\n                                  Scheme is the scheme to be used in the value of the `Location` header in\n                                  the response. When empty, the scheme of the request is used.\n\n\n                                  Scheme redirects can affect the port of the redirect, for more information,\n                                  refer to the documentation for the port field of this filter.\n\n\n                                  Note that values may be added to this enum, implementations\n                                  must ensure that unknown values will not cause a crash.\n\n\n                                  Unknown values here must result in the implementation setting the\n                                  Accepted Condition for the Route to `status: False`, with a\n                                  Reason of `UnsupportedValue`.\n\n\n                                  Support: Extended\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: |-\n                                  StatusCode is the HTTP status code to be used in response.\n\n\n                                  Note that values may be added to this enum, implementations\n                                  must ensure that unknown values will not cause a crash.\n\n\n                                  Unknown values here must result in the implementation setting the\n                                  Accepted Condition for the Route to `status: False`, with a\n                                  Reason of `UnsupportedValue`.\n\n\n                                  Support: Core\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          responseHeaderModifier:\n                            description: |-\n                              ResponseHeaderModifier defines a schema for a filter that modifies response\n                              headers.\n\n\n                              Support: Extended\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          type:\n                            description: |-\n                              Type identifies the type of filter to apply. As with other API fields,\n                              types are classified into three conformance levels:\n\n\n                              - Core: Filter types and their corresponding configuration defined by\n                                \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                implementations must support core filters.\n\n\n                              - Extended: Filter types and their corresponding configuration defined by\n                                \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                are encouraged to support extended filters.\n\n\n                              - Implementation-specific: Filters that are defined and supported by\n                                specific vendors.\n                                In the future, filters showing convergence in behavior across multiple\n                                implementations will be considered for inclusion in extended or core\n                                conformance levels. Filter-specific configuration for such filters\n                                is specified using the ExtensionRef field. `Type` should be set to\n                                \"ExtensionRef\" for custom filters.\n\n\n                              Implementers are encouraged to define custom implementation types to\n                              extend the core API with implementation-specific behavior.\n\n\n                              If a reference to a custom filter type cannot be resolved, the filter\n                              MUST NOT be skipped. Instead, requests that would have been processed by\n                              that filter MUST receive a HTTP error response.\n\n\n                              Note that values may be added to this enum, implementations\n                              must ensure that unknown values will not cause a crash.\n\n\n                              Unknown values here must result in the implementation setting the\n                              Accepted Condition for the Route to `status: False`, with a\n                              Reason of `UnsupportedValue`.\n                            enum:\n                            - RequestHeaderModifier\n                            - ResponseHeaderModifier\n                            - RequestMirror\n                            - RequestRedirect\n                            - URLRewrite\n                            - ExtensionRef\n                            type: string\n                          urlRewrite:\n                            description: |-\n                              URLRewrite defines a schema for a filter that modifies a request during forwarding.\n\n\n                              Support: Extended\n                            properties:\n                              hostname:\n                                description: |-\n                                  Hostname is the value to be used to replace the Host header value during\n                                  forwarding.\n\n\n                                  Support: Extended\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: |-\n                                  Path defines a path rewrite.\n\n\n                                  Support: Extended\n                                properties:\n                                  replaceFullPath:\n                                    description: |-\n                                      ReplaceFullPath specifies the value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: |-\n                                      ReplacePrefixMatch specifies the value with which to replace the prefix\n                                      match of a request during a rewrite or redirect. For example, a request\n                                      to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                      of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                      Note that this matches the behavior of the PathPrefix match type. This\n                                      matches full path elements. A path element refers to the list of labels\n                                      in the path split by the `/` separator. When specified, a trailing `/` is\n                                      ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                      match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                      ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                      Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                      the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                      Request Path | Prefix Match | Replace Prefix | Modified Path\n                                      -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                      /foo         | /foo         | /xyz           | /xyz\n                                      /foo/        | /foo         | /xyz           | /xyz/\n                                      /foo/bar     | /foo         | <empty string> | /bar\n                                      /foo/        | /foo         | <empty string> | /\n                                      /foo         | /foo         | <empty string> | /\n                                      /foo/        | /foo         | /              | /\n                                      /foo         | /foo         | /              | /\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: |-\n                                      Type defines the type of path modifier. Additional types may be\n                                      added in a future release of the API.\n\n\n                                      Note that values may be added to this enum, implementations\n                                      must ensure that unknown values will not cause a crash.\n\n\n                                      Unknown values here must result in the implementation setting the\n                                      Accepted Condition for the Route to `status: False`, with a\n                                      Reason of `UnsupportedValue`.\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                                x-kubernetes-validations:\n                                - message: replaceFullPath must be specified when\n                                    type is set to 'ReplaceFullPath'\n                                  rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath)\n                                    : true'\n                                - message: type must be 'ReplaceFullPath' when replaceFullPath\n                                    is set\n                                  rule: 'has(self.replaceFullPath) ? self.type ==\n                                    ''ReplaceFullPath'' : true'\n                                - message: replacePrefixMatch must be specified when\n                                    type is set to 'ReplacePrefixMatch'\n                                  rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch)\n                                    : true'\n                                - message: type must be 'ReplacePrefixMatch' when\n                                    replacePrefixMatch is set\n                                  rule: 'has(self.replacePrefixMatch) ? self.type\n                                    == ''ReplacePrefixMatch'' : true'\n                            type: object\n                        required:\n                        - type\n                        type: object\n                        x-kubernetes-validations:\n                        - message: filter.requestHeaderModifier must be nil if the\n                            filter.type is not RequestHeaderModifier\n                          rule: '!(has(self.requestHeaderModifier) && self.type !=\n                            ''RequestHeaderModifier'')'\n                        - message: filter.requestHeaderModifier must be specified\n                            for RequestHeaderModifier filter.type\n                          rule: '!(!has(self.requestHeaderModifier) && self.type ==\n                            ''RequestHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be nil if the\n                            filter.type is not ResponseHeaderModifier\n                          rule: '!(has(self.responseHeaderModifier) && self.type !=\n                            ''ResponseHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be specified\n                            for ResponseHeaderModifier filter.type\n                          rule: '!(!has(self.responseHeaderModifier) && self.type\n                            == ''ResponseHeaderModifier'')'\n                        - message: filter.requestMirror must be nil if the filter.type\n                            is not RequestMirror\n                          rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                        - message: filter.requestMirror must be specified for RequestMirror\n                            filter.type\n                          rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')'\n                        - message: filter.requestRedirect must be nil if the filter.type\n                            is not RequestRedirect\n                          rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')'\n                        - message: filter.requestRedirect must be specified for RequestRedirect\n                            filter.type\n                          rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')'\n                        - message: filter.urlRewrite must be nil if the filter.type\n                            is not URLRewrite\n                          rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')'\n                        - message: filter.urlRewrite must be specified for URLRewrite\n                            filter.type\n                          rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')'\n                        - message: filter.extensionRef must be nil if the filter.type\n                            is not ExtensionRef\n                          rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                        - message: filter.extensionRef must be specified for ExtensionRef\n                            filter.type\n                          rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                      maxItems: 16\n                      type: array\n                      x-kubernetes-validations:\n                      - message: May specify either httpRouteFilterRequestRedirect\n                          or httpRouteFilterRequestRewrite, but not both\n                        rule: '!(self.exists(f, f.type == ''RequestRedirect'') &&\n                          self.exists(f, f.type == ''URLRewrite''))'\n                      - message: RequestHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                          <= 1\n                      - message: ResponseHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                          <= 1\n                      - message: RequestRedirect filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestRedirect').size() <=\n                          1\n                      - message: URLRewrite filter cannot be repeated\n                        rule: self.filter(f, f.type == 'URLRewrite').size() <= 1\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: |-\n                        Matches define conditions used for matching the rule against incoming\n                        HTTP requests. Each match is independent, i.e. this rule will be matched\n                        if **any** one of the matches is satisfied.\n\n\n                        For example, take the following matches configuration:\n\n\n                        ```\n                        matches:\n                        - path:\n                            value: \"/foo\"\n                          headers:\n                          - name: \"version\"\n                            value: \"v2\"\n                        - path:\n                            value: \"/v2/foo\"\n                        ```\n\n\n                        For a request to match against this rule, a request must satisfy\n                        EITHER of the two conditions:\n\n\n                        - path prefixed with `/foo` AND contains the header `version: v2`\n                        - path prefix of `/v2/foo`\n\n\n                        See the documentation for HTTPRouteMatch on how to specify multiple\n                        match conditions that should be ANDed together.\n\n\n                        If no matches are specified, the default is a prefix\n                        path match on \"/\", which has the effect of matching every\n                        HTTP request.\n\n\n                        Proxy or Load Balancer routing configuration generated from HTTPRoutes\n                        MUST prioritize matches based on the following criteria, continuing on\n                        ties. Across all rules specified on applicable Routes, precedence must be\n                        given to the match having:\n\n\n                        * \"Exact\" path match.\n                        * \"Prefix\" path match with largest number of characters.\n                        * Method match.\n                        * Largest number of header matches.\n                        * Largest number of query param matches.\n\n\n                        Note: The precedence of RegularExpression path matches are implementation-specific.\n\n\n                        If ties still exist across multiple Routes, matching precedence MUST be\n                        determined in order of the following criteria, continuing on ties:\n\n\n                        * The oldest Route based on creation timestamp.\n                        * The Route appearing first in alphabetical order by\n                          \"{namespace}/{name}\".\n\n\n                        If ties still exist within an HTTPRoute, matching precedence MUST be granted\n                        to the FIRST matching rule (in list order) with a match meeting the above\n                        criteria.\n\n\n                        When no rules matching a request have been successfully attached to the\n                        parent a request is coming from, a HTTP 404 status code MUST be returned.\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given\\naction. Multiple match types\n                          are ANDed together, i.e. the match will\\nevaluate to true\n                          only if all conditions are satisfied.\\n\\n\\nFor example,\n                          the match below will match a HTTP request only if its path\\nstarts\n                          with `/foo` AND it contains the `version: v1` header:\\n\\n\\n```\\nmatch:\\n\\n\\n\\tpath:\\n\\t\n                          \\ value: \\\"/foo\\\"\\n\\theaders:\\n\\t- name: \\\"version\\\"\\n\\t\n                          \\ value \\\"v1\\\"\\n\\n\\n```\"\n                        properties:\n                          headers:\n                            description: |-\n                              Headers specifies HTTP request header matchers. Multiple match values are\n                              ANDed together, meaning, a request must match all the specified headers\n                              to select the route.\n                            items:\n                              description: |-\n                                HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request\n                                headers.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                    case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                    If multiple entries specify equivalent header names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be ignored. Due to the\n                                    case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                    equivalent.\n\n\n                                    When a header is repeated in an HTTP request, it is\n                                    implementation-specific behavior as to how this is represented.\n                                    Generally, proxies should follow the guidance from the RFC:\n                                    https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding\n                                    processing a repeated header, with special handling for \"Set-Cookie\".\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: |-\n                                    Type specifies how to match against the value of the header.\n\n\n                                    Support: Core (Exact)\n\n\n                                    Support: Implementation-specific (RegularExpression)\n\n\n                                    Since RegularExpression HeaderMatchType has implementation-specific\n                                    conformance, implementations can support POSIX, PCRE or any other dialects\n                                    of regular expressions. Please read the implementation's documentation to\n                                    determine the supported dialect.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: |-\n                              Method specifies HTTP method matcher.\n                              When specified, this route will be matched only if the request has the\n                              specified method.\n\n\n                              Support: Extended\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: |-\n                              Path specifies a HTTP request path matcher. If this field is not\n                              specified, a default prefix match on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: |-\n                                  Type specifies how to match against the path Value.\n\n\n                                  Support: Core (Exact, PathPrefix)\n\n\n                                  Support: Implementation-specific (RegularExpression)\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                            x-kubernetes-validations:\n                            - message: value must be an absolute path and start with\n                                '/' when type one of ['Exact', 'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'')\n                                : true'\n                            - message: must not contain '//' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'')\n                                : true'\n                            - message: must not contain '/./' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'')\n                                : true'\n                            - message: must not contain '/../' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'')\n                                : true'\n                            - message: must not contain '%2f' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'')\n                                : true'\n                            - message: must not contain '%2F' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'')\n                                : true'\n                            - message: must not contain '#' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'')\n                                : true'\n                            - message: must not end with '/..' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'')\n                                : true'\n                            - message: must not end with '/.' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'')\n                                : true'\n                            - message: type must be one of ['Exact', 'PathPrefix',\n                                'RegularExpression']\n                              rule: self.type in ['Exact','PathPrefix'] || self.type\n                                == 'RegularExpression'\n                            - message: must only contain valid characters (matching\n                                ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$)\n                                for types ['Exact', 'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r\"\"\"^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$\"\"\")\n                                : true'\n                          queryParams:\n                            description: |-\n                              QueryParams specifies HTTP query parameter matchers. Multiple match\n                              values are ANDed together, meaning, a request must match all the\n                              specified query parameters to select the route.\n\n\n                              Support: Extended\n                            items:\n                              description: |-\n                                HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP\n                                query parameters.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the HTTP query param to be matched. This must be an\n                                    exact string match. (See\n                                    https://tools.ietf.org/html/rfc7230#section-2.7.3).\n\n\n                                    If multiple entries specify equivalent query param names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent query param name MUST be ignored.\n\n\n                                    If a query param is repeated in an HTTP request, the behavior is\n                                    purposely left undefined, since different data planes have different\n                                    capabilities. However, it is *recommended* that implementations should\n                                    match against the first value of the param if the data plane supports it,\n                                    as this behavior is expected in other load balancing contexts outside of\n                                    the Gateway API.\n\n\n                                    Users SHOULD NOT route traffic based on repeated query params to guard\n                                    themselves against potential differences in the implementations.\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: |-\n                                    Type specifies how to match against the value of the query parameter.\n\n\n                                    Support: Extended (Exact)\n\n\n                                    Support: Implementation-specific (RegularExpression)\n\n\n                                    Since RegularExpression QueryParamMatchType has Implementation-specific\n                                    conformance, implementations can support POSIX, PCRE or any other\n                                    dialects of regular expressions. Please read the implementation's\n                                    documentation to determine the supported dialect.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                    sessionPersistence:\n                      description: |+\n                        SessionPersistence defines and configures session persistence\n                        for the route rule.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        absoluteTimeout:\n                          description: |-\n                            AbsoluteTimeout defines the absolute timeout of the persistent\n                            session. Once the AbsoluteTimeout duration has elapsed, the\n                            session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        cookieConfig:\n                          description: |-\n                            CookieConfig provides configuration settings that are specific\n                            to cookie-based session persistence.\n\n\n                            Support: Core\n                          properties:\n                            lifetimeType:\n                              default: Session\n                              description: |-\n                                LifetimeType specifies whether the cookie has a permanent or\n                                session-based lifetime. A permanent cookie persists until its\n                                specified expiry time, defined by the Expires or Max-Age cookie\n                                attributes, while a session cookie is deleted when the current\n                                session ends.\n\n\n                                When set to \"Permanent\", AbsoluteTimeout indicates the\n                                cookie's lifetime via the Expires or Max-Age cookie attributes\n                                and is required.\n\n\n                                When set to \"Session\", AbsoluteTimeout indicates the\n                                absolute lifetime of the cookie tracked by the gateway and\n                                is optional.\n\n\n                                Support: Core for \"Session\" type\n\n\n                                Support: Extended for \"Permanent\" type\n                              enum:\n                              - Permanent\n                              - Session\n                              type: string\n                          type: object\n                        idleTimeout:\n                          description: |-\n                            IdleTimeout defines the idle timeout of the persistent session.\n                            Once the session has been idle for more than the specified\n                            IdleTimeout duration, the session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        sessionName:\n                          description: |-\n                            SessionName defines the name of the persistent session token\n                            which may be reflected in the cookie or the header. Users\n                            should avoid reusing session names to prevent unintended\n                            consequences, such as rejection or unpredictable behavior.\n\n\n                            Support: Implementation-specific\n                          maxLength: 128\n                          type: string\n                        type:\n                          default: Cookie\n                          description: |-\n                            Type defines the type of session persistence such as through\n                            the use a header or cookie. Defaults to cookie based session\n                            persistence.\n\n\n                            Support: Core for \"Cookie\" type\n\n\n                            Support: Extended for \"Header\" type\n                          enum:\n                          - Cookie\n                          - Header\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: AbsoluteTimeout must be specified when cookie lifetimeType\n                          is Permanent\n                        rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType)\n                          || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)'\n                    timeouts:\n                      description: |+\n                        Timeouts defines the timeouts that can be configured for an HTTP request.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        backendRequest:\n                          description: |-\n                            BackendRequest specifies a timeout for an individual request from the gateway\n                            to a backend. This covers the time from when the request first starts being\n                            sent from the gateway to when the full response has been received from the backend.\n\n\n                            Setting a timeout to the zero duration (e.g. \"0s\") SHOULD disable the timeout\n                            completely. Implementations that cannot completely disable the timeout MUST\n                            instead interpret the zero duration as the longest possible value to which\n                            the timeout can be set.\n\n\n                            An entire client HTTP transaction with a gateway, covered by the Request timeout,\n                            may result in more than one call from the gateway to the destination backend,\n                            for example, if automatic retries are supported.\n\n\n                            Because the Request timeout encompasses the BackendRequest timeout, the value of\n                            BackendRequest must be <= the value of Request timeout.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        request:\n                          description: |-\n                            Request specifies the maximum duration for a gateway to respond to an HTTP request.\n                            If the gateway has not been able to respond before this deadline is met, the gateway\n                            MUST return a timeout error.\n\n\n                            For example, setting the `rules.timeouts.request` field to the value `10s` in an\n                            `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds\n                            to complete.\n\n\n                            Setting a timeout to the zero duration (e.g. \"0s\") SHOULD disable the timeout\n                            completely. Implementations that cannot completely disable the timeout MUST\n                            instead interpret the zero duration as the longest possible value to which\n                            the timeout can be set.\n\n\n                            This timeout is intended to cover as close to the whole request-response transaction\n                            as possible although an implementation MAY choose to start the timeout after the entire\n                            request stream has been received instead of immediately after the transaction is\n                            initiated by the client.\n\n\n                            When this field is unspecified, request timeout behavior is implementation-specific.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: backendRequest timeout cannot be longer than request\n                          timeout\n                        rule: '!(has(self.request) && has(self.backendRequest) &&\n                          duration(self.request) != duration(''0s'') && duration(self.backendRequest)\n                          > duration(self.request))'\n                  type: object\n                  x-kubernetes-validations:\n                  - message: RequestRedirect filter must not be used together with\n                      backendRefs\n                    rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ?\n                      (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))):\n                      true'\n                  - message: When using RequestRedirect filter with path.replacePrefixMatch,\n                      exactly one PathPrefix match must be specified\n                    rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect)\n                      && has(f.requestRedirect.path) && f.requestRedirect.path.type\n                      == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch)))\n                      ? ((size(self.matches) != 1 || !has(self.matches[0].path) ||\n                      self.matches[0].path.type != ''PathPrefix'') ? false : true)\n                      : true'\n                  - message: When using URLRewrite filter with path.replacePrefixMatch,\n                      exactly one PathPrefix match must be specified\n                    rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite)\n                      && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch''\n                      && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches)\n                      != 1 || !has(self.matches[0].path) || self.matches[0].path.type\n                      != ''PathPrefix'') ? false : true) : true'\n                  - message: Within backendRefs, when using RequestRedirect filter\n                      with path.replacePrefixMatch, exactly one PathPrefix match must\n                      be specified\n                    rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b,\n                      (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect)\n                      && has(f.requestRedirect.path) && f.requestRedirect.path.type\n                      == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch)))\n                      )) ? ((size(self.matches) != 1 || !has(self.matches[0].path)\n                      || self.matches[0].path.type != ''PathPrefix'') ? false : true)\n                      : true'\n                  - message: Within backendRefs, When using URLRewrite filter with\n                      path.replacePrefixMatch, exactly one PathPrefix match must be\n                      specified\n                    rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b,\n                      (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite)\n                      && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch''\n                      && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches)\n                      != 1 || !has(self.matches[0].path) || self.matches[0].path.type\n                      != ''PathPrefix'') ? false : true) : true'\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: |-\n          HTTPRoute provides a way to route HTTP requests. This includes the capability\n          to match requests by hostname, path, header, or query param. Filters can be\n          used to specify additional processing steps. Backends specify where matching\n          requests should be routed.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: |-\n                  Hostnames defines a set of hostnames that should match against the HTTP Host\n                  header to select a HTTPRoute used to process the request. Implementations\n                  MUST ignore any port value specified in the HTTP Host header while\n                  performing a match and (absent of any applicable header modification\n                  configuration) MUST forward this header unmodified to the backend.\n\n\n                  Valid values for Hostnames are determined by RFC 1123 definition of a\n                  hostname with 2 notable exceptions:\n\n\n                  1. IPs are not allowed.\n                  2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                     label must appear by itself as the first label.\n\n\n                  If a hostname is specified by both the Listener and HTTPRoute, there\n                  must be at least one intersecting hostname for the HTTPRoute to be\n                  attached to the Listener. For example:\n\n\n                  * A Listener with `test.example.com` as the hostname matches HTTPRoutes\n                    that have either not specified any hostnames, or have specified at\n                    least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                    that have either not specified any hostnames or have specified at least\n                    one hostname that matches the Listener hostname. For example,\n                    `*.example.com`, `test.example.com`, and `foo.test.example.com` would\n                    all match. On the other hand, `example.com` and `test.example.net` would\n                    not match.\n\n\n                  Hostnames that are prefixed with a wildcard label (`*.`) are interpreted\n                  as a suffix match. That means that a match for `*.example.com` would match\n                  both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\n\n                  If both the Listener and HTTPRoute have specified hostnames, any\n                  HTTPRoute hostnames that do not match the Listener hostname MUST be\n                  ignored. For example, if a Listener specified `*.example.com`, and the\n                  HTTPRoute specified `test.example.com` and `test.example.net`,\n                  `test.example.net` must not be considered for a match.\n\n\n                  If both the Listener and HTTPRoute have specified hostnames, and none\n                  match with the criteria above, then the HTTPRoute is not accepted. The\n                  implementation must raise an 'Accepted' Condition with a status of\n                  `False` in the corresponding RouteParentStatus.\n\n\n                  In the event that multiple HTTPRoutes specify intersecting hostnames (e.g.\n                  overlapping wildcard matching and exact matching hostnames), precedence must\n                  be given to rules from the HTTPRoute with the largest number of:\n\n\n                  * Characters in a matching non-wildcard hostname.\n                  * Characters in a matching hostname.\n\n\n                  If ties exist across multiple Routes, the matching precedence rules for\n                  HTTPRouteMatches takes over.\n\n\n                  Support: Core\n                items:\n                  description: |-\n                    Hostname is the fully qualified domain name of a network host. This matches\n                    the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                     1. IPs are not allowed.\n                     2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                        label must appear by itself as the first label.\n\n\n                    Hostname can be \"precise\" which is a domain name without the terminating\n                    dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a\n                    domain name prefixed with a single wildcard label (e.g. `*.example.com`).\n\n\n                    Note that as per RFC1035 and RFC1123, a *label* must consist of lower case\n                    alphanumeric characters or '-', and must start and end with an alphanumeric\n                    character. No other punctuation is allowed.\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: |-\n                    HTTPRouteRule defines semantics for matching an HTTP request based on\n                    conditions (matches), processing it (filters), and forwarding the request to\n                    an API object (backendRefs).\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent.\n\n\n                        Failure behavior here depends on how many BackendRefs are specified and\n                        how many are invalid.\n\n\n                        If *all* entries in BackendRefs are invalid, and there are also no filters\n                        specified in this route rule, *all* traffic which matches this rule MUST\n                        receive a 500 status code.\n\n\n                        See the HTTPBackendRef definition for the rules about what makes a single\n                        HTTPBackendRef invalid.\n\n\n                        When a HTTPBackendRef is invalid, 500 status codes MUST be returned for\n                        requests that would have otherwise been routed to an invalid backend. If\n                        multiple backends are specified, and some are invalid, the proportion of\n                        requests that would otherwise have been routed to an invalid backend\n                        MUST receive a 500 status code.\n\n\n                        For example, if two backends are specified with equal weights, and one is\n                        invalid, 50 percent of traffic must receive a 500. Implementations may\n                        choose how that 50 percent is determined.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Extended for Kubernetes ServiceImport\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Core\n                      items:\n                        description: |-\n                          HTTPBackendRef defines how a HTTPRoute forwards a HTTP request.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n                        properties:\n                          filters:\n                            description: |-\n                              Filters defined at this level should be executed if and only if the\n                              request is being forwarded to the backend defined here.\n\n\n                              Support: Implementation-specific (For broader support of filters, use the\n                              Filters field in HTTPRouteRule.)\n                            items:\n                              description: |-\n                                HTTPRouteFilter defines processing steps that must be completed during the\n                                request or response lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway implementations. Some\n                                examples include request or response modification, implementing\n                                authentication strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type of the filter.\n                              properties:\n                                extensionRef:\n                                  description: |-\n                                    ExtensionRef is an optional, implementation-specific extension to the\n                                    \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                                    \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                                    extended filters.\n\n\n                                    This filter can be used multiple times within the same rule.\n\n\n                                    Support: Implementation-specific\n                                  properties:\n                                    group:\n                                      description: |-\n                                        Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                        When unspecified or empty string, core API group is inferred.\n                                      maxLength: 253\n                                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    kind:\n                                      description: Kind is kind of the referent. For\n                                        example \"HTTPRoute\" or \"Service\".\n                                      maxLength: 63\n                                      minLength: 1\n                                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                      type: string\n                                    name:\n                                      description: Name is the name of the referent.\n                                      maxLength: 253\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - group\n                                  - kind\n                                  - name\n                                  type: object\n                                requestHeaderModifier:\n                                  description: |-\n                                    RequestHeaderModifier defines a schema for a filter that modifies request\n                                    headers.\n\n\n                                    Support: Core\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestMirror:\n                                  description: |-\n                                    RequestMirror defines a schema for a filter that mirrors requests.\n                                    Requests are sent to the specified destination, but responses from\n                                    that destination are ignored.\n\n\n                                    This filter can be used multiple times within the same rule. Note that\n                                    not all implementations will be able to support mirroring to multiple\n                                    backends.\n\n\n                                    Support: Extended\n                                  properties:\n                                    backendRef:\n                                      description: |-\n                                        BackendRef references a resource where mirrored requests are sent.\n\n\n                                        Mirrored requests must be sent only to a single destination endpoint\n                                        within this BackendRef, irrespective of how many endpoints are present\n                                        within this BackendRef.\n\n\n                                        If the referent cannot be found, this BackendRef is invalid and must be\n                                        dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                        condition on the Route status is set to `status: False` and not configure\n                                        this backend in the underlying implementation.\n\n\n                                        If there is a cross-namespace reference to an *existing* object\n                                        that is not allowed by a ReferenceGrant, the controller must ensure the\n                                        \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                        with the \"RefNotPermitted\" reason and not configure this backend in the\n                                        underlying implementation.\n\n\n                                        In either error case, the Message of the `ResolvedRefs` Condition\n                                        should be used to provide more detail about the problem.\n\n\n                                        Support: Extended for Kubernetes Service\n\n\n                                        Support: Implementation-specific for any other resource\n                                      properties:\n                                        group:\n                                          default: \"\"\n                                          description: |-\n                                            Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                            When unspecified or empty string, core API group is inferred.\n                                          maxLength: 253\n                                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                          type: string\n                                        kind:\n                                          default: Service\n                                          description: |-\n                                            Kind is the Kubernetes resource kind of the referent. For example\n                                            \"Service\".\n\n\n                                            Defaults to \"Service\" when not specified.\n\n\n                                            ExternalName services can refer to CNAME DNS records that may live\n                                            outside of the cluster and as such are difficult to reason about in\n                                            terms of conformance. They also may not be safe to forward to (see\n                                            CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                            support ExternalName Services.\n\n\n                                            Support: Core (Services with a type other than ExternalName)\n\n\n                                            Support: Implementation-specific (Services with type ExternalName)\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                          type: string\n                                        name:\n                                          description: Name is the name of the referent.\n                                          maxLength: 253\n                                          minLength: 1\n                                          type: string\n                                        namespace:\n                                          description: |-\n                                            Namespace is the namespace of the backend. When unspecified, the local\n                                            namespace is inferred.\n\n\n                                            Note that when a namespace different than the local namespace is specified,\n                                            a ReferenceGrant object is required in the referent namespace to allow that\n                                            namespace's owner to accept the reference. See the ReferenceGrant\n                                            documentation for details.\n\n\n                                            Support: Core\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                          type: string\n                                        port:\n                                          description: |-\n                                            Port specifies the destination port number to use for this resource.\n                                            Port is required when the referent is a Kubernetes Service. In this\n                                            case, the port number is the service port number, not the target port.\n                                            For other resources, destination port might be derived from the referent\n                                            resource or this field.\n                                          format: int32\n                                          maximum: 65535\n                                          minimum: 1\n                                          type: integer\n                                      required:\n                                      - name\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: Must have port for Service reference\n                                        rule: '(size(self.group) == 0 && self.kind\n                                          == ''Service'') ? has(self.port) : true'\n                                  required:\n                                  - backendRef\n                                  type: object\n                                requestRedirect:\n                                  description: |-\n                                    RequestRedirect defines a schema for a filter that responds to the\n                                    request with an HTTP redirection.\n\n\n                                    Support: Core\n                                  properties:\n                                    hostname:\n                                      description: |-\n                                        Hostname is the hostname to be used in the value of the `Location`\n                                        header in the response.\n                                        When empty, the hostname in the `Host` header of the request is used.\n\n\n                                        Support: Core\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: |-\n                                        Path defines parameters used to modify the path of the incoming request.\n                                        The modified path is then used to construct the `Location` header. When\n                                        empty, the request path is used as-is.\n\n\n                                        Support: Extended\n                                      properties:\n                                        replaceFullPath:\n                                          description: |-\n                                            ReplaceFullPath specifies the value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: |-\n                                            ReplacePrefixMatch specifies the value with which to replace the prefix\n                                            match of a request during a rewrite or redirect. For example, a request\n                                            to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                            of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                            Note that this matches the behavior of the PathPrefix match type. This\n                                            matches full path elements. A path element refers to the list of labels\n                                            in the path split by the `/` separator. When specified, a trailing `/` is\n                                            ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                            match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                            ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                            Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                            the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                            Request Path | Prefix Match | Replace Prefix | Modified Path\n                                            -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                            /foo         | /foo         | /xyz           | /xyz\n                                            /foo/        | /foo         | /xyz           | /xyz/\n                                            /foo/bar     | /foo         | <empty string> | /bar\n                                            /foo/        | /foo         | <empty string> | /\n                                            /foo         | /foo         | <empty string> | /\n                                            /foo/        | /foo         | /              | /\n                                            /foo         | /foo         | /              | /\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: |-\n                                            Type defines the type of path modifier. Additional types may be\n                                            added in a future release of the API.\n\n\n                                            Note that values may be added to this enum, implementations\n                                            must ensure that unknown values will not cause a crash.\n\n\n                                            Unknown values here must result in the implementation setting the\n                                            Accepted Condition for the Route to `status: False`, with a\n                                            Reason of `UnsupportedValue`.\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: replaceFullPath must be specified\n                                          when type is set to 'ReplaceFullPath'\n                                        rule: 'self.type == ''ReplaceFullPath'' ?\n                                          has(self.replaceFullPath) : true'\n                                      - message: type must be 'ReplaceFullPath' when\n                                          replaceFullPath is set\n                                        rule: 'has(self.replaceFullPath) ? self.type\n                                          == ''ReplaceFullPath'' : true'\n                                      - message: replacePrefixMatch must be specified\n                                          when type is set to 'ReplacePrefixMatch'\n                                        rule: 'self.type == ''ReplacePrefixMatch''\n                                          ? has(self.replacePrefixMatch) : true'\n                                      - message: type must be 'ReplacePrefixMatch'\n                                          when replacePrefixMatch is set\n                                        rule: 'has(self.replacePrefixMatch) ? self.type\n                                          == ''ReplacePrefixMatch'' : true'\n                                    port:\n                                      description: |-\n                                        Port is the port to be used in the value of the `Location`\n                                        header in the response.\n\n\n                                        If no port is specified, the redirect port MUST be derived using the\n                                        following rules:\n\n\n                                        * If redirect scheme is not-empty, the redirect port MUST be the well-known\n                                          port associated with the redirect scheme. Specifically \"http\" to port 80\n                                          and \"https\" to port 443. If the redirect scheme does not have a\n                                          well-known port, the listener port of the Gateway SHOULD be used.\n                                        * If redirect scheme is empty, the redirect port MUST be the Gateway\n                                          Listener port.\n\n\n                                        Implementations SHOULD NOT add the port number in the 'Location'\n                                        header in the following cases:\n\n\n                                        * A Location header that will use HTTP (whether that is determined via\n                                          the Listener protocol or the Scheme field) _and_ use port 80.\n                                        * A Location header that will use HTTPS (whether that is determined via\n                                          the Listener protocol or the Scheme field) _and_ use port 443.\n\n\n                                        Support: Extended\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: |-\n                                        Scheme is the scheme to be used in the value of the `Location` header in\n                                        the response. When empty, the scheme of the request is used.\n\n\n                                        Scheme redirects can affect the port of the redirect, for more information,\n                                        refer to the documentation for the port field of this filter.\n\n\n                                        Note that values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause a crash.\n\n\n                                        Unknown values here must result in the implementation setting the\n                                        Accepted Condition for the Route to `status: False`, with a\n                                        Reason of `UnsupportedValue`.\n\n\n                                        Support: Extended\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: |-\n                                        StatusCode is the HTTP status code to be used in response.\n\n\n                                        Note that values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause a crash.\n\n\n                                        Unknown values here must result in the implementation setting the\n                                        Accepted Condition for the Route to `status: False`, with a\n                                        Reason of `UnsupportedValue`.\n\n\n                                        Support: Core\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: |-\n                                    ResponseHeaderModifier defines a schema for a filter that modifies response\n                                    headers.\n\n\n                                    Support: Extended\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: |-\n                                    Type identifies the type of filter to apply. As with other API fields,\n                                    types are classified into three conformance levels:\n\n\n                                    - Core: Filter types and their corresponding configuration defined by\n                                      \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                      implementations must support core filters.\n\n\n                                    - Extended: Filter types and their corresponding configuration defined by\n                                      \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                      are encouraged to support extended filters.\n\n\n                                    - Implementation-specific: Filters that are defined and supported by\n                                      specific vendors.\n                                      In the future, filters showing convergence in behavior across multiple\n                                      implementations will be considered for inclusion in extended or core\n                                      conformance levels. Filter-specific configuration for such filters\n                                      is specified using the ExtensionRef field. `Type` should be set to\n                                      \"ExtensionRef\" for custom filters.\n\n\n                                    Implementers are encouraged to define custom implementation types to\n                                    extend the core API with implementation-specific behavior.\n\n\n                                    If a reference to a custom filter type cannot be resolved, the filter\n                                    MUST NOT be skipped. Instead, requests that would have been processed by\n                                    that filter MUST receive a HTTP error response.\n\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause a crash.\n\n\n                                    Unknown values here must result in the implementation setting the\n                                    Accepted Condition for the Route to `status: False`, with a\n                                    Reason of `UnsupportedValue`.\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestMirror\n                                  - RequestRedirect\n                                  - URLRewrite\n                                  - ExtensionRef\n                                  type: string\n                                urlRewrite:\n                                  description: |-\n                                    URLRewrite defines a schema for a filter that modifies a request during forwarding.\n\n\n                                    Support: Extended\n                                  properties:\n                                    hostname:\n                                      description: |-\n                                        Hostname is the value to be used to replace the Host header value during\n                                        forwarding.\n\n\n                                        Support: Extended\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: |-\n                                        Path defines a path rewrite.\n\n\n                                        Support: Extended\n                                      properties:\n                                        replaceFullPath:\n                                          description: |-\n                                            ReplaceFullPath specifies the value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: |-\n                                            ReplacePrefixMatch specifies the value with which to replace the prefix\n                                            match of a request during a rewrite or redirect. For example, a request\n                                            to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                            of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                            Note that this matches the behavior of the PathPrefix match type. This\n                                            matches full path elements. A path element refers to the list of labels\n                                            in the path split by the `/` separator. When specified, a trailing `/` is\n                                            ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                            match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                            ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                            Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                            the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                            Request Path | Prefix Match | Replace Prefix | Modified Path\n                                            -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                            /foo         | /foo         | /xyz           | /xyz\n                                            /foo/        | /foo         | /xyz           | /xyz/\n                                            /foo/bar     | /foo         | <empty string> | /bar\n                                            /foo/        | /foo         | <empty string> | /\n                                            /foo         | /foo         | <empty string> | /\n                                            /foo/        | /foo         | /              | /\n                                            /foo         | /foo         | /              | /\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: |-\n                                            Type defines the type of path modifier. Additional types may be\n                                            added in a future release of the API.\n\n\n                                            Note that values may be added to this enum, implementations\n                                            must ensure that unknown values will not cause a crash.\n\n\n                                            Unknown values here must result in the implementation setting the\n                                            Accepted Condition for the Route to `status: False`, with a\n                                            Reason of `UnsupportedValue`.\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: replaceFullPath must be specified\n                                          when type is set to 'ReplaceFullPath'\n                                        rule: 'self.type == ''ReplaceFullPath'' ?\n                                          has(self.replaceFullPath) : true'\n                                      - message: type must be 'ReplaceFullPath' when\n                                          replaceFullPath is set\n                                        rule: 'has(self.replaceFullPath) ? self.type\n                                          == ''ReplaceFullPath'' : true'\n                                      - message: replacePrefixMatch must be specified\n                                          when type is set to 'ReplacePrefixMatch'\n                                        rule: 'self.type == ''ReplacePrefixMatch''\n                                          ? has(self.replacePrefixMatch) : true'\n                                      - message: type must be 'ReplacePrefixMatch'\n                                          when replacePrefixMatch is set\n                                        rule: 'has(self.replacePrefixMatch) ? self.type\n                                          == ''ReplacePrefixMatch'' : true'\n                                  type: object\n                              required:\n                              - type\n                              type: object\n                              x-kubernetes-validations:\n                              - message: filter.requestHeaderModifier must be nil\n                                  if the filter.type is not RequestHeaderModifier\n                                rule: '!(has(self.requestHeaderModifier) && self.type\n                                  != ''RequestHeaderModifier'')'\n                              - message: filter.requestHeaderModifier must be specified\n                                  for RequestHeaderModifier filter.type\n                                rule: '!(!has(self.requestHeaderModifier) && self.type\n                                  == ''RequestHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be nil\n                                  if the filter.type is not ResponseHeaderModifier\n                                rule: '!(has(self.responseHeaderModifier) && self.type\n                                  != ''ResponseHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be specified\n                                  for ResponseHeaderModifier filter.type\n                                rule: '!(!has(self.responseHeaderModifier) && self.type\n                                  == ''ResponseHeaderModifier'')'\n                              - message: filter.requestMirror must be nil if the filter.type\n                                  is not RequestMirror\n                                rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                              - message: filter.requestMirror must be specified for\n                                  RequestMirror filter.type\n                                rule: '!(!has(self.requestMirror) && self.type ==\n                                  ''RequestMirror'')'\n                              - message: filter.requestRedirect must be nil if the\n                                  filter.type is not RequestRedirect\n                                rule: '!(has(self.requestRedirect) && self.type !=\n                                  ''RequestRedirect'')'\n                              - message: filter.requestRedirect must be specified\n                                  for RequestRedirect filter.type\n                                rule: '!(!has(self.requestRedirect) && self.type ==\n                                  ''RequestRedirect'')'\n                              - message: filter.urlRewrite must be nil if the filter.type\n                                  is not URLRewrite\n                                rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')'\n                              - message: filter.urlRewrite must be specified for URLRewrite\n                                  filter.type\n                                rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')'\n                              - message: filter.extensionRef must be nil if the filter.type\n                                  is not ExtensionRef\n                                rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                              - message: filter.extensionRef must be specified for\n                                  ExtensionRef filter.type\n                                rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-validations:\n                            - message: May specify either httpRouteFilterRequestRedirect\n                                or httpRouteFilterRequestRewrite, but not both\n                              rule: '!(self.exists(f, f.type == ''RequestRedirect'')\n                                && self.exists(f, f.type == ''URLRewrite''))'\n                            - message: May specify either httpRouteFilterRequestRedirect\n                                or httpRouteFilterRequestRewrite, but not both\n                              rule: '!(self.exists(f, f.type == ''RequestRedirect'')\n                                && self.exists(f, f.type == ''URLRewrite''))'\n                            - message: RequestHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                                <= 1\n                            - message: ResponseHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                                <= 1\n                            - message: RequestRedirect filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestRedirect').size()\n                                <= 1\n                            - message: URLRewrite filter cannot be repeated\n                              rule: self.filter(f, f.type == 'URLRewrite').size()\n                                <= 1\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: |-\n                        Filters define the filters that are applied to requests that match\n                        this rule.\n\n\n                        Wherever possible, implementations SHOULD implement filters in the order\n                        they are specified.\n\n\n                        Implementations MAY choose to implement this ordering strictly, rejecting\n                        any combination or order of filters that can not be supported. If implementations\n                        choose a strict interpretation of filter ordering, they MUST clearly document\n                        that behavior.\n\n\n                        To reject an invalid combination or order of filters, implementations SHOULD\n                        consider the Route Rules with this configuration invalid. If all Route Rules\n                        in a Route are invalid, the entire Route would be considered invalid. If only\n                        a portion of Route Rules are invalid, implementations MUST set the\n                        \"PartiallyInvalid\" condition for the Route.\n\n\n                        Conformance-levels at this level are defined based on the type of filter:\n\n\n                        - ALL core filters MUST be supported by all implementations.\n                        - Implementers are encouraged to support extended filters.\n                        - Implementation-specific custom filters have no API guarantees across\n                          implementations.\n\n\n                        Specifying the same filter multiple times is not supported unless explicitly\n                        indicated in the filter.\n\n\n                        All filters are expected to be compatible with each other except for the\n                        URLRewrite and RequestRedirect filters, which may not be combined. If an\n                        implementation can not support other combinations of filters, they must clearly\n                        document that limitation. In cases where incompatible or unsupported\n                        filters are specified and cause the `Accepted` condition to be set to status\n                        `False`, implementations may use the `IncompatibleFilters` reason to specify\n                        this configuration error.\n\n\n                        Support: Core\n                      items:\n                        description: |-\n                          HTTPRouteFilter defines processing steps that must be completed during the\n                          request or response lifecycle. HTTPRouteFilters are meant as an extension\n                          point to express processing that may be done in Gateway implementations. Some\n                          examples include request or response modification, implementing\n                          authentication strategies, rate-limiting, and traffic shaping. API\n                          guarantee/conformance is defined based on the type of the filter.\n                        properties:\n                          extensionRef:\n                            description: |-\n                              ExtensionRef is an optional, implementation-specific extension to the\n                              \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                              \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                              extended filters.\n\n\n                              This filter can be used multiple times within the same rule.\n\n\n                              Support: Implementation-specific\n                            properties:\n                              group:\n                                description: |-\n                                  Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                  When unspecified or empty string, core API group is inferred.\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                description: Kind is kind of the referent. For example\n                                  \"HTTPRoute\" or \"Service\".\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: Name is the name of the referent.\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                            required:\n                            - group\n                            - kind\n                            - name\n                            type: object\n                          requestHeaderModifier:\n                            description: |-\n                              RequestHeaderModifier defines a schema for a filter that modifies request\n                              headers.\n\n\n                              Support: Core\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestMirror:\n                            description: |-\n                              RequestMirror defines a schema for a filter that mirrors requests.\n                              Requests are sent to the specified destination, but responses from\n                              that destination are ignored.\n\n\n                              This filter can be used multiple times within the same rule. Note that\n                              not all implementations will be able to support mirroring to multiple\n                              backends.\n\n\n                              Support: Extended\n                            properties:\n                              backendRef:\n                                description: |-\n                                  BackendRef references a resource where mirrored requests are sent.\n\n\n                                  Mirrored requests must be sent only to a single destination endpoint\n                                  within this BackendRef, irrespective of how many endpoints are present\n                                  within this BackendRef.\n\n\n                                  If the referent cannot be found, this BackendRef is invalid and must be\n                                  dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                  condition on the Route status is set to `status: False` and not configure\n                                  this backend in the underlying implementation.\n\n\n                                  If there is a cross-namespace reference to an *existing* object\n                                  that is not allowed by a ReferenceGrant, the controller must ensure the\n                                  \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                  with the \"RefNotPermitted\" reason and not configure this backend in the\n                                  underlying implementation.\n\n\n                                  In either error case, the Message of the `ResolvedRefs` Condition\n                                  should be used to provide more detail about the problem.\n\n\n                                  Support: Extended for Kubernetes Service\n\n\n                                  Support: Implementation-specific for any other resource\n                                properties:\n                                  group:\n                                    default: \"\"\n                                    description: |-\n                                      Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                      When unspecified or empty string, core API group is inferred.\n                                    maxLength: 253\n                                    pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                    type: string\n                                  kind:\n                                    default: Service\n                                    description: |-\n                                      Kind is the Kubernetes resource kind of the referent. For example\n                                      \"Service\".\n\n\n                                      Defaults to \"Service\" when not specified.\n\n\n                                      ExternalName services can refer to CNAME DNS records that may live\n                                      outside of the cluster and as such are difficult to reason about in\n                                      terms of conformance. They also may not be safe to forward to (see\n                                      CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                      support ExternalName Services.\n\n\n                                      Support: Core (Services with a type other than ExternalName)\n\n\n                                      Support: Implementation-specific (Services with type ExternalName)\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                    type: string\n                                  name:\n                                    description: Name is the name of the referent.\n                                    maxLength: 253\n                                    minLength: 1\n                                    type: string\n                                  namespace:\n                                    description: |-\n                                      Namespace is the namespace of the backend. When unspecified, the local\n                                      namespace is inferred.\n\n\n                                      Note that when a namespace different than the local namespace is specified,\n                                      a ReferenceGrant object is required in the referent namespace to allow that\n                                      namespace's owner to accept the reference. See the ReferenceGrant\n                                      documentation for details.\n\n\n                                      Support: Core\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                    type: string\n                                  port:\n                                    description: |-\n                                      Port specifies the destination port number to use for this resource.\n                                      Port is required when the referent is a Kubernetes Service. In this\n                                      case, the port number is the service port number, not the target port.\n                                      For other resources, destination port might be derived from the referent\n                                      resource or this field.\n                                    format: int32\n                                    maximum: 65535\n                                    minimum: 1\n                                    type: integer\n                                required:\n                                - name\n                                type: object\n                                x-kubernetes-validations:\n                                - message: Must have port for Service reference\n                                  rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                                    ? has(self.port) : true'\n                            required:\n                            - backendRef\n                            type: object\n                          requestRedirect:\n                            description: |-\n                              RequestRedirect defines a schema for a filter that responds to the\n                              request with an HTTP redirection.\n\n\n                              Support: Core\n                            properties:\n                              hostname:\n                                description: |-\n                                  Hostname is the hostname to be used in the value of the `Location`\n                                  header in the response.\n                                  When empty, the hostname in the `Host` header of the request is used.\n\n\n                                  Support: Core\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: |-\n                                  Path defines parameters used to modify the path of the incoming request.\n                                  The modified path is then used to construct the `Location` header. When\n                                  empty, the request path is used as-is.\n\n\n                                  Support: Extended\n                                properties:\n                                  replaceFullPath:\n                                    description: |-\n                                      ReplaceFullPath specifies the value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: |-\n                                      ReplacePrefixMatch specifies the value with which to replace the prefix\n                                      match of a request during a rewrite or redirect. For example, a request\n                                      to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                      of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                      Note that this matches the behavior of the PathPrefix match type. This\n                                      matches full path elements. A path element refers to the list of labels\n                                      in the path split by the `/` separator. When specified, a trailing `/` is\n                                      ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                      match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                      ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                      Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                      the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                      Request Path | Prefix Match | Replace Prefix | Modified Path\n                                      -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                      /foo         | /foo         | /xyz           | /xyz\n                                      /foo/        | /foo         | /xyz           | /xyz/\n                                      /foo/bar     | /foo         | <empty string> | /bar\n                                      /foo/        | /foo         | <empty string> | /\n                                      /foo         | /foo         | <empty string> | /\n                                      /foo/        | /foo         | /              | /\n                                      /foo         | /foo         | /              | /\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: |-\n                                      Type defines the type of path modifier. Additional types may be\n                                      added in a future release of the API.\n\n\n                                      Note that values may be added to this enum, implementations\n                                      must ensure that unknown values will not cause a crash.\n\n\n                                      Unknown values here must result in the implementation setting the\n                                      Accepted Condition for the Route to `status: False`, with a\n                                      Reason of `UnsupportedValue`.\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                                x-kubernetes-validations:\n                                - message: replaceFullPath must be specified when\n                                    type is set to 'ReplaceFullPath'\n                                  rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath)\n                                    : true'\n                                - message: type must be 'ReplaceFullPath' when replaceFullPath\n                                    is set\n                                  rule: 'has(self.replaceFullPath) ? self.type ==\n                                    ''ReplaceFullPath'' : true'\n                                - message: replacePrefixMatch must be specified when\n                                    type is set to 'ReplacePrefixMatch'\n                                  rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch)\n                                    : true'\n                                - message: type must be 'ReplacePrefixMatch' when\n                                    replacePrefixMatch is set\n                                  rule: 'has(self.replacePrefixMatch) ? self.type\n                                    == ''ReplacePrefixMatch'' : true'\n                              port:\n                                description: |-\n                                  Port is the port to be used in the value of the `Location`\n                                  header in the response.\n\n\n                                  If no port is specified, the redirect port MUST be derived using the\n                                  following rules:\n\n\n                                  * If redirect scheme is not-empty, the redirect port MUST be the well-known\n                                    port associated with the redirect scheme. Specifically \"http\" to port 80\n                                    and \"https\" to port 443. If the redirect scheme does not have a\n                                    well-known port, the listener port of the Gateway SHOULD be used.\n                                  * If redirect scheme is empty, the redirect port MUST be the Gateway\n                                    Listener port.\n\n\n                                  Implementations SHOULD NOT add the port number in the 'Location'\n                                  header in the following cases:\n\n\n                                  * A Location header that will use HTTP (whether that is determined via\n                                    the Listener protocol or the Scheme field) _and_ use port 80.\n                                  * A Location header that will use HTTPS (whether that is determined via\n                                    the Listener protocol or the Scheme field) _and_ use port 443.\n\n\n                                  Support: Extended\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: |-\n                                  Scheme is the scheme to be used in the value of the `Location` header in\n                                  the response. When empty, the scheme of the request is used.\n\n\n                                  Scheme redirects can affect the port of the redirect, for more information,\n                                  refer to the documentation for the port field of this filter.\n\n\n                                  Note that values may be added to this enum, implementations\n                                  must ensure that unknown values will not cause a crash.\n\n\n                                  Unknown values here must result in the implementation setting the\n                                  Accepted Condition for the Route to `status: False`, with a\n                                  Reason of `UnsupportedValue`.\n\n\n                                  Support: Extended\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: |-\n                                  StatusCode is the HTTP status code to be used in response.\n\n\n                                  Note that values may be added to this enum, implementations\n                                  must ensure that unknown values will not cause a crash.\n\n\n                                  Unknown values here must result in the implementation setting the\n                                  Accepted Condition for the Route to `status: False`, with a\n                                  Reason of `UnsupportedValue`.\n\n\n                                  Support: Core\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          responseHeaderModifier:\n                            description: |-\n                              ResponseHeaderModifier defines a schema for a filter that modifies response\n                              headers.\n\n\n                              Support: Extended\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          type:\n                            description: |-\n                              Type identifies the type of filter to apply. As with other API fields,\n                              types are classified into three conformance levels:\n\n\n                              - Core: Filter types and their corresponding configuration defined by\n                                \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                implementations must support core filters.\n\n\n                              - Extended: Filter types and their corresponding configuration defined by\n                                \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                are encouraged to support extended filters.\n\n\n                              - Implementation-specific: Filters that are defined and supported by\n                                specific vendors.\n                                In the future, filters showing convergence in behavior across multiple\n                                implementations will be considered for inclusion in extended or core\n                                conformance levels. Filter-specific configuration for such filters\n                                is specified using the ExtensionRef field. `Type` should be set to\n                                \"ExtensionRef\" for custom filters.\n\n\n                              Implementers are encouraged to define custom implementation types to\n                              extend the core API with implementation-specific behavior.\n\n\n                              If a reference to a custom filter type cannot be resolved, the filter\n                              MUST NOT be skipped. Instead, requests that would have been processed by\n                              that filter MUST receive a HTTP error response.\n\n\n                              Note that values may be added to this enum, implementations\n                              must ensure that unknown values will not cause a crash.\n\n\n                              Unknown values here must result in the implementation setting the\n                              Accepted Condition for the Route to `status: False`, with a\n                              Reason of `UnsupportedValue`.\n                            enum:\n                            - RequestHeaderModifier\n                            - ResponseHeaderModifier\n                            - RequestMirror\n                            - RequestRedirect\n                            - URLRewrite\n                            - ExtensionRef\n                            type: string\n                          urlRewrite:\n                            description: |-\n                              URLRewrite defines a schema for a filter that modifies a request during forwarding.\n\n\n                              Support: Extended\n                            properties:\n                              hostname:\n                                description: |-\n                                  Hostname is the value to be used to replace the Host header value during\n                                  forwarding.\n\n\n                                  Support: Extended\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: |-\n                                  Path defines a path rewrite.\n\n\n                                  Support: Extended\n                                properties:\n                                  replaceFullPath:\n                                    description: |-\n                                      ReplaceFullPath specifies the value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: |-\n                                      ReplacePrefixMatch specifies the value with which to replace the prefix\n                                      match of a request during a rewrite or redirect. For example, a request\n                                      to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch\n                                      of \"/xyz\" would be modified to \"/xyz/bar\".\n\n\n                                      Note that this matches the behavior of the PathPrefix match type. This\n                                      matches full path elements. A path element refers to the list of labels\n                                      in the path split by the `/` separator. When specified, a trailing `/` is\n                                      ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n                                      match the prefix `/abc`, but the path `/abcd` would not.\n\n\n                                      ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch.\n                                      Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in\n                                      the implementation setting the Accepted Condition for the Route to `status: False`.\n\n\n                                      Request Path | Prefix Match | Replace Prefix | Modified Path\n                                      -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo         | /xyz/          | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          | /xyz/bar\n                                      /foo         | /foo         | /xyz           | /xyz\n                                      /foo/        | /foo         | /xyz           | /xyz/\n                                      /foo/bar     | /foo         | <empty string> | /bar\n                                      /foo/        | /foo         | <empty string> | /\n                                      /foo         | /foo         | <empty string> | /\n                                      /foo/        | /foo         | /              | /\n                                      /foo         | /foo         | /              | /\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: |-\n                                      Type defines the type of path modifier. Additional types may be\n                                      added in a future release of the API.\n\n\n                                      Note that values may be added to this enum, implementations\n                                      must ensure that unknown values will not cause a crash.\n\n\n                                      Unknown values here must result in the implementation setting the\n                                      Accepted Condition for the Route to `status: False`, with a\n                                      Reason of `UnsupportedValue`.\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                                x-kubernetes-validations:\n                                - message: replaceFullPath must be specified when\n                                    type is set to 'ReplaceFullPath'\n                                  rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath)\n                                    : true'\n                                - message: type must be 'ReplaceFullPath' when replaceFullPath\n                                    is set\n                                  rule: 'has(self.replaceFullPath) ? self.type ==\n                                    ''ReplaceFullPath'' : true'\n                                - message: replacePrefixMatch must be specified when\n                                    type is set to 'ReplacePrefixMatch'\n                                  rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch)\n                                    : true'\n                                - message: type must be 'ReplacePrefixMatch' when\n                                    replacePrefixMatch is set\n                                  rule: 'has(self.replacePrefixMatch) ? self.type\n                                    == ''ReplacePrefixMatch'' : true'\n                            type: object\n                        required:\n                        - type\n                        type: object\n                        x-kubernetes-validations:\n                        - message: filter.requestHeaderModifier must be nil if the\n                            filter.type is not RequestHeaderModifier\n                          rule: '!(has(self.requestHeaderModifier) && self.type !=\n                            ''RequestHeaderModifier'')'\n                        - message: filter.requestHeaderModifier must be specified\n                            for RequestHeaderModifier filter.type\n                          rule: '!(!has(self.requestHeaderModifier) && self.type ==\n                            ''RequestHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be nil if the\n                            filter.type is not ResponseHeaderModifier\n                          rule: '!(has(self.responseHeaderModifier) && self.type !=\n                            ''ResponseHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be specified\n                            for ResponseHeaderModifier filter.type\n                          rule: '!(!has(self.responseHeaderModifier) && self.type\n                            == ''ResponseHeaderModifier'')'\n                        - message: filter.requestMirror must be nil if the filter.type\n                            is not RequestMirror\n                          rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                        - message: filter.requestMirror must be specified for RequestMirror\n                            filter.type\n                          rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')'\n                        - message: filter.requestRedirect must be nil if the filter.type\n                            is not RequestRedirect\n                          rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')'\n                        - message: filter.requestRedirect must be specified for RequestRedirect\n                            filter.type\n                          rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')'\n                        - message: filter.urlRewrite must be nil if the filter.type\n                            is not URLRewrite\n                          rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')'\n                        - message: filter.urlRewrite must be specified for URLRewrite\n                            filter.type\n                          rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')'\n                        - message: filter.extensionRef must be nil if the filter.type\n                            is not ExtensionRef\n                          rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                        - message: filter.extensionRef must be specified for ExtensionRef\n                            filter.type\n                          rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                      maxItems: 16\n                      type: array\n                      x-kubernetes-validations:\n                      - message: May specify either httpRouteFilterRequestRedirect\n                          or httpRouteFilterRequestRewrite, but not both\n                        rule: '!(self.exists(f, f.type == ''RequestRedirect'') &&\n                          self.exists(f, f.type == ''URLRewrite''))'\n                      - message: RequestHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                          <= 1\n                      - message: ResponseHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                          <= 1\n                      - message: RequestRedirect filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestRedirect').size() <=\n                          1\n                      - message: URLRewrite filter cannot be repeated\n                        rule: self.filter(f, f.type == 'URLRewrite').size() <= 1\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: |-\n                        Matches define conditions used for matching the rule against incoming\n                        HTTP requests. Each match is independent, i.e. this rule will be matched\n                        if **any** one of the matches is satisfied.\n\n\n                        For example, take the following matches configuration:\n\n\n                        ```\n                        matches:\n                        - path:\n                            value: \"/foo\"\n                          headers:\n                          - name: \"version\"\n                            value: \"v2\"\n                        - path:\n                            value: \"/v2/foo\"\n                        ```\n\n\n                        For a request to match against this rule, a request must satisfy\n                        EITHER of the two conditions:\n\n\n                        - path prefixed with `/foo` AND contains the header `version: v2`\n                        - path prefix of `/v2/foo`\n\n\n                        See the documentation for HTTPRouteMatch on how to specify multiple\n                        match conditions that should be ANDed together.\n\n\n                        If no matches are specified, the default is a prefix\n                        path match on \"/\", which has the effect of matching every\n                        HTTP request.\n\n\n                        Proxy or Load Balancer routing configuration generated from HTTPRoutes\n                        MUST prioritize matches based on the following criteria, continuing on\n                        ties. Across all rules specified on applicable Routes, precedence must be\n                        given to the match having:\n\n\n                        * \"Exact\" path match.\n                        * \"Prefix\" path match with largest number of characters.\n                        * Method match.\n                        * Largest number of header matches.\n                        * Largest number of query param matches.\n\n\n                        Note: The precedence of RegularExpression path matches are implementation-specific.\n\n\n                        If ties still exist across multiple Routes, matching precedence MUST be\n                        determined in order of the following criteria, continuing on ties:\n\n\n                        * The oldest Route based on creation timestamp.\n                        * The Route appearing first in alphabetical order by\n                          \"{namespace}/{name}\".\n\n\n                        If ties still exist within an HTTPRoute, matching precedence MUST be granted\n                        to the FIRST matching rule (in list order) with a match meeting the above\n                        criteria.\n\n\n                        When no rules matching a request have been successfully attached to the\n                        parent a request is coming from, a HTTP 404 status code MUST be returned.\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given\\naction. Multiple match types\n                          are ANDed together, i.e. the match will\\nevaluate to true\n                          only if all conditions are satisfied.\\n\\n\\nFor example,\n                          the match below will match a HTTP request only if its path\\nstarts\n                          with `/foo` AND it contains the `version: v1` header:\\n\\n\\n```\\nmatch:\\n\\n\\n\\tpath:\\n\\t\n                          \\ value: \\\"/foo\\\"\\n\\theaders:\\n\\t- name: \\\"version\\\"\\n\\t\n                          \\ value \\\"v1\\\"\\n\\n\\n```\"\n                        properties:\n                          headers:\n                            description: |-\n                              Headers specifies HTTP request header matchers. Multiple match values are\n                              ANDed together, meaning, a request must match all the specified headers\n                              to select the route.\n                            items:\n                              description: |-\n                                HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request\n                                headers.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                    case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                    If multiple entries specify equivalent header names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be ignored. Due to the\n                                    case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                    equivalent.\n\n\n                                    When a header is repeated in an HTTP request, it is\n                                    implementation-specific behavior as to how this is represented.\n                                    Generally, proxies should follow the guidance from the RFC:\n                                    https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding\n                                    processing a repeated header, with special handling for \"Set-Cookie\".\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: |-\n                                    Type specifies how to match against the value of the header.\n\n\n                                    Support: Core (Exact)\n\n\n                                    Support: Implementation-specific (RegularExpression)\n\n\n                                    Since RegularExpression HeaderMatchType has implementation-specific\n                                    conformance, implementations can support POSIX, PCRE or any other dialects\n                                    of regular expressions. Please read the implementation's documentation to\n                                    determine the supported dialect.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: |-\n                              Method specifies HTTP method matcher.\n                              When specified, this route will be matched only if the request has the\n                              specified method.\n\n\n                              Support: Extended\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: |-\n                              Path specifies a HTTP request path matcher. If this field is not\n                              specified, a default prefix match on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: |-\n                                  Type specifies how to match against the path Value.\n\n\n                                  Support: Core (Exact, PathPrefix)\n\n\n                                  Support: Implementation-specific (RegularExpression)\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                            x-kubernetes-validations:\n                            - message: value must be an absolute path and start with\n                                '/' when type one of ['Exact', 'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'')\n                                : true'\n                            - message: must not contain '//' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'')\n                                : true'\n                            - message: must not contain '/./' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'')\n                                : true'\n                            - message: must not contain '/../' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'')\n                                : true'\n                            - message: must not contain '%2f' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'')\n                                : true'\n                            - message: must not contain '%2F' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'')\n                                : true'\n                            - message: must not contain '#' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'')\n                                : true'\n                            - message: must not end with '/..' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'')\n                                : true'\n                            - message: must not end with '/.' when type one of ['Exact',\n                                'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'')\n                                : true'\n                            - message: type must be one of ['Exact', 'PathPrefix',\n                                'RegularExpression']\n                              rule: self.type in ['Exact','PathPrefix'] || self.type\n                                == 'RegularExpression'\n                            - message: must only contain valid characters (matching\n                                ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$)\n                                for types ['Exact', 'PathPrefix']\n                              rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r\"\"\"^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$\"\"\")\n                                : true'\n                          queryParams:\n                            description: |-\n                              QueryParams specifies HTTP query parameter matchers. Multiple match\n                              values are ANDed together, meaning, a request must match all the\n                              specified query parameters to select the route.\n\n\n                              Support: Extended\n                            items:\n                              description: |-\n                                HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP\n                                query parameters.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the HTTP query param to be matched. This must be an\n                                    exact string match. (See\n                                    https://tools.ietf.org/html/rfc7230#section-2.7.3).\n\n\n                                    If multiple entries specify equivalent query param names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent query param name MUST be ignored.\n\n\n                                    If a query param is repeated in an HTTP request, the behavior is\n                                    purposely left undefined, since different data planes have different\n                                    capabilities. However, it is *recommended* that implementations should\n                                    match against the first value of the param if the data plane supports it,\n                                    as this behavior is expected in other load balancing contexts outside of\n                                    the Gateway API.\n\n\n                                    Users SHOULD NOT route traffic based on repeated query params to guard\n                                    themselves against potential differences in the implementations.\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: |-\n                                    Type specifies how to match against the value of the query parameter.\n\n\n                                    Support: Extended (Exact)\n\n\n                                    Support: Implementation-specific (RegularExpression)\n\n\n                                    Since RegularExpression QueryParamMatchType has Implementation-specific\n                                    conformance, implementations can support POSIX, PCRE or any other\n                                    dialects of regular expressions. Please read the implementation's\n                                    documentation to determine the supported dialect.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                    sessionPersistence:\n                      description: |+\n                        SessionPersistence defines and configures session persistence\n                        for the route rule.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        absoluteTimeout:\n                          description: |-\n                            AbsoluteTimeout defines the absolute timeout of the persistent\n                            session. Once the AbsoluteTimeout duration has elapsed, the\n                            session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        cookieConfig:\n                          description: |-\n                            CookieConfig provides configuration settings that are specific\n                            to cookie-based session persistence.\n\n\n                            Support: Core\n                          properties:\n                            lifetimeType:\n                              default: Session\n                              description: |-\n                                LifetimeType specifies whether the cookie has a permanent or\n                                session-based lifetime. A permanent cookie persists until its\n                                specified expiry time, defined by the Expires or Max-Age cookie\n                                attributes, while a session cookie is deleted when the current\n                                session ends.\n\n\n                                When set to \"Permanent\", AbsoluteTimeout indicates the\n                                cookie's lifetime via the Expires or Max-Age cookie attributes\n                                and is required.\n\n\n                                When set to \"Session\", AbsoluteTimeout indicates the\n                                absolute lifetime of the cookie tracked by the gateway and\n                                is optional.\n\n\n                                Support: Core for \"Session\" type\n\n\n                                Support: Extended for \"Permanent\" type\n                              enum:\n                              - Permanent\n                              - Session\n                              type: string\n                          type: object\n                        idleTimeout:\n                          description: |-\n                            IdleTimeout defines the idle timeout of the persistent session.\n                            Once the session has been idle for more than the specified\n                            IdleTimeout duration, the session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        sessionName:\n                          description: |-\n                            SessionName defines the name of the persistent session token\n                            which may be reflected in the cookie or the header. Users\n                            should avoid reusing session names to prevent unintended\n                            consequences, such as rejection or unpredictable behavior.\n\n\n                            Support: Implementation-specific\n                          maxLength: 128\n                          type: string\n                        type:\n                          default: Cookie\n                          description: |-\n                            Type defines the type of session persistence such as through\n                            the use a header or cookie. Defaults to cookie based session\n                            persistence.\n\n\n                            Support: Core for \"Cookie\" type\n\n\n                            Support: Extended for \"Header\" type\n                          enum:\n                          - Cookie\n                          - Header\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: AbsoluteTimeout must be specified when cookie lifetimeType\n                          is Permanent\n                        rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType)\n                          || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)'\n                    timeouts:\n                      description: |+\n                        Timeouts defines the timeouts that can be configured for an HTTP request.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        backendRequest:\n                          description: |-\n                            BackendRequest specifies a timeout for an individual request from the gateway\n                            to a backend. This covers the time from when the request first starts being\n                            sent from the gateway to when the full response has been received from the backend.\n\n\n                            Setting a timeout to the zero duration (e.g. \"0s\") SHOULD disable the timeout\n                            completely. Implementations that cannot completely disable the timeout MUST\n                            instead interpret the zero duration as the longest possible value to which\n                            the timeout can be set.\n\n\n                            An entire client HTTP transaction with a gateway, covered by the Request timeout,\n                            may result in more than one call from the gateway to the destination backend,\n                            for example, if automatic retries are supported.\n\n\n                            Because the Request timeout encompasses the BackendRequest timeout, the value of\n                            BackendRequest must be <= the value of Request timeout.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        request:\n                          description: |-\n                            Request specifies the maximum duration for a gateway to respond to an HTTP request.\n                            If the gateway has not been able to respond before this deadline is met, the gateway\n                            MUST return a timeout error.\n\n\n                            For example, setting the `rules.timeouts.request` field to the value `10s` in an\n                            `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds\n                            to complete.\n\n\n                            Setting a timeout to the zero duration (e.g. \"0s\") SHOULD disable the timeout\n                            completely. Implementations that cannot completely disable the timeout MUST\n                            instead interpret the zero duration as the longest possible value to which\n                            the timeout can be set.\n\n\n                            This timeout is intended to cover as close to the whole request-response transaction\n                            as possible although an implementation MAY choose to start the timeout after the entire\n                            request stream has been received instead of immediately after the transaction is\n                            initiated by the client.\n\n\n                            When this field is unspecified, request timeout behavior is implementation-specific.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: backendRequest timeout cannot be longer than request\n                          timeout\n                        rule: '!(has(self.request) && has(self.backendRequest) &&\n                          duration(self.request) != duration(''0s'') && duration(self.backendRequest)\n                          > duration(self.request))'\n                  type: object\n                  x-kubernetes-validations:\n                  - message: RequestRedirect filter must not be used together with\n                      backendRefs\n                    rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ?\n                      (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))):\n                      true'\n                  - message: When using RequestRedirect filter with path.replacePrefixMatch,\n                      exactly one PathPrefix match must be specified\n                    rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect)\n                      && has(f.requestRedirect.path) && f.requestRedirect.path.type\n                      == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch)))\n                      ? ((size(self.matches) != 1 || !has(self.matches[0].path) ||\n                      self.matches[0].path.type != ''PathPrefix'') ? false : true)\n                      : true'\n                  - message: When using URLRewrite filter with path.replacePrefixMatch,\n                      exactly one PathPrefix match must be specified\n                    rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite)\n                      && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch''\n                      && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches)\n                      != 1 || !has(self.matches[0].path) || self.matches[0].path.type\n                      != ''PathPrefix'') ? false : true) : true'\n                  - message: Within backendRefs, when using RequestRedirect filter\n                      with path.replacePrefixMatch, exactly one PathPrefix match must\n                      be specified\n                    rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b,\n                      (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect)\n                      && has(f.requestRedirect.path) && f.requestRedirect.path.type\n                      == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch)))\n                      )) ? ((size(self.matches) != 1 || !has(self.matches[0].path)\n                      || self.matches[0].path.type != ''PathPrefix'') ? false : true)\n                      : true'\n                  - message: Within backendRefs, When using URLRewrite filter with\n                      path.replacePrefixMatch, exactly one PathPrefix match must be\n                      specified\n                    rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b,\n                      (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite)\n                      && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch''\n                      && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches)\n                      != 1 || !has(self.matches[0].path) || self.matches[0].path.type\n                      != ''PathPrefix'') ? false : true) : true'\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: null\n  storedVersions: null\n\n\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997\n    gateway.networking.k8s.io/bundle-version: v1.1.1\n    gateway.networking.k8s.io/channel: experimental\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    helm.sh/resource-policy: keep\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\n  creationTimestamp: null\n  name: grpcroutes.gateway.networking.k8s.io\nspec:\n  group: gateway.networking.k8s.io\n  names:\n    categories:\n    - gateway-api\n    kind: GRPCRoute\n    listKind: GRPCRouteList\n    plural: grpcroutes\n    singular: grpcroute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1\n    schema:\n      openAPIV3Schema:\n        description: |-\n          GRPCRoute provides a way to route gRPC requests. This includes the capability\n          to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests will be routed.\n\n\n          GRPCRoute falls under extended support within the Gateway API. Within the\n          following specification, the word \"MUST\" indicates that an implementation\n          supporting GRPCRoute must conform to the indicated requirement, but an\n          implementation not supporting this route type need not follow the requirement\n          unless explicitly indicated.\n\n\n          Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST\n          accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via\n          ALPN. If the implementation does not support this, then it MUST set the\n          \"Accepted\" condition to \"False\" for the affected listener with a reason of\n          \"UnsupportedProtocol\".  Implementations MAY also accept HTTP/2 connections\n          with an upgrade from HTTP/1.\n\n\n          Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST\n          support HTTP/2 over cleartext TCP (h2c,\n          https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial\n          upgrade from HTTP/1.1, i.e. with prior knowledge\n          (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation\n          does not support this, then it MUST set the \"Accepted\" condition to \"False\"\n          for the affected listener with a reason of \"UnsupportedProtocol\".\n          Implementations MAY also accept HTTP/2 connections with an upgrade from\n          HTTP/1, i.e. without prior knowledge.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of GRPCRoute.\n            properties:\n              hostnames:\n                description: |-\n                  Hostnames defines a set of hostnames to match against the GRPC\n                  Host header to select a GRPCRoute to process the request. This matches\n                  the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                  1. IPs are not allowed.\n                  2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                     label MUST appear by itself as the first label.\n\n\n                  If a hostname is specified by both the Listener and GRPCRoute, there\n                  MUST be at least one intersecting hostname for the GRPCRoute to be\n                  attached to the Listener. For example:\n\n\n                  * A Listener with `test.example.com` as the hostname matches GRPCRoutes\n                    that have either not specified any hostnames, or have specified at\n                    least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches GRPCRoutes\n                    that have either not specified any hostnames or have specified at least\n                    one hostname that matches the Listener hostname. For example,\n                    `test.example.com` and `*.example.com` would both match. On the other\n                    hand, `example.com` and `test.example.net` would not match.\n\n\n                  Hostnames that are prefixed with a wildcard label (`*.`) are interpreted\n                  as a suffix match. That means that a match for `*.example.com` would match\n                  both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\n\n                  If both the Listener and GRPCRoute have specified hostnames, any\n                  GRPCRoute hostnames that do not match the Listener hostname MUST be\n                  ignored. For example, if a Listener specified `*.example.com`, and the\n                  GRPCRoute specified `test.example.com` and `test.example.net`,\n                  `test.example.net` MUST NOT be considered for a match.\n\n\n                  If both the Listener and GRPCRoute have specified hostnames, and none\n                  match with the criteria above, then the GRPCRoute MUST NOT be accepted by\n                  the implementation. The implementation MUST raise an 'Accepted' Condition\n                  with a status of `False` in the corresponding RouteParentStatus.\n\n\n                  If a Route (A) of type HTTPRoute or GRPCRoute is attached to a\n                  Listener and that listener already has another Route (B) of the other\n                  type attached and the intersection of the hostnames of A and B is\n                  non-empty, then the implementation MUST accept exactly one of these two\n                  routes, determined by the following criteria, in order:\n\n\n                  * The oldest Route based on creation timestamp.\n                  * The Route appearing first in alphabetical order by\n                    \"{namespace}/{name}\".\n\n\n                  The rejected Route MUST raise an 'Accepted' condition with a status of\n                  'False' in the corresponding RouteParentStatus.\n\n\n                  Support: Core\n                items:\n                  description: |-\n                    Hostname is the fully qualified domain name of a network host. This matches\n                    the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                     1. IPs are not allowed.\n                     2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                        label must appear by itself as the first label.\n\n\n                    Hostname can be \"precise\" which is a domain name without the terminating\n                    dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a\n                    domain name prefixed with a single wildcard label (e.g. `*.example.com`).\n\n\n                    Note that as per RFC1035 and RFC1123, a *label* must consist of lower case\n                    alphanumeric characters or '-', and must start and end with an alphanumeric\n                    character. No other punctuation is allowed.\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                description: Rules are a list of GRPC matchers, filters and actions.\n                items:\n                  description: |-\n                    GRPCRouteRule defines the semantics for matching a gRPC request based on\n                    conditions (matches), processing it (filters), and forwarding the request to\n                    an API object (backendRefs).\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent.\n\n\n                        Failure behavior here depends on how many BackendRefs are specified and\n                        how many are invalid.\n\n\n                        If *all* entries in BackendRefs are invalid, and there are also no filters\n                        specified in this route rule, *all* traffic which matches this rule MUST\n                        receive an `UNAVAILABLE` status.\n\n\n                        See the GRPCBackendRef definition for the rules about what makes a single\n                        GRPCBackendRef invalid.\n\n\n                        When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for\n                        requests that would have otherwise been routed to an invalid backend. If\n                        multiple backends are specified, and some are invalid, the proportion of\n                        requests that would otherwise have been routed to an invalid backend\n                        MUST receive an `UNAVAILABLE` status.\n\n\n                        For example, if two backends are specified with equal weights, and one is\n                        invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status.\n                        Implementations may choose how that 50 percent is determined.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Core\n                      items:\n                        description: |-\n                          GRPCBackendRef defines how a GRPCRoute forwards a gRPC request.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n                        properties:\n                          filters:\n                            description: |-\n                              Filters defined at this level MUST be executed if and only if the\n                              request is being forwarded to the backend defined here.\n\n\n                              Support: Implementation-specific (For broader support of filters, use the\n                              Filters field in GRPCRouteRule.)\n                            items:\n                              description: |-\n                                GRPCRouteFilter defines processing steps that must be completed during the\n                                request or response lifecycle. GRPCRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway implementations. Some\n                                examples include request or response modification, implementing\n                                authentication strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type of the filter.\n                              properties:\n                                extensionRef:\n                                  description: |-\n                                    ExtensionRef is an optional, implementation-specific extension to the\n                                    \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                                    \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                                    extended filters.\n\n\n                                    Support: Implementation-specific\n\n\n                                    This filter can be used multiple times within the same rule.\n                                  properties:\n                                    group:\n                                      description: |-\n                                        Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                        When unspecified or empty string, core API group is inferred.\n                                      maxLength: 253\n                                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    kind:\n                                      description: Kind is kind of the referent. For\n                                        example \"HTTPRoute\" or \"Service\".\n                                      maxLength: 63\n                                      minLength: 1\n                                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                      type: string\n                                    name:\n                                      description: Name is the name of the referent.\n                                      maxLength: 253\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - group\n                                  - kind\n                                  - name\n                                  type: object\n                                requestHeaderModifier:\n                                  description: |-\n                                    RequestHeaderModifier defines a schema for a filter that modifies request\n                                    headers.\n\n\n                                    Support: Core\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestMirror:\n                                  description: |-\n                                    RequestMirror defines a schema for a filter that mirrors requests.\n                                    Requests are sent to the specified destination, but responses from\n                                    that destination are ignored.\n\n\n                                    This filter can be used multiple times within the same rule. Note that\n                                    not all implementations will be able to support mirroring to multiple\n                                    backends.\n\n\n                                    Support: Extended\n                                  properties:\n                                    backendRef:\n                                      description: |-\n                                        BackendRef references a resource where mirrored requests are sent.\n\n\n                                        Mirrored requests must be sent only to a single destination endpoint\n                                        within this BackendRef, irrespective of how many endpoints are present\n                                        within this BackendRef.\n\n\n                                        If the referent cannot be found, this BackendRef is invalid and must be\n                                        dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                        condition on the Route status is set to `status: False` and not configure\n                                        this backend in the underlying implementation.\n\n\n                                        If there is a cross-namespace reference to an *existing* object\n                                        that is not allowed by a ReferenceGrant, the controller must ensure the\n                                        \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                        with the \"RefNotPermitted\" reason and not configure this backend in the\n                                        underlying implementation.\n\n\n                                        In either error case, the Message of the `ResolvedRefs` Condition\n                                        should be used to provide more detail about the problem.\n\n\n                                        Support: Extended for Kubernetes Service\n\n\n                                        Support: Implementation-specific for any other resource\n                                      properties:\n                                        group:\n                                          default: \"\"\n                                          description: |-\n                                            Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                            When unspecified or empty string, core API group is inferred.\n                                          maxLength: 253\n                                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                          type: string\n                                        kind:\n                                          default: Service\n                                          description: |-\n                                            Kind is the Kubernetes resource kind of the referent. For example\n                                            \"Service\".\n\n\n                                            Defaults to \"Service\" when not specified.\n\n\n                                            ExternalName services can refer to CNAME DNS records that may live\n                                            outside of the cluster and as such are difficult to reason about in\n                                            terms of conformance. They also may not be safe to forward to (see\n                                            CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                            support ExternalName Services.\n\n\n                                            Support: Core (Services with a type other than ExternalName)\n\n\n                                            Support: Implementation-specific (Services with type ExternalName)\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                          type: string\n                                        name:\n                                          description: Name is the name of the referent.\n                                          maxLength: 253\n                                          minLength: 1\n                                          type: string\n                                        namespace:\n                                          description: |-\n                                            Namespace is the namespace of the backend. When unspecified, the local\n                                            namespace is inferred.\n\n\n                                            Note that when a namespace different than the local namespace is specified,\n                                            a ReferenceGrant object is required in the referent namespace to allow that\n                                            namespace's owner to accept the reference. See the ReferenceGrant\n                                            documentation for details.\n\n\n                                            Support: Core\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                          type: string\n                                        port:\n                                          description: |-\n                                            Port specifies the destination port number to use for this resource.\n                                            Port is required when the referent is a Kubernetes Service. In this\n                                            case, the port number is the service port number, not the target port.\n                                            For other resources, destination port might be derived from the referent\n                                            resource or this field.\n                                          format: int32\n                                          maximum: 65535\n                                          minimum: 1\n                                          type: integer\n                                      required:\n                                      - name\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: Must have port for Service reference\n                                        rule: '(size(self.group) == 0 && self.kind\n                                          == ''Service'') ? has(self.port) : true'\n                                  required:\n                                  - backendRef\n                                  type: object\n                                responseHeaderModifier:\n                                  description: |-\n                                    ResponseHeaderModifier defines a schema for a filter that modifies response\n                                    headers.\n\n\n                                    Support: Extended\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: |+\n                                    Type identifies the type of filter to apply. As with other API fields,\n                                    types are classified into three conformance levels:\n\n\n                                    - Core: Filter types and their corresponding configuration defined by\n                                      \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                      implementations supporting GRPCRoute MUST support core filters.\n\n\n                                    - Extended: Filter types and their corresponding configuration defined by\n                                      \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                      are encouraged to support extended filters.\n\n\n                                    - Implementation-specific: Filters that are defined and supported by specific vendors.\n                                      In the future, filters showing convergence in behavior across multiple\n                                      implementations will be considered for inclusion in extended or core\n                                      conformance levels. Filter-specific configuration for such filters\n                                      is specified using the ExtensionRef field. `Type` MUST be set to\n                                      \"ExtensionRef\" for custom filters.\n\n\n                                    Implementers are encouraged to define custom implementation types to\n                                    extend the core API with implementation-specific behavior.\n\n\n                                    If a reference to a custom filter type cannot be resolved, the filter\n                                    MUST NOT be skipped. Instead, requests that would have been processed by\n                                    that filter MUST receive a HTTP error response.\n\n\n                                  enum:\n                                  - ResponseHeaderModifier\n                                  - RequestHeaderModifier\n                                  - RequestMirror\n                                  - ExtensionRef\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                              x-kubernetes-validations:\n                              - message: filter.requestHeaderModifier must be nil\n                                  if the filter.type is not RequestHeaderModifier\n                                rule: '!(has(self.requestHeaderModifier) && self.type\n                                  != ''RequestHeaderModifier'')'\n                              - message: filter.requestHeaderModifier must be specified\n                                  for RequestHeaderModifier filter.type\n                                rule: '!(!has(self.requestHeaderModifier) && self.type\n                                  == ''RequestHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be nil\n                                  if the filter.type is not ResponseHeaderModifier\n                                rule: '!(has(self.responseHeaderModifier) && self.type\n                                  != ''ResponseHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be specified\n                                  for ResponseHeaderModifier filter.type\n                                rule: '!(!has(self.responseHeaderModifier) && self.type\n                                  == ''ResponseHeaderModifier'')'\n                              - message: filter.requestMirror must be nil if the filter.type\n                                  is not RequestMirror\n                                rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                              - message: filter.requestMirror must be specified for\n                                  RequestMirror filter.type\n                                rule: '!(!has(self.requestMirror) && self.type ==\n                                  ''RequestMirror'')'\n                              - message: filter.extensionRef must be nil if the filter.type\n                                  is not ExtensionRef\n                                rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                              - message: filter.extensionRef must be specified for\n                                  ExtensionRef filter.type\n                                rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-validations:\n                            - message: RequestHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                                <= 1\n                            - message: ResponseHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                                <= 1\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: |-\n                        Filters define the filters that are applied to requests that match\n                        this rule.\n\n\n                        The effects of ordering of multiple behaviors are currently unspecified.\n                        This can change in the future based on feedback during the alpha stage.\n\n\n                        Conformance-levels at this level are defined based on the type of filter:\n\n\n                        - ALL core filters MUST be supported by all implementations that support\n                          GRPCRoute.\n                        - Implementers are encouraged to support extended filters.\n                        - Implementation-specific custom filters have no API guarantees across\n                          implementations.\n\n\n                        Specifying the same filter multiple times is not supported unless explicitly\n                        indicated in the filter.\n\n\n                        If an implementation can not support a combination of filters, it must clearly\n                        document that limitation. In cases where incompatible or unsupported\n                        filters are specified and cause the `Accepted` condition to be set to status\n                        `False`, implementations may use the `IncompatibleFilters` reason to specify\n                        this configuration error.\n\n\n                        Support: Core\n                      items:\n                        description: |-\n                          GRPCRouteFilter defines processing steps that must be completed during the\n                          request or response lifecycle. GRPCRouteFilters are meant as an extension\n                          point to express processing that may be done in Gateway implementations. Some\n                          examples include request or response modification, implementing\n                          authentication strategies, rate-limiting, and traffic shaping. API\n                          guarantee/conformance is defined based on the type of the filter.\n                        properties:\n                          extensionRef:\n                            description: |-\n                              ExtensionRef is an optional, implementation-specific extension to the\n                              \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                              \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                              extended filters.\n\n\n                              Support: Implementation-specific\n\n\n                              This filter can be used multiple times within the same rule.\n                            properties:\n                              group:\n                                description: |-\n                                  Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                  When unspecified or empty string, core API group is inferred.\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                description: Kind is kind of the referent. For example\n                                  \"HTTPRoute\" or \"Service\".\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: Name is the name of the referent.\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                            required:\n                            - group\n                            - kind\n                            - name\n                            type: object\n                          requestHeaderModifier:\n                            description: |-\n                              RequestHeaderModifier defines a schema for a filter that modifies request\n                              headers.\n\n\n                              Support: Core\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestMirror:\n                            description: |-\n                              RequestMirror defines a schema for a filter that mirrors requests.\n                              Requests are sent to the specified destination, but responses from\n                              that destination are ignored.\n\n\n                              This filter can be used multiple times within the same rule. Note that\n                              not all implementations will be able to support mirroring to multiple\n                              backends.\n\n\n                              Support: Extended\n                            properties:\n                              backendRef:\n                                description: |-\n                                  BackendRef references a resource where mirrored requests are sent.\n\n\n                                  Mirrored requests must be sent only to a single destination endpoint\n                                  within this BackendRef, irrespective of how many endpoints are present\n                                  within this BackendRef.\n\n\n                                  If the referent cannot be found, this BackendRef is invalid and must be\n                                  dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                  condition on the Route status is set to `status: False` and not configure\n                                  this backend in the underlying implementation.\n\n\n                                  If there is a cross-namespace reference to an *existing* object\n                                  that is not allowed by a ReferenceGrant, the controller must ensure the\n                                  \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                  with the \"RefNotPermitted\" reason and not configure this backend in the\n                                  underlying implementation.\n\n\n                                  In either error case, the Message of the `ResolvedRefs` Condition\n                                  should be used to provide more detail about the problem.\n\n\n                                  Support: Extended for Kubernetes Service\n\n\n                                  Support: Implementation-specific for any other resource\n                                properties:\n                                  group:\n                                    default: \"\"\n                                    description: |-\n                                      Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                      When unspecified or empty string, core API group is inferred.\n                                    maxLength: 253\n                                    pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                    type: string\n                                  kind:\n                                    default: Service\n                                    description: |-\n                                      Kind is the Kubernetes resource kind of the referent. For example\n                                      \"Service\".\n\n\n                                      Defaults to \"Service\" when not specified.\n\n\n                                      ExternalName services can refer to CNAME DNS records that may live\n                                      outside of the cluster and as such are difficult to reason about in\n                                      terms of conformance. They also may not be safe to forward to (see\n                                      CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                      support ExternalName Services.\n\n\n                                      Support: Core (Services with a type other than ExternalName)\n\n\n                                      Support: Implementation-specific (Services with type ExternalName)\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                    type: string\n                                  name:\n                                    description: Name is the name of the referent.\n                                    maxLength: 253\n                                    minLength: 1\n                                    type: string\n                                  namespace:\n                                    description: |-\n                                      Namespace is the namespace of the backend. When unspecified, the local\n                                      namespace is inferred.\n\n\n                                      Note that when a namespace different than the local namespace is specified,\n                                      a ReferenceGrant object is required in the referent namespace to allow that\n                                      namespace's owner to accept the reference. See the ReferenceGrant\n                                      documentation for details.\n\n\n                                      Support: Core\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                    type: string\n                                  port:\n                                    description: |-\n                                      Port specifies the destination port number to use for this resource.\n                                      Port is required when the referent is a Kubernetes Service. In this\n                                      case, the port number is the service port number, not the target port.\n                                      For other resources, destination port might be derived from the referent\n                                      resource or this field.\n                                    format: int32\n                                    maximum: 65535\n                                    minimum: 1\n                                    type: integer\n                                required:\n                                - name\n                                type: object\n                                x-kubernetes-validations:\n                                - message: Must have port for Service reference\n                                  rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                                    ? has(self.port) : true'\n                            required:\n                            - backendRef\n                            type: object\n                          responseHeaderModifier:\n                            description: |-\n                              ResponseHeaderModifier defines a schema for a filter that modifies response\n                              headers.\n\n\n                              Support: Extended\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          type:\n                            description: |+\n                              Type identifies the type of filter to apply. As with other API fields,\n                              types are classified into three conformance levels:\n\n\n                              - Core: Filter types and their corresponding configuration defined by\n                                \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                implementations supporting GRPCRoute MUST support core filters.\n\n\n                              - Extended: Filter types and their corresponding configuration defined by\n                                \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                are encouraged to support extended filters.\n\n\n                              - Implementation-specific: Filters that are defined and supported by specific vendors.\n                                In the future, filters showing convergence in behavior across multiple\n                                implementations will be considered for inclusion in extended or core\n                                conformance levels. Filter-specific configuration for such filters\n                                is specified using the ExtensionRef field. `Type` MUST be set to\n                                \"ExtensionRef\" for custom filters.\n\n\n                              Implementers are encouraged to define custom implementation types to\n                              extend the core API with implementation-specific behavior.\n\n\n                              If a reference to a custom filter type cannot be resolved, the filter\n                              MUST NOT be skipped. Instead, requests that would have been processed by\n                              that filter MUST receive a HTTP error response.\n\n\n                            enum:\n                            - ResponseHeaderModifier\n                            - RequestHeaderModifier\n                            - RequestMirror\n                            - ExtensionRef\n                            type: string\n                        required:\n                        - type\n                        type: object\n                        x-kubernetes-validations:\n                        - message: filter.requestHeaderModifier must be nil if the\n                            filter.type is not RequestHeaderModifier\n                          rule: '!(has(self.requestHeaderModifier) && self.type !=\n                            ''RequestHeaderModifier'')'\n                        - message: filter.requestHeaderModifier must be specified\n                            for RequestHeaderModifier filter.type\n                          rule: '!(!has(self.requestHeaderModifier) && self.type ==\n                            ''RequestHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be nil if the\n                            filter.type is not ResponseHeaderModifier\n                          rule: '!(has(self.responseHeaderModifier) && self.type !=\n                            ''ResponseHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be specified\n                            for ResponseHeaderModifier filter.type\n                          rule: '!(!has(self.responseHeaderModifier) && self.type\n                            == ''ResponseHeaderModifier'')'\n                        - message: filter.requestMirror must be nil if the filter.type\n                            is not RequestMirror\n                          rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                        - message: filter.requestMirror must be specified for RequestMirror\n                            filter.type\n                          rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')'\n                        - message: filter.extensionRef must be nil if the filter.type\n                            is not ExtensionRef\n                          rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                        - message: filter.extensionRef must be specified for ExtensionRef\n                            filter.type\n                          rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                      maxItems: 16\n                      type: array\n                      x-kubernetes-validations:\n                      - message: RequestHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                          <= 1\n                      - message: ResponseHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                          <= 1\n                    matches:\n                      description: |-\n                        Matches define conditions used for matching the rule against incoming\n                        gRPC requests. Each match is independent, i.e. this rule will be matched\n                        if **any** one of the matches is satisfied.\n\n\n                        For example, take the following matches configuration:\n\n\n                        ```\n                        matches:\n                        - method:\n                            service: foo.bar\n                          headers:\n                            values:\n                              version: 2\n                        - method:\n                            service: foo.bar.v2\n                        ```\n\n\n                        For a request to match against this rule, it MUST satisfy\n                        EITHER of the two conditions:\n\n\n                        - service of foo.bar AND contains the header `version: 2`\n                        - service of foo.bar.v2\n\n\n                        See the documentation for GRPCRouteMatch on how to specify multiple\n                        match conditions to be ANDed together.\n\n\n                        If no matches are specified, the implementation MUST match every gRPC request.\n\n\n                        Proxy or Load Balancer routing configuration generated from GRPCRoutes\n                        MUST prioritize rules based on the following criteria, continuing on\n                        ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes.\n                        Precedence MUST be given to the rule with the largest number of:\n\n\n                        * Characters in a matching non-wildcard hostname.\n                        * Characters in a matching hostname.\n                        * Characters in a matching service.\n                        * Characters in a matching method.\n                        * Header matches.\n\n\n                        If ties still exist across multiple Routes, matching precedence MUST be\n                        determined in order of the following criteria, continuing on ties:\n\n\n                        * The oldest Route based on creation timestamp.\n                        * The Route appearing first in alphabetical order by\n                          \"{namespace}/{name}\".\n\n\n                        If ties still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching rule meeting\n                        the above criteria.\n                      items:\n                        description: |-\n                          GRPCRouteMatch defines the predicate used to match requests to a given\n                          action. Multiple match types are ANDed together, i.e. the match will\n                          evaluate to true only if all conditions are satisfied.\n\n\n                          For example, the match below will match a gRPC request only if its service\n                          is `foo` AND it contains the `version: v1` header:\n\n\n                          ```\n                          matches:\n                            - method:\n                              type: Exact\n                              service: \"foo\"\n                              headers:\n                            - name: \"version\"\n                              value \"v1\"\n\n\n                          ```\n                        properties:\n                          headers:\n                            description: |-\n                              Headers specifies gRPC request header matchers. Multiple match values are\n                              ANDed together, meaning, a request MUST match all the specified headers\n                              to select the route.\n                            items:\n                              description: |-\n                                GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request\n                                headers.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the gRPC Header to be matched.\n\n\n                                    If multiple entries specify equivalent header names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be ignored. Due to the\n                                    case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                    equivalent.\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: Type specifies how to match against\n                                    the value of the header.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of the gRPC Header\n                                    to be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: |-\n                              Method specifies a gRPC request service/method matcher. If this field is\n                              not specified, all services and methods will match.\n                            properties:\n                              method:\n                                description: |-\n                                  Value of the method to match against. If left empty or omitted, will\n                                  match all services.\n\n\n                                  At least one of Service and Method MUST be a non-empty string.\n                                maxLength: 1024\n                                type: string\n                              service:\n                                description: |-\n                                  Value of the service to match against. If left empty or omitted, will\n                                  match any service.\n\n\n                                  At least one of Service and Method MUST be a non-empty string.\n                                maxLength: 1024\n                                type: string\n                              type:\n                                default: Exact\n                                description: |-\n                                  Type specifies how to match against the service and/or method.\n                                  Support: Core (Exact with service and method specified)\n\n\n                                  Support: Implementation-specific (Exact with method specified but no service specified)\n\n\n                                  Support: Implementation-specific (RegularExpression)\n                                enum:\n                                - Exact\n                                - RegularExpression\n                                type: string\n                            type: object\n                            x-kubernetes-validations:\n                            - message: One or both of 'service' or 'method' must be\n                                specified\n                              rule: 'has(self.type) ? has(self.service) || has(self.method)\n                                : true'\n                            - message: service must only contain valid characters\n                                (matching ^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$)\n                              rule: '(!has(self.type) || self.type == ''Exact'') &&\n                                has(self.service) ? self.service.matches(r\"\"\"^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$\"\"\"):\n                                true'\n                            - message: method must only contain valid characters (matching\n                                ^[A-Za-z_][A-Za-z_0-9]*$)\n                              rule: '(!has(self.type) || self.type == ''Exact'') &&\n                                has(self.method) ? self.method.matches(r\"\"\"^[A-Za-z_][A-Za-z_0-9]*$\"\"\"):\n                                true'\n                        type: object\n                      maxItems: 8\n                      type: array\n                    sessionPersistence:\n                      description: |+\n                        SessionPersistence defines and configures session persistence\n                        for the route rule.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        absoluteTimeout:\n                          description: |-\n                            AbsoluteTimeout defines the absolute timeout of the persistent\n                            session. Once the AbsoluteTimeout duration has elapsed, the\n                            session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        cookieConfig:\n                          description: |-\n                            CookieConfig provides configuration settings that are specific\n                            to cookie-based session persistence.\n\n\n                            Support: Core\n                          properties:\n                            lifetimeType:\n                              default: Session\n                              description: |-\n                                LifetimeType specifies whether the cookie has a permanent or\n                                session-based lifetime. A permanent cookie persists until its\n                                specified expiry time, defined by the Expires or Max-Age cookie\n                                attributes, while a session cookie is deleted when the current\n                                session ends.\n\n\n                                When set to \"Permanent\", AbsoluteTimeout indicates the\n                                cookie's lifetime via the Expires or Max-Age cookie attributes\n                                and is required.\n\n\n                                When set to \"Session\", AbsoluteTimeout indicates the\n                                absolute lifetime of the cookie tracked by the gateway and\n                                is optional.\n\n\n                                Support: Core for \"Session\" type\n\n\n                                Support: Extended for \"Permanent\" type\n                              enum:\n                              - Permanent\n                              - Session\n                              type: string\n                          type: object\n                        idleTimeout:\n                          description: |-\n                            IdleTimeout defines the idle timeout of the persistent session.\n                            Once the session has been idle for more than the specified\n                            IdleTimeout duration, the session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        sessionName:\n                          description: |-\n                            SessionName defines the name of the persistent session token\n                            which may be reflected in the cookie or the header. Users\n                            should avoid reusing session names to prevent unintended\n                            consequences, such as rejection or unpredictable behavior.\n\n\n                            Support: Implementation-specific\n                          maxLength: 128\n                          type: string\n                        type:\n                          default: Cookie\n                          description: |-\n                            Type defines the type of session persistence such as through\n                            the use a header or cookie. Defaults to cookie based session\n                            persistence.\n\n\n                            Support: Core for \"Cookie\" type\n\n\n                            Support: Extended for \"Header\" type\n                          enum:\n                          - Cookie\n                          - Header\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: AbsoluteTimeout must be specified when cookie lifetimeType\n                          is Permanent\n                        rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType)\n                          || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)'\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of GRPCRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    deprecated: true\n    deprecationWarning: The v1alpha2 version of GRPCRoute has been deprecated and\n      will be removed in a future release of the API. Please upgrade to v1.\n    name: v1alpha2\n    schema:\n      openAPIV3Schema:\n        description: |-\n          GRPCRoute provides a way to route gRPC requests. This includes the capability\n          to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests will be routed.\n\n\n          GRPCRoute falls under extended support within the Gateway API. Within the\n          following specification, the word \"MUST\" indicates that an implementation\n          supporting GRPCRoute must conform to the indicated requirement, but an\n          implementation not supporting this route type need not follow the requirement\n          unless explicitly indicated.\n\n\n          Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST\n          accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via\n          ALPN. If the implementation does not support this, then it MUST set the\n          \"Accepted\" condition to \"False\" for the affected listener with a reason of\n          \"UnsupportedProtocol\".  Implementations MAY also accept HTTP/2 connections\n          with an upgrade from HTTP/1.\n\n\n          Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST\n          support HTTP/2 over cleartext TCP (h2c,\n          https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial\n          upgrade from HTTP/1.1, i.e. with prior knowledge\n          (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation\n          does not support this, then it MUST set the \"Accepted\" condition to \"False\"\n          for the affected listener with a reason of \"UnsupportedProtocol\".\n          Implementations MAY also accept HTTP/2 connections with an upgrade from\n          HTTP/1, i.e. without prior knowledge.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of GRPCRoute.\n            properties:\n              hostnames:\n                description: |-\n                  Hostnames defines a set of hostnames to match against the GRPC\n                  Host header to select a GRPCRoute to process the request. This matches\n                  the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                  1. IPs are not allowed.\n                  2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                     label MUST appear by itself as the first label.\n\n\n                  If a hostname is specified by both the Listener and GRPCRoute, there\n                  MUST be at least one intersecting hostname for the GRPCRoute to be\n                  attached to the Listener. For example:\n\n\n                  * A Listener with `test.example.com` as the hostname matches GRPCRoutes\n                    that have either not specified any hostnames, or have specified at\n                    least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches GRPCRoutes\n                    that have either not specified any hostnames or have specified at least\n                    one hostname that matches the Listener hostname. For example,\n                    `test.example.com` and `*.example.com` would both match. On the other\n                    hand, `example.com` and `test.example.net` would not match.\n\n\n                  Hostnames that are prefixed with a wildcard label (`*.`) are interpreted\n                  as a suffix match. That means that a match for `*.example.com` would match\n                  both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\n\n                  If both the Listener and GRPCRoute have specified hostnames, any\n                  GRPCRoute hostnames that do not match the Listener hostname MUST be\n                  ignored. For example, if a Listener specified `*.example.com`, and the\n                  GRPCRoute specified `test.example.com` and `test.example.net`,\n                  `test.example.net` MUST NOT be considered for a match.\n\n\n                  If both the Listener and GRPCRoute have specified hostnames, and none\n                  match with the criteria above, then the GRPCRoute MUST NOT be accepted by\n                  the implementation. The implementation MUST raise an 'Accepted' Condition\n                  with a status of `False` in the corresponding RouteParentStatus.\n\n\n                  If a Route (A) of type HTTPRoute or GRPCRoute is attached to a\n                  Listener and that listener already has another Route (B) of the other\n                  type attached and the intersection of the hostnames of A and B is\n                  non-empty, then the implementation MUST accept exactly one of these two\n                  routes, determined by the following criteria, in order:\n\n\n                  * The oldest Route based on creation timestamp.\n                  * The Route appearing first in alphabetical order by\n                    \"{namespace}/{name}\".\n\n\n                  The rejected Route MUST raise an 'Accepted' condition with a status of\n                  'False' in the corresponding RouteParentStatus.\n\n\n                  Support: Core\n                items:\n                  description: |-\n                    Hostname is the fully qualified domain name of a network host. This matches\n                    the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                     1. IPs are not allowed.\n                     2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                        label must appear by itself as the first label.\n\n\n                    Hostname can be \"precise\" which is a domain name without the terminating\n                    dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a\n                    domain name prefixed with a single wildcard label (e.g. `*.example.com`).\n\n\n                    Note that as per RFC1035 and RFC1123, a *label* must consist of lower case\n                    alphanumeric characters or '-', and must start and end with an alphanumeric\n                    character. No other punctuation is allowed.\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                description: Rules are a list of GRPC matchers, filters and actions.\n                items:\n                  description: |-\n                    GRPCRouteRule defines the semantics for matching a gRPC request based on\n                    conditions (matches), processing it (filters), and forwarding the request to\n                    an API object (backendRefs).\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent.\n\n\n                        Failure behavior here depends on how many BackendRefs are specified and\n                        how many are invalid.\n\n\n                        If *all* entries in BackendRefs are invalid, and there are also no filters\n                        specified in this route rule, *all* traffic which matches this rule MUST\n                        receive an `UNAVAILABLE` status.\n\n\n                        See the GRPCBackendRef definition for the rules about what makes a single\n                        GRPCBackendRef invalid.\n\n\n                        When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for\n                        requests that would have otherwise been routed to an invalid backend. If\n                        multiple backends are specified, and some are invalid, the proportion of\n                        requests that would otherwise have been routed to an invalid backend\n                        MUST receive an `UNAVAILABLE` status.\n\n\n                        For example, if two backends are specified with equal weights, and one is\n                        invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status.\n                        Implementations may choose how that 50 percent is determined.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Core\n                      items:\n                        description: |-\n                          GRPCBackendRef defines how a GRPCRoute forwards a gRPC request.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n                        properties:\n                          filters:\n                            description: |-\n                              Filters defined at this level MUST be executed if and only if the\n                              request is being forwarded to the backend defined here.\n\n\n                              Support: Implementation-specific (For broader support of filters, use the\n                              Filters field in GRPCRouteRule.)\n                            items:\n                              description: |-\n                                GRPCRouteFilter defines processing steps that must be completed during the\n                                request or response lifecycle. GRPCRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway implementations. Some\n                                examples include request or response modification, implementing\n                                authentication strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type of the filter.\n                              properties:\n                                extensionRef:\n                                  description: |-\n                                    ExtensionRef is an optional, implementation-specific extension to the\n                                    \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                                    \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                                    extended filters.\n\n\n                                    Support: Implementation-specific\n\n\n                                    This filter can be used multiple times within the same rule.\n                                  properties:\n                                    group:\n                                      description: |-\n                                        Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                        When unspecified or empty string, core API group is inferred.\n                                      maxLength: 253\n                                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    kind:\n                                      description: Kind is kind of the referent. For\n                                        example \"HTTPRoute\" or \"Service\".\n                                      maxLength: 63\n                                      minLength: 1\n                                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                      type: string\n                                    name:\n                                      description: Name is the name of the referent.\n                                      maxLength: 253\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - group\n                                  - kind\n                                  - name\n                                  type: object\n                                requestHeaderModifier:\n                                  description: |-\n                                    RequestHeaderModifier defines a schema for a filter that modifies request\n                                    headers.\n\n\n                                    Support: Core\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestMirror:\n                                  description: |-\n                                    RequestMirror defines a schema for a filter that mirrors requests.\n                                    Requests are sent to the specified destination, but responses from\n                                    that destination are ignored.\n\n\n                                    This filter can be used multiple times within the same rule. Note that\n                                    not all implementations will be able to support mirroring to multiple\n                                    backends.\n\n\n                                    Support: Extended\n                                  properties:\n                                    backendRef:\n                                      description: |-\n                                        BackendRef references a resource where mirrored requests are sent.\n\n\n                                        Mirrored requests must be sent only to a single destination endpoint\n                                        within this BackendRef, irrespective of how many endpoints are present\n                                        within this BackendRef.\n\n\n                                        If the referent cannot be found, this BackendRef is invalid and must be\n                                        dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                        condition on the Route status is set to `status: False` and not configure\n                                        this backend in the underlying implementation.\n\n\n                                        If there is a cross-namespace reference to an *existing* object\n                                        that is not allowed by a ReferenceGrant, the controller must ensure the\n                                        \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                        with the \"RefNotPermitted\" reason and not configure this backend in the\n                                        underlying implementation.\n\n\n                                        In either error case, the Message of the `ResolvedRefs` Condition\n                                        should be used to provide more detail about the problem.\n\n\n                                        Support: Extended for Kubernetes Service\n\n\n                                        Support: Implementation-specific for any other resource\n                                      properties:\n                                        group:\n                                          default: \"\"\n                                          description: |-\n                                            Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                            When unspecified or empty string, core API group is inferred.\n                                          maxLength: 253\n                                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                          type: string\n                                        kind:\n                                          default: Service\n                                          description: |-\n                                            Kind is the Kubernetes resource kind of the referent. For example\n                                            \"Service\".\n\n\n                                            Defaults to \"Service\" when not specified.\n\n\n                                            ExternalName services can refer to CNAME DNS records that may live\n                                            outside of the cluster and as such are difficult to reason about in\n                                            terms of conformance. They also may not be safe to forward to (see\n                                            CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                            support ExternalName Services.\n\n\n                                            Support: Core (Services with a type other than ExternalName)\n\n\n                                            Support: Implementation-specific (Services with type ExternalName)\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                          type: string\n                                        name:\n                                          description: Name is the name of the referent.\n                                          maxLength: 253\n                                          minLength: 1\n                                          type: string\n                                        namespace:\n                                          description: |-\n                                            Namespace is the namespace of the backend. When unspecified, the local\n                                            namespace is inferred.\n\n\n                                            Note that when a namespace different than the local namespace is specified,\n                                            a ReferenceGrant object is required in the referent namespace to allow that\n                                            namespace's owner to accept the reference. See the ReferenceGrant\n                                            documentation for details.\n\n\n                                            Support: Core\n                                          maxLength: 63\n                                          minLength: 1\n                                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                          type: string\n                                        port:\n                                          description: |-\n                                            Port specifies the destination port number to use for this resource.\n                                            Port is required when the referent is a Kubernetes Service. In this\n                                            case, the port number is the service port number, not the target port.\n                                            For other resources, destination port might be derived from the referent\n                                            resource or this field.\n                                          format: int32\n                                          maximum: 65535\n                                          minimum: 1\n                                          type: integer\n                                      required:\n                                      - name\n                                      type: object\n                                      x-kubernetes-validations:\n                                      - message: Must have port for Service reference\n                                        rule: '(size(self.group) == 0 && self.kind\n                                          == ''Service'') ? has(self.port) : true'\n                                  required:\n                                  - backendRef\n                                  type: object\n                                responseHeaderModifier:\n                                  description: |-\n                                    ResponseHeaderModifier defines a schema for a filter that modifies response\n                                    headers.\n\n\n                                    Support: Extended\n                                  properties:\n                                    add:\n                                      description: |-\n                                        Add adds the given header(s) (name, value) to the request\n                                        before the action. It appends to any existing values associated\n                                        with the header name.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          add:\n                                          - name: \"my-header\"\n                                            value: \"bar,baz\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo,bar,baz\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: |-\n                                        Remove the given header(s) from the HTTP request before the action. The\n                                        value of Remove is a list of HTTP header names. Note that the header\n                                        names are case-insensitive (see\n                                        https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header1: foo\n                                          my-header2: bar\n                                          my-header3: baz\n\n\n                                        Config:\n                                          remove: [\"my-header1\", \"my-header3\"]\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header2: bar\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                    set:\n                                      description: |-\n                                        Set overwrites the request with the given header (name, value)\n                                        before the action.\n\n\n                                        Input:\n                                          GET /foo HTTP/1.1\n                                          my-header: foo\n\n\n                                        Config:\n                                          set:\n                                          - name: \"my-header\"\n                                            value: \"bar\"\n\n\n                                        Output:\n                                          GET /foo HTTP/1.1\n                                          my-header: bar\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: |-\n                                              Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                              case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                              If multiple entries specify equivalent header names, the first entry with\n                                              an equivalent name MUST be considered for a match. Subsequent entries\n                                              with an equivalent header name MUST be ignored. Due to the\n                                              case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                              equivalent.\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: |+\n                                    Type identifies the type of filter to apply. As with other API fields,\n                                    types are classified into three conformance levels:\n\n\n                                    - Core: Filter types and their corresponding configuration defined by\n                                      \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                      implementations supporting GRPCRoute MUST support core filters.\n\n\n                                    - Extended: Filter types and their corresponding configuration defined by\n                                      \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                      are encouraged to support extended filters.\n\n\n                                    - Implementation-specific: Filters that are defined and supported by specific vendors.\n                                      In the future, filters showing convergence in behavior across multiple\n                                      implementations will be considered for inclusion in extended or core\n                                      conformance levels. Filter-specific configuration for such filters\n                                      is specified using the ExtensionRef field. `Type` MUST be set to\n                                      \"ExtensionRef\" for custom filters.\n\n\n                                    Implementers are encouraged to define custom implementation types to\n                                    extend the core API with implementation-specific behavior.\n\n\n                                    If a reference to a custom filter type cannot be resolved, the filter\n                                    MUST NOT be skipped. Instead, requests that would have been processed by\n                                    that filter MUST receive a HTTP error response.\n\n\n                                  enum:\n                                  - ResponseHeaderModifier\n                                  - RequestHeaderModifier\n                                  - RequestMirror\n                                  - ExtensionRef\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                              x-kubernetes-validations:\n                              - message: filter.requestHeaderModifier must be nil\n                                  if the filter.type is not RequestHeaderModifier\n                                rule: '!(has(self.requestHeaderModifier) && self.type\n                                  != ''RequestHeaderModifier'')'\n                              - message: filter.requestHeaderModifier must be specified\n                                  for RequestHeaderModifier filter.type\n                                rule: '!(!has(self.requestHeaderModifier) && self.type\n                                  == ''RequestHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be nil\n                                  if the filter.type is not ResponseHeaderModifier\n                                rule: '!(has(self.responseHeaderModifier) && self.type\n                                  != ''ResponseHeaderModifier'')'\n                              - message: filter.responseHeaderModifier must be specified\n                                  for ResponseHeaderModifier filter.type\n                                rule: '!(!has(self.responseHeaderModifier) && self.type\n                                  == ''ResponseHeaderModifier'')'\n                              - message: filter.requestMirror must be nil if the filter.type\n                                  is not RequestMirror\n                                rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                              - message: filter.requestMirror must be specified for\n                                  RequestMirror filter.type\n                                rule: '!(!has(self.requestMirror) && self.type ==\n                                  ''RequestMirror'')'\n                              - message: filter.extensionRef must be nil if the filter.type\n                                  is not ExtensionRef\n                                rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                              - message: filter.extensionRef must be specified for\n                                  ExtensionRef filter.type\n                                rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-validations:\n                            - message: RequestHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                                <= 1\n                            - message: ResponseHeaderModifier filter cannot be repeated\n                              rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                                <= 1\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: |-\n                        Filters define the filters that are applied to requests that match\n                        this rule.\n\n\n                        The effects of ordering of multiple behaviors are currently unspecified.\n                        This can change in the future based on feedback during the alpha stage.\n\n\n                        Conformance-levels at this level are defined based on the type of filter:\n\n\n                        - ALL core filters MUST be supported by all implementations that support\n                          GRPCRoute.\n                        - Implementers are encouraged to support extended filters.\n                        - Implementation-specific custom filters have no API guarantees across\n                          implementations.\n\n\n                        Specifying the same filter multiple times is not supported unless explicitly\n                        indicated in the filter.\n\n\n                        If an implementation can not support a combination of filters, it must clearly\n                        document that limitation. In cases where incompatible or unsupported\n                        filters are specified and cause the `Accepted` condition to be set to status\n                        `False`, implementations may use the `IncompatibleFilters` reason to specify\n                        this configuration error.\n\n\n                        Support: Core\n                      items:\n                        description: |-\n                          GRPCRouteFilter defines processing steps that must be completed during the\n                          request or response lifecycle. GRPCRouteFilters are meant as an extension\n                          point to express processing that may be done in Gateway implementations. Some\n                          examples include request or response modification, implementing\n                          authentication strategies, rate-limiting, and traffic shaping. API\n                          guarantee/conformance is defined based on the type of the filter.\n                        properties:\n                          extensionRef:\n                            description: |-\n                              ExtensionRef is an optional, implementation-specific extension to the\n                              \"filter\" behavior.  For example, resource \"myroutefilter\" in group\n                              \"networking.example.net\"). ExtensionRef MUST NOT be used for core and\n                              extended filters.\n\n\n                              Support: Implementation-specific\n\n\n                              This filter can be used multiple times within the same rule.\n                            properties:\n                              group:\n                                description: |-\n                                  Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                  When unspecified or empty string, core API group is inferred.\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                description: Kind is kind of the referent. For example\n                                  \"HTTPRoute\" or \"Service\".\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: Name is the name of the referent.\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                            required:\n                            - group\n                            - kind\n                            - name\n                            type: object\n                          requestHeaderModifier:\n                            description: |-\n                              RequestHeaderModifier defines a schema for a filter that modifies request\n                              headers.\n\n\n                              Support: Core\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestMirror:\n                            description: |-\n                              RequestMirror defines a schema for a filter that mirrors requests.\n                              Requests are sent to the specified destination, but responses from\n                              that destination are ignored.\n\n\n                              This filter can be used multiple times within the same rule. Note that\n                              not all implementations will be able to support mirroring to multiple\n                              backends.\n\n\n                              Support: Extended\n                            properties:\n                              backendRef:\n                                description: |-\n                                  BackendRef references a resource where mirrored requests are sent.\n\n\n                                  Mirrored requests must be sent only to a single destination endpoint\n                                  within this BackendRef, irrespective of how many endpoints are present\n                                  within this BackendRef.\n\n\n                                  If the referent cannot be found, this BackendRef is invalid and must be\n                                  dropped from the Gateway. The controller must ensure the \"ResolvedRefs\"\n                                  condition on the Route status is set to `status: False` and not configure\n                                  this backend in the underlying implementation.\n\n\n                                  If there is a cross-namespace reference to an *existing* object\n                                  that is not allowed by a ReferenceGrant, the controller must ensure the\n                                  \"ResolvedRefs\"  condition on the Route is set to `status: False`,\n                                  with the \"RefNotPermitted\" reason and not configure this backend in the\n                                  underlying implementation.\n\n\n                                  In either error case, the Message of the `ResolvedRefs` Condition\n                                  should be used to provide more detail about the problem.\n\n\n                                  Support: Extended for Kubernetes Service\n\n\n                                  Support: Implementation-specific for any other resource\n                                properties:\n                                  group:\n                                    default: \"\"\n                                    description: |-\n                                      Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                                      When unspecified or empty string, core API group is inferred.\n                                    maxLength: 253\n                                    pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                    type: string\n                                  kind:\n                                    default: Service\n                                    description: |-\n                                      Kind is the Kubernetes resource kind of the referent. For example\n                                      \"Service\".\n\n\n                                      Defaults to \"Service\" when not specified.\n\n\n                                      ExternalName services can refer to CNAME DNS records that may live\n                                      outside of the cluster and as such are difficult to reason about in\n                                      terms of conformance. They also may not be safe to forward to (see\n                                      CVE-2021-25740 for more information). Implementations SHOULD NOT\n                                      support ExternalName Services.\n\n\n                                      Support: Core (Services with a type other than ExternalName)\n\n\n                                      Support: Implementation-specific (Services with type ExternalName)\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                    type: string\n                                  name:\n                                    description: Name is the name of the referent.\n                                    maxLength: 253\n                                    minLength: 1\n                                    type: string\n                                  namespace:\n                                    description: |-\n                                      Namespace is the namespace of the backend. When unspecified, the local\n                                      namespace is inferred.\n\n\n                                      Note that when a namespace different than the local namespace is specified,\n                                      a ReferenceGrant object is required in the referent namespace to allow that\n                                      namespace's owner to accept the reference. See the ReferenceGrant\n                                      documentation for details.\n\n\n                                      Support: Core\n                                    maxLength: 63\n                                    minLength: 1\n                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                    type: string\n                                  port:\n                                    description: |-\n                                      Port specifies the destination port number to use for this resource.\n                                      Port is required when the referent is a Kubernetes Service. In this\n                                      case, the port number is the service port number, not the target port.\n                                      For other resources, destination port might be derived from the referent\n                                      resource or this field.\n                                    format: int32\n                                    maximum: 65535\n                                    minimum: 1\n                                    type: integer\n                                required:\n                                - name\n                                type: object\n                                x-kubernetes-validations:\n                                - message: Must have port for Service reference\n                                  rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                                    ? has(self.port) : true'\n                            required:\n                            - backendRef\n                            type: object\n                          responseHeaderModifier:\n                            description: |-\n                              ResponseHeaderModifier defines a schema for a filter that modifies response\n                              headers.\n\n\n                              Support: Extended\n                            properties:\n                              add:\n                                description: |-\n                                  Add adds the given header(s) (name, value) to the request\n                                  before the action. It appends to any existing values associated\n                                  with the header name.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    add:\n                                    - name: \"my-header\"\n                                      value: \"bar,baz\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo,bar,baz\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: |-\n                                  Remove the given header(s) from the HTTP request before the action. The\n                                  value of Remove is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see\n                                  https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header1: foo\n                                    my-header2: bar\n                                    my-header3: baz\n\n\n                                  Config:\n                                    remove: [\"my-header1\", \"my-header3\"]\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header2: bar\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-type: set\n                              set:\n                                description: |-\n                                  Set overwrites the request with the given header (name, value)\n                                  before the action.\n\n\n                                  Input:\n                                    GET /foo HTTP/1.1\n                                    my-header: foo\n\n\n                                  Config:\n                                    set:\n                                    - name: \"my-header\"\n                                      value: \"bar\"\n\n\n                                  Output:\n                                    GET /foo HTTP/1.1\n                                    my-header: bar\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: |-\n                                        Name is the name of the HTTP Header to be matched. Name matching MUST be\n                                        case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\n\n                                        If multiple entries specify equivalent header names, the first entry with\n                                        an equivalent name MUST be considered for a match. Subsequent entries\n                                        with an equivalent header name MUST be ignored. Due to the\n                                        case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                        equivalent.\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          type:\n                            description: |+\n                              Type identifies the type of filter to apply. As with other API fields,\n                              types are classified into three conformance levels:\n\n\n                              - Core: Filter types and their corresponding configuration defined by\n                                \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n                                implementations supporting GRPCRoute MUST support core filters.\n\n\n                              - Extended: Filter types and their corresponding configuration defined by\n                                \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n                                are encouraged to support extended filters.\n\n\n                              - Implementation-specific: Filters that are defined and supported by specific vendors.\n                                In the future, filters showing convergence in behavior across multiple\n                                implementations will be considered for inclusion in extended or core\n                                conformance levels. Filter-specific configuration for such filters\n                                is specified using the ExtensionRef field. `Type` MUST be set to\n                                \"ExtensionRef\" for custom filters.\n\n\n                              Implementers are encouraged to define custom implementation types to\n                              extend the core API with implementation-specific behavior.\n\n\n                              If a reference to a custom filter type cannot be resolved, the filter\n                              MUST NOT be skipped. Instead, requests that would have been processed by\n                              that filter MUST receive a HTTP error response.\n\n\n                            enum:\n                            - ResponseHeaderModifier\n                            - RequestHeaderModifier\n                            - RequestMirror\n                            - ExtensionRef\n                            type: string\n                        required:\n                        - type\n                        type: object\n                        x-kubernetes-validations:\n                        - message: filter.requestHeaderModifier must be nil if the\n                            filter.type is not RequestHeaderModifier\n                          rule: '!(has(self.requestHeaderModifier) && self.type !=\n                            ''RequestHeaderModifier'')'\n                        - message: filter.requestHeaderModifier must be specified\n                            for RequestHeaderModifier filter.type\n                          rule: '!(!has(self.requestHeaderModifier) && self.type ==\n                            ''RequestHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be nil if the\n                            filter.type is not ResponseHeaderModifier\n                          rule: '!(has(self.responseHeaderModifier) && self.type !=\n                            ''ResponseHeaderModifier'')'\n                        - message: filter.responseHeaderModifier must be specified\n                            for ResponseHeaderModifier filter.type\n                          rule: '!(!has(self.responseHeaderModifier) && self.type\n                            == ''ResponseHeaderModifier'')'\n                        - message: filter.requestMirror must be nil if the filter.type\n                            is not RequestMirror\n                          rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')'\n                        - message: filter.requestMirror must be specified for RequestMirror\n                            filter.type\n                          rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')'\n                        - message: filter.extensionRef must be nil if the filter.type\n                            is not ExtensionRef\n                          rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')'\n                        - message: filter.extensionRef must be specified for ExtensionRef\n                            filter.type\n                          rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')'\n                      maxItems: 16\n                      type: array\n                      x-kubernetes-validations:\n                      - message: RequestHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'RequestHeaderModifier').size()\n                          <= 1\n                      - message: ResponseHeaderModifier filter cannot be repeated\n                        rule: self.filter(f, f.type == 'ResponseHeaderModifier').size()\n                          <= 1\n                    matches:\n                      description: |-\n                        Matches define conditions used for matching the rule against incoming\n                        gRPC requests. Each match is independent, i.e. this rule will be matched\n                        if **any** one of the matches is satisfied.\n\n\n                        For example, take the following matches configuration:\n\n\n                        ```\n                        matches:\n                        - method:\n                            service: foo.bar\n                          headers:\n                            values:\n                              version: 2\n                        - method:\n                            service: foo.bar.v2\n                        ```\n\n\n                        For a request to match against this rule, it MUST satisfy\n                        EITHER of the two conditions:\n\n\n                        - service of foo.bar AND contains the header `version: 2`\n                        - service of foo.bar.v2\n\n\n                        See the documentation for GRPCRouteMatch on how to specify multiple\n                        match conditions to be ANDed together.\n\n\n                        If no matches are specified, the implementation MUST match every gRPC request.\n\n\n                        Proxy or Load Balancer routing configuration generated from GRPCRoutes\n                        MUST prioritize rules based on the following criteria, continuing on\n                        ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes.\n                        Precedence MUST be given to the rule with the largest number of:\n\n\n                        * Characters in a matching non-wildcard hostname.\n                        * Characters in a matching hostname.\n                        * Characters in a matching service.\n                        * Characters in a matching method.\n                        * Header matches.\n\n\n                        If ties still exist across multiple Routes, matching precedence MUST be\n                        determined in order of the following criteria, continuing on ties:\n\n\n                        * The oldest Route based on creation timestamp.\n                        * The Route appearing first in alphabetical order by\n                          \"{namespace}/{name}\".\n\n\n                        If ties still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching rule meeting\n                        the above criteria.\n                      items:\n                        description: |-\n                          GRPCRouteMatch defines the predicate used to match requests to a given\n                          action. Multiple match types are ANDed together, i.e. the match will\n                          evaluate to true only if all conditions are satisfied.\n\n\n                          For example, the match below will match a gRPC request only if its service\n                          is `foo` AND it contains the `version: v1` header:\n\n\n                          ```\n                          matches:\n                            - method:\n                              type: Exact\n                              service: \"foo\"\n                              headers:\n                            - name: \"version\"\n                              value \"v1\"\n\n\n                          ```\n                        properties:\n                          headers:\n                            description: |-\n                              Headers specifies gRPC request header matchers. Multiple match values are\n                              ANDed together, meaning, a request MUST match all the specified headers\n                              to select the route.\n                            items:\n                              description: |-\n                                GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request\n                                headers.\n                              properties:\n                                name:\n                                  description: |-\n                                    Name is the name of the gRPC Header to be matched.\n\n\n                                    If multiple entries specify equivalent header names, only the first\n                                    entry with an equivalent name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be ignored. Due to the\n                                    case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n                                    equivalent.\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: Type specifies how to match against\n                                    the value of the header.\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of the gRPC Header\n                                    to be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: |-\n                              Method specifies a gRPC request service/method matcher. If this field is\n                              not specified, all services and methods will match.\n                            properties:\n                              method:\n                                description: |-\n                                  Value of the method to match against. If left empty or omitted, will\n                                  match all services.\n\n\n                                  At least one of Service and Method MUST be a non-empty string.\n                                maxLength: 1024\n                                type: string\n                              service:\n                                description: |-\n                                  Value of the service to match against. If left empty or omitted, will\n                                  match any service.\n\n\n                                  At least one of Service and Method MUST be a non-empty string.\n                                maxLength: 1024\n                                type: string\n                              type:\n                                default: Exact\n                                description: |-\n                                  Type specifies how to match against the service and/or method.\n                                  Support: Core (Exact with service and method specified)\n\n\n                                  Support: Implementation-specific (Exact with method specified but no service specified)\n\n\n                                  Support: Implementation-specific (RegularExpression)\n                                enum:\n                                - Exact\n                                - RegularExpression\n                                type: string\n                            type: object\n                            x-kubernetes-validations:\n                            - message: One or both of 'service' or 'method' must be\n                                specified\n                              rule: 'has(self.type) ? has(self.service) || has(self.method)\n                                : true'\n                            - message: service must only contain valid characters\n                                (matching ^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$)\n                              rule: '(!has(self.type) || self.type == ''Exact'') &&\n                                has(self.service) ? self.service.matches(r\"\"\"^(?i)\\.?[a-z_][a-z_0-9]*(\\.[a-z_][a-z_0-9]*)*$\"\"\"):\n                                true'\n                            - message: method must only contain valid characters (matching\n                                ^[A-Za-z_][A-Za-z_0-9]*$)\n                              rule: '(!has(self.type) || self.type == ''Exact'') &&\n                                has(self.method) ? self.method.matches(r\"\"\"^[A-Za-z_][A-Za-z_0-9]*$\"\"\"):\n                                true'\n                        type: object\n                      maxItems: 8\n                      type: array\n                    sessionPersistence:\n                      description: |+\n                        SessionPersistence defines and configures session persistence\n                        for the route rule.\n\n\n                        Support: Extended\n\n\n                      properties:\n                        absoluteTimeout:\n                          description: |-\n                            AbsoluteTimeout defines the absolute timeout of the persistent\n                            session. Once the AbsoluteTimeout duration has elapsed, the\n                            session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        cookieConfig:\n                          description: |-\n                            CookieConfig provides configuration settings that are specific\n                            to cookie-based session persistence.\n\n\n                            Support: Core\n                          properties:\n                            lifetimeType:\n                              default: Session\n                              description: |-\n                                LifetimeType specifies whether the cookie has a permanent or\n                                session-based lifetime. A permanent cookie persists until its\n                                specified expiry time, defined by the Expires or Max-Age cookie\n                                attributes, while a session cookie is deleted when the current\n                                session ends.\n\n\n                                When set to \"Permanent\", AbsoluteTimeout indicates the\n                                cookie's lifetime via the Expires or Max-Age cookie attributes\n                                and is required.\n\n\n                                When set to \"Session\", AbsoluteTimeout indicates the\n                                absolute lifetime of the cookie tracked by the gateway and\n                                is optional.\n\n\n                                Support: Core for \"Session\" type\n\n\n                                Support: Extended for \"Permanent\" type\n                              enum:\n                              - Permanent\n                              - Session\n                              type: string\n                          type: object\n                        idleTimeout:\n                          description: |-\n                            IdleTimeout defines the idle timeout of the persistent session.\n                            Once the session has been idle for more than the specified\n                            IdleTimeout duration, the session becomes invalid.\n\n\n                            Support: Extended\n                          pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$\n                          type: string\n                        sessionName:\n                          description: |-\n                            SessionName defines the name of the persistent session token\n                            which may be reflected in the cookie or the header. Users\n                            should avoid reusing session names to prevent unintended\n                            consequences, such as rejection or unpredictable behavior.\n\n\n                            Support: Implementation-specific\n                          maxLength: 128\n                          type: string\n                        type:\n                          default: Cookie\n                          description: |-\n                            Type defines the type of session persistence such as through\n                            the use a header or cookie. Defaults to cookie based session\n                            persistence.\n\n\n                            Support: Core for \"Cookie\" type\n\n\n                            Support: Extended for \"Header\" type\n                          enum:\n                          - Cookie\n                          - Header\n                          type: string\n                      type: object\n                      x-kubernetes-validations:\n                      - message: AbsoluteTimeout must be specified when cookie lifetimeType\n                          is Permanent\n                        rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType)\n                          || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)'\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of GRPCRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: null\n  storedVersions: null\n\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997\n    gateway.networking.k8s.io/bundle-version: v1.1.1\n    gateway.networking.k8s.io/channel: experimental\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    helm.sh/resource-policy: keep\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\n  creationTimestamp: null\n  name: tlsroutes.gateway.networking.k8s.io\nspec:\n  group: gateway.networking.k8s.io\n  names:\n    categories:\n    - gateway-api\n    kind: TLSRoute\n    listKind: TLSRouteList\n    plural: tlsroutes\n    singular: tlsroute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1alpha2\n    schema:\n      openAPIV3Schema:\n        description: |-\n          The TLSRoute resource is similar to TCPRoute, but can be configured\n          to match against TLS-specific metadata. This allows more flexibility\n          in matching streams for a given TLS listener.\n\n\n          If you need to forward traffic to a single target for a TLS listener, you\n          could choose to use a TCPRoute with a TLS listener.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of TLSRoute.\n            properties:\n              hostnames:\n                description: |-\n                  Hostnames defines a set of SNI names that should match against the\n                  SNI attribute of TLS ClientHello message in TLS handshake. This matches\n                  the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                  1. IPs are not allowed in SNI names per RFC 6066.\n                  2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                     label must appear by itself as the first label.\n\n\n                  If a hostname is specified by both the Listener and TLSRoute, there\n                  must be at least one intersecting hostname for the TLSRoute to be\n                  attached to the Listener. For example:\n\n\n                  * A Listener with `test.example.com` as the hostname matches TLSRoutes\n                    that have either not specified any hostnames, or have specified at\n                    least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches TLSRoutes\n                    that have either not specified any hostnames or have specified at least\n                    one hostname that matches the Listener hostname. For example,\n                    `test.example.com` and `*.example.com` would both match. On the other\n                    hand, `example.com` and `test.example.net` would not match.\n\n\n                  If both the Listener and TLSRoute have specified hostnames, any\n                  TLSRoute hostnames that do not match the Listener hostname MUST be\n                  ignored. For example, if a Listener specified `*.example.com`, and the\n                  TLSRoute specified `test.example.com` and `test.example.net`,\n                  `test.example.net` must not be considered for a match.\n\n\n                  If both the Listener and TLSRoute have specified hostnames, and none\n                  match with the criteria above, then the TLSRoute is not accepted. The\n                  implementation must raise an 'Accepted' Condition with a status of\n                  `False` in the corresponding RouteParentStatus.\n\n\n                  Support: Core\n                items:\n                  description: |-\n                    Hostname is the fully qualified domain name of a network host. This matches\n                    the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n\n                     1. IPs are not allowed.\n                     2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n                        label must appear by itself as the first label.\n\n\n                    Hostname can be \"precise\" which is a domain name without the terminating\n                    dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a\n                    domain name prefixed with a single wildcard label (e.g. `*.example.com`).\n\n\n                    Note that as per RFC1035 and RFC1123, a *label* must consist of lower case\n                    alphanumeric characters or '-', and must start and end with an alphanumeric\n                    character. No other punctuation is allowed.\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                description: Rules are a list of TLS matchers and actions.\n                items:\n                  description: TLSRouteRule is the configuration for a given rule.\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent. If unspecified or invalid (refers to a non-existent resource or\n                        a Service with no endpoints), the rule performs no forwarding; if no\n                        filters are specified that would result in a response being sent, the\n                        underlying implementation must actively reject request attempts to this\n                        backend, by rejecting the connection or returning a 500 status code.\n                        Request rejections must respect weight; if an invalid backend is\n                        requested to have 80% of requests, then 80% of requests must be rejected\n                        instead.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Extended for Kubernetes ServiceImport\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Extended\n                      items:\n                        description: |-\n                          BackendRef defines how a Route should forward a request to a Kubernetes\n                          resource.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n\n\n                          Note that when the BackendTLSPolicy object is enabled by the implementation,\n                          there are some extra rules about validity to consider here. See the fields\n                          where this struct is used for more information about the exact behavior.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      minItems: 1\n                      type: array\n                  type: object\n                maxItems: 16\n                minItems: 1\n                type: array\n            required:\n            - rules\n            type: object\n          status:\n            description: Status defines the current state of TLSRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: null\n  storedVersions: null\n\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2997\n    gateway.networking.k8s.io/bundle-version: v1.1.1\n    gateway.networking.k8s.io/channel: experimental\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    helm.sh/resource-policy: keep\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\n  creationTimestamp: null\n  name: tcproutes.gateway.networking.k8s.io\nspec:\n  group: gateway.networking.k8s.io\n  names:\n    categories:\n    - gateway-api\n    kind: TCPRoute\n    listKind: TCPRouteList\n    plural: tcproutes\n    singular: tcproute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1alpha2\n    schema:\n      openAPIV3Schema:\n        description: |-\n          TCPRoute provides a way to route TCP requests. When combined with a Gateway\n          listener, it can be used to forward connections on the port specified by the\n          listener to a set of backends specified by the TCPRoute.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of TCPRoute.\n            properties:\n              parentRefs:\n                description: |+\n                  ParentRefs references the resources (usually Gateways) that a Route wants\n                  to be attached to. Note that the referenced parent resource needs to\n                  allow this for the attachment to be complete. For Gateways, that means\n                  the Gateway needs to allow attachment from Routes of this kind and\n                  namespace. For Services, that means the Service must either be in the same\n                  namespace for a \"producer\" route, or the mesh implementation must support\n                  and allow \"consumer\" routes for the referenced Service. ReferenceGrant is\n                  not applicable for governing ParentRefs to Services - it is not possible to\n                  create a \"producer\" route for a Service in a different namespace from the\n                  Route.\n\n\n                  There are two kinds of parent resources with \"Core\" support:\n\n\n                  * Gateway (Gateway conformance profile)\n                  * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                  This API may be extended in the future to support additional kinds of parent\n                  resources.\n\n\n                  ParentRefs must be _distinct_. This means either that:\n\n\n                  * They select different objects.  If this is the case, then parentRef\n                    entries are distinct. In terms of fields, this means that the\n                    multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n                    be unique across all parentRef entries in the Route.\n                  * They do not select different objects, but for each optional field used,\n                    each ParentRef that selects the same object must set the same set of\n                    optional fields to different values. If one ParentRef sets a\n                    combination of optional fields, all must set the same combination.\n\n\n                  Some examples:\n\n\n                  * If one ParentRef sets `sectionName`, all ParentRefs referencing the\n                    same object must also set `sectionName`.\n                  * If one ParentRef sets `port`, all ParentRefs referencing the same\n                    object must also set `port`.\n                  * If one ParentRef sets `sectionName` and `port`, all ParentRefs\n                    referencing the same object must also set `sectionName` and `port`.\n\n\n                  It is possible to separately reference multiple distinct objects that may\n                  be collapsed by an implementation. For example, some implementations may\n                  choose to merge compatible Gateway Listeners together. If that is the\n                  case, the list of routes attached to those resources should also be\n                  merged.\n\n\n                  Note that for ParentRefs that cross namespace boundaries, there are specific\n                  rules. Cross-namespace references are only valid if they are explicitly\n                  allowed by something in the namespace they are referring to. For example,\n                  Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                  generic way to enable other kinds of cross-namespace reference.\n\n\n\n                  ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                  routes, which apply default routing rules to inbound connections from\n                  any namespace to the Service.\n\n\n                  ParentRefs from a Route to a Service in a different namespace are\n                  \"consumer\" routes, and these routing rules are only applied to outbound\n                  connections originating from the same namespace as the Route, for which\n                  the intended destination of the connections are a Service targeted as a\n                  ParentRef of the Route.\n\n\n\n\n\n\n                items:\n                  description: |-\n                    ParentReference identifies an API object (usually a Gateway) that can be considered\n                    a parent of this resource (usually a route). There are two kinds of parent resources\n                    with \"Core\" support:\n\n\n                    * Gateway (Gateway conformance profile)\n                    * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                    This API may be extended in the future to support additional kinds of parent\n                    resources.\n\n\n                    The API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\n                  properties:\n                    group:\n                      default: gateway.networking.k8s.io\n                      description: |-\n                        Group is the group of the referent.\n                        When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                        To set the core API group (such as for a \"Service\" kind referent),\n                        Group must be explicitly set to \"\" (empty string).\n\n\n                        Support: Core\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: |-\n                        Kind is kind of the referent.\n\n\n                        There are two kinds of parent resources with \"Core\" support:\n\n\n                        * Gateway (Gateway conformance profile)\n                        * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                        Support for other resources is Implementation-Specific.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: |-\n                        Name is the name of the referent.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: |-\n                        Namespace is the namespace of the referent. When unspecified, this refers\n                        to the local namespace of the Route.\n\n\n                        Note that there are specific rules for ParentRefs which cross namespace\n                        boundaries. Cross-namespace references are only valid if they are explicitly\n                        allowed by something in the namespace they are referring to. For example:\n                        Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                        generic way to enable any other kind of cross-namespace reference.\n\n\n\n                        ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                        routes, which apply default routing rules to inbound connections from\n                        any namespace to the Service.\n\n\n                        ParentRefs from a Route to a Service in a different namespace are\n                        \"consumer\" routes, and these routing rules are only applied to outbound\n                        connections originating from the same namespace as the Route, for which\n                        the intended destination of the connections are a Service targeted as a\n                        ParentRef of the Route.\n\n\n\n                        Support: Core\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: |-\n                        Port is the network port this Route targets. It can be interpreted\n                        differently based on the type of parent resource.\n\n\n                        When the parent resource is a Gateway, this targets all listeners\n                        listening on the specified port that also support this kind of Route(and\n                        select this Route). It's not recommended to set `Port` unless the\n                        networking behaviors specified in a Route must apply to a specific port\n                        as opposed to a listener(s) whose port(s) may be changed. When both Port\n                        and SectionName are specified, the name and port of the selected listener\n                        must match both specified values.\n\n\n\n                        When the parent resource is a Service, this targets a specific port in the\n                        Service spec. When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected port must match both specified values.\n\n\n\n                        Implementations MAY choose to support other parent resources.\n                        Implementations supporting other types of parent resources MUST clearly\n                        document how/if Port is interpreted.\n\n\n                        For the purpose of status, an attachment is considered successful as\n                        long as the parent resource accepts it partially. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route,\n                        the Route MUST be considered detached from the Gateway.\n\n\n                        Support: Extended\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: |-\n                        SectionName is the name of a section within the target resource. In the\n                        following resources, SectionName is interpreted as the following:\n\n\n                        * Gateway: Listener name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n                        * Service: Port name. When both Port (experimental) and SectionName\n                        are specified, the name and port of the selected listener must match\n                        both specified values.\n\n\n                        Implementations MAY choose to support attaching Routes to other resources.\n                        If that is the case, they MUST clearly document how SectionName is\n                        interpreted.\n\n\n                        When unspecified (empty string), this will reference the entire resource.\n                        For the purpose of status, an attachment is considered successful if at\n                        least one section in the parent resource accepts it. For example, Gateway\n                        listeners can restrict which Routes can attach to them by Route kind,\n                        namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                        the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this Route, the\n                        Route MUST be considered detached from the Gateway.\n\n\n                        Support: Core\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n                x-kubernetes-validations:\n                - message: sectionName or port must be specified when parentRefs includes\n                    2 or more references to the same parent\n                  rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName)\n                    || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName\n                    == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port)\n                    || p2.port == 0)): true))'\n                - message: sectionName or port must be unique when parentRefs includes\n                    2 or more references to the same parent\n                  rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind\n                    == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__)\n                    || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__\n                    == '')) || (has(p1.__namespace__) && has(p2.__namespace__) &&\n                    p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName)\n                    || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName\n                    == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName\n                    == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port)\n                    || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port\n                    == p2.port))))\n              rules:\n                description: Rules are a list of TCP matchers and actions.\n                items:\n                  description: TCPRouteRule is the configuration for a given rule.\n                  properties:\n                    backendRefs:\n                      description: |-\n                        BackendRefs defines the backend(s) where matching requests should be\n                        sent. If unspecified or invalid (refers to a non-existent resource or a\n                        Service with no endpoints), the underlying implementation MUST actively\n                        reject connection attempts to this backend. Connection rejections must\n                        respect weight; if an invalid backend is requested to have 80% of\n                        connections, then 80% of connections must be rejected instead.\n\n\n                        Support: Core for Kubernetes Service\n\n\n                        Support: Extended for Kubernetes ServiceImport\n\n\n                        Support: Implementation-specific for any other resource\n\n\n                        Support for weight: Extended\n                      items:\n                        description: |-\n                          BackendRef defines how a Route should forward a request to a Kubernetes\n                          resource.\n\n\n                          Note that when a namespace different than the local namespace is specified, a\n                          ReferenceGrant object is required in the referent namespace to allow that\n                          namespace's owner to accept the reference. See the ReferenceGrant\n                          documentation for details.\n\n\n                          <gateway:experimental:description>\n\n\n                          When the BackendRef points to a Kubernetes Service, implementations SHOULD\n                          honor the appProtocol field if it is set for the target Service Port.\n\n\n                          Implementations supporting appProtocol SHOULD recognize the Kubernetes\n                          Standard Application Protocols defined in KEP-3726.\n\n\n                          If a Service appProtocol isn't specified, an implementation MAY infer the\n                          backend protocol through its own means. Implementations MAY infer the\n                          protocol from the Route type referring to the backend Service.\n\n\n                          If a Route is not able to send traffic to the backend using the specified\n                          protocol then the backend is considered invalid. Implementations MUST set the\n                          \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n                          </gateway:experimental:description>\n\n\n                          Note that when the BackendTLSPolicy object is enabled by the implementation,\n                          there are some extra rules about validity to consider here. See the fields\n                          where this struct is used for more information about the exact behavior.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: |-\n                              Group is the group of the referent. For example, \"gateway.networking.k8s.io\".\n                              When unspecified or empty string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: |-\n                              Kind is the Kubernetes resource kind of the referent. For example\n                              \"Service\".\n\n\n                              Defaults to \"Service\" when not specified.\n\n\n                              ExternalName services can refer to CNAME DNS records that may live\n                              outside of the cluster and as such are difficult to reason about in\n                              terms of conformance. They also may not be safe to forward to (see\n                              CVE-2021-25740 for more information). Implementations SHOULD NOT\n                              support ExternalName Services.\n\n\n                              Support: Core (Services with a type other than ExternalName)\n\n\n                              Support: Implementation-specific (Services with type ExternalName)\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: |-\n                              Namespace is the namespace of the backend. When unspecified, the local\n                              namespace is inferred.\n\n\n                              Note that when a namespace different than the local namespace is specified,\n                              a ReferenceGrant object is required in the referent namespace to allow that\n                              namespace's owner to accept the reference. See the ReferenceGrant\n                              documentation for details.\n\n\n                              Support: Core\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: |-\n                              Port specifies the destination port number to use for this resource.\n                              Port is required when the referent is a Kubernetes Service. In this\n                              case, the port number is the service port number, not the target port.\n                              For other resources, destination port might be derived from the referent\n                              resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: |-\n                              Weight specifies the proportion of requests forwarded to the referenced\n                              backend. This is computed as weight/(sum of all weights in this\n                              BackendRefs list). For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision an\n                              implementation supports. Weight is not a percentage and the sum of\n                              weights does not need to equal 100.\n\n\n                              If only one backend is specified and it has a weight greater than 0, 100%\n                              of the traffic is forwarded to that backend. If weight is set to 0, no\n                              traffic should be forwarded for this entry. If unspecified, weight\n                              defaults to 1.\n\n\n                              Support for this field varies based on the context where used.\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                        required:\n                        - name\n                        type: object\n                        x-kubernetes-validations:\n                        - message: Must have port for Service reference\n                          rule: '(size(self.group) == 0 && self.kind == ''Service'')\n                            ? has(self.port) : true'\n                      maxItems: 16\n                      minItems: 1\n                      type: array\n                  type: object\n                maxItems: 16\n                minItems: 1\n                type: array\n            required:\n            - rules\n            type: object\n          status:\n            description: Status defines the current state of TCPRoute.\n            properties:\n              parents:\n                description: |-\n                  Parents is a list of parent resources (usually Gateways) that are\n                  associated with the route, and the status of the route with respect to\n                  each parent. When this route attaches to a parent, the controller that\n                  manages the parent must add an entry to this list when the controller\n                  first sees the route and should update the entry as appropriate when the\n                  route or gateway is modified.\n\n\n                  Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this API\n                  can only populate Route status for the Gateways/parent resources they are\n                  responsible for.\n\n\n                  A maximum of 32 Gateways will be represented in this list. An empty list\n                  means the route has not been attached to any Gateway.\n                items:\n                  description: |-\n                    RouteParentStatus describes the status of a route with respect to an\n                    associated Parent.\n                  properties:\n                    conditions:\n                      description: |-\n                        Conditions describes the status of the route with respect to the Gateway.\n                        Note that the route's availability is also subject to the Gateway's own\n                        status conditions and listener status.\n\n\n                        If the Route's ParentRef specifies an existing Gateway that supports\n                        Routes of this kind AND that Gateway's controller has sufficient access,\n                        then that Gateway's controller MUST set the \"Accepted\" condition on the\n                        Route, to indicate whether the route has been accepted or rejected by the\n                        Gateway, and why.\n\n\n                        A Route MUST be considered \"Accepted\" if at least one of the Route's\n                        rules is implemented by the Gateway.\n\n\n                        There are a number of cases where the \"Accepted\" condition may not be set\n                        due to lack of controller visibility, that includes when:\n\n\n                        * The Route refers to a non-existent parent.\n                        * The Route is of a type that the controller does not support.\n                        * The Route is in a namespace the controller does not have access to.\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource.\\n---\\nThis struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example,\\n\\n\\n\\ttype FooStatus\n                          struct{\\n\\t    // Represents the observations of a foo's\n                          current state.\\n\\t    // Known .status.conditions.type are:\n                          \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\"\\n\\t    //\n                          +patchMergeKey=type\\n\\t    // +patchStrategy=merge\\n\\t    //\n                          +listType=map\\n\\t    // +listMapKey=type\\n\\t    Conditions\n                          []metav1.Condition `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\"\n                          patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\\n\\n\\n\\t\n                          \\   // other fields\\n\\t}\"\n                        properties:\n                          lastTransitionTime:\n                            description: |-\n                              lastTransitionTime is the last time the condition transitioned from one status to another.\n                              This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: |-\n                              message is a human readable message indicating details about the transition.\n                              This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: |-\n                              observedGeneration represents the .metadata.generation that the condition was set based upon.\n                              For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                              with respect to the current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: |-\n                              reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected values and meanings for this field,\n                              and whether the values are considered a guaranteed API.\n                              The value should be a CamelCase string.\n                              This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: |-\n                              type of condition in CamelCase or in foo.example.com/CamelCase.\n                              ---\n                              Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be\n                              useful (see .node.status.conditions), the ability to deconflict is important.\n                              The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: |-\n                        ControllerName is a domain/path string that indicates the name of the\n                        controller that wrote this status. This corresponds with the\n                        controllerName field on GatewayClass.\n\n\n                        Example: \"example.net/gateway-controller\".\n\n\n                        The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are\n                        valid Kubernetes names\n                        (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\n\n                        Controllers MUST populate this field when writing status. Controllers should ensure that\n                        entries to status populated with their ControllerName are cleaned up when they are no\n                        longer necessary.\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: |-\n                        ParentRef corresponds with a ParentRef in the spec that this\n                        RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: gateway.networking.k8s.io\n                          description: |-\n                            Group is the group of the referent.\n                            When unspecified, \"gateway.networking.k8s.io\" is inferred.\n                            To set the core API group (such as for a \"Service\" kind referent),\n                            Group must be explicitly set to \"\" (empty string).\n\n\n                            Support: Core\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: |-\n                            Kind is kind of the referent.\n\n\n                            There are two kinds of parent resources with \"Core\" support:\n\n\n                            * Gateway (Gateway conformance profile)\n                            * Service (Mesh conformance profile, ClusterIP Services only)\n\n\n                            Support for other resources is Implementation-Specific.\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: |-\n                            Name is the name of the referent.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: |-\n                            Namespace is the namespace of the referent. When unspecified, this refers\n                            to the local namespace of the Route.\n\n\n                            Note that there are specific rules for ParentRefs which cross namespace\n                            boundaries. Cross-namespace references are only valid if they are explicitly\n                            allowed by something in the namespace they are referring to. For example:\n                            Gateway has the AllowedRoutes field, and ReferenceGrant provides a\n                            generic way to enable any other kind of cross-namespace reference.\n\n\n\n                            ParentRefs from a Route to a Service in the same namespace are \"producer\"\n                            routes, which apply default routing rules to inbound connections from\n                            any namespace to the Service.\n\n\n                            ParentRefs from a Route to a Service in a different namespace are\n                            \"consumer\" routes, and these routing rules are only applied to outbound\n                            connections originating from the same namespace as the Route, for which\n                            the intended destination of the connections are a Service targeted as a\n                            ParentRef of the Route.\n\n\n\n                            Support: Core\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: |-\n                            Port is the network port this Route targets. It can be interpreted\n                            differently based on the type of parent resource.\n\n\n                            When the parent resource is a Gateway, this targets all listeners\n                            listening on the specified port that also support this kind of Route(and\n                            select this Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to a specific port\n                            as opposed to a listener(s) whose port(s) may be changed. When both Port\n                            and SectionName are specified, the name and port of the selected listener\n                            must match both specified values.\n\n\n\n                            When the parent resource is a Service, this targets a specific port in the\n                            Service spec. When both Port (experimental) and SectionName are specified,\n                            the name and port of the selected port must match both specified values.\n\n\n\n                            Implementations MAY choose to support other parent resources.\n                            Implementations supporting other types of parent resources MUST clearly\n                            document how/if Port is interpreted.\n\n\n                            For the purpose of status, an attachment is considered successful as\n                            long as the parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment\n                            from the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n\n\n                            Support: Extended\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: |-\n                            SectionName is the name of a section within the target resource. In the\n                            following resources, SectionName is interpreted as the following:\n\n\n                            * Gateway: Listener name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n                            * Service: Port name. When both Port (experimental) and SectionName\n                            are specified, the name and port of the selected listener must match\n                            both specified values.\n\n\n                            Implementations MAY choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName is\n                            interpreted.\n\n\n                            When unspecified (empty string), this will reference the entire resource.\n                            For the purpose of status, an attachment is considered successful if at\n                            least one section in the parent resource accepts it. For example, Gateway\n                            listeners can restrict which Routes can attach to them by Route kind,\n                            namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from\n                            the referencing Route, the Route MUST be considered successfully\n                            attached. If no Gateway listeners accept attachment from this Route, the\n                            Route MUST be considered detached from the Gateway.\n\n\n                            Support: Core\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: null\n  storedVersions: null\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: externalworkloads.workload.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: workload.linkerd.io\n  names:\n    categories:\n    - external\n    kind: ExternalWorkload\n    listKind: ExternalWorkloadList\n    plural: externalworkloads\n    singular: externalworkload\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTls:\n                description: meshTls describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                # TODO: relax this in the future when ipv6 is supported\n                # an external workload (like a pod) should only\n                # support 2 interfaces\n                maxItems: 1\n            type: object\n            required:\n            - meshTls\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTls.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n  - name: v1beta1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTLS:\n                description: meshTLS describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload. This field may\n                  hold a maximum of two entries. If one entry, it can be an\n                  IPv4 or IPv6 address; if two entries it should contain one\n                  IPv4 address and one IPv6 address.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                maxItems: 2\n            type: object\n            required:\n            - meshTLS\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTLS.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n"
  },
  {
    "path": "cli/cmd/testdata/upgrade_crds_without_gateway_api.golden",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: authorizationpolicies.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: AuthorizationPolicy\n    plural: authorizationpolicies\n    singular: authorizationpolicy\n    shortNames: [authzpolicy]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied server\n                resources.\n              type: object\n              required: [targetRef, requiredAuthenticationRefs]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the authorization\n                    policy applies.\n                  type: object\n                  required: [kind, name]\n                  # Modified from the gateway API.\n                  # Copyright 2020 The Kubernetes Authors\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: >-\n                        Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                requiredAuthenticationRefs:\n                  description: >-\n                    RequiredAuthenticationRefs enumerates a set of required\n                    authentications. ALL authentications must be satisfied for\n                    the authorization to apply. If any of the referred objects\n                    cannot be found, the authorization will be ignored.\n                  type: array\n                  items:\n                    type: object\n                    required: [kind, name]\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: egressnetworks.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    categories:\n    - policy\n    kind: EgressNetwork\n    listKind: EgressNetworkList\n    plural: egressnetworks\n    singular: egressnetwork\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An EgressNetwork captures traffic to egress destinations\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              trafficPolicy:\n                description: >-\n                  This field controls the traffic policy enforced upon traffic\n                  that does not match any explicit route resources associated\n                  with an instance of this object. The values that are allowed\n                  currently are:\n                   - Allow - permits all traffic, even if it has not been\n                                explicitly described via attaching an xRoute\n                                resources.\n                   - Deny -  blocks all traffic that has not been described via\n                                attaching an xRoute resource.\n                type: string\n                enum:\n                - Allow\n                - Deny\n              networks:\n                type: array\n                items:\n                  type: object\n                  required: [cidr]\n                  properties:\n                    cidr:\n                      description: >-\n                        The CIDR of the network to be authorized.\n                      type: string\n                    except:\n                      description: >-\n                        A list of IP networks/addresses not to be included in\n                        the above `cidr`.\n                      type: array\n                      items:\n                        type: string\n            type: object\n            required:\n            - trafficPolicy\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httplocalratelimitpolicies.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPLocalRateLimitPolicy\n    listKind: HTTPLocalRateLimitPolicyList\n    plural: httplocalratelimitpolicies\n    singular: httplocalratelimitpolicy\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      subresources:\n        status: {}\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required: [targetRef]\n              properties:\n                targetRef:\n                  description: >-\n                    TargetRef references a resource to which the rate limit\n                    policy applies. Only Server is allowed.\n                  type: object\n                  required: [kind, name]\n                  properties:\n                    group:\n                      description: >-\n                        Group is the group of the referent. When empty, the\n                        Kubernetes core API group is inferred.\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      description: Kind is the kind of the referent.\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: Name is the name of the referent.\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                total:\n                  description: >-\n                    Overall rate-limit, which all traffic coming to this\n                    target should abide.\n                    If unset no overall limit is applied.\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                identity:\n                  description: >-\n                    Fairness for individual identities; each separate client,\n                    grouped by identity, will have this rate-limit. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: object\n                  required: [requestsPerSecond]\n                  properties:\n                    requestsPerSecond:\n                      format: int64\n                      type: integer\n                overrides:\n                  description: >-\n                    Overrides for traffic from a specific client. The\n                    requestsPerSecond value should be less than or equal to the\n                    total requestsPerSecond (if set).\n                  type: array\n                  items:\n                    type: object\n                    required: [requestsPerSecond, clientRefs]\n                    properties:\n                      requestsPerSecond:\n                        format: int64\n                        type: integer\n                      clientRefs:\n                        type: array\n                        items:\n                          type: object\n                          required: [kind, name]\n                          properties:\n                            group:\n                              description: >-\n                                Group is the group of the referent. When empty, the\n                                Kubernetes core API group is inferred.\n                              maxLength: 253\n                              pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                              type: string\n                            kind:\n                              description: Kind is the kind of the referent.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                              type: string\n                            namespace:\n                              description: >-\n                                  Namespace is the namespace of the referent.\n                                  When unspecified (or empty string), this refers to the\n                                  local namespace of the Policy.\n                              maxLength: 63\n                              minLength: 1\n                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                              type: string\n                            name:\n                              description: Name is the name of the referent.\n                              maxLength: 253\n                              minLength: 1\n                              type: string\n            status:\n              type: object\n              properties:\n                conditions:\n                  type: array\n                  items:\n                    type: object\n                    properties:\n                      lastTransitionTime:\n                        description: lastTransitionTime is the last time the\n                          condition transitioned from one status to another.\n                        format: date-time\n                        type: string\n                      status:\n                        description: status of the condition (one of True, False, Unknown)\n                        enum:\n                        - \"True\"\n                        - \"False\"\n                        - Unknown\n                        type: string\n                      type:\n                        description: type of the condition in CamelCase or in\n                          foo.example.com/CamelCase.\n                        maxLength: 316\n                        pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                        type: string\n                      reason:\n                        description: reason contains a programmatic identifier\n                          indicating the reason for the condition's last\n                          transition. Producers of specific condition types may\n                          define expected values and meanings for this field, and\n                          whether the values are considered a guaranteed API. The\n                          value should be a CamelCase string. This field may not\n                          be empty.\n                        maxLength: 1024\n                        minLength: 1\n                        pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                        type: string\n                      message:\n                        description: message is a human readable message\n                          indicating details about the transition. This may be an\n                          empty string.\n                        maxLength: 32768\n                        type: string\n                  required:\n                  - status\n                  - type\n                targetRef:\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Server\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                  required:\n                  - name\n                  type: object\n              required:\n              - targetRef\n      additionalPrinterColumns:\n      - name: Target_kind\n        description: The resource kind to which the rate-limit applies\n        type: string\n        jsonPath: .spec.targetRef.kind\n      - name: Target_name\n        type: string\n        description: The resource name to which the rate-limit applies\n        jsonPath: .spec.targetRef.name\n      - name: Total_RPS\n        description: The overall rate-limit\n        type: integer\n        format: int32\n        jsonPath: .spec.total.requestsPerSecond\n      - name: Identity_RPS\n        description: The rate-limit per identity\n        type: integer\n        format: int32\n        jsonPath: .spec.identity.requestsPerSecond\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httproutes.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: HTTPRoute\n    listKind: HTTPRouteList\n    plural: httproutes\n    singular: httproute\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\n                              All   implementations must support core filters. \\n\\n \"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"port\"\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      type: array\n                      items:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          port:\n                            type: integer\n                          namespace:\n                            type: string\n                            default: \"default\"\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta2\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .spec.hostnames\n      name: Hostnames\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1beta3\n    schema:\n      openAPIV3Schema:\n        description: HTTPRoute provides a way to route HTTP requests. This includes\n          the capability to match requests by hostname, path, header, or query param.\n          Filters can be used to specify additional processing steps. Backends specify\n          where matching requests should be routed.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Spec defines the desired state of HTTPRoute.\n            properties:\n              hostnames:\n                description: \"Hostnames defines a set of hostname that should match\n                  against the HTTP Host header to select a HTTPRoute to process the\n                  request. This matches the RFC 1123 definition of a hostname with\n                  2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname may\n                  be prefixed with a wildcard label (`*.`). The wildcard    label\n                  must appear by itself as the first label. \\n If a hostname is specified\n                  by both the Listener and HTTPRoute, there must be at least one intersecting\n                  hostname for the HTTPRoute to be attached to the Listener. For example:\n                  \\n * A Listener with `test.example.com` as the hostname matches\n                  HTTPRoutes   that have either not specified any hostnames, or have\n                  specified at   least one of `test.example.com` or `*.example.com`.\n                  * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n                  \\  that have either not specified any hostnames or have specified\n                  at least   one hostname that matches the Listener hostname. For\n                  example,   `*.example.com`, `test.example.com`, and `foo.test.example.com`\n                  would   all match. On the other hand, `example.com` and `test.example.net`\n                  would   not match. \\n Hostnames that are prefixed with a wildcard\n                  label (`*.`) are interpreted as a suffix match. That means that\n                  a match for `*.example.com` would match both `test.example.com`,\n                  and `foo.test.example.com`, but not `example.com`. \\n If both the\n                  Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames\n                  that do not match the Listener hostname MUST be ignored. For example,\n                  if a Listener specified `*.example.com`, and the HTTPRoute specified\n                  `test.example.com` and `test.example.net`, `test.example.net` must\n                  not be considered for a match. \\n If both the Listener and HTTPRoute\n                  have specified hostnames, and none match with the criteria above,\n                  then the HTTPRoute is not accepted. The implementation must raise\n                  an 'Accepted' Condition with a status of `False` in the corresponding\n                  RouteParentStatus. \\n Support: Core\"\n                items:\n                  description: \"Hostname is the fully qualified domain name of a network\n                    host. This matches the RFC 1123 definition of a hostname with\n                    2 notable exceptions: \\n 1. IPs are not allowed. 2. A hostname\n                    may be prefixed with a wildcard label (`*.`). The wildcard    label\n                    must appear by itself as the first label. \\n Hostname can be \\\"precise\\\"\n                    which is a domain name without the terminating dot of a network\n                    host (e.g. \\\"foo.example.com\\\") or \\\"wildcard\\\", which is a domain\n                    name prefixed with a single wildcard label (e.g. `*.example.com`).\n                    \\n Note that as per RFC1035 and RFC1123, a *label* must consist\n                    of lower case alphanumeric characters or '-', and must start and\n                    end with an alphanumeric character. No other punctuation is allowed.\"\n                  maxLength: 253\n                  minLength: 1\n                  pattern: ^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                  type: string\n                maxItems: 16\n                type: array\n              parentRefs:\n                description: \"ParentRefs references the resources (usually Gateways)\n                  that a Route wants to be attached to. Note that the referenced parent\n                  resource needs to allow this for the attachment to be complete.\n                  For Gateways, that means the Gateway needs to allow attachment from\n                  Routes of this kind and namespace. \\n The only kind of parent resource\n                  with \\\"Core\\\" support is Gateway. This API may be extended in the\n                  future to support additional kinds of parent resources such as one\n                  of the route kinds. \\n It is invalid to reference an identical parent\n                  more than once. It is valid to reference multiple distinct sections\n                  within the same parent resource, such as 2 Listeners within a Gateway.\n                  \\n It is possible to separately reference multiple distinct objects\n                  that may be collapsed by an implementation. For example, some implementations\n                  may choose to merge compatible Gateway Listeners together. If that\n                  is the case, the list of routes attached to those resources should\n                  also be merged.\"\n                items:\n                  description: \"ParentReference identifies an API object (usually\n                    a Gateway) that can be considered a parent of this resource (usually\n                    a route). The only kind of parent resource with \\\"Core\\\" support\n                    is Gateway. This API may be extended in the future to support\n                    additional kinds of parent resources, such as HTTPRoute. \\n The\n                    API object must be valid in the cluster; the Group and Kind must\n                    be registered in the cluster for this reference to be valid.\"\n                  properties:\n                    group:\n                      default: policy.linkerd.io\n                      description: \"Group is the group of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                    kind:\n                      default: Gateway\n                      description: \"Kind is kind of the referent. \\n Support: Core\n                        (Gateway) Support: Custom (Other Resources)\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                      type: string\n                    name:\n                      description: \"Name is the name of the referent. \\n Support:\n                        Core\"\n                      maxLength: 253\n                      minLength: 1\n                      type: string\n                    namespace:\n                      description: \"Namespace is the namespace of the referent. When\n                        unspecified (or empty string), this refers to the local namespace\n                        of the Route. \\n Support: Core\"\n                      maxLength: 63\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                      type: string\n                    port:\n                      description: \"Port specifies the destination\n                        port number to use for this resource.\n                        Port is required when the referent is\n                        a Kubernetes Service. In this case, the\n                        port number is the service port number,\n                        not the target port. For other resources,\n                        destination port might be derived from\n                        the referent resource or this field. \\n Support: Extended\"\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    sectionName:\n                      description: \"SectionName is the name of a section within the\n                        target resource. In the following resources, SectionName is\n                        interpreted as the following: \\n * Gateway: Listener Name.\n                        When both Port (experimental) and SectionName are specified,\n                        the name and port of the selected listener must match both\n                        specified values. \\n Implementations MAY choose to support\n                        attaching Routes to other resources. If that is the case,\n                        they MUST clearly document how SectionName is interpreted.\n                        \\n When unspecified (empty string), this will reference the\n                        entire resource. For the purpose of status, an attachment\n                        is considered successful if at least one section in the parent\n                        resource accepts it. For example, Gateway listeners can restrict\n                        which Routes can attach to them by Route kind, namespace,\n                        or hostname. If 1 of 2 Gateway listeners accept attachment\n                        from the referencing Route, the Route MUST be considered successfully\n                        attached. If no Gateway listeners accept attachment from this\n                        Route, the Route MUST be considered detached from the Gateway.\n                        \\n Support: Core\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                      type: string\n                  required:\n                  - name\n                  type: object\n                maxItems: 32\n                type: array\n              rules:\n                default:\n                - matches:\n                  - path:\n                      type: PathPrefix\n                      value: /\n                description: Rules are a list of HTTP matchers, filters and actions.\n                items:\n                  description: HTTPRouteRule defines semantics for matching an HTTP\n                    request based on conditions (matches) and processing it (filters).\n                  properties:\n                    backendRefs:\n                      description: \"BackendRefs defines the backend(s) where matching\n                        requests should be sent. \\n Failure behavior here depends\n                        on how many BackendRefs are specified and how many are invalid.\n                        \\n If *all* entries in BackendRefs are invalid, and there\n                        are also no filters specified in this route rule, *all* traffic\n                        which matches this rule MUST receive a 500 status code. \\n\n                        See the HTTPBackendRef definition for the rules about what\n                        makes a single HTTPBackendRef invalid. \\n When a HTTPBackendRef\n                        is invalid, 500 status codes MUST be returned for requests\n                        that would have otherwise been routed to an invalid backend.\n                        If multiple backends are specified, and some are invalid,\n                        the proportion of requests that would otherwise have been\n                        routed to an invalid backend MUST receive a 500 status code.\n                        \\n For example, if two backends are specified with equal weights,\n                        and one is invalid, 50 percent of traffic must receive a 500.\n                        Implementations may choose how that 50 percent is determined.\n                        \\n Support: Core for Kubernetes Service \\n Support: Implementation-specific\n                        for any other resource \\n Support for weight: Core\"\n                      items:\n                        description: HTTPBackendRef defines how a HTTPRoute should\n                          forward an HTTP request.\n                        properties:\n                          group:\n                            default: \"\"\n                            description: Group is the group of the referent. For example,\n                              \"gateway.networking.k8s.io\". When unspecified or empty\n                              string, core API group is inferred.\n                            maxLength: 253\n                            pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                            type: string\n                          kind:\n                            default: Service\n                            description: Kind is kind of the referent. For example\n                              \"HTTPRoute\" or \"Service\". Defaults to \"Service\" when\n                              not specified.\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                            type: string\n                          name:\n                            description: Name is the name of the referent.\n                            maxLength: 253\n                            minLength: 1\n                            type: string\n                          namespace:\n                            description: \"Namespace is the namespace of the backend.\n                              When unspecified, the local namespace is inferred. \\n\n                              Note that when a namespace is specified, a ReferenceGrant\n                              object is required in the referent namespace to allow\n                              that namespace's owner to accept the reference. See\n                              the ReferenceGrant documentation for details. \\n Support:\n                              Core\"\n                            maxLength: 63\n                            minLength: 1\n                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                            type: string\n                          port:\n                            description: Port specifies the destination port number\n                              to use for this resource. Port is required when the\n                              referent is a Kubernetes Service. In this case, the\n                              port number is the service port number, not the target\n                              port. For other resources, destination port might be\n                              derived from the referent resource or this field.\n                            format: int32\n                            maximum: 65535\n                            minimum: 1\n                            type: integer\n                          weight:\n                            default: 1\n                            description: \"Weight specifies the proportion of requests\n                              forwarded to the referenced backend. This is computed\n                              as weight/(sum of all weights in this BackendRefs list).\n                              For non-zero values, there may be some epsilon from\n                              the exact proportion defined here depending on the precision\n                              an implementation supports. Weight is not a percentage\n                              and the sum of weights does not need to equal 100. \\n\n                              If only one backend is specified and it has a weight\n                              greater than 0, 100% of the traffic is forwarded to\n                              that backend. If weight is set to 0, no traffic should\n                              be forwarded for this entry. If unspecified, weight\n                              defaults to 1. \\n Support for this field varies based\n                              on the context where used.\"\n                            format: int32\n                            maximum: 1000000\n                            minimum: 0\n                            type: integer\n                          filters:\n                            description: \"Filters defined at this level should be\n                              executed if and only if the request is being forwarded\n                              to the backend defined here. \\n Support: Implementation-specific\n                              (For broader support of filters, use the Filters field\n                              in HTTPRouteRule.)\"\n                            items:\n                              description: HTTPRouteFilter defines processing steps\n                                that must be completed during the request or response\n                                lifecycle. HTTPRouteFilters are meant as an extension\n                                point to express processing that may be done in Gateway\n                                implementations. Some examples include request or\n                                response modification, implementing authentication\n                                strategies, rate-limiting, and traffic shaping. API\n                                guarantee/conformance is defined based on the type\n                                of the filter.\n                              properties:\n                                requestHeaderModifier:\n                                  description: \"RequestHeaderModifier defines a schema\n                                    for a filter that modifies request headers. \\n\n                                    Support: Core\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                requestRedirect:\n                                  description: \"RequestRedirect defines a schema for\n                                    a filter that responds to the request with an\n                                    HTTP redirection. \\n Support: Core\"\n                                  properties:\n                                    hostname:\n                                      description: \"Hostname is the hostname to be\n                                        used in the value of the `Location` header\n                                        in the response. When empty, the hostname\n                                        in the `Host` header of the request is used.\n                                        \\n Support: Core\"\n                                      maxLength: 253\n                                      minLength: 1\n                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                      type: string\n                                    path:\n                                      description: \"Path defines parameters used to\n                                        modify the path of the incoming request. The\n                                        modified path is then used to construct the\n                                        `Location` header. When empty, the request\n                                        path is used as-is. \\n Support: Extended\"\n                                      properties:\n                                        replaceFullPath:\n                                          description: ReplaceFullPath specifies the\n                                            value with which to replace the full path\n                                            of a request during a rewrite or redirect.\n                                          maxLength: 1024\n                                          type: string\n                                        replacePrefixMatch:\n                                          description: \"ReplacePrefixMatch specifies\n                                            the value with which to replace the prefix\n                                            match of a request during a rewrite or\n                                            redirect. For example, a request to \\\"/foo/bar\\\"\n                                            with a prefix match of \\\"/foo\\\" and a\n                                            ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                            modified to \\\"/xyz/bar\\\". \\n Note that\n                                            this matches the behavior of the PathPrefix\n                                            match type. This matches full path elements.\n                                            A path element refers to the list of labels\n                                            in the path split by the `/` separator.\n                                            When specified, a trailing `/` is ignored.\n                                            For example, the paths `/abc`, `/abc/`,\n                                            and `/abc/def` would all match the prefix\n                                            `/abc`, but the path `/abcd` would not.\n                                            \\n Request Path | Prefix Match | Replace\n                                            Prefix | Modified Path -------------|--------------|----------------|----------\n                                            /foo/bar     | /foo         | /xyz           |\n                                            /xyz/bar /foo/bar     | /foo         |\n                                            /xyz/          | /xyz/bar /foo/bar     |\n                                            /foo/        | /xyz           | /xyz/bar\n                                            /foo/bar     | /foo/        | /xyz/          |\n                                            /xyz/bar /foo         | /foo         |\n                                            /xyz           | /xyz /foo/        | /foo\n                                            \\        | /xyz           | /xyz/ /foo/bar\n                                            \\    | /foo         | <empty string> |\n                                            /bar /foo/        | /foo         | <empty\n                                            string> | / /foo         | /foo         |\n                                            <empty string> | / /foo/        | /foo\n                                            \\        | /              | / /foo         |\n                                            /foo         | /              | /\"\n                                          maxLength: 1024\n                                          type: string\n                                        type:\n                                          description: \"Type defines the type of path\n                                            modifier. Additional types may be added\n                                            in a future release of the API. \\n Note\n                                            that values may be added to this enum,\n                                            implementations must ensure that unknown\n                                            values will not cause a crash. \\n Unknown\n                                            values here must result in the implementation\n                                            setting the Accepted Condition for the\n                                            Route to `status: False`, with a Reason\n                                            of `UnsupportedValue`.\"\n                                          enum:\n                                          - ReplaceFullPath\n                                          - ReplacePrefixMatch\n                                          type: string\n                                      required:\n                                      - type\n                                      type: object\n                                    port:\n                                      description: \"Port is the port to be used in\n                                        the value of the `Location` header in the\n                                        response. \\n If no port is specified, the\n                                        redirect port MUST be derived using the following\n                                        rules: \\n * If redirect scheme is not-empty,\n                                        the redirect port MUST be the well-known port\n                                        associated with the redirect scheme. Specifically\n                                        \\\"http\\\" to port 80 and \\\"https\\\" to port\n                                        443. If the redirect scheme does not have\n                                        a well-known port, the listener port of the\n                                        Gateway SHOULD be used. * If redirect scheme\n                                        is empty, the redirect port MUST be the Gateway\n                                        Listener port. \\n Implementations SHOULD NOT\n                                        add the port number in the 'Location' header\n                                        in the following cases: \\n * A Location header\n                                        that will use HTTP (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 80. * A Location header that\n                                        will use HTTPS (whether that is determined\n                                        via the Listener protocol or the Scheme field)\n                                        _and_ use port 443. \\n Support: Extended\"\n                                      format: int32\n                                      maximum: 65535\n                                      minimum: 1\n                                      type: integer\n                                    scheme:\n                                      description: \"Scheme is the scheme to be used\n                                        in the value of the `Location` header in the\n                                        response. When empty, the scheme of the request\n                                        is used. \\n Scheme redirects can affect the\n                                        port of the redirect, for more information,\n                                        refer to the documentation for the port field\n                                        of this filter. \\n Note that values may be\n                                        added to this enum, implementations must ensure\n                                        that unknown values will not cause a crash.\n                                        \\n Unknown values here must result in the\n                                        implementation setting the Accepted Condition\n                                        for the Route to `status: False`, with a Reason\n                                        of `UnsupportedValue`. \\n Support: Extended\"\n                                      enum:\n                                      - http\n                                      - https\n                                      type: string\n                                    statusCode:\n                                      default: 302\n                                      description: \"StatusCode is the HTTP status\n                                        code to be used in response. \\n Note that\n                                        values may be added to this enum, implementations\n                                        must ensure that unknown values will not cause\n                                        a crash. \\n Unknown values here must result\n                                        in the implementation setting the Accepted\n                                        Condition for the Route to `status: False`,\n                                        with a Reason of `UnsupportedValue`. \\n Support:\n                                        Core\"\n                                      enum:\n                                      - 301\n                                      - 302\n                                      type: integer\n                                  type: object\n                                responseHeaderModifier:\n                                  description: \"ResponseHeaderModifier defines a schema\n                                    for a filter that modifies response headers. \\n\n                                    Support: Extended\"\n                                  properties:\n                                    add:\n                                      description: \"Add adds the given header(s) (name,\n                                        value) to the request before the action. It\n                                        appends to any existing values associated\n                                        with the header name. \\n Input: GET /foo HTTP/1.1\n                                        my-header: foo \\n Config: add: - name: \\\"my-header\\\"\n                                        value: \\\"bar,baz\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: foo,bar,baz\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    remove:\n                                      description: \"Remove the given header(s) from\n                                        the HTTP request before the action. The value\n                                        of Remove is a list of HTTP header names.\n                                        Note that the header names are case-insensitive\n                                        (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                        \\n Input: GET /foo HTTP/1.1 my-header1: foo\n                                        my-header2: bar my-header3: baz \\n Config:\n                                        remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n\n                                        Output: GET /foo HTTP/1.1 my-header2: bar\"\n                                      items:\n                                        type: string\n                                      maxItems: 16\n                                      type: array\n                                    set:\n                                      description: \"Set overwrites the request with\n                                        the given header (name, value) before the\n                                        action. \\n Input: GET /foo HTTP/1.1 my-header:\n                                        foo \\n Config: set: - name: \\\"my-header\\\"\n                                        value: \\\"bar\\\" \\n Output: GET /foo HTTP/1.1\n                                        my-header: bar\"\n                                      items:\n                                        description: HTTPHeader represents an HTTP\n                                          Header name and value as defined by RFC\n                                          7230.\n                                        properties:\n                                          name:\n                                            description: \"Name is the name of the\n                                              HTTP Header to be matched. Name matching\n                                              MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                              \\n If multiple entries specify equivalent\n                                              header names, the first entry with an\n                                              equivalent name MUST be considered for\n                                              a match. Subsequent entries with an\n                                              equivalent header name MUST be ignored.\n                                              Due to the case-insensitivity of header\n                                              names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                              equivalent.\"\n                                            maxLength: 256\n                                            minLength: 1\n                                            pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                            type: string\n                                          value:\n                                            description: Value is the value of HTTP\n                                              Header to be matched.\n                                            maxLength: 4096\n                                            minLength: 1\n                                            type: string\n                                        required:\n                                        - name\n                                        - value\n                                        type: object\n                                      maxItems: 16\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                  type: object\n                                type:\n                                  description: \"Type identifies the type of filter\n                                    to apply. As with other API fields, types are\n                                    classified into three conformance levels: \\n -\n                                    Core: Filter types and their corresponding configuration\n                                    defined by \\\"Support: Core\\\" in this package,\n                                    e.g. \\\"RequestHeaderModifier\\\". All implementations\n                                    must support core filters. \\n - Extended: Filter\n                                    types and their corresponding configuration defined\n                                    by \\\"Support: Extended\\\" in this package, e.g.\n                                    \\\"RequestMirror\\\". Implementers are encouraged\n                                    to support extended filters. \\n - Implementation-specific:\n                                    Filters that are defined and supported by specific\n                                    vendors. In the future, filters showing convergence\n                                    in behavior across multiple implementations will\n                                    be considered for inclusion in extended or core\n                                    conformance levels. Filter-specific configuration\n                                    for such filters is specified using the ExtensionRef\n                                    field. `Type` should be set to \\\"ExtensionRef\\\"\n                                    for custom filters. \\n Implementers are encouraged\n                                    to define custom implementation types to extend\n                                    the core API with implementation-specific behavior.\n                                    \\n If a reference to a custom filter type cannot\n                                    be resolved, the filter MUST NOT be skipped. Instead,\n                                    requests that would have been processed by that\n                                    filter MUST receive a HTTP error response. \\n\n                                    Note that values may be added to this enum, implementations\n                                    must ensure that unknown values will not cause\n                                    a crash. \\n Unknown values here must result in\n                                    the implementation setting the Accepted Condition\n                                    for the Route to `status: False`, with a Reason\n                                    of `UnsupportedValue`.\"\n                                  enum:\n                                  - RequestHeaderModifier\n                                  - ResponseHeaderModifier\n                                  - RequestRedirect\n                                  type: string\n                              required:\n                              - type\n                              type: object\n                            maxItems: 16\n                            type: array\n                        required:\n                        - name\n                        type: object\n                      maxItems: 16\n                      type: array\n                    filters:\n                      description: \"Filters define the filters that are applied to\n                        requests that match this rule. \\n The effects of ordering\n                        of multiple behaviors are currently unspecified. This can\n                        change in the future based on feedback during the alpha stage.\n                        \\n Conformance-levels at this level are defined based on the\n                        type of filter: \\n - ALL core filters MUST be supported by\n                        all implementations. - Implementers are encouraged to support\n                        extended filters. - Implementation-specific custom filters\n                        have no API guarantees across   implementations. \\n Specifying\n                        a core filter multiple times has unspecified or custom conformance.\n                        \\n All filters are expected to be compatible with each other\n                        except for the URLRewrite and RequestRedirect filters, which\n                        may not be combined. If an implementation can not support\n                        other combinations of filters, they must clearly document\n                        that limitation. In all cases where incompatible or unsupported\n                        filters are specified, implementations MUST add a warning\n                        condition to status. \\n Support: Core\"\n                      items:\n                        description: HTTPRouteFilter defines processing steps that\n                          must be completed during the request or response lifecycle.\n                          HTTPRouteFilters are meant as an extension point to express\n                          processing that may be done in Gateway implementations.\n                          Some examples include request or response modification,\n                          implementing authentication strategies, rate-limiting, and\n                          traffic shaping. API guarantee/conformance is defined based\n                          on the type of the filter.\n                        properties:\n                          requestHeaderModifier:\n                            description: \"RequestHeaderModifier defines a schema for\n                              a filter that modifies request headers. \\n Support:\n                              Core\"\n                            properties:\n                              add:\n                                description: \"Add adds the given header(s) (name,\n                                  value) to the request before the action. It appends\n                                  to any existing values associated with the header\n                                  name. \\n Input:   GET /foo HTTP/1.1   my-header:\n                                  foo \\n Config:   add:   - name: \\\"my-header\\\"     value:\n                                  \\\"bar\\\" \\n Output:   GET /foo HTTP/1.1   my-header:\n                                  foo   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              remove:\n                                description: \"Remove the given header(s) from the\n                                  HTTP request before the action. The value of Remove\n                                  is a list of HTTP header names. Note that the header\n                                  names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n                                  \\n Input:   GET /foo HTTP/1.1   my-header1: foo\n                                  \\  my-header2: bar   my-header3: baz \\n Config:\n                                  \\  remove: [\\\"my-header1\\\", \\\"my-header3\\\"] \\n Output:\n                                  \\  GET /foo HTTP/1.1   my-header2: bar\"\n                                items:\n                                  type: string\n                                maxItems: 16\n                                type: array\n                              set:\n                                description: \"Set overwrites the request with the\n                                  given header (name, value) before the action. \\n\n                                  Input:   GET /foo HTTP/1.1   my-header: foo \\n Config:\n                                  \\  set:   - name: \\\"my-header\\\"     value: \\\"bar\\\"\n                                  \\n Output:   GET /foo HTTP/1.1   my-header: bar\"\n                                items:\n                                  description: HTTPHeader represents an HTTP Header\n                                    name and value as defined by RFC 7230.\n                                  properties:\n                                    name:\n                                      description: \"Name is the name of the HTTP Header\n                                        to be matched. Name matching MUST be case\n                                        insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                        \\n If multiple entries specify equivalent\n                                        header names, the first entry with an equivalent\n                                        name MUST be considered for a match. Subsequent\n                                        entries with an equivalent header name MUST\n                                        be ignored. Due to the case-insensitivity\n                                        of header names, \\\"foo\\\" and \\\"Foo\\\" are considered\n                                        equivalent.\"\n                                      maxLength: 256\n                                      minLength: 1\n                                      pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                      type: string\n                                    value:\n                                      description: Value is the value of HTTP Header\n                                        to be matched.\n                                      maxLength: 4096\n                                      minLength: 1\n                                      type: string\n                                  required:\n                                  - name\n                                  - value\n                                  type: object\n                                maxItems: 16\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                            type: object\n                          requestRedirect:\n                            description: \"RequestRedirect defines a schema for a filter\n                              that responds to the request with an HTTP redirection.\n                              \\n Support: Core\"\n                            properties:\n                              hostname:\n                                description: \"Hostname is the hostname to be used\n                                  in the value of the `Location` header in the response.\n                                  When empty, the hostname of the request is used.\n                                  \\n Support: Core\"\n                                maxLength: 253\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              path:\n                                description: \"Path defines parameters used to\n                                  modify the path of the incoming request. The\n                                  modified path is then used to construct the\n                                  `Location` header. When empty, the request\n                                  path is used as-is. \\n Support: Extended\"\n                                properties:\n                                  replaceFullPath:\n                                    description: ReplaceFullPath specifies the\n                                      value with which to replace the full path\n                                      of a request during a rewrite or redirect.\n                                    maxLength: 1024\n                                    type: string\n                                  replacePrefixMatch:\n                                    description: \"ReplacePrefixMatch specifies\n                                      the value with which to replace the prefix\n                                      match of a request during a rewrite or\n                                      redirect. For example, a request to \\\"/foo/bar\\\"\n                                      with a prefix match of \\\"/foo\\\" and a\n                                      ReplacePrefixMatch of \\\"/xyz\\\" would be\n                                      modified to \\\"/xyz/bar\\\". \\n Note that\n                                      this matches the behavior of the PathPrefix\n                                      match type. This matches full path elements.\n                                      A path element refers to the list of labels\n                                      in the path split by the `/` separator.\n                                      When specified, a trailing `/` is ignored.\n                                      For example, the paths `/abc`, `/abc/`,\n                                      and `/abc/def` would all match the prefix\n                                      `/abc`, but the path `/abcd` would not.\n                                      \\n Request Path | Prefix Match | Replace\n                                      Prefix | Modified Path -------------|--------------|----------------|----------\n                                      /foo/bar     | /foo         | /xyz           |\n                                      /xyz/bar /foo/bar     | /foo         |\n                                      /xyz/          | /xyz/bar /foo/bar     |\n                                      /foo/        | /xyz           | /xyz/bar\n                                      /foo/bar     | /foo/        | /xyz/          |\n                                      /xyz/bar /foo         | /foo         |\n                                      /xyz           | /xyz /foo/        | /foo\n                                      \\        | /xyz           | /xyz/ /foo/bar\n                                      \\    | /foo         | <empty string> |\n                                      /bar /foo/        | /foo         | <empty\n                                      string> | / /foo         | /foo         |\n                                      <empty string> | / /foo/        | /foo\n                                      \\        | /              | / /foo         |\n                                      /foo         | /              | /\"\n                                    maxLength: 1024\n                                    type: string\n                                  type:\n                                    description: \"Type defines the type of path\n                                      modifier. Additional types may be added\n                                      in a future release of the API. \\n Note\n                                      that values may be added to this enum,\n                                      implementations must ensure that unknown\n                                      values will not cause a crash. \\n Unknown\n                                      values here must result in the implementation\n                                      setting the Accepted Condition for the\n                                      Route to `status: False`, with a Reason\n                                      of `UnsupportedValue`.\"\n                                    enum:\n                                    - ReplaceFullPath\n                                    - ReplacePrefixMatch\n                                    type: string\n                                required:\n                                - type\n                                type: object\n                              port:\n                                description: \"Port is the port to be used in the value\n                                  of the `Location` header in the response. When empty,\n                                  port (if specified) of the request is used. \\n Support:\n                                  Extended\"\n                                format: int32\n                                maximum: 65535\n                                minimum: 1\n                                type: integer\n                              scheme:\n                                description: \"Scheme is the scheme to be used in the\n                                  value of the `Location` header in the response.\n                                  When empty, the scheme of the request is used. \\n\n                                  Support: Extended\"\n                                enum:\n                                - http\n                                - https\n                                type: string\n                              statusCode:\n                                default: 302\n                                description: \"StatusCode is the HTTP status code to\n                                  be used in response. \\n Support: Core\"\n                                enum:\n                                - 301\n                                - 302\n                                type: integer\n                            type: object\n                          type:\n                            description: \"Type identifies the type of filter to apply.\n                              As with other API fields, types are classified into\n                              three conformance levels: \\n - Core: Filter types and\n                              their corresponding configuration defined by   \\\"Support:\n                              Core\\\" in this package, e.g. \\\"RequestHeaderModifier\\\".\"\n                            enum:\n                            - RequestHeaderModifier\n                            - RequestRedirect\n                            type: string\n                        required:\n                        - type\n                        type: object\n                      maxItems: 16\n                      type: array\n                    matches:\n                      default:\n                      - path:\n                          type: PathPrefix\n                          value: /\n                      description: \"Matches define conditions used for matching the\n                        rule against incoming HTTP requests. Each match is independent,\n                        i.e. this rule will be matched if **any** one of the matches\n                        is satisfied. \\n For example, take the following matches configuration:\n                        \\n ``` matches: - path:     value: \\\"/foo\\\"   headers:   -\n                        name: \\\"version\\\"     value: \\\"v2\\\" - path:     value: \\\"/v2/foo\\\"\n                        ``` \\n For a request to match against this rule, a request\n                        must satisfy EITHER of the two conditions: \\n - path prefixed\n                        with `/foo` AND contains the header `version: v2` - path prefix\n                        of `/v2/foo` \\n See the documentation for HTTPRouteMatch on\n                        how to specify multiple match conditions that should be ANDed\n                        together. \\n If no matches are specified, the default is a\n                        prefix path match on \\\"/\\\", which has the effect of matching\n                        every HTTP request. \\n Proxy or Load Balancer routing configuration\n                        generated from HTTPRoutes MUST prioritize rules based on the\n                        following criteria, continuing on ties. Precedence must be\n                        given to the the Rule with the largest number of: \\n * Characters\n                        in a matching non-wildcard hostname. * Characters in a matching\n                        hostname. * Characters in a matching path. * Header matches.\n                        * Query param matches. \\n If ties still exist across multiple\n                        Routes, matching precedence MUST be determined in order of\n                        the following criteria, continuing on ties: \\n * The oldest\n                        Route based on creation timestamp. * The Route appearing first\n                        in alphabetical order by   \\\"{namespace}/{name}\\\". \\n If ties\n                        still exist within the Route that has been given precedence,\n                        matching precedence MUST be granted to the first matching\n                        rule meeting the above criteria. \\n When no rules matching\n                        a request have been successfully attached to the parent a\n                        request is coming from, a HTTP 404 status code MUST be returned.\"\n                      items:\n                        description: \"HTTPRouteMatch defines the predicate used to\n                          match requests to a given action. Multiple match types are\n                          ANDed together, i.e. the match will evaluate to true only\n                          if all conditions are satisfied. \\n For example, the match\n                          below will match a HTTP request only if its path starts\n                          with `/foo` AND it contains the `version: v1` header: \\n\n                          ``` match:   path:     value: \\\"/foo\\\"   headers:   - name:\n                          \\\"version\\\"     value \\\"v1\\\" ```\"\n                        properties:\n                          headers:\n                            description: Headers specifies HTTP request header matchers.\n                              Multiple match values are ANDed together, meaning, a\n                              request must match all the specified headers to select\n                              the route.\n                            items:\n                              description: HTTPHeaderMatch describes how to select\n                                a HTTP route by matching HTTP request headers.\n                              properties:\n                                name:\n                                  description: \"Name is the name of the HTTP Header\n                                    to be matched. Name matching MUST be case insensitive.\n                                    (See https://tools.ietf.org/html/rfc7230#section-3.2).\n                                    \\n If multiple entries specify equivalent header\n                                    names, only the first entry with an equivalent\n                                    name MUST be considered for a match. Subsequent\n                                    entries with an equivalent header name MUST be\n                                    ignored. Due to the case-insensitivity of header\n                                    names, \\\"foo\\\" and \\\"Foo\\\" are considered equivalent.\n                                    \\n When a header is repeated in an HTTP request,\n                                    it is implementation-specific behavior as to how\n                                    this is represented. Generally, proxies should\n                                    follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2\n                                    regarding processing a repeated header, with special\n                                    handling for \\\"Set-Cookie\\\".\"\n                                  maxLength: 256\n                                  minLength: 1\n                                  pattern: ^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the header. \\n Support: Core (Exact)\n                                    \\n Support: Custom (RegularExpression) \\n Since\n                                    RegularExpression HeaderMatchType has custom conformance,\n                                    implementations can support POSIX, PCRE or any\n                                    other dialects of regular expressions. Please\n                                    read the implementation's documentation to determine\n                                    the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP Header to\n                                    be matched.\n                                  maxLength: 4096\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                          method:\n                            description: \"Method specifies HTTP method matcher. When\n                              specified, this route will be matched only if the request\n                              has the specified method. \\n Support: Extended\"\n                            enum:\n                            - GET\n                            - HEAD\n                            - POST\n                            - PUT\n                            - DELETE\n                            - CONNECT\n                            - OPTIONS\n                            - TRACE\n                            - PATCH\n                            type: string\n                          path:\n                            default:\n                              type: PathPrefix\n                              value: /\n                            description: Path specifies a HTTP request path matcher.\n                              If this field is not specified, a default prefix match\n                              on the \"/\" path is provided.\n                            properties:\n                              type:\n                                default: PathPrefix\n                                description: \"Type specifies how to match against\n                                  the path Value. \\n Support: Core (Exact, PathPrefix)\n                                  \\n Support: Custom (RegularExpression)\"\n                                enum:\n                                - Exact\n                                - PathPrefix\n                                - RegularExpression\n                                type: string\n                              value:\n                                default: /\n                                description: Value of the HTTP path to match against.\n                                maxLength: 1024\n                                type: string\n                            type: object\n                          queryParams:\n                            description: QueryParams specifies HTTP query parameter\n                              matchers. Multiple match values are ANDed together,\n                              meaning, a request must match all the specified query\n                              parameters to select the route.\n                            items:\n                              description: HTTPQueryParamMatch describes how to select\n                                a HTTP route by matching HTTP query parameters.\n                              properties:\n                                name:\n                                  description: Name is the name of the HTTP query\n                                    param to be matched. This must be an exact string\n                                    match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n                                  maxLength: 256\n                                  minLength: 1\n                                  type: string\n                                type:\n                                  default: Exact\n                                  description: \"Type specifies how to match against\n                                    the value of the query parameter. \\n Support:\n                                    Extended (Exact) \\n Support: Custom (RegularExpression)\n                                    \\n Since RegularExpression QueryParamMatchType\n                                    has custom conformance, implementations can support\n                                    POSIX, PCRE or any other dialects of regular expressions.\n                                    Please read the implementation's documentation\n                                    to determine the supported dialect.\"\n                                  enum:\n                                  - Exact\n                                  - RegularExpression\n                                  type: string\n                                value:\n                                  description: Value is the value of HTTP query param\n                                    to be matched.\n                                  maxLength: 1024\n                                  minLength: 1\n                                  type: string\n                              required:\n                              - name\n                              - value\n                              type: object\n                            maxItems: 16\n                            type: array\n                            x-kubernetes-list-map-keys:\n                            - name\n                            x-kubernetes-list-type: map\n                        type: object\n                      maxItems: 8\n                      type: array\n                    timeouts:\n                      description: \"Timeouts defines the timeouts that can be configured\n                        for an HTTP request. \\n Support: Core \\n <gateway:experimental>\"\n                      properties:\n                        backendRequest:\n                          description: \"BackendRequest specifies a timeout for an\n                            individual request from the gateway to a backend service.\n                            Typically used in conjunction with automatic retries,\n                            if supported by an implementation. Default is the value\n                            of Request timeout. \\n Support: Extended\"\n                          format: duration\n                          type: string\n                        request:\n                          description: \"Request specifies a timeout for responding\n                            to client HTTP requests, disabled by default. \\n For example,\n                            the following rule will timeout if a client request is\n                            taking longer than 10 seconds to complete: \\n ``` rules:\n                            - timeouts: request: 10s backendRefs: ... ``` \\n Support:\n                            Core\"\n                          format: duration\n                          type: string\n                      type: object\n                  type: object\n                maxItems: 16\n                type: array\n            type: object\n          status:\n            description: Status defines the current state of HTTPRoute.\n            properties:\n              parents:\n                description: \"Parents is a list of parent resources (usually Gateways)\n                  that are associated with the route, and the status of the route\n                  with respect to each parent. When this route attaches to a parent,\n                  the controller that manages the parent must add an entry to this\n                  list when the controller first sees the route and should update\n                  the entry as appropriate when the route or gateway is modified.\n                  \\n Note that parent references that cannot be resolved by an implementation\n                  of this API will not be added to this list. Implementations of this\n                  API can only populate Route status for the Gateways/parent resources\n                  they are responsible for. \\n A maximum of 32 Gateways will be represented\n                  in this list. An empty list means the route has not been attached\n                  to any Gateway.\"\n                items:\n                  description: RouteParentStatus describes the status of a route with\n                    respect to an associated Parent.\n                  properties:\n                    conditions:\n                      description: \"Conditions describes the status of the route with\n                        respect to the Gateway. Note that the route's availability\n                        is also subject to the Gateway's own status conditions and\n                        listener status. \\n If the Route's ParentRef specifies an\n                        existing Gateway that supports Routes of this kind AND that\n                        Gateway's controller has sufficient access, then that Gateway's\n                        controller MUST set the \\\"Accepted\\\" condition on the Route,\n                        to indicate whether the route has been accepted or rejected\n                        by the Gateway, and why. \\n A Route MUST be considered \\\"Accepted\\\"\n                        if at least one of the Route's rules is implemented by the\n                        Gateway. \\n There are a number of cases where the \\\"Accepted\\\"\n                        condition may not be set due to lack of controller visibility,\n                        that includes when: \\n * The Route refers to a non-existent\n                        parent. * The Route is of a type that the controller does\n                        not support. * The Route is in a namespace the the controller\n                        does not have access to.\"\n                      items:\n                        description: \"Condition contains details for one aspect of\n                          the current state of this API Resource. --- This struct\n                          is intended for direct use as an array at the field path\n                          .status.conditions.  For example, type FooStatus struct{\n                          \\    // Represents the observations of a foo's current state.\n                          \\    // Known .status.conditions.type are: \\\"Available\\\",\n                          \\\"Progressing\\\", and \\\"Degraded\\\"     // +patchMergeKey=type\n                          \\    // +patchStrategy=merge     // +listType=map     //\n                          +listMapKey=type     Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                          patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                          \\n     // other fields }\"\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            maxLength: 32768\n                            type: string\n                          observedGeneration:\n                            description: observedGeneration represents the .metadata.generation\n                              that the condition was set based upon. For instance,\n                              if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                              is 9, the condition is out of date with respect to the\n                              current state of the instance.\n                            format: int64\n                            minimum: 0\n                            type: integer\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                      maxItems: 8\n                      minItems: 1\n                      type: array\n                      x-kubernetes-list-map-keys:\n                      - type\n                      x-kubernetes-list-type: map\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    parentRef:\n                      description: ParentRef corresponds with a ParentRef in the spec\n                        that this RouteParentStatus struct describes the status of.\n                      properties:\n                        group:\n                          default: policy.linkerd.io\n                          description: \"Group is the group of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Gateway\n                          description: \"Kind is kind of the referent. \\n Support:\n                            Core (Gateway) Support: Custom (Other Resources)\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent. \\n Support:\n                            Core\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\n                            When unspecified (or empty string), this refers to the\n                            local namespace of the Route. \\n Support: Core\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                        port:\n                          description: \"Port is the network port this Route targets.\n                            It can be interpreted differently based on the type of\n                            parent resource. \\n When the parent resource is a Gateway,\n                            this targets all listeners listening on the specified\n                            port that also support this kind of Route(and select this\n                            Route). It's not recommended to set `Port` unless the\n                            networking behaviors specified in a Route must apply to\n                            a specific port as opposed to a listener(s) whose port(s)\n                            may be changed. When both Port and SectionName are specified,\n                            the name and port of the selected listener must match\n                            both specified values. \\n Implementations MAY choose to\n                            support other parent resources. Implementations supporting\n                            other types of parent resources MUST clearly document\n                            how/if Port is interpreted. \\n For the purpose of status,\n                            an attachment is considered successful as long as the\n                            parent resource accepts it partially. For example, Gateway\n                            listeners can restrict which Routes can attach to them\n                            by Route kind, namespace, or hostname. If 1 of 2 Gateway\n                            listeners accept attachment from the referencing Route,\n                            the Route MUST be considered successfully attached. If\n                            no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Extended \\n <gateway:experimental>\"\n                          format: int32\n                          maximum: 65535\n                          minimum: 1\n                          type: integer\n                        sectionName:\n                          description: \"SectionName is the name of a section within\n                            the target resource. In the following resources, SectionName\n                            is interpreted as the following: \\n * Gateway: Listener\n                            Name. When both Port (experimental) and SectionName are\n                            specified, the name and port of the selected listener\n                            must match both specified values. \\n Implementations MAY\n                            choose to support attaching Routes to other resources.\n                            If that is the case, they MUST clearly document how SectionName\n                            is interpreted. \\n When unspecified (empty string), this\n                            will reference the entire resource. For the purpose of\n                            status, an attachment is considered successful if at least\n                            one section in the parent resource accepts it. For example,\n                            Gateway listeners can restrict which Routes can attach\n                            to them by Route kind, namespace, or hostname. If 1 of\n                            2 Gateway listeners accept attachment from the referencing\n                            Route, the Route MUST be considered successfully attached.\n                            If no Gateway listeners accept attachment from this Route,\n                            the Route MUST be considered detached from the Gateway.\n                            \\n Support: Core\"\n                          maxLength: 253\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                      required:\n                      - name\n                      type: object\n                  required:\n                  - controllerName\n                  - parentRef\n                  type: object\n                maxItems: 32\n                type: array\n            required:\n            - parents\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: meshtlsauthentications.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: MeshTLSAuthentication\n    plural: meshtlsauthentications\n    singular: meshtlsauthentication\n    shortNames: [meshtlsauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                MeshTLSAuthentication defines a list of authenticated client IDs\n                to be referenced by an `AuthorizationPolicy`. If a client\n                connection has the mutually-authenticated identity that matches\n                ANY of the of the provided identities, the connection is\n                considered authenticated.\n              type: object\n              oneOf:\n                - required: [identities]\n                - required: [identityRefs]\n              properties:\n                identities:\n                  description: >-\n                    Authorizes clients with the provided proxy identity strings\n                    (as provided via MTLS)\n\n                    The `*` prefix can be used to match all identities in\n                    a domain. An identity string of `*` indicates that\n                    all authentication clients are authorized.\n                  type: array\n                  minItems: 1\n                  items:\n                    type: string\n                identityRefs:\n                  type: array\n                  minItems: 1\n                  items:\n                    type: object\n                    required:\n                      - kind\n                    properties:\n                      group:\n                        description: >-\n                          Group is the group of the referent. When empty, the\n                          Kubernetes core API group is inferred.\"\n                        maxLength: 253\n                        pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                        type: string\n                      kind:\n                        description: >-\n                          Kind is the kind of the referent.\n                        maxLength: 63\n                        minLength: 1\n                        pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                        type: string\n                      name:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this refers to all resources of the specified Group\n                          and Kind in the specified namespace.\n                        maxLength: 253\n                        minLength: 1\n                        type: string\n                      namespace:\n                        description: >-\n                          Name is the name of the referent. When unspecified,\n                          this authentication refers to the local namespace.\n                        maxLength: 253\n                        type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: networkauthentications.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: NetworkAuthentication\n    plural: networkauthentications\n    singular: networkauthentication\n    shortNames: [netauthn, networkauthn]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                NetworkAuthentication defines a list of authenticated client\n                networks to be referenced by an `AuthorizationPolicy`. If a\n                client connection originates from ANY of the of the provided\n                networks, the connection is considered authenticated.\n              type: object\n              required: [networks]\n              properties:\n                networks:\n                  type: array\n                  items:\n                    type: object\n                    required: [cidr]\n                    properties:\n                      cidr:\n                        description: >-\n                          The CIDR of the network to be authorized.\n                        type: string\n                      except:\n                        description: >-\n                          A list of IP networks/addresses not to be included in\n                          the above `cidr`.\n                        type: array\n                        items:\n                          type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serverauthorizations.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  scope: Namespaced\n  names:\n    kind: ServerAuthorization\n    plural: serverauthorizations\n    singular: serverauthorization\n    shortNames: [saz, serverauthz, srvauthz]\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 ServerAuthorization is deprecated; use policy.linkerd.io/v1beta1 ServerAuthorization\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n    - name: v1beta1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              description: >-\n                Authorizes clients to communicate with Linkerd-proxied servers.\n              type: object\n              required: [server, client]\n              properties:\n                server:\n                  description: >-\n                    Identifies servers in the same namespace for which this\n                    authorization applies.\n\n                    Only one of `name` or `selector` may be specified.\n                  type: object\n                  oneOf:\n                    - required: [name]\n                    - required: [selector]\n                  properties:\n                    name:\n                      description: References a `Server` instance by name\n                      type: string\n                      pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                    selector:\n                      description: >-\n                        A label query over servers on which this authorization applies.\n                      type: object\n                      properties:\n                        matchLabels:\n                          type: object\n                          x-kubernetes-preserve-unknown-fields: true\n                        matchExpressions:\n                          type: array\n                          items:\n                            type: object\n                            required: [key, operator]\n                            properties:\n                              key:\n                                type: string\n                              operator:\n                                type: string\n                                enum: [In, NotIn, Exists, DoesNotExist]\n                              values:\n                                type: array\n                                items:\n                                  type: string\n                client:\n                  description:  Describes clients authorized to access a server.\n                  type: object\n                  properties:\n                    networks:\n                      description: >-\n                        Limits the client IP addresses to which this\n                        authorization applies. If unset, the server chooses a\n                        default (typically, all IPs or the cluster's pod\n                        network).\n                      type: array\n                      items:\n                        type: object\n                        required: [cidr]\n                        properties:\n                          cidr:\n                            type: string\n                          except:\n                            type: array\n                            items:\n                              type: string\n                    unauthenticated:\n                      description: >-\n                        Authorizes unauthenticated clients to access a server.\n                      type: boolean\n                    meshTLS:\n                      type: object\n                      properties:\n                        unauthenticatedTLS:\n                          type: boolean\n                          description: >-\n                            Indicates that no client identity is required for\n                            communication.\n\n                            This is mostly important for the identity\n                            controller, which must terminate TLS connections\n                            from clients that do not yet have a certificate.\n                        identities:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            strings (as provided via MTLS)\n\n                            The `*` prefix can be used to match all identities in\n                            a domain. An identity string of `*` indicates that\n                            all authentication clients are authorized.\n                          type: array\n                          items:\n                            type: string\n                            pattern: '^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'\n                        serviceAccounts:\n                          description: >-\n                            Authorizes clients with the provided proxy identity\n                            service accounts (as provided via MTLS)\n                          type: array\n                          items:\n                            type: object\n                            required: [name]\n                            properties:\n                              name:\n                                description: The ServiceAccount's name.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n                              namespace:\n                                description: >-\n                                  The ServiceAccount's namespace. If unset, the\n                                  authorization's namespace is used.\n                                type: string\n                                pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'\n      additionalPrinterColumns:\n      - name: Server\n        type: string\n        description: The server that this grants access to\n        jsonPath: .spec.server.name\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: servers.policy.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: policy.linkerd.io\n  names:\n    kind: Server\n    plural: servers\n    singular: server\n    shortNames: [srv]\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1alpha1 Server is deprecated; use policy.linkerd.io/v1beta1 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n                  oneOf:\n                    - required: [matchExpressions]\n                    - required: [matchLabels]\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n    - name: v1beta1\n      served: true\n      storage: false\n      deprecated: true\n      deprecationWarning: \"policy.linkerd.io/v1beta1 Server is deprecated; use policy.linkerd.io/v1beta3 Server\"\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - podSelector\n                - port\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta2\n      served: true\n      storage: false\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n    - name: v1beta3\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          required: [spec]\n          properties:\n            spec:\n              type: object\n              required:\n                - port\n              oneOf:\n                - required: [podSelector]\n                - required: [externalWorkloadSelector]\n              properties:\n                accessPolicy:\n                  type: string\n                  default: deny\n                  description: >-\n                    Default access policy to apply when the traffic doesn't match any of the policy rules.\n                podSelector:\n                  type: object\n                  description: >-\n                    Selects pods in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                externalWorkloadSelector:\n                  type: object\n                  description: >-\n                    Selects ExternalWorkloads in the same namespace.\n\n                    The result of matchLabels and matchExpressions are ANDed.\n                    Selects all if empty.\n                  properties:\n                    matchLabels:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    matchExpressions:\n                      type: array\n                      items:\n                        type: object\n                        required: [key, operator]\n                        properties:\n                          key:\n                            type: string\n                          operator:\n                            type: string\n                            enum: [In, NotIn, Exists, DoesNotExist]\n                          values:\n                            type: array\n                            items:\n                              type: string\n                port:\n                  description: >-\n                    A port name or number. Must exist in a pod spec.\n                  x-kubernetes-int-or-string: true\n                proxyProtocol:\n                  description: >-\n                    Configures protocol discovery for inbound connections.\n\n                    Supersedes the `config.linkerd.io/opaque-ports` annotation.\n                  type: string\n                  default: unknown\n      additionalPrinterColumns:\n      - name: Port\n        type: string\n        description: The port the server is listening on\n        jsonPath: .spec.port\n      - name: Protocol\n        type: string\n        description: The protocol of the server\n        jsonPath: .spec.proxyProtocol\n      - name: Access Policy\n        type: string\n        description: The default access policy applied when the traffic doesn't match any of the policy rules\n        jsonPath: .spec.accessPolicy\n---\n###\n### Service Profile CRD\n###\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: serviceprofiles.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: linkerd.io\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            required:\n            - routes\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  - name: v1alpha2\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            description: Spec is the custom resource spec\n            properties:\n              dstOverrides:\n                type: array\n                required:\n                - authority\n                - weight\n                items:\n                  type: object\n                  description: WeightedDst is a weighted alternate destination.\n                  properties:\n                    authority:\n                      type: string\n                    weight:\n                      x-kubernetes-int-or-string: true\n                      anyOf:\n                      - type: integer\n                      - type: string\n                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n              opaquePorts:\n                type: array\n                items:\n                  type: string\n              retryBudget:\n                type: object\n                required:\n                - minRetriesPerSecond\n                - retryRatio\n                - ttl\n                description: RetryBudget describes the maximum number of retries that should be issued to this service.\n                properties:\n                  minRetriesPerSecond:\n                    format: int32\n                    type: integer\n                  retryRatio:\n                    type: number\n                    format: float\n                  ttl:\n                    type: string\n              routes:\n                type: array\n                items:\n                  type: object\n                  description: RouteSpec specifies a Route resource.\n                  required:\n                  - condition\n                  - name\n                  properties:\n                    condition:\n                      type: object\n                      description: RequestMatch describes the conditions under which to match a Route.\n                      properties:\n                        pathRegex:\n                          type: string\n                        method:\n                          type: string\n                        all:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        any:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                        not:\n                          type: array\n                          items:\n                            type: object\n                            x-kubernetes-preserve-unknown-fields: true\n                    isRetryable:\n                      type: boolean\n                    name:\n                      type: string\n                    timeout:\n                      type: string\n                    responseClasses:\n                      type: array\n                      items:\n                        type: object\n                        required:\n                        - condition\n                        description: ResponseClass describes how to classify a response (e.g. success or failures).\n                        properties:\n                          condition:\n                            type: object\n                            description: ResponseMatch describes the conditions under\n                              which to classify a response.\n                            properties:\n                              all:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              any:\n                                type: array\n                                items:\n                                  type: object\n                                  x-kubernetes-preserve-unknown-fields: true\n                              not:\n                                type: object\n                                x-kubernetes-preserve-unknown-fields: true\n                              status:\n                                type: object\n                                description: Range describes a range of integers (e.g. status codes).\n                                properties:\n                                  max:\n                                    format: int32\n                                    type: integer\n                                  min:\n                                    format: int32\n                                    type: integer\n                          isFailure:\n                            type: boolean\n  scope: Namespaced\n  names:\n    plural: serviceprofiles\n    singular: serviceprofile\n    kind: ServiceProfile\n    shortNames:\n    - sp\n\n\n\n\n\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: externalworkloads.workload.linkerd.io\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n  labels:\n    helm.sh/chart: linkerd-crds-0.0.0-undefined\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: workload.linkerd.io\n  names:\n    categories:\n    - external\n    kind: ExternalWorkload\n    listKind: ExternalWorkloadList\n    plural: externalworkloads\n    singular: externalworkload\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTls:\n                description: meshTls describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                # TODO: relax this in the future when ipv6 is supported\n                # an external workload (like a pod) should only\n                # support 2 interfaces\n                maxItems: 1\n            type: object\n            required:\n            - meshTls\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTls.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n  - name: v1beta1\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        description: >-\n          An ExternalWorkload describes a single workload (i.e. a deployable unit) external\n          to the cluster that should be enrolled in the mesh.\n        type: object\n        required: [spec]\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              meshTLS:\n                description: meshTLS describes TLS settings associated with an\n                  external workload.\n                properties:\n                  identity:\n                    type: string\n                    description: identity of the workload. Corresponds to the\n                      identity used in the workload's certificate. It is used\n                      by peers to perform verification in the mTLS handshake.\n                    minLength: 1\n                    maxLength: 253\n                  serverName:\n                    type: string\n                    description: serverName is the name of the workload in DNS\n                      format. It is used by the workload to terminate TLS using\n                      SNI.\n                    minLength: 1\n                    maxLength: 253\n                type: object\n                required:\n                - identity\n                - serverName\n              ports:\n                type: array\n                description: ports describes a list of ports exposed by the\n                  workload\n                items:\n                  properties:\n                    name:\n                      type: string\n                      description: name must be an IANA_SVC_NAME and unique\n                        within the ports set. Each named port can be referred\n                        to by services.\n                    port:\n                      format: int32\n                      maximum: 65535\n                      minimum: 1\n                      type: integer\n                    protocol:\n                      description: protocol exposed by the port. Must be UDP or\n                        TCP. Defaults to TCP.\n                      type: string\n                      default: \"TCP\"\n                  type: object\n                  required:\n                  - port\n              workloadIPs:\n                type: array\n                description: workloadIPs contains a list of IP addresses that\n                  can be used to send traffic to the workload. This field may\n                  hold a maximum of two entries. If one entry, it can be an\n                  IPv4 or IPv6 address; if two entries it should contain one\n                  IPv4 address and one IPv6 address.\n                items:\n                  type: object\n                  properties:\n                    ip:\n                      type: string\n                maxItems: 2\n            type: object\n            required:\n            - meshTLS\n          status:\n            type: object\n            properties:\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    lastProbeTime:\n                      description: lastProbeTime is the last time the\n                        healthcheck endpoint was probed.\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the\n                        condition transitioned from one status to another.\n                      format: date-time\n                      type: string\n                    status:\n                      description: status of the condition (one of True, False, Unknown)\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of the condition in CamelCase or in\n                        foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                    reason:\n                      description: reason contains a programmatic identifier\n                        indicating the reason for the condition's last\n                        transition. Producers of specific condition types may\n                        define expected values and meanings for this field, and\n                        whether the values are considered a guaranteed API. The\n                        value should be a CamelCase string. This field may not\n                        be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    message:\n                      description: message is a human readable message\n                        indicating details about the transition. This may be an\n                        empty string.\n                      maxLength: 32768\n                      type: string\n                required:\n                - status\n                - type\n    additionalPrinterColumns:\n    - jsonPath: .spec.meshTLS.identity\n      name: Identity\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n"
  },
  {
    "path": "cli/cmd/testdata/valid-crt.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIBwDCCAWegAwIBAgIRAJRIgZ8RtO8Ewg1Xepf8T44wCgYIKoZIzj0EAwIwKTEn\nMCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\nODA3MTM0N1oXDTMwMDgyNjA3MTM0N1owKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\na2VyZC5jbHVzdGVyLmxvY2FsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/Fp\nfcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72dQvRaYanuxD36Dt1\n2/JxyiSgxKWRdoay+aNwMG4wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB\nAf8CAQAwHQYDVR0OBBYEFI1WnrqMYKaHHOo+zpyiiDq2pO0KMCkGA1UdEQQiMCCC\nHmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAKBggqhkjOPQQDAgNHADBE\nAiAtuoI5XuCtrGVRzSmRTl2ra28aV9MyTU7d5qnTAFHKSgIgRKCvluOSgA5O21p5\n51tdrmkHEZRr0qlLSJdHYgEfMzk=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cli/cmd/testdata/valid-key.pem",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIAAe8nfbzZu9c/OB2+8xJM0Fz7NUwTQazulkFNs4TI5+oAoGCCqGSM49\nAwEHoUQDQgAE1/FpfcRnDcedL6AjUaXYPv4DIMBaJufOI5NWty+XSX7JjXgZtM72\ndQvRaYanuxD36Dt12/JxyiSgxKWRdoay+Q==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "cli/cmd/testdata/valid-trust-anchors.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIBwTCCAWagAwIBAgIQeDZp5lDaIygQ5UfMKZrFATAKBggqhkjOPQQDAjApMScw\nJQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\nMDcxMjQ3WhcNMzAwODI2MDcxMjQ3WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\nZXJkLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARqc70Z\nl1vgw79rjB5uSITICUA6GyfvSFfcuIis7B/XFSkkwAHU5S/s1AAP+R0TX7HBWUC4\nuaG4WWsiwJKNn7mgo3AwbjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB\n/wIBATAdBgNVHQ4EFgQU5YtjVVPfd7I7NLHsn2C26EByGV0wKQYDVR0RBCIwIIIe\naWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0kAMEYC\nIQCN7lBFLDDvjx6V0+XkjpKERRsJYf5adMvnloFl48ilJgIhANtxhndcr+QJPuC8\nvgUC0d2/9FMueIVMb+46WTCOjsqr\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cli/cmd/testdata/valid-with-rsa-anchor-crt.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDczCCAVugAwIBAgIQBnEaBGxW2/TyGJ0cE3hARDANBgkqhkiG9w0BAQsFADAl\nMSMwIQYDVQQDExpyb290LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAeFw0yMjAyMDMw\nNTQ2MzZaFw0zMjAyMDEwNTQ2MzZaMCkxJzAlBgNVBAMTHmlkZW50aXR5Lmxpbmtl\ncmQuY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAq1eW0j\nd5hETb5jC5N2oZmJSNLLy1DQ0BbSmIEgoLhUmhRFjNvTm6PcqMnVykZZighF1Nhv\n35JED57w7F7rTjijZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/\nAgEAMB0GA1UdDgQWBBQc4E9DlG65aIumqcos/z03+C0KUDAfBgNVHSMEGDAWgBQF\nL+ZbJA8ZzhZug1IYeyRqnuHhkTANBgkqhkiG9w0BAQsFAAOCAgEAyOrxL0aWmbUj\nSjN7WUJOYDEy9aOYDzv3Itj7SIY9G8wg25RvmbMY5R9/LcmVDj9s2Y5ivwXnmfsE\nVsYm7H4Q4H/MjkcFSI8J0iLbTtACkXgPvNx3rFaFXZ9Oe7tMXEfySE0NNXjGwa+a\nkJtZ+ybpOqUIbNqjl+e3Srg1f9QznZik3hHbrBXaLEyAFOee5ijQyxFYFHS7VuhS\n8b586FmGl/8a3/ES5VmGKJh3p4J3aD3MiuLvtL2phh+CdeFmGPYNz4jjb5y4QC1S\np1ZXoxYoeHJuvg6AVCqhwB+xOd8Hr03YUpZSw93tPn3WOtlYrHf8jpx/JV2CLyp3\npTviAv+tnbSVRD378dSs7ir4Q7/jI9KV5N8v1D6/q442nRMVko7zyFMJfryEY+RU\nRfsCsmmgJRCCUEgGXLxQObWyX7+103BqZgFRUfbEaWuSmM8yQyOkFdg5AGMHTOOn\nWe4gG0hnL3hTFZJxofa7Ll1QUe3kwTWTgZdcs/Qn+ZvaZi1VQRVo0lCEOJ+k2NUo\nnDHH9xik7lEv+6cRXPUuQnuO58cXn53tXOqNpA966AXyEGbglsJIzd+Qhgqgz0T5\n9Ag+VdWoDqE5O3E3xhd2CVSHSRIEdlV6z38n/0I6xlFUh3VnPijfYuNv7KYAIQkN\nwstaHKuqI8yy2STQKeVbw+8pCX6VhS0=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cli/cmd/testdata/valid-with-rsa-anchor-key.pem",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIBEi2tPdN7b8F4lLkq4X6f0EWm2NuJoOKMM0vHJD792NoAoGCCqGSM49\nAwEHoUQDQgAECrV5bSN3mERNvmMLk3ahmYlI0svLUNDQFtKYgSCguFSaFEWM29Ob\no9yoydXKRlmKCEXU2G/fkkQPnvDsXutOOA==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "cli/cmd/testdata/valid-with-rsa-anchor-trust-anchors.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFGTCCAwGgAwIBAgIQYvKHmkN454rA18Vg9l8TrDANBgkqhkiG9w0BAQsFADAl\nMSMwIQYDVQQDExpyb290LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAeFw0yMjAyMDMw\nNTQ1NTVaFw0zMjAyMDEwNTQ1NTNaMCUxIzAhBgNVBAMTGnJvb3QubGlua2VyZC5j\nbHVzdGVyLmxvY2FsMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzbli\nL5U1YpclMWjzK1wPQHK8E5RJT0x7yn09e8o/GaG+xo+PQBNe8f26DQQi75N6wpZD\noyDcnfOjPE4ptnB8DU9desDL+KJb1KhUoafqtnc7iXp0fqUIfe/CyLZ7Ve/5yM9C\nRIFPNkLMV4WSXFbHr2xWFPtl78qovei2Ja4AJMkhFEemAxZspBPR2LL274zXBpsP\nsYJl/ZA61RK5WvFF83cMPeM6fc1SzQIYPw3ptRf+eprsZRiX/51/QdGfkxBa7iJB\nE5nCUYsCLhADlSdfljUqD6Jj5luUeYXJJ+UL92NBp/whA1rKXbhQQDs2yXZQyRat\ndVNoSCUCGtsQn4f8mgRIPD3wGIB8hJtYBfigfvOit6KkquDKT3SUSA7HDgfJ14dA\nL9ID5o1Jvdd6zhmzKTa5Uz9PgeDDk6+P0Ac2xssoVKJ5lSDbcEzn6u5RbQSUOuVX\nULYoYXO5i4FM3C1sO9PqYCpMR1YZTFL8qWk5Be5BNH3XwAg84rKmjzgxGWNjk1c/\nk5MYy2AZmNQsQS0Zcxcy2O4BovZlc6eJ1rIdk7norWh5Q6DhABzJswdRiK/E+5UQ\n5WuwAUFI0D8+/34lHecB+TLPoSID8Zy5FdBFL3qJz6JNPsID3PyJyyrjqcNcemUE\nLyOX0RzcUCYlifeqHigd7frOLS98rybv4u9VpjECAwEAAaNFMEMwDgYDVR0PAQH/\nBAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFAUv5lskDxnOFm6D\nUhh7JGqe4eGRMA0GCSqGSIb3DQEBCwUAA4ICAQDJW/5E4euxYMOvt4KDwVCLlLt7\n2Wt3u7Kq1rp0qssT47vdU9FtuYXWLI19smGI4KTg4D8r8gMLg6mbi1otZkyrSNLf\nwIja9Aag1KCqk1OfgHLcdyfXLEADWvv3AuoFuBh/RgiciLII6ieOxG/g8j80EC7I\n2PsxDaAKxrLOg5ZFZf8TSnXyZnENn++tAhb5tm0LqTnRKD0og2nZ7z7IeuYaJGJC\n9q0+LZW5C5PhFsmBKKp4xQ96OsjPLQnyoEKMehpvwv3t/WFh5f5XSVNI3gcLoyNj\nZctHl6SUFJGY1L0uJDeWNrPaGyCpaClLVNeyZanjqV9A/jNC2cdtd02VGFZBLlr6\n7j7Aeioe26fJT0Tr6Ynu/eikuL1TEmgUwcHKwkIyUXV1qPTN4JDNnupmMAu9T7Z0\n21YpgYu64TysVRKrY7Zu4D4rDzol3Y46yvhFZGY+7KEBL+0TPq2ll9QRBPp/Rbzc\ntcN9RSep/KJZlW0d7jgrPhg/pvAHuvq/FZlhMiPqQD5BGSyxbiTcyXtI7j5kDgvU\n2WJfkgRRr9RfbpSlX7o1U4Y2UKxLXwmFnXkPAV8n+HLk7/bcdUVXaXgVsP6RVXgw\n++9zFJi9fJj3ZnKQH/U6MIba6cn3oBMXQOTQs0BdN/JTh7Bt+knwqcdOvKT4wBbw\nmcB/6uAtyaBhb8h/xg==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cli/cmd/testdata/wrong-algo-crt.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIICSTCCAaqgAwIBAgIRAOw34oqyySOZhDLCLMv6kbUwCgYIKoZIzj0EAwQwKTEn\nMCUGA1UEAxMeaWRlbnRpdHkubGlua2VyZC5jbHVzdGVyLmxvY2FsMB4XDTIwMDgy\nODA3MTcyOVoXDTMwMDgyNjA3MTcyOVowKTEnMCUGA1UEAxMeaWRlbnRpdHkubGlu\na2VyZC5jbHVzdGVyLmxvY2FsMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAkAsh\n/r0v+5Qy35vftqP2XKkCAJE36l7SFmHKnZxeZL1My555vgBF0lfrkXgp60U2+DVO\nRzLwf6g7ZjTjH9AR/X0Bkw8hADgM+1/BGihBBouCOH10QSwiKRxoSBZO7ag/RDNW\naC930A6qbBKc3cpNhZv3iNTtz3r1ZeOP7FafY4Bwf3CjcDBuMA4GA1UdDwEB/wQE\nAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQZ7Yb52kCQt1x2KYT0\nllIQBBFr7zApBgNVHREEIjAggh5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9j\nYWwwCgYIKoZIzj0EAwQDgYwAMIGIAkIBqKwxWdOcM/hdyGtFAzyIETI4pUkjwJw5\nXeIpP43FMEPVDR0ZbDVpVzbGHK4+9lAlfakpk0r8pOelY29/HvefyBkCQgHLqWW7\nT+GwAa7E2am9y5WDCg0RtYdyU80A3thoQ2zkFu8cOYleLuYGkJHd/gl7TKhEa6OD\nVvOjtBdWb+QlmoyFQg==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cli/cmd/testdata/wrong-algo-key.pem",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMIHcAgEBBEIBp3mlnf/lbgNualyYQlofrcppBspo25FY7iZrKBDJ9M5K8CEVGiMa\nsgOtvd/7HRgycPZOkh2T0Wh5OksD7avcQ36gBwYFK4EEACOhgYkDgYYABACQCyH+\nvS/7lDLfm9+2o/ZcqQIAkTfqXtIWYcqdnF5kvUzLnnm+AEXSV+uReCnrRTb4NU5H\nMvB/qDtmNOMf0BH9fQGTDyEAOAz7X8EaKEEGi4I4fXRBLCIpHGhIFk7tqD9EM1Zo\nL3fQDqpsEpzdyk2Fm/eI1O3PevVl44/sVp9jgHB/cA==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "cli/cmd/testdata/wrong-algo-trust-anchors.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIICRzCCAamgAwIBAgIQT4qZtBUt41u4ZVA4ul5zUjAKBggqhkjOPQQDBDApMScw\nJQYDVQQDEx5pZGVudGl0eS5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4\nMDcxNjI0WhcNMzAwODI2MDcxNjI0WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5r\nZXJkLmNsdXN0ZXIubG9jYWwwgZswEAYHKoZIzj0CAQYFK4EEACMDgYYABABBK2MN\n+VGlLi9bLVQTIEWQPKlJrsiNPNHpS1DFQdTaFAYweafrj8pSo0EcsUJ0xjgiJNYg\njstKKqpaOoGkL4b0AgAXdwZs1ghQ8FgD8EZ5hd6Ssiah10kz+9+Tk4E1clD+f4y9\nz2k+pW1EmTej/raAWX/P22PExR2x+XoJbWejuuLy6aNwMG4wDgYDVR0PAQH/BAQD\nAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFJ8LR7dqfJJqyMka6Sl3\nm0kkyij6MCkGA1UdEQQiMCCCHmlkZW50aXR5LmxpbmtlcmQuY2x1c3Rlci5sb2Nh\nbDAKBggqhkjOPQQDBAOBiwAwgYcCQU2QOkw8f4h1mDFwQ2kEgbbCfb+Or4tCrs4U\n9g8jFD5/gQn3AuR74sDm+3jvawnZ9NedS8lU4EKipDL/vJrZYXRtAkIBX0aF7/aF\nn27d7RhL/35ig9+68+XwH+NcegznFtE4WtBeWnKjm5s81w5nEXGd574O5Jr10CR6\n0mHHS0YKRoVBYMQ=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cli/cmd/testdata/wrong-domain-crt.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIB3jCCAYOgAwIBAgIQMskiLz88VdgNWjwe75DprTAKBggqhkjOPQQDAjAmMSQw\nIgYDVQQDExt3cm9uZy5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4MDcx\nNTM2WhcNMzAwODI2MDcxNTM2WjApMScwJQYDVQQDEx5pZGVudGl0eS5saW5rZXJk\nLmNsdXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQJ072zm6KO\nc+WCQrFLFAJ9kQLl3pgZS3A2l6+89BNA1/y2hvAq0SVKLjvAAYcoEPBUHjnrjK88\nQkXA6cGRSeiho4GPMIGMMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/\nAgEAMB0GA1UdDgQWBBSw6OUJiajIXQIiq4+L0l3bBu4OdDAfBgNVHSMEGDAWgBTC\nRqClH4N8b8i1607HMSMlU3jPLzAmBgNVHREEHzAdght3cm9uZy5saW5rZXJkLmNs\ndXN0ZXIubG9jYWwwCgYIKoZIzj0EAwIDSQAwRgIhANes+knUYsqdVej0CgABsu5w\nJ2TaBErPu33x4b9ZP4EoAiEA/PRdgzLUQwxeYr0b/VzwBY9TaATiUfFPpW3EuO5A\nLRs=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cli/cmd/testdata/wrong-domain-key.pem",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIKMtQnhk5H/EyXbyF+y54p64Sa4ooBgUNbnPufu5yk7xoAoGCCqGSM49\nAwEHoUQDQgAECdO9s5uijnPlgkKxSxQCfZEC5d6YGUtwNpevvPQTQNf8tobwKtEl\nSi47wAGHKBDwVB4564yvPEJFwOnBkUnooQ==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "cli/cmd/testdata/wrong-domain-trust-anchors.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIBtjCCAV2gAwIBAgIQCZQHKgxVFpe6pISBjvOq2DAKBggqhkjOPQQDAjAmMSQw\nIgYDVQQDExt3cm9uZy5saW5rZXJkLmNsdXN0ZXIubG9jYWwwHhcNMjAwODI4MDcx\nNDI3WhcNMzAwODI2MDcxNDI3WjAmMSQwIgYDVQQDExt3cm9uZy5saW5rZXJkLmNs\ndXN0ZXIubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS0DyGjPETBnVTO\nFgNPQUwEBU3d+Yr1dsNITANB1R2C4T+CM4+EJ07qV+65XqbwkKqSEfps8muwzzRt\n2h5hHAOTo20wazAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAd\nBgNVHQ4EFgQUwkagpR+DfG/ItetOxzEjJVN4zy8wJgYDVR0RBB8wHYIbd3Jvbmcu\nbGlua2VyZC5jbHVzdGVyLmxvY2FsMAoGCCqGSM49BAMCA0cAMEQCIE0a4IBtqAOR\nZ/oxDjdSd4i5AZBrIxzPLPJhRkv5eeDbAiBNDapVdcwwiMb7ciWQdgIPWxUvJbyx\n7va5AtGhIKZK9w==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "cli/cmd/uninject.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/linkerd/linkerd2/pkg/inject\"\n\t\"github.com/spf13/cobra\"\n)\n\ntype resourceTransformerUninject struct {\n\tvalues *linkerd2.Values\n}\n\ntype resourceTransformerUninjectSilent struct {\n\tvalues *linkerd2.Values\n}\n\nfunc runUninjectCmd(inputs []io.Reader, errWriter, outWriter io.Writer, values *linkerd2.Values, output string) int {\n\treturn transformInput(inputs, errWriter, outWriter, resourceTransformerUninject{values}, output)\n}\n\nfunc runUninjectSilentCmd(inputs []io.Reader, errWriter, outWriter io.Writer, values *linkerd2.Values, output string) int {\n\treturn transformInput(inputs, errWriter, outWriter, resourceTransformerUninjectSilent{values}, output)\n}\n\nfunc newCmdUninject() *cobra.Command {\n\tvar output string\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"uninject [flags] CONFIG-FILE\",\n\t\tShort: \"Remove the Linkerd proxy from a Kubernetes config\",\n\t\tLong: `Remove the Linkerd proxy from a Kubernetes config.\n\nYou can uninject resources contained in a single file, inside a folder and its\nsub-folders, or coming from stdin.`,\n\t\tExample: `  # Uninject all the deployments in the default namespace.\n  kubectl get deploy -o yaml | linkerd uninject - | kubectl apply -f -\n\n  # Download a resource and uninject it through stdin.\n  curl http://url.to/yml | linkerd uninject - | kubectl apply -f -\n\n  # Uninject all the resources inside a folder and its sub-folders.\n  linkerd uninject <folder> | kubectl apply -f -`,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\n\t\t\tif len(args) < 1 {\n\t\t\t\treturn fmt.Errorf(\"please specify a kubernetes resource file\")\n\t\t\t}\n\n\t\t\tin, err := read(args[0])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\texitCode := runUninjectCmd(in, os.Stderr, os.Stdout, nil, output)\n\t\t\tos.Exit(exitCode)\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcmd.Flags().StringVarP(&output, \"output\", \"o\", \"yaml\", \"Output format, one of: json|yaml\")\n\n\treturn cmd\n}\n\nfunc (rt resourceTransformerUninject) transform(bytes []byte) ([]byte, []inject.Report, error) {\n\tconf := inject.NewResourceConfig(rt.values, inject.OriginWebhook, controlPlaneNamespace)\n\n\treport, err := conf.ParseMetaAndYAML(bytes)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\toutput, err := conf.Uninject(report)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tif output == nil {\n\t\toutput = bytes\n\t\treport.UnsupportedResource = true\n\t}\n\n\treturn output, []inject.Report{*report}, nil\n}\n\nfunc (rt resourceTransformerUninjectSilent) transform(bytes []byte) ([]byte, []inject.Report, error) {\n\treturn resourceTransformerUninject(rt).transform(bytes)\n}\n\nfunc (resourceTransformerUninject) generateReport(reports []inject.Report, output io.Writer) {\n\t// leading newline to separate from yaml output on stdout\n\toutput.Write([]byte(\"\\n\"))\n\n\tfor _, r := range reports {\n\t\tif r.Uninjected.Proxy || r.Uninjected.ProxyInit {\n\t\t\toutput.Write([]byte(fmt.Sprintf(\"%s \\\"%s\\\" uninjected\\n\", r.Kind, r.Name)))\n\t\t} else {\n\t\t\tif r.Kind != \"\" {\n\t\t\t\toutput.Write([]byte(fmt.Sprintf(\"%s \\\"%s\\\" skipped\\n\", r.Kind, r.Name)))\n\t\t\t} else {\n\t\t\t\toutput.Write([]byte(fmt.Sprintln(\"document missing \\\"kind\\\" field, skipped\")))\n\t\t\t}\n\t\t}\n\t}\n\n\t// trailing newline to separate from kubectl output if piping\n\toutput.Write([]byte(\"\\n\"))\n}\n\nfunc (resourceTransformerUninjectSilent) generateReport(reports []inject.Report, output io.Writer) {\n}\n"
  },
  {
    "path": "cli/cmd/uninject_test.go",
    "content": "package cmd\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\n\tcharts \"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n)\n\n// TestUninjectYAML does the reverse of TestInjectYAML.\n// We use as input the same \"golden\" file and as expected output the same \"input\" file as in the inject tests.\nfunc TestUninjectYAML(t *testing.T) {\n\n\ttestCases := []struct {\n\t\tinputFileName  string\n\t\tgoldenFileName string\n\t\treportFileName string\n\t}{\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_deployment.golden.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_deployment.input.yml\",\n\t\t\treportFileName: \"inject_emojivoto_deployment_uninject.report\",\n\t\t},\n\t\t{\n\t\t\t// remove all the linkerd.io/* annotations\n\t\t\tinputFileName:  \"inject_emojivoto_deployment_overridden_noinject.golden.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_deployment_uninjected.input.yml\",\n\t\t\treportFileName: \"inject_emojivoto_deployment_uninject.report\",\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_list.golden.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_list.input.yml\",\n\t\t\treportFileName: \"inject_emojivoto_list_uninject.report\",\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_deployment_hostNetwork_true.input.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_deployment_hostNetwork_true.input.yml\",\n\t\t\treportFileName: \"inject_emojivoto_deployment_hostNetwork_true_uninject.report\",\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_deployment_injectDisabled.input.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_deployment_injectDisabled.input.yml\",\n\t\t\treportFileName: \"inject_emojivoto_deployment_injectDisabled_uninject.report\",\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_deployment_controller_name.golden.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_deployment_controller_name.input.yml\",\n\t\t\treportFileName: \"inject_emojivoto_deployment_controller_name_uninject.report\",\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_statefulset.golden.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_statefulset.input.yml\",\n\t\t\treportFileName: \"inject_emojivoto_statefulset_uninject.report\",\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_pod.golden.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_pod.input.yml\",\n\t\t\treportFileName: \"inject_emojivoto_pod_uninject.report\",\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_pod_nativesidecar.golden.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_pod.input.yml\",\n\t\t\treportFileName: \"inject_emojivoto_pod_uninject.report\",\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_deployment_udp.golden.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_deployment_udp.input.yml\",\n\t\t\treportFileName: \"inject_emojivoto_deployment_udp_uninject.report\",\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_istio.input.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_istio.input.yml\",\n\t\t\treportFileName: \"inject_emojivoto_istio_uninject.report\",\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_contour.golden.yml\",\n\t\t\tgoldenFileName: \"inject_contour.input.yml\",\n\t\t\treportFileName: \"inject_contour_uninject.report\",\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_deployment_config_overrides.golden.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_deployment_config_overrides.input.yml\",\n\t\t\treportFileName: \"inject_emojivoto_deployment_uninject.report\",\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_namespace_good.golden.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_namespace_uninjected_good.golden.yml\",\n\t\t\treportFileName: \"inject_emojivoto_namespace_uninjected_good.golden.report\",\n\t\t},\n\t\t{\n\t\t\tinputFileName:  \"inject_emojivoto_namespace_overidden_good.golden.yml\",\n\t\t\tgoldenFileName: \"inject_emojivoto_namespace_uninjected_good.golden.yml\",\n\t\t\treportFileName: \"inject_emojivoto_namespace_uninjected_good.golden.report\",\n\t\t},\n\t}\n\n\tvalues, err := charts.NewValues()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d: %s\", i, tc.inputFileName), func(t *testing.T) {\n\t\t\tfile, err := os.Open(\"testdata/\" + tc.inputFileName)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error opening test input file: %v\\n\", err)\n\t\t\t}\n\n\t\t\tread := []io.Reader{bufio.NewReader(file)}\n\n\t\t\toutput := new(bytes.Buffer)\n\t\t\treport := new(bytes.Buffer)\n\n\t\t\texitCode := runUninjectCmd(read, report, output, values, \"yaml\")\n\t\t\tif exitCode != 0 {\n\t\t\t\tt.Errorf(\"Failed to uninject %s\", tc.inputFileName)\n\t\t\t}\n\n\t\t\tif err := testDataDiffer.DiffTestYAML(tc.goldenFileName, output.String()); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\ttestDataDiffer.DiffTestdata(t, tc.reportFileName, report.String())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cli/cmd/uninstall.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\tpkgCmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nconst (\n\tyamlSep = \"---\\n\"\n)\n\nfunc newCmdUninstall() *cobra.Command {\n\tvar force bool\n\tvar output string\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"uninstall\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Output Kubernetes resources to uninstall Linkerd control plane\",\n\t\tLong: `Output Kubernetes resources to uninstall Linkerd control plane.\n\nThis command provides all Kubernetes namespace-scoped and cluster-scoped resources (e.g services, deployments, RBACs, etc.) necessary to uninstall Linkerd control plane.`,\n\t\tExample: ` linkerd uninstall | kubectl delete -f -`,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif !force {\n\n\t\t\t\tvar fail bool\n\t\t\t\t// Retrieve any installed extensions\n\t\t\t\textensionNamespaces, err := k8sAPI.GetAllNamespacesWithExtensionLabel(cmd.Context())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\t// map of the namespace and the extension name\n\t\t\t\t// Namespace is used as key so as to support custom namespace installs\n\t\t\t\textensions := make(map[string]string)\n\t\t\t\tif len(extensionNamespaces) > 0 {\n\t\t\t\t\tfor _, extension := range extensionNamespaces {\n\t\t\t\t\t\textensions[extension.Name] = extension.Labels[k8s.LinkerdExtensionLabel]\n\t\t\t\t\t}\n\n\t\t\t\t\t// Retrieve all the extension names\n\t\t\t\t\textensionNames := make([]string, 0, len(extensions))\n\t\t\t\t\tfor _, v := range extensions {\n\t\t\t\t\t\textensionNames = append(extensionNames, fmt.Sprintf(\"* %s\", v))\n\t\t\t\t\t}\n\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"Please uninstall the following extensions before uninstalling the control-plane:\\n\\t%s\\n\", strings.Join(extensionNames, \"\\n\\t\"))\n\t\t\t\t\tfail = true\n\t\t\t\t}\n\n\t\t\t\tpodList, err := k8sAPI.CoreV1().Pods(\"\").List(cmd.Context(), metav1.ListOptions{LabelSelector: k8s.ControllerNSLabel})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tvar injectedPods []string\n\t\t\t\tfor _, pod := range podList.Items {\n\t\t\t\t\t// skip core control-plane namespace, and extension namespaces\n\t\t\t\t\tif pod.Namespace != controlPlaneNamespace && extensions[pod.Namespace] == \"\" {\n\t\t\t\t\t\tinjectedPods = append(injectedPods, fmt.Sprintf(\"* %s\", pod.Name))\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif len(injectedPods) > 0 {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"Please uninject the following pods before uninstalling the control-plane:\\n\\t%s\\n\", strings.Join(injectedPods, \"\\n\\t\"))\n\t\t\t\t\tfail = true\n\t\t\t\t}\n\n\t\t\t\tif fail {\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tselector, err := pkgCmd.GetLabelSelector(k8s.ControllerNSLabel)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\terr = pkgCmd.Uninstall(cmd.Context(), k8sAPI, selector, output)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintln(os.Stderr, err)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcmd.Flags().BoolVarP(&force, \"force\", \"f\", force, \"Force uninstall even if there exist non-control-plane injected pods\")\n\tcmd.PersistentFlags().StringVarP(&output, \"output\", \"o\", \"yaml\", \"Output format. One of: json|yaml\")\n\treturn cmd\n}\n"
  },
  {
    "path": "cli/cmd/upgrade.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/cli/flag\"\n\tl5dcharts \"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/linkerd/linkerd2/pkg/config\"\n\tflagspkg \"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\tvaluespkg \"helm.sh/helm/v3/pkg/cli/values\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nconst (\n\tfailMessage            = \"For troubleshooting help, visit: https://linkerd.io/upgrade/#troubleshooting\\n\"\n\ttrustRootChangeMessage = \"Rotating the trust anchors will affect existing proxies\\nSee https://linkerd.io/2/tasks/rotating_identity_certificates/ for more information\"\n)\n\nvar (\n\tmanifests string\n\tforce     bool\n)\n\n/* The upgrade commands all follow the same flow:\n * 1. Load default values from the Linkerd2 chart\n * 2. Update the values with stored overrides\n * 3. Apply flags to further modify the values\n * 4. Render the chart using those values\n *\n * The individual commands (upgrade, upgrade config, and upgrade control-plane)\n * differ in which flags are available to each, what pre-check validations\n * are done, and which subset of the chart is rendered.\n */\nfunc newCmdUpgrade() *cobra.Command {\n\tvalues, err := l5dcharts.NewValues()\n\tif err != nil {\n\t\tfmt.Fprint(os.Stderr, err.Error())\n\t\tos.Exit(1)\n\t}\n\n\tvar crds bool\n\tvar options valuespkg.Options\n\tvar output string\n\n\tinstallUpgradeFlags, installUpgradeFlagSet, err := makeInstallUpgradeFlags(values)\n\tif err != nil {\n\t\tfmt.Fprint(os.Stderr, err.Error())\n\t\tos.Exit(1)\n\t}\n\tproxyFlags, proxyFlagSet := makeProxyFlags(values)\n\tflags := flattenFlags(installUpgradeFlags, proxyFlags)\n\n\tupgradeFlagSet := makeUpgradeFlags()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"upgrade [flags]\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Output Kubernetes configs to upgrade an existing Linkerd control plane\",\n\t\tLong: `Output Kubernetes configs to upgrade an existing Linkerd control plane.\n\nNote that the default flag values for this command come from the Linkerd control\nplane. The default values displayed in the Flags section below only apply to the\ninstall command.\n\nThe upgrade can be configured by using the --set, --values, --set-string and --set-file flags.\nA full list of configurable values can be found at https://www.github.com/linkerd/linkerd2/tree/main/charts/linkerd-control-plane/values.yaml\n`,\n\n\t\tExample: `  # Upgrade CRDs first\n  linkerd upgrade --crds | kubectl apply -f -\n\n  # Then upgrade the control plane\n  linkerd upgrade | kubectl apply -f -\n\n  # And lastly, remove linkerd resources that no longer exist in the current version\n  linkerd prune | kubectl delete -f -`,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tk, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to create a kubernetes client: %w\", err)\n\t\t\t}\n\n\t\t\tif crds {\n\t\t\t\t// The CRD chart is not configurable.\n\t\t\t\t// TODO(ver): Error if values have been configured?\n\t\t\t\tif _, err := upgradeCRDs(cmd.Context(), k, options, output).WriteTo(os.Stdout); err != nil {\n\t\t\t\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tif err = upgradeControlPlaneRunE(cmd.Context(), k, flags, options, manifests, output); err != nil {\n\t\t\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcmd.Flags().AddFlagSet(installUpgradeFlagSet)\n\tcmd.Flags().AddFlagSet(proxyFlagSet)\n\tcmd.PersistentFlags().AddFlagSet(upgradeFlagSet)\n\tflagspkg.AddValueOptionsFlags(cmd.Flags(), &options)\n\tcmd.Flags().BoolVar(&crds, \"crds\", false, \"Upgrade Linkerd CRDs\")\n\tcmd.PersistentFlags().StringVarP(&output, \"output\", \"o\", \"yaml\", \"Output format. One of: json|yaml\")\n\n\treturn cmd\n}\n\n// makeConfigClient is used to re-initialize the Kubernetes client in order\n// to fetch existing configuration. It accepts two arguments: a Kubernetes\n// client, and a path to a manifest file. If the manifest path is empty, the\n// client will not be re-initialized. When non-empty, the client will be\n// replaced by a fake Kubernetes client that will hold the values parsed from\n// the manifest.\nfunc makeConfigClient(k *k8s.KubernetesAPI, localManifestPath string) (*k8s.KubernetesAPI, error) {\n\tif localManifestPath == \"\" {\n\t\treturn k, nil\n\t}\n\n\t// We need a Kubernetes client to fetch configs and issuer secrets.\n\treaders, err := read(localManifestPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse manifests from %s: %w\", localManifestPath, err)\n\t}\n\n\tk, err = k8s.NewFakeAPIFromManifests(readers)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse Kubernetes objects from manifest %s: %w\", localManifestPath, err)\n\t}\n\treturn k, nil\n}\n\n// makeUpgradeFlags returns a FlagSet of flags that are only accessible at upgrade-time\n// and not at install-time.  These flags do not configure the Values used to\n// render the chart but instead modify the behavior of the upgrade command itself.\n// They are not persisted in any way.\nfunc makeUpgradeFlags() *pflag.FlagSet {\n\tupgradeFlags := pflag.NewFlagSet(\"upgrade-only\", pflag.ExitOnError)\n\n\tupgradeFlags.StringVar(\n\t\t&manifests, \"from-manifests\", \"\",\n\t\t\"Read config from a Linkerd install YAML rather than from Kubernetes\",\n\t)\n\tupgradeFlags.BoolVar(\n\t\t&force, \"force\", false,\n\t\t\"Force upgrade operation even when issuer certificate does not work with the trust anchors of all proxies\",\n\t)\n\treturn upgradeFlags\n}\n\nfunc upgradeControlPlaneRunE(ctx context.Context, k *k8s.KubernetesAPI, flags []flag.Flag, options valuespkg.Options, localManifestPath string, format string) error {\n\n\tcrds := bytes.Buffer{}\n\terr := renderCRDs(ctx, k, &crds, options, \"yaml\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = healthcheck.CheckCustomResourceDefinitions(ctx, k, crds.String())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Linkerd CRDs must be installed first. Run linkerd upgrade with the --crds flag:\\n%w\", err)\n\t}\n\n\t// Re-initialize client if a local manifest path is used\n\tk, err = makeConfigClient(k, localManifestPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tbuf, err := upgradeControlPlane(ctx, k, flags, options, format)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, flag := range flags {\n\t\tif flag.Name() == \"identity-trust-anchors-file\" && flag.IsSet() {\n\t\t\tfmt.Fprintf(os.Stderr, \"\\n%s %s\\n\\n\", warnStatus, trustRootChangeMessage)\n\t\t}\n\t}\n\n\t_, err = buf.WriteTo(os.Stdout)\n\treturn err\n}\n\nfunc upgradeCRDs(ctx context.Context, k *k8s.KubernetesAPI, options valuespkg.Options, format string) *bytes.Buffer {\n\tvar buf bytes.Buffer\n\tif err := renderCRDs(ctx, k, &buf, options, format); err != nil {\n\t\tupgradeErrorf(\"Could not render upgrade configuration: %s\", err)\n\t}\n\treturn &buf\n}\n\nfunc upgradeControlPlane(ctx context.Context, k *k8s.KubernetesAPI, flags []flag.Flag, options valuespkg.Options, format string) (*bytes.Buffer, error) {\n\tvalues, err := loadStoredValues(ctx, k)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to load stored values: %w\", err)\n\t}\n\n\tif values == nil {\n\t\treturn nil, errors.New(\n\t\t\t`Could not find the linkerd-config-overrides secret.\n\t\t\tIf Linkerd was installed with Helm, please use Helm to perform upgrades`)\n\t}\n\n\terr = flag.ApplySetFlags(values, flags)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif values.Identity.Issuer.Scheme == string(corev1.SecretTypeTLS) {\n\t\tfor _, flag := range flags {\n\t\t\tif (flag.Name() == \"identity-issuer-certificate-file\" || flag.Name() == \"identity-issuer-key-file\") && flag.IsSet() {\n\t\t\t\treturn nil, errors.New(\"cannot update issuer certificates if you are using external cert management solution\")\n\t\t\t}\n\t\t}\n\t}\n\n\terr = validateValues(ctx, k, values)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !force && values.Identity.Issuer.Scheme == k8s.IdentityIssuerSchemeLinkerd {\n\t\terr = ensureIssuerCertWorksWithAllProxies(ctx, k, values)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// Create values override\n\tvaluesOverrides, err := options.MergeValues(nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !isRunAsRoot(valuesOverrides) {\n\t\terr = healthcheck.CheckNodesHaveNonDockerRuntime(ctx, k)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvar buf bytes.Buffer\n\tif err = renderControlPlane(&buf, values, valuesOverrides, format); err != nil {\n\t\tupgradeErrorf(\"Could not render upgrade configuration: %s\", err)\n\t}\n\treturn &buf, nil\n}\n\nfunc loadStoredValues(ctx context.Context, k *k8s.KubernetesAPI) (*l5dcharts.Values, error) {\n\t// Load the default values from the chart.\n\tvalues, err := l5dcharts.NewValues()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Load the stored overrides from the linkerd-config-overrides secret.\n\tsecret, err := k.CoreV1().Secrets(controlPlaneNamespace).Get(ctx, \"linkerd-config-overrides\", metav1.GetOptions{})\n\tif kerrors.IsNotFound(err) {\n\t\treturn nil, nil\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbytes, ok := secret.Data[\"linkerd-config-overrides\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"secret/linkerd-config-overrides is missing linkerd-config-overrides data\")\n\t}\n\n\tbytes, err = config.RemoveGlobalFieldIfPresent(bytes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Unmarshal the overrides directly onto the values.  This has the effect\n\t// of merging the two with the overrides taking priority.\n\terr = yaml.Unmarshal(bytes, values)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn values, nil\n}\n\n// upgradeErrorf prints the error message and quits the upgrade process\nfunc upgradeErrorf(format string, a ...interface{}) {\n\ttemplate := fmt.Sprintf(\"%s %s\\n%s\\n\", failStatus, format, failMessage)\n\tfmt.Fprintf(os.Stderr, template, a...)\n\tos.Exit(1)\n}\n\nfunc ensureIssuerCertWorksWithAllProxies(ctx context.Context, k *k8s.KubernetesAPI, values *l5dcharts.Values) error {\n\tcred, err := tls.ValidateAndCreateCreds(\n\t\tvalues.Identity.Issuer.TLS.CrtPEM,\n\t\tvalues.Identity.Issuer.TLS.KeyPEM,\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmeshedPods, err := healthcheck.GetMeshedPodsIdentityData(ctx, k, \"\")\n\tvar problematicPods []string\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, pod := range meshedPods {\n\t\t// Skip control plane pods since they load their trust anchors from the linkerd-identity-trust-anchors configmap.\n\t\tif pod.Namespace == controlPlaneNamespace {\n\t\t\tcontinue\n\t\t}\n\t\tanchors, err := tls.DecodePEMCertPool(pod.Anchors)\n\n\t\tif anchors != nil {\n\t\t\terr = cred.Verify(anchors, \"\", time.Time{})\n\t\t}\n\n\t\tif err != nil {\n\t\t\tproblematicPods = append(problematicPods, fmt.Sprintf(\"* %s/%s\", pod.Namespace, pod.Name))\n\t\t}\n\t}\n\n\tif len(problematicPods) > 0 {\n\t\terrorMessageHeader := \"You are attempting to use an issuer certificate which does not validate against the trust anchors of the following pods:\"\n\t\terrorMessageFooter := \"These pods do not have the current trust bundle and must be restarted.  Use the --force flag to proceed anyway (this will likely prevent those pods from sending or receiving traffic).\"\n\t\treturn fmt.Errorf(\"%s\\n\\t%s\\n%s\", errorMessageHeader, strings.Join(problematicPods, \"\\n\\t\"), errorMessageFooter)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cli/cmd/upgrade_test.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/cli/flag\"\n\tcharts \"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\tflagspkg \"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n\t\"github.com/spf13/pflag\"\n\tvaluespkg \"helm.sh/helm/v3/pkg/cli/values\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\nconst (\n\tupgradeProxyVersion        = \"UPGRADE-PROXY-VERSION\"\n\tupgradeControlPlaneVersion = \"UPGRADE-CONTROL-PLANE-VERSION\"\n\tupgradeDebugVersion        = \"UPGRADE-DEBUG-VERSION\"\n\toverridesSecret            = \"Secret/linkerd-config-overrides\"\n\tlinkerdConfigMap           = \"ConfigMap/linkerd-config\"\n)\n\ntype (\n\tissuerCerts struct {\n\t\tcaFile  string\n\t\tca      string\n\t\tcrtFile string\n\t\tcrt     string\n\t\tkeyFile string\n\t\tkey     string\n\t}\n\n\tflagOptions struct {\n\t\tflags           []flag.Flag\n\t\tflagSet         *pflag.FlagSet\n\t\ttemplateOptions *valuespkg.Options\n\t}\n)\n\n/* Test cases */\n\n/* Most test cases in this file work by first rendering an install manifest\n   list, creating a fake k8s client initialized with those manifests, rendering\n   an upgrade manifest list, and comparing the install manifests to the upgrade\n   manifests. In some cases we expect these manifests to be identical and in\n   others there are certain expected differences */\n\nfunc TestUpgradeDefault(t *testing.T) {\n\tinstallOpts, upgradeOpts := testOptions(t)\n\tinstall, upgrade, err := renderInstallAndUpgrade(t, installOpts, upgradeOpts.flags, *upgradeOpts.templateOptions)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Install and upgrade manifests should be identical except for the version.\n\texpected := replaceVersions(install.String())\n\texpectedManifests := parseManifestList(expected)\n\tupgradeManifests := parseManifestList(upgrade.String())\n\tfor id, diffs := range diffManifestLists(expectedManifests, upgradeManifests) {\n\t\tfor _, diff := range diffs {\n\t\t\tif ignorableDiff(id, diff) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.Errorf(\"Unexpected diff in %s:\\n%s\", id, diff.String())\n\t\t}\n\t}\n}\n\nfunc TestUpgradeHA(t *testing.T) {\n\tinstallOpts, upgradeOpts := testOptions(t)\n\tinstallOpts.HighAvailability = true\n\tinstall, upgrade, err := renderInstallAndUpgrade(t, installOpts, upgradeOpts.flags, *upgradeOpts.templateOptions)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Install and upgrade manifests should be identical except for the version.\n\texpected := replaceVersions(install.String())\n\texpectedManifests := parseManifestList(expected)\n\tupgradeManifests := parseManifestList(upgrade.String())\n\tfor id, diffs := range diffManifestLists(expectedManifests, upgradeManifests) {\n\t\tfor _, diff := range diffs {\n\t\t\tif ignorableDiff(id, diff) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.Errorf(\"Unexpected diff in %s:\\n%s\", id, diff.String())\n\t\t}\n\t}\n}\n\nfunc TestUpgradeExternalIssuer(t *testing.T) {\n\tinstallOpts, err := testInstallOptionsNoCerts(false)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create install options: %s\", err)\n\t}\n\n\tupgradeOpts, err := testUpgradeOptions()\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create upgrade options: %s\", err)\n\t}\n\n\tissuer := generateIssuerCerts(t, true)\n\tdefer issuer.cleanup()\n\n\tinstallOpts.Identity.Issuer.Scheme = string(corev1.SecretTypeTLS)\n\tca, err := base64.StdEncoding.DecodeString(issuer.ca)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tinstallOpts.IdentityTrustAnchorsPEM = string(ca)\n\tinstall := renderInstall(t, installOpts)\n\tupgrade, err := renderUpgrade(install.String()+externalIssuerSecret(issuer), upgradeOpts.flags, *upgradeOpts.templateOptions)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Install and upgrade manifests should be identical except for the version.\n\texpected := replaceVersions(install.String())\n\texpectedManifests := parseManifestList(expected)\n\tupgradeManifests := parseManifestList(upgrade.String())\n\tfor id, diffs := range diffManifestLists(expectedManifests, upgradeManifests) {\n\t\tfor _, diff := range diffs {\n\t\t\tif ignorableDiff(id, diff) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.Errorf(\"Unexpected diff in %s:\\n%s\", id, diff.String())\n\t\t}\n\t}\n}\n\nfunc TestUpgradeIssuerWithExternalIssuerFails(t *testing.T) {\n\tinstallOpts, upgradeOpts := testOptions(t)\n\n\tissuer := generateIssuerCerts(t, true)\n\tdefer issuer.cleanup()\n\n\tinstallOpts.IdentityTrustDomain = \"cluster.local\"\n\tinstallOpts.IdentityTrustDomain = issuer.ca\n\tinstallOpts.Identity.Issuer.Scheme = string(corev1.SecretTypeTLS)\n\tinstallOpts.Identity.Issuer.TLS.CrtPEM = issuer.crt\n\tinstallOpts.Identity.Issuer.TLS.KeyPEM = issuer.key\n\tinstall := renderInstall(t, installOpts)\n\n\tupgradedIssuer := generateIssuerCerts(t, true)\n\tdefer upgradedIssuer.cleanup()\n\n\tupgradeOpts.flagSet.Set(\"identity-trust-anchors-file\", upgradedIssuer.caFile)\n\tupgradeOpts.flagSet.Set(\"identity-issuer-certificate-file\", upgradedIssuer.crtFile)\n\tupgradeOpts.flagSet.Set(\"identity-issuer-key-file\", upgradedIssuer.keyFile)\n\n\t_, err := renderUpgrade(install.String()+externalIssuerSecret(issuer), upgradeOpts.flags, *upgradeOpts.templateOptions)\n\n\texpectedErr := \"cannot update issuer certificates if you are using external cert management solution\"\n\n\tif err == nil || err.Error() != expectedErr {\n\t\tt.Errorf(\"Expected error: %s but got %s\", expectedErr, err)\n\t}\n}\n\nfunc TestUpgradeOverwriteIssuer(t *testing.T) {\n\tinstallOpts, upgradeOpts := testOptions(t)\n\n\tissuerCerts := generateIssuerCerts(t, true)\n\tdefer issuerCerts.cleanup()\n\n\tupgradeOpts.flagSet.Set(\"identity-trust-anchors-file\", issuerCerts.caFile)\n\tupgradeOpts.flagSet.Set(\"identity-issuer-certificate-file\", issuerCerts.crtFile)\n\tupgradeOpts.flagSet.Set(\"identity-issuer-key-file\", issuerCerts.keyFile)\n\n\tinstall, upgrade, err := renderInstallAndUpgrade(t, installOpts, upgradeOpts.flags, *upgradeOpts.templateOptions)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// When upgrading the trust root, we expect to see the new trust root passed\n\t// to each proxy, the trust root updated in the linkerd-config, and the\n\t// updated credentials in the linkerd-identity-issuer secret.\n\texpected := replaceVersions(install.String())\n\texpectedManifests := parseManifestList(expected)\n\tupgradeManifests := parseManifestList(upgrade.String())\n\tfor id, diffs := range diffManifestLists(expectedManifests, upgradeManifests) {\n\t\tfor _, diff := range diffs {\n\t\t\tif ignorableDiff(id, diff) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif isProxyEnvDiff(diff.path) {\n\t\t\t\t// Trust root has changed.\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif id == \"Deployment/linkerd-identity\" || id == \"Deployment/linkerd-proxy-injector\" {\n\t\t\t\tif pathMatch(diff.path, []string{\"spec\", \"template\", \"spec\", \"containers\", \"*\", \"env\", \"*\", \"value\"}) && diff.b.(string) == issuerCerts.ca {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif pathMatch(diff.path, []string{\"spec\", \"template\", \"metadata\", \"annotations\", \"linkerd.io/trust-root-sha256\"}) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Unexpected diff in %s:\\n%s\", id, diff.String())\n\t\t\t}\n\t\t\tif id == \"Deployment/linkerd-destination\" {\n\t\t\t\tif pathMatch(diff.path, []string{\"spec\", \"template\", \"metadata\", \"annotations\", \"linkerd.io/trust-root-sha256\"}) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif id == \"Secret/linkerd-identity-issuer\" {\n\t\t\t\tif pathMatch(diff.path, []string{\"data\", \"crt.pem\"}) {\n\t\t\t\t\tif diff.b.(string) != issuerCerts.crt {\n\t\t\t\t\t\tdiff.a = issuerCerts.crt\n\t\t\t\t\t\tt.Errorf(\"Unexpected diff in %s:\\n%s\", id, diff.String())\n\t\t\t\t\t}\n\t\t\t\t} else if pathMatch(diff.path, []string{\"data\", \"key.pem\"}) {\n\t\t\t\t\tif diff.b.(string) != issuerCerts.key {\n\t\t\t\t\t\tdiff.a = issuerCerts.key\n\t\t\t\t\t\tt.Errorf(\"Unexpected diff in %s:\\n%s\", id, diff.String())\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"Unexpected diff in %s:\\n%s\", id, diff.String())\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif id == \"ConfigMap/linkerd-identity-trust-roots\" {\n\t\t\t\tif pathMatch(diff.path, []string{\"data\", \"ca-bundle.crt\"}) {\n\t\t\t\t\tca, err := base64.StdEncoding.DecodeString(issuerCerts.ca)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatal(err)\n\t\t\t\t\t}\n\t\t\t\t\tif diff.b.(string) != string(ca) {\n\t\t\t\t\t\tdiff.a = string(ca)\n\t\t\t\t\t\tt.Errorf(\"Unexpected diff in %s:\\n%s\", id, diff.String())\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"Unexpected diff in %s:\\n%s\", id, diff.String())\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tt.Errorf(\"Unexpected diff in %s:\\n%s\", id, diff.String())\n\t\t}\n\t}\n}\n\nfunc TestUpgradeFailsWithOnlyIssuerCert(t *testing.T) {\n\tinstallOpts, upgradeOpts := testOptions(t)\n\n\tissuerCerts := generateIssuerCerts(t, true)\n\tdefer issuerCerts.cleanup()\n\n\tupgradeOpts.flagSet.Set(\"identity-trust-anchors-file\", issuerCerts.caFile)\n\tupgradeOpts.flagSet.Set(\"identity-issuer-certificate-file\", issuerCerts.crtFile)\n\n\t_, _, err := renderInstallAndUpgrade(t, installOpts, upgradeOpts.flags, *upgradeOpts.templateOptions)\n\n\texpectedErr := \"failed to validate issuer credentials: failed to read CA: tls: Public and private key do not match\"\n\n\tif err == nil || err.Error() != expectedErr {\n\t\tt.Errorf(\"Expected error: %s but got %s\", expectedErr, err)\n\t}\n}\n\nfunc TestUpgradeFailsWithOnlyIssuerKey(t *testing.T) {\n\tinstallOpts, upgradeOpts := testOptions(t)\n\n\tissuerCerts := generateIssuerCerts(t, false)\n\tdefer issuerCerts.cleanup()\n\n\tupgradeOpts.flagSet.Set(\"identity-trust-anchors-file\", issuerCerts.caFile)\n\tupgradeOpts.flagSet.Set(\"identity-issuer-certificate-file\", issuerCerts.crtFile)\n\n\t_, _, err := renderInstallAndUpgrade(t, installOpts, upgradeOpts.flags, *upgradeOpts.templateOptions)\n\n\texpectedErr := \"failed to validate issuer credentials: failed to read CA: tls: Public and private key do not match\"\n\n\tif err == nil || err.Error() != expectedErr {\n\t\tt.Errorf(\"Expected error: %s but got %s\", expectedErr, err)\n\t}\n}\n\nfunc TestUpgradeRootFailsWithOldPods(t *testing.T) {\n\tinstallOpts, upgradeOpts := testOptions(t)\n\n\toldIssuer := generateIssuerCerts(t, false)\n\tdefer oldIssuer.cleanup()\n\n\tinstall := renderInstall(t, installOpts)\n\n\tissuerCerts := generateIssuerCerts(t, true)\n\tdefer issuerCerts.cleanup()\n\n\tupgradeOpts.flagSet.Set(\"identity-trust-anchors-file\", issuerCerts.caFile)\n\tupgradeOpts.flagSet.Set(\"identity-issuer-certificate-file\", issuerCerts.crtFile)\n\tupgradeOpts.flagSet.Set(\"identity-issuer-key-file\", issuerCerts.keyFile)\n\n\t_, err := renderUpgrade(install.String()+podWithSidecar(oldIssuer), upgradeOpts.flags, *upgradeOpts.templateOptions)\n\n\texpectedErr := \"You are attempting to use an issuer certificate which does not validate against the trust anchors of the following pods\"\n\tif err == nil || !strings.HasPrefix(err.Error(), expectedErr) {\n\t\tt.Errorf(\"Expected error: %s but got %s\", expectedErr, err)\n\t}\n}\n\n// this test constructs a set of secrets resources\nfunc TestUpgradeWebhookCrtsNameChange(t *testing.T) {\n\tinstallOpts, upgradeOpts := testOptions(t)\n\n\tinjectorCerts := generateCerts(t, \"linkerd-proxy-injector.linkerd.svc\", false)\n\tdefer injectorCerts.cleanup()\n\tinstallOpts.ProxyInjector.TLS = &charts.TLS{\n\t\tExternalSecret: true,\n\t\tCaBundle:       injectorCerts.ca,\n\t}\n\n\tvalidatorCerts := generateCerts(t, \"linkerd-sp-validator.linkerd.svc\", false)\n\tdefer validatorCerts.cleanup()\n\tinstallOpts.ProfileValidator.TLS = &charts.TLS{\n\t\tExternalSecret: true,\n\t\tCaBundle:       validatorCerts.ca,\n\t}\n\n\trendered := renderInstall(t, installOpts)\n\texpected := replaceVersions(rendered.String())\n\n\t// switch back to old tls secret names.\n\tinstall := replaceK8sSecrets(expected)\n\n\tupgrade, err := renderUpgrade(install, upgradeOpts.flags, *upgradeOpts.templateOptions)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\texpectedManifests := parseManifestList(expected)\n\tupgradeManifests := parseManifestList(upgrade.String())\n\tfor id, diffs := range diffManifestLists(expectedManifests, upgradeManifests) {\n\t\tfor _, diff := range diffs {\n\t\t\tif ignorableDiff(id, diff) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.Errorf(\"Unexpected diff in %s:\\n%s\", id, diff.String())\n\t\t}\n\t}\n}\n\nfunc TestUpgradeCRDsWithGatewayAPI(t *testing.T) {\n\tinstallOpts := valuespkg.Options{\n\t\tValues: []string{\"installGatewayAPI=true\"},\n\t}\n\tvar installBuf bytes.Buffer\n\tif err := renderCRDs(context.Background(), nil, &installBuf, installOpts, \"yaml\"); err != nil {\n\t\tt.Fatalf(\"could not render install manifests: %s\", err)\n\t}\n\tinstallManifest := installBuf.String()\n\tk, err := k8s.NewFakeAPIFromManifests([]io.Reader{strings.NewReader(installManifest)})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to initialize fake API: %s\\n\\n%s\", err, installManifest)\n\t}\n\tupgradeBuf := upgradeCRDs(context.Background(), k, valuespkg.Options{}, \"yaml\")\n\tupgradeManifest := upgradeBuf.String()\n\n\tif err := testDataDiffer.DiffTestYAML(\"upgrade_crds_with_gateway_api.golden\", upgradeManifest); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestUpgradeCRDsWithExternalGatewayAPI(t *testing.T) {\n\tgatewayAPIManifest := `---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httproutes.gateway.networking.k8s.io\nspec:\n  versions:\n    - name: v1\n`\n\n\tinstallOpts := valuespkg.Options{\n\t\tValues: []string{\"installGatewayAPI=false\"},\n\t}\n\tvar installBuf bytes.Buffer\n\tif err := renderCRDs(context.Background(), nil, &installBuf, installOpts, \"yaml\"); err != nil {\n\t\tt.Fatalf(\"could not render install manifests: %s\", err)\n\t}\n\tinstallManifest := installBuf.String()\n\tk, err := k8s.NewFakeAPIFromManifests([]io.Reader{strings.NewReader(installManifest), strings.NewReader(gatewayAPIManifest)})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to initialize fake API: %s\\n\\n%s\", err, installManifest)\n\t}\n\tupgradeBuf := upgradeCRDs(context.Background(), k, valuespkg.Options{}, \"yaml\")\n\tupgradeManifest := upgradeBuf.String()\n\n\tif err := testDataDiffer.DiffTestYAML(\"upgrade_crds_without_gateway_api.golden\", upgradeManifest); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestUpgradeCRDsWithUnsupportedGatewayAPIVersion(t *testing.T) {\n\tgatewayAPIManifest := `---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: httproutes.gateway.networking.k8s.io\nspec:\n  versions:\n    - name: v1alpha1\n`\n\n\tinstallOpts := valuespkg.Options{\n\t\tValues: []string{\"installGatewayAPI=false\"},\n\t}\n\tvar installBuf bytes.Buffer\n\tif err := renderCRDs(context.Background(), nil, &installBuf, installOpts, \"yaml\"); err != nil {\n\t\tt.Fatalf(\"could not render install manifests: %s\", err)\n\t}\n\tinstallManifest := installBuf.String()\n\tk, err := k8s.NewFakeAPIFromManifests([]io.Reader{strings.NewReader(installManifest), strings.NewReader(gatewayAPIManifest)})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to initialize fake API: %s\\n\\n%s\", err, installManifest)\n\t}\n\terr = renderCRDs(context.Background(), k, nil, valuespkg.Options{}, \"yaml\")\n\tif err == nil {\n\t\tt.Error(\"upgrade from unsupported version of the Gateway API should fail\")\n\t}\n}\n\nfunc replaceK8sSecrets(input string) string {\n\tmanifest := strings.ReplaceAll(input, \"kubernetes.io/tls\", \"Opaque\")\n\tmanifest = strings.ReplaceAll(manifest, \"tls.key\", \"key.pem\")\n\tmanifest = strings.ReplaceAll(manifest, \"tls.crt\", \"crt.pem\")\n\tmanifest = strings.ReplaceAll(manifest, \"linkerd-proxy-injector-k8s-tls\", \"linkerd-proxy-injector-tls\")\n\tmanifest = strings.ReplaceAll(manifest, \"linkerd-tap-k8s-tls\", \"linkerd-tap-tls\")\n\tmanifest = strings.ReplaceAll(manifest, \"linkerd-sp-validator-k8s-tls\", \"linkerd-sp-validator-tls\")\n\tmanifest = strings.ReplaceAll(manifest, \"linkerd-policy-validator-k8s-tls\", \"linkerd-policy-validator-tls\")\n\treturn manifest\n}\n\nfunc TestUpgradeTwoLevelWebhookCrts(t *testing.T) {\n\tinstallOpts, upgradeOpts := testOptions(t)\n\n\t// This tests the case where the webhook certs are not self-signed.\n\tinjectorCerts := generateCerts(t, \"linkerd-proxy-injector.linkerd.svc\", false)\n\tdefer injectorCerts.cleanup()\n\tinstallOpts.ProxyInjector.TLS = &charts.TLS{\n\t\tExternalSecret: true,\n\t\tCaBundle:       injectorCerts.ca,\n\t}\n\n\tvalidatorCerts := generateCerts(t, \"linkerd-sp-validator.linkerd.svc\", false)\n\tdefer validatorCerts.cleanup()\n\tinstallOpts.ProfileValidator.TLS = &charts.TLS{\n\t\tExternalSecret: true,\n\t\tCaBundle:       validatorCerts.ca,\n\t}\n\n\tinstall := renderInstall(t, installOpts)\n\tupgrade, err := renderUpgrade(install.String(), upgradeOpts.flags, *upgradeOpts.templateOptions)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\texpected := replaceVersions(install.String())\n\texpectedManifests := parseManifestList(expected)\n\tupgradeManifests := parseManifestList(upgrade.String())\n\tfor id, diffs := range diffManifestLists(expectedManifests, upgradeManifests) {\n\t\tfor _, diff := range diffs {\n\t\t\tif ignorableDiff(id, diff) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.Errorf(\"Unexpected diff in %s:\\n%s\", id, diff.String())\n\t\t}\n\t}\n}\n\n/* Helpers */\n\nfunc testUpgradeOptions() (flagOptions, error) {\n\tdefaults, err := charts.NewValues()\n\tif err != nil {\n\t\treturn flagOptions{}, err\n\t}\n\n\tinstallUpgradeFlags, installUpgradeFlagSet, err := makeInstallUpgradeFlags(defaults)\n\tif err != nil {\n\t\treturn flagOptions{}, err\n\t}\n\tproxyFlags, proxyFlagSet := makeProxyFlags(defaults)\n\tupgradeFlagSet := makeUpgradeFlags()\n\n\tflags := flattenFlags(installUpgradeFlags, proxyFlags)\n\tflagSet := pflag.NewFlagSet(\"upgrade\", pflag.ExitOnError)\n\tflagSet.AddFlagSet(installUpgradeFlagSet)\n\tflagSet.AddFlagSet(proxyFlagSet)\n\tflagSet.AddFlagSet(upgradeFlagSet)\n\n\t// Explicitly set controller override to upgrade control plane version\n\ttemplateOpts := &valuespkg.Options{}\n\tflagspkg.AddValueOptionsFlags(flagSet, templateOpts)\n\tflagSet.Set(\"set\", fmt.Sprintf(\"controllerImageVersion=%[1]s,linkerdVersion=%[1]s\", upgradeControlPlaneVersion))\n\n\tflagSet.Set(\"set\", fmt.Sprintf(\"proxy.image.version=%s\", upgradeProxyVersion))\n\n\treturn flagOptions{flags, flagSet, templateOpts}, nil\n}\n\nfunc testOptions(t *testing.T) (*charts.Values, flagOptions) {\n\tt.Helper()\n\tinstallValues, err := testInstallOptions()\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create install options: %s\", err)\n\t}\n\tflagOpts, err := testUpgradeOptions()\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create upgrade options: %s\", err)\n\t}\n\n\treturn installValues, flagOpts\n}\n\nfunc replaceVersions(manifest string) string {\n\tmanifest = strings.ReplaceAll(manifest, installProxyVersion, upgradeProxyVersion)\n\tmanifest = strings.ReplaceAll(manifest, installControlPlaneVersion, upgradeControlPlaneVersion)\n\tmanifest = strings.ReplaceAll(manifest, installDebugVersion, upgradeDebugVersion)\n\treturn manifest\n}\n\nfunc generateIssuerCerts(t *testing.T, b64encode bool) issuerCerts {\n\tt.Helper()\n\treturn generateCerts(t, \"identity.linkerd.cluster.local\", b64encode)\n}\n\nfunc generateCerts(t *testing.T, name string, b64encode bool) issuerCerts {\n\tt.Helper()\n\tca, err := tls.GenerateRootCAWithDefaults(\"test\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tissuer, err := ca.GenerateCA(name, -1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcaPem := strings.TrimSpace(issuer.Cred.EncodePEM())\n\tkeyPem := strings.TrimSpace(issuer.Cred.EncodePrivateKeyPEM())\n\tcrtPem := strings.TrimSpace(issuer.Cred.EncodeCertificatePEM())\n\n\tcaFile, err := os.CreateTemp(\"\", \"ca.*.pem\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcrtFile, err := os.CreateTemp(\"\", \"crt.*.pem\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tkeyFile, err := os.CreateTemp(\"\", \"key.*.pem\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, err = caFile.Write([]byte(caPem))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t_, err = crtFile.Write([]byte(crtPem))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t_, err = keyFile.Write([]byte(keyPem))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif b64encode {\n\t\tcaPem = base64.StdEncoding.EncodeToString([]byte(caPem))\n\t\tcrtPem = base64.StdEncoding.EncodeToString([]byte(crtPem))\n\t\tkeyPem = base64.StdEncoding.EncodeToString([]byte(keyPem))\n\t}\n\n\treturn issuerCerts{\n\t\tcaFile:  caFile.Name(),\n\t\tca:      caPem,\n\t\tcrtFile: crtFile.Name(),\n\t\tcrt:     crtPem,\n\t\tkeyFile: keyFile.Name(),\n\t\tkey:     keyPem,\n\t}\n}\n\nfunc (ic issuerCerts) cleanup() {\n\tos.Remove(ic.caFile)\n\tos.Remove(ic.crtFile)\n\tos.Remove(ic.keyFile)\n}\n\nfunc externalIssuerSecret(certs issuerCerts) string {\n\treturn fmt.Sprintf(`---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\ndata:\n  tls.crt: %s\n  tls.key: %s\n  ca.crt: %s\ntype: kubernetes.io/tls\n`, certs.crt, certs.key, certs.ca)\n}\n\nfunc indentLines(s string, prefix string) string {\n\tlines := strings.Split(s, \"\\n\")\n\tfor i, line := range lines {\n\t\tlines[i] = prefix + line\n\t}\n\treturn strings.Join(lines, \"\\n\")\n}\n\nfunc podWithSidecar(certs issuerCerts) string {\n\treturn fmt.Sprintf(`---\napiVersion: v1\nkind: Pod\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli some-version\n    linkerd.io/proxy-version: some-version\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: backend-wrong-anchors\n  namespace: some-namespace\nspec:\n  containers:\n  - env:\n    - name: LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\n      value: |\n%s\n    image: cr.l5d.io/linkerd/proxy:some-version\n    name: linkerd-proxy\n`, indentLines(certs.ca, \"        \"))\n}\n\nfunc isProxyEnvDiff(path []string) bool {\n\ttemplate := []string{\"spec\", \"template\", \"spec\", \"containers\", \"*\", \"env\", \"*\", \"value\"}\n\treturn pathMatch(path, template)\n}\n\nfunc pathMatch(path []string, template []string) bool {\n\tif len(path) != len(template) {\n\t\treturn false\n\t}\n\tfor i, elem := range template {\n\t\tif elem != \"*\" && elem != path[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc renderInstall(t *testing.T, values *charts.Values) *bytes.Buffer {\n\tt.Helper()\n\tvar buf bytes.Buffer\n\tif err := renderCRDs(context.Background(), nil, &buf, valuespkg.Options{}, \"yaml\"); err != nil {\n\t\tt.Fatalf(\"could not render install manifests: %s\", err)\n\t}\n\tbuf.WriteString(\"---\\n\")\n\tif err := renderControlPlane(&buf, values, nil, \"yaml\"); err != nil {\n\t\tt.Fatalf(\"could not render install manifests: %s\", err)\n\t}\n\treturn &buf\n}\n\nfunc renderUpgrade(installManifest string, upgradeOpts []flag.Flag, templateOpts valuespkg.Options) (*bytes.Buffer, error) {\n\tk, err := k8s.NewFakeAPIFromManifests([]io.Reader{strings.NewReader(externalGatewayAPIManifest), strings.NewReader(installManifest)})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to initialize fake API: %w\\n\\n%s\", err, installManifest)\n\t}\n\tbuf := upgradeCRDs(context.Background(), k, valuespkg.Options{}, \"yaml\")\n\tbuf.WriteString(\"---\\n\")\n\tcpbuf, err := upgradeControlPlane(context.Background(), k, upgradeOpts, templateOpts, \"yaml\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbuf.Write(cpbuf.Bytes())\n\treturn buf, nil\n}\n\nfunc renderInstallAndUpgrade(t *testing.T, installOpts *charts.Values, upgradeOpts []flag.Flag, templateOpts valuespkg.Options) (*bytes.Buffer, *bytes.Buffer, error) {\n\tt.Helper()\n\terr := validateValues(context.Background(), nil, installOpts)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"failed to validate values: %w\", err)\n\t}\n\tinstallBuf := renderInstall(t, installOpts)\n\tupgradeBuf, err := renderUpgrade(installBuf.String(), upgradeOpts, templateOpts)\n\treturn installBuf, upgradeBuf, err\n}\n\n// Certain resources are expected to change during an upgrade. We can safely\n// ignore these diffs in every test.\nfunc ignorableDiff(id string, diff diff) bool {\n\tif id == overridesSecret {\n\t\t// The config overrides will always change because at least the control\n\t\t// plane and proxy versions will change.\n\t\treturn true\n\t}\n\tif id == linkerdConfigMap {\n\t\t// The linkerd-config values will always change because at least the control\n\t\t// plane and proxy versions will change.\n\t\treturn true\n\t}\n\tif (strings.HasPrefix(id, \"MutatingWebhookConfiguration\") || strings.HasPrefix(id, \"ValidatingWebhookConfiguration\")) &&\n\t\tpathMatch(diff.path, []string{\"webhooks\", \"*\", \"clientConfig\", \"caBundle\"}) {\n\t\t// Webhook TLS chains are regenerated upon upgrade so we expect the\n\t\t// caBundle to change.\n\t\treturn true\n\t}\n\tif strings.HasPrefix(id, \"APIService\") &&\n\t\tpathMatch(diff.path, []string{\"spec\", \"caBundle\"}) {\n\t\t// APIService TLS chains are regenerated upon upgrade so we expect the\n\t\t// caBundle to change.\n\t\treturn true\n\t}\n\n\tif (id == \"Deployment/linkerd-sp-validator\" || id == \"Deployment/linkerd-proxy-injector\" || id == \"Deployment/linkerd-tap\" || id == \"Deployment/linkerd-destination\") &&\n\t\tpathMatch(diff.path, []string{\"spec\", \"template\", \"metadata\", \"annotations\", \"checksum/config\"}) {\n\t\t// APIService TLS chains are regenerated upon upgrade so we expect the\n\t\t// caBundle to change.\n\t\treturn true\n\t}\n\n\tif id == \"Secret/linkerd-proxy-injector-tls\" || id == \"Secret/linkerd-sp-validator-tls\" ||\n\t\tid == \"Secret/linkerd-tap-tls\" || id == \"Secret/linkerd-sp-validator-k8s-tls\" ||\n\t\tid == \"Secret/linkerd-proxy-injector-k8s-tls\" || id == \"Secret/linkerd-tap-k8s-tls\" ||\n\t\tid == \"Secret/linkerd-policy-validator-tls\" || id == \"Secret/linkerd-policy-validator-k8s-tls\" {\n\t\t// Webhook and APIService TLS chains are regenerated upon upgrade.\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "cli/cmd/version.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nconst defaultVersionString = \"unavailable\"\n\ntype versionOptions struct {\n\tshortVersion      bool\n\tonlyClientVersion bool\n\tproxy             bool\n\tnamespace         string\n}\n\nfunc newVersionOptions() *versionOptions {\n\treturn &versionOptions{\n\t\tshortVersion:      false,\n\t\tonlyClientVersion: false,\n\t\tproxy:             false,\n\t\tnamespace:         \"\",\n\t}\n}\n\nfunc newCmdVersion() *cobra.Command {\n\toptions := newVersionOptions()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"version\",\n\t\tShort: \"Print the client and server version information\",\n\t\tArgs:  cobra.NoArgs,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tvar k8sAPI *k8s.KubernetesAPI\n\t\t\tvar err error\n\t\t\tif !options.onlyClientVersion {\n\t\t\t\tk8sAPI, err = k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconfigureAndRunVersion(k8sAPI, options, os.Stdout)\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().BoolVar(&options.shortVersion, \"short\", options.shortVersion, \"Print the version number(s) only, with no additional output\")\n\tcmd.PersistentFlags().BoolVar(&options.onlyClientVersion, \"client\", options.onlyClientVersion, \"Print the client version only\")\n\tcmd.PersistentFlags().BoolVar(&options.proxy, \"proxy\", options.proxy, \"Print data-plane versions\")\n\tcmd.PersistentFlags().StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace, \"Namespace to use for --proxy versions (default: all namespaces)\")\n\n\treturn cmd\n}\n\nfunc configureAndRunVersion(\n\tk8sAPI *k8s.KubernetesAPI,\n\toptions *versionOptions,\n\tstdout io.Writer,\n) {\n\tclientVersion := version.Version\n\tif options.shortVersion {\n\t\tfmt.Fprintln(stdout, clientVersion)\n\t} else {\n\t\tfmt.Fprintf(stdout, \"Client version: %s\\n\", clientVersion)\n\t}\n\n\tif !options.onlyClientVersion {\n\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\tdefer cancel()\n\t\tserverVersion, err := healthcheck.GetServerVersion(ctx, controlPlaneNamespace, k8sAPI)\n\t\tif err != nil {\n\t\t\tserverVersion = defaultVersionString\n\t\t}\n\n\t\tif options.shortVersion {\n\t\t\tfmt.Fprintln(stdout, serverVersion)\n\t\t} else {\n\t\t\tfmt.Fprintf(stdout, \"Server version: %s\\n\", serverVersion)\n\t\t}\n\n\t\tif options.proxy {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\t\tdefer cancel()\n\t\t\tselector := fmt.Sprintf(\"%s=%s\", k8s.ControllerNSLabel, controlPlaneNamespace)\n\t\t\tpodList, err := k8sAPI.CoreV1().Pods(options.namespace).List(ctx, metav1.ListOptions{LabelSelector: selector})\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintln(stdout, \"Proxy versions: unavailable\")\n\t\t\t} else {\n\t\t\t\tcounts := make(map[string]int)\n\t\t\t\tfor _, pod := range podList.Items {\n\t\t\t\t\tcounts[k8s.GetProxyVersion(pod)]++\n\t\t\t\t}\n\t\t\t\tif len(counts) == 0 {\n\t\t\t\t\tfmt.Fprintln(stdout, \"Proxy versions: unavailable\")\n\t\t\t\t} else {\n\t\t\t\t\tfmt.Fprintln(stdout, \"Proxy versions:\")\n\t\t\t\t\tfor version, count := range counts {\n\t\t\t\t\t\tfmt.Fprintf(stdout, \"\\t%s (%d pods)\\n\", version, count)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cli/flag/flag.go",
    "content": "package flag\n\nimport (\n\t\"time\"\n\n\tcharts \"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/spf13/pflag\"\n)\n\ntype (\n\t// Flag is an interface which describes a command line flag that affects the\n\t// Helm Values used to render Helm charts.  This interface allows us to\n\t// iterate over flags which have been set and apply their effects to the\n\t// Values.\n\tFlag interface {\n\t\tApply(values *charts.Values) error\n\t\tIsSet() bool\n\t\tName() string\n\t}\n\n\t// UintFlag is a Flag with a uint typed value.\n\tUintFlag struct {\n\t\tname    string\n\t\tValue   uint\n\t\tflagSet *pflag.FlagSet\n\t\tapply   func(values *charts.Values, value uint) error\n\t}\n\n\t// Int64Flag is a Flag with an int64 typed value.\n\tInt64Flag struct {\n\t\tname    string\n\t\tValue   int64\n\t\tflagSet *pflag.FlagSet\n\t\tapply   func(values *charts.Values, value int64) error\n\t}\n\n\t// StringFlag is a Flag with a string typed value.\n\tStringFlag struct {\n\t\tname    string\n\t\tValue   string\n\t\tflagSet *pflag.FlagSet\n\t\tapply   func(values *charts.Values, value string) error\n\t}\n\n\t// StringSliceFlag is a Flag with a []string typed value.\n\tStringSliceFlag struct {\n\t\tname    string\n\t\tValue   []string\n\t\tflagSet *pflag.FlagSet\n\t\tapply   func(values *charts.Values, value []string) error\n\t}\n\n\t// BoolFlag is a Flag with a bool typed value.\n\tBoolFlag struct {\n\t\tname    string\n\t\tValue   bool\n\t\tflagSet *pflag.FlagSet\n\t\tapply   func(values *charts.Values, value bool) error\n\t}\n\n\t// DurationFlag is a Flag with a time.Duration typed value.\n\tDurationFlag struct {\n\t\tname    string\n\t\tValue   time.Duration\n\t\tflagSet *pflag.FlagSet\n\t\tapply   func(values *charts.Values, value time.Duration) error\n\t}\n)\n\n// NewUintFlag creates a new uint typed Flag that executes the given function\n// when applied.  The flag is attached to the given FlagSet.\nfunc NewUintFlag(flagSet *pflag.FlagSet, name string, defaultValue uint, description string, apply func(values *charts.Values, value uint) error) *UintFlag {\n\tflag := UintFlag{\n\t\tname:    name,\n\t\tflagSet: flagSet,\n\t\tapply:   apply,\n\t}\n\tflagSet.UintVar(&flag.Value, name, defaultValue, description)\n\treturn &flag\n}\n\n// NewInt64Flag creates a new int64 typed Flag that executes the given function\n// when applied.  The flag is attached to the given FlagSet.\nfunc NewInt64Flag(flagSet *pflag.FlagSet, name string, defaultValue int64, description string, apply func(values *charts.Values, value int64) error) *Int64Flag {\n\tflag := Int64Flag{\n\t\tname:    name,\n\t\tflagSet: flagSet,\n\t\tapply:   apply,\n\t}\n\tflagSet.Int64Var(&flag.Value, name, defaultValue, description)\n\treturn &flag\n}\n\n// NewStringFlag creates a new string typed Flag that executes the given function\n// when applied.  The flag is attached to the given FlagSet.\nfunc NewStringFlag(flagSet *pflag.FlagSet, name string, defaultValue string, description string, apply func(values *charts.Values, value string) error) *StringFlag {\n\tflag := StringFlag{\n\t\tname:    name,\n\t\tflagSet: flagSet,\n\t\tapply:   apply,\n\t}\n\tflagSet.StringVar(&flag.Value, name, defaultValue, description)\n\treturn &flag\n}\n\n// NewStringSliceFlag creates a new []string typed Flag that executes the given function\n// when applied.  The flag is attached to the given FlagSet.\nfunc NewStringSliceFlag(flagSet *pflag.FlagSet, name string, defaultValue []string, description string, apply func(values *charts.Values, value []string) error) *StringSliceFlag {\n\tflag := StringSliceFlag{\n\t\tname:    name,\n\t\tflagSet: flagSet,\n\t\tapply:   apply,\n\t}\n\tflagSet.StringSliceVar(&flag.Value, name, defaultValue, description)\n\treturn &flag\n}\n\n// NewStringFlagP creates a new string typed Flag that executes the given function\n// when applied.  The flag is attached to the given FlagSet.\nfunc NewStringFlagP(flagSet *pflag.FlagSet, name string, short string, defaultValue string, description string, apply func(values *charts.Values, value string) error) *StringFlag {\n\tflag := StringFlag{\n\t\tname:    name,\n\t\tflagSet: flagSet,\n\t\tapply:   apply,\n\t}\n\tflagSet.StringVarP(&flag.Value, name, short, defaultValue, description)\n\treturn &flag\n}\n\n// NewBoolFlag creates a new bool typed Flag that executes the given function\n// when applied.  The flag is attached to the given FlagSet.\nfunc NewBoolFlag(flagSet *pflag.FlagSet, name string, defaultValue bool, description string, apply func(values *charts.Values, value bool) error) *BoolFlag {\n\tflag := BoolFlag{\n\t\tname:    name,\n\t\tflagSet: flagSet,\n\t\tapply:   apply,\n\t}\n\tflagSet.BoolVar(&flag.Value, name, defaultValue, description)\n\treturn &flag\n}\n\n// NewDurationFlag creates a new time.Duration typed Flag that executes the given function\n// when applied.  The flag is attached to the given FlagSet.\nfunc NewDurationFlag(flagSet *pflag.FlagSet, name string, defaultValue time.Duration, description string, apply func(values *charts.Values, value time.Duration) error) *DurationFlag {\n\tflag := DurationFlag{\n\t\tname:    name,\n\t\tflagSet: flagSet,\n\t\tapply:   apply,\n\t}\n\tflagSet.DurationVar(&flag.Value, name, defaultValue, description)\n\treturn &flag\n}\n\n// Apply executes the stored apply function on the given Values.\nfunc (flag *UintFlag) Apply(values *charts.Values) error {\n\treturn flag.apply(values, flag.Value)\n}\n\n// IsSet returns true if and only if the Flag has been explicitly set with a value.\nfunc (flag *UintFlag) IsSet() bool {\n\treturn flag.flagSet.Changed(flag.name)\n}\n\n// Name returns the name of the flag.\nfunc (flag *UintFlag) Name() string {\n\treturn flag.name\n}\n\n// Apply executes the stored apply function on the given Values.\nfunc (flag *Int64Flag) Apply(values *charts.Values) error {\n\treturn flag.apply(values, flag.Value)\n}\n\n// IsSet returns true if and only if the Flag has been explicitly set with a value.\nfunc (flag *Int64Flag) IsSet() bool {\n\treturn flag.flagSet.Changed(flag.name)\n}\n\n// Name returns the name of the flag.\nfunc (flag *Int64Flag) Name() string {\n\treturn flag.name\n}\n\n// Apply executes the stored apply function on the given Values.\nfunc (flag *StringFlag) Apply(values *charts.Values) error {\n\treturn flag.apply(values, flag.Value)\n}\n\n// Set sets the given value to the underlying Flag\nfunc (flag *StringFlag) Set(value string) error {\n\treturn flag.flagSet.Set(flag.name, value)\n}\n\n// IsSet returns true if and only if the Flag has been explicitly set with a value.\nfunc (flag *StringFlag) IsSet() bool {\n\treturn flag.flagSet.Changed(flag.name)\n}\n\n// Name returns the name of the flag.\nfunc (flag *StringFlag) Name() string {\n\treturn flag.name\n}\n\n// Apply executes the stored apply function on the given Values.\nfunc (flag *StringSliceFlag) Apply(values *charts.Values) error {\n\treturn flag.apply(values, flag.Value)\n}\n\n// IsSet returns true if and only if the Flag has been explicitly set with a value.\nfunc (flag *StringSliceFlag) IsSet() bool {\n\treturn flag.flagSet.Changed(flag.name)\n}\n\n// Name returns the name of the flag.\nfunc (flag *StringSliceFlag) Name() string {\n\treturn flag.name\n}\n\n// Apply executes the stored apply function on the given Values.\nfunc (flag *BoolFlag) Apply(values *charts.Values) error {\n\treturn flag.apply(values, flag.Value)\n}\n\n// IsSet returns true if and only if the Flag has been explicitly set with a value.\nfunc (flag *BoolFlag) IsSet() bool {\n\treturn flag.flagSet.Changed(flag.name)\n}\n\n// Name returns the name of the flag.\nfunc (flag *BoolFlag) Name() string {\n\treturn flag.name\n}\n\n// Apply executes the stored apply function on the given Values.\nfunc (flag *DurationFlag) Apply(values *charts.Values) error {\n\treturn flag.apply(values, flag.Value)\n}\n\n// IsSet returns true if and only if the Flag has been explicitly set with a value.\nfunc (flag *DurationFlag) IsSet() bool {\n\treturn flag.flagSet.Changed(flag.name)\n}\n\n// Name returns the name of the flag.\nfunc (flag *DurationFlag) Name() string {\n\treturn flag.name\n}\n\n// ApplySetFlags iterates through the given slice of Flags and applies the\n// effect of each set flag to the given Values.  Flags effects are applied\n// in the order they appear in the slice.\nfunc ApplySetFlags(values *charts.Values, flags []Flag) error {\n\tfor _, flag := range flags {\n\t\tif flag.IsSet() {\n\t\t\terr := flag.Apply(values)\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"
  },
  {
    "path": "cli/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/cli/cmd\"\n)\n\nfunc main() {\n\troot := cmd.NewRootCmd()\n\targs := os.Args[1:]\n\tif _, _, err := root.Find(args); err != nil {\n\t\tif strings.HasPrefix(args[0], \"-\") {\n\t\t\tfmt.Fprintln(os.Stderr, \"Cannot accept flags before Linkerd extension name\")\n\t\t\tos.Exit(1)\n\t\t}\n\t\tpath, err := exec.LookPath(fmt.Sprintf(\"linkerd-%s\", args[0]))\n\t\tif err == nil {\n\t\t\t// We're working with a Linkerd plugin at this point which means\n\t\t\t// it's up to the plugin to cleanse the arguments if needed.\n\t\t\t//nolint:gosec\n\t\t\tplugin := exec.Command(path, args[1:]...)\n\t\t\tplugin.Stdin = os.Stdin\n\t\t\tplugin.Stdout = os.Stdout\n\t\t\tplugin.Stderr = os.Stderr\n\t\t\terr = plugin.Run()\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintln(os.Stderr, err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\tif err := root.Execute(); err != nil {\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "cli/table/table.go",
    "content": "package table\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"sort\"\n\t\"strings\"\n\t\"unicode/utf8\"\n)\n\ntype (\n\t// Table represents a table of data to be rendered.\n\tTable struct {\n\t\tColumns       []Column\n\t\tData          []Row\n\t\tSort          []int\n\t\tColumnSpacing string\n\t}\n\n\t// Row is a single row of data in a table.\n\tRow = []string\n\n\t// Column represents metadata about a column in a table.\n\tColumn struct {\n\t\tHeader string\n\t\tWidth  int\n\t\t// If false, render this column.\n\t\tHide bool\n\t\t// If true, set the width to the widest value in this column.\n\t\tFlexible  bool\n\t\tLeftAlign bool\n\t}\n)\n\nconst defaultColumnSpacing = \"  \"\n\n// NewTable creates a new table with the given columns and rows.\nfunc NewTable(cols []Column, data []Row) Table {\n\treturn Table{\n\t\tColumns:       cols,\n\t\tData:          data,\n\t\tSort:          []int{},\n\t\tColumnSpacing: defaultColumnSpacing,\n\t}\n}\n\n// NewColumn creates a new flexible column with the given name.\nfunc NewColumn(header string) Column {\n\treturn Column{\n\t\tHeader:   header,\n\t\tFlexible: true,\n\t\tWidth:    utf8.RuneCountInString(header),\n\t}\n}\n\n// WithLeftAlign turns on the left align of this column and returns it.\nfunc (c Column) WithLeftAlign() Column {\n\tc.LeftAlign = true\n\treturn c\n}\n\n// Render writes the full table to the given Writer.\nfunc (t *Table) Render(w io.Writer) {\n\tcolumnWidths := t.columnWidths()\n\tt.renderRow(w, t.headerRow(), columnWidths)\n\tt.sort()\n\tfor _, row := range t.Data {\n\t\tt.renderRow(w, row, columnWidths)\n\t}\n}\n\nfunc (t *Table) columnWidths() []int {\n\twidths := make([]int, len(t.Columns))\n\tfor c, col := range t.Columns {\n\t\twidth := col.Width\n\t\tif col.Flexible {\n\t\t\tfor _, row := range t.Data {\n\t\t\t\tif utf8.RuneCountInString(row[c]) > width {\n\t\t\t\t\twidth = utf8.RuneCountInString(row[c])\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\twidths[c] = width\n\t}\n\treturn widths\n}\n\nfunc (t *Table) sort() {\n\tif len(t.Sort) == 0 {\n\t\treturn\n\t}\n\tsort.Slice(t.Data, func(i, j int) bool {\n\t\tfor _, sortCol := range t.Sort {\n\t\t\tif t.Data[i][sortCol] < t.Data[j][sortCol] {\n\t\t\t\treturn true\n\t\t\t} else if t.Data[i][sortCol] > t.Data[j][sortCol] {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn false\n\t})\n}\n\nfunc (t *Table) renderRow(w io.Writer, row Row, columnWidths []int) {\n\tfor c, col := range t.Columns {\n\t\tif col.Hide {\n\t\t\tcontinue\n\t\t}\n\t\tvalue := row[c]\n\t\tif utf8.RuneCountInString(value) > columnWidths[c] {\n\t\t\tvalue = value[:columnWidths[c]]\n\t\t}\n\t\tpadding := strings.Repeat(\" \", columnWidths[c]-utf8.RuneCountInString(value))\n\t\tspacing := t.ColumnSpacing\n\t\tif strings.HasSuffix(value, \"─\") && c < len(t.Columns)-1 && strings.HasPrefix(row[c+1], \"─\") {\n\t\t\tspacing = \"──\"\n\t\t}\n\t\tif strings.HasPrefix(value, \"─\") {\n\t\t\tpadding = strings.Repeat(\"─\", columnWidths[c]-utf8.RuneCountInString(value))\n\t\t\tfmt.Fprintf(w, \"%s%s%s\", padding, value, spacing)\n\t\t} else if strings.HasSuffix(value, \"─\") {\n\t\t\tpadding = strings.Repeat(\"─\", columnWidths[c]-utf8.RuneCountInString(value))\n\t\t\tfmt.Fprintf(w, \"%s%s%s\", value, padding, spacing)\n\t\t} else if col.LeftAlign {\n\t\t\tfmt.Fprintf(w, \"%s%s%s\", value, padding, spacing)\n\t\t} else {\n\t\t\tfmt.Fprintf(w, \"%s%s%s\", padding, value, spacing)\n\t\t}\n\t}\n\tfmt.Fprint(w, \"\\n\")\n}\n\nfunc (t *Table) headerRow() Row {\n\trow := make(Row, len(t.Columns))\n\tfor c, col := range t.Columns {\n\t\trow[c] = col.Header\n\t}\n\treturn row\n}\n"
  },
  {
    "path": "controller/api/destination/client.go",
    "content": "package destination\n\nimport (\n\t\"context\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"go.opencensus.io/plugin/ocgrpc\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nconst (\n\tdestinationPort       = 8086\n\tdestinationDeployment = \"linkerd-destination\"\n)\n\n// NewClient creates a client for the control plane Destination API that\n// implements the Destination service.\nfunc NewClient(addr string) (pb.DestinationClient, *grpc.ClientConn, error) {\n\tconn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStatsHandler(&ocgrpc.ClientHandler{}))\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn pb.NewDestinationClient(conn), conn, nil\n}\n\n// NewExternalClient creates a client for the control plane Destination API\n// to run from outside a Kubernetes cluster.\nfunc NewExternalClient(ctx context.Context, controlPlaneNamespace string, kubeAPI *k8s.KubernetesAPI, pod string) (pb.DestinationClient, *grpc.ClientConn, error) {\n\tvar portForward *k8s.PortForward\n\tvar err error\n\tif pod == \"\" {\n\t\tportForward, err = k8s.NewPortForward(\n\t\t\tctx,\n\t\t\tkubeAPI,\n\t\t\tcontrolPlaneNamespace,\n\t\t\tdestinationDeployment,\n\t\t\t\"localhost\",\n\t\t\t0,\n\t\t\tdestinationPort,\n\t\t\tfalse,\n\t\t)\n\t} else {\n\t\tportForward, err = k8s.NewPodPortForward(kubeAPI, controlPlaneNamespace, pod, \"localhost\", 0, destinationPort, false)\n\t}\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tdestinationAddress := portForward.AddressAndPort()\n\tif err = portForward.Init(); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn NewClient(destinationAddress)\n}\n"
  },
  {
    "path": "controller/api/destination/dedup_profile_listener.go",
    "content": "package destination\n\nimport (\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\ntype dedupProfileListener struct {\n\tparent      watcher.ProfileUpdateListener\n\tstate       *sp.ServiceProfile\n\tinitialized bool\n\tlog         *log.Entry\n}\n\nfunc newDedupProfileListener(\n\tparent watcher.ProfileUpdateListener,\n\tlog *log.Entry,\n) watcher.ProfileUpdateListener {\n\treturn &dedupProfileListener{parent, nil, false, log}\n}\n\nfunc (p *dedupProfileListener) Update(profile *sp.ServiceProfile) {\n\tif p.initialized && profile == p.state {\n\t\tlog.Debug(\"Skipping redundant update\")\n\t\treturn\n\t}\n\tp.parent.Update(profile)\n\tp.initialized = true\n\tp.state = profile\n}\n"
  },
  {
    "path": "controller/api/destination/default_profile_listener.go",
    "content": "package destination\n\nimport (\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\ntype defaultProfileListener struct {\n\tparent  watcher.ProfileUpdateListener\n\tprofile *sp.ServiceProfile\n\tlog     *log.Entry\n}\n\nfunc newDefaultProfileListener(\n\tprofile *sp.ServiceProfile,\n\tparent watcher.ProfileUpdateListener,\n\tlog *log.Entry,\n) watcher.ProfileUpdateListener {\n\treturn &defaultProfileListener{parent, profile, log}\n}\n\nfunc (p *defaultProfileListener) Update(profile *sp.ServiceProfile) {\n\tif profile == nil {\n\t\tlog.Debug(\"Using default profile\")\n\t\tprofile = p.profile\n\t}\n\tp.parent.Update(profile)\n}\n"
  },
  {
    "path": "controller/api/destination/destination_fuzzer.go",
    "content": "package destination\n\nimport (\n\t\"testing\"\n\n\tfuzz \"github.com/AdaLogics/go-fuzz-headers\"\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\t\"github.com/linkerd/linkerd2/controller/api/util\"\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tlogging \"github.com/sirupsen/logrus\"\n)\n\nfunc init() {\n\ttesting.Init()\n}\n\n// FuzzAdd fuzzes the Add method of the destination server.\nfunc FuzzAdd(data []byte) int {\n\tf := fuzz.NewConsumer(data)\n\tset := watcher.AddressSet{}\n\terr := f.GenerateStruct(&set)\n\tif err != nil {\n\t\treturn 0\n\t}\n\tt := &testing.T{}\n\t_, translator := makeEndpointTranslator(t)\n\ttranslator.Start()\n\tdefer translator.Stop()\n\ttranslator.Add(set)\n\ttranslator.Remove(set)\n\treturn 1\n}\n\n// FuzzGet fuzzes the Get method of the destination server.\nfunc FuzzGet(data []byte) int {\n\tf := fuzz.NewConsumer(data)\n\tdest1 := &pb.GetDestination{}\n\terr := f.GenerateStruct(dest1)\n\tif err != nil {\n\t\treturn 0\n\t}\n\tdest2 := &pb.GetDestination{}\n\terr = f.GenerateStruct(dest2)\n\tif err != nil {\n\t\treturn 0\n\t}\n\tdest3 := &pb.GetDestination{}\n\terr = f.GenerateStruct(dest3)\n\tif err != nil {\n\t\treturn 0\n\t}\n\tt := &testing.T{}\n\tserver := makeServer(t)\n\n\tstream := &bufferingGetStream{\n\t\tupdates:          make(chan *pb.Update, 50),\n\t\tMockServerStream: util.NewMockServerStream(),\n\t}\n\t_ = server.Get(dest1, stream)\n\t_ = server.Get(dest2, stream)\n\t_ = server.Get(dest3, stream)\n\treturn 1\n}\n\n// FuzzGetProfile fuzzes the GetProfile method of the destination server.\nfunc FuzzGetProfile(data []byte) int {\n\tf := fuzz.NewConsumer(data)\n\tdest := &pb.GetDestination{}\n\terr := f.GenerateStruct(dest)\n\tif err != nil {\n\t\treturn 0\n\t}\n\tt := &testing.T{}\n\tserver := makeServer(t)\n\tstream := &bufferingGetProfileStream{\n\t\tupdates:          []*pb.DestinationProfile{},\n\t\tMockServerStream: util.NewMockServerStream(),\n\t}\n\tstream.Cancel()\n\t_ = server.GetProfile(dest, stream)\n\treturn 1\n}\n\n// FuzzProfileTranslatorUpdate fuzzes the Update method of the profile translator.\nfunc FuzzProfileTranslatorUpdate(data []byte) int {\n\tf := fuzz.NewConsumer(data)\n\tprofile := &sp.ServiceProfile{}\n\terr := f.GenerateStruct(profile)\n\tif err != nil {\n\t\treturn 0\n\t}\n\tt := &testing.T{}\n\n\tid := watcher.ServiceID{Namespace: \"bar\", Name: \"foo\"}\n\tserver := &mockDestinationGetProfileServer{profilesReceived: make(chan *pb.DestinationProfile, 50)}\n\ttranslator, err := newProfileTranslator(id, server, logging.WithField(\"test\", t.Name()), \"foo.bar.svc.cluster.local\", 80, nil)\n\tif err != nil {\n\t\treturn 0\n\t}\n\ttranslator.Start()\n\tdefer translator.Stop()\n\ttranslator.Update(profile)\n\treturn 1\n}\n"
  },
  {
    "path": "controller/api/destination/endpoint_profile_translator.go",
    "content": "package destination\n\nimport (\n\t\"fmt\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\tlogging \"github.com/sirupsen/logrus\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype endpointProfileTranslator struct {\n\tforceOpaqueTransport,\n\tenableH2Upgrade bool\n\tcontrollerNS        string\n\tidentityTrustDomain string\n\tdefaultOpaquePorts  map[uint32]struct{}\n\n\tmeshedHttp2ClientParams *pb.Http2ClientParams\n\n\tstream    pb.Destination_GetProfileServer\n\tendStream chan struct{}\n\n\tupdates chan *watcher.Address\n\tstop    chan struct{}\n\n\tcurrent *pb.DestinationProfile\n\n\tlog *logging.Entry\n}\n\n// endpointProfileUpdatesQueueOverflowCounter is a prometheus counter that is incremented\n// whenever the profile updates queue overflows.\n//\n// We omit ip and port labels because they are high cardinality.\nvar endpointProfileUpdatesQueueOverflowCounter = promauto.NewCounter(\n\tprometheus.CounterOpts{\n\t\tName: \"endpoint_profile_updates_queue_overflow\",\n\t\tHelp: \"A counter incremented whenever the endpoint profile updates queue overflows\",\n\t},\n)\n\n// newEndpointProfileTranslator translates pod updates and profile updates to\n// DestinationProfiles for endpoints\nfunc newEndpointProfileTranslator(\n\tforceOpaqueTransport bool,\n\tenableH2Upgrade bool,\n\tcontrollerNS,\n\tidentityTrustDomain string,\n\tdefaultOpaquePorts map[uint32]struct{},\n\tmeshedHTTP2ClientParams *pb.Http2ClientParams,\n\tstream pb.Destination_GetProfileServer,\n\tendStream chan struct{},\n\tlog *logging.Entry,\n\tqueueCapacity int,\n) *endpointProfileTranslator {\n\treturn &endpointProfileTranslator{\n\t\tforceOpaqueTransport: forceOpaqueTransport,\n\t\tenableH2Upgrade:      enableH2Upgrade,\n\t\tcontrollerNS:         controllerNS,\n\t\tidentityTrustDomain:  identityTrustDomain,\n\t\tdefaultOpaquePorts:   defaultOpaquePorts,\n\n\t\tmeshedHttp2ClientParams: meshedHTTP2ClientParams,\n\n\t\tstream:    stream,\n\t\tendStream: endStream,\n\t\tupdates:   make(chan *watcher.Address, queueCapacity),\n\t\tstop:      make(chan struct{}),\n\n\t\tlog: log.WithField(\"component\", \"endpoint-profile-translator\"),\n\t}\n}\n\n// Start initiates a goroutine which processes update events off of the\n// endpointProfileTranslator's internal queue and sends to the grpc stream as\n// appropriate. The goroutine calls non-thread-safe Send, therefore Start must\n// not be called more than once.\nfunc (ept *endpointProfileTranslator) Start() {\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase update := <-ept.updates:\n\t\t\t\tept.update(update)\n\t\t\tcase <-ept.stop:\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n}\n\n// Stop terminates the goroutine started by Start.\nfunc (ept *endpointProfileTranslator) Stop() {\n\tclose(ept.stop)\n}\n\n// Update enqueues an address update to be translated into a DestinationProfile.\n// An error is returned if the update cannot be enqueued.\nfunc (ept *endpointProfileTranslator) Update(address *watcher.Address) error {\n\tselect {\n\tcase ept.updates <- address:\n\t\t// Update has been successfully enqueued.\n\t\treturn nil\n\tdefault:\n\t\tselect {\n\t\tcase <-ept.endStream:\n\t\t\t// The endStream channel has already been closed so no action is\n\t\t\t// necessary.\n\t\t\treturn fmt.Errorf(\"profile update stream closed\")\n\t\tdefault:\n\t\t\t// We are unable to enqueue because the channel does not have capacity.\n\t\t\t// The stream has fallen too far behind and should be closed.\n\t\t\tendpointProfileUpdatesQueueOverflowCounter.Inc()\n\t\t\tclose(ept.endStream)\n\t\t\treturn fmt.Errorf(\"profile update queue full; aborting stream\")\n\t\t}\n\t}\n}\n\nfunc (ept *endpointProfileTranslator) queueLen() int {\n\treturn len(ept.updates)\n}\n\nfunc (ept *endpointProfileTranslator) update(address *watcher.Address) {\n\tvar opaquePorts map[uint32]struct{}\n\tif address.Pod != nil {\n\t\topaquePorts = watcher.GetAnnotatedOpaquePorts(address.Pod, ept.defaultOpaquePorts)\n\t} else {\n\t\topaquePorts = watcher.GetAnnotatedOpaquePortsForExternalWorkload(address.ExternalWorkload, ept.defaultOpaquePorts)\n\t}\n\tendpoint, err := ept.createEndpoint(*address, opaquePorts)\n\tif err != nil {\n\t\tept.log.Errorf(\"Failed to create endpoint for %s:%d: %s\",\n\t\t\taddress.IP, address.Port, err)\n\t\treturn\n\t}\n\tept.log.Debugf(\"Created endpoint: %+v\", endpoint)\n\n\t_, opaqueProtocol := opaquePorts[address.Port]\n\tprofile := &pb.DestinationProfile{\n\t\tRetryBudget:    defaultRetryBudget(),\n\t\tEndpoint:       endpoint,\n\t\tOpaqueProtocol: opaqueProtocol || address.OpaqueProtocol,\n\t}\n\tif proto.Equal(profile, ept.current) {\n\t\tept.log.Debugf(\"Ignoring redundant profile update: %+v\", profile)\n\t\treturn\n\t}\n\n\tept.log.Debugf(\"Sending profile update: %+v\", profile)\n\tif err := ept.stream.Send(profile); err != nil {\n\t\tept.log.Errorf(\"failed to send profile update: %s\", err)\n\t\treturn\n\t}\n\n\tept.current = profile\n}\n\nfunc (ept *endpointProfileTranslator) createEndpoint(address watcher.Address, opaquePorts map[uint32]struct{}) (*pb.WeightedAddr, error) {\n\tvar weightedAddr *pb.WeightedAddr\n\tvar err error\n\tif address.ExternalWorkload != nil {\n\t\tweightedAddr, err = createWeightedAddrForExternalWorkload(address, ept.forceOpaqueTransport, opaquePorts, ept.meshedHttp2ClientParams)\n\t} else {\n\t\tweightedAddr, err = createWeightedAddr(address, opaquePorts,\n\t\t\tept.forceOpaqueTransport, ept.enableH2Upgrade, ept.identityTrustDomain, ept.controllerNS, ept.meshedHttp2ClientParams)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// `Get` doesn't include the namespace in the per-endpoint\n\t// metadata, so it needs to be special-cased.\n\tif address.Pod != nil {\n\t\tweightedAddr.MetricLabels[\"namespace\"] = address.Pod.Namespace\n\t} else if address.ExternalWorkload != nil {\n\t\tweightedAddr.MetricLabels[\"namespace\"] = address.ExternalWorkload.Namespace\n\t}\n\n\treturn weightedAddr, err\n}\n"
  },
  {
    "path": "controller/api/destination/endpoint_profile_translator_test.go",
    "content": "package destination\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\tconsts \"github.com/linkerd/linkerd2/pkg/k8s\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestEndpointProfileTranslator(t *testing.T) {\n\t// logging.SetLevel(logging.TraceLevel)\n\t// defer logging.SetLevel(logging.PanicLevel)\n\n\tpod := corev1.Pod{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tconsts.ProxyOpaquePortsAnnotation: \"sidecar,http\",\n\t\t\t},\n\t\t},\n\t\tSpec: corev1.PodSpec{\n\t\t\tInitContainers: []corev1.Container{\n\t\t\t\tcorev1.Container{\n\t\t\t\t\tPorts: []corev1.ContainerPort{\n\t\t\t\t\t\tcorev1.ContainerPort{\n\t\t\t\t\t\t\tName:          \"sidecar\",\n\t\t\t\t\t\t\tContainerPort: 8081,\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\tContainers: []corev1.Container{\n\t\t\t\tcorev1.Container{\n\t\t\t\t\tPorts: []corev1.ContainerPort{\n\t\t\t\t\t\tcorev1.ContainerPort{\n\t\t\t\t\t\t\tName:          \"http\",\n\t\t\t\t\t\t\tContainerPort: 8080,\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\taddr := &watcher.Address{\n\t\tIP:   \"10.10.11.11\",\n\t\tPort: 8080,\n\t}\n\tpodAddr1 := &watcher.Address{\n\t\tIP:   \"10.10.11.11\",\n\t\tPort: 8080,\n\t\tPod:  &pod,\n\t}\n\tpodAddr2 := &watcher.Address{\n\t\tIP:   \"10.10.11.11\",\n\t\tPort: 8081,\n\t\tPod:  &pod,\n\t}\n\n\tt.Run(\"Sends update\", func(t *testing.T) {\n\t\tmockGetProfileServer := &mockDestinationGetProfileServer{\n\t\t\tprofilesReceived: make(chan *pb.DestinationProfile), // UNBUFFERED\n\t\t}\n\t\tlog := logging.WithField(\"test\", t.Name())\n\t\ttranslator := newEndpointProfileTranslator(\n\t\t\ttrue, true, \"cluster\", \"identity\", make(map[uint32]struct{}), nil,\n\t\t\tmockGetProfileServer,\n\t\t\tnil,\n\t\t\tlog,\n\t\t\tDefaultStreamQueueCapacity,\n\t\t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\tif err := translator.Update(addr); err != nil {\n\t\t\tt.Fatal(\"Expected update\")\n\t\t}\n\t\tselect {\n\t\tcase p := <-mockGetProfileServer.profilesReceived:\n\t\t\tlog.Debugf(\"Received update: %v\", p)\n\t\tcase <-time.After(1 * time.Second):\n\t\t\tt.Fatal(\"No update received\")\n\t\t}\n\n\t\tif err := translator.Update(addr); err != nil {\n\t\t\tt.Fatal(\"Unexpected update\")\n\t\t}\n\t\tselect {\n\t\tcase p := <-mockGetProfileServer.profilesReceived:\n\t\t\tt.Fatalf(\"Duplicate update sent: %v\", p)\n\t\tcase <-time.After(1 * time.Second):\n\t\t}\n\n\t\tif err := translator.Update(podAddr1); err != nil {\n\t\t\tt.Fatal(\"Expected update\")\n\t\t}\n\n\t\tp1 := <-mockGetProfileServer.profilesReceived\n\t\tif !p1.GetOpaqueProtocol() {\n\t\t\tt.Errorf(\"Expected port 8080 to be opaque\")\n\t\t}\n\n\t\tif err := translator.Update(podAddr2); err != nil {\n\t\t\tt.Fatal(\"Expected update\")\n\t\t}\n\n\t\tp2 := <-mockGetProfileServer.profilesReceived\n\t\tif !p2.GetOpaqueProtocol() {\n\t\t\tt.Errorf(\"Expected port 8081 to be opaque\")\n\t\t}\n\t})\n\n\tt.Run(\"Handles overflow\", func(t *testing.T) {\n\t\tmockGetProfileServer := &mockDestinationGetProfileServer{\n\t\t\tprofilesReceived: make(chan *pb.DestinationProfile, 1),\n\t\t}\n\t\tlog := logging.WithField(\"test\", t.Name())\n\t\tendStream := make(chan struct{})\n\t\tqueueCapacity := DefaultStreamQueueCapacity\n\t\ttranslator := newEndpointProfileTranslator(\n\t\t\ttrue, true, \"cluster\", \"identity\", make(map[uint32]struct{}), nil,\n\t\t\tmockGetProfileServer,\n\t\t\tendStream,\n\t\t\tlog,\n\t\t\tqueueCapacity,\n\t\t)\n\n\t\t// We avoid starting the translator so that it doesn't drain its update\n\t\t// queue and we can test the overflow behavior.\n\n\t\tfor i := 0; i < queueCapacity/2; i++ {\n\t\t\tif err := translator.Update(podAddr1); err != nil {\n\t\t\t\tt.Fatal(\"Expected update\")\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase <-endStream:\n\t\t\t\tt.Fatal(\"Stream ended prematurely\")\n\t\t\tdefault:\n\t\t\t}\n\n\t\t\tif err := translator.Update(addr); err != nil {\n\t\t\t\tt.Fatal(\"Expected update\")\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase <-endStream:\n\t\t\t\tt.Fatal(\"Stream ended prematurely\")\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\n\t\t// The queue should be full and the next update should fail.\n\t\tt.Logf(\"Queue length=%d capacity=%d\", translator.queueLen(), queueCapacity)\n\t\tif err := translator.Update(podAddr1); err == nil {\n\t\t\tif !errors.Is(err, http.ErrServerClosed) {\n\t\t\t\tt.Fatalf(\"Expected update to fail; queue=%d; capacity=%d\", translator.queueLen(), queueCapacity)\n\t\t\t}\n\t\t}\n\n\t\tselect {\n\t\tcase <-endStream:\n\t\tdefault:\n\t\t\tt.Fatal(\"Stream should have ended\")\n\t\t}\n\n\t\t// XXX We should assert that endpointProfileUpdatesQueueOverflowCounter\n\t\t// == 1 but we can't read counter values.\n\t})\n}\n"
  },
  {
    "path": "controller/api/destination/endpoint_translator.go",
    "content": "package destination\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n\t\"reflect\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2-proxy-api/go/net\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\tewv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/addr\"\n\tpkgK8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\nconst (\n\tdefaultWeight uint32 = 10000\n\n\t// inboundListenAddr is the environment variable holding the inbound\n\t// listening address for the proxy container.\n\tenvInboundListenAddr = \"LINKERD2_PROXY_INBOUND_LISTEN_ADDR\"\n\tenvAdminListenAddr   = \"LINKERD2_PROXY_ADMIN_LISTEN_ADDR\"\n\tenvControlListenAddr = \"LINKERD2_PROXY_CONTROL_LISTEN_ADDR\"\n\n\tdefaultProxyInboundPort = 4143\n)\n\n// endpointTranslator satisfies EndpointUpdateListener and translates updates\n// into Destination.Get messages.\ntype (\n\tendpointTranslator struct {\n\t\tcontrollerNS        string\n\t\tidentityTrustDomain string\n\t\tnodeTopologyZone    string\n\t\tnodeName            string\n\t\tdefaultOpaquePorts  map[uint32]struct{}\n\n\t\tforceOpaqueTransport,\n\t\tenableH2Upgrade,\n\t\tenableEndpointFiltering,\n\t\tenableIPv6,\n\n\t\textEndpointZoneWeights bool\n\n\t\tmeshedHTTP2ClientParams *pb.Http2ClientParams\n\n\t\tavailableEndpoints watcher.AddressSet\n\t\tfilteredSnapshot   watcher.AddressSet\n\t\tstream             pb.Destination_GetServer\n\t\tendStream          chan struct{}\n\t\tlog                *logging.Entry\n\t\toverflowCounter    prometheus.Counter\n\n\t\tupdates chan interface{}\n\t\tstop    chan struct{}\n\t}\n\n\taddUpdate struct {\n\t\tset watcher.AddressSet\n\t}\n\n\tremoveUpdate struct {\n\t\tset watcher.AddressSet\n\t}\n\n\tnoEndpointsUpdate struct {\n\t\texists bool\n\t}\n)\n\nvar updatesQueueOverflowCounter = promauto.NewCounterVec(\n\tprometheus.CounterOpts{\n\t\tName: \"endpoint_updates_queue_overflow\",\n\t\tHelp: \"A counter incremented whenever the endpoint updates queue overflows\",\n\t},\n\t[]string{\n\t\t\"service\",\n\t},\n)\n\nfunc newEndpointTranslator(\n\tcontrollerNS string,\n\tidentityTrustDomain string,\n\tforceOpaqueTransport,\n\tenableH2Upgrade,\n\tenableEndpointFiltering,\n\tenableIPv6,\n\textEndpointZoneWeights bool,\n\tmeshedHTTP2ClientParams *pb.Http2ClientParams,\n\tservice string,\n\tsrcNodeName string,\n\tdefaultOpaquePorts map[uint32]struct{},\n\tk8sAPI *k8s.MetadataAPI,\n\tstream pb.Destination_GetServer,\n\tendStream chan struct{},\n\tlog *logging.Entry,\n\tqueueCapacity int,\n) (*endpointTranslator, error) {\n\tlog = log.WithFields(logging.Fields{\n\t\t\"component\": \"endpoint-translator\",\n\t\t\"service\":   service,\n\t})\n\n\tnodeTopologyZone, err := getNodeTopologyZone(k8sAPI, srcNodeName)\n\tif err != nil {\n\t\tlog.Errorf(\"Failed to get node topology zone for node %s: %s\", srcNodeName, err)\n\t}\n\tavailableEndpoints := newEmptyAddressSet()\n\n\tfilteredSnapshot := newEmptyAddressSet()\n\n\tcounter, err := updatesQueueOverflowCounter.GetMetricWith(prometheus.Labels{\"service\": service})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create updates queue overflow counter: %w\", err)\n\t}\n\n\treturn &endpointTranslator{\n\t\tcontrollerNS,\n\t\tidentityTrustDomain,\n\t\tnodeTopologyZone,\n\t\tsrcNodeName,\n\t\tdefaultOpaquePorts,\n\t\tforceOpaqueTransport,\n\t\tenableH2Upgrade,\n\t\tenableEndpointFiltering,\n\t\tenableIPv6,\n\t\textEndpointZoneWeights,\n\t\tmeshedHTTP2ClientParams,\n\n\t\tavailableEndpoints,\n\t\tfilteredSnapshot,\n\t\tstream,\n\t\tendStream,\n\t\tlog,\n\t\tcounter,\n\t\tmake(chan interface{}, queueCapacity),\n\t\tmake(chan struct{}),\n\t}, nil\n}\n\nfunc (et *endpointTranslator) Add(set watcher.AddressSet) {\n\tet.enqueueUpdate(&addUpdate{set})\n}\n\nfunc (et *endpointTranslator) Remove(set watcher.AddressSet) {\n\tet.enqueueUpdate(&removeUpdate{set})\n}\n\nfunc (et *endpointTranslator) NoEndpoints(exists bool) {\n\tet.enqueueUpdate(&noEndpointsUpdate{exists})\n}\n\n// Add, Remove, and NoEndpoints are called from a client-go informer callback\n// and therefore must not block. For each of these, we enqueue an update in\n// a channel so that it can be processed asyncronously. To ensure that enqueuing\n// does not block, we first check to see if there is capacity in the buffered\n// channel. If there is not, we drop the update and signal to the stream that\n// it has fallen too far behind and should be closed.\nfunc (et *endpointTranslator) enqueueUpdate(update interface{}) {\n\tselect {\n\tcase et.updates <- update:\n\t\t// Update has been successfully enqueued.\n\tdefault:\n\t\t// We are unable to enqueue because the channel does not have capacity.\n\t\t// The stream has fallen too far behind and should be closed.\n\t\tet.overflowCounter.Inc()\n\t\tselect {\n\t\tcase <-et.endStream:\n\t\t\t// The endStream channel has already been closed so no action is\n\t\t\t// necessary.\n\t\tdefault:\n\t\t\tet.log.Error(\"endpoint update queue full; aborting stream\")\n\t\t\tclose(et.endStream)\n\t\t}\n\t}\n}\n\n// Start initiates a goroutine which processes update events off of the\n// endpointTranslator's internal queue and sends to the grpc stream as\n// appropriate. The goroutine calls several non-thread-safe functions (including\n// Send) and therefore, Start must not be called more than once.\nfunc (et *endpointTranslator) Start() {\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase update, ok := <-et.updates:\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tet.processUpdate(update)\n\t\t\tcase <-et.stop:\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n}\n\n// Stop terminates the goroutine started by Start.\nfunc (et *endpointTranslator) Stop() {\n\tclose(et.stop)\n}\n\n// DrainAndStop closes the updates channel, causing the goroutine started by\n// Start to terminate after processing all remaining updates.\nfunc (et *endpointTranslator) DrainAndStop() {\n\tclose(et.updates)\n}\n\nfunc (et *endpointTranslator) processUpdate(update interface{}) {\n\tswitch update := update.(type) {\n\tcase *addUpdate:\n\t\tet.add(update.set)\n\tcase *removeUpdate:\n\t\tet.remove(update.set)\n\tcase *noEndpointsUpdate:\n\t\tet.noEndpoints(update.exists)\n\t}\n}\n\nfunc (et *endpointTranslator) add(set watcher.AddressSet) {\n\tfor id, address := range set.Addresses {\n\t\tet.availableEndpoints.Addresses[id] = address\n\t}\n\n\tet.availableEndpoints.Labels = set.Labels\n\tet.availableEndpoints.LocalTrafficPolicy = set.LocalTrafficPolicy\n\n\tet.sendFilteredUpdate()\n}\n\nfunc (et *endpointTranslator) remove(set watcher.AddressSet) {\n\tfor id := range set.Addresses {\n\t\tdelete(et.availableEndpoints.Addresses, id)\n\t}\n\n\tet.sendFilteredUpdate()\n}\n\nfunc (et *endpointTranslator) noEndpoints(exists bool) {\n\tet.log.Debugf(\"NoEndpoints(%+v)\", exists)\n\n\tet.availableEndpoints.Addresses = map[watcher.ID]watcher.Address{}\n\n\tet.sendFilteredUpdate()\n}\n\nfunc (et *endpointTranslator) sendFilteredUpdate() {\n\tfiltered := et.filterAddresses()\n\tfiltered = et.selectAddressFamily(filtered)\n\tdiffAdd, diffRemove := et.diffEndpoints(filtered)\n\n\tif len(diffAdd.Addresses) > 0 {\n\t\tet.sendClientAdd(diffAdd)\n\t}\n\tif len(diffRemove.Addresses) > 0 {\n\t\tet.sendClientRemove(diffRemove)\n\t}\n\n\tet.filteredSnapshot = filtered\n}\n\nfunc (et *endpointTranslator) selectAddressFamily(addresses watcher.AddressSet) watcher.AddressSet {\n\tfiltered := make(map[watcher.ID]watcher.Address)\n\tfor id, addr := range addresses.Addresses {\n\t\tif id.IPFamily == corev1.IPv6Protocol && !et.enableIPv6 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif id.IPFamily == corev1.IPv4Protocol && et.enableIPv6 {\n\t\t\t// Only consider IPv4 address for which there's not already an IPv6\n\t\t\t// alternative\n\t\t\taltID := id\n\t\t\taltID.IPFamily = corev1.IPv6Protocol\n\t\t\tif _, ok := addresses.Addresses[altID]; ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tfiltered[id] = addr\n\t}\n\n\treturn watcher.AddressSet{\n\t\tAddresses:          filtered,\n\t\tLabels:             addresses.Labels,\n\t\tLocalTrafficPolicy: addresses.LocalTrafficPolicy,\n\t}\n}\n\n// filterAddresses is responsible for filtering endpoints based on the node's\n// topology zone. The client will only receive endpoints with the same\n// consumption zone as the node. An endpoints consumption zone is set\n// by its Hints field and can be different than its actual Topology zone.\n// when service.spec.internalTrafficPolicy is set to local, Topology Aware\n// Hints are not used.\nfunc (et *endpointTranslator) filterAddresses() watcher.AddressSet {\n\tfiltered := make(map[watcher.ID]watcher.Address)\n\n\t// If endpoint filtering is disabled, return all available addresses.\n\tif !et.enableEndpointFiltering {\n\t\tfor k, v := range et.availableEndpoints.Addresses {\n\t\t\tfiltered[k] = v\n\t\t}\n\t\treturn watcher.AddressSet{\n\t\t\tAddresses: filtered,\n\t\t\tLabels:    et.availableEndpoints.Labels,\n\t\t}\n\t}\n\n\t// If service.spec.internalTrafficPolicy is set to local, filter and return the addresses\n\t// for local node only\n\tif et.availableEndpoints.LocalTrafficPolicy {\n\t\tet.log.Debugf(\"Filtering through addresses that should be consumed by node %s\", et.nodeName)\n\t\tfor id, address := range et.availableEndpoints.Addresses {\n\t\t\tif address.Pod != nil && address.Pod.Spec.NodeName == et.nodeName {\n\t\t\t\tfiltered[id] = address\n\t\t\t}\n\t\t}\n\t\tet.log.Debugf(\"Filtered from %d to %d addresses\", len(et.availableEndpoints.Addresses), len(filtered))\n\t\treturn watcher.AddressSet{\n\t\t\tAddresses:          filtered,\n\t\t\tLabels:             et.availableEndpoints.Labels,\n\t\t\tLocalTrafficPolicy: et.availableEndpoints.LocalTrafficPolicy,\n\t\t}\n\t}\n\t// If any address does not have a hint, then all hints are ignored and all\n\t// available addresses are returned. This replicates kube-proxy behavior\n\t// documented in the KEP: https://github.com/kubernetes/enhancements/blob/master/keps/sig-network/2433-topology-aware-hints/README.md#kube-proxy\n\tfor _, address := range et.availableEndpoints.Addresses {\n\t\tif len(address.ForZones) == 0 {\n\t\t\tfor k, v := range et.availableEndpoints.Addresses {\n\t\t\t\tfiltered[k] = v\n\t\t\t}\n\t\t\tet.log.Debugf(\"Hints not available on endpointslice. Zone Filtering disabled. Falling back to routing to all pods\")\n\t\t\treturn watcher.AddressSet{\n\t\t\t\tAddresses:          filtered,\n\t\t\t\tLabels:             et.availableEndpoints.Labels,\n\t\t\t\tLocalTrafficPolicy: et.availableEndpoints.LocalTrafficPolicy,\n\t\t\t}\n\t\t}\n\t}\n\n\t// Each address that has a hint matching the node's zone should be added\n\t// to the set of addresses that will be returned.\n\tet.log.Debugf(\"Filtering through addresses that should be consumed by zone %s\", et.nodeTopologyZone)\n\tfor id, address := range et.availableEndpoints.Addresses {\n\t\tfor _, zone := range address.ForZones {\n\t\t\tif zone.Name == et.nodeTopologyZone {\n\t\t\t\tfiltered[id] = address\n\t\t\t}\n\t\t}\n\t}\n\tif len(filtered) > 0 {\n\t\tet.log.Debugf(\"Filtered from %d to %d addresses\", len(et.availableEndpoints.Addresses), len(filtered))\n\t\treturn watcher.AddressSet{\n\t\t\tAddresses:          filtered,\n\t\t\tLabels:             et.availableEndpoints.Labels,\n\t\t\tLocalTrafficPolicy: et.availableEndpoints.LocalTrafficPolicy,\n\t\t}\n\t}\n\n\t// If there were no filtered addresses, then fall to using endpoints from\n\t// all zones.\n\tfor k, v := range et.availableEndpoints.Addresses {\n\t\tfiltered[k] = v\n\t}\n\treturn watcher.AddressSet{\n\t\tAddresses:          filtered,\n\t\tLabels:             et.availableEndpoints.Labels,\n\t\tLocalTrafficPolicy: et.availableEndpoints.LocalTrafficPolicy,\n\t}\n}\n\n// diffEndpoints calculates the difference between the filtered set of\n// endpoints in the current (Add/Remove) operation and the snapshot of\n// previously filtered endpoints. This diff allows the client to receive only\n// the endpoints that match the topological zone, by adding new endpoints and\n// removing stale ones.\nfunc (et *endpointTranslator) diffEndpoints(filtered watcher.AddressSet) (watcher.AddressSet, watcher.AddressSet) {\n\tadd := make(map[watcher.ID]watcher.Address)\n\tremove := make(map[watcher.ID]watcher.Address)\n\n\tfor id, new := range filtered.Addresses {\n\t\told, ok := et.filteredSnapshot.Addresses[id]\n\t\tif !ok {\n\t\t\tadd[id] = new\n\t\t} else if !reflect.DeepEqual(old, new) {\n\t\t\tadd[id] = new\n\t\t}\n\t}\n\n\tfor id, address := range et.filteredSnapshot.Addresses {\n\t\tif _, ok := filtered.Addresses[id]; !ok {\n\t\t\tremove[id] = address\n\t\t}\n\t}\n\n\treturn watcher.AddressSet{\n\t\t\tAddresses: add,\n\t\t\tLabels:    filtered.Labels,\n\t\t},\n\t\twatcher.AddressSet{\n\t\t\tAddresses: remove,\n\t\t\tLabels:    filtered.Labels,\n\t\t}\n}\n\nfunc (et *endpointTranslator) sendClientAdd(set watcher.AddressSet) {\n\taddrs := []*pb.WeightedAddr{}\n\tfor _, address := range set.Addresses {\n\t\tvar (\n\t\t\twa          *pb.WeightedAddr\n\t\t\topaquePorts map[uint32]struct{}\n\t\t\terr         error\n\t\t)\n\t\tif address.Pod != nil {\n\t\t\topaquePorts = watcher.GetAnnotatedOpaquePorts(address.Pod, et.defaultOpaquePorts)\n\t\t\twa, err = createWeightedAddr(address, opaquePorts,\n\t\t\t\tet.forceOpaqueTransport, et.enableH2Upgrade, et.identityTrustDomain, et.controllerNS, et.meshedHTTP2ClientParams)\n\t\t\tif err != nil {\n\t\t\t\tet.log.Errorf(\"Failed to translate Pod endpoints to weighted addr: %s\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else if address.ExternalWorkload != nil {\n\t\t\topaquePorts = watcher.GetAnnotatedOpaquePortsForExternalWorkload(address.ExternalWorkload, et.defaultOpaquePorts)\n\t\t\twa, err = createWeightedAddrForExternalWorkload(address, et.forceOpaqueTransport, opaquePorts, et.meshedHTTP2ClientParams)\n\t\t\tif err != nil {\n\t\t\t\tet.log.Errorf(\"Failed to translate ExternalWorkload endpoints to weighted addr: %s\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\t// When there's no associated pod, we may still need to set metadata\n\t\t\t// (especially for remote multi-cluster services).\n\t\t\tvar addr *net.TcpAddress\n\t\t\taddr, err = toAddr(address)\n\t\t\tif err != nil {\n\t\t\t\tet.log.Errorf(\"Failed to translate endpoints to weighted addr: %s\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvar authOverride *pb.AuthorityOverride\n\t\t\tif address.AuthorityOverride != \"\" {\n\t\t\t\tauthOverride = &pb.AuthorityOverride{\n\t\t\t\t\tAuthorityOverride: address.AuthorityOverride,\n\t\t\t\t}\n\t\t\t}\n\t\t\twa = &pb.WeightedAddr{\n\t\t\t\tAddr:              addr,\n\t\t\t\tWeight:            defaultWeight,\n\t\t\t\tAuthorityOverride: authOverride,\n\t\t\t\tMetricLabels:      map[string]string{},\n\t\t\t}\n\n\t\t\tif address.Identity != \"\" {\n\t\t\t\twa.TlsIdentity = &pb.TlsIdentity{\n\t\t\t\t\tStrategy: &pb.TlsIdentity_DnsLikeIdentity_{\n\t\t\t\t\t\tDnsLikeIdentity: &pb.TlsIdentity_DnsLikeIdentity{\n\t\t\t\t\t\t\tName: address.Identity,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tif et.enableH2Upgrade {\n\t\t\t\t\twa.ProtocolHint = &pb.ProtocolHint{\n\t\t\t\t\t\tProtocol: &pb.ProtocolHint_H2_{\n\t\t\t\t\t\t\tH2: &pb.ProtocolHint_H2{},\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\twa.Http2 = et.meshedHTTP2ClientParams\n\t\t\t}\n\t\t}\n\n\t\tif et.nodeTopologyZone != \"\" && address.Zone != nil {\n\t\t\tif *address.Zone == et.nodeTopologyZone {\n\t\t\t\twa.MetricLabels[\"zone_locality\"] = \"local\"\n\n\t\t\t\tif et.extEndpointZoneWeights {\n\t\t\t\t\t// EXPERIMENTAL: Use the endpoint weight field to indicate zonal\n\t\t\t\t\t// preference so that local endoints are more heavily weighted.\n\t\t\t\t\twa.Weight *= 10\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twa.MetricLabels[\"zone_locality\"] = \"remote\"\n\t\t\t}\n\t\t} else {\n\t\t\twa.MetricLabels[\"zone_locality\"] = \"unknown\"\n\t\t}\n\n\t\taddrs = append(addrs, wa)\n\t}\n\n\tadd := &pb.Update{Update: &pb.Update_Add{\n\t\tAdd: &pb.WeightedAddrSet{\n\t\t\tAddrs:        addrs,\n\t\t\tMetricLabels: set.Labels,\n\t\t},\n\t}}\n\n\tet.log.Debugf(\"Sending destination add: %+v\", add)\n\tif err := et.stream.Send(add); err != nil {\n\t\tet.log.Debugf(\"Failed to send address update: %s\", err)\n\t}\n}\n\nfunc (et *endpointTranslator) sendClientRemove(set watcher.AddressSet) {\n\taddrs := []*net.TcpAddress{}\n\tfor _, address := range set.Addresses {\n\t\ttcpAddr, err := toAddr(address)\n\t\tif err != nil {\n\t\t\tet.log.Errorf(\"Failed to translate endpoints to addr: %s\", err)\n\t\t\tcontinue\n\t\t}\n\t\taddrs = append(addrs, tcpAddr)\n\t}\n\n\tremove := &pb.Update{Update: &pb.Update_Remove{\n\t\tRemove: &pb.AddrSet{\n\t\t\tAddrs: addrs,\n\t\t},\n\t}}\n\n\tet.log.Debugf(\"Sending destination remove: %+v\", remove)\n\tif err := et.stream.Send(remove); err != nil {\n\t\tet.log.Debugf(\"Failed to send address update: %s\", err)\n\t}\n}\n\nfunc toAddr(address watcher.Address) (*net.TcpAddress, error) {\n\tip, err := addr.ParseProxyIP(address.IP)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &net.TcpAddress{\n\t\tIp:   ip,\n\t\tPort: address.Port,\n\t}, nil\n}\n\nfunc createWeightedAddrForExternalWorkload(\n\taddress watcher.Address,\n\tforceOpaqueTransport bool,\n\topaquePorts map[uint32]struct{},\n\thttp2 *pb.Http2ClientParams,\n) (*pb.WeightedAddr, error) {\n\ttcpAddr, err := toAddr(address)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tweightedAddr := pb.WeightedAddr{\n\t\tAddr:         tcpAddr,\n\t\tWeight:       defaultWeight,\n\t\tMetricLabels: map[string]string{},\n\t}\n\n\tweightedAddr.MetricLabels = pkgK8s.GetExternalWorkloadLabels(address.OwnerKind, address.OwnerName, address.ExternalWorkload)\n\t// If the address is not backed by an ExternalWorkload, there is no additional metadata\n\t// to add.\n\tif address.ExternalWorkload == nil {\n\t\treturn &weightedAddr, nil\n\t}\n\n\tweightedAddr.ProtocolHint = &pb.ProtocolHint{}\n\tweightedAddr.Http2 = http2\n\n\t_, opaquePort := opaquePorts[address.Port]\n\topaquePort = opaquePort || address.OpaqueProtocol\n\n\tif forceOpaqueTransport || opaquePort {\n\t\tport := getInboundPortFromExternalWorkload(&address.ExternalWorkload.Spec)\n\t\tweightedAddr.ProtocolHint.OpaqueTransport = &pb.ProtocolHint_OpaqueTransport{InboundPort: port}\n\t}\n\n\t// If address is set as opaque by a Server, or its port is set as\n\t// opaque by annotation or default value, then set the hinted protocol to\n\t// Opaque.\n\tif opaquePort {\n\t\tweightedAddr.ProtocolHint.Protocol = &pb.ProtocolHint_Opaque_{\n\t\t\tOpaque: &pb.ProtocolHint_Opaque{},\n\t\t}\n\t} else {\n\t\tweightedAddr.ProtocolHint.Protocol = &pb.ProtocolHint_H2_{\n\t\t\tH2: &pb.ProtocolHint_H2{},\n\t\t}\n\t}\n\n\t// we assume external workloads support only SPIRE identity\n\tweightedAddr.TlsIdentity = &pb.TlsIdentity{\n\t\tStrategy: &pb.TlsIdentity_UriLikeIdentity_{\n\t\t\tUriLikeIdentity: &pb.TlsIdentity_UriLikeIdentity{\n\t\t\t\tUri: address.ExternalWorkload.Spec.MeshTLS.Identity,\n\t\t\t},\n\t\t},\n\t\tServerName: &pb.TlsIdentity_DnsLikeIdentity{\n\t\t\tName: address.ExternalWorkload.Spec.MeshTLS.ServerName,\n\t\t},\n\t}\n\n\tweightedAddr.MetricLabels = pkgK8s.GetExternalWorkloadLabels(address.OwnerKind, address.OwnerName, address.ExternalWorkload)\n\t// Set a zone label, even if it is empty (for consistency).\n\tz := \"\"\n\tif address.Zone != nil {\n\t\tz = *address.Zone\n\t}\n\tweightedAddr.MetricLabels[\"zone\"] = z\n\n\treturn &weightedAddr, nil\n}\n\nfunc createWeightedAddr(\n\taddress watcher.Address,\n\topaquePorts map[uint32]struct{},\n\tforceOpaqueTransport bool,\n\tenableH2Upgrade bool,\n\tidentityTrustDomain string,\n\tcontrollerNS string,\n\tmeshedHttp2 *pb.Http2ClientParams,\n) (*pb.WeightedAddr, error) {\n\ttcpAddr, err := toAddr(address)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tweightedAddr := pb.WeightedAddr{\n\t\tAddr:         tcpAddr,\n\t\tWeight:       defaultWeight,\n\t\tMetricLabels: map[string]string{},\n\t}\n\n\t// If the address is not backed by a pod, there is no additional metadata\n\t// to add.\n\tif address.Pod == nil {\n\t\treturn &weightedAddr, nil\n\t}\n\n\tskippedInboundPorts := getPodSkippedInboundPortsAnnotations(address.Pod)\n\n\tcontrollerNSLabel := address.Pod.Labels[pkgK8s.ControllerNSLabel]\n\tsa, ns := pkgK8s.GetServiceAccountAndNS(address.Pod)\n\tweightedAddr.MetricLabels = pkgK8s.GetPodLabels(address.OwnerKind, address.OwnerName, address.Pod)\n\n\t// Set a zone label, even if it is empty (for consistency).\n\tz := \"\"\n\tif address.Zone != nil {\n\t\tz = *address.Zone\n\t}\n\tweightedAddr.MetricLabels[\"zone\"] = z\n\n\t_, isSkippedInboundPort := skippedInboundPorts[address.Port]\n\n\tif controllerNSLabel != \"\" && !isSkippedInboundPort {\n\t\tweightedAddr.Http2 = meshedHttp2\n\t\tweightedAddr.ProtocolHint = &pb.ProtocolHint{}\n\n\t\tmetaPorts, err := getPodMetaPorts(&address.Pod.Spec)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to read pod meta ports: %w\", err)\n\t\t}\n\n\t\t_, opaquePort := opaquePorts[address.Port]\n\t\topaquePort = opaquePort || address.OpaqueProtocol\n\t\t_, isMetaPort := metaPorts[address.Port]\n\n\t\tif !isMetaPort && (forceOpaqueTransport || opaquePort) {\n\t\t\tport, err := getInboundPort(&address.Pod.Spec)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to read inbound port: %w\", err)\n\t\t\t}\n\t\t\tweightedAddr.ProtocolHint.OpaqueTransport = &pb.ProtocolHint_OpaqueTransport{InboundPort: port}\n\t\t}\n\n\t\t// If address is set as opaque by a Server, or its port is set as\n\t\t// opaque by annotation or default value, then set the hinted protocol to\n\t\t// Opaque.\n\t\tif opaquePort {\n\t\t\tweightedAddr.ProtocolHint.Protocol = &pb.ProtocolHint_Opaque_{\n\t\t\t\tOpaque: &pb.ProtocolHint_Opaque{},\n\t\t\t}\n\t\t} else if enableH2Upgrade {\n\t\t\t// If the pod is controlled by any Linkerd control plane, then it can be\n\t\t\t// hinted that this destination knows H2 (and handles our orig-proto\n\t\t\t// translation)\n\t\t\tweightedAddr.ProtocolHint.Protocol = &pb.ProtocolHint_H2_{\n\t\t\t\tH2: &pb.ProtocolHint_H2{},\n\t\t\t}\n\t\t}\n\t}\n\n\t// If the pod is controlled by the same Linkerd control plane, then it can\n\t// participate in identity with peers.\n\t//\n\t// TODO this should be relaxed to match a trust domain annotation so that\n\t// multiple meshes can participate in identity if they share trust roots.\n\tif identityTrustDomain != \"\" &&\n\t\tcontrollerNSLabel == controllerNS &&\n\t\t!isSkippedInboundPort {\n\n\t\tid := fmt.Sprintf(\"%s.%s.serviceaccount.identity.%s.%s\", sa, ns, controllerNSLabel, identityTrustDomain)\n\t\ttlsId := &pb.TlsIdentity_DnsLikeIdentity{Name: id}\n\n\t\tweightedAddr.TlsIdentity = &pb.TlsIdentity{\n\t\t\tStrategy: &pb.TlsIdentity_DnsLikeIdentity_{\n\t\t\t\tDnsLikeIdentity: tlsId,\n\t\t\t},\n\t\t\tServerName: tlsId,\n\t\t}\n\t}\n\n\treturn &weightedAddr, nil\n}\n\nfunc getNodeTopologyZone(k8sAPI *k8s.MetadataAPI, srcNode string) (string, error) {\n\tnode, err := k8sAPI.Get(k8s.Node, srcNode)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif zone, ok := node.Labels[corev1.LabelTopologyZone]; ok {\n\t\treturn zone, nil\n\t}\n\treturn \"\", nil\n}\n\nfunc newEmptyAddressSet() watcher.AddressSet {\n\treturn watcher.AddressSet{\n\t\tAddresses: make(map[watcher.ID]watcher.Address),\n\t\tLabels:    make(map[string]string),\n\t}\n}\n\n// getInboundPort gets the inbound port from the proxy container's environment\n// variable.\nfunc getInboundPort(podSpec *corev1.PodSpec) (uint32, error) {\n\tports, err := getPodPorts(podSpec, map[string]struct{}{envInboundListenAddr: {}})\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tport := ports[envInboundListenAddr]\n\tif port == 0 {\n\t\treturn 0, fmt.Errorf(\"failed to find inbound port in %s\", envInboundListenAddr)\n\t}\n\treturn port, nil\n}\n\nfunc getPodMetaPorts(podSpec *corev1.PodSpec) (map[uint32]struct{}, error) {\n\tports, err := getPodPorts(podSpec, map[string]struct{}{\n\t\tenvAdminListenAddr:   {},\n\t\tenvControlListenAddr: {},\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinvertedPorts := map[uint32]struct{}{}\n\tfor _, port := range ports {\n\t\tinvertedPorts[port] = struct{}{}\n\t}\n\treturn invertedPorts, nil\n}\n\nfunc getPodPorts(podSpec *corev1.PodSpec, addrEnvNames map[string]struct{}) (map[string]uint32, error) {\n\tcontainers := append(podSpec.InitContainers, podSpec.Containers...)\n\tfor _, containerSpec := range containers {\n\t\tports := map[string]uint32{}\n\t\tif containerSpec.Name != pkgK8s.ProxyContainerName {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, envVar := range containerSpec.Env {\n\t\t\t_, hasEnv := addrEnvNames[envVar.Name]\n\t\t\tif !hasEnv {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\taddrPort, err := netip.ParseAddrPort(envVar.Value)\n\t\t\tif err != nil {\n\t\t\t\treturn map[string]uint32{}, fmt.Errorf(\"failed to parse inbound port for proxy container: %w\", err)\n\t\t\t}\n\n\t\t\tports[envVar.Name] = uint32(addrPort.Port())\n\t\t}\n\t\tif len(ports) != len(addrEnvNames) {\n\t\t\tmissingEnv := []string{}\n\t\t\tfor env := range ports {\n\t\t\t\t_, hasEnv := addrEnvNames[env]\n\t\t\t\tif !hasEnv {\n\t\t\t\t\tmissingEnv = append(missingEnv, env)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn map[string]uint32{}, fmt.Errorf(\"failed to find %s environment variables in proxy container\", missingEnv)\n\t\t}\n\t\treturn ports, nil\n\t}\n\treturn map[string]uint32{}, fmt.Errorf(\"failed to find %s environment variables in any container for given pod spec\", addrEnvNames)\n}\n\n// getInboundPortFromExternalWorkload gets the inbound port from the ExternalWorkload spec\n// variable.\nfunc getInboundPortFromExternalWorkload(ewSpec *ewv1beta1.ExternalWorkloadSpec) uint32 {\n\tfor _, p := range ewSpec.Ports {\n\t\tif p.Name == pkgK8s.ProxyPortName {\n\t\t\treturn uint32(p.Port)\n\t\t}\n\t}\n\n\treturn defaultProxyInboundPort\n}\n"
  },
  {
    "path": "controller/api/destination/endpoint_translator_test.go",
    "content": "package destination\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2-proxy-api/go/net\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\tewv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\t\"github.com/linkerd/linkerd2/pkg/addr\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"google.golang.org/protobuf/proto\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tv1 \"k8s.io/api/discovery/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nvar (\n\tpod1 = watcher.Address{\n\t\tIP:   \"1.1.1.1\",\n\t\tPort: 1,\n\t\tPod: &corev1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"pod1\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\tk8s.ControllerNSLabel:    \"linkerd\",\n\t\t\t\t\tk8s.ProxyDeploymentLabel: \"deployment-name\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: corev1.PodSpec{\n\t\t\t\tServiceAccountName: \"serviceaccount-name\",\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: k8s.ProxyContainerName,\n\t\t\t\t\t\tEnv: []corev1.EnvVar{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envInboundListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4143\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envAdminListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4191\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envControlListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4190\",\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\tOwnerKind: \"replicationcontroller\",\n\t\tOwnerName: \"rc-name\",\n\t}\n\n\tpod1IPv6 = watcher.Address{\n\t\tIP:   \"2001:0db8:85a3:0000:0000:8a2e:0370:7333\",\n\t\tPort: 1,\n\t\tPod: &corev1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"pod1\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\tk8s.ControllerNSLabel:    \"linkerd\",\n\t\t\t\t\tk8s.ProxyDeploymentLabel: \"deployment-name\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: corev1.PodSpec{\n\t\t\t\tServiceAccountName: \"serviceaccount-name\",\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: k8s.ProxyContainerName,\n\t\t\t\t\t\tEnv: []corev1.EnvVar{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envInboundListenAddr,\n\t\t\t\t\t\t\t\tValue: \"[::]:4143\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envAdminListenAddr,\n\t\t\t\t\t\t\t\tValue: \"[::]:4191\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envControlListenAddr,\n\t\t\t\t\t\t\t\tValue: \"[::]:4190\",\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\tOwnerKind: \"replicationcontroller\",\n\t\tOwnerName: \"rc-name\",\n\t}\n\n\tpod2 = watcher.Address{\n\t\tIP:   \"1.1.1.2\",\n\t\tPort: 2,\n\t\tPod: &corev1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"pod2\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\tk8s.ControllerNSLabel:    \"linkerd\",\n\t\t\t\t\tk8s.ProxyDeploymentLabel: \"deployment-name\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: corev1.PodSpec{\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: k8s.ProxyContainerName,\n\t\t\t\t\t\tEnv: []corev1.EnvVar{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envInboundListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4143\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envAdminListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4191\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envControlListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4190\",\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\tpod3 = watcher.Address{\n\t\tIP:   \"2001:0db8:85a3:0000:0000:8a2e:0370:7334\",\n\t\tPort: 3,\n\t\tPod: &corev1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"pod3\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\tk8s.ControllerNSLabel:    \"linkerd\",\n\t\t\t\t\tk8s.ProxyDeploymentLabel: \"deployment-name\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: corev1.PodSpec{\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: k8s.ProxyContainerName,\n\t\t\t\t\t\tEnv: []corev1.EnvVar{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envInboundListenAddr,\n\t\t\t\t\t\t\t\tValue: \"[::1]:4143\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envAdminListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4191\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envControlListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4190\",\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\tpodOpaque = watcher.Address{\n\t\tIP:   \"1.1.1.4\",\n\t\tPort: 4,\n\t\tPod: &corev1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"pod4\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\tk8s.ControllerNSLabel:    \"linkerd\",\n\t\t\t\t\tk8s.ProxyDeploymentLabel: \"deployment-name\",\n\t\t\t\t},\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyOpaquePortsAnnotation: \"4\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: corev1.PodSpec{\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: k8s.ProxyContainerName,\n\t\t\t\t\t\tEnv: []corev1.EnvVar{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envInboundListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4143\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envAdminListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4191\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envControlListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4190\",\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\tOpaqueProtocol: true,\n\t}\n\n\tpodAdmin = watcher.Address{\n\t\tIP:   \"1.1.1.5\",\n\t\tPort: 4191,\n\t\tPod: &corev1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"podAdmin\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\tk8s.ControllerNSLabel:    \"linkerd\",\n\t\t\t\t\tk8s.ProxyDeploymentLabel: \"deployment-name\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: corev1.PodSpec{\n\t\t\t\tServiceAccountName: \"serviceaccount-name\",\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: k8s.ProxyContainerName,\n\t\t\t\t\t\tEnv: []corev1.EnvVar{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envInboundListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4143\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envAdminListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4191\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envControlListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4190\",\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\tOwnerKind: \"replicationcontroller\",\n\t\tOwnerName: \"rc-name\",\n\t}\n\n\tpodControl = watcher.Address{\n\t\tIP:   \"1.1.1.6\",\n\t\tPort: 4190,\n\t\tPod: &corev1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"podControl\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\tk8s.ControllerNSLabel:    \"linkerd\",\n\t\t\t\t\tk8s.ProxyDeploymentLabel: \"deployment-name\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: corev1.PodSpec{\n\t\t\t\tServiceAccountName: \"serviceaccount-name\",\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: k8s.ProxyContainerName,\n\t\t\t\t\t\tEnv: []corev1.EnvVar{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envInboundListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4143\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envAdminListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4191\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  envControlListenAddr,\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4190\",\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\tOwnerKind: \"replicationcontroller\",\n\t\tOwnerName: \"rc-name\",\n\t}\n\n\tew1 = watcher.Address{\n\t\tIP:   \"1.1.1.1\",\n\t\tPort: 1,\n\t\tExternalWorkload: &ewv1beta1.ExternalWorkload{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"ew-1\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t},\n\t\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\t\tMeshTLS: ewv1beta1.MeshTLS{\n\t\t\t\t\tIdentity:   \"spiffe://some-domain/ew-1\",\n\t\t\t\t\tServerName: \"server.local\",\n\t\t\t\t},\n\t\t\t\tPorts: []ewv1beta1.PortSpec{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: k8s.ProxyPortName,\n\t\t\t\t\t\tPort: 4143,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOwnerKind: \"workloadgroup\",\n\t\tOwnerName: \"wg-name\",\n\t}\n\n\tew2 = watcher.Address{\n\t\tIP:   \"1.1.1.2\",\n\t\tPort: 2,\n\t\tExternalWorkload: &ewv1beta1.ExternalWorkload{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"ew-2\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\tk8s.ControllerNSLabel:    \"linkerd\",\n\t\t\t\t\tk8s.ProxyDeploymentLabel: \"deployment-name\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\t\tMeshTLS: ewv1beta1.MeshTLS{\n\t\t\t\t\tIdentity:   \"spiffe://some-domain/ew-2\",\n\t\t\t\t\tServerName: \"server.local\",\n\t\t\t\t},\n\t\t\t\tPorts: []ewv1beta1.PortSpec{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: k8s.ProxyPortName,\n\t\t\t\t\t\tPort: 4143,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tew3 = watcher.Address{\n\t\tIP:   \"1.1.1.3\",\n\t\tPort: 3,\n\t\tExternalWorkload: &ewv1beta1.ExternalWorkload{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"ew-3\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\tk8s.ControllerNSLabel:    \"linkerd\",\n\t\t\t\t\tk8s.ProxyDeploymentLabel: \"deployment-name\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\t\tMeshTLS: ewv1beta1.MeshTLS{\n\t\t\t\t\tIdentity:   \"spiffe://some-domain/ew-3\",\n\t\t\t\t\tServerName: \"server.local\",\n\t\t\t\t},\n\t\t\t\tPorts: []ewv1beta1.PortSpec{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: k8s.ProxyPortName,\n\t\t\t\t\t\tPort: 4143,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tewOpaque = watcher.Address{\n\t\tIP:   \"1.1.1.4\",\n\t\tPort: 4,\n\t\tExternalWorkload: &ewv1beta1.ExternalWorkload{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"pod4\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyOpaquePortsAnnotation: \"4\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\t\tMeshTLS: ewv1beta1.MeshTLS{\n\t\t\t\t\tIdentity:   \"spiffe://some-domain/ew-opaque\",\n\t\t\t\t\tServerName: \"server.local\",\n\t\t\t\t},\n\n\t\t\t\tPorts: []ewv1beta1.PortSpec{\n\t\t\t\t\t{\n\t\t\t\t\t\tPort: 4143,\n\t\t\t\t\t\tName: \"linkerd-proxy\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOpaqueProtocol: true,\n\t}\n\n\tewNoProxyPort = watcher.Address{\n\t\tIP:   \"1.1.1.1\",\n\t\tPort: 1,\n\t\tExternalWorkload: &ewv1beta1.ExternalWorkload{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"ew-4\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t},\n\t\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\t\tMeshTLS: ewv1beta1.MeshTLS{\n\t\t\t\t\tIdentity:   \"spiffe://some-domain/ew-4\",\n\t\t\t\t\tServerName: \"server.local\",\n\t\t\t\t},\n\t\t\t\tPorts: []ewv1beta1.PortSpec{},\n\t\t\t},\n\t\t},\n\t\tOwnerKind: \"workloadgroup\",\n\t\tOwnerName: \"wg-name\",\n\t}\n\n\tewOverrideProxyPort = watcher.Address{\n\t\tIP:   \"1.1.1.1\",\n\t\tPort: 1,\n\t\tExternalWorkload: &ewv1beta1.ExternalWorkload{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"ew-4\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t},\n\t\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\t\tMeshTLS: ewv1beta1.MeshTLS{\n\t\t\t\t\tIdentity:   \"spiffe://some-domain/ew-4\",\n\t\t\t\t\tServerName: \"server.local\",\n\t\t\t\t},\n\t\t\t\tPorts: []ewv1beta1.PortSpec{{\n\t\t\t\t\tPort: 1111,\n\t\t\t\t\tName: \"linkerd-proxy\",\n\t\t\t\t}},\n\t\t\t},\n\t\t},\n\t\tOwnerKind: \"workloadgroup\",\n\t\tOwnerName: \"wg-name\",\n\t}\n\n\tremoteGateway1 = watcher.Address{\n\t\tIP:   \"1.1.1.1\",\n\t\tPort: 1,\n\t}\n\n\tremoteGateway2 = watcher.Address{\n\t\tIP:       \"1.1.1.2\",\n\t\tPort:     2,\n\t\tIdentity: \"some-identity\",\n\t}\n\n\tremoteGatewayAuthOverride = watcher.Address{\n\t\tIP:                \"1.1.1.2\",\n\t\tPort:              2,\n\t\tIdentity:          \"some-identity\",\n\t\tAuthorityOverride: \"some-auth.com:2\",\n\t}\n\n\twest1aAddress = watcher.Address{\n\t\tIP:   \"1.1.1.1\",\n\t\tPort: 1,\n\t\tForZones: []v1.ForZone{\n\t\t\t{Name: \"west-1a\"},\n\t\t},\n\t}\n\twest1bAddress = watcher.Address{\n\t\tIP:   \"1.1.1.1\",\n\t\tPort: 2,\n\t\tForZones: []v1.ForZone{\n\t\t\t{Name: \"west-1b\"},\n\t\t},\n\t}\n\tAddressOnTest123Node = watcher.Address{\n\t\tIP:   \"1.1.1.1\",\n\t\tPort: 1,\n\t\tPod: &corev1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"pod1\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\tk8s.ControllerNSLabel:    \"linkerd\",\n\t\t\t\t\tk8s.ProxyDeploymentLabel: \"deployment-name\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: corev1.PodSpec{\n\t\t\t\tNodeName: \"test-123\",\n\t\t\t},\n\t\t},\n\t}\n\tAddressNotOnTest123Node = watcher.Address{\n\t\tIP:   \"1.1.1.2\",\n\t\tPort: 2,\n\t\tPod: &corev1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"pod1\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\tk8s.ControllerNSLabel:    \"linkerd\",\n\t\t\t\t\tk8s.ProxyDeploymentLabel: \"deployment-name\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: corev1.PodSpec{\n\t\t\t\tNodeName: \"test-234\",\n\t\t\t},\n\t\t},\n\t}\n)\n\nfunc TestEndpointTranslatorForRemoteGateways(t *testing.T) {\n\tt.Run(\"Sends one update for add and another for remove\", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForServices(remoteGateway1, remoteGateway2))\n\t\ttranslator.Remove(mkAddressSetForServices(remoteGateway2))\n\n\t\texpectedNumUpdates := 2\n\t\t<-mockGetServer.updatesReceived // Add\n\t\t<-mockGetServer.updatesReceived // Remove\n\n\t\tif len(mockGetServer.updatesReceived) != 0 {\n\t\t\tt.Fatalf(\"Expecting [%d] updates, got [%d].\", expectedNumUpdates, expectedNumUpdates+len(mockGetServer.updatesReceived))\n\t\t}\n\t})\n\n\tt.Run(\"Recovers after emptying address et\", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForServices(remoteGateway1))\n\t\ttranslator.Remove(mkAddressSetForServices(remoteGateway1))\n\t\ttranslator.Add(mkAddressSetForServices(remoteGateway1))\n\n\t\texpectedNumUpdates := 3\n\t\t<-mockGetServer.updatesReceived // Add\n\t\t<-mockGetServer.updatesReceived // Remove\n\t\t<-mockGetServer.updatesReceived // Add\n\n\t\tif len(mockGetServer.updatesReceived) != 0 {\n\t\t\tt.Fatalf(\"Expecting [%d] updates, got [%d].\", expectedNumUpdates, expectedNumUpdates+len(mockGetServer.updatesReceived))\n\t\t}\n\t})\n\n\tt.Run(\"Sends TlsIdentity when enabled\", func(t *testing.T) {\n\t\texpectedTLSIdentity := &pb.TlsIdentity_DnsLikeIdentity{\n\t\t\tName: \"some-identity\",\n\t\t}\n\n\t\texpectedProtocolHint := &pb.ProtocolHint{\n\t\t\tProtocol: &pb.ProtocolHint_H2_{\n\t\t\t\tH2: &pb.ProtocolHint_H2{},\n\t\t\t},\n\t\t}\n\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForServices(remoteGateway2))\n\n\t\taddrs := (<-mockGetServer.updatesReceived).GetAdd().GetAddrs()\n\t\tif len(addrs) != 1 {\n\t\t\tt.Fatalf(\"Expected [1] address returned, got %v\", addrs)\n\t\t}\n\n\t\tactualTLSIdentity := addrs[0].GetTlsIdentity().GetDnsLikeIdentity()\n\t\tif diff := deep.Equal(actualTLSIdentity, expectedTLSIdentity); diff != nil {\n\t\t\tt.Fatalf(\"TlsIdentity: %v\", diff)\n\t\t}\n\n\t\tactualProtocolHint := addrs[0].GetProtocolHint()\n\t\tif diff := deep.Equal(actualProtocolHint, expectedProtocolHint); diff != nil {\n\t\t\tt.Fatalf(\"ProtocolHint: %v\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"Sends TlsIdentity and Auth override when present\", func(t *testing.T) {\n\t\texpectedTLSIdentity := &pb.TlsIdentity_DnsLikeIdentity{\n\t\t\tName: \"some-identity\",\n\t\t}\n\n\t\texpectedProtocolHint := &pb.ProtocolHint{\n\t\t\tProtocol: &pb.ProtocolHint_H2_{\n\t\t\t\tH2: &pb.ProtocolHint_H2{},\n\t\t\t},\n\t\t}\n\n\t\texpectedAuthOverride := &pb.AuthorityOverride{\n\t\t\tAuthorityOverride: \"some-auth.com:2\",\n\t\t}\n\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForServices(remoteGatewayAuthOverride))\n\n\t\taddrs := (<-mockGetServer.updatesReceived).GetAdd().GetAddrs()\n\t\tif len(addrs) != 1 {\n\t\t\tt.Fatalf(\"Expected [1] address returned, got %v\", addrs)\n\t\t}\n\n\t\tactualTLSIdentity := addrs[0].GetTlsIdentity().GetDnsLikeIdentity()\n\t\tif diff := deep.Equal(actualTLSIdentity, expectedTLSIdentity); diff != nil {\n\t\t\tt.Fatalf(\"TlsIdentity %v\", diff)\n\t\t}\n\n\t\tactualProtocolHint := addrs[0].GetProtocolHint()\n\t\tif diff := deep.Equal(actualProtocolHint, expectedProtocolHint); diff != nil {\n\t\t\tt.Fatalf(\"ProtocolHint %v\", diff)\n\t\t}\n\n\t\tactualAuthOverride := addrs[0].GetAuthorityOverride()\n\t\tif diff := deep.Equal(actualAuthOverride, expectedAuthOverride); diff != nil {\n\t\t\tt.Fatalf(\"AuthOverride %v\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"Does not send TlsIdentity when not present\", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForServices(remoteGateway1))\n\n\t\taddrs := (<-mockGetServer.updatesReceived).GetAdd().GetAddrs()\n\t\tif len(addrs) != 1 {\n\t\t\tt.Fatalf(\"Expected [1] address returned, got %v\", addrs)\n\t\t}\n\n\t\tif addrs[0].TlsIdentity != nil {\n\t\t\tt.Fatalf(\"Expected no TlsIdentity to be sent, but got [%v]\", addrs[0].TlsIdentity)\n\t\t}\n\t\tif addrs[0].ProtocolHint != nil {\n\t\t\tt.Fatalf(\"Expected no ProtocolHint to be sent, but got [%v]\", addrs[0].TlsIdentity)\n\t\t}\n\t})\n\n}\n\nfunc TestEndpointTranslatorForPods(t *testing.T) {\n\tt.Run(\"Sends one update for add and another for remove\", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForPods(t, pod1, pod2))\n\t\ttranslator.Remove(mkAddressSetForPods(t, pod2))\n\n\t\texpectedNumUpdates := 2\n\t\t<-mockGetServer.updatesReceived // Add\n\t\t<-mockGetServer.updatesReceived // Remove\n\n\t\tif len(mockGetServer.updatesReceived) != 0 {\n\t\t\tt.Fatalf(\"Expecting [%d] updates, got [%d].\", expectedNumUpdates, expectedNumUpdates+len(mockGetServer.updatesReceived))\n\t\t}\n\t})\n\n\tt.Run(\"Sends addresses as removed or added\", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForServices(pod1, pod2, pod3))\n\t\ttranslator.Remove(mkAddressSetForServices(pod3))\n\n\t\taddressesAdded := (<-mockGetServer.updatesReceived).GetAdd().Addrs\n\t\tactualNumberOfAdded := len(addressesAdded)\n\t\texpectedNumberOfAdded := 3\n\t\tif actualNumberOfAdded != expectedNumberOfAdded {\n\t\t\tt.Fatalf(\"Expecting [%d] addresses to be added, got [%d]: %v\", expectedNumberOfAdded, actualNumberOfAdded, addressesAdded)\n\t\t}\n\n\t\taddressesRemoved := (<-mockGetServer.updatesReceived).GetRemove().Addrs\n\t\tactualNumberOfRemoved := len(addressesRemoved)\n\t\texpectedNumberOfRemoved := 1\n\t\tif actualNumberOfRemoved != expectedNumberOfRemoved {\n\t\t\tt.Fatalf(\"Expecting [%d] addresses to be removed, got [%d]: %v\", expectedNumberOfRemoved, actualNumberOfRemoved, addressesRemoved)\n\t\t}\n\n\t\tsort.Slice(addressesAdded, func(i, j int) bool {\n\t\t\treturn addressesAdded[i].GetAddr().Port < addressesAdded[j].GetAddr().Port\n\t\t})\n\t\tcheckAddressAndWeight(t, addressesAdded[0], pod1, defaultWeight)\n\t\tcheckAddressAndWeight(t, addressesAdded[1], pod2, defaultWeight)\n\t\tcheckAddress(t, addressesRemoved[0], pod3)\n\t})\n\n\tt.Run(\"Sends addresses with opaque transport\", func(t *testing.T) {\n\t\texpectedProtocolHint := &pb.ProtocolHint{\n\t\t\tProtocol: &pb.ProtocolHint_H2_{\n\t\t\t\tH2: &pb.ProtocolHint_H2{},\n\t\t\t},\n\t\t\tOpaqueTransport: &pb.ProtocolHint_OpaqueTransport{\n\t\t\t\tInboundPort: 4143,\n\t\t\t},\n\t\t}\n\n\t\tmockGetServer, translator := makeEndpointTranslatorWithOpaqueTransport(t, true)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForPods(t, pod1, pod2, pod3))\n\n\t\taddressesAdded := (<-mockGetServer.updatesReceived).GetAdd().Addrs\n\t\tactualNumberOfAdded := len(addressesAdded)\n\t\texpectedNumberOfAdded := 3\n\t\tif actualNumberOfAdded != expectedNumberOfAdded {\n\t\t\tt.Fatalf(\"Expecting [%d] addresses to be added, got [%d]: %v\", expectedNumberOfAdded, actualNumberOfAdded, addressesAdded)\n\t\t}\n\n\t\tfor i := 0; i < 3; i++ {\n\t\t\tactualProtocolHint := addressesAdded[i].GetProtocolHint()\n\t\t\tif diff := deep.Equal(actualProtocolHint, expectedProtocolHint); diff != nil {\n\t\t\t\tt.Fatalf(\"ProtocolHint: %v\", diff)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Sends admin addresses without opaque transport\", func(t *testing.T) {\n\t\texpectedProtocolHint := &pb.ProtocolHint{\n\t\t\tProtocol: &pb.ProtocolHint_H2_{\n\t\t\t\tH2: &pb.ProtocolHint_H2{},\n\t\t\t},\n\t\t}\n\n\t\tmockGetServer, translator := makeEndpointTranslatorWithOpaqueTransport(t, true)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForPods(t, podAdmin, podControl))\n\n\t\taddressesAdded := (<-mockGetServer.updatesReceived).GetAdd().Addrs\n\t\tactualNumberOfAdded := len(addressesAdded)\n\t\texpectedNumberOfAdded := 2\n\t\tif actualNumberOfAdded != expectedNumberOfAdded {\n\t\t\tt.Fatalf(\"Expecting [%d] addresses to be added, got [%d]: %v\", expectedNumberOfAdded, actualNumberOfAdded, addressesAdded)\n\t\t}\n\n\t\tfor _, addressAdded := range addressesAdded {\n\t\t\tactualProtocolHint := addressAdded.GetProtocolHint()\n\t\t\tif diff := deep.Equal(actualProtocolHint, expectedProtocolHint); diff != nil {\n\t\t\t\tt.Fatalf(\"ProtocolHint: %v\", diff)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Sends metric labels with added addresses\", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForPods(t, pod1))\n\n\t\tupdate := <-mockGetServer.updatesReceived\n\n\t\tactualGlobalMetricLabels := update.GetAdd().MetricLabels\n\t\texpectedGlobalMetricLabels := map[string]string{\"namespace\": \"service-ns\", \"service\": \"service-name\"}\n\t\tif diff := deep.Equal(actualGlobalMetricLabels, expectedGlobalMetricLabels); diff != nil {\n\t\t\tt.Fatalf(\"Expected global metric labels sent to be [%v] but was [%v]\", expectedGlobalMetricLabels, actualGlobalMetricLabels)\n\t\t}\n\n\t\tactualAddedAddress1MetricLabels := update.GetAdd().Addrs[0].MetricLabels\n\t\texpectedAddedAddress1MetricLabels := map[string]string{\n\t\t\t\"pod\":                   \"pod1\",\n\t\t\t\"replicationcontroller\": \"rc-name\",\n\t\t\t\"serviceaccount\":        \"serviceaccount-name\",\n\t\t\t\"control_plane_ns\":      \"linkerd\",\n\t\t\t\"zone\":                  \"\",\n\t\t\t\"zone_locality\":         \"unknown\",\n\t\t}\n\t\tif diff := deep.Equal(actualAddedAddress1MetricLabels, expectedAddedAddress1MetricLabels); diff != nil {\n\t\t\tt.Fatalf(\"Expected global metric labels sent to be [%v] but was [%v]\", expectedAddedAddress1MetricLabels, actualAddedAddress1MetricLabels)\n\t\t}\n\t})\n\n\tt.Run(\"Sends TlsIdentity when enabled\", func(t *testing.T) {\n\t\texpectedTLSIdentity := &pb.TlsIdentity_DnsLikeIdentity{\n\t\t\tName: \"serviceaccount-name.ns.serviceaccount.identity.linkerd.trust.domain\",\n\t\t}\n\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForPods(t, pod1))\n\n\t\taddrs := (<-mockGetServer.updatesReceived).GetAdd().GetAddrs()\n\t\tif len(addrs) != 1 {\n\t\t\tt.Fatalf(\"Expected [1] address returned, got %v\", addrs)\n\t\t}\n\n\t\tactualTLSIdentity := addrs[0].GetTlsIdentity().GetDnsLikeIdentity()\n\t\tif diff := deep.Equal(actualTLSIdentity, expectedTLSIdentity); diff != nil {\n\t\t\tt.Fatalf(\"Expected TlsIdentity to be [%v] but was [%v]\", expectedTLSIdentity, actualTLSIdentity)\n\t\t}\n\t})\n\n\tt.Run(\"Sends Opaque ProtocolHint for opaque ports\", func(t *testing.T) {\n\t\texpectedProtocolHint := &pb.ProtocolHint{\n\t\t\tProtocol: &pb.ProtocolHint_Opaque_{\n\t\t\t\tOpaque: &pb.ProtocolHint_Opaque{},\n\t\t\t},\n\t\t\tOpaqueTransport: &pb.ProtocolHint_OpaqueTransport{\n\t\t\t\tInboundPort: 4143,\n\t\t\t},\n\t\t}\n\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForServices(podOpaque))\n\n\t\taddrs := (<-mockGetServer.updatesReceived).GetAdd().GetAddrs()\n\t\tif len(addrs) != 1 {\n\t\t\tt.Fatalf(\"Expected [1] address returned, got %v\", addrs)\n\t\t}\n\n\t\tactualProtocolHint := addrs[0].GetProtocolHint()\n\t\tif diff := deep.Equal(actualProtocolHint, expectedProtocolHint); diff != nil {\n\t\t\tt.Fatalf(\"ProtocolHint: %v\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"Sends IPv6 only when pod has both IPv4 and IPv6\", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForPods(t, pod1, pod1IPv6))\n\n\t\taddrs := (<-mockGetServer.updatesReceived).GetAdd().GetAddrs()\n\t\tif len(addrs) != 1 {\n\t\t\tt.Fatalf(\"Expected [1] address returned, got %v\", addrs)\n\t\t}\n\t\tif ipPort := addr.ProxyAddressToString(addrs[0].GetAddr()); ipPort != \"[2001:db8:85a3::8a2e:370:7333]:1\" {\n\t\t\tt.Fatalf(\"Expected address to be [%s], got [%s]\", \"[2001:db8:85a3::8a2e:370:7333]:1\", ipPort)\n\t\t}\n\n\t\tif updates := len(mockGetServer.updatesReceived); updates > 0 {\n\t\t\tt.Fatalf(\"Expected to receive no more messages, received [%d]\", updates)\n\t\t}\n\t})\n\n\tt.Run(\"Sends IPv4 only when pod has both IPv4 and IPv6 but the latter in another zone \", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\tpod1West1a := pod1\n\t\tpod1West1a.ForZones = []v1.ForZone{\n\t\t\t{Name: \"west-1a\"},\n\t\t}\n\n\t\tpod1IPv6West1b := pod1IPv6\n\t\tpod1IPv6West1b.ForZones = []v1.ForZone{\n\t\t\t{Name: \"west-1b\"},\n\t\t}\n\n\t\ttranslator.Add(mkAddressSetForPods(t, pod1West1a, pod1IPv6West1b))\n\n\t\taddrs := (<-mockGetServer.updatesReceived).GetAdd().GetAddrs()\n\t\tif len(addrs) != 1 {\n\t\t\tt.Fatalf(\"Expected [1] address returned, got %v\", addrs)\n\t\t}\n\t\tif ipPort := addr.ProxyAddressToString(addrs[0].GetAddr()); ipPort != \"1.1.1.1:1\" {\n\t\t\tt.Fatalf(\"Expected address to be [%s], got [%s]\", \"1.1.1.1:1\", ipPort)\n\t\t}\n\n\t\tif updates := len(mockGetServer.updatesReceived); updates > 0 {\n\t\t\tt.Fatalf(\"Expected to receive no more messages, received [%d]\", updates)\n\t\t}\n\t})\n}\n\nfunc TestEndpointTranslatorExternalWorkloads(t *testing.T) {\n\tt.Run(\"Sends one update for add and another for remove\", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForExternalWorkloads(ew1, ew2))\n\t\ttranslator.Remove(mkAddressSetForExternalWorkloads(ew2))\n\n\t\texpectedNumUpdates := 2\n\t\t<-mockGetServer.updatesReceived // Add\n\t\t<-mockGetServer.updatesReceived // Remove\n\n\t\tif len(mockGetServer.updatesReceived) != 0 {\n\t\t\tt.Fatalf(\"Expecting [%d] updates, got [%d].\", expectedNumUpdates, expectedNumUpdates+len(mockGetServer.updatesReceived))\n\t\t}\n\t})\n\n\tt.Run(\"Sends addresses as removed or added\", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForExternalWorkloads(ew1, ew2, ew3))\n\t\ttranslator.Remove(mkAddressSetForExternalWorkloads(ew3))\n\n\t\taddressesAdded := (<-mockGetServer.updatesReceived).GetAdd().Addrs\n\t\tactualNumberOfAdded := len(addressesAdded)\n\t\texpectedNumberOfAdded := 3\n\t\tif actualNumberOfAdded != expectedNumberOfAdded {\n\t\t\tt.Fatalf(\"Expecting [%d] addresses to be added, got [%d]: %v\", expectedNumberOfAdded, actualNumberOfAdded, addressesAdded)\n\t\t}\n\n\t\taddressesRemoved := (<-mockGetServer.updatesReceived).GetRemove().Addrs\n\t\tactualNumberOfRemoved := len(addressesRemoved)\n\t\texpectedNumberOfRemoved := 1\n\t\tif actualNumberOfRemoved != expectedNumberOfRemoved {\n\t\t\tt.Fatalf(\"Expecting [%d] addresses to be removed, got [%d]: %v\", expectedNumberOfRemoved, actualNumberOfRemoved, addressesRemoved)\n\t\t}\n\n\t\tsort.Slice(addressesAdded, func(i, j int) bool {\n\t\t\treturn addressesAdded[i].GetAddr().Port < addressesAdded[j].GetAddr().Port\n\t\t})\n\t\tcheckAddressAndWeight(t, addressesAdded[0], ew1, defaultWeight)\n\t\tcheckAddressAndWeight(t, addressesAdded[1], ew2, defaultWeight)\n\t\tcheckAddress(t, addressesRemoved[0], ew3)\n\t})\n\n\tt.Run(\"Sends metric labels with added addresses\", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForExternalWorkloads(ew1))\n\n\t\tupdate := <-mockGetServer.updatesReceived\n\n\t\tactualGlobalMetricLabels := update.GetAdd().MetricLabels\n\t\texpectedGlobalMetricLabels := map[string]string{\"namespace\": \"service-ns\", \"service\": \"service-name\"}\n\t\tif diff := deep.Equal(actualGlobalMetricLabels, expectedGlobalMetricLabels); diff != nil {\n\t\t\tt.Fatalf(\"Expected global metric labels sent to be [%v] but was [%v]\", expectedGlobalMetricLabels, actualGlobalMetricLabels)\n\t\t}\n\n\t\tactualAddedAddress1MetricLabels := update.GetAdd().Addrs[0].MetricLabels\n\t\texpectedAddedAddress1MetricLabels := map[string]string{\n\t\t\t\"external_workload\": \"ew-1\",\n\t\t\t\"zone\":              \"\",\n\t\t\t\"zone_locality\":     \"unknown\",\n\t\t\t\"workloadgroup\":     \"wg-name\",\n\t\t}\n\t\tif diff := deep.Equal(actualAddedAddress1MetricLabels, expectedAddedAddress1MetricLabels); diff != nil {\n\t\t\tt.Fatalf(\"Expected global metric labels sent to be [%v] but was [%v]\", expectedAddedAddress1MetricLabels, actualAddedAddress1MetricLabels)\n\t\t}\n\t})\n\n\tt.Run(\"Sends TlsIdentity and Server Name when enabled\", func(t *testing.T) {\n\t\texpectedTLSIdentity := &pb.TlsIdentity{\n\t\t\tStrategy: &pb.TlsIdentity_UriLikeIdentity_{\n\t\t\t\tUriLikeIdentity: &pb.TlsIdentity_UriLikeIdentity{\n\t\t\t\t\tUri: \"spiffe://some-domain/ew-1\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tServerName: &pb.TlsIdentity_DnsLikeIdentity{\n\t\t\t\tName: \"server.local\",\n\t\t\t},\n\t\t}\n\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForExternalWorkloads(ew1))\n\t\taddrs := (<-mockGetServer.updatesReceived).GetAdd().GetAddrs()\n\t\tif len(addrs) != 1 {\n\t\t\tt.Fatalf(\"Expected [1] address returned, got %v\", addrs)\n\t\t}\n\n\t\tactualTLSIdentity := addrs[0].GetTlsIdentity()\n\t\tif diff := deep.Equal(actualTLSIdentity, expectedTLSIdentity); diff != nil {\n\t\t\tt.Fatalf(\"Expected TlsIdentity to be [%v] but was [%v]\", expectedTLSIdentity, actualTLSIdentity)\n\t\t}\n\t})\n\n\tt.Run(\"Sends Opaque ProtocolHint for opaque ports\", func(t *testing.T) {\n\t\texpectedProtocolHint := &pb.ProtocolHint{\n\t\t\tProtocol: &pb.ProtocolHint_Opaque_{\n\t\t\t\tOpaque: &pb.ProtocolHint_Opaque{},\n\t\t\t},\n\t\t\tOpaqueTransport: &pb.ProtocolHint_OpaqueTransport{\n\t\t\t\tInboundPort: 4143,\n\t\t\t},\n\t\t}\n\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForExternalWorkloads(ewOpaque))\n\n\t\taddrs := (<-mockGetServer.updatesReceived).GetAdd().GetAddrs()\n\t\tif len(addrs) != 1 {\n\t\t\tt.Fatalf(\"Expected [1] address returned, got %v\", addrs)\n\t\t}\n\n\t\tactualProtocolHint := addrs[0].GetProtocolHint()\n\t\tif diff := deep.Equal(actualProtocolHint, expectedProtocolHint); diff != nil {\n\t\t\tt.Fatalf(\"ProtocolHint: %v\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"(forced opaque transport): Works with default proxy port when port missing from definition\", func(t *testing.T) {\n\t\texpectedProtocolHint := &pb.ProtocolHint{\n\t\t\tProtocol: &pb.ProtocolHint_H2_{\n\t\t\t\tH2: &pb.ProtocolHint_H2{},\n\t\t\t},\n\t\t\tOpaqueTransport: &pb.ProtocolHint_OpaqueTransport{\n\t\t\t\tInboundPort: 4143,\n\t\t\t},\n\t\t}\n\n\t\tmockGetServer, translator := makeEndpointTranslatorWithOpaqueTransport(t, true)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForExternalWorkloads(ewNoProxyPort))\n\n\t\taddrs := (<-mockGetServer.updatesReceived).GetAdd().GetAddrs()\n\t\tif len(addrs) != 1 {\n\t\t\tt.Fatalf(\"Expected [1] address returned, got %v\", addrs)\n\t\t}\n\n\t\tactualProtocolHint := addrs[0].GetProtocolHint()\n\t\tif diff := deep.Equal(actualProtocolHint, expectedProtocolHint); diff != nil {\n\t\t\tt.Fatalf(\"ProtocolHint: %v\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"(forced opaque transport): Works with override proxy port\", func(t *testing.T) {\n\t\texpectedProtocolHint := &pb.ProtocolHint{\n\t\t\tProtocol: &pb.ProtocolHint_H2_{\n\t\t\t\tH2: &pb.ProtocolHint_H2{},\n\t\t\t},\n\t\t\tOpaqueTransport: &pb.ProtocolHint_OpaqueTransport{\n\t\t\t\tInboundPort: 1111,\n\t\t\t},\n\t\t}\n\n\t\tmockGetServer, translator := makeEndpointTranslatorWithOpaqueTransport(t, true)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForExternalWorkloads(ewOverrideProxyPort))\n\n\t\taddrs := (<-mockGetServer.updatesReceived).GetAdd().GetAddrs()\n\t\tif len(addrs) != 1 {\n\t\t\tt.Fatalf(\"Expected [1] address returned, got %v\", addrs)\n\t\t}\n\n\t\tactualProtocolHint := addrs[0].GetProtocolHint()\n\t\tif diff := deep.Equal(actualProtocolHint, expectedProtocolHint); diff != nil {\n\t\t\tt.Fatalf(\"ProtocolHint: %v\", diff)\n\t\t}\n\t})\n}\n\nfunc TestEndpointTranslatorTopologyAwareFilter(t *testing.T) {\n\tt.Run(\"Sends one update for add and none for remove\", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForServices(west1aAddress, west1bAddress))\n\t\ttranslator.Remove(mkAddressSetForServices(west1bAddress))\n\n\t\t// Only the address meant for west-1a should be added, which means\n\t\t// that when we try to remove the address meant for west-1b there\n\t\t// should be no remove update.\n\t\texpectedNumUpdates := 1\n\t\t<-mockGetServer.updatesReceived // Add\n\n\t\tif len(mockGetServer.updatesReceived) != 0 {\n\t\t\tt.Fatalf(\"Expecting [%d] updates, got [%d].\", expectedNumUpdates, expectedNumUpdates+len(mockGetServer.updatesReceived))\n\t\t}\n\t})\n}\n\nfunc TestEndpointTranslatorExperimentalZoneWeights(t *testing.T) {\n\tzoneA := \"west-1a\"\n\tzoneB := \"west-1b\"\n\taddrA := watcher.Address{\n\t\tIP:   \"7.9.7.9\",\n\t\tPort: 7979,\n\t\tZone: &zoneA,\n\t}\n\taddrB := watcher.Address{\n\t\tIP:   \"9.7.9.7\",\n\t\tPort: 9797,\n\t\tZone: &zoneB,\n\t}\n\n\tt.Run(\"Disabled\", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.extEndpointZoneWeights = false\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForServices(addrA, addrB))\n\n\t\taddrs := (<-mockGetServer.updatesReceived).GetAdd().GetAddrs()\n\t\tif len(addrs) != 2 {\n\t\t\tt.Fatalf(\"Expected [2] addresses returned, got %v\", addrs)\n\t\t}\n\t\tsort.Slice(addrs, func(i, j int) bool {\n\t\t\treturn addrs[i].GetAddr().Port < addrs[j].GetAddr().Port\n\t\t})\n\t\tcheckAddressAndWeight(t, addrs[0], addrA, defaultWeight)\n\t\tcheckAddressAndWeight(t, addrs[1], addrB, defaultWeight)\n\t})\n\n\tt.Run(\"Applies weights\", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.extEndpointZoneWeights = true\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Add(mkAddressSetForServices(addrA, addrB))\n\n\t\taddrs := (<-mockGetServer.updatesReceived).GetAdd().GetAddrs()\n\t\tif len(addrs) != 2 {\n\t\t\tt.Fatalf(\"Expected [2] addresses returned, got %v\", addrs)\n\t\t}\n\t\tsort.Slice(addrs, func(i, j int) bool {\n\t\t\treturn addrs[i].GetAddr().Port < addrs[j].GetAddr().Port\n\t\t})\n\t\tcheckAddressAndWeight(t, addrs[0], addrA, defaultWeight*10)\n\t\tcheckAddressAndWeight(t, addrs[1], addrB, defaultWeight)\n\t})\n}\n\nfunc TestEndpointTranslatorForLocalTrafficPolicy(t *testing.T) {\n\tt.Run(\"Sends one update for add and none for remove\", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\t\taddressSet := mkAddressSetForServices(AddressOnTest123Node, AddressNotOnTest123Node)\n\t\taddressSet.LocalTrafficPolicy = true\n\t\ttranslator.Add(addressSet)\n\t\ttranslator.Remove(mkAddressSetForServices(AddressNotOnTest123Node))\n\n\t\t// Only the address meant for AddressOnTest123Node should be added, which means\n\t\t// that when we try to remove the address meant for AddressNotOnTest123Node there\n\t\t// should be no remove update.\n\t\texpectedNumUpdates := 1\n\t\t<-mockGetServer.updatesReceived // Add\n\n\t\tif len(mockGetServer.updatesReceived) != 0 {\n\t\t\tt.Fatalf(\"Expecting [%d] updates, got [%d].\", expectedNumUpdates, expectedNumUpdates+len(mockGetServer.updatesReceived))\n\t\t}\n\t})\n\n\tt.Run(\"Removes cannot change LocalTrafficPolicy\", func(t *testing.T) {\n\t\tmockGetServer, translator := makeEndpointTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\t\taddressSet := mkAddressSetForServices(AddressOnTest123Node, AddressNotOnTest123Node)\n\t\taddressSet.LocalTrafficPolicy = true\n\t\ttranslator.Add(addressSet)\n\t\tset := watcher.AddressSet{\n\t\t\tAddresses:          make(map[watcher.ServiceID]watcher.Address),\n\t\t\tLabels:             map[string]string{\"service\": \"service-name\", \"namespace\": \"service-ns\"},\n\t\t\tLocalTrafficPolicy: false,\n\t\t}\n\t\ttranslator.Remove(set)\n\n\t\t// Only the address meant for AddressOnTest123Node should be added.\n\t\t// The remove with no addresses should not change the LocalTrafficPolicy\n\t\t// and should be a noop that does not send an update.\n\t\texpectedNumUpdates := 1\n\t\t<-mockGetServer.updatesReceived // Add\n\n\t\tif len(mockGetServer.updatesReceived) != 0 {\n\t\t\tt.Fatalf(\"Expecting [%d] updates, got [%d].\", expectedNumUpdates, expectedNumUpdates+len(mockGetServer.updatesReceived))\n\t\t}\n\t})\n}\n\n// TestConcurrency, to be triggered with `go test -race`, shouldn't report a race condition\nfunc TestConcurrency(t *testing.T) {\n\t_, translator := makeEndpointTranslator(t)\n\ttranslator.Start()\n\tdefer translator.Stop()\n\n\tvar wg sync.WaitGroup\n\tfor i := 0; i < 10; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\ttranslator.Add(mkAddressSetForServices(west1aAddress, west1bAddress))\n\t\t\ttranslator.Remove(mkAddressSetForServices(west1bAddress))\n\t\t}()\n\t}\n\n\twg.Wait()\n}\n\nfunc TestGetInboundPort(t *testing.T) {\n\tpodSpec := &corev1.PodSpec{\n\t\tContainers: []corev1.Container{\n\t\t\t{\n\t\t\t\tName: k8s.ProxyContainerName,\n\t\t\t\tEnv: []corev1.EnvVar{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:  envInboundListenAddr,\n\t\t\t\t\t\tValue: \"1.2.3.4:8080\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tport, err := getInboundPort(podSpec)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\tif port != 8080 {\n\t\tt.Fatalf(\"Expecting port [%d], got [%d]\", 8080, port)\n\t}\n\n\tpodSpec.Containers[0].Env[0].Value = \"[2001:db8::94]:8080\"\n\tport, err = getInboundPort(podSpec)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\tif port != 8080 {\n\t\tt.Fatalf(\"Expecting port [%d], got [%d]\", 8080, port)\n\t}\n}\n\nfunc mkAddressSetForServices(gatewayAddresses ...watcher.Address) watcher.AddressSet {\n\tset := watcher.AddressSet{\n\t\tAddresses: make(map[watcher.ServiceID]watcher.Address),\n\t\tLabels:    map[string]string{\"service\": \"service-name\", \"namespace\": \"service-ns\"},\n\t}\n\tfor _, a := range gatewayAddresses {\n\t\ta := a // pin\n\n\t\tid := watcher.ServiceID{\n\t\t\tName: strings.Join([]string{\n\t\t\t\ta.IP,\n\t\t\t\tfmt.Sprint(a.Port),\n\t\t\t}, \"-\"),\n\t\t}\n\t\tset.Addresses[id] = a\n\t}\n\treturn set\n}\n\nfunc mkAddressSetForPods(t *testing.T, podAddresses ...watcher.Address) watcher.AddressSet {\n\tt.Helper()\n\n\tset := watcher.AddressSet{\n\t\tAddresses: make(map[watcher.PodID]watcher.Address),\n\t\tLabels:    map[string]string{\"service\": \"service-name\", \"namespace\": \"service-ns\"},\n\t}\n\tfor _, p := range podAddresses {\n\t\t// The IP family is set on the PodID used to index the\n\t\t// watcher.Address; here we simply detect it\n\t\tfam := corev1.IPv4Protocol\n\t\taddr, err := netip.ParseAddr(p.IP)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Invalid IP '%s': %s\", p.IP, err)\n\t\t}\n\t\tif addr.Is6() {\n\t\t\tfam = corev1.IPv6Protocol\n\t\t}\n\n\t\tid := watcher.PodID{\n\t\t\tName:      p.Pod.Name,\n\t\t\tNamespace: p.Pod.Namespace,\n\t\t\tIPFamily:  fam,\n\t\t}\n\t\tset.Addresses[id] = p\n\t}\n\treturn set\n}\n\nfunc mkAddressSetForExternalWorkloads(ewAddresses ...watcher.Address) watcher.AddressSet {\n\tset := watcher.AddressSet{\n\t\tAddresses: make(map[watcher.PodID]watcher.Address),\n\t\tLabels:    map[string]string{\"service\": \"service-name\", \"namespace\": \"service-ns\"},\n\t}\n\tfor _, ew := range ewAddresses {\n\t\tid := watcher.ExternalWorkloadID{Name: ew.ExternalWorkload.Name, Namespace: ew.ExternalWorkload.Namespace}\n\t\tset.Addresses[id] = ew\n\t}\n\treturn set\n}\n\nfunc checkAddressAndWeight(t *testing.T, actual *pb.WeightedAddr, expected watcher.Address, weight uint32) {\n\tt.Helper()\n\n\tcheckAddress(t, actual.GetAddr(), expected)\n\tif actual.GetWeight() != weight {\n\t\tt.Fatalf(\"Expected weight [%+v] but got [%+v]\", weight, actual.GetWeight())\n\t}\n}\n\nfunc checkAddress(t *testing.T, actual *net.TcpAddress, expected watcher.Address) {\n\tt.Helper()\n\n\texpectedAddr, err := addr.ParseProxyIP(expected.IP)\n\texpectedTCP := net.TcpAddress{\n\t\tIp:   expectedAddr,\n\t\tPort: expected.Port,\n\t}\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to parse expected IP [%s]: %s\", expected.IP, err)\n\t}\n\tif actual.Ip.GetIpv4() == 0 && actual.Ip.GetIpv6() == nil {\n\t\tt.Fatal(\"Actual IP is empty\")\n\t}\n\tif actual.Ip.GetIpv4() != expectedTCP.Ip.GetIpv4() {\n\t\tt.Fatalf(\"Expected IPv4 [%+v] but got [%+v]\", expectedTCP.Ip, actual.Ip)\n\t}\n\tif !proto.Equal(actual.Ip.GetIpv6(), expectedTCP.Ip.GetIpv6()) {\n\t\tt.Fatalf(\"Expected IPv6 [%+v] but got [%+v]\", expectedTCP.Ip, actual.Ip)\n\t}\n\tif actual.Port != expectedTCP.Port {\n\t\tt.Fatalf(\"Expected port [%+v] but got [%+v]\", expectedTCP.Port, actual.Port)\n\t}\n}\n"
  },
  {
    "path": "controller/api/destination/external-workload/controller_util.go",
    "content": "package externalworkload\n\nimport (\n\t\"reflect\"\n\n\tewv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\tdiscoveryv1 \"k8s.io/api/discovery/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/util/sets\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\nfunc (ec *EndpointsController) getServicesToUpdateOnExternalWorkloadChange(old, cur interface{}) sets.Set[string] {\n\tnewEw, newEwOk := cur.(*ewv1beta1.ExternalWorkload)\n\toldEw, oldEwOk := old.(*ewv1beta1.ExternalWorkload)\n\n\tif !oldEwOk {\n\t\tec.log.Errorf(\"Expected (cur) to be an EndpointSlice in getServicesToUpdateOnExternalWorkloadChange(), got type: %T\", cur)\n\t\treturn sets.Set[string]{}\n\t}\n\n\tif !newEwOk {\n\t\tec.log.Errorf(\"Expected (old) to be an EndpointSlice in getServicesToUpdateOnExternalWorkloadChange(), got type: %T\", old)\n\t\treturn sets.Set[string]{}\n\t}\n\n\tif newEw.ResourceVersion == oldEw.ResourceVersion {\n\t\t// Periodic resync will send update events for all known ExternalWorkloads.\n\t\t// Two different versions of the same pod will always have different RVs\n\t\treturn sets.Set[string]{}\n\t}\n\n\tewChanged, labelsChanged := ewEndpointsChanged(oldEw, newEw)\n\tif !ewChanged && !labelsChanged {\n\t\tec.log.Errorf(\"skipping update; nothing has changed between old rv %s and new rv %s\", oldEw.ResourceVersion, newEw.ResourceVersion)\n\t\treturn sets.Set[string]{}\n\t}\n\n\tservices, err := ec.getExternalWorkloadSvcMembership(newEw)\n\tif err != nil {\n\t\tec.log.Errorf(\"unable to get pod %s/%s's service memberships: %v\", newEw.Namespace, newEw.Name, err)\n\t\treturn sets.Set[string]{}\n\t}\n\n\tif labelsChanged {\n\t\toldServices, err := ec.getExternalWorkloadSvcMembership(oldEw)\n\t\tif err != nil {\n\t\t\tec.log.Errorf(\"unable to get pod %s/%s's service memberships: %v\", oldEw.Namespace, oldEw.Name, err)\n\t\t}\n\t\tservices = determineNeededServiceUpdates(oldServices, services, ewChanged)\n\t}\n\n\treturn services\n}\n\nfunc determineNeededServiceUpdates(oldServices, services sets.Set[string], specChanged bool) sets.Set[string] {\n\tif specChanged {\n\t\t// if the labels and spec changed, all services need to be updated\n\t\tservices = services.Union(oldServices)\n\t} else {\n\t\t// if only the labels changed, services not common to both the new\n\t\t// and old service set (the disjuntive union) need to be updated\n\t\tservices = services.Difference(oldServices).Union(oldServices.Difference(services))\n\t}\n\treturn services\n}\n\n// getExternalWorkloadSvcMembership accepts a pointer to an external workload\n// resource and returns a set of service keys (<namespace>/<name>). The set\n// includes all services local to the workload's namespace that match the workload.\nfunc (ec *EndpointsController) getExternalWorkloadSvcMembership(workload *ewv1beta1.ExternalWorkload) (sets.Set[string], error) {\n\tkeys := sets.Set[string]{}\n\tservices, err := ec.k8sAPI.Svc().Lister().Services(workload.Namespace).List(labels.Everything())\n\tif err != nil {\n\t\treturn keys, err\n\t}\n\n\tfor _, svc := range services {\n\t\tif svc.Spec.Selector == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Taken from upstream k8s code, this checks whether a given object has\n\t\t// a deleted state before returning a `namespace/name` key. This is\n\t\t// important since we do not want to consider a service that has been\n\t\t// deleted and is waiting for cache eviction\n\t\tkey, err := cache.DeletionHandlingMetaNamespaceKeyFunc(svc)\n\t\tif err != nil {\n\t\t\treturn sets.Set[string]{}, err\n\t\t}\n\n\t\t// Check if service selects our ExternalWorkload.\n\t\tif labels.ValidatedSetSelector(svc.Spec.Selector).Matches(labels.Set(workload.Labels)) {\n\t\t\tkeys.Insert(key)\n\t\t}\n\t}\n\n\treturn keys, nil\n}\n\n// getEndpointSliceFromDeleteAction parses an EndpointSlice from a delete action.\nfunc (ec *EndpointsController) getEndpointSliceFromDeleteAction(obj interface{}) *discoveryv1.EndpointSlice {\n\tif endpointSlice, ok := obj.(*discoveryv1.EndpointSlice); ok {\n\t\t// Enqueue all the services that the pod used to be a member of.\n\t\t// This is the same thing we do when we add a pod.\n\t\treturn endpointSlice\n\t}\n\t// If we reached here it means the pod was deleted but its final state is unrecorded.\n\ttombstone, ok := obj.(cache.DeletedFinalStateUnknown)\n\tif !ok {\n\t\tec.log.Errorf(\"Couldn't get object from tombstone\")\n\t\treturn nil\n\t}\n\tendpointSlice, ok := tombstone.Obj.(*discoveryv1.EndpointSlice)\n\tif !ok {\n\t\tec.log.Errorf(\"Tombstone contained object that is not a EndpointSlice\")\n\t\treturn nil\n\t}\n\treturn endpointSlice\n}\n\n// getExternalWorkloadFromDeleteAction parses an ExternalWorkload from a delete action.\nfunc (ec *EndpointsController) getExternalWorkloadFromDeleteAction(obj interface{}) *ewv1beta1.ExternalWorkload {\n\tif ew, ok := obj.(*ewv1beta1.ExternalWorkload); ok {\n\t\treturn ew\n\t}\n\n\t// If we reached here it means the pod was deleted but its final state is unrecorded.\n\ttombstone, ok := obj.(cache.DeletedFinalStateUnknown)\n\tif !ok {\n\t\tec.log.Errorf(\"couldn't get object from tombstone %#v\", obj)\n\t\treturn nil\n\t}\n\n\tew, ok := tombstone.Obj.(*ewv1beta1.ExternalWorkload)\n\tif !ok {\n\t\tec.log.Errorf(\"tombstone contained object that is not a ExternalWorkload: %#v\", obj)\n\t\treturn nil\n\t}\n\treturn ew\n}\n\n// ewEndpointsChanged returns two boolean values. The first is true if the ExternalWorkload has\n// changed in a way that may change existing endpoints. The second value is true if the\n// ExternalWorkload has changed in a way that may affect which Services it matches.\nfunc ewEndpointsChanged(oldEw, newEw *ewv1beta1.ExternalWorkload) (bool, bool) {\n\t// Check if the ExternalWorkload labels have changed, indicating a possible\n\t// change in the service membership\n\tlabelsChanged := false\n\tif !reflect.DeepEqual(newEw.Labels, oldEw.Labels) {\n\t\tlabelsChanged = true\n\t}\n\n\t// If the ExternalWorkload's deletion timestamp is set, remove endpoint from ready address.\n\tif newEw.DeletionTimestamp != oldEw.DeletionTimestamp {\n\t\treturn true, labelsChanged\n\t}\n\t// If the ExternalWorkload's readiness has changed, the associated endpoint address\n\t// will move from the unready endpoints set to the ready endpoints.\n\t// So for the purposes of an endpoint, a readiness change on an ExternalWorkload\n\t// means we have a changed ExternalWorkload.\n\tif IsEwReady(oldEw) != IsEwReady(newEw) {\n\t\treturn true, labelsChanged\n\t}\n\n\t// Check if the ExternalWorkload IPs have changed\n\tif len(oldEw.Spec.WorkloadIPs) != len(newEw.Spec.WorkloadIPs) {\n\t\treturn true, labelsChanged\n\t}\n\tfor i := range oldEw.Spec.WorkloadIPs {\n\t\tif oldEw.Spec.WorkloadIPs[i].Ip != newEw.Spec.WorkloadIPs[i].Ip {\n\t\t\treturn true, labelsChanged\n\t\t}\n\t}\n\n\t// Check if the Ports  have changed\n\tif len(oldEw.Spec.Ports) != len(newEw.Spec.Ports) {\n\t\treturn true, labelsChanged\n\t}\n\n\t// Determine if the ports have changed between workload resources\n\tportSet := make(map[int32]ewv1beta1.PortSpec)\n\tfor _, ps := range newEw.Spec.Ports {\n\t\tportSet[ps.Port] = ps\n\t}\n\n\tfor _, oldPs := range oldEw.Spec.Ports {\n\t\t// If the port number is present in the new workload but not the old\n\t\t// one, then we have a diff and we return early\n\t\tnewPs, ok := portSet[oldPs.Port]\n\t\tif !ok {\n\t\t\treturn true, labelsChanged\n\t\t}\n\n\t\t// If the port is present in both workloads, we check to see if any of\n\t\t// the port spec's values have changed, e.g. name or protocol\n\t\tif newPs.Name != oldPs.Name || newPs.Protocol != oldPs.Protocol {\n\t\t\treturn true, labelsChanged\n\t\t}\n\t}\n\n\treturn false, labelsChanged\n}\n\nfunc managedByController(es *discoveryv1.EndpointSlice) bool {\n\tesManagedBy := es.Labels[discoveryv1.LabelManagedBy]\n\treturn managedBy == esManagedBy\n}\n\nfunc managedByChanged(endpointSlice1, endpointSlice2 *discoveryv1.EndpointSlice) bool {\n\treturn managedByController(endpointSlice1) != managedByController(endpointSlice2)\n}\n\nfunc IsEwReady(ew *ewv1beta1.ExternalWorkload) bool {\n\tif len(ew.Status.Conditions) == 0 {\n\t\treturn false\n\t}\n\n\t// Loop through the conditions and look at each condition in turn starting\n\t// from the top.\n\tfor i := range ew.Status.Conditions {\n\t\tcond := ew.Status.Conditions[i]\n\t\t// Stop once we find a 'Ready' condition. We expect a resource to only\n\t\t// have one 'Ready' type condition.\n\t\tif cond.Type == ewv1beta1.WorkloadReady && cond.Status == ewv1beta1.ConditionTrue {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "controller/api/destination/external-workload/endpoints_controller.go",
    "content": "package externalworkload\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\tewv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tdiscoveryv1 \"k8s.io/api/discovery/v1\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/client-go/tools/cache\"\n\t\"k8s.io/client-go/tools/leaderelection\"\n\t\"k8s.io/client-go/tools/leaderelection/resourcelock\"\n\t\"k8s.io/client-go/util/workqueue\"\n\tendpointslicerec \"k8s.io/endpointslice\"\n\tepsliceutil \"k8s.io/endpointslice/util\"\n)\n\nconst (\n\t// Name of the lease resource the controller will use\n\tleaseName = \"linkerd-destination-endpoint-write\"\n\n\t// Duration of the lease\n\t// Core controllers (kube-controller-manager) has a duration of 15 seconds\n\tleaseDuration = 30 * time.Second\n\n\t// Deadline for the leader to refresh its lease. Core controllers have a\n\t// deadline of 10 seconds.\n\tleaseRenewDeadline = 10 * time.Second\n\n\t// Duration a leader elector should wait in between action re-tries.\n\t// Core controllers have a value of 2 seconds.\n\tleaseRetryPeriod = 2 * time.Second\n\n\t// Name of the controller. Used as an annotation value for all created\n\t// EndpointSlice objects\n\tmanagedBy = \"linkerd-external-workloads-controller\"\n\n\t// Max number of endpoints per EndpointSlice\n\tmaxEndpointsQuota = 100\n\n\t// Max retries for a service to be reconciled\n\tmaxRetryBudget = 15\n)\n\n// EndpointsController reconciles service memberships for ExternalWorkload resources\n// by writing EndpointSlice objects for Services that select one or more\n// external endpoints.\ntype EndpointsController struct {\n\tk8sAPI     *k8s.API\n\tlog        *logging.Entry\n\tqueue      workqueue.TypedRateLimitingInterface[string]\n\treconciler *endpointsReconciler\n\tstop       chan struct{}\n\n\tlec leaderelection.LeaderElectionConfig\n\tinformerHandlers\n\tdropsMetric workqueue.CounterMetric\n}\n\n// informerHandlers holds handles to callbacks that have been registered with\n// the API Server client's informers.\n//\n// These callbacks will be registered when a controller is elected as leader,\n// and de-registered when the lease is lost.\ntype informerHandlers struct {\n\tewHandle  cache.ResourceEventHandlerRegistration\n\tesHandle  cache.ResourceEventHandlerRegistration\n\tsvcHandle cache.ResourceEventHandlerRegistration\n\n\t// Mutex to guard handler registration since the elector loop may start\n\t// executing callbacks when a controller starts reading in a background task\n\tsync.Mutex\n}\n\n// The EndpointsController code has been structured (and modified) based on the\n// core EndpointSlice controller. Copyright 2014 The Kubernetes Authors\n// https://github.com/kubernetes/kubernetes/blob/29fad383dab0dd7b7b563ec9eae10156616a6f34/pkg/controller/endpointslice/endpointslice_controller.go\n//\n// There are some fundamental differences between the core endpoints controller\n// and Linkerd's endpoints controller; for one, the churn rate is expected to be\n// much lower for a controller that reconciles ExternalWorkload resources.\n// Furthermore, the structure of the resource is different, statuses do not\n// contain as many conditions, and the lifecycle of an ExternalWorkload is\n// different to that of a Pod (e.g. a workload is long lived).\n//\n// NewEndpointsController creates a new controller. The controller must be\n// started with its `Start()` method.\nfunc NewEndpointsController(k8sAPI *k8s.API, hostname, controllerNs string, stopCh chan struct{}, exportQueueMetrics bool) (*EndpointsController, error) {\n\tqueueName := \"endpoints_controller_workqueue\"\n\tworkQueueConfig := workqueue.TypedRateLimitingQueueConfig[string]{\n\t\tName: queueName,\n\t}\n\n\tvar dropsMetric workqueue.CounterMetric = &noopCounterMetric{}\n\tif exportQueueMetrics {\n\t\tprovider := newWorkQueueMetricsProvider()\n\t\tworkQueueConfig.MetricsProvider = provider\n\t\tdropsMetric = provider.NewDropsMetric(queueName)\n\t}\n\n\tec := &EndpointsController{\n\t\tk8sAPI:     k8sAPI,\n\t\treconciler: newEndpointsReconciler(k8sAPI, managedBy, maxEndpointsQuota),\n\t\tqueue:      workqueue.NewTypedRateLimitingQueueWithConfig[string](workqueue.DefaultTypedControllerRateLimiter[string](), workQueueConfig),\n\t\tstop:       stopCh,\n\t\tlog: logging.WithFields(logging.Fields{\n\t\t\t\"component\": \"external-endpoints-controller\",\n\t\t}),\n\t\tdropsMetric: dropsMetric,\n\t}\n\n\t// Store configuration for leader elector client. The leader elector will\n\t// accept three callbacks. When a lease is claimed, the elector will mark\n\t// the manager as a 'leader'. When a lease is released, the elector will set\n\t// the isLeader value back to false.\n\tec.lec = leaderelection.LeaderElectionConfig{\n\t\t// When runtime context is cancelled, lock will be released. Implies any\n\t\t// code guarded by the lease _must_ finish before cancelling.\n\t\tReleaseOnCancel: true,\n\t\tLock: &resourcelock.LeaseLock{\n\t\t\tLeaseMeta: metav1.ObjectMeta{\n\t\t\t\tName:      leaseName,\n\t\t\t\tNamespace: controllerNs,\n\t\t\t},\n\t\t\tClient: k8sAPI.Client.CoordinationV1(),\n\t\t\tLockConfig: resourcelock.ResourceLockConfig{\n\t\t\t\tIdentity: hostname,\n\t\t\t},\n\t\t},\n\t\tLeaseDuration: leaseDuration,\n\t\tRenewDeadline: leaseRenewDeadline,\n\t\tRetryPeriod:   leaseRetryPeriod,\n\t\tCallbacks: leaderelection.LeaderCallbacks{\n\t\t\tOnStartedLeading: func(ctx context.Context) {\n\t\t\t\terr := ec.addHandlers()\n\t\t\t\tif err != nil {\n\t\t\t\t\t// If the leader has failed to register callbacks then\n\t\t\t\t\t// panic; we are in a bad state that's hard to recover from\n\t\t\t\t\t// gracefully.\n\t\t\t\t\tpanic(fmt.Sprintf(\"failed to register event handlers: %v\", err))\n\t\t\t\t}\n\t\t\t},\n\t\t\tOnStoppedLeading: func() {\n\t\t\t\terr := ec.removeHandlers()\n\t\t\t\tif err != nil {\n\t\t\t\t\t// If the leader has failed to de-register callbacks then\n\t\t\t\t\t// panic; otherwise, we risk racing with the newly elected\n\t\t\t\t\t// leader\n\t\t\t\t\tpanic(fmt.Sprintf(\"failed to de-register event handlers: %v\", err))\n\t\t\t\t}\n\t\t\t\tec.log.Infof(\"%s released lease\", hostname)\n\t\t\t},\n\t\t\tOnNewLeader: func(identity string) {\n\t\t\t\tif identity == hostname {\n\t\t\t\t\tec.log.Infof(\"%s acquired lease\", hostname)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\treturn ec, nil\n}\n\n// addHandlers will register a set of callbacks with the different informers\n// needed to synchronise endpoint state.\nfunc (ec *EndpointsController) addHandlers() error {\n\tvar err error\n\tec.Lock()\n\tdefer ec.Unlock()\n\n\t// Wipe out previously observed state. This ensures we will not have stale\n\t// cache errors due to events that happened when callbacks were not firing.\n\tec.reconciler.endpointTracker = epsliceutil.NewEndpointSliceTracker()\n\n\tec.svcHandle, err = ec.k8sAPI.Svc().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc:    ec.onServiceUpdate,\n\t\tDeleteFunc: ec.onServiceUpdate,\n\t\tUpdateFunc: func(_, newObj interface{}) {\n\t\t\tec.onServiceUpdate(newObj)\n\t\t},\n\t})\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tec.esHandle, err = ec.k8sAPI.ES().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc:    ec.onEndpointSliceAdd,\n\t\tUpdateFunc: ec.onEndpointSliceUpdate,\n\t\tDeleteFunc: ec.onEndpointSliceDelete,\n\t})\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tec.ewHandle, err = ec.k8sAPI.ExtWorkload().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc:    ec.onAddExternalWorkload,\n\t\tDeleteFunc: ec.onDeleteExternalWorkload,\n\t\tUpdateFunc: ec.onUpdateExternalWorkload,\n\t})\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// removeHandlers will de-register callbacks\nfunc (ec *EndpointsController) removeHandlers() error {\n\tvar err error\n\tec.Lock()\n\tdefer ec.Unlock()\n\tif ec.svcHandle != nil {\n\t\tif err = ec.k8sAPI.Svc().Informer().RemoveEventHandler(ec.svcHandle); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif ec.ewHandle != nil {\n\t\tif err = ec.k8sAPI.ExtWorkload().Informer().RemoveEventHandler(ec.ewHandle); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif ec.esHandle != nil {\n\t\tif err = ec.k8sAPI.ES().Informer().RemoveEventHandler(ec.esHandle); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Start will run the endpoint manager's processing loop and leader elector.\n//\n// The function will spawn three background tasks; one to run the leader elector\n// client, one that will process updates applied by the informer\n// callbacks and one to handle shutdown signals and propagate them to all\n// components.\n//\n// Warning: Do not call Start() more than once\nfunc (ec *EndpointsController) Start() {\n\t// Create a parent context that will be used by leader elector to gracefully\n\t// shutdown.\n\t//\n\t// When cancelled (either through cancel function or by having its done\n\t// channel closed), the leader elector will release the lease and stop its\n\t// execution.\n\tctx, cancel := context.WithCancel(context.Background())\n\tgo func() {\n\t\tfor {\n\t\t\t// Block until a lease is acquired or a lease has been released\n\t\t\tleaderelection.RunOrDie(ctx, ec.lec)\n\t\t\t// If the context has been cancelled, exit the function, otherwise\n\t\t\t// continue spinning.\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\tec.log.Trace(\"leader election client received shutdown signal\")\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t}()\n\n\t// When a shutdown signal is received over the manager's stop channel, it is\n\t// propagated to the elector through the context object and to the queue\n\t// through its dedicated `Shutdown()` function.\n\tgo func() {\n\t\t// Block until a shutdown signal arrives\n\t\t<-ec.stop\n\t\t// Drain the queue before signalling the lease to terminate\n\t\tec.queue.ShutDownWithDrain()\n\t\t// Propagate shutdown to elector\n\t\tcancel()\n\t\tec.log.Infof(\"received shutdown signal\")\n\t}()\n\n\t// Start a background task to process updates.\n\tgo ec.processQueue()\n}\n\n// processQueue spins and pops elements off the queue. When the queue has\n// received a shutdown signal it exists.\n//\n// The queue uses locking internally so this function is thread safe and can\n// have many workers call it in parallel; workers will not process the same item\n// at the same time.\nfunc (ec *EndpointsController) processQueue() {\n\tfor {\n\t\tkey, quit := ec.queue.Get()\n\t\tif quit {\n\t\t\tec.log.Trace(\"queue received shutdown signal\")\n\t\t\treturn\n\t\t}\n\n\t\terr := ec.syncService(key)\n\t\tec.handleError(err, key)\n\n\t\t// Tell the queue that we are done processing this key. This will\n\t\t// unblock the key for other workers to process if executing in\n\t\t// parallel, or if it needs to be re-queued because another update has\n\t\t// been received.\n\t\tec.queue.Done(key)\n\t}\n}\n\n// handleError will look at the result of the queue update processing step and\n// decide whether an update should be re-tried or marked as done.\n//\n// The queue operates with an error budget. When exceeded, the item is evicted\n// from the queue (and its retry history wiped). Otherwise, the item is enqueued\n// according to the queue's rate limiting algorithm.\nfunc (ec *EndpointsController) handleError(err error, key string) {\n\tif err == nil {\n\t\t// Wipe out rate limiting history for key when processing was successful.\n\t\t// Next time this key is used, it will get its own fresh rate limiter\n\t\t// error budget\n\t\tec.queue.Forget(key)\n\t\treturn\n\t}\n\n\tif ec.queue.NumRequeues(key) < maxRetryBudget {\n\t\tec.queue.AddRateLimited(key)\n\t\treturn\n\t}\n\n\tec.queue.Forget(key)\n\tec.dropsMetric.Inc()\n\tec.log.Errorf(\"dropped Service %s out of update queue: %v\", key, err)\n}\n\n// syncService will run a reconciliation function for a single Service object\n// that needs to have its EndpointSlice objects reconciled.\nfunc (ec *EndpointsController) syncService(update string) error {\n\tnamespace, name, err := cache.SplitMetaNamespaceKey(update)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsvc, err := ec.k8sAPI.Svc().Lister().Services(namespace).Get(name)\n\tif err != nil {\n\t\t// If the error is anything except a 'NotFound' then bubble up the error\n\t\t// and re-queue the entry; the service will be re-processed at some\n\t\t// point in the future.\n\t\tif !kerrors.IsNotFound(err) {\n\t\t\treturn err\n\t\t}\n\n\t\tec.reconciler.endpointTracker.DeleteService(namespace, name)\n\t\t// The service has been deleted, return nil so that it won't be retried.\n\t\treturn nil\n\t}\n\n\tif svc.Spec.Type == corev1.ServiceTypeExternalName {\n\t\t// services with Type ExternalName do not receive any endpoints\n\t\treturn nil\n\t}\n\n\tif svc.Spec.Selector == nil {\n\t\t// services without a selector will not get any endpoints automatically\n\t\t// created; this is done out-of-band by the service operator\n\t\treturn nil\n\t}\n\n\tewSelector := labels.Set(svc.Spec.Selector).AsSelectorPreValidated()\n\tews, err := ec.k8sAPI.ExtWorkload().Lister().List(ewSelector)\n\tif err != nil {\n\t\t// This operation should be infallible since we retrieve from the cache\n\t\t// (we can guarantee we will receive at least an empty list), for good\n\t\t// measure, bubble up the error if one will be returned by the informer.\n\t\treturn err\n\t}\n\n\tesSelector := labels.Set(map[string]string{\n\t\tdiscoveryv1.LabelServiceName: svc.Name,\n\t\tdiscoveryv1.LabelManagedBy:   managedBy,\n\t}).AsSelectorPreValidated()\n\tepSlices, err := ec.k8sAPI.ES().Lister().List(esSelector)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tepSlices = dropEndpointSlicesPendingDeletion(epSlices)\n\tif ec.reconciler.endpointTracker.StaleSlices(svc, epSlices) {\n\t\tec.log.Warnf(\"detected EndpointSlice informer cache is out of date when processing %s\", update)\n\t\treturn errors.New(\"EndpointSlice informer cache is out of date\")\n\t}\n\terr = ec.reconciler.reconcile(svc, ews, epSlices)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// When a service update has been received (regardless of the event type, i.e.\n// can be Added, Modified, Deleted) send it to the endpoint controller for\n// processing.\nfunc (ec *EndpointsController) onServiceUpdate(obj interface{}) {\n\tkey, err := cache.MetaNamespaceKeyFunc(obj)\n\tif err != nil {\n\t\tec.log.Infof(\"failed to get key for object %+v: %v\", obj, err)\n\t\treturn\n\t}\n\n\tnamespace, _, err := cache.SplitMetaNamespaceKey(key)\n\tif err != nil {\n\t\tec.log.Infof(\"failed to get namespace from key %s: %v\", key, err)\n\t}\n\n\t// Skip processing 'core' services\n\tif namespace == \"kube-system\" {\n\t\treturn\n\t}\n\n\tec.queue.Add(key)\n}\n\n// onEndpointSliceAdd queues a sync for the relevant Service for a sync if the\n// EndpointSlice resource version does not match the expected version in the\n// endpointSliceTracker.\nfunc (ec *EndpointsController) onEndpointSliceAdd(obj interface{}) {\n\tes := obj.(*discoveryv1.EndpointSlice)\n\tif es == nil {\n\t\tec.log.Info(\"Invalid EndpointSlice provided to onEndpointSliceAdd()\")\n\t\treturn\n\t}\n\n\tif managedByController(es) && ec.reconciler.endpointTracker.ShouldSync(es) {\n\t\tec.queueServiceForEndpointSlice(es)\n\t}\n}\n\n// onEndpointSliceUpdate queues a sync for the relevant Service for a sync if\n// the EndpointSlice resource version does not match the expected version in the\n// endpointSliceTracker or the managed-by value of the EndpointSlice has changed\n// from or to this controller.\nfunc (ec *EndpointsController) onEndpointSliceUpdate(prevObj, obj interface{}) {\n\tprevEndpointSlice := prevObj.(*discoveryv1.EndpointSlice)\n\tendpointSlice := obj.(*discoveryv1.EndpointSlice)\n\tif endpointSlice == nil || prevEndpointSlice == nil {\n\t\tec.log.Info(\"Invalid EndpointSlice provided to onEndpointSliceUpdate()\")\n\t\treturn\n\t}\n\n\t// EndpointSlice generation does not change when labels change. Although the\n\t// controller will never change LabelServiceName, users might. This check\n\t// ensures that we handle changes to this label.\n\tsvcName := endpointSlice.Labels[discoveryv1.LabelServiceName]\n\tprevSvcName := prevEndpointSlice.Labels[discoveryv1.LabelServiceName]\n\tif svcName != prevSvcName {\n\t\tec.log.Infof(\"label changed label: %s, oldService: %s, newService: %s, endpointsliece: %s\", discoveryv1.LabelServiceName, prevSvcName, svcName, endpointSlice.Name)\n\t\tec.queueServiceForEndpointSlice(endpointSlice)\n\t\tec.queueServiceForEndpointSlice(prevEndpointSlice)\n\t\treturn\n\t}\n\tif managedByChanged(prevEndpointSlice, endpointSlice) ||\n\t\t(managedByController(endpointSlice) && ec.reconciler.endpointTracker.ShouldSync(endpointSlice)) {\n\t\tec.queueServiceForEndpointSlice(endpointSlice)\n\t}\n}\n\n// onEndpointSliceDelete queues a sync for the relevant Service for a sync if the\n// EndpointSlice resource version does not match the expected version in the\n// endpointSliceTracker.\nfunc (ec *EndpointsController) onEndpointSliceDelete(obj interface{}) {\n\tendpointSlice := ec.getEndpointSliceFromDeleteAction(obj)\n\tif endpointSlice != nil && managedByController(endpointSlice) && ec.reconciler.endpointTracker.Has(endpointSlice) {\n\t\t// This returns false if we didn't expect the EndpointSlice to be\n\t\t// deleted. If that is the case, we queue the Service for another sync.\n\t\tif !ec.reconciler.endpointTracker.HandleDeletion(endpointSlice) {\n\t\t\tec.queueServiceForEndpointSlice(endpointSlice)\n\t\t}\n\t}\n}\n\n// queueServiceForEndpointSlice attempts to queue the corresponding Service for\n// the provided EndpointSlice.\nfunc (ec *EndpointsController) queueServiceForEndpointSlice(endpointSlice *discoveryv1.EndpointSlice) {\n\tkey, err := endpointslicerec.ServiceControllerKey(endpointSlice)\n\tif err != nil {\n\t\tec.log.Errorf(\"Couldn't get key for EndpointSlice %+v: %v\", endpointSlice, err)\n\t\treturn\n\t}\n\n\tec.queue.Add(key)\n}\n\nfunc (ec *EndpointsController) onAddExternalWorkload(obj interface{}) {\n\tew, ok := obj.(*ewv1beta1.ExternalWorkload)\n\tif !ok {\n\t\tec.log.Errorf(\"couldn't get ExternalWorkload from object %#v\", obj)\n\t\treturn\n\t}\n\n\tservices, err := ec.getExternalWorkloadSvcMembership(ew)\n\tif err != nil {\n\t\tec.log.Errorf(\"failed to get service membership for %s/%s: %v\", ew.Namespace, ew.Name, err)\n\t\treturn\n\t}\n\n\tfor svc := range services {\n\t\tec.queue.Add(svc)\n\t}\n}\n\nfunc (ec *EndpointsController) onUpdateExternalWorkload(old, cur interface{}) {\n\tservices := ec.getServicesToUpdateOnExternalWorkloadChange(old, cur)\n\n\tfor svc := range services {\n\t\tec.queue.Add(svc)\n\t}\n}\n\nfunc (ec *EndpointsController) onDeleteExternalWorkload(obj interface{}) {\n\tew := ec.getExternalWorkloadFromDeleteAction(obj)\n\tif ew != nil {\n\t\tec.onAddExternalWorkload(ew)\n\t}\n}\n\nfunc dropEndpointSlicesPendingDeletion(endpointSlices []*discoveryv1.EndpointSlice) []*discoveryv1.EndpointSlice {\n\tn := 0\n\tfor _, endpointSlice := range endpointSlices {\n\t\tif endpointSlice.DeletionTimestamp == nil {\n\t\t\tendpointSlices[n] = endpointSlice\n\t\t\tn++\n\t\t}\n\t}\n\treturn endpointSlices[:n]\n}\n"
  },
  {
    "path": "controller/api/destination/external-workload/endpoints_controller_test.go",
    "content": "package externalworkload\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\tewv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tv1 \"k8s.io/api/core/v1\"\n\tdiscoveryv1 \"k8s.io/api/discovery/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n\tk8stesting \"k8s.io/client-go/testing\"\n\t\"k8s.io/client-go/tools/cache\"\n\t\"k8s.io/utils/ptr\"\n)\n\ntype endpointSliceController struct {\n\t*EndpointsController\n\tendpointSliceStore     cache.Store\n\texternalWorkloadsStore cache.Store\n\tserviceStore           cache.Store\n}\n\nfunc newController(t *testing.T) (*k8s.API, func() []k8stesting.Action, *endpointSliceController) {\n\tt.Helper()\n\n\tk8sAPI, actions, err := k8s.NewFakeAPIWithActions()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error %v\", err)\n\t}\n\n\tesController, err := NewEndpointsController(k8sAPI, \"hostname\", \"linkerd\", make(chan struct{}), false)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error %v\", err)\n\t}\n\n\treturn k8sAPI, actions, &endpointSliceController{\n\t\tesController,\n\t\tk8sAPI.ES().Informer().GetStore(),\n\t\tk8sAPI.ExtWorkload().Informer().GetStore(),\n\t\tk8sAPI.Svc().Informer().GetStore(),\n\t}\n\n}\n\nfunc newExternalWorkload(n int, namespace string, ready bool, terminating bool) *ewv1beta1.ExternalWorkload {\n\tstatus := ewv1beta1.ConditionTrue\n\tif !ready {\n\t\tstatus = ewv1beta1.ConditionFalse\n\t}\n\n\tvar deletionTimestamp *metav1.Time\n\tif terminating {\n\t\tdeletionTimestamp = &metav1.Time{\n\t\t\tTime: time.Now(),\n\t\t}\n\t}\n\n\tew := &ewv1beta1.ExternalWorkload{\n\t\tTypeMeta: metav1.TypeMeta{APIVersion: \"v1\"},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace:         namespace,\n\t\t\tName:              fmt.Sprintf(\"ew-%d\", n),\n\t\t\tLabels:            map[string]string{\"foo\": \"bar\"},\n\t\t\tDeletionTimestamp: deletionTimestamp,\n\t\t\tResourceVersion:   fmt.Sprint(n),\n\t\t},\n\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\tPorts: []ewv1beta1.PortSpec{\n\t\t\t\t{\n\t\t\t\t\tName: \"name\",\n\t\t\t\t\tPort: 444,\n\t\t\t\t},\n\t\t\t},\n\t\t\tWorkloadIPs: []ewv1beta1.WorkloadIP{\n\t\t\t\t{Ip: \"1.2.3.4\"},\n\t\t\t},\n\t\t},\n\t\tStatus: ewv1beta1.ExternalWorkloadStatus{\n\t\t\tConditions: []ewv1beta1.WorkloadCondition{\n\t\t\t\t{\n\t\t\t\t\tType:   ewv1beta1.WorkloadReady,\n\t\t\t\t\tStatus: status,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\treturn ew\n}\n\n// Ensure SyncService for service with no selector results in no action\nfunc TestSyncServiceNoSelector(t *testing.T) {\n\tns := metav1.NamespaceDefault\n\tserviceName := \"testing-1\"\n\t_, actions, esController := newController(t)\n\tesController.serviceStore.Add(&v1.Service{\n\t\tObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: ns},\n\t\tSpec: v1.ServiceSpec{\n\t\t\tPorts: []v1.ServicePort{{TargetPort: intstr.FromInt32(80)}},\n\t\t},\n\t})\n\n\terr := esController.syncService(fmt.Sprintf(\"%s/%s\", ns, serviceName))\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %s\", err)\n\t}\n\n\tif len(actions()) != 0 {\n\t\tt.Errorf(\"expected 0 actions, got: %d\", len(actions()))\n\t}\n}\n\nfunc TestServiceExternalNameTypeSync(t *testing.T) {\n\tserviceName := \"testing-1\"\n\tnamespace := \"zahari\"\n\n\ttestCases := []struct {\n\t\tdesc    string\n\t\tservice *v1.Service\n\t}{\n\t\t{\n\t\t\tdesc: \"External name with selector and ports should not receive endpoint slices\",\n\t\t\tservice: &v1.Service{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace},\n\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\tSelector: map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\tPorts:    []v1.ServicePort{{Port: 80}},\n\t\t\t\t\tType:     v1.ServiceTypeExternalName,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"External name with ports should not receive endpoint slices\",\n\t\t\tservice: &v1.Service{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace},\n\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\tPorts: []v1.ServicePort{{Port: 80}},\n\t\t\t\t\tType:  v1.ServiceTypeExternalName,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"External name with selector should not receive endpoint slices\",\n\t\t\tservice: &v1.Service{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace},\n\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\tSelector: map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\tType:     v1.ServiceTypeExternalName,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"External name without selector and ports should not receive endpoint slices\",\n\t\t\tservice: &v1.Service{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace},\n\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\tType: v1.ServiceTypeExternalName,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tclient, actions, esController := newController(t)\n\t\t\tew := newExternalWorkload(1, namespace, true, false)\n\t\t\terr := esController.externalWorkloadsStore.Add(ew)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\terr = esController.serviceStore.Add(tc.service)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\terr = esController.syncService(fmt.Sprintf(\"%s/%s\", namespace, serviceName))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\tif len(actions()) != 0 {\n\t\t\t\tt.Errorf(\"expected 0 actions, got: %d\", len(actions()))\n\t\t\t}\n\n\t\t\tsliceList, err := client.Client.DiscoveryV1().EndpointSlices(namespace).List(context.TODO(), metav1.ListOptions{})\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif len(sliceList.Items) != 0 {\n\t\t\t\tt.Errorf(\"Expected 0 endpoint slices, got: %d\", len(sliceList.Items))\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Ensure SyncService for service with pending deletion results in no action\nfunc TestSyncServicePendingDeletion(t *testing.T) {\n\tns := metav1.NamespaceDefault\n\tserviceName := \"testing-1\"\n\tdeletionTimestamp := metav1.Now()\n\t_, actions, esController := newController(t)\n\tesController.serviceStore.Add(&v1.Service{\n\t\tObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: ns, DeletionTimestamp: &deletionTimestamp},\n\t\tSpec: v1.ServiceSpec{\n\t\t\tSelector: map[string]string{\"foo\": \"bar\"},\n\t\t\tPorts:    []v1.ServicePort{{TargetPort: intstr.FromInt32(80)}},\n\t\t},\n\t})\n\n\terr := esController.syncService(fmt.Sprintf(\"%s/%s\", ns, serviceName))\n\tif err != nil {\n\t\tt.Errorf(\"unexpected error: %s\", err)\n\t}\n\tif len(actions()) != 0 {\n\t\tt.Errorf(\"Expected 0 actions, got: %d\", len(actions()))\n\t}\n}\n\n// Ensure SyncService correctly selects ExternalWorkload.\nfunc TestSyncServiceExternalWorkloadSelection(t *testing.T) {\n\tclient, actions, esController := newController(t)\n\tns := \"test-ns\"\n\n\tew1 := newExternalWorkload(1, ns, true, false)\n\tesController.externalWorkloadsStore.Add(ew1)\n\n\t// ensure this ew will not match the selector\n\tew2 := newExternalWorkload(2, ns, true, false)\n\tew2.Labels[\"foo\"] = \"boo\"\n\tesController.externalWorkloadsStore.Add(ew2)\n\n\tstandardSyncService(t, esController, ns, \"testing-1\")\n\texpectActions(t, actions(), 1, \"create\", \"endpointslices\")\n\n\t// an endpoint slice should be created, it should only reference ew1 (not ew2)\n\tslices, err := client.Client.DiscoveryV1().EndpointSlices(ns).List(context.TODO(), metav1.ListOptions{})\n\tif err != nil {\n\t\tt.Errorf(\"Expected no error fetching endpoint slices, got: %s\", err)\n\t}\n\tif len(slices.Items) != 1 {\n\t\tt.Errorf(\"Expected 1 endpoint slices, got: %d\", len(slices.Items))\n\t}\n\n\tslice := slices.Items[0]\n\tif len(slice.Endpoints) != 1 {\n\t\tt.Errorf(\"Expected 1 endpoint in first slice, got: %d\", len(slice.Endpoints))\n\t}\n\tendpoint := slice.Endpoints[0]\n\tif endpoint.TargetRef.Kind != \"ExternalWorkload\" || endpoint.TargetRef.Namespace != ns || endpoint.TargetRef.Name != ew1.Name {\n\t\tt.Errorf(\"Expected endpoint to target ExternalWorkload\")\n\t}\n}\n\nfunc TestSyncServiceEndpointSlicePendingDeletion(t *testing.T) {\n\tclient, actions, esController := newController(t)\n\tns := \"test-ns\"\n\tserviceName := \"testing-1\"\n\tservice := createService(t, esController, ns, serviceName)\n\terr := esController.syncService(fmt.Sprintf(\"%s/%s\", ns, serviceName))\n\tif err != nil {\n\t\tt.Fatalf(\"Expected no error creating EndpointSlice: %v\", err)\n\t}\n\n\tgvk := schema.GroupVersionKind{Version: \"v1\", Kind: \"Service\"}\n\townerRef := metav1.NewControllerRef(service, gvk)\n\n\tdeletedTs := metav1.Now()\n\tendpointSlice := &discoveryv1.EndpointSlice{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:            \"epSlice-1\",\n\t\t\tNamespace:       ns,\n\t\t\tOwnerReferences: []metav1.OwnerReference{*ownerRef},\n\t\t\tLabels: map[string]string{\n\t\t\t\tdiscoveryv1.LabelServiceName: serviceName,\n\t\t\t\tdiscoveryv1.LabelManagedBy:   managedBy,\n\t\t\t},\n\t\t\tDeletionTimestamp: &deletedTs,\n\t\t},\n\t\tAddressType: discoveryv1.AddressTypeIPv4,\n\t}\n\terr = esController.endpointSliceStore.Add(endpointSlice)\n\tif err != nil {\n\t\tt.Fatalf(\"Expected no error adding EndpointSlice: %v\", err)\n\t}\n\t_, err = client.Client.DiscoveryV1().EndpointSlices(ns).Create(context.TODO(), endpointSlice, metav1.CreateOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"Expected no error creating EndpointSlice: %v\", err)\n\t}\n\n\tnumActionsBefore := len(actions())\n\terr = esController.syncService(fmt.Sprintf(\"%s/%s\", ns, serviceName))\n\tif err != nil {\n\t\tt.Errorf(\"Expected no error syncing service, got: %s\", err)\n\t}\n\n\t// The EndpointSlice marked for deletion should be ignored by the controller, and thus\n\t// should not result in any action.\n\tif len(actions()) != numActionsBefore {\n\t\tt.Errorf(\"Expected 0 more actions, got %d\", len(actions())-numActionsBefore)\n\t}\n}\n\nfunc makeExternalWorkload(resVersion, name string, labels map[string]string, ports map[int32]string, ips []string) *ewv1beta1.ExternalWorkload {\n\tportSpecs := []ewv1beta1.PortSpec{}\n\tfor port, name := range ports {\n\t\tspec := ewv1beta1.PortSpec{\n\t\t\tPort: port,\n\t\t}\n\t\tif name != \"\" {\n\t\t\tspec.Name = name\n\t\t}\n\t\tportSpecs = append(portSpecs, spec)\n\t}\n\n\twIps := []ewv1beta1.WorkloadIP{}\n\tfor _, ip := range ips {\n\t\twIps = append(wIps, ewv1beta1.WorkloadIP{Ip: ip})\n\t}\n\n\tew := &ewv1beta1.ExternalWorkload{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:            name,\n\t\t\tNamespace:       \"ns\",\n\t\t\tLabels:          labels,\n\t\t\tResourceVersion: resVersion,\n\t\t},\n\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\tMeshTLS: ewv1beta1.MeshTLS{\n\t\t\t\tIdentity:   \"some-identity\",\n\t\t\t\tServerName: \"some-sni\",\n\t\t\t},\n\t\t\tPorts:       portSpecs,\n\t\t\tWorkloadIPs: wIps,\n\t\t},\n\t\tStatus: ewv1beta1.ExternalWorkloadStatus{\n\t\t\tConditions: []ewv1beta1.WorkloadCondition{\n\t\t\t\t{\n\t\t\t\t\tType:   ewv1beta1.WorkloadReady,\n\t\t\t\t\tStatus: ewv1beta1.ConditionTrue,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tew.ObjectMeta.UID = types.UID(fmt.Sprintf(\"%s-%s\", ew.Namespace, ew.Name))\n\treturn ew\n}\n\nfunc TestSyncService(t *testing.T) {\n\tcreationTimestamp := metav1.Now()\n\tnamespace := \"test-ns\"\n\ttestcases := []struct {\n\t\tname                  string\n\t\tservice               *v1.Service\n\t\texternalWorkloads     []*ewv1beta1.ExternalWorkload\n\t\texpectedEndpointPorts []discoveryv1.EndpointPort\n\t\texpectedEndpoints     []discoveryv1.Endpoint\n\t}{\n\t\t{\n\t\t\tname: \"EW with multiple IPs and Service with ipFamilies=ipv4\",\n\t\t\tservice: &v1.Service{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:              \"foobar\",\n\t\t\t\t\tNamespace:         namespace,\n\t\t\t\t\tCreationTimestamp: creationTimestamp,\n\t\t\t\t},\n\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\tPorts: []v1.ServicePort{\n\t\t\t\t\t\t{Name: \"tcp-example\", TargetPort: intstr.FromInt32(80), Protocol: v1.ProtocolTCP},\n\t\t\t\t\t\t{Name: \"udp-example\", TargetPort: intstr.FromInt32(161), Protocol: v1.ProtocolUDP},\n\t\t\t\t\t\t{Name: \"sctp-example\", TargetPort: intstr.FromInt32(3456), Protocol: v1.ProtocolSCTP},\n\t\t\t\t\t},\n\t\t\t\t\tSelector:   map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\tIPFamilies: []v1.IPFamily{v1.IPv4Protocol},\n\t\t\t\t},\n\t\t\t},\n\t\t\texternalWorkloads: []*ewv1beta1.ExternalWorkload{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tNamespace:         namespace,\n\t\t\t\t\t\tName:              \"ew0\",\n\t\t\t\t\t\tLabels:            map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\t\tDeletionTimestamp: nil,\n\t\t\t\t\t},\n\t\t\t\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\t\t\t\tWorkloadIPs: []ewv1beta1.WorkloadIP{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp: \"10.0.0.1\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPorts: []ewv1beta1.PortSpec{\n\t\t\t\t\t\t\t{Name: \"tcp-example\", Port: 80, Protocol: v1.ProtocolTCP},\n\t\t\t\t\t\t\t{Name: \"udp-example\", Port: 161, Protocol: v1.ProtocolUDP},\n\t\t\t\t\t\t\t{Name: \"sctp-example\", Port: 3456, Protocol: v1.ProtocolSCTP},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tStatus: ewv1beta1.ExternalWorkloadStatus{\n\t\t\t\t\t\tConditions: []ewv1beta1.WorkloadCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   ewv1beta1.WorkloadReady,\n\t\t\t\t\t\t\t\tStatus: ewv1beta1.ConditionTrue,\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\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tNamespace:         namespace,\n\t\t\t\t\t\tName:              \"ew1\",\n\t\t\t\t\t\tLabels:            map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\t\tDeletionTimestamp: nil,\n\t\t\t\t\t},\n\n\t\t\t\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\t\t\t\tWorkloadIPs: []ewv1beta1.WorkloadIP{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp: \"10.0.0.2\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp: \"fd08::5678:0000:0000:9abc:def0\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPorts: []ewv1beta1.PortSpec{\n\t\t\t\t\t\t\t{Name: \"tcp-example\", Port: 80, Protocol: v1.ProtocolTCP},\n\t\t\t\t\t\t\t{Name: \"udp-example\", Port: 161, Protocol: v1.ProtocolUDP},\n\t\t\t\t\t\t\t{Name: \"sctp-example\", Port: 3456, Protocol: v1.ProtocolSCTP},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tStatus: ewv1beta1.ExternalWorkloadStatus{\n\t\t\t\t\t\tConditions: []ewv1beta1.WorkloadCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   ewv1beta1.WorkloadReady,\n\t\t\t\t\t\t\t\tStatus: ewv1beta1.ConditionTrue,\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\texpectedEndpointPorts: []discoveryv1.EndpointPort{\n\t\t\t\t{\n\t\t\t\t\tName:     ptr.To(\"udp-example\"),\n\t\t\t\t\tProtocol: protoPtr(v1.ProtocolUDP),\n\t\t\t\t\tPort:     ptr.To(int32(161)),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     ptr.To(\"tcp-example\"),\n\t\t\t\t\tProtocol: protoPtr(v1.ProtocolTCP),\n\t\t\t\t\tPort:     ptr.To(int32(80)),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     ptr.To(\"sctp-example\"),\n\t\t\t\t\tProtocol: protoPtr(v1.ProtocolSCTP),\n\t\t\t\t\tPort:     ptr.To(int32(3456)),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedEndpoints: []discoveryv1.Endpoint{\n\t\t\t\t{\n\t\t\t\t\tConditions: discoveryv1.EndpointConditions{\n\t\t\t\t\t\tReady:       ptr.To(true),\n\t\t\t\t\t\tServing:     ptr.To(true),\n\t\t\t\t\t\tTerminating: ptr.To(false),\n\t\t\t\t\t},\n\t\t\t\t\tAddresses: []string{\"10.0.0.1\"},\n\t\t\t\t\tTargetRef: &v1.ObjectReference{Kind: \"ExternalWorkload\", Namespace: namespace, Name: \"ew0\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tConditions: discoveryv1.EndpointConditions{\n\t\t\t\t\t\tReady:       ptr.To(true),\n\t\t\t\t\t\tServing:     ptr.To(true),\n\t\t\t\t\t\tTerminating: ptr.To(false),\n\t\t\t\t\t},\n\t\t\t\t\tAddresses: []string{\"10.0.0.2\"},\n\t\t\t\t\tTargetRef: &v1.ObjectReference{Kind: \"ExternalWorkload\", Namespace: namespace, Name: \"ew1\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"EWs with multiple IPs and Service with ipFamilies=ipv6\",\n\t\t\tservice: &v1.Service{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:              \"foobar\",\n\t\t\t\t\tNamespace:         namespace,\n\t\t\t\t\tCreationTimestamp: creationTimestamp,\n\t\t\t\t},\n\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\tPorts: []v1.ServicePort{\n\t\t\t\t\t\t{Name: \"tcp-example\", TargetPort: intstr.FromInt32(80), Protocol: v1.ProtocolTCP},\n\t\t\t\t\t\t{Name: \"udp-example\", TargetPort: intstr.FromInt32(161), Protocol: v1.ProtocolUDP},\n\t\t\t\t\t\t{Name: \"sctp-example\", TargetPort: intstr.FromInt32(3456), Protocol: v1.ProtocolSCTP},\n\t\t\t\t\t},\n\t\t\t\t\tSelector:   map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\tIPFamilies: []v1.IPFamily{v1.IPv6Protocol},\n\t\t\t\t},\n\t\t\t},\n\t\t\texternalWorkloads: []*ewv1beta1.ExternalWorkload{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tNamespace:         namespace,\n\t\t\t\t\t\tName:              \"ew0\",\n\t\t\t\t\t\tLabels:            map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\t\tDeletionTimestamp: nil,\n\t\t\t\t\t},\n\t\t\t\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\t\t\t\tWorkloadIPs: []ewv1beta1.WorkloadIP{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp: \"10.0.0.1\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPorts: []ewv1beta1.PortSpec{\n\t\t\t\t\t\t\t{Name: \"tcp-example\", Port: 80, Protocol: v1.ProtocolTCP},\n\t\t\t\t\t\t\t{Name: \"udp-example\", Port: 161, Protocol: v1.ProtocolUDP},\n\t\t\t\t\t\t\t{Name: \"sctp-example\", Port: 3456, Protocol: v1.ProtocolSCTP},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tStatus: ewv1beta1.ExternalWorkloadStatus{\n\t\t\t\t\t\tConditions: []ewv1beta1.WorkloadCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   ewv1beta1.WorkloadReady,\n\t\t\t\t\t\t\t\tStatus: ewv1beta1.ConditionTrue,\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\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tNamespace:         namespace,\n\t\t\t\t\t\tName:              \"ew1\",\n\t\t\t\t\t\tLabels:            map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\t\tDeletionTimestamp: nil,\n\t\t\t\t\t},\n\t\t\t\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\t\t\t\tWorkloadIPs: []ewv1beta1.WorkloadIP{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp: \"10.0.0.2\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\n\t\t\t\t\t\t\t\tIp: \"fd08::5678:0000:0000:9abc:def0\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPorts: []ewv1beta1.PortSpec{\n\t\t\t\t\t\t\t{Name: \"tcp-example\", Port: 80, Protocol: v1.ProtocolTCP},\n\t\t\t\t\t\t\t{Name: \"udp-example\", Port: 161, Protocol: v1.ProtocolUDP},\n\t\t\t\t\t\t\t{Name: \"sctp-example\", Port: 3456, Protocol: v1.ProtocolSCTP},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tStatus: ewv1beta1.ExternalWorkloadStatus{\n\t\t\t\t\t\tConditions: []ewv1beta1.WorkloadCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   ewv1beta1.WorkloadReady,\n\t\t\t\t\t\t\t\tStatus: ewv1beta1.ConditionTrue,\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\texpectedEndpointPorts: []discoveryv1.EndpointPort{\n\t\t\t\t{\n\t\t\t\t\tName:     ptr.To(\"udp-example\"),\n\t\t\t\t\tProtocol: protoPtr(v1.ProtocolUDP),\n\t\t\t\t\tPort:     ptr.To(int32(161)),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     ptr.To(\"tcp-example\"),\n\t\t\t\t\tProtocol: protoPtr(v1.ProtocolTCP),\n\t\t\t\t\tPort:     ptr.To(int32(80)),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     ptr.To(\"sctp-example\"),\n\t\t\t\t\tProtocol: protoPtr(v1.ProtocolSCTP),\n\t\t\t\t\tPort:     ptr.To(int32(3456)),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedEndpoints: []discoveryv1.Endpoint{\n\t\t\t\t{\n\t\t\t\t\tConditions: discoveryv1.EndpointConditions{\n\t\t\t\t\t\tReady:       ptr.To(true),\n\t\t\t\t\t\tServing:     ptr.To(true),\n\t\t\t\t\t\tTerminating: ptr.To(false),\n\t\t\t\t\t},\n\t\t\t\t\tAddresses: []string{\"fd08::5678:0000:0000:9abc:def0\"},\n\t\t\t\t\tTargetRef: &v1.ObjectReference{Kind: \"ExternalWorkload\", Namespace: namespace, Name: \"ew1\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Not ready workloads\",\n\t\t\tservice: &v1.Service{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:              \"foobar\",\n\t\t\t\t\tNamespace:         namespace,\n\t\t\t\t\tCreationTimestamp: creationTimestamp,\n\t\t\t\t},\n\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\tPorts: []v1.ServicePort{\n\t\t\t\t\t\t{Name: \"tcp-example\", TargetPort: intstr.FromInt32(80), Protocol: v1.ProtocolTCP},\n\t\t\t\t\t\t{Name: \"udp-example\", TargetPort: intstr.FromInt32(161), Protocol: v1.ProtocolUDP},\n\t\t\t\t\t\t{Name: \"sctp-example\", TargetPort: intstr.FromInt32(3456), Protocol: v1.ProtocolSCTP},\n\t\t\t\t\t},\n\t\t\t\t\tSelector:   map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\tIPFamilies: []v1.IPFamily{v1.IPv4Protocol},\n\t\t\t\t},\n\t\t\t},\n\t\t\texternalWorkloads: []*ewv1beta1.ExternalWorkload{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tNamespace:         namespace,\n\t\t\t\t\t\tName:              \"ew0\",\n\t\t\t\t\t\tLabels:            map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\t\tDeletionTimestamp: nil,\n\t\t\t\t\t},\n\t\t\t\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\t\t\t\tWorkloadIPs: []ewv1beta1.WorkloadIP{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp: \"10.0.0.1\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPorts: []ewv1beta1.PortSpec{\n\t\t\t\t\t\t\t{Name: \"tcp-example\", Port: 80, Protocol: v1.ProtocolTCP},\n\t\t\t\t\t\t\t{Name: \"udp-example\", Port: 161, Protocol: v1.ProtocolUDP},\n\t\t\t\t\t\t\t{Name: \"sctp-example\", Port: 3456, Protocol: v1.ProtocolSCTP},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tStatus: ewv1beta1.ExternalWorkloadStatus{\n\t\t\t\t\t\tConditions: []ewv1beta1.WorkloadCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   ewv1beta1.WorkloadReady,\n\t\t\t\t\t\t\t\tStatus: ewv1beta1.ConditionTrue,\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\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tNamespace:         namespace,\n\t\t\t\t\t\tName:              \"ew1\",\n\t\t\t\t\t\tLabels:            map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\t\tDeletionTimestamp: nil,\n\t\t\t\t\t},\n\t\t\t\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\t\t\t\tWorkloadIPs: []ewv1beta1.WorkloadIP{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp: \"10.0.0.2\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\n\t\t\t\t\t\t\t\tIp: \"fd08::5678:0000:0000:9abc:def0\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPorts: []ewv1beta1.PortSpec{\n\t\t\t\t\t\t\t{Name: \"tcp-example\", Port: 80, Protocol: v1.ProtocolTCP},\n\t\t\t\t\t\t\t{Name: \"udp-example\", Port: 161, Protocol: v1.ProtocolUDP},\n\t\t\t\t\t\t\t{Name: \"sctp-example\", Port: 3456, Protocol: v1.ProtocolSCTP},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tStatus: ewv1beta1.ExternalWorkloadStatus{\n\t\t\t\t\t\tConditions: []ewv1beta1.WorkloadCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   ewv1beta1.WorkloadReady,\n\t\t\t\t\t\t\t\tStatus: ewv1beta1.ConditionFalse,\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\texpectedEndpointPorts: []discoveryv1.EndpointPort{\n\t\t\t\t{\n\t\t\t\t\tName:     ptr.To(\"udp-example\"),\n\t\t\t\t\tProtocol: protoPtr(v1.ProtocolUDP),\n\t\t\t\t\tPort:     ptr.To(int32(161)),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     ptr.To(\"tcp-example\"),\n\t\t\t\t\tProtocol: protoPtr(v1.ProtocolTCP),\n\t\t\t\t\tPort:     ptr.To(int32(80)),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     ptr.To(\"sctp-example\"),\n\t\t\t\t\tProtocol: protoPtr(v1.ProtocolSCTP),\n\t\t\t\t\tPort:     ptr.To(int32(3456)),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedEndpoints: []discoveryv1.Endpoint{\n\t\t\t\t{\n\t\t\t\t\tConditions: discoveryv1.EndpointConditions{\n\t\t\t\t\t\tReady:       ptr.To(true),\n\t\t\t\t\t\tServing:     ptr.To(true),\n\t\t\t\t\t\tTerminating: ptr.To(false),\n\t\t\t\t\t},\n\t\t\t\t\tAddresses: []string{\"10.0.0.1\"},\n\t\t\t\t\tTargetRef: &v1.ObjectReference{Kind: \"ExternalWorkload\", Namespace: namespace, Name: \"ew0\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tConditions: discoveryv1.EndpointConditions{\n\t\t\t\t\t\tReady:       ptr.To(false),\n\t\t\t\t\t\tServing:     ptr.To(false),\n\t\t\t\t\t\tTerminating: ptr.To(false),\n\t\t\t\t\t},\n\t\t\t\t\tAddresses: []string{\"10.0.0.2\"},\n\t\t\t\t\tTargetRef: &v1.ObjectReference{Kind: \"ExternalWorkload\", Namespace: namespace, Name: \"ew1\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Two Ready workloads with the same IPs\",\n\t\t\tservice: &v1.Service{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:              \"foobar\",\n\t\t\t\t\tNamespace:         namespace,\n\t\t\t\t\tCreationTimestamp: creationTimestamp,\n\t\t\t\t},\n\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\tPorts: []v1.ServicePort{\n\t\t\t\t\t\t{Name: \"tcp-example\", TargetPort: intstr.FromInt32(80), Protocol: v1.ProtocolTCP},\n\t\t\t\t\t\t{Name: \"udp-example\", TargetPort: intstr.FromInt32(161), Protocol: v1.ProtocolUDP},\n\t\t\t\t\t\t{Name: \"sctp-example\", TargetPort: intstr.FromInt32(3456), Protocol: v1.ProtocolSCTP},\n\t\t\t\t\t},\n\t\t\t\t\tSelector:   map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\tIPFamilies: []v1.IPFamily{v1.IPv4Protocol},\n\t\t\t\t},\n\t\t\t},\n\t\t\texternalWorkloads: []*ewv1beta1.ExternalWorkload{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tNamespace:         namespace,\n\t\t\t\t\t\tName:              \"ew0\",\n\t\t\t\t\t\tLabels:            map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\t\tDeletionTimestamp: nil,\n\t\t\t\t\t},\n\t\t\t\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\t\t\t\tWorkloadIPs: []ewv1beta1.WorkloadIP{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp: \"10.0.0.1\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPorts: []ewv1beta1.PortSpec{\n\t\t\t\t\t\t\t{Name: \"tcp-example\", Port: 80, Protocol: v1.ProtocolTCP},\n\t\t\t\t\t\t\t{Name: \"udp-example\", Port: 161, Protocol: v1.ProtocolUDP},\n\t\t\t\t\t\t\t{Name: \"sctp-example\", Port: 3456, Protocol: v1.ProtocolSCTP},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tStatus: ewv1beta1.ExternalWorkloadStatus{\n\t\t\t\t\t\tConditions: []ewv1beta1.WorkloadCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   ewv1beta1.WorkloadReady,\n\t\t\t\t\t\t\t\tStatus: ewv1beta1.ConditionTrue,\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\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tNamespace:         namespace,\n\t\t\t\t\t\tName:              \"ew1\",\n\t\t\t\t\t\tLabels:            map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\t\tDeletionTimestamp: nil,\n\t\t\t\t\t},\n\t\t\t\t\tSpec: ewv1beta1.ExternalWorkloadSpec{\n\t\t\t\t\t\tWorkloadIPs: []ewv1beta1.WorkloadIP{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tIp: \"10.0.0.1\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPorts: []ewv1beta1.PortSpec{\n\t\t\t\t\t\t\t{Name: \"tcp-example\", Port: 80, Protocol: v1.ProtocolTCP},\n\t\t\t\t\t\t\t{Name: \"udp-example\", Port: 161, Protocol: v1.ProtocolUDP},\n\t\t\t\t\t\t\t{Name: \"sctp-example\", Port: 3456, Protocol: v1.ProtocolSCTP},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tStatus: ewv1beta1.ExternalWorkloadStatus{\n\t\t\t\t\t\tConditions: []ewv1beta1.WorkloadCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   ewv1beta1.WorkloadReady,\n\t\t\t\t\t\t\t\tStatus: ewv1beta1.ConditionTrue,\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\texpectedEndpointPorts: []discoveryv1.EndpointPort{\n\t\t\t\t{\n\t\t\t\t\tName:     ptr.To(\"udp-example\"),\n\t\t\t\t\tProtocol: protoPtr(v1.ProtocolUDP),\n\t\t\t\t\tPort:     ptr.To(int32(161)),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     ptr.To(\"tcp-example\"),\n\t\t\t\t\tProtocol: protoPtr(v1.ProtocolTCP),\n\t\t\t\t\tPort:     ptr.To(int32(80)),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     ptr.To(\"sctp-example\"),\n\t\t\t\t\tProtocol: protoPtr(v1.ProtocolSCTP),\n\t\t\t\t\tPort:     ptr.To(int32(3456)),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedEndpoints: []discoveryv1.Endpoint{\n\t\t\t\t{\n\t\t\t\t\tConditions: discoveryv1.EndpointConditions{\n\t\t\t\t\t\tReady:       ptr.To(true),\n\t\t\t\t\t\tServing:     ptr.To(true),\n\t\t\t\t\t\tTerminating: ptr.To(false),\n\t\t\t\t\t},\n\t\t\t\t\tAddresses: []string{\"10.0.0.1\"},\n\t\t\t\t\tTargetRef: &v1.ObjectReference{Kind: \"ExternalWorkload\", Namespace: namespace, Name: \"ew0\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tConditions: discoveryv1.EndpointConditions{\n\t\t\t\t\t\tReady:       ptr.To(true),\n\t\t\t\t\t\tServing:     ptr.To(true),\n\t\t\t\t\t\tTerminating: ptr.To(false),\n\t\t\t\t\t},\n\t\t\t\t\tAddresses: []string{\"10.0.0.1\"},\n\t\t\t\t\tTargetRef: &v1.ObjectReference{Kind: \"ExternalWorkload\", Namespace: namespace, Name: \"ew1\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, testcase := range testcases {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tclient, actions, esController := newController(t)\n\n\t\t\tfor _, ew := range testcase.externalWorkloads {\n\t\t\t\tesController.externalWorkloadsStore.Add(ew)\n\t\t\t}\n\t\t\tesController.serviceStore.Add(testcase.service)\n\n\t\t\t_, err := esController.k8sAPI.Client.CoreV1().Services(testcase.service.Namespace).Create(context.TODO(), testcase.service, metav1.CreateOptions{})\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Expected no error creating service, got: %s\", err)\n\t\t\t}\n\t\t\terr = esController.syncService(fmt.Sprintf(\"%s/%s\", testcase.service.Namespace, testcase.service.Name))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Expected no error, got: %s\", err)\n\t\t\t}\n\n\t\t\t// last action should be to create endpoint slice\n\t\t\texpectActions(t, actions(), 1, \"create\", \"endpointslices\")\n\t\t\tsliceList, err := client.Client.DiscoveryV1().EndpointSlices(testcase.service.Namespace).List(context.TODO(), metav1.ListOptions{})\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Expected no error fetching endpoint slices, got: %s\", err)\n\t\t\t}\n\n\t\t\tif len(sliceList.Items) != 1 {\n\t\t\t\tt.Errorf(\"Expected 1 endpoints slices\")\n\t\t\t}\n\n\t\t\t// ensure all attributes of endpoint slice match expected state\n\t\t\tslice := sliceList.Items[0]\n\n\t\t\t// check expected ports\n\n\t\t\tif !reflect.DeepEqual(testcase.expectedEndpointPorts, slice.Ports) {\n\t\t\t\tt.Error(\"actual ports do not match expected ones\")\n\n\t\t\t\tfor i, ep := range slice.Ports {\n\t\t\t\t\tt.Logf(\n\t\t\t\t\t\t\"actual port[%d]: name=%s proto=%v port=%v\",\n\t\t\t\t\t\ti,\n\t\t\t\t\t\tptr.Deref(ep.Name, \"<nil>\"),\n\t\t\t\t\t\tptr.Deref(ep.Protocol, \"<nil>\"),\n\t\t\t\t\t\tptr.Deref(ep.Port, 0),\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tfor i, ep := range testcase.expectedEndpointPorts {\n\t\t\t\t\tt.Logf(\n\t\t\t\t\t\t\"expected port[%d]: name=%s proto=%v port=%v\",\n\t\t\t\t\t\ti,\n\t\t\t\t\t\tptr.Deref(ep.Name, \"<nil>\"),\n\t\t\t\t\t\tptr.Deref(ep.Protocol, \"<nil>\"),\n\t\t\t\t\t\tptr.Deref(ep.Port, 0),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// sort actual endpoints in terms of targetRef name, in ascending\n\t\t\t// order. This will ensure reflection package doesn't give a\n\t\t\t// spurious error.\n\n\t\t\tsort.Slice(slice.Endpoints, func(i, j int) bool {\n\t\t\t\treturn slice.Endpoints[i].TargetRef.Name < slice.Endpoints[j].TargetRef.Name\n\t\t\t})\n\n\t\t\tif !reflect.DeepEqual(testcase.expectedEndpoints, slice.Endpoints) {\n\t\t\t\tt.Error(\"actual endpoints do not match expected ones\")\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Test diffing logic that determines if two workloads with the same name and\n// namespace have changed enough to warrant reconciliation\n// TODO: Add tests for labels change\nfunc TestEwEndpointsChanged(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tname        string\n\t\told         *ewv1beta1.ExternalWorkload\n\t\tupdated     *ewv1beta1.ExternalWorkload\n\t\tspecChanged bool\n\t}{\n\t\t{\n\t\t\tname: \"no change\",\n\t\t\told: makeExternalWorkload(\n\t\t\t\t\"1\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tupdated: makeExternalWorkload(\n\t\t\t\t\"1\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tspecChanged: false,\n\t\t},\n\t\t{\n\t\t\tname: \"updated workload adds an IP address\",\n\t\t\told: makeExternalWorkload(\n\t\t\t\t\"1\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tupdated: makeExternalWorkload(\n\t\t\t\t\"2\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\", \"192.0.3.0\"},\n\t\t\t),\n\t\t\tspecChanged: true,\n\t\t},\n\t\t{\n\t\t\tname: \"updated workload removes an IP address\",\n\t\t\told: makeExternalWorkload(\n\t\t\t\t\"1\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\", \"192.0.3.0\"},\n\t\t\t),\n\t\t\tupdated: makeExternalWorkload(\n\t\t\t\t\"2\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tspecChanged: true,\n\t\t},\n\t\t{\n\t\t\tname: \"updated workload changes an IP address\",\n\t\t\told: makeExternalWorkload(\n\t\t\t\t\"1\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tupdated: makeExternalWorkload(\n\t\t\t\t\"2\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.3.0\"},\n\t\t\t),\n\t\t\tspecChanged: true,\n\t\t},\n\t\t{\n\t\t\tname: \"updated workload adds new port\",\n\t\t\told: makeExternalWorkload(\n\t\t\t\t\"1\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tupdated: makeExternalWorkload(\n\t\t\t\t\"2\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-1\", 2: \"port-2\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tspecChanged: true,\n\t\t},\n\t\t{\n\t\t\tname: \"updated workload removes port\",\n\t\t\told: makeExternalWorkload(\n\t\t\t\t\"1\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-1\", 2: \"port-2\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tupdated: makeExternalWorkload(\n\t\t\t\t\"2\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tspecChanged: true,\n\t\t},\n\t\t{\n\t\t\tname: \"updated workload changes port number\",\n\t\t\told: makeExternalWorkload(\n\t\t\t\t\"1\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tupdated: makeExternalWorkload(\n\t\t\t\t\"2\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{2: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tspecChanged: true,\n\t\t},\n\t\t{\n\t\t\tname: \"updated workload changes port name\",\n\t\t\told: makeExternalWorkload(\n\t\t\t\t\"1\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tupdated: makeExternalWorkload(\n\t\t\t\t\"2\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-foo\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tspecChanged: true,\n\t\t},\n\t\t{\n\t\t\tname: \"updated workload removes port name\",\n\t\t\told: makeExternalWorkload(\n\t\t\t\t\"1\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tupdated: makeExternalWorkload(\n\t\t\t\t\"2\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tnil,\n\t\t\t\tmap[int32]string{1: \"\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tspecChanged: true,\n\t\t},\n\t} {\n\t\ttt := tt // Pin\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tspecChanged, _ := ewEndpointsChanged(tt.old, tt.updated)\n\t\t\tif tt.specChanged != specChanged {\n\t\t\t\tt.Errorf(\"expected specChanged '%v', got '%v'\", tt.specChanged, specChanged)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Test diffing logic that determines if two workloads with the same name and\n// namespace have changed enough to warrant reconciliation\nfunc TestWorkloadServicesToUpdate(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tname           string\n\t\told            *ewv1beta1.ExternalWorkload\n\t\tupdated        *ewv1beta1.ExternalWorkload\n\t\tk8sConfigs     []string\n\t\texpectServices map[string]struct{}\n\t}{\n\t\t{\n\t\t\tname: \"no change\",\n\t\t\told: makeExternalWorkload(\n\t\t\t\t\"1\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tmap[string]string{\"app\": \"test\"},\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tupdated: makeExternalWorkload(\n\t\t\t\t\"1\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tmap[string]string{\"app\": \"test\"},\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tk8sConfigs: []string{`\n            apiVersion: v1\n            kind: Service\n            metadata:\n              name: svc-1\n              namespace: ns\n            spec:\n              selector:\n                app: test`,\n\t\t\t},\n\t\t\texpectServices: map[string]struct{}{},\n\t\t},\n\t\t{\n\t\t\tname: \"labels and spec have changed\",\n\t\t\told: makeExternalWorkload(\n\t\t\t\t\"1\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tmap[string]string{\"app\": \"test-1\"},\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tupdated: makeExternalWorkload(\n\t\t\t\t\"2\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tmap[string]string{\"app\": \"test-2\"},\n\t\t\t\tmap[int32]string{2: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tk8sConfigs: []string{`\n            apiVersion: v1\n            kind: Service\n            metadata:\n              name: svc-1\n              namespace: ns\n            spec:\n              selector:\n                app: test-1`, `\n            apiVersion: v1\n            kind: Service\n            metadata:\n              name: svc-2\n              namespace: ns\n            spec:\n              selector:\n                app: test-2`,\n\t\t\t},\n\t\t\texpectServices: map[string]struct{}{\"ns/svc-1\": {}, \"ns/svc-2\": {}},\n\t\t},\n\t\t{\n\t\t\tname: \"spec has changed\",\n\t\t\told: makeExternalWorkload(\n\t\t\t\t\"1\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tmap[string]string{\"app\": \"test-1\"},\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tupdated: makeExternalWorkload(\n\t\t\t\t\"2\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tmap[string]string{\"app\": \"test-1\"},\n\t\t\t\tmap[int32]string{2: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tk8sConfigs: []string{`\n            apiVersion: v1\n            kind: Service\n            metadata:\n              name: svc-1\n              namespace: ns\n            spec:\n              selector:\n                app: test-1`,\n\t\t\t},\n\t\t\texpectServices: map[string]struct{}{\"ns/svc-1\": {}},\n\t\t},\n\t\t{\n\t\t\tname: \"labels have changed\",\n\t\t\told: makeExternalWorkload(\n\t\t\t\t\"1\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tmap[string]string{\"app\": \"test-1\", \"env\": \"staging\"},\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tupdated: makeExternalWorkload(\n\t\t\t\t\"2\",\n\t\t\t\t\"wlkd1\",\n\t\t\t\tmap[string]string{\"app\": \"test-1\", \"env\": \"prod\"},\n\t\t\t\tmap[int32]string{1: \"port-1\"},\n\t\t\t\t[]string{\"192.0.2.0\"},\n\t\t\t),\n\t\t\tk8sConfigs: []string{`\n            apiVersion: v1\n            kind: Service\n            metadata:\n              name: internal\n              namespace: ns\n            spec:\n              selector:\n                app: test-1`, `\n            apiVersion: v1\n            kind: Service\n            metadata:\n              name: staging\n              namespace: ns\n            spec:\n              selector:\n                env: staging`, `\n            apiVersion: v1\n            kind: Service\n            metadata:\n              name: prod\n              namespace: ns\n            spec:\n              selector:\n                env: prod`,\n\t\t\t},\n\t\t\texpectServices: map[string]struct{}{\"ns/staging\": {}, \"ns/prod\": {}},\n\t\t}} {\n\t\ttt := tt // Pin\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error %v\", err)\n\t\t\t}\n\n\t\t\tec, err := NewEndpointsController(k8sAPI, \"my-hostname\", \"controlplane-ns\", make(chan struct{}), false)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error %v\", err)\n\t\t\t}\n\n\t\t\tec.Start()\n\t\t\tk8sAPI.Sync(nil)\n\n\t\t\tservices := ec.getServicesToUpdateOnExternalWorkloadChange(tt.old, tt.updated)\n\t\t\tif len(services) != len(tt.expectServices) {\n\t\t\t\tt.Fatalf(\"expected %d services to update, got %d services instead\", len(tt.expectServices), len(services))\n\t\t\t}\n\n\t\t\tfor svc := range services {\n\t\t\t\tif _, ok := tt.expectServices[svc]; !ok {\n\t\t\t\t\tt.Errorf(\"unexpected service key %s found in list of results\", svc)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t}\n}\n\n// Assert that de-registering handlers won't result in cache staleness issues\n//\n// The test will simulate a scenario where a lease is acquired, an endpointslice\n// created, and the lease is lost. Without wiping out state, this test will\n// fail, since any changes made to the resources will not be observed while the\n// lease is not held; these changes will result in stale cache entries (since\n// the state diverged).\nfunc TestLeaderElectionSyncsState(t *testing.T) {\n\tclient, actions, esController := newController(t)\n\tns := \"test-ns\"\n\tservice := createService(t, esController, ns, \"test-svc\")\n\tew1 := newExternalWorkload(1, ns, false, true)\n\tesController.serviceStore.Add(service)\n\tesController.externalWorkloadsStore.Add(ew1)\n\n\t// Simulate a lease being acquired,\n\terr := esController.addHandlers()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when registering client-go callbacks: %v\", err)\n\t}\n\n\terr = esController.syncService(fmt.Sprintf(\"%s/%s\", ns, service.Name))\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when processing service %s/%s: %v\", ns, service.Name, err)\n\t}\n\texpectActions(t, actions(), 1, \"create\", \"endpointslices\")\n\n\tslices, err := client.Client.DiscoveryV1().EndpointSlices(ns).List(context.TODO(), metav1.ListOptions{})\n\tif err != nil {\n\t\tt.Errorf(\"expected no error fetching endpoint slices, got: %s\", err)\n\t}\n\tif len(slices.Items) != 1 {\n\t\tt.Errorf(\"expected 1 endpoint slices, got: %d\", len(slices.Items))\n\t}\n\tsliceName := slices.Items[0].Name\n\n\t// Simulate a lease being lost; we delete the previously created\n\t// endpointslice out-of-band.\n\terr = esController.removeHandlers()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when de-registering client-go callbacks: %v\", err)\n\t}\n\terr = client.Client.DiscoveryV1().EndpointSlices(ns).Delete(context.TODO(), sliceName, metav1.DeleteOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when deleting endpointslice %s/%s: %v\", ns, sliceName, err)\n\t}\n\tslices, err = client.Client.DiscoveryV1().EndpointSlices(ns).List(context.TODO(), metav1.ListOptions{})\n\tif err != nil {\n\t\tt.Errorf(\"expected no error fetching endpoint slices, got: %s\", err)\n\t}\n\tif len(slices.Items) != 0 {\n\t\tt.Errorf(\"expected 0 endpoint slices, got: %d\", len(slices.Items))\n\t}\n\n\t// The lease is re-acquired. We should start with a clean slate to avoid\n\t// cache staleness errors.\n\tesController.addHandlers()\n\terr = esController.syncService(fmt.Sprintf(\"%s/%s\", ns, service.Name))\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when processing service %s/%s: %v\", ns, service.Name, err)\n\t}\n\texpectActions(t, actions(), 1, \"create\", \"endpointslices\")\n\tslices, err = client.Client.DiscoveryV1().EndpointSlices(ns).List(context.TODO(), metav1.ListOptions{})\n\tif err != nil {\n\t\tt.Errorf(\"expected no error fetching endpoint slices, got: %s\", err)\n\t}\n\tif len(slices.Items) != 1 {\n\t\tt.Errorf(\"expected 1 endpoint slices, got: %d\", len(slices.Items))\n\t}\n\tif slices.Items[0].Name == sliceName {\n\t\tt.Fatalf(\"expected newly created slice's name to be different than the initial slice, got: %s\", sliceName)\n\t}\n\n}\n\n// protoPtr takes a Protocol and returns a pointer to it.\nfunc protoPtr(proto v1.Protocol) *v1.Protocol {\n\treturn &proto\n}\n\nfunc newStatusCondition(ready bool) ewv1beta1.WorkloadCondition {\n\tvar status ewv1beta1.WorkloadConditionStatus\n\tif ready {\n\t\tstatus = ewv1beta1.ConditionTrue\n\t} else {\n\t\tstatus = ewv1beta1.ConditionFalse\n\t}\n\treturn ewv1beta1.WorkloadCondition{\n\t\tType:               ewv1beta1.WorkloadReady,\n\t\tStatus:             status,\n\t\tLastProbeTime:      metav1.Time{},\n\t\tLastTransitionTime: metav1.NewTime(time.Now()),\n\t\tReason:             \"test\",\n\t\tMessage:            \"test\",\n\t}\n}\n\n//nolint:all\nfunc expectActions(t *testing.T, actions []k8stesting.Action, num int, verb, resource string) {\n\tt.Helper()\n\t// if actions are less the below logic will panic\n\tif num > len(actions) {\n\t\tt.Fatalf(\"len of actions %v is unexpected. Expected to be at least %v\", len(actions), num+1)\n\t}\n\n\tfor i := 0; i < num; i++ {\n\t\trelativePos := len(actions) - i - 1\n\t\tif actions[relativePos].GetVerb() != verb {\n\t\t\tt.Errorf(\"Expected action -%d verb to be %s, was: %s\", i, verb, actions[relativePos].GetVerb())\n\t\t}\n\t\tif resource != actions[relativePos].GetResource().Resource {\n\t\t\tt.Errorf(\"Expected action -%d resource to be %s, was: %s\", i, resource, actions[relativePos].GetResource().Resource)\n\t\t}\n\t}\n}\n\nfunc createService(t *testing.T, esController *endpointSliceController, namespace, serviceName string) *v1.Service {\n\tt.Helper()\n\tservice := &v1.Service{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:              serviceName,\n\t\t\tNamespace:         namespace,\n\t\t\tCreationTimestamp: metav1.NewTime(time.Now()),\n\t\t\tUID:               types.UID(namespace + \"-\" + serviceName),\n\t\t},\n\t\tSpec: v1.ServiceSpec{\n\t\t\tPorts:      []v1.ServicePort{{TargetPort: intstr.FromInt32(80)}},\n\t\t\tSelector:   map[string]string{\"foo\": \"bar\"},\n\t\t\tIPFamilies: []v1.IPFamily{v1.IPv4Protocol},\n\t\t},\n\t}\n\tesController.serviceStore.Add(service)\n\t_, err := esController.k8sAPI.Client.CoreV1().Services(namespace).Create(context.TODO(), service, metav1.CreateOptions{})\n\tif err != nil {\n\t\tt.Error(\"Expected no error creating service\")\n\t}\n\treturn service\n}\n\nfunc standardSyncService(t *testing.T, esController *endpointSliceController, namespace, serviceName string) {\n\tt.Helper()\n\tcreateService(t, esController, namespace, serviceName)\n\n\terr := esController.syncService(fmt.Sprintf(\"%s/%s\", namespace, serviceName))\n\tif err != nil {\n\t\tt.Error(\"Expected no error syncing service\")\n\t}\n}\n"
  },
  {
    "path": "controller/api/destination/external-workload/endpoints_reconciler.go",
    "content": "package externalworkload\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sort\"\n\n\tewv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tdiscoveryv1 \"k8s.io/api/discovery/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tutilerrors \"k8s.io/apimachinery/pkg/util/errors\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n\tepsliceutil \"k8s.io/endpointslice/util\"\n\tutilnet \"k8s.io/utils/net\"\n)\n\n// endpointsReconciler is a subcomponent of the EndpointsController.\n//\n// Its main responsibility is to reconcile a service's endpoints (by diffing\n// states) and keeping track of any drifts between the informer cache and what\n// has been written to the API Server\ntype endpointsReconciler struct {\n\tk8sAPI         *k8s.API\n\tlog            *logging.Entry\n\tcontrollerName string\n\t// Upstream utility component that will internally track the most recent\n\t// resourceVersion observed for an EndpointSlice\n\tendpointTracker *epsliceutil.EndpointSliceTracker\n\tmaxEndpoints    int\n\t// TODO (matei): add metrics around events\n}\n\n// endpointMeta is a helper struct that incldues attributes slices will be\n// grouped on (i.e. ports and the address family supported).\n//\n// Note: this is inspired from the upstream EndpointSlice controller impl.\ntype endpointMeta struct {\n\tports       []discoveryv1.EndpointPort\n\taddressType discoveryv1.AddressType\n}\n\n// newEndpointsReconciler takes an API client and returns a reconciler with\n// logging and a tracker set-up\nfunc newEndpointsReconciler(k8sAPI *k8s.API, controllerName string, maxEndpoints int) *endpointsReconciler {\n\treturn &endpointsReconciler{\n\t\tk8sAPI,\n\t\tlogging.WithFields(logging.Fields{\n\t\t\t\"component\": \"external-endpoints-reconciler\",\n\t\t}),\n\t\tcontrollerName,\n\t\tepsliceutil.NewEndpointSliceTracker(),\n\t\tmaxEndpoints,\n\t}\n\n}\n\n// === Reconciler ===\n\n// reconcile is the main entry-point for the reconciler's work.\n//\n// It accepts a slice of external workloads and their corresponding service.\n// Optionally, if the controller has previously created any slices for this\n// service, these will also be passed in. The reconciler will:\n//\n// * Determine what address types the service supports\n// * For each address type, it will determine which slices to process (an\n// EndpointSlice is specialised and supports only one type)\nfunc (r *endpointsReconciler) reconcile(svc *corev1.Service, ews []*ewv1beta1.ExternalWorkload, existingSlices []*discoveryv1.EndpointSlice) error {\n\ttoDelete := []*discoveryv1.EndpointSlice{}\n\tslicesByAddrType := make(map[discoveryv1.AddressType][]*discoveryv1.EndpointSlice)\n\terrs := []error{}\n\n\t// Get the list of supported address types for the service\n\tsupportedAddrTypes := getSupportedAddressTypes(svc)\n\tfor _, slice := range existingSlices {\n\t\t// If a slice has an address type that the service does not support, then\n\t\t// it should be deleted\n\t\tif _, supported := supportedAddrTypes[slice.AddressType]; !supported {\n\t\t\ttoDelete = append(toDelete, slice)\n\t\t\tcontinue\n\t\t}\n\n\t\t// If this is the first time we see this address type, create the list\n\t\t// in the set.\n\t\tif _, ok := slicesByAddrType[slice.AddressType]; !ok {\n\t\t\tslicesByAddrType[slice.AddressType] = []*discoveryv1.EndpointSlice{}\n\t\t}\n\n\t\tslicesByAddrType[slice.AddressType] = append(slicesByAddrType[slice.AddressType], slice)\n\t}\n\n\t// For each supported address type, reconcile endpoint slices that match the\n\t// given type\n\tfor addrType := range supportedAddrTypes {\n\t\texistingSlices := slicesByAddrType[addrType]\n\t\terr := r.reconcileByAddressType(svc, ews, existingSlices, addrType)\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\n\t// delete services whose address type is no longer supported by the service\n\tfor _, slice := range toDelete {\n\t\terr := r.k8sAPI.Client.DiscoveryV1().EndpointSlices(svc.Namespace).Delete(context.TODO(), slice.Name, metav1.DeleteOptions{})\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\n\treturn utilerrors.NewAggregate(errs)\n}\n\n// reconcileByAddressType operates on a set of external workloads, their\n// service, and any endpointslices that have been created by the controller. It\n// will compute the diff that needs to be written to the API Server.\nfunc (r *endpointsReconciler) reconcileByAddressType(svc *corev1.Service, extWorkloads []*ewv1beta1.ExternalWorkload, existingSlices []*discoveryv1.EndpointSlice, addrType discoveryv1.AddressType) error {\n\tslicesToCreate := []*discoveryv1.EndpointSlice{}\n\tslicesToUpdate := []*discoveryv1.EndpointSlice{}\n\tslicesToDelete := []*discoveryv1.EndpointSlice{}\n\n\t// We start the reconciliation by checking ownerRefs\n\t//\n\t// We follow the upstream here and look at our existing slices and segment\n\t// by ports.\n\texistingSlicesByPorts := map[epsliceutil.PortMapKey][]*discoveryv1.EndpointSlice{}\n\tfor _, slice := range existingSlices {\n\t\t// Loop through the endpointslices and figure out which endpointslice\n\t\t// does not have an ownerRef set to the service. If a slice has been\n\t\t// selected but does not point to the service, we delete it.\n\t\tif ownedBy(slice, svc) {\n\t\t\thash := epsliceutil.NewPortMapKey(slice.Ports)\n\t\t\texistingSlicesByPorts[hash] = append(existingSlicesByPorts[hash], slice)\n\t\t} else {\n\t\t\tslicesToDelete = append(slicesToDelete, slice)\n\t\t}\n\t}\n\n\t// desiredEndpointsByPortMap represents a set of endpoints grouped together\n\t// by the list of ports they use. These are the endpoints that we will keep\n\t// and write to the API server.\n\tdesiredEndpointsByPortMap := map[epsliceutil.PortMapKey]epsliceutil.EndpointSet{}\n\t// desiredMetaByPortMap represents grouping metadata keyed off by the same\n\t// hashed port list as the endpoints.\n\tdesiredMetaByPortMap := map[epsliceutil.PortMapKey]*endpointMeta{}\n\n\tfor _, extWorkload := range extWorkloads {\n\t\t// We skip workloads with no IPs.\n\t\t//\n\t\t// Note: workloads only have a 'Ready' status so we do not care about\n\t\t// other status conditions.\n\t\tif len(extWorkload.Spec.WorkloadIPs) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Find which ports a service selects (or maps to) on an external workload\n\t\t// Note: we require all workload ports are documented. Pods do not have\n\t\t// to document all of their container ports.\n\t\tports := r.findEndpointPorts(svc, extWorkload)\n\t\tportHash := epsliceutil.NewPortMapKey(ports)\n\t\tif _, ok := desiredMetaByPortMap[portHash]; !ok {\n\t\t\tdesiredMetaByPortMap[portHash] = &endpointMeta{ports, addrType}\n\t\t}\n\n\t\tif _, ok := desiredEndpointsByPortMap[portHash]; !ok {\n\t\t\tdesiredEndpointsByPortMap[portHash] = epsliceutil.EndpointSet{}\n\t\t}\n\n\t\tep := externalWorkloadToEndpoint(addrType, extWorkload, svc)\n\t\tif len(ep.Addresses) > 0 {\n\t\t\tdesiredEndpointsByPortMap[portHash].Insert(&ep)\n\t\t}\n\t}\n\n\tfor portKey, desiredEndpoints := range desiredEndpointsByPortMap {\n\t\tcreate, update, del := r.reconcileEndpointsByPortMap(svc, existingSlicesByPorts[portKey], desiredEndpoints, desiredMetaByPortMap[portKey])\n\t\tslicesToCreate = append(slicesToCreate, create...)\n\t\tslicesToUpdate = append(slicesToUpdate, update...)\n\t\tslicesToDelete = append(slicesToDelete, del...)\n\t}\n\n\t// If there are any slices whose ports no longer match what we want in our\n\t// current reconciliation, delete them\n\tfor portHash, existingSlices := range existingSlicesByPorts {\n\t\tif _, ok := desiredEndpointsByPortMap[portHash]; !ok {\n\t\t\tslicesToDelete = append(slicesToDelete, existingSlices...)\n\t\t}\n\t}\n\n\treturn r.finalize(svc, slicesToCreate, slicesToUpdate, slicesToDelete)\n}\n\n// reconcileEndpointsByPortMap will compute the state diff to be written to the\n// API Server for a service. The function takes into account any existing\n// endpoint slices and any external workloads matched by the service.\n// The function works on slices and workloads that have been already grouped by\n// a common set of ports.\nfunc (r *endpointsReconciler) reconcileEndpointsByPortMap(svc *corev1.Service, existingSlices []*discoveryv1.EndpointSlice, desiredEps epsliceutil.EndpointSet, desiredMeta *endpointMeta) ([]*discoveryv1.EndpointSlice, []*discoveryv1.EndpointSlice, []*discoveryv1.EndpointSlice) {\n\tslicesByName := map[string]*discoveryv1.EndpointSlice{}\n\tsliceNamesUnchanged := map[string]struct{}{}\n\tsliceNamesToUpdate := map[string]struct{}{}\n\tsliceNamesToDelete := map[string]struct{}{}\n\n\t// 1. Figure out which endpoints are no longer required in the existing\n\t// slices, and update endpoints that have changed\n\tfor _, existingSlice := range existingSlices {\n\t\tslicesByName[existingSlice.Name] = existingSlice\n\t\tkeepEndpoints := []discoveryv1.Endpoint{}\n\t\tepUpdated := false\n\t\tfor _, endpoint := range existingSlice.Endpoints {\n\t\t\tendpoint := endpoint // pin\n\t\t\tfound := desiredEps.Get(&endpoint)\n\t\t\t// If the endpoint is desired (i.e. a workload exists with an IP and\n\t\t\t// we want to add it to the service's endpoints), then we should\n\t\t\t// keep it.\n\t\t\tif found != nil {\n\t\t\t\tkeepEndpoints = append(keepEndpoints, *found)\n\t\t\t\t// We know the slice already contains an endpoint we want, but\n\t\t\t\t// has the endpoint changed? If yes, we need to persist it\n\t\t\t\tif !epsliceutil.EndpointsEqualBeyondHash(found, &endpoint) {\n\t\t\t\t\tepUpdated = true\n\t\t\t\t}\n\n\t\t\t\t// Once an endpoint has been found in a slice, we can delete it\n\t\t\t\tdesiredEps.Delete(&endpoint)\n\t\t\t}\n\t\t}\n\n\t\t// Re-generate labels and see whether service's labels have changed\n\t\tlabels, labelsChanged := setEndpointSliceLabels(existingSlice, svc, r.controllerName)\n\n\t\t// Consider what kind of reconciliation we should proceed with:\n\t\t//\n\t\t// 1. We can have a set of endpoints that have changed; this can either\n\t\t// mean we need to update the endpoints, or it can also mean we have no\n\t\t// endpoints to keep.\n\t\t// 2. We need to update the slice's metadata because labels have\n\t\t// changed.\n\t\t// 3. Slice remains unchanged so we have a noop on our hands\n\t\tif epUpdated || len(existingSlice.Endpoints) != len(keepEndpoints) {\n\t\t\tif len(keepEndpoints) == 0 {\n\t\t\t\t// When there are no endpoints to keep, then the slice should be\n\t\t\t\t// deleted\n\t\t\t\tsliceNamesToDelete[existingSlice.Name] = struct{}{}\n\t\t\t} else {\n\t\t\t\t// There is at least one endpoint to keep / update\n\t\t\t\tslice := existingSlice.DeepCopy()\n\t\t\t\tslice.Labels = labels\n\t\t\t\tslice.Endpoints = keepEndpoints\n\t\t\t\tsliceNamesToUpdate[slice.Name] = struct{}{}\n\t\t\t\tslicesByName[slice.Name] = slice\n\t\t\t}\n\t\t} else if labelsChanged {\n\t\t\tslice := existingSlice.DeepCopy()\n\t\t\tslice.Labels = labels\n\t\t\tsliceNamesToUpdate[slice.Name] = struct{}{}\n\t\t\tslicesByName[slice.Name] = slice\n\t\t} else {\n\t\t\t// Unchanged, we save it for later.\n\t\t\t// unchanged slices may receive new endpoints that are leftover if\n\t\t\t// they're not past their quotaca\n\t\t\tsliceNamesUnchanged[existingSlice.Name] = struct{}{}\n\t\t}\n\t}\n\n\t// 2. If we still have desired endpoints left, but they haven't matched any\n\t// endpoint that already exists in a slice, we need to add it somewhere.\n\t//\n\t// We start by adding our leftover endpoints to the list of endpoints we\n\t// will update anyway (to save a write).\n\tif desiredEps.Len() > 0 && len(sliceNamesToUpdate) > 0 {\n\t\tslices := []*discoveryv1.EndpointSlice{}\n\t\tfor sliceName := range sliceNamesToUpdate {\n\t\t\tslices = append(slices, slicesByName[sliceName])\n\t\t}\n\n\t\t// Sort in descending order of capacity; fullest first.\n\t\tsort.Slice(slices, func(i, j int) bool {\n\t\t\treturn len(slices[i].Endpoints) > len(slices[j].Endpoints)\n\t\t})\n\n\t\t// Iterate and fill up the slices\n\t\tfor _, slice := range slices {\n\t\t\tfor desiredEps.Len() > 0 && len(slice.Endpoints) < r.maxEndpoints {\n\t\t\t\tep, _ := desiredEps.PopAny()\n\t\t\t\tslice.Endpoints = append(slice.Endpoints, *ep)\n\t\t\t}\n\t\t}\n\t}\n\n\t// If we have remaining endpoints, we need to deal with them\n\t// by using unchanged slices or creating new ones\n\tslicesToCreate := []*discoveryv1.EndpointSlice{}\n\tfor desiredEps.Len() > 0 {\n\t\tvar sliceToFill *discoveryv1.EndpointSlice\n\n\t\t// Deal with any remaining endpoints by:\n\t\t// (a) adding to unchanged slices first\n\t\tif desiredEps.Len() < r.maxEndpoints && len(sliceNamesUnchanged) > 0 {\n\t\t\tunchangedSlices := []*discoveryv1.EndpointSlice{}\n\t\t\tfor unchangedSlice := range sliceNamesUnchanged {\n\t\t\t\tunchangedSlices = append(unchangedSlices, slicesByName[unchangedSlice])\n\t\t\t}\n\n\t\t\tsliceToFill = getSliceToFill(unchangedSlices, desiredEps.Len(), r.maxEndpoints)\n\t\t}\n\n\t\t// If we have no unchanged slice to fill, then\n\t\t// (b) create a new slice\n\t\tif sliceToFill == nil {\n\t\t\tsliceToFill = newEndpointSlice(svc, desiredMeta, r.controllerName)\n\t\t} else {\n\t\t\t// deep copy required to mutate slice\n\t\t\tsliceToFill = sliceToFill.DeepCopy()\n\t\t\tslicesByName[sliceToFill.Name] = sliceToFill\n\t\t}\n\n\t\t// Fill out the slice\n\t\tfor desiredEps.Len() > 0 && len(sliceToFill.Endpoints) < r.maxEndpoints {\n\t\t\tep, _ := desiredEps.PopAny()\n\t\t\tsliceToFill.Endpoints = append(sliceToFill.Endpoints, *ep)\n\t\t}\n\n\t\t// Figure out what kind of slice we just filled and update the diffed\n\t\t// state\n\t\tif sliceToFill.Name != \"\" {\n\t\t\tsliceNamesToUpdate[sliceToFill.Name] = struct{}{}\n\t\t\tdelete(sliceNamesUnchanged, sliceToFill.Name)\n\t\t} else {\n\t\t\tslicesToCreate = append(slicesToCreate, sliceToFill)\n\t\t}\n\t}\n\n\tslicesToUpdate := []*discoveryv1.EndpointSlice{}\n\tfor name := range sliceNamesToUpdate {\n\t\tslicesToUpdate = append(slicesToUpdate, slicesByName[name])\n\t}\n\n\tslicesToDelete := []*discoveryv1.EndpointSlice{}\n\tfor name := range sliceNamesToDelete {\n\t\tslicesToDelete = append(slicesToDelete, slicesByName[name])\n\t}\n\n\treturn slicesToCreate, slicesToUpdate, slicesToDelete\n}\n\n// finalize performs writes to the API Server to update the state after it's\n// been diffed.\nfunc (r *endpointsReconciler) finalize(svc *corev1.Service, slicesToCreate, slicesToUpdate, slicesToDelete []*discoveryv1.EndpointSlice) error {\n\t// If there are slices to create and delete, change the creates to updates\n\t// of the slices that would otherwise be deleted.\n\tfor i := 0; i < len(slicesToDelete); {\n\t\tif len(slicesToCreate) == 0 {\n\t\t\tbreak\n\t\t}\n\t\tsliceToDelete := slicesToDelete[i]\n\t\tslice := slicesToCreate[len(slicesToCreate)-1]\n\t\t// Only update EndpointSlices that are owned by this Service and have\n\t\t// the same AddressType. We need to avoid updating EndpointSlices that\n\t\t// are being garbage collected for an old Service with the same name.\n\t\t// The AddressType field is immutable. Since Services also consider\n\t\t// IPFamily immutable, the only case where this should matter will be\n\t\t// the migration from IP to IPv4 and IPv6 AddressTypes, where there's a\n\t\t// chance EndpointSlices with an IP AddressType would otherwise be\n\t\t// updated to IPv4 or IPv6 without this check.\n\t\tif sliceToDelete.AddressType == slice.AddressType && ownedBy(sliceToDelete, svc) {\n\t\t\tslice.Name = sliceToDelete.Name\n\t\t\tslicesToCreate = slicesToCreate[:len(slicesToCreate)-1]\n\t\t\tslicesToUpdate = append(slicesToUpdate, slice)\n\t\t\tslicesToDelete = append(slicesToDelete[:i], slicesToDelete[i+1:]...)\n\t\t} else {\n\t\t\ti++\n\t\t}\n\t}\n\n\tr.log.Debugf(\"reconciliation result for %s/%s: %d to add, %d to update, %d to remove\", svc.Namespace, svc.Name, len(slicesToCreate), len(slicesToUpdate), len(slicesToDelete))\n\n\t// Create EndpointSlices only if the service has not been marked for\n\t// deletion; according to the upstream implementation not doing so has the\n\t// potential to cause race conditions\n\tif svc.DeletionTimestamp == nil {\n\t\t// TODO: context with timeout\n\t\tfor _, slice := range slicesToCreate {\n\t\t\tr.log.Tracef(\"starting create: %s/%s\", slice.Namespace, slice.Name)\n\t\t\tcreatedSlice, err := r.k8sAPI.Client.DiscoveryV1().EndpointSlices(svc.Namespace).Create(context.TODO(), slice, metav1.CreateOptions{})\n\t\t\tif err != nil {\n\t\t\t\t// If the namespace  is terminating, operations will not\n\t\t\t\t// succeed. Drop the entire reconiliation effort\n\t\t\t\tif errors.HasStatusCause(err, corev1.NamespaceTerminatingCause) {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.endpointTracker.Update(createdSlice)\n\t\t\tr.log.Tracef(\"finished creating: %s/%s\", createdSlice.Namespace, createdSlice.Name)\n\t\t}\n\t}\n\n\tfor _, slice := range slicesToUpdate {\n\t\tr.log.Tracef(\"starting update: %s/%s\", slice.Namespace, slice.Name)\n\t\tupdatedSlice, err := r.k8sAPI.Client.DiscoveryV1().EndpointSlices(svc.Namespace).Update(context.TODO(), slice, metav1.UpdateOptions{})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.endpointTracker.Update(updatedSlice)\n\t\tr.log.Tracef(\"finished updating: %s/%s\", updatedSlice.Namespace, updatedSlice.Name)\n\t}\n\n\tfor _, slice := range slicesToDelete {\n\t\tr.log.Tracef(\"starting delete: %s/%s\", slice.Namespace, slice.Name)\n\t\terr := r.k8sAPI.Client.DiscoveryV1().EndpointSlices(svc.Namespace).Delete(context.TODO(), slice.Name, metav1.DeleteOptions{})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.endpointTracker.ExpectDeletion(slice)\n\t\tr.log.Tracef(\"finished deleting: %s/%s\", slice.Namespace, slice.Name)\n\t}\n\n\treturn nil\n}\n\n// === Utility ===\n\n// Creates a new endpointslice object\nfunc newEndpointSlice(svc *corev1.Service, meta *endpointMeta, controllerName string) *discoveryv1.EndpointSlice {\n\t// We need an ownerRef to point to our service\n\townerRef := metav1.NewControllerRef(svc, schema.GroupVersionKind{Version: \"v1\", Kind: \"Service\"})\n\tslice := &discoveryv1.EndpointSlice{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tGenerateName:    fmt.Sprintf(\"linkerd-external-%s-\", svc.Name),\n\t\t\tNamespace:       svc.Namespace,\n\t\t\tLabels:          map[string]string{},\n\t\t\tOwnerReferences: []metav1.OwnerReference{*ownerRef},\n\t\t},\n\t\tAddressType: meta.addressType,\n\t\tEndpoints:   []discoveryv1.Endpoint{},\n\t\tPorts:       meta.ports,\n\t}\n\tlabels, _ := setEndpointSliceLabels(slice, svc, controllerName)\n\tslice.Labels = labels\n\treturn slice\n}\n\n// getSliceToFill will return an endpoint slice from a list of endpoint slices\n// whose capacity is closest to being full when numEndpoints are added. If no\n// slice fits the criteria a nil pointer is returned\nfunc getSliceToFill(slices []*discoveryv1.EndpointSlice, numEndpoints, maxEndpoints int) *discoveryv1.EndpointSlice {\n\tclosestDiff := maxEndpoints\n\tvar closestSlice *discoveryv1.EndpointSlice\n\tfor _, slice := range slices {\n\t\tdiff := maxEndpoints - (numEndpoints + len(slice.Endpoints))\n\t\tif diff >= 0 && diff < closestDiff {\n\t\t\tclosestDiff = diff\n\t\t\tclosestSlice = slice\n\t\t\tif closestDiff == 0 {\n\t\t\t\treturn closestSlice\n\t\t\t}\n\t\t}\n\t}\n\treturn closestSlice\n}\n\n// setEndpointSliceLabels returns a new map with the new endpoint slice labels,\n// and returns true if there was an update.\n//\n// Slice labels should always be equivalent to Service labels, except for a\n// reserved IsHeadlessService, LabelServiceName, and LabelManagedBy. If any\n// reserved labels have changed on the service, they are not copied over.\n//\n// copied from https://github.com/kubernetes/endpointslice/commit/a09c1c9580d13f5020248d25c7fd11f5dde6dd9b\n// copyright 2019 The Kubernetes Authors\nfunc setEndpointSliceLabels(es *discoveryv1.EndpointSlice, service *corev1.Service, controllerName string) (map[string]string, bool) {\n\tisReserved := func(label string) bool {\n\t\tif label == discoveryv1.LabelServiceName ||\n\t\t\tlabel == discoveryv1.LabelManagedBy ||\n\t\t\tlabel == corev1.IsHeadlessService {\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\n\tupdated := false\n\tepLabels := make(map[string]string)\n\tsvcLabels := make(map[string]string)\n\n\t// check if the endpoint slice and the service have the same labels\n\t// clone current slice labels except the reserved labels\n\tfor key, value := range es.Labels {\n\t\tif isReserved(key) {\n\t\t\tcontinue\n\t\t}\n\t\t// copy endpoint slice labels\n\t\tepLabels[key] = value\n\t}\n\n\tfor key, value := range service.Labels {\n\t\tif isReserved(key) {\n\t\t\tcontinue\n\t\t}\n\t\t// copy service labels\n\t\tsvcLabels[key] = value\n\t}\n\n\t// if the labels are not identical update the slice with the corresponding service labels\n\tfor svcLabelKey, svcLabelVal := range svcLabels {\n\t\tepLabelVal, found := epLabels[svcLabelKey]\n\t\tif !found {\n\t\t\tupdated = true\n\t\t\tbreak\n\t\t}\n\n\t\tif svcLabelVal != epLabelVal {\n\t\t\tupdated = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// add or remove headless label depending on the service Type\n\tif service.Spec.ClusterIP == corev1.ClusterIPNone {\n\t\tsvcLabels[corev1.IsHeadlessService] = \"\"\n\t} else {\n\t\tdelete(svcLabels, corev1.IsHeadlessService)\n\t}\n\n\t// override endpoint slices reserved labels\n\tsvcLabels[discoveryv1.LabelServiceName] = service.Name\n\tsvcLabels[discoveryv1.LabelManagedBy] = controllerName\n\n\treturn svcLabels, updated\n}\n\nfunc externalWorkloadToEndpoint(addrType discoveryv1.AddressType, ew *ewv1beta1.ExternalWorkload, svc *corev1.Service) discoveryv1.Endpoint {\n\t// Note: an ExternalWorkload does not have the same lifecycle as a pod; we\n\t// do not mark a workload as \"Terminating\". Because of that, our code is\n\t// simpler than the upstream and we never have to consider:\n\t// * publishNotReadyAddresses (found on a service)\n\t// * deletionTimestamps (found normally on a pod)\n\t// * or a terminating flag on the endpoint\n\tserving := IsEwReady(ew)\n\n\taddresses := []string{}\n\t// We assume the workload has been validated beforehand and contains a valid\n\t// IP address regardless of its address family.\n\tfor _, addr := range ew.Spec.WorkloadIPs {\n\t\tip := addr.Ip\n\t\tisIPv6 := utilnet.IsIPv6String(ip)\n\t\tif isIPv6 && addrType == discoveryv1.AddressTypeIPv6 {\n\t\t\taddresses = append(addresses, ip)\n\t\t} else if !isIPv6 && addrType == discoveryv1.AddressTypeIPv4 {\n\t\t\taddresses = append(addresses, ip)\n\t\t}\n\t}\n\n\tterminating := false\n\tep := discoveryv1.Endpoint{\n\t\tAddresses: addresses,\n\t\tConditions: discoveryv1.EndpointConditions{\n\t\t\tReady:       &serving,\n\t\t\tServing:     &serving,\n\t\t\tTerminating: &terminating,\n\t\t},\n\t\tTargetRef: &corev1.ObjectReference{\n\t\t\tKind:      \"ExternalWorkload\",\n\t\t\tNamespace: ew.Namespace,\n\t\t\tName:      ew.Name,\n\t\t\tUID:       ew.UID,\n\t\t},\n\t}\n\n\tzone, ok := ew.Labels[corev1.LabelTopologyZone]\n\tif ok {\n\t\tep.Zone = &zone\n\t}\n\n\t// Add a hostname conditionally\n\t// Note: upstream does this a bit differently; pods may include a hostname\n\t// as part of their spec. We consider a hostname as long as the service is\n\t// headless since that's what we would use a hostname for when routing in\n\t// linkerd (we care about DNS record creation)\n\tif svc.Spec.ClusterIP == corev1.ClusterIPNone && ew.Namespace == svc.Namespace {\n\t\tep.Hostname = &ew.Name\n\t}\n\n\treturn ep\n}\n\nfunc ownedBy(slice *discoveryv1.EndpointSlice, svc *corev1.Service) bool {\n\tfor _, o := range slice.OwnerReferences {\n\t\tif o.UID == svc.UID && o.Kind == \"Service\" && o.APIVersion == \"v1\" {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// findEndpointPorts is a utility function that will return a list of ports\n// that are documented on an external workload and selected by a service\nfunc (r *endpointsReconciler) findEndpointPorts(svc *corev1.Service, ew *ewv1beta1.ExternalWorkload) []discoveryv1.EndpointPort {\n\tepPorts := []discoveryv1.EndpointPort{}\n\t// If we are dealing with a headless service, upstream implementation allows\n\t// the service not to have any ports\n\tif len(svc.Spec.Ports) == 0 && svc.Spec.ClusterIP == corev1.ClusterIPNone {\n\t\treturn epPorts\n\t}\n\n\tfor _, svcPort := range svc.Spec.Ports {\n\t\tsvcPort := svcPort // pin\n\t\tportNum, err := findWorkloadPort(ew, &svcPort)\n\t\tif err != nil {\n\t\t\tr.log.Errorf(\"failed to find port for service %s/%s: %v\", svc.Namespace, svc.Name, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tportName := &svcPort.Name\n\t\tif *portName == \"\" {\n\t\t\tportName = nil\n\t\t}\n\t\tportProto := &svcPort.Protocol\n\t\tif *portProto == \"\" {\n\t\t\tportProto = nil\n\t\t}\n\t\tepPorts = append(epPorts, discoveryv1.EndpointPort{\n\t\t\tName:     portName,\n\t\t\tPort:     &portNum,\n\t\t\tProtocol: portProto,\n\t\t})\n\t}\n\n\treturn epPorts\n}\n\n// findWorkloadPort is provided a service port and an external workload and\n// checks whether the workload documents in its spec the target port referenced\n// by the service.\n//\n// adapted from copied from k8s.io/kubernetes/pkg/api/v1/pod\nfunc findWorkloadPort(ew *ewv1beta1.ExternalWorkload, svcPort *corev1.ServicePort) (int32, error) {\n\ttargetPort := svcPort.TargetPort\n\tswitch targetPort.Type {\n\tcase intstr.String:\n\t\tname := targetPort.StrVal\n\t\tfor _, wPort := range ew.Spec.Ports {\n\t\t\tif wPort.Name == name && wPort.Protocol == svcPort.Protocol {\n\t\t\t\treturn wPort.Port, nil\n\t\t\t}\n\t\t}\n\tcase intstr.Int:\n\t\t// Ensure the port is documented in the workload spec, since we\n\t\t// require it.\n\t\t// Upstream version allows for undocumented container ports here (i.e.\n\t\t// it returns the int value).\n\t\tfor _, wPort := range ew.Spec.Ports {\n\t\t\tport := int32(targetPort.IntValue())\n\t\t\tif wPort.Port == port && wPort.Protocol == svcPort.Protocol {\n\t\t\t\treturn port, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn 0, fmt.Errorf(\"no suitable port for targetPort %s on workload %s/%s\", targetPort.String(), ew.Namespace, ew.Name)\n}\n\n// getSupportedAddressTypes will return a set of address families (AF) supported\n// by this service. A service may be IPv4 or IPv6 only, or it may be dual-stack.\nfunc getSupportedAddressTypes(svc *corev1.Service) map[discoveryv1.AddressType]struct{} {\n\tafs := map[discoveryv1.AddressType]struct{}{}\n\t// Field only applies to LoadBalancer, ClusterIP and NodePort services. A\n\t// headless service will not receive any IP families; it may hold max 2\n\t// entries and can be mutated (although the 'primary' choice is never\n\t// removed).\n\t// See client-go type documentation for more info.\n\tfor _, af := range svc.Spec.IPFamilies {\n\t\tif af == corev1.IPv4Protocol {\n\t\t\tafs[discoveryv1.AddressTypeIPv4] = struct{}{}\n\t\t} else if af == corev1.IPv6Protocol {\n\t\t\tafs[discoveryv1.AddressTypeIPv6] = struct{}{}\n\t\t}\n\t}\n\n\tif len(afs) > 0 {\n\t\t// If we appended at least one address family, it means we didn't have\n\t\t// to deal with a headless service.\n\t\treturn afs\n\t}\n\n\t// Note: our logic will differ from the upstream Kubernetes controller.\n\t// Specifically, our minimum k8s version is greater than v1.20. Upstream\n\t// controller needs to handle an upgrade path from v1.19 to newer APIs,\n\t// which we disregard since we can assume all services will see contain the\n\t// `IPFamilies` field\n\t//\n\t// Our only other option is to have a headless service. Our ExternalWorkload\n\t// CRD is generic over the AF used so we may create slices for both AF_INET\n\t// and AF_INET6\n\tafs[discoveryv1.AddressTypeIPv4] = struct{}{}\n\tafs[discoveryv1.AddressTypeIPv6] = struct{}{}\n\treturn afs\n}\n"
  },
  {
    "path": "controller/api/destination/external-workload/endpoints_reconciler_test.go",
    "content": "package externalworkload\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\n\tewv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tdiscoveryv1 \"k8s.io/api/discovery/v1\"\n\tmetav1 \"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\tk8stesting \"k8s.io/client-go/testing\"\n\tepsliceutil \"k8s.io/endpointslice/util\"\n\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n\t\"k8s.io/apimachinery/pkg/util/rand\"\n\t\"sigs.k8s.io/yaml\"\n)\n\ntype IP struct {\n\taddressType discoveryv1.AddressType\n\tip          string\n}\n\nvar (\n\thttpUnnamedPort = corev1.ServicePort{\n\t\tPort: 8080,\n\t\tTargetPort: intstr.IntOrString{\n\t\t\tType:   intstr.Int,\n\t\t\tIntVal: 8080,\n\t\t},\n\t}\n\n\thttpNamedPort = corev1.ServicePort{\n\t\tTargetPort: intstr.IntOrString{\n\t\t\tType:   intstr.String,\n\t\t\tStrVal: \"http\",\n\t\t},\n\t}\n\n\tdefaultTestEndpointsQuota = 100\n\n\ttestControllerName = \"test-controller\"\n)\n\n// === Test create / update / delete ===\n\n// Test that when a service has no endpointslices written to the API Server, reconciling\n// with a workload will create new endpointslices (one per IP family)\nfunc TestReconcilerCreatesNewEndpointSlices(t *testing.T) {\n\t// We do not need to receive anything through the informers so\n\t// create a client with no cached resources\n\tk8sAPI, err := k8s.NewFakeAPI([]string{}...)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when creating Kubernetes clientset: %v\", err)\n\t}\n\n\tfor _, tc := range []struct {\n\t\tapp      string\n\t\tfamilies []corev1.IPFamily\n\t\tIPs      []IP\n\t}{\n\t\t{\n\t\t\t\"testIPv4\",\n\t\t\t[]corev1.IPFamily{corev1.IPv4Protocol},\n\t\t\t[]IP{\n\t\t\t\t{discoveryv1.AddressTypeIPv4, \"192.0.2.0\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"testIPv6\",\n\t\t\t[]corev1.IPFamily{corev1.IPv6Protocol},\n\t\t\t[]IP{\n\t\t\t\t{discoveryv1.AddressTypeIPv6, \"2001:db8::8a2e:370:7334\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"testDualStack\",\n\t\t\t[]corev1.IPFamily{corev1.IPv4Protocol, corev1.IPv6Protocol},\n\t\t\t[]IP{\n\t\t\t\t{discoveryv1.AddressTypeIPv4, \"192.0.2.0\"},\n\t\t\t\t{discoveryv1.AddressTypeIPv6, \"2001:db8::8a2e:370:7334\"},\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(tc.app, func(t *testing.T) {\n\t\t\tsvc := makeService(tc.app, tc.families, map[string]string{\"app\": tc.app}, []corev1.ServicePort{httpUnnamedPort}, \"\")\n\n\t\t\tIPs := []string{}\n\t\t\tfor _, ip := range tc.IPs {\n\t\t\t\tIPs = append(IPs, ip.ip)\n\t\t\t}\n\t\t\tew := makeExternalWorkload(\"1\", \"wlkd-\"+tc.app, map[string]string{\"app\": \"\"}, map[int32]string{8080: \"\"}, IPs)\n\n\t\t\tr := newEndpointsReconciler(k8sAPI, testControllerName, defaultTestEndpointsQuota)\n\t\t\terr = r.reconcile(svc, []*ewv1beta1.ExternalWorkload{ew}, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error when reconciling endpoints: %v\", err)\n\t\t\t}\n\n\t\t\tendpointSlices := fetchEndpointSlices(t, k8sAPI, svc)\n\t\t\tif len(endpointSlices) != len(tc.families) {\n\t\t\t\tt.Fatalf(\"expected %d endpointslices after reconciliation, got %d instead\", len(tc.families), len(endpointSlices))\n\t\t\t}\n\n\t\t\tfor _, ip := range tc.IPs {\n\t\t\t\texpectedEndpoint := makeEndpoint([]string{ip.ip}, true, ew)\n\n\t\t\t\tvar matchingES *discoveryv1.EndpointSlice\n\t\t\t\tfor _, es := range endpointSlices {\n\t\t\t\t\tes := es\n\t\t\t\t\tif es.AddressType == ip.addressType {\n\t\t\t\t\t\tmatchingES = &es\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif matchingES == nil {\n\t\t\t\t\tt.Fatalf(\"expected to find endpointslice for IP family %s, but none was found\", ip.addressType)\n\t\t\t\t}\n\n\t\t\t\tif len(matchingES.Endpoints) != 1 {\n\t\t\t\t\tt.Fatalf(\"expected %d endpointslices endpoints after reconciliation, got %d instead\", 1, len(matchingES.Endpoints))\n\t\t\t\t}\n\n\t\t\t\tep := matchingES.Endpoints[0]\n\t\t\t\tdiffEndpoints(t, ep, expectedEndpoint)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Test that when a service has no endpointslices written to the API Server, reconciling\n// with a workload will create a new endpointslice. Since it is a headless\n// service, we will also get a hostname\nfunc TestReconcilerCreatesNewEndpointSliceHeadless(t *testing.T) {\n\t// We do not need to receive anything through the informers so\n\t// create a client with no cached resources\n\tk8sAPI, err := k8s.NewFakeAPI([]string{}...)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when creating Kubernetes clientset: %v\", err)\n\t}\n\n\tsvc := makeService(\"test-svc\", []corev1.IPFamily{corev1.IPv4Protocol}, map[string]string{\"app\": \"test\"}, []corev1.ServicePort{httpUnnamedPort}, \"\")\n\tsvc.Spec.ClusterIP = corev1.ClusterIPNone\n\tew := makeExternalWorkload(\"1\", \"wlkd-1\", map[string]string{\"app\": \"\"}, map[int32]string{8080: \"\"}, []string{\"192.0.2.0\"})\n\tew.Namespace = \"default\"\n\tew.ObjectMeta.UID = types.UID(fmt.Sprintf(\"%s-%s\", ew.Namespace, ew.Name))\n\n\tr := newEndpointsReconciler(k8sAPI, testControllerName, defaultTestEndpointsQuota)\n\terr = r.reconcile(svc, []*ewv1beta1.ExternalWorkload{ew}, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when reconciling endpoints: %v\", err)\n\t}\n\n\texpectedEndpoint := makeEndpoint([]string{\"192.0.2.0\"}, true, ew)\n\tes := fetchEndpointSlices(t, k8sAPI, svc)\n\tif len(es) != 1 {\n\t\tt.Fatalf(\"expected %d endpointslices after reconciliation, got %d instead\", 1, len(es))\n\t}\n\n\tif len(es[0].Endpoints) != 1 {\n\t\tt.Fatalf(\"expected %d endpointslices after reconciliation, got %d instead\", 1, len(es[0].Endpoints))\n\t}\n\n\tif es[0].AddressType != discoveryv1.AddressTypeIPv4 {\n\t\tt.Fatalf(\"expected endpointslice to have AF %s, got %s instead\", discoveryv1.AddressTypeIPv4, es[0].AddressType)\n\t}\n\tep := es[0].Endpoints[0]\n\tdiffEndpoints(t, ep, expectedEndpoint)\n\n\tif _, ok := es[0].Labels[corev1.IsHeadlessService]; !ok {\n\t\tt.Errorf(\"expected \\\"%s\\\" label to be present on the service\", corev1.IsHeadlessService)\n\t}\n\n\tif ep.Hostname == nil {\n\t\tt.Fatalf(\"expected endpoint to have a hostname\")\n\t}\n\n\tif *ep.Hostname != ew.Name {\n\t\tt.Errorf(\"expected \\\"%s\\\" as a hostname, got: %s\", ew.Name, *ep.Hostname)\n\t}\n\n}\n\n// Test that when a service has an endpointslice written to the API Server,\n// reconciling with the two workloads updates the endpointslice\nfunc TestReconcilerUpdatesEndpointSlice(t *testing.T) {\n\t// Create a service\n\tsvc := makeService(\"test-svc\", []corev1.IPFamily{corev1.IPv4Protocol, corev1.IPv6Protocol}, map[string]string{\"app\": \"test\"}, []corev1.ServicePort{httpUnnamedPort}, \"\")\n\n\t// Create our existing workload\n\tewCreated := makeExternalWorkload(\"1\", \"wlkd-1\", map[string]string{\"app\": \"test\"}, map[int32]string{8080: \"\"}, []string{\"192.0.2.1\", \"2001:db8::8a2e:370:7333\"})\n\n\t// Create endpointslices for IPv4 and IPv6\n\tport := int32(8080)\n\tports := []discoveryv1.EndpointPort{{\n\t\tPort: &port,\n\t}}\n\tesIPv4, esIPv6 := makeDualEndpointSlices(svc, ports)\n\tendpointsIPv4 := []discoveryv1.Endpoint{externalWorkloadToEndpoint(discoveryv1.AddressTypeIPv4, ewCreated, svc)}\n\tesIPv4.Endpoints = endpointsIPv4\n\tesIPv4.Generation = 1\n\tendpointsIPv6 := []discoveryv1.Endpoint{externalWorkloadToEndpoint(discoveryv1.AddressTypeIPv6, ewCreated, svc)}\n\tesIPv6.Endpoints = endpointsIPv6\n\tesIPv6.Generation = 1\n\n\t// Create our \"new\" workloads\n\tewUpdatedIPv4 := makeExternalWorkload(\"1\", \"wlkd-2\", map[string]string{\"app\": \"test\"}, map[int32]string{8080: \"\"}, []string{\"192.0.2.0\"})\n\tewUpdatedIPv6 := makeExternalWorkload(\"1\", \"wlkd-3\", map[string]string{\"app\": \"test\"}, map[int32]string{8080: \"\"}, []string{\"2001:db8::8a2e:370:7334\"})\n\n\t// Convert endpointslice to string and register with fake client\n\tk8sAPI, err := k8s.NewFakeAPI(endpointSliceAsYaml(t, esIPv4), endpointSliceAsYaml(t, esIPv6))\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when creating Kubernetes clientset: %v\", err)\n\t}\n\n\tr := newEndpointsReconciler(k8sAPI, testControllerName, defaultTestEndpointsQuota)\n\terr = r.reconcile(svc, []*ewv1beta1.ExternalWorkload{ewCreated, ewUpdatedIPv4, ewUpdatedIPv6}, []*discoveryv1.EndpointSlice{esIPv4, esIPv6})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when reconciling endpoints: %v\", err)\n\t}\n\n\tfor _, ep := range getEndpoints(t, k8sAPI, svc, esIPv4) {\n\t\tif ep.TargetRef.Name == ewUpdatedIPv4.Name {\n\t\t\texpectedEndpoint := makeEndpoint([]string{\"192.0.2.0\"}, true, ewUpdatedIPv4)\n\t\t\tdiffEndpoints(t, ep, expectedEndpoint)\n\t\t} else if ep.TargetRef.Name == ewCreated.Name {\n\t\t\texpectedEndpoint := makeEndpoint([]string{\"192.0.2.1\"}, true, ewCreated)\n\t\t\tdiffEndpoints(t, ep, expectedEndpoint)\n\t\t} else {\n\t\t\tt.Errorf(\"found unexpected targetRef name %s\", ep.TargetRef.Name)\n\t\t}\n\t}\n\n\tfor _, ep := range getEndpoints(t, k8sAPI, svc, esIPv6) {\n\t\tif ep.TargetRef.Name == ewUpdatedIPv6.Name {\n\t\t\texpectedEndpoint := makeEndpoint([]string{\"2001:db8::8a2e:370:7334\"}, true, ewUpdatedIPv6)\n\t\t\tdiffEndpoints(t, ep, expectedEndpoint)\n\t\t} else if ep.TargetRef.Name == ewCreated.Name {\n\t\t\texpectedEndpoint := makeEndpoint([]string{\"2001:db8::8a2e:370:7333\"}, true, ewCreated)\n\t\t\tdiffEndpoints(t, ep, expectedEndpoint)\n\t\t} else {\n\t\t\tt.Errorf(\"found unexpected targetRef name %s\", ep.TargetRef.Name)\n\t\t}\n\t}\n}\n\nfunc getEndpoints(\n\tt *testing.T,\n\tk8sAPI *k8s.API,\n\tsvc *corev1.Service,\n\tes *discoveryv1.EndpointSlice,\n) []discoveryv1.Endpoint {\n\tt.Helper()\n\tslice, err := k8sAPI.Client.DiscoveryV1().EndpointSlices(svc.Namespace).Get(context.Background(), es.Name, metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when retrieving endpointslice: %v\", err)\n\t}\n\tif len(slice.Endpoints) != 2 {\n\t\tt.Fatalf(\"expected %d endpointslices after reconciliation, got %d instead\", 2, len(slice.Endpoints))\n\t}\n\n\tif slice.AddressType != es.AddressType {\n\t\tt.Fatalf(\"expected endpointslice to have AF %s, got %s instead\", es.AddressType, slice.AddressType)\n\t}\n\n\treturn slice.Endpoints\n}\n\n// When an endpoint has changed, we should see the endpointslice change its\n// endpoint\nfunc TestReconcilerUpdatesEndpointSliceInPlace(t *testing.T) {\n\t// Create a service\n\tsvc := makeService(\"test-svc\", []corev1.IPFamily{corev1.IPv4Protocol}, map[string]string{\"app\": \"test\"}, []corev1.ServicePort{httpUnnamedPort}, \"\")\n\n\t// Create our existing workload\n\tewCreated := makeExternalWorkload(\"1\", \"wlkd-1\", map[string]string{\"app\": \"test\"}, map[int32]string{8080: \"\"}, []string{\"192.0.2.1\"})\n\n\t// Create an endpointslice\n\tport := int32(8080)\n\tports := []discoveryv1.EndpointPort{{\n\t\tPort: &port,\n\t}}\n\tes := makeEndpointSlice(svc, discoveryv1.AddressTypeIPv4, ports)\n\tendpoints := []discoveryv1.Endpoint{}\n\tendpoints = append(endpoints, externalWorkloadToEndpoint(discoveryv1.AddressTypeIPv4, ewCreated, svc))\n\tes.Endpoints = endpoints\n\tes.Generation = 1\n\n\t// Convert endpointslice to string and register with fake client\n\tk8sAPI, err := k8s.NewFakeAPI(endpointSliceAsYaml(t, es))\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when creating Kubernetes clientset: %v\", err)\n\t}\n\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when retrieving endpointslice: %v\", err)\n\t}\n\n\t// Change the workload\n\tewCreated.Labels = map[string]string{\n\t\tcorev1.LabelTopologyZone: \"zone1\",\n\t}\n\n\tr := newEndpointsReconciler(k8sAPI, testControllerName, defaultTestEndpointsQuota)\n\terr = r.reconcile(svc, []*ewv1beta1.ExternalWorkload{ewCreated, ewCreated}, []*discoveryv1.EndpointSlice{es})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when reconciling endpoints: %v\", err)\n\t}\n\n\tslice, err := k8sAPI.Client.DiscoveryV1().EndpointSlices(svc.Namespace).Get(context.Background(), es.Name, metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when retrieving endpointslice: %v\", err)\n\t}\n\tif len(slice.Endpoints) != 1 {\n\t\tt.Fatalf(\"expected %d endpointslices after reconciliation, got %d instead\", 1, len(slice.Endpoints))\n\t}\n\n\tif slice.AddressType != discoveryv1.AddressTypeIPv4 {\n\t\tt.Fatalf(\"expected endpointslice to have AF %s, got %s instead\", discoveryv1.AddressTypeIPv4, slice.AddressType)\n\t}\n\n\tif slice.Generation == 1 {\n\t\tt.Fatalf(\"expected endpointslice to have its generation bumped after update\")\n\t}\n\n\tif *slice.Endpoints[0].Zone != \"zone1\" {\n\t\tt.Fatalf(\"expected endpoint to be updated with new zone topology\")\n\t}\n}\n\n// === Test ports ===\n\n// A named port on a service can target a different port on a workload\nfunc TestReconcileEndpointSlicesNamedPorts(t *testing.T) {\n\tsvc := makeService(\"test-svc\", []corev1.IPFamily{corev1.IPv4Protocol}, map[string]string{\"app\": \"test\"}, []corev1.ServicePort{httpNamedPort}, \"192.0.2.1\")\n\tews := []*ewv1beta1.ExternalWorkload{}\n\t// Generate a large number of external workloads\n\t// randomise ports so that a named port maps to different target values\n\tfor i := 0; i < 300; i++ {\n\t\tready := !(i%3 == 0)\n\t\toffset := i % 5\n\t\tgenIp := fmt.Sprintf(\"192.%d.%d.%d\", i%5, i%3, i%2)\n\t\tgenPort := int32(8080 + offset)\n\t\tew := makeExternalWorkload(\"1\", fmt.Sprintf(\"wlkd-%d\", i), map[string]string{\"app\": \"test\"}, map[int32]string{genPort: \"http\"}, []string{genIp})\n\t\tew.Status.Conditions = []ewv1beta1.WorkloadCondition{newStatusCondition(ready)}\n\t\tews = append(ews, ew)\n\t}\n\n\tk8sAPI, err := k8s.NewFakeAPI([]string{}...)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when initializing API client: %v\", err)\n\t}\n\n\t// Start with 100 endpoints max quota. Since we have 5 possible ports\n\t// mapping to name 'http' we will generate 5 slices\n\tr := newEndpointsReconciler(k8sAPI, testControllerName, defaultTestEndpointsQuota)\n\tr.reconcile(svc, ews, []*discoveryv1.EndpointSlice{})\n\tslices := fetchEndpointSlices(t, k8sAPI, svc)\n\texpectedNumSlices := 5\n\tif len(slices) != expectedNumSlices {\n\t\tt.Fatalf(\"expected %d slices to be created, got %d instead\", expectedNumSlices, len(slices))\n\t}\n\n\t// We should have 5 slices with 60 endpoints each\n\texpectSlicesWithLengths(t, []int{60, 60, 60, 60, 60}, slices)\n\texpectedSlices := []discoveryv1.EndpointSlice{}\n\tfor i := range slices {\n\t\tport := int32(8080 + i)\n\t\texpectedSlices = append(expectedSlices, discoveryv1.EndpointSlice{\n\t\t\tPorts: []discoveryv1.EndpointPort{\n\t\t\t\t{\n\t\t\t\t\tPort: &port,\n\t\t\t\t},\n\t\t\t},\n\t\t\tAddressType: discoveryv1.AddressTypeIPv4,\n\t\t})\n\t}\n\n\t// Diff the ports\n\tdiffEndpointSlicePorts(t, expectedSlices, slices)\n}\n\n// === Test packing logic ===\n\n// a simple use case with 250 workloads matching a service and no existing slices\n// reconcile should create 3 slices, completely filling 2 of them\nfunc TestReconcileManyWorkloads(t *testing.T) {\n\tsvc := makeService(\"test-svc\", []corev1.IPFamily{corev1.IPv4Protocol}, map[string]string{\"app\": \"test\"}, []corev1.ServicePort{httpUnnamedPort}, \"10.0.2.1\")\n\t// start with 250 workloads\n\tews := []*ewv1beta1.ExternalWorkload{}\n\tfor i := 0; i < 250; i++ {\n\t\tready := !(i%3 == 0)\n\t\tgenIp := fmt.Sprintf(\"192.%d.%d.%d\", i%5, i%3, i%2)\n\t\tew := makeExternalWorkload(\"1\", fmt.Sprintf(\"wlkd-%d\", i), map[string]string{\"app\": \"test\"}, map[int32]string{8080: \"\"}, []string{genIp})\n\n\t\tew.Status.Conditions = []ewv1beta1.WorkloadCondition{newStatusCondition(ready)}\n\t\tews = append(ews, ew)\n\t}\n\n\tk8sAPI, actions := newClientset(t, []string{})\n\tr := newEndpointsReconciler(k8sAPI, testControllerName, defaultTestEndpointsQuota)\n\tr.reconcile(svc, ews, []*discoveryv1.EndpointSlice{})\n\texpectActions(t, actions(), 3, \"create\", \"endpointslices\")\n\n\tslices := fetchEndpointSlices(t, k8sAPI, svc)\n\texpectSlicesWithLengths(t, []int{100, 100, 50}, slices)\n}\n\n// Test with preexisting slices. 250 pods matching a service:\n// * First es: 62 endpoints (all desired)\n// * Second es: 61 endpoints (all desired)\n// We have 127 leftover to add.\n//\n// We will drop 27 in the first slice closest to full\nfunc TestReconcileEndpointSlicesSomePreexisting(t *testing.T) {\n\tsvc := makeService(\"test-svc\", []corev1.IPFamily{corev1.IPv4Protocol}, map[string]string{\"app\": \"test\"}, []corev1.ServicePort{httpUnnamedPort}, \"10.0.2.1\")\n\t// start with 250 workloads\n\tews := []*ewv1beta1.ExternalWorkload{}\n\tfor i := 0; i < 250; i++ {\n\t\tready := !(i%3 == 0)\n\t\tgenIp := fmt.Sprintf(\"192.%d.%d.%d\", i%5, i%3, i%2)\n\t\tew := makeExternalWorkload(\"1\", fmt.Sprintf(\"wlkd-%d\", i), map[string]string{\"app\": \"test\"}, map[int32]string{8080: \"\"}, []string{genIp})\n\n\t\tew.Status.Conditions = []ewv1beta1.WorkloadCondition{newStatusCondition(ready)}\n\t\tews = append(ews, ew)\n\t}\n\n\t// Create an endpointslice\n\tport := int32(8080)\n\tesPorts := []discoveryv1.EndpointPort{{\n\t\tPort: &port,\n\t}}\n\n\tes1 := makeEndpointSlice(svc, discoveryv1.AddressTypeIPv4, esPorts)\n\t// Take a quarter of workloads in the first slice\n\tfor i := 1; i < len(ews)-4; i += 4 {\n\t\taddrs := []string{ews[i].Spec.WorkloadIPs[0].Ip}\n\t\tisReady := IsEwReady(ews[i])\n\t\tes1.Endpoints = append(es1.Endpoints, makeEndpoint(addrs, isReady, ews[i]))\n\t}\n\n\tes2 := makeEndpointSlice(svc, discoveryv1.AddressTypeIPv4, esPorts)\n\t// Take a quarter of workloads in the second slice\n\tfor i := 3; i < len(ews)-4; i += 4 {\n\t\taddrs := []string{ews[i].Spec.WorkloadIPs[0].Ip}\n\t\tisReady := IsEwReady(ews[i])\n\t\tes2.Endpoints = append(es2.Endpoints, makeEndpoint(addrs, isReady, ews[i]))\n\t}\n\n\texistingSlices := []*discoveryv1.EndpointSlice{es1, es2}\n\tcmc := newCacheMutationCheck(existingSlices)\n\tk8sAPI, actions := newClientset(t, []string{})\n\tfor _, slice := range existingSlices {\n\t\t_, err := k8sAPI.Client.DiscoveryV1().EndpointSlices(svc.Namespace).Create(context.TODO(), slice, metav1.CreateOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error when creating Kubernetes obj: %v\", err)\n\t\t}\n\t}\n\n\tr := newEndpointsReconciler(k8sAPI, testControllerName, defaultTestEndpointsQuota)\n\tr.reconcile(svc, ews, existingSlices)\n\texpectActions(t, actions(), 2, \"update\", \"endpointslices\")\n\n\tslices := fetchEndpointSlices(t, k8sAPI, svc)\n\texpectSlicesWithLengths(t, []int{100, 100, 50}, slices)\n\n\t// ensure cache mutation has not occurred\n\tcmc.Check(t)\n}\n\n// Ensure reconciler updates everything in-place when a service requires a\n// change. That means we expect to only see updates, no creates.\nfunc TestReconcileEndpointSlicesUpdatingSvc(t *testing.T) {\n\tsvc := makeService(\"test-svc\", []corev1.IPFamily{corev1.IPv4Protocol}, map[string]string{\"app\": \"test\"}, []corev1.ServicePort{httpUnnamedPort}, \"10.0.2.1\")\n\t// start with 250 workloads\n\tews := []*ewv1beta1.ExternalWorkload{}\n\tfor i := 0; i < 250; i++ {\n\t\tready := !(i%3 == 0)\n\t\tgenIp := fmt.Sprintf(\"192.%d.%d.%d\", i%5, i%3, i%2)\n\t\tew := makeExternalWorkload(\"1\", fmt.Sprintf(\"wlkd-%d\", i), map[string]string{\"app\": \"test\"}, map[int32]string{8080: \"\"}, []string{genIp})\n\n\t\tew.Status.Conditions = []ewv1beta1.WorkloadCondition{newStatusCondition(ready)}\n\t\tews = append(ews, ew)\n\t}\n\n\tk8sAPI, actions := newClientset(t, []string{})\n\tr := newEndpointsReconciler(k8sAPI, testControllerName, defaultTestEndpointsQuota)\n\tr.reconcile(svc, ews, []*discoveryv1.EndpointSlice{})\n\n\tslices := fetchEndpointSlices(t, k8sAPI, svc)\n\texpectSlicesWithLengths(t, []int{100, 100, 50}, slices)\n\tfor _, ew := range ews {\n\t\tew.Spec.Ports[0].Port = int32(81)\n\t}\n\tsvc.Spec.Ports[0].TargetPort.IntVal = 81\n\n\tr.reconcile(svc, ews, []*discoveryv1.EndpointSlice{&slices[0], &slices[1], &slices[2]})\n\texpectActions(t, actions(), 3, \"update\", \"endpointslices\")\n\tslices = fetchEndpointSlices(t, k8sAPI, svc)\n\texpectSlicesWithLengths(t, []int{100, 100, 50}, slices)\n\tfor _, slice := range slices {\n\t\tif *slice.Ports[0].Port != 81 {\n\t\t\tt.Errorf(\"expected targetPort value to be 81, got: %d\", slice.Ports[0].Port)\n\t\t}\n\t}\n}\n\n// When service labels update, all slices will require a change.\n//\n// This test will ensure that we update slices with the appropriate labels when\n// a service has changed.\nfunc TestReconcileEndpointSlicesLabelsUpdatingSvc(t *testing.T) {\n\tsvc := makeService(\"test-svc\", []corev1.IPFamily{corev1.IPv4Protocol}, map[string]string{\"app\": \"test\"}, []corev1.ServicePort{httpUnnamedPort}, \"10.0.2.1\")\n\t// start with 250 workloads\n\tews := []*ewv1beta1.ExternalWorkload{}\n\tfor i := 0; i < 250; i++ {\n\t\tready := !(i%3 == 0)\n\t\tgenIp := fmt.Sprintf(\"192.%d.%d.%d\", i%5, i%3, i%2)\n\t\tew := makeExternalWorkload(\"1\", fmt.Sprintf(\"wlkd-%d\", i), map[string]string{\"app\": \"test\"}, map[int32]string{8080: \"\"}, []string{genIp})\n\n\t\tew.Status.Conditions = []ewv1beta1.WorkloadCondition{newStatusCondition(ready)}\n\t\tews = append(ews, ew)\n\t}\n\n\tk8sAPI, actions := newClientset(t, []string{})\n\tr := newEndpointsReconciler(k8sAPI, testControllerName, defaultTestEndpointsQuota)\n\tr.reconcile(svc, ews, []*discoveryv1.EndpointSlice{})\n\n\tslices := fetchEndpointSlices(t, k8sAPI, svc)\n\texpectSlicesWithLengths(t, []int{100, 100, 50}, slices)\n\n\t// update service with new labels\n\tsvc.Labels = map[string]string{\"foo\": \"bar\"}\n\tr.reconcile(svc, ews, []*discoveryv1.EndpointSlice{&slices[0], &slices[1], &slices[2]})\n\texpectActions(t, actions(), 3, \"update\", \"endpointslices\")\n\n\tslices = fetchEndpointSlices(t, k8sAPI, svc)\n\texpectSlicesWithLengths(t, []int{100, 100, 50}, slices)\n\t// check that the labels were updated\n\tfor _, slice := range slices {\n\t\tw, ok := slice.Labels[\"foo\"]\n\t\tif !ok {\n\t\t\tt.Errorf(\"expected label \\\"foo\\\" from parent service not found\")\n\t\t} else if \"bar\" != w {\n\t\t\tt.Errorf(\"expected EndpointSlice to have parent service labels: have %s value, expected bar\", w)\n\t\t}\n\t}\n}\n\n// In some cases, such as service labels updates, all slices for that service will require a change\n// However, this should not happen for reserved labels\nfunc TestReconcileEndpointSlicesReservedLabelsSvc(t *testing.T) {\n\tsvc := makeService(\"test-svc\", []corev1.IPFamily{corev1.IPv4Protocol}, map[string]string{\"app\": \"test\"}, []corev1.ServicePort{httpUnnamedPort}, \"10.0.2.1\")\n\t// start with 250 workloads\n\tews := []*ewv1beta1.ExternalWorkload{}\n\tfor i := 0; i < 250; i++ {\n\t\tready := !(i%3 == 0)\n\t\tgenIp := fmt.Sprintf(\"192.%d.%d.%d\", i%5, i%3, i%2)\n\t\tew := makeExternalWorkload(\"1\", fmt.Sprintf(\"wlkd-%d\", i), map[string]string{\"app\": \"test\"}, map[int32]string{8080: \"\"}, []string{genIp})\n\n\t\tew.Status.Conditions = []ewv1beta1.WorkloadCondition{newStatusCondition(ready)}\n\t\tews = append(ews, ew)\n\t}\n\n\tk8sAPI, actions := newClientset(t, []string{})\n\tr := newEndpointsReconciler(k8sAPI, testControllerName, defaultTestEndpointsQuota)\n\tr.reconcile(svc, ews, []*discoveryv1.EndpointSlice{})\n\tnumActionExpected := 3\n\n\tslices := fetchEndpointSlices(t, k8sAPI, svc)\n\texpectSlicesWithLengths(t, []int{100, 100, 50}, slices)\n\tnumActionExpected++\n\n\t// update service with new labels\n\tsvc.Labels = map[string]string{discoveryv1.LabelServiceName: \"bad\", discoveryv1.LabelManagedBy: \"actor\", corev1.IsHeadlessService: \"invalid\"}\n\tr.reconcile(svc, ews, []*discoveryv1.EndpointSlice{&slices[0], &slices[1], &slices[2]})\n\tslices = fetchEndpointSlices(t, k8sAPI, svc)\n\tnumActionExpected++\n\tif len(actions()) != numActionExpected {\n\t\tt.Errorf(\"expected %d actions, got %d instead\", numActionExpected, len(actions()))\n\t}\n\n\texpectSlicesWithLengths(t, []int{100, 100, 50}, slices)\n\t// check that the labels were updated\n\tfor _, slice := range slices {\n\t\tif v := slice.Labels[discoveryv1.LabelServiceName]; v == \"bad\" {\n\t\t\tt.Errorf(\"unexpected label value \\\"%s\\\" from parent service found on slice\", \"bad\")\n\t\t}\n\n\t\tif v := slice.Labels[discoveryv1.LabelManagedBy]; v == \"actor\" {\n\t\t\tt.Errorf(\"unexpected label value \\\"%s\\\" from parent service found on slice\", \"actor\")\n\t\t}\n\n\t\tif v := slice.Labels[corev1.IsHeadlessService]; v == \"invalid\" {\n\t\t\tt.Errorf(\"unexpected label value \\\"%s\\\" from parent service found on slice\", \"invalid\")\n\t\t}\n\t}\n}\n\nfunc TestEndpointSlicesAreRecycled(t *testing.T) {\n\tsvc := makeService(\"test-svc\", []corev1.IPFamily{corev1.IPv4Protocol}, map[string]string{\"app\": \"test\"}, []corev1.ServicePort{httpUnnamedPort}, \"10.0.2.1\")\n\t// start with 250 workloads\n\tews := []*ewv1beta1.ExternalWorkload{}\n\tfor i := 0; i < 300; i++ {\n\t\tready := !(i%3 == 0)\n\t\tgenIp := fmt.Sprintf(\"192.%d.%d.%d\", i%5, i%3, i%2)\n\t\tew := makeExternalWorkload(\"1\", fmt.Sprintf(\"wlkd-%d\", i), map[string]string{\"app\": \"test\"}, map[int32]string{8080: \"\"}, []string{genIp})\n\n\t\tew.Status.Conditions = []ewv1beta1.WorkloadCondition{newStatusCondition(ready)}\n\t\tews = append(ews, ew)\n\t}\n\n\t// Create an endpointslice\n\tport := int32(8080)\n\tesPorts := []discoveryv1.EndpointPort{{\n\t\tPort: &port,\n\t}}\n\n\t// generate 10 existing slices with 30 endpoints each\n\texistingSlices := []*discoveryv1.EndpointSlice{}\n\tfor i, ew := range ews {\n\t\tsliceNum := i / 30\n\t\tif i%30 == 0 {\n\t\t\texistingSlices = append(existingSlices, makeEndpointSlice(svc, discoveryv1.AddressTypeIPv4, esPorts))\n\t\t}\n\n\t\taddrs := []string{ews[i].Spec.WorkloadIPs[0].Ip}\n\t\tisReady := IsEwReady(ews[i])\n\t\texistingSlices[sliceNum].Endpoints = append(existingSlices[sliceNum].Endpoints, makeEndpoint(addrs, isReady, ew))\n\t}\n\n\tcmc := newCacheMutationCheck(existingSlices)\n\tk8sAPI, err := k8s.NewFakeAPI([]string{}...)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when creating Kubernetes clientset: %v\", err)\n\t}\n\n\tfor _, slice := range existingSlices {\n\t\t_, err := k8sAPI.Client.DiscoveryV1().EndpointSlices(svc.Namespace).Create(context.TODO(), slice, metav1.CreateOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error when creating Kubernetes obj: %v\", err)\n\t\t}\n\t}\n\n\tfor _, ew := range ews {\n\t\tew.Spec.Ports[0].Port = int32(81)\n\t}\n\n\t// changing a service port should require all slices to be updated, time for a repack\n\tsvc.Spec.Ports[0].TargetPort.IntVal = 81\n\tr := newEndpointsReconciler(k8sAPI, testControllerName, defaultTestEndpointsQuota)\n\tr.reconcile(svc, ews, existingSlices)\n\n\tslices := fetchEndpointSlices(t, k8sAPI, svc)\n\texpectSlicesWithLengths(t, []int{100, 100, 100}, slices)\n\t// ensure cache mutation has not occurred\n\tcmc.Check(t)\n}\n\nfunc newClientset(t *testing.T, k8sConfigs []string) (*k8s.API, func() []k8stesting.Action) {\n\tk8sAPI, actions, err := k8s.NewFakeAPIWithActions(k8sConfigs...)\n\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error %v\", err)\n\t}\n\n\treturn k8sAPI, actions\n}\n\nfunc makeDualEndpointSlices(svc *corev1.Service, ports []discoveryv1.EndpointPort) (*discoveryv1.EndpointSlice, *discoveryv1.EndpointSlice) {\n\tesIPv4 := makeEndpointSlice(svc, discoveryv1.AddressTypeIPv4, ports)\n\tesIPv6 := makeEndpointSlice(svc, discoveryv1.AddressTypeIPv6, ports)\n\treturn esIPv4, esIPv6\n}\n\nfunc makeEndpointSlice(svc *corev1.Service, addrType discoveryv1.AddressType, ports []discoveryv1.EndpointPort) *discoveryv1.EndpointSlice {\n\t// We need an ownerRef to point to our service\n\townerRef := metav1.NewControllerRef(svc, schema.GroupVersionKind{Version: \"v1\", Kind: \"Service\"})\n\tslice := &discoveryv1.EndpointSlice{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:            fmt.Sprintf(\"linkerd-external-%s-%s\", svc.Name, rand.String(8)),\n\t\t\tNamespace:       svc.Namespace,\n\t\t\tLabels:          map[string]string{},\n\t\t\tOwnerReferences: []metav1.OwnerReference{*ownerRef},\n\t\t},\n\t\tAddressType: addrType,\n\t\tEndpoints:   []discoveryv1.Endpoint{},\n\t\tPorts:       ports,\n\t}\n\tlabels, _ := setEndpointSliceLabels(slice, svc, testControllerName)\n\tslice.Labels = labels\n\treturn slice\n}\n\n// Helper function that tests a set of slices matches a list of expected lengths\n// for number of endpoints\nfunc expectSlicesWithLengths(t *testing.T, expectedLengths []int, es []discoveryv1.EndpointSlice) {\n\tt.Helper()\n\tnoMatch := []string{}\n\tfor _, slice := range es {\n\t\tepLen := len(slice.Endpoints)\n\t\tmatched := false\n\t\tfor i := 0; i < len(expectedLengths); i++ {\n\t\t\tif epLen == expectedLengths[i] {\n\t\t\t\tmatched = true\n\t\t\t\texpectedLengths = append(expectedLengths[:i], expectedLengths[i+1:]...)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif !matched {\n\t\t\tnoMatch = append(noMatch, fmt.Sprintf(\"%s/%s (%d)\", slice.Namespace, slice.Name, len(slice.Endpoints)))\n\t\t}\n\t}\n\n\tif len(noMatch) > 0 {\n\t\tt.Fatalf(\"slices %s did not match the required lengths, unmatched lengths: %v\", strings.Join(noMatch, \", \"), expectedLengths)\n\t}\n}\n\nfunc diffEndpointSlicePorts(t *testing.T, expected, actual []discoveryv1.EndpointSlice) {\n\tt.Helper()\n\tif len(expected) != len(actual) {\n\t\tt.Fatalf(\"expected %d slices, got %d instead\", len(expected), len(actual))\n\t}\n\n\tunmatched := []discoveryv1.EndpointSlice{}\n\tfor _, actualSlice := range actual {\n\t\tmatched := false\n\t\tfor i := 0; i < len(expected); i++ {\n\t\t\texpectedSlice := expected[i]\n\t\t\texpectedHash := epsliceutil.NewPortMapKey(expectedSlice.Ports)\n\t\t\tactualHash := epsliceutil.NewPortMapKey(actualSlice.Ports)\n\n\t\t\tif (actualSlice.AddressType == expectedSlice.AddressType) &&\n\t\t\t\t(actualHash == expectedHash) {\n\t\t\t\tmatched = true\n\t\t\t\texpected = append(expected[:i], expected[i+1:]...)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif !matched {\n\t\t\tunmatched = append(unmatched, actualSlice)\n\t\t}\n\t}\n\n\tif len(expected) != 0 {\n\t\tt.Errorf(\"expected slices not found in actual list of EndpointSlices\")\n\t}\n\n\tif len(unmatched) > 0 {\n\t\tt.Errorf(\"found %d slices that do not match expected ports\", len(unmatched))\n\t}\n}\n\n// === Test utilities ===\n\n// Modify a slice's name in-place since the fake API server does not support\n// generated names\nfunc endpointSliceAsYaml(t *testing.T, es *discoveryv1.EndpointSlice) string {\n\tif es.Name == \"\" {\n\t\tes.Name = fmt.Sprintf(\"%s-%s\", es.ObjectMeta.GenerateName, rand.String(5))\n\t\tes.GenerateName = \"\"\n\t}\n\tes.TypeMeta = metav1.TypeMeta{\n\t\tAPIVersion: \"discovery.k8s.io/v1\",\n\t\tKind:       \"EndpointSlice\",\n\t}\n\n\tb, err := yaml.Marshal(es)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when serializing endpointslices to yaml\")\n\t}\n\n\treturn string(b)\n\n}\n\nfunc makeService(\n\tname string,\n\tipFamilies []corev1.IPFamily,\n\tselector map[string]string,\n\tports []corev1.ServicePort,\n\tclusterIP string) *corev1.Service {\n\treturn &corev1.Service{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: \"default\",\n\t\t\tUID:       types.UID(name),\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tPorts:      ports,\n\t\t\tSelector:   selector,\n\t\t\tClusterIP:  clusterIP,\n\t\t\tIPFamilies: ipFamilies,\n\t\t},\n\t\tStatus: corev1.ServiceStatus{},\n\t}\n}\n\nfunc makeEndpoint(addrs []string, isReady bool, ew *ewv1beta1.ExternalWorkload) discoveryv1.Endpoint {\n\trdy := &isReady\n\tterm := !isReady\n\tep := discoveryv1.Endpoint{\n\t\tAddresses: addrs,\n\t\tConditions: discoveryv1.EndpointConditions{\n\t\t\tReady:       rdy,\n\t\t\tServing:     rdy,\n\t\t\tTerminating: &term,\n\t\t},\n\t\tTargetRef: &corev1.ObjectReference{\n\t\t\tKind:      ew.Kind,\n\t\t\tNamespace: ew.Namespace,\n\t\t\tName:      ew.Name,\n\t\t\tUID:       ew.UID,\n\t\t},\n\t}\n\treturn ep\n}\n\nfunc fetchEndpointSlices(t *testing.T, k8sAPI *k8s.API, svc *corev1.Service) []discoveryv1.EndpointSlice {\n\tt.Helper()\n\tselector := labels.Set(map[string]string{\n\t\tdiscoveryv1.LabelServiceName: svc.Name,\n\t\tdiscoveryv1.LabelManagedBy:   testControllerName,\n\t}).AsSelectorPreValidated()\n\tfetchedSlices, err := k8sAPI.Client.DiscoveryV1().EndpointSlices(svc.Namespace).List(context.Background(), metav1.ListOptions{\n\t\tLabelSelector: selector.String(),\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error when fetching endpointslices: %v\", err)\n\t}\n\n\treturn fetchedSlices.Items\n}\n\nfunc diffEndpoints(t *testing.T, actual, expected discoveryv1.Endpoint) {\n\tt.Helper()\n\tif len(actual.Addresses) != len(expected.Addresses) {\n\t\tt.Errorf(\"expected %d addresses, got %d instead\", len(expected.Addresses), len(actual.Addresses))\n\t}\n\n\tif actual.Conditions.Ready != nil && expected.Conditions.Ready != nil {\n\t\tif *actual.Conditions.Ready != *expected.Conditions.Ready {\n\t\t\tt.Errorf(\"expected \\\"ready\\\" condition to be %t, got %t instead\", *expected.Conditions.Ready, *actual.Conditions.Ready)\n\t\t}\n\t}\n\n\tif actual.Conditions.Serving != nil && expected.Conditions.Serving != nil {\n\t\tif *actual.Conditions.Serving != *expected.Conditions.Serving {\n\t\t\tt.Errorf(\"expected \\\"serving\\\" condition to be %t, got %t instead\", *expected.Conditions.Serving, *actual.Conditions.Serving)\n\t\t}\n\t}\n\n\tif actual.Conditions.Terminating != nil && expected.Conditions.Terminating != nil {\n\t\tif *actual.Conditions.Terminating != *expected.Conditions.Terminating {\n\t\t\tt.Errorf(\"expected \\\"terminating\\\" condition to be %t, got %t instead\", *expected.Conditions.Terminating, *actual.Conditions.Terminating)\n\t\t}\n\t}\n\n\tif actual.Zone != nil && expected.Zone != nil {\n\t\tif *actual.Zone != *expected.Zone {\n\t\t\tt.Errorf(\"expected \\\"zone=%s\\\", got \\\"zone=%s\\\" instead\", *expected.Zone, *actual.Zone)\n\t\t}\n\t}\n\n\tactualAddrs := toSet(actual.Addresses)\n\texpAddrs := toSet(expected.Addresses)\n\tfor actualAddr := range actualAddrs {\n\t\tif _, found := expAddrs[actualAddr]; !found {\n\t\t\tt.Errorf(\"found unexpected address %s in the actual endpoint\", actualAddr)\n\t\t}\n\t}\n\n\tfor expAddr := range expAddrs {\n\t\tif _, found := actualAddrs[expAddr]; !found {\n\t\t\tt.Errorf(\"expected to find address %s in the actual endpoint\", expAddr)\n\t\t}\n\t}\n\n\texpRef := expected.TargetRef\n\tactRef := actual.TargetRef\n\tif expRef.UID != actRef.UID {\n\t\tt.Errorf(\"expected targetRef with UID %s; got %s instead\", expRef.UID, actRef.UID)\n\t}\n\n\tif expRef.Name != actRef.Name {\n\t\tt.Errorf(\"expected targetRef with name %s; got %s instead\", expRef.Name, actRef.Name)\n\t}\n\n}\n\n// === impl cache mutation check\n\n// Code originally forked from:\n//\n// https://github.com/kubernetes/endpointslice/commit/a09c1c9580d13f5020248d25c7fd11f5dde6dd9b\n\n// cacheMutationCheck helps ensure that cached objects have not been changed\n// in any way throughout a test run.\ntype cacheMutationCheck struct {\n\tobjects []cacheObject\n}\n\n// cacheObject stores a reference to an original object as well as a deep copy\n// of that object to track any mutations in the original object.\ntype cacheObject struct {\n\toriginal runtime.Object\n\tdeepCopy runtime.Object\n}\n\n// newCacheMutationCheck initializes a cacheMutationCheck with EndpointSlices.\nfunc newCacheMutationCheck(endpointSlices []*discoveryv1.EndpointSlice) cacheMutationCheck {\n\tcmc := cacheMutationCheck{}\n\tfor _, endpointSlice := range endpointSlices {\n\t\tcmc.Add(endpointSlice)\n\t}\n\treturn cmc\n}\n\n// Add appends a runtime.Object and a deep copy of that object into the\n// cacheMutationCheck.\nfunc (cmc *cacheMutationCheck) Add(o runtime.Object) {\n\tcmc.objects = append(cmc.objects, cacheObject{\n\t\toriginal: o,\n\t\tdeepCopy: o.DeepCopyObject(),\n\t})\n}\n\n// Check verifies that no objects in the cacheMutationCheck have been mutated.\nfunc (cmc *cacheMutationCheck) Check(t *testing.T) {\n\tfor _, o := range cmc.objects {\n\t\tif !reflect.DeepEqual(o.original, o.deepCopy) {\n\t\t\t// Cached objects can't be safely mutated and instead should be deep\n\t\t\t// copied before changed in any way.\n\t\t\tt.Errorf(\"Cached object was unexpectedly mutated. Original: %+v, Mutated: %+v\", o.deepCopy, o.original)\n\t\t}\n\t}\n}\n\nfunc toSet(s []string) map[string]struct{} {\n\tset := map[string]struct{}{}\n\tfor _, k := range s {\n\t\tset[k] = struct{}{}\n\t}\n\treturn set\n}\n"
  },
  {
    "path": "controller/api/destination/external-workload/queue_metrics.go",
    "content": "package externalworkload\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\t\"k8s.io/client-go/util/workqueue\"\n)\n\n// Code is functionally the same as usptream metrics provider. The difference is that\n// we rely on promauto instead of init() method for metrics registering as the logic\n// used to register the metrics in the upstream implementation uses k8s.io/component-base/metrics/legacyregistry\n// The latter does not work with our metrics registry and hence these metrics do not\n// end up exposed via our admin's server /metrics endpoint.\n//\n// https://github.com/kubernetes/component-base/blob/68f947b04ec3a353e63bbef2c1f935bb9ce0061d/metrics/prometheus/workqueue/metrics.go\n\nconst (\n\tWorkQueueSubsystem         = \"workqueue\"\n\tDepthKey                   = \"depth\"\n\tAddsKey                    = \"adds_total\"\n\tQueueLatencyKey            = \"queue_duration_seconds\"\n\tWorkDurationKey            = \"work_duration_seconds\"\n\tUnfinishedWorkKey          = \"unfinished_work_seconds\"\n\tLongestRunningProcessorKey = \"longest_running_processor_seconds\"\n\tRetriesKey                 = \"retries_total\"\n\tDropsTotalKey              = \"drops_total\"\n)\n\ntype queueMetricsProvider struct {\n\tdepth                   *prometheus.GaugeVec\n\tadds                    *prometheus.CounterVec\n\tlatency                 *prometheus.HistogramVec\n\tworkDuration            *prometheus.HistogramVec\n\tunfinished              *prometheus.GaugeVec\n\tlongestRunningProcessor *prometheus.GaugeVec\n\tretries                 *prometheus.CounterVec\n\tdrops                   *prometheus.CounterVec\n}\n\nfunc newWorkQueueMetricsProvider() *queueMetricsProvider {\n\treturn &queueMetricsProvider{\n\t\tdepth: promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\t\tSubsystem: WorkQueueSubsystem,\n\t\t\tName:      DepthKey,\n\t\t\tHelp:      \"Current depth of workqueue\",\n\t\t}, []string{\"name\"}),\n\n\t\tadds: promauto.NewCounterVec(prometheus.CounterOpts{\n\t\t\tSubsystem: WorkQueueSubsystem,\n\t\t\tName:      AddsKey,\n\t\t\tHelp:      \"Total number of adds handled by workqueue\",\n\t\t}, []string{\"name\"}),\n\n\t\tlatency: promauto.NewHistogramVec(prometheus.HistogramOpts{\n\t\t\tSubsystem: WorkQueueSubsystem,\n\t\t\tName:      QueueLatencyKey,\n\t\t\tHelp:      \"How long in seconds an item stays in workqueue before being requested.\",\n\t\t\tBuckets:   prometheus.ExponentialBuckets(10e-9, 10, 10),\n\t\t}, []string{\"name\"}),\n\n\t\tworkDuration: promauto.NewHistogramVec(prometheus.HistogramOpts{\n\t\t\tSubsystem: WorkQueueSubsystem,\n\t\t\tName:      WorkDurationKey,\n\t\t\tHelp:      \"How long in seconds processing an item from workqueue takes.\",\n\t\t\tBuckets:   prometheus.ExponentialBuckets(10e-9, 10, 10),\n\t\t}, []string{\"name\"}),\n\n\t\tunfinished: promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\t\tSubsystem: WorkQueueSubsystem,\n\t\t\tName:      UnfinishedWorkKey,\n\t\t\tHelp: \"How many seconds of work has done that \" +\n\t\t\t\t\"is in progress and hasn't been observed by work_duration. Large \" +\n\t\t\t\t\"values indicate stuck threads. One can deduce the number of stuck \" +\n\t\t\t\t\"threads by observing the rate at which this increases.\",\n\t\t}, []string{\"name\"}),\n\n\t\tlongestRunningProcessor: promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\t\tSubsystem: WorkQueueSubsystem,\n\t\t\tName:      LongestRunningProcessorKey,\n\t\t\tHelp: \"How many seconds has the longest running \" +\n\t\t\t\t\"processor for workqueue been running.\",\n\t\t}, []string{\"name\"}),\n\n\t\tretries: promauto.NewCounterVec(prometheus.CounterOpts{\n\t\t\tSubsystem: WorkQueueSubsystem,\n\t\t\tName:      RetriesKey,\n\t\t\tHelp:      \"Total number of retries handled by workqueue\",\n\t\t}, []string{\"name\"}),\n\t\tdrops: promauto.NewCounterVec(prometheus.CounterOpts{\n\t\t\tSubsystem: WorkQueueSubsystem,\n\t\t\tName:      DropsTotalKey,\n\t\t\tHelp:      \"Total number of dropped items from the queue due to exceeding retry threshold\",\n\t\t}, []string{\"name\"}),\n\t}\n}\n\nfunc (p queueMetricsProvider) NewDepthMetric(name string) workqueue.GaugeMetric {\n\treturn p.depth.WithLabelValues(name)\n}\n\nfunc (p queueMetricsProvider) NewAddsMetric(name string) workqueue.CounterMetric {\n\treturn p.adds.WithLabelValues(name)\n}\n\nfunc (p queueMetricsProvider) NewLatencyMetric(name string) workqueue.HistogramMetric {\n\treturn p.latency.WithLabelValues(name)\n}\n\nfunc (p queueMetricsProvider) NewWorkDurationMetric(name string) workqueue.HistogramMetric {\n\treturn p.workDuration.WithLabelValues(name)\n}\n\nfunc (p queueMetricsProvider) NewUnfinishedWorkSecondsMetric(name string) workqueue.SettableGaugeMetric {\n\treturn p.unfinished.WithLabelValues(name)\n}\n\nfunc (p queueMetricsProvider) NewLongestRunningProcessorSecondsMetric(name string) workqueue.SettableGaugeMetric {\n\treturn p.longestRunningProcessor.WithLabelValues(name)\n}\n\nfunc (p queueMetricsProvider) NewRetriesMetric(name string) workqueue.CounterMetric {\n\treturn p.retries.WithLabelValues(name)\n}\n\nfunc (p queueMetricsProvider) NewDropsMetric(name string) workqueue.CounterMetric {\n\treturn p.drops.WithLabelValues(name)\n}\n\ntype noopCounterMetric struct{}\n\nfunc (noopCounterMetric) Inc() {}\n"
  },
  {
    "path": "controller/api/destination/fallback_profile_listener.go",
    "content": "package destination\n\nimport (\n\t\"sync\"\n\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tlogging \"github.com/sirupsen/logrus\"\n)\n\ntype fallbackProfileListener struct {\n\tprimary, backup *childListener\n\tparent          watcher.ProfileUpdateListener\n\tlog             *logging.Entry\n\tmutex           sync.Mutex\n}\n\ntype childListener struct {\n\t// state is only referenced from the outer struct primaryProfileListener\n\t// or backupProfileListener (e.g. listener.state where listener's type is\n\t// _not_ this struct). structcheck issues a false positive for this field\n\t// as it does not think it's used.\n\t//nolint:structcheck\n\tstate       *sp.ServiceProfile\n\tinitialized bool\n\tparent      *fallbackProfileListener\n}\n\n// newFallbackProfileListener takes a parent ProfileUpdateListener and returns\n// two ProfileUpdateListeners: a primary and a backup.\n//\n// If the primary listener is updated with a non-nil value, it is published to\n// the parent listener.\n//\n// Otherwise, if the backup listener has most recently been updated with a\n// non-nil value, its valeu is published to the parent listener.\n//\n// A nil ServiceProfile is published only when both the primary and backup have\n// been initialized and have nil values.\nfunc newFallbackProfileListener(\n\tparent watcher.ProfileUpdateListener,\n\tlog *logging.Entry,\n) (watcher.ProfileUpdateListener, watcher.ProfileUpdateListener) {\n\t// Primary and backup share a lock to ensure updates are atomic.\n\tfallback := fallbackProfileListener{\n\t\tmutex: sync.Mutex{},\n\t\tlog:   log,\n\t}\n\n\tprimary := childListener{\n\t\tinitialized: false,\n\t\tparent:      &fallback,\n\t}\n\n\tbackup := childListener{\n\t\tinitialized: false,\n\t\tparent:      &fallback,\n\t}\n\n\tfallback.parent = parent\n\tfallback.primary = &primary\n\tfallback.backup = &backup\n\n\treturn &primary, &backup\n}\n\nfunc (f *fallbackProfileListener) publish() {\n\tif !f.primary.initialized {\n\t\tf.log.Debug(\"Waiting for primary profile listener to be initialized\")\n\t\treturn\n\t}\n\tif !f.backup.initialized {\n\t\tf.log.Debug(\"Waiting for backup profile listener to be initialized\")\n\t\treturn\n\t}\n\n\tif f.primary.state == nil && f.backup.state != nil {\n\t\tf.log.Debug(\"Publishing backup profile\")\n\t\tf.parent.Update(f.backup.state)\n\t\treturn\n\t}\n\n\tf.log.Debug(\"Publishing primary profile\")\n\tf.parent.Update(f.primary.state)\n}\n\nfunc (p *childListener) Update(profile *sp.ServiceProfile) {\n\tp.parent.mutex.Lock()\n\tdefer p.parent.mutex.Unlock()\n\n\tp.state = profile\n\tp.initialized = true\n\tp.parent.publish()\n}\n"
  },
  {
    "path": "controller/api/destination/fallback_profile_listener_test.go",
    "content": "package destination\n\nimport (\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\ntype mockListener struct {\n\treceived []*sp.ServiceProfile\n}\n\nfunc (m *mockListener) Update(profile *sp.ServiceProfile) {\n\tm.received = append(m.received, profile)\n}\n\nfunc (m *mockListener) ClientClose() <-chan struct{} {\n\treturn make(<-chan struct{})\n}\n\nfunc (m *mockListener) ServerClose() <-chan struct{} {\n\treturn make(<-chan struct{})\n}\n\nfunc (m *mockListener) Stop() {}\n\nfunc TestFallbackProfileListener(t *testing.T) {\n\n\tprimaryProfile := sp.ServiceProfile{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"primary\",\n\t\t},\n\t}\n\n\tbackupProfile := sp.ServiceProfile{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"backup\",\n\t\t},\n\t}\n\n\tt.Run(\"Primary updated\", func(t *testing.T) {\n\t\tprimary, backup, listener := newListeners()\n\t\tprimary.Update(&primaryProfile)\n\t\tassertEq(t, listener.received, []*sp.ServiceProfile{})\n\t\tbackup.Update(nil)\n\t\tassertEq(t, listener.received, []*sp.ServiceProfile{&primaryProfile})\n\t})\n\n\tt.Run(\"Backup updated\", func(t *testing.T) {\n\t\tprimary, backup, listener := newListeners()\n\t\tbackup.Update(&backupProfile)\n\t\tprimary.Update(nil)\n\t\tassertEq(t, listener.received, []*sp.ServiceProfile{&backupProfile})\n\t})\n\n\tt.Run(\"Primary cleared\", func(t *testing.T) {\n\t\tprimary, backup, listener := newListeners()\n\t\tbackup.Update(nil)\n\t\tprimary.Update(&primaryProfile)\n\t\tprimary.Update(nil)\n\t\tassertEq(t, listener.received, []*sp.ServiceProfile{&primaryProfile, nil})\n\t})\n\n\tt.Run(\"Backup cleared\", func(t *testing.T) {\n\t\tprimary, backup, listener := newListeners()\n\t\tbackup.Update(&backupProfile)\n\t\tprimary.Update(nil)\n\t\tbackup.Update(nil)\n\t\tassertEq(t, listener.received, []*sp.ServiceProfile{&backupProfile, nil})\n\t})\n\n\tt.Run(\"Primary overrides backup\", func(t *testing.T) {\n\t\tprimary, backup, listener := newListeners()\n\t\tbackup.Update(&backupProfile)\n\t\tprimary.Update(&primaryProfile)\n\t\tassertEq(t, listener.received, []*sp.ServiceProfile{&primaryProfile})\n\t})\n\n\tt.Run(\"Backup update ignored\", func(t *testing.T) {\n\t\tprimary, backup, listener := newListeners()\n\t\tprimary.Update(&primaryProfile)\n\t\tbackup.Update(&backupProfile)\n\t\tbackup.Update(nil)\n\t\tassertEq(t, listener.received, []*sp.ServiceProfile{&primaryProfile, &primaryProfile})\n\t})\n\n\tt.Run(\"Fallback to backup\", func(t *testing.T) {\n\t\tprimary, backup, listener := newListeners()\n\t\tprimary.Update(&primaryProfile)\n\t\tbackup.Update(&backupProfile)\n\t\tprimary.Update(nil)\n\t\tassertEq(t, listener.received, []*sp.ServiceProfile{&primaryProfile, &backupProfile})\n\t})\n\n}\n\nfunc newListeners() (watcher.ProfileUpdateListener, watcher.ProfileUpdateListener, *mockListener) {\n\tlistener := &mockListener{\n\t\treceived: []*sp.ServiceProfile{},\n\t}\n\n\tprimary, backup := newFallbackProfileListener(listener, logging.NewEntry(logging.New()))\n\treturn primary, backup, listener\n}\n\nfunc assertEq(t *testing.T, received []*sp.ServiceProfile, expected []*sp.ServiceProfile) {\n\tt.Helper()\n\tif len(received) != len(expected) {\n\t\tt.Fatalf(\"Expected %d profile updates, got %d\", len(expected), len(received))\n\t}\n\tfor i, profile := range received {\n\t\tif profile != expected[i] {\n\t\t\tt.Fatalf(\"Expected profile update %v, got %v\", expected[i], profile)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "controller/api/destination/federated_service_watcher.go",
    "content": "package destination\n\nimport (\n\t\"fmt\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tlabels \"github.com/linkerd/linkerd2/pkg/k8s\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\n// FederatedServiceWatcher watches federated services for the local discovery\n// and remote discovery annotations and subscribes to the approprite local and\n// remote services.\ntype federatedServiceWatcher struct {\n\tservices       map[watcher.ServiceID]*federatedService\n\tk8sAPI         *k8s.API\n\tmetadataAPI    *k8s.MetadataAPI\n\tconfig         *Config\n\tclusterStore   *watcher.ClusterStore\n\tlocalEndpoints *watcher.EndpointsWatcher\n\n\tlog *logging.Entry\n\n\tsync.RWMutex\n}\n\ntype remoteDiscoveryID struct {\n\tcluster string\n\tservice watcher.ServiceID\n}\n\n// FederatedService represents a federated service and it may have a local\n// discovery target and remote discovery targets. This struct holds a list of\n// subsribers that are subscribed to the federated service.\ntype federatedService struct {\n\tnamespace string\n\n\tlocalDiscovery  string\n\tremoteDiscovery []remoteDiscoveryID\n\tsubscribers     []federatedServiceSubscriber\n\n\tmetadataAPI    *k8s.MetadataAPI\n\tconfig         *Config\n\tlocalEndpoints *watcher.EndpointsWatcher\n\tclusterStore   *watcher.ClusterStore\n\tlog            *logging.Entry\n\n\tsync.Mutex\n}\n\n// FederatedServiceSubscriber holds all the state for an individual subscriber\n// stream to a federated service.\ntype federatedServiceSubscriber struct {\n\tport       uint32\n\tnodeName   string\n\tinstanceID string\n\n\tlocalTranslators  map[string]*endpointTranslator\n\tremoteTranslators map[remoteDiscoveryID]*endpointTranslator\n\n\tstream    *synchronizedGetStream\n\tendStream chan struct{}\n}\n\nfunc newFederatedServiceWatcher(\n\tk8sAPI *k8s.API,\n\tmetadataAPI *k8s.MetadataAPI,\n\tconfig *Config,\n\tclusterStore *watcher.ClusterStore,\n\tlocalEndpoints *watcher.EndpointsWatcher,\n\tlog *logging.Entry,\n) (*federatedServiceWatcher, error) {\n\tfsw := &federatedServiceWatcher{\n\t\tservices:       make(map[watcher.ServiceID]*federatedService),\n\t\tk8sAPI:         k8sAPI,\n\t\tmetadataAPI:    metadataAPI,\n\t\tconfig:         config,\n\t\tclusterStore:   clusterStore,\n\t\tlocalEndpoints: localEndpoints,\n\t\tlog: log.WithFields(logging.Fields{\n\t\t\t\"component\": \"federated-service-watcher\",\n\t\t}),\n\t}\n\n\tvar err error\n\t_, err = k8sAPI.Svc().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc:    fsw.addService,\n\t\tDeleteFunc: fsw.deleteService,\n\t\tUpdateFunc: fsw.updateService,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn fsw, nil\n}\n\nfunc (fsw *federatedServiceWatcher) Subscribe(\n\tservice string,\n\tnamespace string,\n\tport uint32,\n\tnodeName string,\n\tinstanceID string,\n\tstream pb.Destination_GetServer,\n\tendStream chan struct{},\n) error {\n\tid := watcher.ServiceID{Namespace: namespace, Name: service}\n\tfsw.RLock()\n\tif federatedService, ok := fsw.services[id]; ok {\n\t\tfsw.RUnlock()\n\t\tfsw.log.Debugf(\"Subscribing to federated service %s/%s\", namespace, service)\n\t\tfederatedService.subscribe(port, nodeName, instanceID, stream, endStream)\n\t\treturn nil\n\t} else {\n\t\tfsw.RUnlock()\n\t}\n\treturn fmt.Errorf(\"service %s/%s is not a federated service\", namespace, service)\n}\n\nfunc (fsw *federatedServiceWatcher) Unsubscribe(\n\tservice string,\n\tnamespace string,\n\tstream pb.Destination_GetServer,\n) {\n\tid := watcher.ServiceID{Namespace: namespace, Name: service}\n\tfsw.RLock()\n\tif federatedService, ok := fsw.services[id]; ok {\n\t\tfsw.RUnlock()\n\t\tfsw.log.Debugf(\"Unsubscribing from federated service %s/%s\", namespace, service)\n\t\tfederatedService.unsubscribe(stream)\n\t} else {\n\t\tfsw.RUnlock()\n\t}\n}\n\nfunc (fsw *federatedServiceWatcher) addService(obj interface{}) {\n\tservice := obj.(*corev1.Service)\n\tid := watcher.ServiceID{\n\t\tNamespace: service.Namespace,\n\t\tName:      service.Name,\n\t}\n\n\tif isFederatedService(service) {\n\t\tfsw.Lock()\n\t\tif federatedService, ok := fsw.services[id]; ok {\n\t\t\tfsw.Unlock()\n\t\t\tfsw.log.Debugf(\"Updating federated service %s/%s\", service.Namespace, service.Name)\n\t\t\tfederatedService.update(service)\n\t\t} else {\n\t\t\tfsw.log.Debugf(\"Adding federated service %s/%s\", service.Namespace, service.Name)\n\t\t\tfederatedService = fsw.newFederatedService(service)\n\t\t\tfsw.services[id] = federatedService\n\t\t\tfsw.Unlock()\n\t\t\tfederatedService.update(service)\n\t\t}\n\t} else {\n\t\tfsw.Lock()\n\t\tif federatedService, ok := fsw.services[id]; ok {\n\t\t\tdelete(fsw.services, id)\n\t\t\tfsw.Unlock()\n\t\t\tfsw.log.Debugf(\"Service %s/%s is no longer a federated service\", service.Namespace, service.Name)\n\t\t\tfederatedService.delete()\n\t\t} else {\n\t\t\tfsw.Unlock()\n\t\t}\n\t}\n}\n\nfunc (fsw *federatedServiceWatcher) updateService(oldObj interface{}, newObj interface{}) {\n\tfsw.addService(newObj)\n}\n\nfunc (fsw *federatedServiceWatcher) deleteService(obj interface{}) {\n\tservice, ok := obj.(*corev1.Service)\n\tif !ok {\n\t\ttombstone, ok := obj.(cache.DeletedFinalStateUnknown)\n\t\tif !ok {\n\t\t\tfsw.log.Errorf(\"couldn't get object from DeletedFinalStateUnknown %#v\", obj)\n\t\t\treturn\n\t\t}\n\t\tservice, ok = tombstone.Obj.(*corev1.Service)\n\t\tif !ok {\n\t\t\tfsw.log.Errorf(\"DeletedFinalStateUnknown contained object that is not a Service %#v\", obj)\n\t\t\treturn\n\t\t}\n\t}\n\n\tid := watcher.ServiceID{\n\t\tNamespace: service.Namespace,\n\t\tName:      service.Name,\n\t}\n\tfsw.Lock()\n\tif federatedService, ok := fsw.services[id]; ok {\n\t\tdelete(fsw.services, id)\n\t\tfsw.Unlock()\n\t\tfederatedService.delete()\n\t} else {\n\t\tfsw.Unlock()\n\t}\n\n}\n\nfunc (fsw *federatedServiceWatcher) newFederatedService(service *corev1.Service) *federatedService {\n\treturn &federatedService{\n\t\tnamespace: service.Namespace,\n\n\t\tlocalDiscovery:  service.Annotations[labels.LocalDiscoveryAnnotation],\n\t\tremoteDiscovery: remoteDiscoveryIDs(service, fsw.log),\n\t\tsubscribers:     []federatedServiceSubscriber{},\n\n\t\tmetadataAPI:    fsw.metadataAPI,\n\t\tconfig:         fsw.config,\n\t\tlocalEndpoints: fsw.localEndpoints,\n\t\tclusterStore:   fsw.clusterStore,\n\t\tlog:            fsw.log.WithFields(logging.Fields{\"service\": service.Name, \"namespace\": service.Namespace}),\n\t}\n}\n\nfunc (fs *federatedService) update(service *corev1.Service) {\n\tfs.Lock()\n\tdefer fs.Unlock()\n\n\tnewRemoteDiscovery := remoteDiscoveryIDs(service, fs.log)\n\tfor _, id := range newRemoteDiscovery {\n\t\tif !slices.Contains(fs.remoteDiscovery, id) {\n\t\t\tfor i := range fs.subscribers {\n\t\t\t\tfs.remoteDiscoverySubscribe(&fs.subscribers[i], id)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, id := range fs.remoteDiscovery {\n\t\tif !slices.Contains(newRemoteDiscovery, id) {\n\t\t\tfor i := range fs.subscribers {\n\t\t\t\tfs.remoteDiscoveryUnsubscribe(&fs.subscribers[i], id)\n\t\t\t}\n\t\t}\n\t}\n\tfs.remoteDiscovery = newRemoteDiscovery\n\n\tnewLocalDiscovery := service.Annotations[labels.LocalDiscoveryAnnotation]\n\tif fs.localDiscovery != service.Annotations[labels.LocalDiscoveryAnnotation] {\n\t\tif newLocalDiscovery != \"\" {\n\t\t\tfor i := range fs.subscribers {\n\t\t\t\tif fs.localDiscovery != \"\" {\n\t\t\t\t\tfs.localDiscoveryUnsubscribe(&fs.subscribers[i], fs.localDiscovery)\n\t\t\t\t}\n\t\t\t\tfs.localDiscoverySubscribe(&fs.subscribers[i], newLocalDiscovery)\n\t\t\t}\n\t\t} else {\n\t\t\tfor i := range fs.subscribers {\n\t\t\t\tfs.localDiscoveryUnsubscribe(&fs.subscribers[i], fs.localDiscovery)\n\t\t\t}\n\t\t}\n\t}\n\tfs.localDiscovery = newLocalDiscovery\n}\n\nfunc (fs *federatedService) delete() {\n\tfs.Lock()\n\tdefer fs.Unlock()\n\n\tfor _, subscriber := range fs.subscribers {\n\t\tfor id, translator := range subscriber.remoteTranslators {\n\t\t\tremoteWatcher, _, found := fs.clusterStore.Get(id.cluster)\n\t\t\tif !found {\n\t\t\t\tfs.log.Errorf(\"Failed to get remote cluster %s\", id.cluster)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tremoteWatcher.Unsubscribe(id.service, subscriber.port, subscriber.instanceID, translator)\n\t\t\ttranslator.Stop()\n\t\t}\n\t\tfor localDiscovery, translator := range subscriber.localTranslators {\n\t\t\tfs.localEndpoints.Unsubscribe(watcher.ServiceID{Namespace: fs.namespace, Name: localDiscovery}, subscriber.port, subscriber.instanceID, translator)\n\t\t\ttranslator.Stop()\n\t\t}\n\t\tclose(subscriber.endStream)\n\t}\n}\n\nfunc (fs *federatedService) subscribe(\n\tport uint32,\n\tnodeName string,\n\tinstanceID string,\n\tstream pb.Destination_GetServer,\n\tendStream chan struct{},\n) {\n\tfs.Lock()\n\tdefer fs.Unlock()\n\n\tsyncStream := newSyncronizedGetStream(stream, fs.log)\n\tsyncStream.Start()\n\n\tsubscriber := federatedServiceSubscriber{\n\t\tstream:            syncStream,\n\t\tendStream:         endStream,\n\t\tremoteTranslators: make(map[remoteDiscoveryID]*endpointTranslator, 0),\n\t\tlocalTranslators:  make(map[string]*endpointTranslator, 0),\n\t\tport:              port,\n\t\tnodeName:          nodeName,\n\t\tinstanceID:        instanceID,\n\t}\n\tfor _, id := range fs.remoteDiscovery {\n\t\tfs.remoteDiscoverySubscribe(&subscriber, id)\n\t}\n\tif fs.localDiscovery != \"\" {\n\t\tfs.localDiscoverySubscribe(&subscriber, fs.localDiscovery)\n\t}\n\n\tfs.subscribers = append(fs.subscribers, subscriber)\n}\n\nfunc (fs *federatedService) unsubscribe(\n\tstream pb.Destination_GetServer,\n) {\n\tfs.Lock()\n\tdefer fs.Unlock()\n\n\tsubscribers := make([]federatedServiceSubscriber, 0)\n\tfor i, subscriber := range fs.subscribers {\n\t\tif subscriber.stream.inner == stream {\n\t\t\tfor id := range subscriber.remoteTranslators {\n\t\t\t\tfs.remoteDiscoveryUnsubscribe(&fs.subscribers[i], id)\n\t\t\t}\n\t\t\tfor localDiscovery := range subscriber.localTranslators {\n\t\t\t\tfs.localDiscoveryUnsubscribe(&fs.subscribers[i], localDiscovery)\n\t\t\t}\n\t\t\tsubscriber.stream.Stop()\n\t\t} else {\n\t\t\tsubscribers = append(subscribers, subscriber)\n\t\t}\n\t}\n\tfs.subscribers = subscribers\n}\n\nfunc (fs *federatedService) remoteDiscoverySubscribe(\n\tsubscriber *federatedServiceSubscriber,\n\tid remoteDiscoveryID,\n) {\n\tremoteWatcher, remoteConfig, found := fs.clusterStore.Get(id.cluster)\n\tif !found {\n\t\tfs.log.Errorf(\"Failed to get remote cluster %s\", id.cluster)\n\t\treturn\n\t}\n\n\ttranslator, err := newEndpointTranslator(\n\t\tfs.config.ControllerNS,\n\t\tremoteConfig.TrustDomain,\n\t\tfs.config.ForceOpaqueTransport,\n\t\tfs.config.EnableH2Upgrade,\n\t\tfalse, // Disable endpoint filtering for remote discovery.\n\t\tfs.config.EnableIPv6,\n\t\tfs.config.ExtEndpointZoneWeights,\n\t\tfs.config.MeshedHttp2ClientParams,\n\t\tfmt.Sprintf(\"%s.%s.svc.%s:%d\", id.service, fs.namespace, remoteConfig.ClusterDomain, subscriber.port),\n\t\tsubscriber.nodeName,\n\t\tfs.config.DefaultOpaquePorts,\n\t\tfs.metadataAPI,\n\t\tsubscriber.stream,\n\t\tsubscriber.endStream,\n\t\tfs.log,\n\t\tfs.config.StreamQueueCapacity,\n\t)\n\tif err != nil {\n\t\tfs.log.Errorf(\"Failed to create endpoint translator for remote discovery service %q in cluster %s: %s\", id.service.Name, id.cluster, err)\n\t\treturn\n\t}\n\n\ttranslator.Start()\n\tsubscriber.remoteTranslators[id] = translator\n\n\tfs.log.Debugf(\"Subscribing to remote discovery service %s in cluster %s\", id.service, id.cluster)\n\terr = remoteWatcher.Subscribe(watcher.ServiceID{Namespace: id.service.Namespace, Name: id.service.Name}, subscriber.port, subscriber.instanceID, translator)\n\tif err != nil {\n\t\tfs.log.Errorf(\"Failed to subscribe to remote discovery service %q in cluster %s: %s\", id.service.Name, id.cluster, err)\n\t}\n}\n\nfunc (fs *federatedService) remoteDiscoveryUnsubscribe(\n\tsubscriber *federatedServiceSubscriber,\n\tid remoteDiscoveryID,\n) {\n\tremoteWatcher, _, found := fs.clusterStore.Get(id.cluster)\n\tif !found {\n\t\tfs.log.Errorf(\"Failed to get remote cluster %s\", id.cluster)\n\t\treturn\n\t}\n\n\ttranslator := subscriber.remoteTranslators[id]\n\tfs.log.Debugf(\"Unsubscribing from remote discovery service %s in cluster %s\", id.service, id.cluster)\n\tremoteWatcher.Unsubscribe(id.service, subscriber.port, subscriber.instanceID, translator)\n\ttranslator.NoEndpoints(true)\n\ttranslator.DrainAndStop()\n\tdelete(subscriber.remoteTranslators, id)\n}\n\nfunc (fs *federatedService) localDiscoverySubscribe(\n\tsubscriber *federatedServiceSubscriber,\n\tlocalDiscovery string,\n) {\n\ttranslator, err := newEndpointTranslator(\n\t\tfs.config.ControllerNS,\n\t\tfs.config.IdentityTrustDomain,\n\t\tfs.config.ForceOpaqueTransport,\n\t\tfs.config.EnableH2Upgrade,\n\t\ttrue,\n\t\tfs.config.EnableIPv6,\n\t\tfs.config.ExtEndpointZoneWeights,\n\t\tfs.config.MeshedHttp2ClientParams,\n\t\tlocalDiscovery,\n\t\tsubscriber.nodeName,\n\t\tfs.config.DefaultOpaquePorts,\n\t\tfs.metadataAPI,\n\t\tsubscriber.stream,\n\t\tsubscriber.endStream,\n\t\tfs.log,\n\t\tfs.config.StreamQueueCapacity,\n\t)\n\tif err != nil {\n\t\tfs.log.Errorf(\"Failed to create endpoint translator for %s: %s\", localDiscovery, err)\n\t\treturn\n\t}\n\ttranslator.Start()\n\tsubscriber.localTranslators[localDiscovery] = translator\n\n\tfs.log.Debugf(\"Subscribing to local discovery service %s\", localDiscovery)\n\terr = fs.localEndpoints.Subscribe(watcher.ServiceID{Namespace: fs.namespace, Name: localDiscovery}, subscriber.port, subscriber.instanceID, translator)\n\tif err != nil {\n\t\tfs.log.Errorf(\"Failed to subscribe to %s: %s\", localDiscovery, err)\n\t}\n}\n\nfunc (fs *federatedService) localDiscoveryUnsubscribe(\n\tsubscriber *federatedServiceSubscriber,\n\tlocalDiscovery string,\n) {\n\ttranslator, found := subscriber.localTranslators[localDiscovery]\n\tif found {\n\t\tfs.log.Debugf(\"Unsubscribing to local discovery service %s\", localDiscovery)\n\t\tfs.localEndpoints.Unsubscribe(watcher.ServiceID{Namespace: fs.namespace, Name: localDiscovery}, subscriber.port, subscriber.instanceID, translator)\n\t\ttranslator.NoEndpoints(true)\n\t\ttranslator.DrainAndStop()\n\t\tdelete(subscriber.localTranslators, localDiscovery)\n\t}\n}\n\nfunc remoteDiscoveryIDs(service *corev1.Service, log *logging.Entry) []remoteDiscoveryID {\n\tremoteDiscovery, remoteDiscoveryFound := service.Annotations[labels.RemoteDiscoveryAnnotation]\n\tif !remoteDiscoveryFound {\n\t\treturn nil\n\t}\n\n\tremotes := strings.Split(remoteDiscovery, \",\")\n\tids := make([]remoteDiscoveryID, 0)\n\tfor _, remote := range remotes {\n\t\tparts := strings.Split(remote, \"@\")\n\t\tif len(parts) != 2 {\n\t\t\tlog.Errorf(\"Invalid remote discovery service '%s'\", remote)\n\t\t\tcontinue\n\t\t}\n\t\tremoteSvc := parts[0]\n\t\tcluster := parts[1]\n\t\tids = append(ids, remoteDiscoveryID{\n\t\t\tcluster: cluster,\n\t\t\tservice: watcher.ServiceID{\n\t\t\t\tNamespace: service.Namespace,\n\t\t\t\tName:      remoteSvc,\n\t\t\t},\n\t\t})\n\t}\n\treturn ids\n}\n\nfunc isFederatedService(service *corev1.Service) bool {\n\t_, localDiscoveryFound := service.Annotations[labels.LocalDiscoveryAnnotation]\n\t_, remoteDiscoveryFound := service.Annotations[labels.RemoteDiscoveryAnnotation]\n\treturn localDiscoveryFound || remoteDiscoveryFound\n}\n"
  },
  {
    "path": "controller/api/destination/federated_service_watcher_test.go",
    "content": "package destination\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"slices\"\n\t\"testing\"\n\t\"time\"\n\n\tlogging \"github.com/sirupsen/logrus\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/addr\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nfunc TestFederatedService(t *testing.T) {\n\tfsw, err := mockFederatedServiceWatcher(t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tmockGetServer := &mockDestinationGetServer{updatesReceived: make(chan *pb.Update, 50)}\n\n\tfsw.Subscribe(\"bb-federated\", \"test\", 8080, \"node\", \"\", mockGetServer, nil)\n\n\tupdates := []*pb.Update{}\n\tupdates = append(updates, <-mockGetServer.updatesReceived)\n\tupdates = append(updates, <-mockGetServer.updatesReceived)\n\tassertUpdatesContains(t, updates, \"bb-west-1\", \"172.17.0.1:8080\")\n\tassertUpdatesContains(t, updates, \"bb-east-1\", \"172.17.1.1:8080\")\n}\n\nfunc TestRemoteJoinFederatedService(t *testing.T) {\n\tfsw, err := mockFederatedServiceWatcher(t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tmockGetServer := &mockDestinationGetServer{updatesReceived: make(chan *pb.Update, 50)}\n\n\tfsw.Subscribe(\"bb-federated\", \"test\", 8080, \"node\", \"\", mockGetServer, nil)\n\n\tupdates := []*pb.Update{}\n\tupdates = append(updates, <-mockGetServer.updatesReceived)\n\tupdates = append(updates, <-mockGetServer.updatesReceived)\n\tassertUpdatesContains(t, updates, \"bb-west-1\", \"172.17.0.1:8080\")\n\tassertUpdatesContains(t, updates, \"bb-east-1\", \"172.17.1.1:8080\")\n\n\tfederatedSvc, err := fsw.k8sAPI.Svc().Lister().Services(\"test\").Get(\"bb-federated\")\n\tif err != nil {\n\t\tt.Fatalf(\"error getting federated service: %s\", err)\n\t}\n\tnewFederatedSvc := federatedSvc.DeepCopy()\n\tnewFederatedSvc.Annotations[\"multicluster.linkerd.io/remote-discovery\"] = \"bb@east,bb@north\"\n\tfsw.updateService(federatedSvc, newFederatedSvc)\n\n\tupdates = append(updates, <-mockGetServer.updatesReceived)\n\tassertUpdatesContains(t, updates, \"bb-north-1\", \"172.17.2.1:8080\")\n}\n\nfunc TestRemoteLeaveFederatedService(t *testing.T) {\n\tfsw, err := mockFederatedServiceWatcher(t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tmockGetServer := &mockDestinationGetServer{updatesReceived: make(chan *pb.Update, 50)}\n\n\tfsw.Subscribe(\"bb-federated\", \"test\", 8080, \"node\", \"\", mockGetServer, nil)\n\n\tupdates := []*pb.Update{}\n\tupdates = append(updates, <-mockGetServer.updatesReceived)\n\tupdates = append(updates, <-mockGetServer.updatesReceived)\n\tassertUpdatesContains(t, updates, \"bb-west-1\", \"172.17.0.1:8080\")\n\tassertUpdatesContains(t, updates, \"bb-east-1\", \"172.17.1.1:8080\")\n\n\tfederatedSvc, err := fsw.k8sAPI.Svc().Lister().Services(\"test\").Get(\"bb-federated\")\n\tif err != nil {\n\t\tt.Fatalf(\"error getting federated service: %s\", err)\n\t}\n\tnewFederatedSvc := federatedSvc.DeepCopy()\n\tdelete(newFederatedSvc.Annotations, \"multicluster.linkerd.io/remote-discovery\")\n\tfsw.updateService(federatedSvc, newFederatedSvc)\n\n\tupdates = append(updates, <-mockGetServer.updatesReceived)\n\tassertUpdatesRemoves(t, updates, \"172.17.1.1:8080\")\n}\n\nfunc TestLocalLeaveFederatedService(t *testing.T) {\n\tfsw, err := mockFederatedServiceWatcher(t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tmockGetServer := &mockDestinationGetServer{updatesReceived: make(chan *pb.Update, 50)}\n\n\tfsw.Subscribe(\"bb-federated\", \"test\", 8080, \"node\", \"\", mockGetServer, nil)\n\n\tupdates := []*pb.Update{}\n\tupdates = append(updates, <-mockGetServer.updatesReceived)\n\tupdates = append(updates, <-mockGetServer.updatesReceived)\n\tassertUpdatesContains(t, updates, \"bb-west-1\", \"172.17.0.1:8080\")\n\tassertUpdatesContains(t, updates, \"bb-east-1\", \"172.17.1.1:8080\")\n\n\tfederatedSvc, err := fsw.k8sAPI.Svc().Lister().Services(\"test\").Get(\"bb-federated\")\n\tif err != nil {\n\t\tt.Fatalf(\"error getting federated service: %s\", err)\n\t}\n\tnewFederatedSvc := federatedSvc.DeepCopy()\n\tdelete(newFederatedSvc.Annotations, \"multicluster.linkerd.io/local-discovery\")\n\tfsw.updateService(federatedSvc, newFederatedSvc)\n\n\tupdates = append(updates, <-mockGetServer.updatesReceived)\n\tassertUpdatesRemoves(t, updates, \"172.17.0.1:8080\")\n\n\tfederatedSvc = newFederatedSvc\n\tnewFederatedSvc = federatedSvc.DeepCopy()\n\tnewFederatedSvc.Annotations[\"multicluster.linkerd.io/local-discovery\"] = \"bb\"\n\tfsw.updateService(federatedSvc, newFederatedSvc)\n\n\tupdates = append(updates, <-mockGetServer.updatesReceived)\n\tassertUpdatesContains(t, updates, \"bb-west-1\", \"172.17.0.1:8080\")\n}\n\nfunc mockFederatedServiceWatcher(t *testing.T) (*federatedServiceWatcher, error) {\n\tk8sAPI, err := k8s.NewFakeAPI(westConfigs...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"NewFakeAPI returned an error: %w\", err)\n\t}\n\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"NewFakeMetadataAPI returned an error: %w\", err)\n\t}\n\tlocalEndpoints, err := watcher.NewEndpointsWatcher(k8sAPI, metadataAPI, logging.WithField(\"test\", t.Name()), false, \"local\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"NewEndpointsWatcher returned an error: %w\", err)\n\t}\n\n\tprom := prometheus.NewRegistry()\n\tclusterStore, err := watcher.NewClusterStoreWithDecoder(k8sAPI.Client, \"linkerd\", false,\n\t\twatcher.CreateMulticlusterDecoder(map[string][]string{\n\t\t\t\"east\":  eastConfigs,\n\t\t\t\"north\": northConfigs,\n\t\t}),\n\t\tprom,\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"NewClusterStoreWithDecoder returned an error: %w\", err)\n\t}\n\tfsw, err := newFederatedServiceWatcher(k8sAPI, metadataAPI, &Config{StreamQueueCapacity: DefaultStreamQueueCapacity}, clusterStore, localEndpoints, logging.WithField(\"test\", t.Name()))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"newFederatedServiceWatcher returned an error: %w\", err)\n\t}\n\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\tclusterStore.Sync(nil)\n\n\t// Wait for the cluster store to be populated with the remote clusters.\n\terr = testutil.RetryFor(30*time.Second, func() error {\n\t\tif _, _, found := clusterStore.Get(\"east\"); !found {\n\t\t\treturn errors.New(\"east cluster not found in cluster store\")\n\t\t}\n\t\tif _, _, found := clusterStore.Get(\"north\"); !found {\n\t\t\treturn errors.New(\"north cluster not found in cluster store\")\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"timed out waiting for cluster store to be populated: %w\", err)\n\t}\n\n\treturn fsw, nil\n}\n\nfunc assertUpdatesContains(t *testing.T, updates []*pb.Update, pod, address string) {\n\tt.Helper()\n\tif !slices.ContainsFunc[[]*pb.Update, *pb.Update](updates, func(u *pb.Update) bool {\n\t\tif u.GetAdd() == nil || len(u.GetAdd().GetAddrs()) == 0 {\n\t\t\treturn false\n\t\t}\n\t\tendpoint := u.GetAdd().GetAddrs()[0]\n\t\treturn addr.ProxyAddressToString(endpoint.GetAddr()) == address && endpoint.MetricLabels[\"pod\"] == pod\n\t}) {\n\t\tt.Errorf(\"expected updates to contain pod %s with address %s\", pod, address)\n\t}\n}\n\nfunc assertUpdatesRemoves(t *testing.T, updates []*pb.Update, address string) {\n\tt.Helper()\n\tif !slices.ContainsFunc[[]*pb.Update, *pb.Update](updates, func(u *pb.Update) bool {\n\t\tif u.GetRemove() == nil || len(u.GetRemove().GetAddrs()) == 0 {\n\t\t\treturn false\n\t\t}\n\t\tendpoint := u.GetRemove().GetAddrs()[0]\n\t\treturn addr.ProxyAddressToString(endpoint) == address\n\t}) {\n\t\tt.Errorf(\"expected updates to contain remove of address %s\", address)\n\t}\n}\n\nvar (\n\twestConfigs = []string{\n\t\t`\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: linkerd`,\n\t\t`\napiVersion: v1\nkind: Secret\ntype: mirror.linkerd.io/remote-kubeconfig\nmetadata:\n  namespace: linkerd\n  name: east-cluster-credentials\n  labels:\n    multicluster.linkerd.io/cluster-name: east\n  annotations:\n    multicluster.linkerd.io/trust-domain: cluster.local\n    multicluster.linkerd.io/cluster-domain: cluster.local\ndata:\n  kubeconfig: ZWFzdAo= # east\n`,\n\t\t`\napiVersion: v1\nkind: Secret\ntype: mirror.linkerd.io/remote-kubeconfig\nmetadata:\n  namespace: linkerd\n  name: north-cluster-credentials\n  labels:\n    multicluster.linkerd.io/cluster-name: north\n  annotations:\n    multicluster.linkerd.io/trust-domain: cluster.local\n    multicluster.linkerd.io/cluster-domain: cluster.local\ndata:\n  kubeconfig: bm9ydGgK # north\n`,\n\t\t`\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: test`,\n\t\t`\napiVersion: v1\nkind: Service\nmetadata:\n  name: bb-federated\n  namespace: test\n  annotations:\n    multicluster.linkerd.io/remote-discovery: bb@east\n    multicluster.linkerd.io/local-discovery: bb\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8080`,\n\t\t`\n  apiVersion: v1\n  kind: Service\n  metadata:\n    name: bb\n    namespace: test\n  spec:\n    type: LoadBalancer\n    ports:\n    - port: 8080`,\n\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: bb\n  namespace: test\nsubsets:\n- addresses:\n  - ip: 172.17.0.1\n    targetRef:\n      kind: Pod\n      name: bb-west-1\n      namespace: test\n  ports:\n  - port: 8080\n`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: bb-west-1\n  namespace: test\n  ownerReferences:\n  - kind: ReplicaSet\n    name: bb-west\nstatus:\n  phase: Running\n  podIP: 172.17.0.1`,\n\t}\n\n\teastConfigs = []string{\n\t\t`\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: test`,\n\t\t`\n  apiVersion: v1\n  kind: Service\n  metadata:\n    name: bb\n    namespace: test\n  spec:\n    type: LoadBalancer\n    ports:\n    - port: 8080`,\n\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: bb\n  namespace: test\nsubsets:\n- addresses:\n  - ip: 172.17.1.1\n    targetRef:\n      kind: Pod\n      name: bb-east-1\n      namespace: test\n  ports:\n  - port: 8080\n`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: bb-east-1\n  namespace: test\n  ownerReferences:\n  - kind: ReplicaSet\n    name: bb-east\nstatus:\n  phase: Running\n  podIP: 172.17.1.1`,\n\t}\n\n\tnorthConfigs = []string{\n\t\t`\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: test`,\n\t\t`\n  apiVersion: v1\n  kind: Service\n  metadata:\n    name: bb\n    namespace: test\n  spec:\n    type: LoadBalancer\n    ports:\n    - port: 8080`,\n\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: bb\n  namespace: test\nsubsets:\n- addresses:\n  - ip: 172.17.2.1\n    targetRef:\n      kind: Pod\n      name: bb-north-1\n      namespace: test\n  ports:\n  - port: 8080\n`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: bb-north-1\n  namespace: test\n  ownerReferences:\n  - kind: ReplicaSet\n    name: bb-north\nstatus:\n  phase: Running\n  podIP: 172.17.2.1`,\n\t}\n)\n"
  },
  {
    "path": "controller/api/destination/opaque_ports_adaptor.go",
    "content": "package destination\n\nimport (\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n)\n\n// opaquePortsAdaptor holds an underlying ProfileUpdateListener and updates\n// that listener with changes to a service's opaque ports annotation. It\n// implements OpaquePortsUpdateListener and should be passed to a source of\n// profile updates and opaque ports updates.\ntype opaquePortsAdaptor struct {\n\tlistener    watcher.ProfileUpdateListener\n\tprofile     *sp.ServiceProfile\n\topaquePorts map[uint32]struct{}\n}\n\nfunc newOpaquePortsAdaptor(listener watcher.ProfileUpdateListener) *opaquePortsAdaptor {\n\treturn &opaquePortsAdaptor{\n\t\tlistener: listener,\n\t}\n}\n\nfunc (opa *opaquePortsAdaptor) Update(profile *sp.ServiceProfile) {\n\topa.profile = profile\n\topa.publish()\n}\n\nfunc (opa *opaquePortsAdaptor) UpdateService(ports map[uint32]struct{}) {\n\topa.opaquePorts = ports\n\topa.publish()\n}\n\nfunc (opa *opaquePortsAdaptor) publish() {\n\tif opa.profile != nil {\n\t\tp := *opa.profile\n\t\tp.Spec.OpaquePorts = opa.opaquePorts\n\t\topa.listener.Update(&p)\n\t}\n}\n"
  },
  {
    "path": "controller/api/destination/profile_translator.go",
    "content": "package destination\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/golang/protobuf/ptypes/duration\"\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\tmeta \"github.com/linkerd/linkerd2-proxy-api/go/meta\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\t\"github.com/linkerd/linkerd2/pkg/profiles\"\n\t\"github.com/linkerd/linkerd2/pkg/util\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\tlogging \"github.com/sirupsen/logrus\"\n)\n\nconst millisPerDecimilli = 10\n\n// implements the ProfileUpdateListener interface\ntype profileTranslator struct {\n\tfullyQualifiedName string\n\tport               uint32\n\tparentRef          *meta.Metadata\n\n\tstream          pb.Destination_GetProfileServer\n\tendStream       chan struct{}\n\tlog             *logging.Entry\n\toverflowCounter prometheus.Counter\n\n\tupdates chan *sp.ServiceProfile\n\tstop    chan struct{}\n}\n\nvar profileUpdatesQueueOverflowCounter = promauto.NewCounterVec(\n\tprometheus.CounterOpts{\n\t\tName: \"profile_updates_queue_overflow\",\n\t\tHelp: \"A counter incremented whenever the profile updates queue overflows\",\n\t},\n\t[]string{\n\t\t\"fqn\",\n\t\t\"port\",\n\t},\n)\n\nfunc newProfileTranslator(serviceID watcher.ServiceID, stream pb.Destination_GetProfileServer, log *logging.Entry, fqn string, port uint32, endStream chan struct{}) (*profileTranslator, error) {\n\treturn newProfileTranslatorWithCapacity(serviceID, stream, log, fqn, port, endStream, DefaultStreamQueueCapacity)\n}\n\nfunc newProfileTranslatorWithCapacity(serviceID watcher.ServiceID, stream pb.Destination_GetProfileServer, log *logging.Entry, fqn string, port uint32, endStream chan struct{}, queueCapacity int) (*profileTranslator, error) {\n\tparentRef := &meta.Metadata{\n\t\tKind: &meta.Metadata_Resource{\n\t\t\tResource: &meta.Resource{\n\t\t\t\tGroup:     \"core\",\n\t\t\t\tKind:      \"Service\",\n\t\t\t\tName:      serviceID.Name,\n\t\t\t\tNamespace: serviceID.Namespace,\n\t\t\t\tPort:      port,\n\t\t\t},\n\t\t},\n\t}\n\n\toverflowCounter, err := profileUpdatesQueueOverflowCounter.GetMetricWith(prometheus.Labels{\"fqn\": fqn, \"port\": fmt.Sprintf(\"%d\", port)})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create profile updates queue overflow counter: %w\", err)\n\t}\n\n\treturn &profileTranslator{\n\t\tfullyQualifiedName: fqn,\n\t\tport:               port,\n\t\tparentRef:          parentRef,\n\n\t\tstream:          stream,\n\t\tendStream:       endStream,\n\t\tlog:             log.WithField(\"component\", \"profile-translator\"),\n\t\toverflowCounter: overflowCounter,\n\t\tupdates:         make(chan *sp.ServiceProfile, queueCapacity),\n\t\tstop:            make(chan struct{}),\n\t}, nil\n}\n\n// Update is called from a client-go informer callback and therefore must not\n// We enqueue an update in a channel so that it can be processed asyncronously.\n// To ensure that enqueuing does not block, we first check to see if there is\n// capacity in the buffered channel. If there is not, we drop the update and\n// signal to the stream that it has fallen too far behind and should be closed.\nfunc (pt *profileTranslator) Update(profile *sp.ServiceProfile) {\n\tselect {\n\tcase pt.updates <- profile:\n\t\t// Update has been successfully enqueued.\n\tdefault:\n\t\t// We are unable to enqueue because the channel does not have capacity.\n\t\t// The stream has fallen too far behind and should be closed.\n\t\tpt.overflowCounter.Inc()\n\t\tselect {\n\t\tcase <-pt.endStream:\n\t\t\t// The endStream channel has already been closed so no action is\n\t\t\t// necessary.\n\t\tdefault:\n\t\t\tpt.log.Error(\"profile update queue full; aborting stream\")\n\t\t\tclose(pt.endStream)\n\t\t}\n\t}\n}\n\n// Start initiates a goroutine which processes update events off of the\n// profileTranslator's internal queue and sends to the grpc stream as\n// appropriate. The goroutine calls non-thread-safe Send, therefore Start must\n// not be called more than once.\nfunc (pt *profileTranslator) Start() {\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase update := <-pt.updates:\n\t\t\t\tpt.update(update)\n\t\t\tcase <-pt.stop:\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n}\n\n// Stop terminates the goroutine started by Start.\nfunc (pt *profileTranslator) Stop() {\n\tclose(pt.stop)\n}\n\nfunc (pt *profileTranslator) update(profile *sp.ServiceProfile) {\n\tif profile == nil {\n\t\tpt.log.Debugf(\"Sending default profile\")\n\t\tif err := pt.stream.Send(pt.defaultServiceProfile()); err != nil {\n\t\t\tpt.log.Errorf(\"failed to send default service profile: %s\", err)\n\t\t}\n\t\treturn\n\t}\n\n\tdestinationProfile, err := pt.createDestinationProfile(profile)\n\tif err != nil {\n\t\tpt.log.Error(err)\n\t\treturn\n\t}\n\tpt.log.Debugf(\"Sending profile update: %+v\", destinationProfile)\n\tif err := pt.stream.Send(destinationProfile); err != nil {\n\t\tpt.log.Errorf(\"failed to send profile update: %s\", err)\n\t}\n}\n\nfunc (pt *profileTranslator) defaultServiceProfile() *pb.DestinationProfile {\n\treturn &pb.DestinationProfile{\n\t\tRoutes:             []*pb.Route{},\n\t\tRetryBudget:        defaultRetryBudget(),\n\t\tFullyQualifiedName: pt.fullyQualifiedName,\n\t}\n}\n\nfunc defaultRetryBudget() *pb.RetryBudget {\n\treturn &pb.RetryBudget{\n\t\tMinRetriesPerSecond: 10,\n\t\tRetryRatio:          0.2,\n\t\tTtl: &duration.Duration{\n\t\t\tSeconds: 10,\n\t\t},\n\t}\n}\n\nfunc toDuration(d time.Duration) *duration.Duration {\n\tif d == 0 {\n\t\treturn nil\n\t}\n\treturn &duration.Duration{\n\t\tSeconds: int64(d / time.Second),\n\t\tNanos:   int32(d % time.Second),\n\t}\n}\n\n// createDestinationProfile returns a Proxy API DestinationProfile, given a\n// ServiceProfile.\nfunc (pt *profileTranslator) createDestinationProfile(profile *sp.ServiceProfile) (*pb.DestinationProfile, error) {\n\tvar profileRef *meta.Metadata\n\tif profile != nil {\n\t\tprofileRef = &meta.Metadata{\n\t\t\tKind: &meta.Metadata_Resource{\n\t\t\t\tResource: &meta.Resource{\n\t\t\t\t\tGroup:     sp.SchemeGroupVersion.Group,\n\t\t\t\t\tKind:      profile.Kind,\n\t\t\t\t\tName:      profile.Name,\n\t\t\t\t\tNamespace: profile.Namespace,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\troutes := make([]*pb.Route, 0)\n\tfor _, route := range profile.Spec.Routes {\n\t\tpbRoute, err := toRoute(profile, route)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\troutes = append(routes, pbRoute)\n\t}\n\tbudget := defaultRetryBudget()\n\tif profile.Spec.RetryBudget != nil {\n\t\tbudget.MinRetriesPerSecond = profile.Spec.RetryBudget.MinRetriesPerSecond\n\t\tbudget.RetryRatio = profile.Spec.RetryBudget.RetryRatio\n\t\tttl, err := time.ParseDuration(profile.Spec.RetryBudget.TTL)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tbudget.Ttl = toDuration(ttl)\n\t}\n\tvar opaqueProtocol bool\n\tif profile.Spec.OpaquePorts != nil {\n\t\t_, opaqueProtocol = profile.Spec.OpaquePorts[pt.port]\n\t}\n\treturn &pb.DestinationProfile{\n\t\tParentRef:          pt.parentRef,\n\t\tProfileRef:         profileRef,\n\t\tRoutes:             routes,\n\t\tRetryBudget:        budget,\n\t\tDstOverrides:       toDstOverrides(profile.Spec.DstOverrides, pt.port),\n\t\tFullyQualifiedName: pt.fullyQualifiedName,\n\t\tOpaqueProtocol:     opaqueProtocol,\n\t}, nil\n}\n\nfunc toDstOverrides(dsts []*sp.WeightedDst, port uint32) []*pb.WeightedDst {\n\tpbDsts := []*pb.WeightedDst{}\n\tfor _, dst := range dsts {\n\t\tauthority := dst.Authority\n\t\t// If the authority does not parse as a host:port, add the port provided by `GetProfile`.\n\t\tif _, _, err := net.SplitHostPort(authority); err != nil {\n\t\t\tauthority = net.JoinHostPort(authority, fmt.Sprintf(\"%d\", port))\n\t\t}\n\n\t\tpbDst := &pb.WeightedDst{\n\t\t\tAuthority: authority,\n\t\t\t// Weights are expressed in decimillis: 10_000 represents 100%\n\t\t\tWeight: uint32(dst.Weight.MilliValue() * millisPerDecimilli),\n\t\t}\n\t\tpbDsts = append(pbDsts, pbDst)\n\t}\n\treturn pbDsts\n}\n\n// toRoute returns a Proxy API Route, given a ServiceProfile Route.\nfunc toRoute(profile *sp.ServiceProfile, route *sp.RouteSpec) (*pb.Route, error) {\n\tcond, err := toRequestMatch(route.Condition)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trcs := make([]*pb.ResponseClass, 0)\n\tfor _, rc := range route.ResponseClasses {\n\t\tpbRc, err := toResponseClass(rc)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trcs = append(rcs, pbRc)\n\t}\n\tvar timeout time.Duration // No default timeout\n\tif route.Timeout != \"\" {\n\t\ttimeout, err = time.ParseDuration(route.Timeout)\n\t\tif err != nil {\n\t\t\tlogging.Errorf(\n\t\t\t\t\"failed to parse duration for route '%s' in service profile '%s' in namespace '%s': %s\",\n\t\t\t\troute.Name,\n\t\t\t\tprofile.Name,\n\t\t\t\tprofile.Namespace,\n\t\t\t\terr,\n\t\t\t)\n\t\t}\n\t}\n\treturn &pb.Route{\n\t\tCondition:       cond,\n\t\tResponseClasses: rcs,\n\t\tMetricsLabels:   map[string]string{\"route\": route.Name},\n\t\tIsRetryable:     route.IsRetryable,\n\t\tTimeout:         toDuration(timeout),\n\t}, nil\n}\n\n// toResponseClass returns a Proxy API ResponseClass, given a ServiceProfile\n// ResponseClass.\nfunc toResponseClass(rc *sp.ResponseClass) (*pb.ResponseClass, error) {\n\tcond, err := toResponseMatch(rc.Condition)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &pb.ResponseClass{\n\t\tCondition: cond,\n\t\tIsFailure: rc.IsFailure,\n\t}, nil\n}\n\n// toResponseMatch returns a Proxy API ResponseMatch, given a ServiceProfile\n// ResponseMatch.\nfunc toResponseMatch(rspMatch *sp.ResponseMatch) (*pb.ResponseMatch, error) {\n\tif rspMatch == nil {\n\t\treturn nil, errors.New(\"missing response match\")\n\t}\n\terr := profiles.ValidateResponseMatch(rspMatch)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmatches := make([]*pb.ResponseMatch, 0)\n\n\tif rspMatch.All != nil {\n\t\tall := make([]*pb.ResponseMatch, 0)\n\t\tfor _, m := range rspMatch.All {\n\t\t\tpbM, err := toResponseMatch(m)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tall = append(all, pbM)\n\t\t}\n\t\tmatches = append(matches, &pb.ResponseMatch{\n\t\t\tMatch: &pb.ResponseMatch_All{\n\t\t\t\tAll: &pb.ResponseMatch_Seq{\n\t\t\t\t\tMatches: all,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t}\n\n\tif rspMatch.Any != nil {\n\t\tany := make([]*pb.ResponseMatch, 0)\n\t\tfor _, m := range rspMatch.Any {\n\t\t\tpbM, err := toResponseMatch(m)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tany = append(any, pbM)\n\t\t}\n\t\tmatches = append(matches, &pb.ResponseMatch{\n\t\t\tMatch: &pb.ResponseMatch_Any{\n\t\t\t\tAny: &pb.ResponseMatch_Seq{\n\t\t\t\t\tMatches: any,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t}\n\n\tif rspMatch.Status != nil {\n\t\tmatches = append(matches, &pb.ResponseMatch{\n\t\t\tMatch: &pb.ResponseMatch_Status{\n\t\t\t\tStatus: &pb.HttpStatusRange{\n\t\t\t\t\tMax: rspMatch.Status.Max,\n\t\t\t\t\tMin: rspMatch.Status.Min,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t}\n\n\tif rspMatch.Not != nil {\n\t\tnot, err := toResponseMatch(rspMatch.Not)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmatches = append(matches, &pb.ResponseMatch{\n\t\t\tMatch: &pb.ResponseMatch_Not{\n\t\t\t\tNot: not,\n\t\t\t},\n\t\t})\n\t}\n\n\tif len(matches) == 0 {\n\t\treturn nil, errors.New(\"a response match must have a field set\")\n\t}\n\tif len(matches) == 1 {\n\t\treturn matches[0], nil\n\t}\n\treturn &pb.ResponseMatch{\n\t\tMatch: &pb.ResponseMatch_All{\n\t\t\tAll: &pb.ResponseMatch_Seq{\n\t\t\t\tMatches: matches,\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\n// toRequestMatch returns a Proxy API RequestMatch, given a ServiceProfile\n// RequestMatch.\nfunc toRequestMatch(reqMatch *sp.RequestMatch) (*pb.RequestMatch, error) {\n\tif reqMatch == nil {\n\t\treturn nil, errors.New(\"missing request match\")\n\t}\n\terr := profiles.ValidateRequestMatch(reqMatch)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmatches := make([]*pb.RequestMatch, 0)\n\n\tif reqMatch.All != nil {\n\t\tall := make([]*pb.RequestMatch, 0)\n\t\tfor _, m := range reqMatch.All {\n\t\t\tpbM, err := toRequestMatch(m)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tall = append(all, pbM)\n\t\t}\n\t\tmatches = append(matches, &pb.RequestMatch{\n\t\t\tMatch: &pb.RequestMatch_All{\n\t\t\t\tAll: &pb.RequestMatch_Seq{\n\t\t\t\t\tMatches: all,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t}\n\n\tif reqMatch.Any != nil {\n\t\tany := make([]*pb.RequestMatch, 0)\n\t\tfor _, m := range reqMatch.Any {\n\t\t\tpbM, err := toRequestMatch(m)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tany = append(any, pbM)\n\t\t}\n\t\tmatches = append(matches, &pb.RequestMatch{\n\t\t\tMatch: &pb.RequestMatch_Any{\n\t\t\t\tAny: &pb.RequestMatch_Seq{\n\t\t\t\t\tMatches: any,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t}\n\n\tif reqMatch.Method != \"\" {\n\t\tmatches = append(matches, &pb.RequestMatch{\n\t\t\tMatch: &pb.RequestMatch_Method{\n\t\t\t\tMethod: util.ParseMethod(reqMatch.Method),\n\t\t\t},\n\t\t})\n\t}\n\n\tif reqMatch.Not != nil {\n\t\tnot, err := toRequestMatch(reqMatch.Not)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmatches = append(matches, &pb.RequestMatch{\n\t\t\tMatch: &pb.RequestMatch_Not{\n\t\t\t\tNot: not,\n\t\t\t},\n\t\t})\n\t}\n\n\tif reqMatch.PathRegex != \"\" {\n\t\tmatches = append(matches, &pb.RequestMatch{\n\t\t\tMatch: &pb.RequestMatch_Path{\n\t\t\t\tPath: &pb.PathMatch{\n\t\t\t\t\tRegex: reqMatch.PathRegex,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t}\n\n\tif len(matches) == 0 {\n\t\treturn nil, errors.New(\"a request match must have a field set\")\n\t}\n\tif len(matches) == 1 {\n\t\treturn matches[0], nil\n\t}\n\treturn &pb.RequestMatch{\n\t\tMatch: &pb.RequestMatch_All{\n\t\t\tAll: &pb.RequestMatch_Seq{\n\t\t\t\tMatches: matches,\n\t\t\t},\n\t\t},\n\t}, nil\n}\n"
  },
  {
    "path": "controller/api/destination/profile_translator_test.go",
    "content": "package destination\n\nimport (\n\t\"testing\"\n\n\t\"github.com/golang/protobuf/ptypes/duration\"\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\thttpPb \"github.com/linkerd/linkerd2-proxy-api/go/http_types\"\n\t\"github.com/linkerd/linkerd2-proxy-api/go/meta\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tlogging \"github.com/sirupsen/logrus\"\n\t\"google.golang.org/protobuf/proto\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nvar (\n\tgetButNotPrivate = &sp.RequestMatch{\n\t\tAll: []*sp.RequestMatch{\n\t\t\t{\n\t\t\t\tMethod: \"GET\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tNot: &sp.RequestMatch{\n\t\t\t\t\tPathRegex: \"/private/.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tpbGetButNotPrivate = &pb.RequestMatch{\n\t\tMatch: &pb.RequestMatch_All{\n\t\t\tAll: &pb.RequestMatch_Seq{\n\t\t\t\tMatches: []*pb.RequestMatch{\n\t\t\t\t\t{\n\t\t\t\t\t\tMatch: &pb.RequestMatch_Method{\n\t\t\t\t\t\t\tMethod: &httpPb.HttpMethod{\n\t\t\t\t\t\t\t\tType: &httpPb.HttpMethod_Registered_{\n\t\t\t\t\t\t\t\t\tRegistered: httpPb.HttpMethod_GET,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tMatch: &pb.RequestMatch_Not{\n\t\t\t\t\t\t\tNot: &pb.RequestMatch{\n\t\t\t\t\t\t\t\tMatch: &pb.RequestMatch_Path{\n\t\t\t\t\t\t\t\t\tPath: &pb.PathMatch{\n\t\t\t\t\t\t\t\t\t\tRegex: \"/private/.*\",\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\tlogin = &sp.RequestMatch{\n\t\tPathRegex: \"/login\",\n\t}\n\n\tpbLogin = &pb.RequestMatch{\n\t\tMatch: &pb.RequestMatch_Path{\n\t\t\tPath: &pb.PathMatch{\n\t\t\t\tRegex: \"/login\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfiveXX = &sp.ResponseMatch{\n\t\tStatus: &sp.Range{\n\t\t\tMin: 500,\n\t\t\tMax: 599,\n\t\t},\n\t}\n\n\tpbFiveXX = &pb.ResponseMatch{\n\t\tMatch: &pb.ResponseMatch_Status{\n\t\t\tStatus: &pb.HttpStatusRange{\n\t\t\t\tMin: 500,\n\t\t\t\tMax: 599,\n\t\t\t},\n\t\t},\n\t}\n\n\tfiveXXfourTwenty = &sp.ResponseMatch{\n\t\tAny: []*sp.ResponseMatch{\n\t\t\tfiveXX,\n\t\t\t{\n\t\t\t\tStatus: &sp.Range{\n\t\t\t\t\tMin: 420,\n\t\t\t\t\tMax: 420,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tpbFiveXXfourTwenty = &pb.ResponseMatch{\n\t\tMatch: &pb.ResponseMatch_Any{\n\t\t\tAny: &pb.ResponseMatch_Seq{\n\t\t\t\tMatches: []*pb.ResponseMatch{\n\t\t\t\t\tpbFiveXX,\n\t\t\t\t\t{\n\t\t\t\t\t\tMatch: &pb.ResponseMatch_Status{\n\t\t\t\t\t\t\tStatus: &pb.HttpStatusRange{\n\t\t\t\t\t\t\t\tMin: 420,\n\t\t\t\t\t\t\t\tMax: 420,\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\troute1 = &sp.RouteSpec{\n\t\tName:      \"route1\",\n\t\tCondition: getButNotPrivate,\n\t\tResponseClasses: []*sp.ResponseClass{\n\t\t\t{\n\t\t\t\tCondition: fiveXX,\n\t\t\t\tIsFailure: true,\n\t\t\t},\n\t\t},\n\t}\n\n\tpbRoute1 = &pb.Route{\n\t\tMetricsLabels: map[string]string{\n\t\t\t\"route\": \"route1\",\n\t\t},\n\t\tCondition: pbGetButNotPrivate,\n\t\tResponseClasses: []*pb.ResponseClass{\n\t\t\t{\n\t\t\t\tCondition: pbFiveXX,\n\t\t\t\tIsFailure: true,\n\t\t\t},\n\t\t},\n\t\tTimeout: nil,\n\t}\n\n\troute2 = &sp.RouteSpec{\n\t\tName:      \"route2\",\n\t\tCondition: login,\n\t\tResponseClasses: []*sp.ResponseClass{\n\t\t\t{\n\t\t\t\tCondition: fiveXXfourTwenty,\n\t\t\t\tIsFailure: true,\n\t\t\t},\n\t\t},\n\t}\n\n\tpbRoute2 = &pb.Route{\n\t\tMetricsLabels: map[string]string{\n\t\t\t\"route\": \"route2\",\n\t\t},\n\t\tCondition: pbLogin,\n\t\tResponseClasses: []*pb.ResponseClass{\n\t\t\t{\n\t\t\t\tCondition: pbFiveXXfourTwenty,\n\t\t\t\tIsFailure: true,\n\t\t\t},\n\t\t},\n\t\tTimeout: nil,\n\t}\n\n\tspTypeMeta = metav1.TypeMeta{\n\t\tKind: \"ServiceProfile\",\n\t}\n\tspObjectMeta = metav1.ObjectMeta{\n\t\tName:      \"foo.bar.svc.cluster.local\",\n\t\tNamespace: \"bar\",\n\t}\n\n\tprofile = &sp.ServiceProfile{\n\t\tTypeMeta:   spTypeMeta,\n\t\tObjectMeta: spObjectMeta,\n\t\tSpec: sp.ServiceProfileSpec{\n\t\t\tRoutes: []*sp.RouteSpec{\n\t\t\t\troute1,\n\t\t\t\troute2,\n\t\t\t},\n\t\t},\n\t}\n\n\tpbProfile = &pb.DestinationProfile{\n\t\tFullyQualifiedName: \"foo.bar.svc.cluster.local\",\n\t\tParentRef: &meta.Metadata{\n\t\t\tKind: &meta.Metadata_Resource{\n\t\t\t\tResource: &meta.Resource{\n\t\t\t\t\tGroup:     \"core\",\n\t\t\t\t\tKind:      \"Service\",\n\t\t\t\t\tName:      \"foo\",\n\t\t\t\t\tNamespace: \"bar\",\n\t\t\t\t\tPort:      80,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tProfileRef: &meta.Metadata{\n\t\t\tKind: &meta.Metadata_Resource{\n\t\t\t\tResource: &meta.Resource{\n\t\t\t\t\tGroup:     \"linkerd.io\",\n\t\t\t\t\tKind:      \"ServiceProfile\",\n\t\t\t\t\tName:      \"foo.bar.svc.cluster.local\",\n\t\t\t\t\tNamespace: \"bar\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tRoutes: []*pb.Route{\n\t\t\tpbRoute1,\n\t\t\tpbRoute2,\n\t\t},\n\t\tRetryBudget: defaultRetryBudget(),\n\t}\n\n\tdefaultPbProfile = &pb.DestinationProfile{\n\t\tRoutes:      []*pb.Route{},\n\t\tRetryBudget: defaultRetryBudget(),\n\t}\n\n\tmultipleRequestMatches = &sp.ServiceProfile{\n\t\tTypeMeta:   spTypeMeta,\n\t\tObjectMeta: spObjectMeta,\n\t\tSpec: sp.ServiceProfileSpec{\n\t\t\tRoutes: []*sp.RouteSpec{\n\t\t\t\t{\n\t\t\t\t\tName: \"multipleRequestMatches\",\n\t\t\t\t\tCondition: &sp.RequestMatch{\n\t\t\t\t\t\tMethod:    \"GET\",\n\t\t\t\t\t\tPathRegex: \"/my/path\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tpbRequestMatchAll = &pb.DestinationProfile{\n\t\tFullyQualifiedName: pbProfile.FullyQualifiedName,\n\t\tParentRef:          pbProfile.ParentRef,\n\t\tProfileRef:         pbProfile.ProfileRef,\n\t\tRoutes: []*pb.Route{\n\t\t\t{\n\t\t\t\tCondition: &pb.RequestMatch{\n\t\t\t\t\tMatch: &pb.RequestMatch_All{\n\t\t\t\t\t\tAll: &pb.RequestMatch_Seq{\n\t\t\t\t\t\t\tMatches: []*pb.RequestMatch{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tMatch: &pb.RequestMatch_Method{\n\t\t\t\t\t\t\t\t\t\tMethod: &httpPb.HttpMethod{\n\t\t\t\t\t\t\t\t\t\t\tType: &httpPb.HttpMethod_Registered_{\n\t\t\t\t\t\t\t\t\t\t\t\tRegistered: httpPb.HttpMethod_GET,\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\t{\n\t\t\t\t\t\t\t\t\tMatch: &pb.RequestMatch_Path{\n\t\t\t\t\t\t\t\t\t\tPath: &pb.PathMatch{\n\t\t\t\t\t\t\t\t\t\t\tRegex: \"/my/path\",\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\tMetricsLabels: map[string]string{\n\t\t\t\t\t\"route\": \"multipleRequestMatches\",\n\t\t\t\t},\n\t\t\t\tResponseClasses: []*pb.ResponseClass{},\n\t\t\t\tTimeout:         nil,\n\t\t\t},\n\t\t},\n\t\tRetryBudget: defaultRetryBudget(),\n\t}\n\n\tnotEnoughRequestMatches = &sp.ServiceProfile{\n\t\tTypeMeta:   spTypeMeta,\n\t\tObjectMeta: spObjectMeta,\n\t\tSpec: sp.ServiceProfileSpec{\n\t\t\tRoutes: []*sp.RouteSpec{\n\t\t\t\t{\n\t\t\t\t\tCondition: &sp.RequestMatch{},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tmultipleResponseMatches = &sp.ServiceProfile{\n\t\tTypeMeta:   spTypeMeta,\n\t\tObjectMeta: spObjectMeta,\n\t\tSpec: sp.ServiceProfileSpec{\n\t\t\tRoutes: []*sp.RouteSpec{\n\t\t\t\t{\n\t\t\t\t\tName: \"multipleResponseMatches\",\n\t\t\t\t\tCondition: &sp.RequestMatch{\n\t\t\t\t\t\tMethod: \"GET\",\n\t\t\t\t\t},\n\t\t\t\t\tResponseClasses: []*sp.ResponseClass{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tCondition: &sp.ResponseMatch{\n\t\t\t\t\t\t\t\tStatus: &sp.Range{\n\t\t\t\t\t\t\t\t\tMin: 400,\n\t\t\t\t\t\t\t\t\tMax: 499,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tNot: &sp.ResponseMatch{\n\t\t\t\t\t\t\t\t\tStatus: &sp.Range{\n\t\t\t\t\t\t\t\t\t\tMin: 404,\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\tpbResponseMatchAll = &pb.DestinationProfile{\n\t\tFullyQualifiedName: pbProfile.FullyQualifiedName,\n\t\tParentRef:          pbProfile.ParentRef,\n\t\tProfileRef:         pbProfile.ProfileRef,\n\t\tRoutes: []*pb.Route{\n\t\t\t{\n\t\t\t\tCondition: &pb.RequestMatch{\n\t\t\t\t\tMatch: &pb.RequestMatch_Method{\n\t\t\t\t\t\tMethod: &httpPb.HttpMethod{\n\t\t\t\t\t\t\tType: &httpPb.HttpMethod_Registered_{\n\t\t\t\t\t\t\t\tRegistered: httpPb.HttpMethod_GET,\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\tMetricsLabels: map[string]string{\n\t\t\t\t\t\"route\": \"multipleResponseMatches\",\n\t\t\t\t},\n\t\t\t\tResponseClasses: []*pb.ResponseClass{\n\t\t\t\t\t{\n\t\t\t\t\t\tCondition: &pb.ResponseMatch{\n\t\t\t\t\t\t\tMatch: &pb.ResponseMatch_All{\n\t\t\t\t\t\t\t\tAll: &pb.ResponseMatch_Seq{\n\t\t\t\t\t\t\t\t\tMatches: []*pb.ResponseMatch{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tMatch: &pb.ResponseMatch_Status{\n\t\t\t\t\t\t\t\t\t\t\t\tStatus: &pb.HttpStatusRange{\n\t\t\t\t\t\t\t\t\t\t\t\t\tMin: 400,\n\t\t\t\t\t\t\t\t\t\t\t\t\tMax: 499,\n\t\t\t\t\t\t\t\t\t\t\t\t},\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\t{\n\t\t\t\t\t\t\t\t\t\t\tMatch: &pb.ResponseMatch_Not{\n\t\t\t\t\t\t\t\t\t\t\t\tNot: &pb.ResponseMatch{\n\t\t\t\t\t\t\t\t\t\t\t\t\tMatch: &pb.ResponseMatch_Status{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tStatus: &pb.HttpStatusRange{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tMin: 404,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\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\tTimeout: nil,\n\t\t\t},\n\t\t},\n\t\tRetryBudget: defaultRetryBudget(),\n\t}\n\n\toneSidedStatusRange = &sp.ServiceProfile{\n\t\tTypeMeta:   spTypeMeta,\n\t\tObjectMeta: spObjectMeta,\n\t\tSpec: sp.ServiceProfileSpec{\n\t\t\tRoutes: []*sp.RouteSpec{\n\t\t\t\t{\n\t\t\t\t\tCondition: &sp.RequestMatch{\n\t\t\t\t\t\tMethod: \"GET\",\n\t\t\t\t\t},\n\t\t\t\t\tResponseClasses: []*sp.ResponseClass{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tCondition: &sp.ResponseMatch{\n\t\t\t\t\t\t\t\tStatus: &sp.Range{\n\t\t\t\t\t\t\t\t\tMin: 200,\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\tinvalidStatusRange = &sp.ServiceProfile{\n\t\tTypeMeta:   spTypeMeta,\n\t\tObjectMeta: spObjectMeta,\n\t\tSpec: sp.ServiceProfileSpec{\n\t\t\tRoutes: []*sp.RouteSpec{\n\t\t\t\t{\n\t\t\t\t\tCondition: &sp.RequestMatch{\n\t\t\t\t\t\tMethod: \"GET\",\n\t\t\t\t\t},\n\t\t\t\t\tResponseClasses: []*sp.ResponseClass{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tCondition: &sp.ResponseMatch{\n\t\t\t\t\t\t\t\tStatus: &sp.Range{\n\t\t\t\t\t\t\t\t\tMin: 201,\n\t\t\t\t\t\t\t\t\tMax: 200,\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\tnotEnoughResponseMatches = &sp.ServiceProfile{\n\t\tTypeMeta:   spTypeMeta,\n\t\tObjectMeta: spObjectMeta,\n\t\tSpec: sp.ServiceProfileSpec{\n\t\t\tRoutes: []*sp.RouteSpec{\n\t\t\t\t{\n\t\t\t\t\tCondition: &sp.RequestMatch{\n\t\t\t\t\t\tMethod: \"GET\",\n\t\t\t\t\t},\n\t\t\t\t\tResponseClasses: []*sp.ResponseClass{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tCondition: &sp.ResponseMatch{},\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\trouteWithTimeout = &sp.RouteSpec{\n\t\tName:            \"routeWithTimeout\",\n\t\tCondition:       login,\n\t\tResponseClasses: []*sp.ResponseClass{},\n\t\tTimeout:         \"200ms\",\n\t}\n\n\tprofileWithTimeout = &sp.ServiceProfile{\n\t\tTypeMeta:   spTypeMeta,\n\t\tObjectMeta: spObjectMeta,\n\t\tSpec: sp.ServiceProfileSpec{\n\t\t\tRoutes: []*sp.RouteSpec{\n\t\t\t\trouteWithTimeout,\n\t\t\t},\n\t\t},\n\t}\n\n\tpbRouteWithTimeout = &pb.Route{\n\t\tMetricsLabels: map[string]string{\n\t\t\t\"route\": \"routeWithTimeout\",\n\t\t},\n\t\tCondition:       pbLogin,\n\t\tResponseClasses: []*pb.ResponseClass{},\n\t\tTimeout: &duration.Duration{\n\t\t\tNanos: 200000000, // 200ms\n\t\t},\n\t}\n\n\tpbProfileWithTimeout = &pb.DestinationProfile{\n\t\tFullyQualifiedName: pbProfile.FullyQualifiedName,\n\t\tParentRef:          pbProfile.ParentRef,\n\t\tProfileRef:         pbProfile.ProfileRef,\n\t\tRoutes: []*pb.Route{\n\t\t\tpbRouteWithTimeout,\n\t\t},\n\t\tRetryBudget: defaultRetryBudget(),\n\t}\n)\n\nfunc newMockTranslator(t *testing.T) (*profileTranslator, chan *pb.DestinationProfile) {\n\tt.Helper()\n\tid := watcher.ServiceID{Namespace: \"bar\", Name: \"foo\"}\n\tserver := &mockDestinationGetProfileServer{profilesReceived: make(chan *pb.DestinationProfile, 50)}\n\ttranslator, err := newProfileTranslator(id, server, logging.WithField(\"test\", t.Name()), \"foo.bar.svc.cluster.local\", 80, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create profile translator: %s\", err)\n\t}\n\treturn translator, server.profilesReceived\n}\n\nfunc TestProfileTranslator(t *testing.T) {\n\tt.Run(\"Sends update\", func(t *testing.T) {\n\t\ttranslator, profilesReceived := newMockTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Update(profile)\n\n\t\tactualPbProfile := <-profilesReceived\n\t\tif !proto.Equal(actualPbProfile, pbProfile) {\n\t\t\tt.Fatalf(\"Expected profile sent to be [%v] but was [%v]\", pbProfile, actualPbProfile)\n\t\t}\n\t\tnumProfiles := len(profilesReceived) + 1\n\t\tif numProfiles != 1 {\n\t\t\tt.Fatalf(\"Expecting [1] profile, got [%d]. Updates: %v\", numProfiles, profilesReceived)\n\t\t}\n\t})\n\n\tt.Run(\"Request match with more than one field becomes ALL\", func(t *testing.T) {\n\t\ttranslator, profilesReceived := newMockTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Update(multipleRequestMatches)\n\n\t\tactualPbProfile := <-profilesReceived\n\t\tif !proto.Equal(actualPbProfile, pbRequestMatchAll) {\n\t\t\tt.Fatalf(\"Expected profile sent to be [%v] but was [%v]\", pbRequestMatchAll, actualPbProfile)\n\t\t}\n\t\tnumProfiles := len(profilesReceived) + 1\n\t\tif numProfiles != 1 {\n\t\t\tt.Fatalf(\"Expecting [1] profiles, got [%d]. Updates: %v\", numProfiles, profilesReceived)\n\t\t}\n\t})\n\n\tt.Run(\"Ignores request match without any fields\", func(t *testing.T) {\n\t\ttranslator, profilesReceived := newMockTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Update(notEnoughRequestMatches)\n\n\t\tnumProfiles := len(profilesReceived)\n\t\tif numProfiles != 0 {\n\t\t\tt.Fatalf(\"Expecting [0] profiles, got [%d]. Updates: %v\", numProfiles, profilesReceived)\n\t\t}\n\t})\n\n\tt.Run(\"Response match with more than one field becomes ALL\", func(t *testing.T) {\n\t\ttranslator, profilesReceived := newMockTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Update(multipleResponseMatches)\n\n\t\tactualPbProfile := <-profilesReceived\n\t\tif !proto.Equal(actualPbProfile, pbResponseMatchAll) {\n\t\t\tt.Fatalf(\"Expected profile sent to be [%v] but was [%v]\", pbResponseMatchAll, actualPbProfile)\n\t\t}\n\t\tnumProfiles := len(profilesReceived) + 1\n\t\tif numProfiles != 1 {\n\t\t\tt.Fatalf(\"Expecting [1] profiles, got [%d]. Updates: %v\", numProfiles, profilesReceived)\n\t\t}\n\t})\n\n\tt.Run(\"Ignores response match without any fields\", func(t *testing.T) {\n\t\ttranslator, profilesReceived := newMockTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Update(notEnoughResponseMatches)\n\n\t\tnumProfiles := len(profilesReceived)\n\t\tif numProfiles != 0 {\n\t\t\tt.Fatalf(\"Expecting [0] profiles, got [%d]. Updates: %v\", numProfiles, profilesReceived)\n\t\t}\n\t})\n\n\tt.Run(\"Ignores response match with invalid status range\", func(t *testing.T) {\n\t\ttranslator, profilesReceived := newMockTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Update(invalidStatusRange)\n\n\t\tnumProfiles := len(profilesReceived)\n\t\tif numProfiles != 0 {\n\t\t\tt.Fatalf(\"Expecting [0] profiles, got [%d]. Updates: %v\", numProfiles, profilesReceived)\n\t\t}\n\t})\n\n\tt.Run(\"Sends update for one sided status range\", func(t *testing.T) {\n\t\ttranslator, profilesReceived := newMockTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Update(oneSidedStatusRange)\n\n\t\t<-profilesReceived\n\n\t\tnumProfiles := len(profilesReceived) + 1\n\t\tif numProfiles != 1 {\n\t\t\tt.Fatalf(\"Expecting [1] profile, got [%d]. Updates: %v\", numProfiles, profilesReceived)\n\t\t}\n\t})\n\n\tt.Run(\"Sends empty update\", func(t *testing.T) {\n\t\tserver := &mockDestinationGetProfileServer{profilesReceived: make(chan *pb.DestinationProfile, 50)}\n\t\ttranslator, err := newProfileTranslator(watcher.ID{}, server, logging.WithField(\"test\", t.Name()), \"\", 80, nil)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to create profile translator: %s\", err)\n\t\t}\n\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Update(nil)\n\n\t\tactualPbProfile := <-server.profilesReceived\n\t\tif !proto.Equal(actualPbProfile, defaultPbProfile) {\n\t\t\tt.Fatalf(\"Expected profile sent to be [%v] but was [%v]\", defaultPbProfile, actualPbProfile)\n\t\t}\n\t\tnumProfiles := len(server.profilesReceived) + 1\n\t\tif numProfiles != 1 {\n\t\t\tt.Fatalf(\"Expecting [1] profile, got [%d]. Updates: %v\", numProfiles, server.profilesReceived)\n\t\t}\n\t})\n\n\tt.Run(\"Sends update with custom timeout\", func(t *testing.T) {\n\t\ttranslator, profilesReceived := newMockTranslator(t)\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\ttranslator.Update(profileWithTimeout)\n\n\t\tactualPbProfile := <-profilesReceived\n\t\tif !proto.Equal(actualPbProfile, pbProfileWithTimeout) {\n\t\t\tt.Fatalf(\"Expected profile sent to be [%v] but was [%v]\", pbProfileWithTimeout, actualPbProfile)\n\t\t}\n\t\tnumProfiles := len(profilesReceived) + 1\n\t\tif numProfiles != 1 {\n\t\t\tt.Fatalf(\"Expecting [1] profile, got [%d]. Updates: %v\", numProfiles, profilesReceived)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "controller/api/destination/server.go",
    "content": "package destination\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tlabels \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/prometheus\"\n\t\"github.com/linkerd/linkerd2/pkg/util\"\n\tlogging \"github.com/sirupsen/logrus\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/peer\"\n\t\"google.golang.org/grpc/status\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n)\n\n// DefaultStreamQueueCapacity defines the default maximum number of pending\n// updates buffered per stream before the stream is closed.\nconst DefaultStreamQueueCapacity = 100\n\ntype (\n\tConfig struct {\n\t\tControllerNS,\n\t\tIdentityTrustDomain,\n\t\tClusterDomain string\n\n\t\tForceOpaqueTransport,\n\t\tEnableH2Upgrade,\n\t\tEnableEndpointSlices,\n\t\tEnableIPv6,\n\t\tExtEndpointZoneWeights bool\n\n\t\tMeshedHttp2ClientParams *pb.Http2ClientParams\n\n\t\tDefaultOpaquePorts map[uint32]struct{}\n\n\t\tStreamQueueCapacity int\n\t}\n\n\tserver struct {\n\t\tpb.UnimplementedDestinationServer\n\n\t\tconfig Config\n\n\t\tworkloads         *watcher.WorkloadWatcher\n\t\tendpoints         *watcher.EndpointsWatcher\n\t\topaquePorts       *watcher.OpaquePortsWatcher\n\t\tprofiles          *watcher.ProfileWatcher\n\t\tclusterStore      *watcher.ClusterStore\n\t\tfederatedServices *federatedServiceWatcher\n\n\t\tk8sAPI      *k8s.API\n\t\tmetadataAPI *k8s.MetadataAPI\n\t\tlog         *logging.Entry\n\t\tshutdown    <-chan struct{}\n\t}\n)\n\n// NewServer returns a new instance of the destination server.\n//\n// The destination server serves service discovery and other information to the\n// proxy.  This implementation supports the \"k8s\" destination scheme and expects\n// destination paths to be of the form:\n// <service>.<namespace>.svc.cluster.local:<port>\n//\n// If the port is omitted, 80 is used as a default.  If the namespace is\n// omitted, \"default\" is used as a default.append\n//\n// Addresses for the given destination are fetched from the Kubernetes Endpoints\n// API.\nfunc NewServer(\n\taddr string,\n\tconfig Config,\n\tk8sAPI *k8s.API,\n\tmetadataAPI *k8s.MetadataAPI,\n\tclusterStore *watcher.ClusterStore,\n\tshutdown <-chan struct{},\n) (*grpc.Server, error) {\n\tlog := logging.WithFields(logging.Fields{\n\t\t\"addr\":      addr,\n\t\t\"component\": \"server\",\n\t})\n\n\t// Initialize indexers that are used across watchers\n\terr := watcher.InitializeIndexers(k8sAPI)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tworkloads, err := watcher.NewWorkloadWatcher(k8sAPI, metadataAPI, log, config.EnableEndpointSlices, config.DefaultOpaquePorts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tendpoints, err := watcher.NewEndpointsWatcher(k8sAPI, metadataAPI, log, config.EnableEndpointSlices, \"local\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\topaquePorts, err := watcher.NewOpaquePortsWatcher(k8sAPI, log, config.DefaultOpaquePorts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tprofiles, err := watcher.NewProfileWatcher(k8sAPI, log)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfederatedServices, err := newFederatedServiceWatcher(k8sAPI, metadataAPI, &config, clusterStore, endpoints, log)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsrv := server{\n\t\tpb.UnimplementedDestinationServer{},\n\t\tconfig,\n\t\tworkloads,\n\t\tendpoints,\n\t\topaquePorts,\n\t\tprofiles,\n\t\tclusterStore,\n\t\tfederatedServices,\n\t\tk8sAPI,\n\t\tmetadataAPI,\n\t\tlog,\n\t\tshutdown,\n\t}\n\n\ts := prometheus.NewGrpcServer(grpc.MaxConcurrentStreams(0))\n\t// linkerd2-proxy-api/destination.Destination (proxy-facing)\n\tpb.RegisterDestinationServer(s, &srv)\n\treturn s, nil\n}\n\nfunc (s *server) Get(dest *pb.GetDestination, stream pb.Destination_GetServer) error {\n\tlog := s.log\n\n\tclient, _ := peer.FromContext(stream.Context())\n\tif client != nil {\n\t\tlog = log.WithField(\"remote\", client.Addr)\n\t}\n\n\tvar token contextToken\n\tif dest.GetContextToken() != \"\" {\n\t\tlog.Debugf(\"Dest token: %q\", dest.GetContextToken())\n\t\ttoken = s.parseContextToken(dest.GetContextToken())\n\t\tlog = log.WithFields(logging.Fields{\"context-pod\": token.Pod, \"context-ns\": token.Ns})\n\t}\n\n\tlog.Debugf(\"Get %s\", dest.GetPath())\n\n\tstreamEnd := make(chan struct{})\n\t// The host must be fully-qualified or be an IP address.\n\thost, port, err := getHostAndPort(dest.GetPath())\n\tif err != nil {\n\t\tlog.Debugf(\"Invalid service %s\", dest.GetPath())\n\t\treturn status.Errorf(codes.InvalidArgument, \"Invalid authority: %s\", dest.GetPath())\n\t}\n\n\t// Return error for an IP query\n\tif ip := net.ParseIP(host); ip != nil {\n\t\treturn status.Errorf(codes.InvalidArgument, \"IP queries not supported by Get API: host=%s\", host)\n\t}\n\n\tservice, instanceID, err := parseK8sServiceName(host, s.config.ClusterDomain)\n\tif err != nil {\n\t\tlog.Debugf(\"Invalid service %s\", dest.GetPath())\n\t\treturn status.Errorf(codes.InvalidArgument, \"Invalid authority: %s\", dest.GetPath())\n\t}\n\n\tsvc, err := s.k8sAPI.Svc().Lister().Services(service.Namespace).Get(service.Name)\n\tif err != nil {\n\t\tif kerrors.IsNotFound(err) {\n\t\t\tlog.Debugf(\"Service not found %s\", service)\n\t\t\treturn status.Errorf(codes.NotFound, \"Service %s.%s not found\", service.Name, service.Namespace)\n\t\t}\n\t\tlog.Debugf(\"Failed to get service %s: %v\", service, err)\n\t\treturn status.Errorf(codes.Internal, \"Failed to get service %s\", dest.GetPath())\n\t}\n\n\tif isFederatedService(svc) {\n\t\t// Federated service\n\t\tremoteDiscovery := svc.Annotations[labels.RemoteDiscoveryAnnotation]\n\t\tlocalDiscovery := svc.Annotations[labels.LocalDiscoveryAnnotation]\n\t\tlog.Debugf(\"Federated service discovery, remote:[%s] local:[%s]\", remoteDiscovery, localDiscovery)\n\t\terr := s.federatedServices.Subscribe(svc.Name, svc.Namespace, port, token.NodeName, instanceID, stream, streamEnd)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"Failed to subscribe to federated service %q: %s\", dest.GetPath(), err)\n\t\t\treturn err\n\t\t}\n\t\tdefer s.federatedServices.Unsubscribe(svc.Name, svc.Namespace, stream)\n\t} else if cluster, found := svc.Labels[labels.RemoteDiscoveryLabel]; found {\n\t\tlog.Debug(\"Remote discovery service detected\")\n\t\t// Remote discovery\n\t\tremoteSvc, found := svc.Labels[labels.RemoteServiceLabel]\n\t\tif !found {\n\t\t\tlog.Debugf(\"Remote discovery service missing remote service name %s\", service)\n\t\t\treturn status.Errorf(codes.FailedPrecondition, \"Remote discovery service missing remote service name %s\", dest.GetPath())\n\t\t}\n\t\tremoteWatcher, remoteConfig, found := s.clusterStore.Get(cluster)\n\t\tif !found {\n\t\t\tlog.Errorf(\"Failed to get remote cluster %s\", cluster)\n\t\t\treturn status.Errorf(codes.NotFound, \"Remote cluster not found: %s\", cluster)\n\t\t}\n\t\ttranslator, err := newEndpointTranslator(\n\t\t\ts.config.ControllerNS,\n\t\t\tremoteConfig.TrustDomain,\n\t\t\ts.config.ForceOpaqueTransport,\n\t\t\ts.config.EnableH2Upgrade,\n\t\t\tfalse, // Disable endpoint filtering for remote discovery.\n\t\t\ts.config.EnableIPv6,\n\t\t\ts.config.ExtEndpointZoneWeights,\n\t\t\ts.config.MeshedHttp2ClientParams,\n\t\t\tfmt.Sprintf(\"%s.%s.svc.%s:%d\", remoteSvc, service.Namespace, remoteConfig.ClusterDomain, port),\n\t\t\ttoken.NodeName,\n\t\t\ts.config.DefaultOpaquePorts,\n\t\t\ts.metadataAPI,\n\t\t\tstream,\n\t\t\tstreamEnd,\n\t\t\tlog,\n\t\t\ts.config.StreamQueueCapacity,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn status.Errorf(codes.Internal, \"Failed to create endpoint translator: %s\", err)\n\t\t}\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\terr = remoteWatcher.Subscribe(watcher.ServiceID{Namespace: service.Namespace, Name: remoteSvc}, port, instanceID, translator)\n\t\tif err != nil {\n\t\t\tvar ise watcher.InvalidService\n\t\t\tif errors.As(err, &ise) {\n\t\t\t\tlog.Debugf(\"Invalid remote discovery service %s\", dest.GetPath())\n\t\t\t\treturn status.Errorf(codes.InvalidArgument, \"Invalid authority: %s\", dest.GetPath())\n\t\t\t}\n\t\t\tlog.Errorf(\"Failed to subscribe to remote discovery service %q in cluster %s: %s\", dest.GetPath(), cluster, err)\n\t\t\treturn err\n\t\t}\n\t\tdefer remoteWatcher.Unsubscribe(watcher.ServiceID{Namespace: service.Namespace, Name: remoteSvc}, port, instanceID, translator)\n\n\t} else {\n\t\tlog.Debug(\"Local discovery service detected\")\n\t\t// Local discovery\n\t\ttranslator, err := newEndpointTranslator(\n\t\t\ts.config.ControllerNS,\n\t\t\ts.config.IdentityTrustDomain,\n\t\t\ts.config.ForceOpaqueTransport,\n\t\t\ts.config.EnableH2Upgrade,\n\t\t\ttrue,\n\t\t\ts.config.EnableIPv6,\n\t\t\ts.config.ExtEndpointZoneWeights,\n\t\t\ts.config.MeshedHttp2ClientParams,\n\t\t\tdest.GetPath(),\n\t\t\ttoken.NodeName,\n\t\t\ts.config.DefaultOpaquePorts,\n\t\t\ts.metadataAPI,\n\t\t\tstream,\n\t\t\tstreamEnd,\n\t\t\tlog,\n\t\t\ts.config.StreamQueueCapacity,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn status.Errorf(codes.Internal, \"Failed to create endpoint translator: %s\", err)\n\t\t}\n\t\ttranslator.Start()\n\t\tdefer translator.Stop()\n\n\t\terr = s.endpoints.Subscribe(service, port, instanceID, translator)\n\t\tif err != nil {\n\t\t\tvar ise watcher.InvalidService\n\t\t\tif errors.As(err, &ise) {\n\t\t\t\tlog.Debugf(\"Invalid service %s\", dest.GetPath())\n\t\t\t\treturn status.Errorf(codes.InvalidArgument, \"Invalid authority: %s\", dest.GetPath())\n\t\t\t}\n\t\t\tlog.Errorf(\"Failed to subscribe to %s: %s\", dest.GetPath(), err)\n\t\t\treturn err\n\t\t}\n\t\tdefer s.endpoints.Unsubscribe(service, port, instanceID, translator)\n\t}\n\n\tselect {\n\tcase <-s.shutdown:\n\tcase <-stream.Context().Done():\n\t\tlog.Debugf(\"Get %s cancelled\", dest.GetPath())\n\tcase <-streamEnd:\n\t\tlog.Errorf(\"Get %s stream aborted\", dest.GetPath())\n\t}\n\n\treturn nil\n}\n\nfunc (s *server) GetProfile(dest *pb.GetDestination, stream pb.Destination_GetProfileServer) error {\n\tlog := s.log\n\n\tclient, _ := peer.FromContext(stream.Context())\n\tif client != nil {\n\t\tlog = log.WithField(\"remote\", client.Addr)\n\t}\n\n\tvar token contextToken\n\tif dest.GetContextToken() != \"\" {\n\t\tlog.Debugf(\"Dest token: %q\", dest.GetContextToken())\n\t\ttoken = s.parseContextToken(dest.GetContextToken())\n\t\tlog = log.WithFields(logging.Fields{\"context-pod\": token.Pod, \"context-ns\": token.Ns})\n\t}\n\n\tlog.Debugf(\"Getting profile for %s\", dest.GetPath())\n\n\t// The host must be fully-qualified or be an IP address.\n\thost, port, err := getHostAndPort(dest.GetPath())\n\tif err != nil {\n\t\tlog.Debugf(\"Invalid address %q\", dest.GetPath())\n\t\treturn status.Errorf(codes.InvalidArgument, \"invalid authority: %q: %q\", dest.GetPath(), err)\n\t}\n\n\tif ip := net.ParseIP(host); ip != nil {\n\t\terr = s.getProfileByIP(token, ip, port, log, stream)\n\t\tif err != nil {\n\t\t\tvar ise watcher.InvalidService\n\t\t\tif errors.As(err, &ise) {\n\t\t\t\tlog.Debugf(\"Invalid service %s\", dest.GetPath())\n\t\t\t\treturn status.Errorf(codes.InvalidArgument, \"Invalid authority: %s\", dest.GetPath())\n\t\t\t}\n\t\t\tlog.Errorf(\"Failed to subscribe to profile by ip %q: %q\", dest.GetPath(), err)\n\t\t}\n\t\treturn err\n\t}\n\n\terr = s.getProfileByName(token, host, port, log, stream)\n\tif err != nil {\n\t\tvar ise watcher.InvalidService\n\t\tif errors.As(err, &ise) {\n\t\t\tlog.Debugf(\"Invalid service %s\", dest.GetPath())\n\t\t\treturn status.Errorf(codes.InvalidArgument, \"Invalid authority: %s\", dest.GetPath())\n\t\t}\n\t\tlog.Errorf(\"Failed to subscribe to profile by name %q: %q\", dest.GetPath(), err)\n\t}\n\treturn err\n}\n\nfunc (s *server) getProfileByIP(\n\ttoken contextToken,\n\tip net.IP,\n\tport uint32,\n\tlog *logging.Entry,\n\tstream pb.Destination_GetProfileServer,\n) error {\n\t// Get the service that the IP currently maps to.\n\tsvcID, err := getSvcID(s.k8sAPI, ip.String(), s.log)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif svcID == nil {\n\t\treturn s.subscribeToEndpointProfile(nil, \"\", ip.String(), port, log, stream)\n\t}\n\n\tfqn := fmt.Sprintf(\"%s.%s.svc.%s\", svcID.Name, svcID.Namespace, s.config.ClusterDomain)\n\treturn s.subscribeToServiceProfile(*svcID, token, fqn, port, log, stream)\n}\n\nfunc (s *server) getProfileByName(\n\ttoken contextToken,\n\thost string,\n\tport uint32,\n\tlog *logging.Entry,\n\tstream pb.Destination_GetProfileServer,\n) error {\n\tservice, hostname, err := parseK8sServiceName(host, s.config.ClusterDomain)\n\tif err != nil {\n\t\ts.log.Debugf(\"Invalid service %s\", host)\n\t\treturn status.Errorf(codes.InvalidArgument, \"invalid service %q: %q\", host, err)\n\t}\n\n\t// If the pod name (instance ID) is not empty, it means we parsed a DNS\n\t// name. When we fetch the profile using a pod's DNS name, we want to\n\t// return an endpoint in the profile response.\n\tif hostname != \"\" {\n\t\treturn s.subscribeToEndpointProfile(&service, hostname, \"\", port, log, stream)\n\t}\n\n\treturn s.subscribeToServiceProfile(service, token, host, port, log, stream)\n}\n\n// Resolves a profile for a service, sending updates to the provided stream.\n//\n// This function does not return until the stream is closed.\nfunc (s *server) subscribeToServiceProfile(\n\tservice watcher.ID,\n\ttoken contextToken,\n\tfqn string,\n\tport uint32,\n\tlog *logging.Entry,\n\tstream pb.Destination_GetProfileServer,\n) error {\n\tlog = log.\n\t\tWithField(\"ns\", service.Namespace).\n\t\tWithField(\"svc\", service.Name).\n\t\tWithField(\"port\", port)\n\n\tcanceled := stream.Context().Done()\n\tstreamEnd := make(chan struct{})\n\n\t// We build up the pipeline of profile updaters backwards, starting from\n\t// the translator which takes profile updates, translates them to protobuf\n\t// and pushes them onto the gRPC stream.\n\ttranslator, err := newProfileTranslatorWithCapacity(service, stream, log, fqn, port, streamEnd, s.config.StreamQueueCapacity)\n\tif err != nil {\n\t\treturn status.Errorf(codes.Internal, \"Failed to create profile translator: %s\", err)\n\t}\n\ttranslator.Start()\n\tdefer translator.Stop()\n\n\t// The opaque ports adaptor merges profile updates with service opaque\n\t// port annotation updates; it then publishes the result to the traffic\n\t// split adaptor.\n\topaquePortsAdaptor := newOpaquePortsAdaptor(translator)\n\n\t// Create an adaptor that merges service-level opaque port configurations\n\t// onto profile updates.\n\terr = s.opaquePorts.Subscribe(service, opaquePortsAdaptor)\n\tif err != nil {\n\t\tlog.Warnf(\"Failed to subscribe to service updates for %s: %s\", service, err)\n\t\treturn err\n\t}\n\tdefer s.opaquePorts.Unsubscribe(service, opaquePortsAdaptor)\n\n\t// Ensure that (1) nil values are turned into a default policy and (2)\n\t// subsequent updates that refer to same service profile object are\n\t// deduplicated to prevent sending redundant updates.\n\tdup := newDedupProfileListener(opaquePortsAdaptor, log)\n\tdefaultProfile := sp.ServiceProfile{}\n\tlistener := newDefaultProfileListener(&defaultProfile, dup, log)\n\n\t// The primary lookup uses the context token to determine the requester's\n\t// namespace. If there's no namespace in the token, start a single\n\t// subscription.\n\tif token.Ns == \"\" {\n\t\treturn s.subscribeToServiceWithoutContext(fqn, listener, canceled, log, streamEnd)\n\t}\n\treturn s.subscribeToServicesWithContext(fqn, token, listener, canceled, log, streamEnd)\n}\n\n// subscribeToServicesWithContext establishes two profile watches: a \"backup\"\n// watch (ignoring the client namespace) and a preferred \"primary\" watch\n// assuming the client's context. Once updates are received for both watches, we\n// select over both watches to send profile updates to the stream. A nil update\n// may be sent if both the primary and backup watches are initialized with a nil\n// value.\nfunc (s *server) subscribeToServicesWithContext(\n\tfqn string,\n\ttoken contextToken,\n\tlistener watcher.ProfileUpdateListener,\n\tcanceled <-chan struct{},\n\tlog *logging.Entry,\n\tstreamEnd <-chan struct{},\n) error {\n\t// We ned to support two subscriptions:\n\t// - First, a backup subscription that assumes the context of the server\n\t//   namespace.\n\t// - And then, a primary subscription that assumes the context of the client\n\t//   namespace.\n\tprimary, backup := newFallbackProfileListener(listener, log)\n\n\t// The backup lookup ignores the context token to lookup any\n\t// server-namespace-hosted profiles.\n\tbackupID, err := profileID(fqn, contextToken{}, s.config.ClusterDomain)\n\tif err != nil {\n\t\tlog.Debug(\"Invalid service\")\n\t\treturn status.Errorf(codes.InvalidArgument, \"invalid profile ID: %s\", err)\n\t}\n\terr = s.profiles.Subscribe(backupID, backup)\n\tif err != nil {\n\t\tlog.Warnf(\"Failed to subscribe to profile: %s\", err)\n\t\treturn err\n\t}\n\tdefer s.profiles.Unsubscribe(backupID, backup)\n\n\tprimaryID, err := profileID(fqn, token, s.config.ClusterDomain)\n\tif err != nil {\n\t\tlog.Debug(\"Invalid service\")\n\t\treturn status.Errorf(codes.InvalidArgument, \"invalid profile ID: %s\", err)\n\t}\n\terr = s.profiles.Subscribe(primaryID, primary)\n\tif err != nil {\n\t\tlog.Warnf(\"Failed to subscribe to profile: %s\", err)\n\t\treturn err\n\t}\n\tdefer s.profiles.Unsubscribe(primaryID, primary)\n\n\tselect {\n\tcase <-s.shutdown:\n\tcase <-canceled:\n\t\tlog.Debugf(\"GetProfile %s cancelled\", fqn)\n\tcase <-streamEnd:\n\t\tlog.Errorf(\"GetProfile %s stream aborted\", fqn)\n\t}\n\treturn nil\n}\n\n// subscribeToServiceWithoutContext establishes a single profile watch, assuming\n// no client context. All udpates are published to the provided listener.\nfunc (s *server) subscribeToServiceWithoutContext(\n\tfqn string,\n\tlistener watcher.ProfileUpdateListener,\n\tcanceled <-chan struct{},\n\tlog *logging.Entry,\n\tstreamEnd <-chan struct{},\n) error {\n\tid, err := profileID(fqn, contextToken{}, s.config.ClusterDomain)\n\tif err != nil {\n\t\tlog.Debug(\"Invalid service\")\n\t\treturn status.Errorf(codes.InvalidArgument, \"invalid profile ID: %s\", err)\n\t}\n\terr = s.profiles.Subscribe(id, listener)\n\tif err != nil {\n\t\tlog.Warnf(\"Failed to subscribe to profile: %s\", err)\n\t\treturn err\n\t}\n\tdefer s.profiles.Unsubscribe(id, listener)\n\n\tselect {\n\tcase <-s.shutdown:\n\tcase <-canceled:\n\t\tlog.Debugf(\"GetProfile %s cancelled\", fqn)\n\tcase <-streamEnd:\n\t\tlog.Errorf(\"GetProfile %s stream aborted\", fqn)\n\t}\n\treturn nil\n}\n\n// Resolves a profile for a single endpoint, sending updates to the provided\n// stream.\n//\n// This function does not return until the stream is closed.\nfunc (s *server) subscribeToEndpointProfile(\n\tservice *watcher.ServiceID,\n\thostname,\n\tip string,\n\tport uint32,\n\tlog *logging.Entry,\n\tstream pb.Destination_GetProfileServer,\n) error {\n\tcanceled := stream.Context().Done()\n\tstreamEnd := make(chan struct{})\n\ttranslator := newEndpointProfileTranslator(\n\t\ts.config.ForceOpaqueTransport,\n\t\ts.config.EnableH2Upgrade,\n\t\ts.config.ControllerNS,\n\t\ts.config.IdentityTrustDomain,\n\t\ts.config.DefaultOpaquePorts,\n\t\ts.config.MeshedHttp2ClientParams,\n\t\tstream,\n\t\tstreamEnd,\n\t\tlog,\n\t\ts.config.StreamQueueCapacity,\n\t)\n\ttranslator.Start()\n\tdefer translator.Stop()\n\n\tvar err error\n\tip, err = s.workloads.Subscribe(service, hostname, ip, port, translator)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer s.workloads.Unsubscribe(ip, port, translator)\n\n\tselect {\n\tcase <-s.shutdown:\n\tcase <-canceled:\n\t\ts.log.Debugf(\"Cancelled\")\n\tcase <-streamEnd:\n\t\tlog.Errorf(\"GetProfile %s:%d stream aborted\", ip, port)\n\t}\n\treturn nil\n}\n\n// getSvcID returns the service that corresponds to a Cluster IP address if one\n// exists.\nfunc getSvcID(k8sAPI *k8s.API, clusterIP string, log *logging.Entry) (*watcher.ServiceID, error) {\n\tobjs, err := k8sAPI.Svc().Informer().GetIndexer().ByIndex(watcher.PodIPIndex, clusterIP)\n\tif err != nil {\n\t\treturn nil, status.Error(codes.Unknown, err.Error())\n\t}\n\tservices := make([]*corev1.Service, 0)\n\tfor _, obj := range objs {\n\t\tservice := obj.(*corev1.Service)\n\t\tservices = append(services, service)\n\t}\n\tif len(services) > 1 {\n\t\tconflictingServices := []string{}\n\t\tfor _, service := range services {\n\t\t\tconflictingServices = append(conflictingServices, fmt.Sprintf(\"%s:%s\", service.Namespace, service.Name))\n\t\t}\n\t\tlog.Warnf(\"found conflicting %s cluster IP: %s\", clusterIP, strings.Join(conflictingServices, \",\"))\n\t\treturn nil, status.Errorf(codes.FailedPrecondition, \"found %d services with conflicting cluster IP %s\", len(services), clusterIP)\n\t}\n\tif len(services) == 0 {\n\t\treturn nil, nil\n\t}\n\tservice := &watcher.ServiceID{\n\t\tNamespace: services[0].Namespace,\n\t\tName:      services[0].Name,\n\t}\n\treturn service, nil\n}\n\n////////////\n/// util ///\n////////////\n\ntype contextToken struct {\n\tNs       string `json:\"ns,omitempty\"`\n\tNodeName string `json:\"nodeName,omitempty\"`\n\tPod      string `json:\"pod,omitempty\"`\n}\n\nfunc (s *server) parseContextToken(token string) contextToken {\n\tctxToken := contextToken{}\n\tif token == \"\" {\n\t\treturn ctxToken\n\t}\n\tif err := json.Unmarshal([]byte(token), &ctxToken); err != nil {\n\t\t// if json is invalid, means token can have ns:<namespace> form\n\t\tparts := strings.Split(token, \":\")\n\t\tif len(parts) == 2 && parts[0] == \"ns\" {\n\t\t\ts.log.Warnf(\"context token %s using old token format\", token)\n\t\t\tctxToken = contextToken{\n\t\t\t\tNs: parts[1],\n\t\t\t}\n\t\t} else {\n\t\t\ts.log.Errorf(\"context token %s is invalid: %s\", token, err)\n\t\t}\n\t}\n\treturn ctxToken\n}\n\nfunc profileID(authority string, ctxToken contextToken, clusterDomain string) (watcher.ProfileID, error) {\n\thost, _, err := getHostAndPort(authority)\n\tif err != nil {\n\t\treturn watcher.ProfileID{}, fmt.Errorf(\"invalid authority: %w\", err)\n\t}\n\tservice, _, err := parseK8sServiceName(host, clusterDomain)\n\tif err != nil {\n\t\treturn watcher.ProfileID{}, fmt.Errorf(\"invalid k8s service name: %w\", err)\n\t}\n\tid := watcher.ProfileID{\n\t\tName:      fmt.Sprintf(\"%s.%s.svc.%s\", service.Name, service.Namespace, clusterDomain),\n\t\tNamespace: service.Namespace,\n\t}\n\tif ctxToken.Ns != \"\" {\n\t\tid.Namespace = ctxToken.Ns\n\t}\n\treturn id, nil\n}\n\nfunc getHostAndPort(authority string) (string, watcher.Port, error) {\n\tif !strings.Contains(authority, \":\") {\n\t\treturn authority, watcher.Port(80), nil\n\t}\n\n\thost, sport, err := net.SplitHostPort(authority)\n\tif err != nil {\n\t\treturn \"\", 0, fmt.Errorf(\"invalid destination: %w\", err)\n\t}\n\tport, err := strconv.Atoi(sport)\n\tif err != nil {\n\t\treturn \"\", 0, fmt.Errorf(\"invalid port %s: %w\", sport, err)\n\t}\n\tif port <= 0 || port > 65535 {\n\t\treturn \"\", 0, fmt.Errorf(\"invalid port %d\", port)\n\t}\n\treturn host, watcher.Port(port), nil\n}\n\ntype instanceID = string\n\n// parseK8sServiceName is a utility that destructures a Kubernetes service hostname into its constituent components.\n//\n// If the authority does not represent a Kubernetes service, an error is returned.\n//\n// If the hostname is a pod DNS name, then the pod's name (instanceID) is returned\n// as well. See https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/.\nfunc parseK8sServiceName(fqdn, clusterDomain string) (watcher.ServiceID, instanceID, error) {\n\tlabels := strings.Split(fqdn, \".\")\n\tsuffix := append([]string{\"svc\"}, strings.Split(clusterDomain, \".\")...)\n\n\tif !hasSuffix(labels, suffix) {\n\t\treturn watcher.ServiceID{}, \"\", fmt.Errorf(\"name %s does not match cluster domain %s\", fqdn, clusterDomain)\n\t}\n\n\tn := len(labels)\n\tif n == 2+len(suffix) {\n\t\t// <service>.<namespace>.<suffix>\n\t\tservice := watcher.ServiceID{\n\t\t\tName:      labels[0],\n\t\t\tNamespace: labels[1],\n\t\t}\n\t\treturn service, \"\", nil\n\t}\n\n\tif n == 3+len(suffix) {\n\t\t// <instance-id>.<service>.<namespace>.<suffix>\n\t\tinstanceID := labels[0]\n\t\tservice := watcher.ServiceID{\n\t\t\tName:      labels[1],\n\t\t\tNamespace: labels[2],\n\t\t}\n\t\treturn service, instanceID, nil\n\t}\n\n\treturn watcher.ServiceID{}, \"\", fmt.Errorf(\"invalid k8s service %s\", fqdn)\n}\n\nfunc hasSuffix(slice []string, suffix []string) bool {\n\tif len(slice) < len(suffix) {\n\t\treturn false\n\t}\n\tfor i, s := range slice[len(slice)-len(suffix):] {\n\t\tif s != suffix[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc getPodSkippedInboundPortsAnnotations(pod *corev1.Pod) map[uint32]struct{} {\n\tannotation, ok := pod.Annotations[labels.ProxyIgnoreInboundPortsAnnotation]\n\tif !ok || annotation == \"\" {\n\t\treturn nil\n\t}\n\n\treturn util.ParsePorts(annotation)\n}\n"
  },
  {
    "path": "controller/api/destination/server_ipv6_test.go",
    "content": "package destination\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2/controller/api/util\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tdiscovery \"k8s.io/api/discovery/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n)\n\nfunc TestIPv6(t *testing.T) {\n\tport := int32(port)\n\tprotocol := corev1.ProtocolTCP\n\n\tserver := makeServer(t)\n\n\tstream := &bufferingGetStream{\n\t\tupdates:          make(chan *pb.Update, 50),\n\t\tMockServerStream: util.NewMockServerStream(),\n\t}\n\tdefer stream.Cancel()\n\n\tt.Run(\"Return only IPv6 endpoint for dual-stack service\", func(t *testing.T) {\n\t\ttestReturnEndpointsForServer(t, server, stream, fullyQualifiedNameDual, podIPv6Dual, uint32(port))\n\t})\n\n\tt.Run(\"Returns only IPv4 endpoint when service becomes single-stack IPv4\", func(t *testing.T) {\n\t\tpatch := []byte(`{\"spec\":{\"clusterIPs\": [\"172.17.13.0\"], \"ipFamilies\":[\"IPv4\"]}}`)\n\t\t_, err := server.k8sAPI.Client.CoreV1().Services(\"ns\").Patch(context.Background(), \"name-ds\", types.MergePatchType, patch, metav1.PatchOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed patching name-ds service: %s\", err)\n\t\t}\n\t\tif err = server.k8sAPI.Client.DiscoveryV1().EndpointSlices(\"ns\").Delete(context.Background(), \"name-ds-ipv6\", metav1.DeleteOptions{}); err != nil {\n\t\t\tt.Fatalf(\"Failed deleting name-ds-ipv6 ES: %s\", err)\n\t\t}\n\n\t\tupdate := <-stream.updates\n\t\tif updateAddAddress(t, update)[0] != \"172.17.0.19:8989\" {\n\t\t\tt.Fatalf(\"Expected %s but got %s\", \"172.17.0.19:8989\", updateAddAddress(t, update)[0])\n\t\t}\n\n\t\tupdate = <-stream.updates\n\t\tif updateRemoveAddress(t, update)[0] != \"[2001:db8::94]:8989\" {\n\t\t\tt.Fatalf(\"Expected %s but got %s\", \"[2001:db8::94]:8989\", updateRemoveAddress(t, update)[0])\n\t\t}\n\t})\n\n\tt.Run(\"Returns only IPv6 endpoint when service becomes dual-stack again\", func(t *testing.T) {\n\t\t// We patch the service to become dual-stack again and we add the IPv6\n\t\t// ES. We should receive the events for the removal of the IPv4 ES and\n\t\t// the addition of the IPv6 one.\n\t\tpatch := []byte(`{\"spec\":{\"clusterIPs\": [\"172.17.13.0\",\"2001:db8::88\"], \"ipFamilies\":[\"IPv4\",\"IPv6\"]}}`)\n\t\t_, err := server.k8sAPI.Client.CoreV1().Services(\"ns\").Patch(context.Background(), \"name-ds\", types.MergePatchType, patch, metav1.PatchOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed patching name-ds service: %s\", err)\n\t\t}\n\n\t\tes := &discovery.EndpointSlice{\n\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\tKind:       \"EndpointSlice\",\n\t\t\t\tAPIVersion: \"discovery.k8s.io/v1\",\n\t\t\t},\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"name-ds-ipv6\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"kubernetes.io/service-name\": \"name-ds\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tAddressType: discovery.AddressTypeIPv6,\n\t\t\tPorts: []discovery.EndpointPort{\n\t\t\t\t{\n\t\t\t\t\tPort:     &port,\n\t\t\t\t\tProtocol: &protocol,\n\t\t\t\t},\n\t\t\t},\n\t\t\tEndpoints: []discovery.Endpoint{\n\t\t\t\t{\n\t\t\t\t\tAddresses: []string{\"2001:db8::94\"},\n\t\t\t\t\tTargetRef: &corev1.ObjectReference{\n\t\t\t\t\t\tKind:      \"Pod\",\n\t\t\t\t\t\tNamespace: \"ns\",\n\t\t\t\t\t\tName:      \"name-ds\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tif _, err := server.k8sAPI.Client.DiscoveryV1().EndpointSlices(\"ns\").Create(context.Background(), es, metav1.CreateOptions{}); err != nil {\n\t\t\tt.Fatalf(\"Failed creating name-ds-ipv6 ES: %s\", err)\n\t\t}\n\n\t\tupdate := <-stream.updates\n\t\tif updateAddAddress(t, update)[0] != \"[2001:db8::94]:8989\" {\n\t\t\tt.Fatalf(\"Expected %s but got %s\", \"[2001:db8::94]:8989\", updateAddAddress(t, update)[0])\n\t\t}\n\n\t\tupdate = <-stream.updates\n\t\tif updateRemoveAddress(t, update)[0] != \"172.17.0.19:8989\" {\n\t\t\tt.Fatalf(\"Expected %s but got %s\", \"172.17.0.19:8989\", updateRemoveAddress(t, update)[0])\n\t\t}\n\t})\n\n\tt.Run(\"Doesn't return anything when adding an IPv4 to the dual-stack service\", func(t *testing.T) {\n\t\tes := &discovery.EndpointSlice{\n\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\tKind:       \"EndpointSlice\",\n\t\t\t\tAPIVersion: \"discovery.k8s.io/v1\",\n\t\t\t},\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"name-ds-ipv4-2\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"kubernetes.io/service-name\": \"name-ds\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tAddressType: discovery.AddressTypeIPv4,\n\t\t\tPorts: []discovery.EndpointPort{\n\t\t\t\t{\n\t\t\t\t\tPort:     &port,\n\t\t\t\t\tProtocol: &protocol,\n\t\t\t\t},\n\t\t\t},\n\t\t\tEndpoints: []discovery.Endpoint{\n\t\t\t\t{\n\t\t\t\t\tAddresses: []string{\"172.17.0.20\"},\n\t\t\t\t\tTargetRef: &corev1.ObjectReference{\n\t\t\t\t\t\tKind:      \"Pod\",\n\t\t\t\t\t\tNamespace: \"ns\",\n\t\t\t\t\t\tName:      \"name-ds\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tif _, err := server.k8sAPI.Client.DiscoveryV1().EndpointSlices(\"ns\").Create(context.Background(), es, metav1.CreateOptions{}); err != nil {\n\t\t\tt.Fatalf(\"Failed creating name-ds-ipv4-2 ES: %s\", err)\n\t\t}\n\n\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\tif len(stream.updates) != 0 {\n\t\t\tt.Fatalf(\"Expected no events but got %#v\", stream.updates)\n\t\t}\n\t})\n\n\tt.Run(\"Doesn't return anything when removing an IPv4 ES from the dual-stack service\", func(t *testing.T) {\n\t\tif err := server.k8sAPI.Client.DiscoveryV1().EndpointSlices(\"ns\").Delete(context.Background(), \"name-ds-ipv4-2\", metav1.DeleteOptions{}); err != nil {\n\t\t\tt.Fatalf(\"Failed deleting name-ds-ipv4-2 ES: %s\", err)\n\t\t}\n\n\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\tif len(stream.updates) != 0 {\n\t\t\tt.Fatalf(\"Expected no events but got %#v\", stream.updates)\n\t\t}\n\t})\n\n\tt.Run(\"Doesn't return anything when the service becomes single-stack IPv6\", func(t *testing.T) {\n\t\tpatch := []byte(`{\"spec\":{\"clusterIPs\": [\"2001:db8::88\"], \"ipFamilies\":[\"IPv6\"]}}`)\n\t\t_, err := server.k8sAPI.Client.CoreV1().Services(\"ns\").Patch(context.Background(), \"name-ds\", types.MergePatchType, patch, metav1.PatchOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed patching name-ds service: %s\", err)\n\t\t}\n\t\tif err := server.k8sAPI.Client.DiscoveryV1().EndpointSlices(\"ns\").Delete(context.Background(), \"name-ds-ipv4\", metav1.DeleteOptions{}); err != nil {\n\t\t\tt.Fatalf(\"Failed deleting name-ds-ipv4 ES: %s\", err)\n\t\t}\n\n\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\tif len(stream.updates) != 0 {\n\t\t\tt.Fatalf(\"Expected no events but got %#v\", stream.updates)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "controller/api/destination/server_test.go",
    "content": "package destination\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tgonet \"net\"\n\t\"net/netip\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/golang/protobuf/ptypes/duration\"\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2-proxy-api/go/net\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\t\"github.com/linkerd/linkerd2/controller/api/util\"\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta3\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/addr\"\n\tpkgk8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n\tlogging \"github.com/sirupsen/logrus\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n)\n\nconst fullyQualifiedName = \"name1.ns.svc.mycluster.local\"\nconst fullyQualifiedNameIPv6 = \"name-ipv6.ns.svc.mycluster.local\"\nconst fullyQualifiedNameDual = \"name-ds.ns.svc.mycluster.local\"\nconst fullyQualifiedNameOpaque = \"name3.ns.svc.mycluster.local\"\nconst fullyQualifiedNameOpaqueService = \"name4.ns.svc.mycluster.local\"\nconst fullyQualifiedNameSkipped = \"name5.ns.svc.mycluster.local\"\nconst fullyQualifiedPodDNS = \"pod-0.statefulset-svc.ns.svc.mycluster.local\"\nconst clusterIP = \"172.17.12.0\"\nconst clusterIPv6 = \"2001:db8::88\"\nconst clusterIPOpaque = \"172.17.12.1\"\nconst podIP1 = \"172.17.0.12\"\nconst podIP1v6 = \"2001:db8::68\"\nconst podIPv6Dual = \"2001:db8::94\"\nconst podIP2 = \"172.17.0.13\"\nconst podIP3 = \"172.17.0.20\"\nconst podIP4 = \"172.17.0.21\"\nconst podIPOpaque = \"172.17.0.14\"\nconst podIPSkipped = \"172.17.0.15\"\nconst podIPPolicy = \"172.17.0.16\"\nconst podIPStatefulSet = \"172.17.13.15\"\nconst externalIP = \"192.168.1.20\"\nconst externalIPv6 = \"2001:db8::78\"\nconst externalWorkloadIP = \"200.1.1.1\"\nconst externalWorkloadIPPolicy = \"200.1.1.2\"\nconst port uint32 = 8989\nconst linkerdAdminPort uint32 = 4191\nconst opaquePort uint32 = 4242\nconst skippedPort uint32 = 24224\n\nfunc TestGet(t *testing.T) {\n\tt.Run(\"Returns error if not valid service name\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := &bufferingGetStream{\n\t\t\tupdates:          make(chan *pb.Update, 50),\n\t\t\tMockServerStream: util.NewMockServerStream(),\n\t\t}\n\n\t\terr := server.Get(&pb.GetDestination{Scheme: \"k8s\", Path: \"linkerd.io\"}, stream)\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"Expecting error, got nothing\")\n\t\t}\n\t})\n\n\tt.Run(\"Returns InvalidArgument for ExternalName service\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := &bufferingGetStream{\n\t\t\tupdates:          make(chan *pb.Update, 50),\n\t\t\tMockServerStream: util.NewMockServerStream(),\n\t\t}\n\n\t\terr := server.Get(&pb.GetDestination{Scheme: \"k8s\", Path: \"externalname.ns.svc.cluster.local\"}, stream)\n\n\t\tcode := status.Code(err)\n\t\tif code != codes.InvalidArgument {\n\t\t\tt.Fatalf(\"Expected InvalidArgument, got %s\", code)\n\t\t}\n\t})\n\n\tt.Run(\"Returns endpoints (IPv4)\", func(t *testing.T) {\n\t\ttestReturnEndpoints(t, fullyQualifiedName, podIP1, port)\n\t})\n\n\tt.Run(\"Returns endpoints (IPv6)\", func(t *testing.T) {\n\t\ttestReturnEndpoints(t, fullyQualifiedNameIPv6, podIP1v6, port)\n\t})\n\n\tt.Run(\"Returns endpoints (dual-stack)\", func(t *testing.T) {\n\t\ttestReturnEndpoints(t, fullyQualifiedNameDual, podIPv6Dual, port)\n\t})\n\n\tt.Run(\"Sets meshed HTTP/2 client params\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\t\thttp2Params := pb.Http2ClientParams{\n\t\t\tKeepAlive: &pb.Http2ClientParams_KeepAlive{\n\t\t\t\tTimeout:  &duration.Duration{Seconds: 10},\n\t\t\t\tInterval: &duration.Duration{Seconds: 20},\n\t\t\t},\n\t\t}\n\t\tserver.config.MeshedHttp2ClientParams = &http2Params\n\n\t\tstream := &bufferingGetStream{\n\t\t\tupdates:          make(chan *pb.Update, 50),\n\t\t\tMockServerStream: util.NewMockServerStream(),\n\t\t}\n\t\tdefer stream.Cancel()\n\t\terrs := make(chan error)\n\n\t\t// server.Get blocks until the grpc stream is complete so we call it\n\t\t// in a goroutine and watch stream.updates for updates.\n\t\tgo func() {\n\t\t\terr := server.Get(&pb.GetDestination{Scheme: \"k8s\", Path: fmt.Sprintf(\"%s:%d\", fullyQualifiedName, port)}, stream)\n\t\t\tif err != nil {\n\t\t\t\terrs <- err\n\t\t\t}\n\t\t}()\n\n\t\tselect {\n\t\tcase update := <-stream.updates:\n\t\t\tadd, ok := update.GetUpdate().(*pb.Update_Add)\n\t\t\tif !ok {\n\t\t\t\tt.Fatalf(\"Update expected to be an add, but was %+v\", update)\n\t\t\t}\n\t\t\taddr := add.Add.Addrs[0]\n\t\t\tif !reflect.DeepEqual(addr.GetHttp2(), &http2Params) {\n\t\t\t\tt.Fatalf(\"Expected HTTP/2 client params to be %v, but got %v\", &http2Params, addr.GetHttp2())\n\t\t\t}\n\t\tcase err := <-errs:\n\t\t\tt.Fatalf(\"Got error: %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"Does not set unmeshed HTTP/2 client params\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\t\thttp2Params := pb.Http2ClientParams{\n\t\t\tKeepAlive: &pb.Http2ClientParams_KeepAlive{\n\t\t\t\tTimeout:  &duration.Duration{Seconds: 10},\n\t\t\t\tInterval: &duration.Duration{Seconds: 20},\n\t\t\t},\n\t\t}\n\t\tserver.config.MeshedHttp2ClientParams = &http2Params\n\n\t\tstream := &bufferingGetStream{\n\t\t\tupdates:          make(chan *pb.Update, 50),\n\t\t\tMockServerStream: util.NewMockServerStream(),\n\t\t}\n\t\tdefer stream.Cancel()\n\t\terrs := make(chan error)\n\n\t\t// server.Get blocks until the grpc stream is complete so we call it\n\t\t// in a goroutine and watch stream.updates for updates.\n\t\tgo func() {\n\t\t\terr := server.Get(&pb.GetDestination{Scheme: \"k8s\", Path: fmt.Sprintf(\"%s:%d\", \"name2.ns.svc.mycluster.local\", port)}, stream)\n\t\t\tif err != nil {\n\t\t\t\terrs <- err\n\t\t\t}\n\t\t}()\n\n\t\tselect {\n\t\tcase update := <-stream.updates:\n\t\t\tadd, ok := update.GetUpdate().(*pb.Update_Add)\n\t\t\tif !ok {\n\t\t\t\tt.Fatalf(\"Update expected to be an add, but was %+v\", update)\n\t\t\t}\n\t\t\taddr := add.Add.Addrs[0]\n\t\t\tif addr.GetHttp2() != nil {\n\t\t\t\tt.Fatalf(\"Expected HTTP/2 client params to be nil, but got %v\", addr.GetHttp2())\n\t\t\t}\n\t\tcase err := <-errs:\n\t\t\tt.Fatalf(\"Got error: %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"Return endpoint with unknown protocol hint and identity when service name contains skipped inbound port\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := &bufferingGetStream{\n\t\t\tupdates:          make(chan *pb.Update, 50),\n\t\t\tMockServerStream: util.NewMockServerStream(),\n\t\t}\n\t\tdefer stream.Cancel()\n\t\terrs := make(chan error)\n\n\t\tpath := fmt.Sprintf(\"%s:%d\", fullyQualifiedNameSkipped, skippedPort)\n\n\t\t// server.Get blocks until the grpc stream is complete so we call it\n\t\t// in a goroutine and watch stream.updates for updates.\n\t\tgo func() {\n\t\t\terr := server.Get(&pb.GetDestination{\n\t\t\t\tScheme: \"k8s\",\n\t\t\t\tPath:   path,\n\t\t\t}, stream)\n\t\t\tif err != nil {\n\t\t\t\terrs <- err\n\t\t\t}\n\t\t}()\n\n\t\tselect {\n\t\tcase update := <-stream.updates:\n\t\t\taddrs := update.GetAdd().Addrs\n\t\t\tif len(addrs) == 0 {\n\t\t\t\tt.Fatalf(\"Expected len(addrs) to be > 0\")\n\t\t\t}\n\n\t\t\tif addrs[0].GetProtocolHint().GetProtocol() != nil || addrs[0].GetProtocolHint().GetOpaqueTransport() != nil {\n\t\t\t\tt.Fatalf(\"Expected protocol hint for %s to be nil but got %+v\", path, addrs[0].ProtocolHint)\n\t\t\t}\n\n\t\t\tif addrs[0].TlsIdentity != nil {\n\t\t\t\tt.Fatalf(\"Expected TLS identity for %s to be nil but got %+v\", path, addrs[0].TlsIdentity)\n\t\t\t}\n\t\tcase err := <-errs:\n\t\t\tt.Fatalf(\"Got error: %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"Return endpoint opaque protocol controlled by a server\", func(t *testing.T) {\n\t\ttestOpaque(t, \"policy-test\")\n\t})\n\n\tt.Run(\"Return endpoint opaque protocol controlled by a server (native sidecar)\", func(t *testing.T) {\n\t\ttestOpaque(t, \"native\")\n\t})\n\n\tt.Run(\"Remote discovery\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\t// Wait for cluster store to be synced.\n\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\tstream := &bufferingGetStream{\n\t\t\tupdates:          make(chan *pb.Update, 50),\n\t\t\tMockServerStream: util.NewMockServerStream(),\n\t\t}\n\t\tdefer stream.Cancel()\n\t\terrs := make(chan error)\n\n\t\t// server.Get blocks until the grpc stream is complete so we call it\n\t\t// in a goroutine and watch stream.updates for updates.\n\t\tgo func() {\n\t\t\terr := server.Get(&pb.GetDestination{Scheme: \"k8s\", Path: fmt.Sprintf(\"%s:%d\", \"foo-target.ns.svc.mycluster.local\", 80)}, stream)\n\t\t\tif err != nil {\n\t\t\t\terrs <- err\n\t\t\t}\n\t\t}()\n\n\t\tselect {\n\t\tcase update := <-stream.updates:\n\t\t\tif updateAddAddress(t, update)[0] != fmt.Sprintf(\"%s:%d\", \"172.17.55.1\", 80) {\n\t\t\t\tt.Fatalf(\"Expected %s but got %s\", fmt.Sprintf(\"%s:%d\", podIP1, port), updateAddAddress(t, update)[0])\n\t\t\t}\n\n\t\t\tif len(stream.updates) != 0 {\n\t\t\t\tt.Fatalf(\"Expected 1 update but got %d: %v\", 1+len(stream.updates), stream.updates)\n\t\t\t}\n\n\t\tcase err := <-errs:\n\t\t\tt.Fatalf(\"Got error: %s\", err)\n\t\t}\n\t})\n}\n\nfunc testOpaque(t *testing.T, name string) {\n\tserver, client := getServerWithClient(t)\n\n\tstream := &bufferingGetStream{\n\t\tupdates:          make(chan *pb.Update, 50),\n\t\tMockServerStream: util.NewMockServerStream(),\n\t}\n\tdefer stream.Cancel()\n\terrs := make(chan error)\n\n\tpath := fmt.Sprintf(\"%s.ns.svc.mycluster.local:%d\", name, 80)\n\n\t// server.Get blocks until the grpc stream is complete so we call it\n\t// in a goroutine and watch stream.updates for updates.\n\tgo func() {\n\t\terr := server.Get(&pb.GetDestination{\n\t\t\tScheme: \"k8s\",\n\t\t\tPath:   path,\n\t\t}, stream)\n\t\tif err != nil {\n\t\t\terrs <- err\n\t\t}\n\t}()\n\n\tselect {\n\tcase err := <-errs:\n\t\tt.Fatalf(\"Got error: %s\", err)\n\tcase update := <-stream.updates:\n\t\taddrs := update.GetAdd().Addrs\n\t\tif len(addrs) == 0 {\n\t\t\tt.Fatalf(\"Expected len(addrs) to be > 0\")\n\t\t}\n\n\t\tif addrs[0].GetProtocolHint().GetOpaqueTransport() == nil {\n\t\t\tt.Fatalf(\"Expected opaque transport for %s but was nil\", path)\n\t\t}\n\t}\n\n\t// Update the Server's pod selector so that it no longer selects the\n\t// pod. This should result in the proxy protocol no longer being marked\n\t// as opaque.\n\tsrv, err := client.ServerV1beta3().Servers(\"ns\").Get(context.Background(), name, metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// PodSelector is updated to NOT select the pod\n\tsrv.Spec.PodSelector.MatchLabels = map[string]string{\"app\": \"FOOBAR\"}\n\t_, err = client.ServerV1beta3().Servers(\"ns\").Update(context.Background(), srv, metav1.UpdateOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tselect {\n\tcase update := <-stream.updates:\n\t\taddrs := update.GetAdd().Addrs\n\t\tif len(addrs) == 0 {\n\t\t\tt.Fatalf(\"Expected len(addrs) to be > 0\")\n\t\t}\n\n\t\tif addrs[0].GetProtocolHint().GetOpaqueTransport() != nil {\n\t\t\tt.Fatalf(\"Expected opaque transport to be nil for %s but was %+v\", path, *addrs[0].GetProtocolHint().GetOpaqueTransport())\n\t\t}\n\tcase err := <-errs:\n\t\tt.Fatalf(\"Got error: %s\", err)\n\t}\n\n\t// Update the Server's pod selector so that it once again selects the\n\t// pod. This should result in the proxy protocol once again being marked\n\t// as opaque.\n\tsrv.Spec.PodSelector.MatchLabels = map[string]string{\"app\": name}\n\n\t_, err = client.ServerV1beta3().Servers(\"ns\").Update(context.Background(), srv, metav1.UpdateOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tselect {\n\tcase update := <-stream.updates:\n\t\taddrs := update.GetAdd().Addrs\n\t\tif len(addrs) == 0 {\n\t\t\tt.Fatalf(\"Expected len(addrs) to be > 0\")\n\t\t}\n\n\t\tif addrs[0].GetProtocolHint().GetOpaqueTransport() == nil {\n\t\t\tt.Fatalf(\"Expected opaque transport for %s but was nil\", path)\n\t\t}\n\tcase err := <-errs:\n\t\tt.Fatalf(\"Got error: %s\", err)\n\t}\n}\n\nfunc TestGetProfiles(t *testing.T) {\n\tt.Run(\"Returns error if not valid service name\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := &bufferingGetProfileStream{\n\t\t\tupdates:          []*pb.DestinationProfile{},\n\t\t\tMockServerStream: util.NewMockServerStream(),\n\t\t}\n\t\tdefer stream.Cancel()\n\t\terr := server.GetProfile(&pb.GetDestination{Scheme: \"k8s\", Path: \"linkerd.io\"}, stream)\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"Expecting error, got nothing\")\n\t\t}\n\t})\n\n\tt.Run(\"Returns InvalidArgument for ExternalName service\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := &bufferingGetProfileStream{\n\t\t\tupdates:          []*pb.DestinationProfile{},\n\t\t\tMockServerStream: util.NewMockServerStream(),\n\t\t}\n\t\tdefer stream.Cancel()\n\n\t\terr := server.GetProfile(&pb.GetDestination{Scheme: \"k8s\", Path: \"externalname.ns.svc.cluster.local\"}, stream)\n\t\tcode := status.Code(err)\n\t\tif code != codes.InvalidArgument {\n\t\t\tt.Fatalf(\"Expected InvalidArgument, got %s\", code)\n\t\t}\n\t})\n\n\tt.Run(\"Returns server profile\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, fullyQualifiedName, port, \"ns:other\")\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\tif profile.FullyQualifiedName != fullyQualifiedName {\n\t\t\tt.Fatalf(\"Expected fully qualified name '%s', but got '%s'\",\n\t\t\t\tfullyQualifiedName, profile.FullyQualifiedName)\n\t\t}\n\t\tif profile.OpaqueProtocol {\n\t\t\tt.Fatalf(\"Expected port %d to not be an opaque protocol, but it was\", port)\n\t\t}\n\t\troutes := profile.GetRoutes()\n\t\tif len(routes) != 1 {\n\t\t\tt.Fatalf(\"Expected 0 routes but got %d: %v\", len(routes), routes)\n\t\t}\n\t})\n\n\tt.Run(\"Return service profile when using json token\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, fullyQualifiedName, port, `{\"ns\":\"other\"}`)\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\tif profile.FullyQualifiedName != fullyQualifiedName {\n\t\t\tt.Fatalf(\"Expected fully qualified name '%s', but got '%s'\", fullyQualifiedName, profile.FullyQualifiedName)\n\t\t}\n\t\troutes := profile.GetRoutes()\n\t\tif len(routes) != 1 {\n\t\t\tt.Fatalf(\"Expected 1 route got %d: %v\", len(routes), routes)\n\t\t}\n\t})\n\n\tt.Run(\"Returns client profile\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, fullyQualifiedName, port, `{\"ns\":\"client-ns\"}`)\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\troutes := profile.GetRoutes()\n\t\tif len(routes) != 1 {\n\t\t\tt.Fatalf(\"Expected 1 route but got %d: %v\", len(routes), routes)\n\t\t}\n\t\tif !routes[0].GetIsRetryable() {\n\t\t\tt.Fatalf(\"Expected route to be retryable, but it was not\")\n\t\t}\n\t})\n\n\tt.Run(\"Return profile when using cluster IP\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, clusterIP, port, \"\")\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\tif profile.FullyQualifiedName != fullyQualifiedName {\n\t\t\tt.Fatalf(\"Expected fully qualified name '%s', but got '%s'\", fullyQualifiedName, profile.FullyQualifiedName)\n\t\t}\n\t\tif profile.OpaqueProtocol {\n\t\t\tt.Fatalf(\"Expected port %d to not be an opaque protocol, but it was\", port)\n\t\t}\n\t\troutes := profile.GetRoutes()\n\t\tif len(routes) != 1 {\n\t\t\tt.Fatalf(\"Expected 1 route but got %d: %v\", len(routes), routes)\n\t\t}\n\t})\n\n\tt.Run(\"Return profile when using secondary cluster IP\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, clusterIPv6, port, \"\")\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\tif profile.FullyQualifiedName != fullyQualifiedNameDual {\n\t\t\tt.Fatalf(\"Expected fully qualified name '%s', but got '%s'\", fullyQualifiedName, profile.FullyQualifiedName)\n\t\t}\n\t\tif profile.OpaqueProtocol {\n\t\t\tt.Fatalf(\"Expected port %d to not be an opaque protocol, but it was\", port)\n\t\t}\n\t\troutes := profile.GetRoutes()\n\t\tif len(routes) != 1 {\n\t\t\tt.Fatalf(\"Expected 1 route but got %d: %v\", len(routes), routes)\n\t\t}\n\t})\n\n\tt.Run(\"Return profile with endpoint when using pod DNS\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, fullyQualifiedPodDNS, port, \"ns:ns\")\n\t\tdefer stream.Cancel()\n\n\t\tepAddr, err := toAddress(podIPStatefulSet, port)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Got error: %s\", err)\n\t\t}\n\n\t\t// An explanation for why we expect 1 to 3 updates is in test cases\n\t\t// above\n\t\tupdates := stream.Updates()\n\t\tif len(updates) == 0 || len(updates) > 3 {\n\t\t\tt.Fatalf(\"Expected 1 to 3 updates but got %d: %v\", len(updates), updates)\n\t\t}\n\n\t\tfirst := updates[0]\n\t\tif first.Endpoint == nil {\n\t\t\tt.Fatalf(\"Expected response to have endpoint field\")\n\t\t}\n\t\tif first.OpaqueProtocol {\n\t\t\tt.Fatalf(\"Expected port %d to not be an opaque protocol, but it was\", port)\n\t\t}\n\t\t_, exists := first.Endpoint.MetricLabels[\"namespace\"]\n\t\tif !exists {\n\t\t\tt.Fatalf(\"Expected 'namespace' metric label to exist but it did not\")\n\t\t}\n\t\tif first.GetEndpoint().GetProtocolHint() == nil {\n\t\t\tt.Fatalf(\"Expected protocol hint but found none\")\n\t\t}\n\t\tif first.GetEndpoint().GetProtocolHint().GetOpaqueTransport() != nil {\n\t\t\tt.Fatalf(\"Expected pod to not support opaque traffic on port %d\", port)\n\t\t}\n\t\tif first.Endpoint.Addr.Ip.GetIpv4() == 0 && first.Endpoint.Addr.Ip.GetIpv6() == nil {\n\t\t\tt.Fatal(\"IP is empty\")\n\t\t}\n\t\tif first.Endpoint.Addr.String() != epAddr.String() {\n\t\t\tt.Fatalf(\"Expected endpoint IP to be %s, but it was %s\", epAddr.Ip, first.Endpoint.Addr.Ip)\n\t\t}\n\t})\n\n\tt.Run(\"Return profile with endpoint when using pod IP\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\t\thttp2Params := pb.Http2ClientParams{\n\t\t\tKeepAlive: &pb.Http2ClientParams_KeepAlive{\n\t\t\t\tTimeout:  &duration.Duration{Seconds: 10},\n\t\t\t\tInterval: &duration.Duration{Seconds: 20},\n\t\t\t},\n\t\t}\n\t\tserver.config.MeshedHttp2ClientParams = &http2Params\n\n\t\tstream := profileStream(t, server, podIP1, port, \"ns:ns\")\n\t\tdefer stream.Cancel()\n\n\t\tepAddr, err := toAddress(podIP1, port)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Got error: %s\", err)\n\t\t}\n\n\t\t// An explanation for why we expect 1 to 3 updates is in test cases\n\t\t// above\n\t\tupdates := stream.Updates()\n\t\tif len(updates) == 0 || len(updates) > 3 {\n\t\t\tt.Fatalf(\"Expected 1 to 3 updates but got %d: %v\", len(updates), updates)\n\t\t}\n\n\t\tfirst := updates[0]\n\t\tif first.Endpoint == nil {\n\t\t\tt.Fatalf(\"Expected response to have endpoint field\")\n\t\t}\n\t\tif first.OpaqueProtocol {\n\t\t\tt.Fatalf(\"Expected port %d to not be an opaque protocol, but it was\", port)\n\t\t}\n\t\t_, exists := first.Endpoint.MetricLabels[\"namespace\"]\n\t\tif !exists {\n\t\t\tt.Fatalf(\"Expected 'namespace' metric label to exist but it did not\")\n\t\t}\n\t\tif first.GetEndpoint().GetProtocolHint() == nil {\n\t\t\tt.Fatalf(\"Expected protocol hint but found none\")\n\t\t}\n\t\tif first.GetEndpoint().GetProtocolHint().GetOpaqueTransport() != nil {\n\t\t\tt.Fatalf(\"Expected pod to not support opaque traffic on port %d\", port)\n\t\t}\n\t\tif first.Endpoint.Addr.Ip.GetIpv4() == 0 && first.Endpoint.Addr.Ip.GetIpv6() == nil {\n\t\t\tt.Fatal(\"IP is empty\")\n\t\t}\n\t\tif first.Endpoint.Addr.String() != epAddr.String() {\n\t\t\tt.Fatalf(\"Expected endpoint IP to be %s, but it was %s\", epAddr.Ip, first.Endpoint.Addr.Ip)\n\t\t}\n\t\tif !reflect.DeepEqual(first.Endpoint.GetHttp2(), &http2Params) {\n\t\t\tt.Fatalf(\"Expected HTTP/2 client params to be %v, but got %v\", &http2Params, first.Endpoint.GetHttp2())\n\t\t}\n\t})\n\n\tt.Run(\"Return profile with endpoint when using pod secondary IP\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, podIPv6Dual, port, \"ns:ns\")\n\t\tdefer stream.Cancel()\n\n\t\tepAddr, err := toAddress(podIPv6Dual, port)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Got error: %s\", err)\n\t\t}\n\n\t\t// An explanation for why we expect 1 to 3 updates is in test cases\n\t\t// above\n\t\tupdates := stream.Updates()\n\t\tif len(updates) == 0 || len(updates) > 3 {\n\t\t\tt.Fatalf(\"Expected 1 to 3 updates but got %d: %v\", len(updates), updates)\n\t\t}\n\n\t\tfirst := updates[0]\n\t\tif first.Endpoint == nil {\n\t\t\tt.Fatalf(\"Expected response to have endpoint field\")\n\t\t}\n\t\tif first.OpaqueProtocol {\n\t\t\tt.Fatalf(\"Expected port %d to not be an opaque protocol, but it was\", port)\n\t\t}\n\t\t_, exists := first.Endpoint.MetricLabels[\"namespace\"]\n\t\tif !exists {\n\t\t\tt.Fatalf(\"Expected 'namespace' metric label to exist but it did not\")\n\t\t}\n\t\tif first.GetEndpoint().GetProtocolHint() == nil {\n\t\t\tt.Fatalf(\"Expected protocol hint but found none\")\n\t\t}\n\t\tif first.GetEndpoint().GetProtocolHint().GetOpaqueTransport() != nil {\n\t\t\tt.Fatalf(\"Expected pod to not support opaque traffic on port %d\", port)\n\t\t}\n\t\tif first.Endpoint.Addr.Ip.GetIpv4() == 0 && first.Endpoint.Addr.Ip.GetIpv6() == nil {\n\t\t\tt.Fatal(\"IP is empty\")\n\t\t}\n\t\tif first.Endpoint.Addr.String() != epAddr.String() {\n\t\t\tt.Fatalf(\"Expected endpoint IP to be %s, but it was %s\", epAddr.Ip, first.Endpoint.Addr.Ip)\n\t\t}\n\t})\n\n\tt.Run(\"Profile gets updated after pod becomes ready\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\t// podIP3 is initially not ready\n\t\tstream := profileStream(t, server, podIP3, linkerdAdminPort, \"ns:ns\")\n\t\tdefer stream.Cancel()\n\n\t\tupdates := stream.Updates()\n\t\tif len(updates) != 1 {\n\t\t\tt.Fatalf(\"Expected 1 update but got %d: %v\", len(updates), updates)\n\t\t}\n\n\t\tprofile := updates[0]\n\t\tif profile.Endpoint == nil {\n\t\t\tt.Fatalf(\"Expected response to have endpoint field\")\n\t\t}\n\t\tif profile.Endpoint.TlsIdentity != nil {\n\t\t\tt.Fatalf(\"Expected endpoint TLS identity to be nil but was %v\", profile.Endpoint.TlsIdentity)\n\t\t}\n\n\t\tpod, err := server.k8sAPI.Client.CoreV1().Pods(\"ns\").Get(context.Background(), \"name1-20\", metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to retrieve pod: %s\", err)\n\t\t}\n\t\tpod.Status.Phase = corev1.PodRunning\n\t\t_, err = server.k8sAPI.Client.CoreV1().Pods(\"ns\").Update(context.Background(), pod, metav1.UpdateOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to update pod: %s\", err)\n\t\t}\n\n\t\tprofile = getLastProfileUpdate(t, stream, 2)\n\t\tif profile.Endpoint == nil {\n\t\t\tt.Fatalf(\"Expected response to have endpoint field\")\n\t\t}\n\t\tif profile.Endpoint.TlsIdentity == nil {\n\t\t\tt.Fatalf(\"Expected endpoint TLS identity to not be nil\")\n\t\t}\n\t})\n\n\tt.Run(\"Profile gets updated after pod becomes ready (native sidecar)\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\t// podIP3 is initially not ready\n\t\tstream := profileStream(t, server, podIP4, linkerdAdminPort, \"ns:ns\")\n\t\tdefer stream.Cancel()\n\n\t\tupdates := stream.Updates()\n\t\tif len(updates) != 1 {\n\t\t\tt.Fatalf(\"Expected 1 update but got %d: %v\", len(updates), updates)\n\t\t}\n\n\t\tprofile := updates[0]\n\t\tif profile.Endpoint == nil {\n\t\t\tt.Fatalf(\"Expected response to have endpoint field\")\n\t\t}\n\t\tif profile.Endpoint.TlsIdentity != nil {\n\t\t\tt.Fatalf(\"Expected endpoint TLS identity to be nil but was %v\", profile.Endpoint.TlsIdentity)\n\t\t}\n\n\t\tpod, err := server.k8sAPI.Client.CoreV1().Pods(\"ns\").Get(context.Background(), \"name1-21\", metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to retrieve pod: %s\", err)\n\t\t}\n\t\tpod.Status.Phase = corev1.PodRunning\n\t\t_, err = server.k8sAPI.Client.CoreV1().Pods(\"ns\").Update(context.Background(), pod, metav1.UpdateOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to update pod: %s\", err)\n\t\t}\n\n\t\tprofile = getLastProfileUpdate(t, stream, 2)\n\t\tif profile.Endpoint == nil {\n\t\t\tt.Fatalf(\"Expected response to have endpoint field\")\n\t\t}\n\t\tif profile.Endpoint.TlsIdentity == nil {\n\t\t\tt.Fatalf(\"Expected endpoint TLS identity to not be nil\")\n\t\t}\n\t})\n\n\tt.Run(\"Return profile with endpoint when using externalworkload IP\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\t\thttp2Params := pb.Http2ClientParams{\n\t\t\tKeepAlive: &pb.Http2ClientParams_KeepAlive{\n\t\t\t\tTimeout:  &duration.Duration{Seconds: 10},\n\t\t\t\tInterval: &duration.Duration{Seconds: 20},\n\t\t\t},\n\t\t}\n\t\tserver.config.MeshedHttp2ClientParams = &http2Params\n\n\t\tstream := profileStream(t, server, externalWorkloadIP, port, \"ns:ns\")\n\t\tdefer stream.Cancel()\n\n\t\tepAddr, err := toAddress(externalWorkloadIP, port)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Got error: %s\", err)\n\t\t}\n\n\t\t// An explanation for why we expect 1 to 3 updates is in test cases\n\t\t// above\n\t\tupdates := stream.Updates()\n\t\tif len(updates) == 0 || len(updates) > 3 {\n\t\t\tt.Fatalf(\"Expected 1 to 3 updates but got %d: %v\", len(updates), updates)\n\t\t}\n\n\t\tfirst := updates[0]\n\t\tif first.Endpoint == nil {\n\t\t\tt.Fatalf(\"Expected response to have endpoint field\")\n\t\t}\n\t\tif first.OpaqueProtocol {\n\t\t\tt.Fatalf(\"Expected port %d to not be an opaque protocol, but it was\", port)\n\t\t}\n\t\t_, exists := first.Endpoint.MetricLabels[\"namespace\"]\n\t\tif !exists {\n\t\t\tt.Fatalf(\"Expected 'namespace' metric label to exist but it did not %v\", first.Endpoint)\n\t\t}\n\t\tif first.GetEndpoint().GetProtocolHint() == nil {\n\t\t\tt.Fatalf(\"Expected protocol hint but found none\")\n\t\t}\n\t\tif first.GetEndpoint().GetProtocolHint().GetOpaqueTransport() != nil {\n\t\t\tt.Fatalf(\"Expected externalworkload to not support opaque traffic on port %d\", port)\n\t\t}\n\t\tif first.Endpoint.Addr.Ip.GetIpv4() == 0 && first.Endpoint.Addr.Ip.GetIpv6() == nil {\n\t\t\tt.Fatal(\"IP is empty\")\n\t\t}\n\t\tif first.Endpoint.Addr.String() != epAddr.String() {\n\t\t\tt.Fatalf(\"Expected endpoint IP to be %s, but it was %s\", epAddr.Ip, first.Endpoint.Addr.Ip)\n\t\t}\n\t\tif !reflect.DeepEqual(first.Endpoint.GetHttp2(), &http2Params) {\n\t\t\tt.Fatalf(\"Expected HTTP/2 client params to be %v, but got %v\", &http2Params, first.Endpoint.GetHttp2())\n\t\t}\n\t})\n\n\tt.Run(\"Return default profile when IP does not map to service or pod\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, \"172.0.0.0\", 1234, \"\")\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\tif profile.RetryBudget == nil {\n\t\t\tt.Fatalf(\"Expected default profile to have a retry budget\")\n\t\t}\n\t})\n\n\tt.Run(\"Return profile with no opaque transport when pod does not have label and port is opaque\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\t// port 3306 is in the default opaque port list\n\t\tstream := profileStream(t, server, podIP2, 3306, \"\")\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\tif profile.Endpoint == nil {\n\t\t\tt.Fatalf(\"Expected response to have endpoint field\")\n\t\t}\n\n\t\tif profile.Endpoint.GetProtocolHint().GetOpaqueTransport() != nil {\n\t\t\tt.Fatalf(\"Expected no opaque transport but found one\")\n\t\t}\n\t\tif profile.GetEndpoint().GetHttp2() != nil {\n\t\t\tt.Fatalf(\"Expected no HTTP/2 client parameters but found one\")\n\t\t}\n\t})\n\n\tt.Run(\"Return profile with no protocol hint when pod does not have label\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, podIP2, port, \"\")\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\tif profile.Endpoint == nil {\n\t\t\tt.Fatalf(\"Expected response to have endpoint field\")\n\t\t}\n\t\tif profile.Endpoint.GetProtocolHint().GetProtocol() != nil || profile.Endpoint.GetProtocolHint().GetOpaqueTransport() != nil {\n\t\t\tt.Fatalf(\"Expected no protocol hint but found one\")\n\t\t}\n\t})\n\n\tt.Run(\"Return profile with protocol hint for default opaque port when pod is unmeshed\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\t// 3306 is in the default opaque list\n\t\tstream := profileStream(t, server, podIP2, 3306, \"\")\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\tif profile.Endpoint == nil {\n\t\t\tt.Fatalf(\"Expected response to have endpoint field\")\n\t\t}\n\t\tif !profile.OpaqueProtocol {\n\t\t\tt.Fatal(\"Expected port 3306 to be an opaque protocol, but it was not\")\n\t\t}\n\t\tif profile.GetEndpoint().GetProtocolHint() != nil {\n\t\t\tt.Fatalf(\"Expected protocol hint to be nil\")\n\t\t}\n\t})\n\n\tt.Run(\"Return non-opaque protocol profile when using cluster IP and opaque protocol port\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, clusterIPOpaque, opaquePort, \"\")\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\tif profile.FullyQualifiedName != fullyQualifiedNameOpaque {\n\t\t\tt.Fatalf(\"Expected fully qualified name '%s', but got '%s'\", fullyQualifiedNameOpaque, profile.FullyQualifiedName)\n\t\t}\n\t\tif profile.OpaqueProtocol {\n\t\t\tt.Fatalf(\"Expected port %d to not be an opaque protocol, but it was\", opaquePort)\n\t\t}\n\t})\n\n\tt.Run(\"Return opaque protocol profile with endpoint when using pod IP and opaque protocol port\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, podIPOpaque, opaquePort, \"\")\n\t\tdefer stream.Cancel()\n\n\t\tepAddr, err := toAddress(podIPOpaque, opaquePort)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Got error: %s\", err)\n\t\t}\n\n\t\t// An explanation for why we expect 1 to 3 updates is in test cases\n\t\t// above\n\t\tupdates := stream.Updates()\n\t\tif len(updates) == 0 || len(updates) > 3 {\n\t\t\tt.Fatalf(\"Expected 1 to 3 updates but got %d: %v\", len(updates), updates)\n\t\t}\n\n\t\tprofile := assertSingleProfile(t, updates)\n\t\tif profile.Endpoint == nil {\n\t\t\tt.Fatalf(\"Expected response to have endpoint field\")\n\t\t}\n\t\tif !profile.OpaqueProtocol {\n\t\t\tt.Fatalf(\"Expected port %d to be an opaque protocol, but it was not\", opaquePort)\n\t\t}\n\t\t_, exists := profile.Endpoint.MetricLabels[\"namespace\"]\n\t\tif !exists {\n\t\t\tt.Fatalf(\"Expected 'namespace' metric label to exist but it did not\")\n\t\t}\n\t\tif profile.Endpoint.ProtocolHint == nil {\n\t\t\tt.Fatalf(\"Expected protocol hint but found none\")\n\t\t}\n\t\tif profile.Endpoint.GetProtocolHint().GetOpaqueTransport().GetInboundPort() != 4143 {\n\t\t\tt.Fatalf(\"Expected pod to support opaque traffic on port 4143\")\n\t\t}\n\t\tif profile.Endpoint.Addr.Ip.GetIpv4() == 0 && profile.Endpoint.Addr.Ip.GetIpv6() == nil {\n\t\t\tt.Fatal(\"IP is empty\")\n\t\t}\n\t\tif profile.Endpoint.Addr.String() != epAddr.String() {\n\t\t\tt.Fatalf(\"Expected endpoint IP port to be %d, but it was %d\", epAddr.Port, profile.Endpoint.Addr.Port)\n\t\t}\n\t})\n\n\tt.Run(\"Return opaque protocol profile when using service name with opaque port annotation\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, fullyQualifiedNameOpaqueService, opaquePort, \"\")\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\tif profile.FullyQualifiedName != fullyQualifiedNameOpaqueService {\n\t\t\tt.Fatalf(\"Expected fully qualified name '%s', but got '%s'\", fullyQualifiedNameOpaqueService, profile.FullyQualifiedName)\n\t\t}\n\t\tif !profile.OpaqueProtocol {\n\t\t\tt.Fatalf(\"Expected port %d to be an opaque protocol, but it was not\", opaquePort)\n\t\t}\n\t})\n\n\tt.Run(\"Return profile with unknown protocol hint and identity when pod contains skipped inbound port\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, podIPSkipped, skippedPort, \"\")\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\taddr := profile.GetEndpoint()\n\t\tif addr == nil {\n\t\t\tt.Fatalf(\"Expected to not be nil\")\n\t\t}\n\t\tif addr.GetProtocolHint().GetProtocol() != nil || addr.GetProtocolHint().GetOpaqueTransport() != nil {\n\t\t\tt.Fatalf(\"Expected protocol hint for %s to be nil but got %+v\", podIPSkipped, addr.ProtocolHint)\n\t\t}\n\t\tif addr.TlsIdentity != nil {\n\t\t\tt.Fatalf(\"Expected TLS identity for %s to be nil but got %+v\", podIPSkipped, addr.TlsIdentity)\n\t\t}\n\t})\n\n\tt.Run(\"Return opaque protocol profile with endpoint when using externalworkload IP and opaque protocol port\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, externalWorkloadIP, opaquePort, \"\")\n\t\tdefer stream.Cancel()\n\n\t\tepAddr, err := toAddress(externalWorkloadIP, opaquePort)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Got error: %s\", err)\n\t\t}\n\n\t\t// An explanation for why we expect 1 to 3 updates is in test cases\n\t\t// above\n\t\tupdates := stream.Updates()\n\t\tif len(updates) == 0 || len(updates) > 3 {\n\t\t\tt.Fatalf(\"Expected 1 to 3 updates but got %d: %v\", len(updates), updates)\n\t\t}\n\n\t\tprofile := assertSingleProfile(t, updates)\n\t\tif profile.Endpoint == nil {\n\t\t\tt.Fatalf(\"Expected response to have endpoint field\")\n\t\t}\n\t\tif !profile.OpaqueProtocol {\n\t\t\tt.Fatalf(\"Expected port %d to be an opaque protocol, but it was not\", opaquePort)\n\t\t}\n\t\t_, exists := profile.Endpoint.MetricLabels[\"namespace\"]\n\t\tif !exists {\n\t\t\tt.Fatalf(\"Expected 'namespace' metric label to exist but it did not\")\n\t\t}\n\t\tif profile.Endpoint.ProtocolHint == nil {\n\t\t\tt.Fatalf(\"Expected protocol hint but found none\")\n\t\t}\n\t\tif profile.Endpoint.GetProtocolHint().GetOpaqueTransport().GetInboundPort() != 4143 {\n\t\t\tt.Fatalf(\"Expected pod to support opaque traffic on port 4143\")\n\t\t}\n\t\tif profile.Endpoint.Addr.Ip.GetIpv4() == 0 && profile.Endpoint.Addr.Ip.GetIpv6() == nil {\n\t\t\tt.Fatal(\"IP is empty\")\n\t\t}\n\t\tif profile.Endpoint.Addr.String() != epAddr.String() {\n\t\t\tt.Fatalf(\"Expected endpoint IP port to be %d, but it was %d\", epAddr.Port, profile.Endpoint.Addr.Port)\n\t\t}\n\t})\n\n\tt.Run(\"Return profile with opaque protocol when using Pod IP selected by a Server\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, podIPPolicy, 80, \"\")\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\tif profile.Endpoint == nil {\n\t\t\tt.Fatalf(\"Expected response to have endpoint field\")\n\t\t}\n\t\tif !profile.OpaqueProtocol {\n\t\t\tt.Fatalf(\"Expected port %d to be an opaque protocol, but it was not\", 80)\n\t\t}\n\t\tif profile.Endpoint.GetProtocolHint() == nil {\n\t\t\tt.Fatalf(\"Expected protocol hint but found none\")\n\t\t}\n\t\tif profile.Endpoint.GetProtocolHint().GetOpaqueTransport().GetInboundPort() != 4143 {\n\t\t\tt.Fatalf(\"Expected pod to support opaque traffic on port 4143\")\n\t\t}\n\t})\n\n\tt.Run(\"Return profile with opaque protocol when using externalworkload IP selected by a Server\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, externalWorkloadIPPolicy, 80, \"\")\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\tif profile.Endpoint == nil {\n\t\t\tt.Fatalf(\"Expected response to have endpoint field\")\n\t\t}\n\t\tif !profile.OpaqueProtocol {\n\t\t\tt.Fatalf(\"Expected port %d to be an opaque protocol, but it was not\", 80)\n\t\t}\n\t\tif profile.Endpoint.GetProtocolHint() == nil {\n\t\t\tt.Fatalf(\"Expected protocol hint but found none\")\n\t\t}\n\t\tif profile.Endpoint.GetProtocolHint().GetOpaqueTransport().GetInboundPort() != 4143 {\n\t\t\tt.Fatalf(\"Expected pod to support opaque traffic on port 4143\")\n\t\t}\n\t})\n\n\tt.Run(\"Return profile with opaque protocol when using an opaque port with an external IP\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, externalIP, 3306, \"\")\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\tif !profile.OpaqueProtocol {\n\t\t\tt.Fatalf(\"Expected port %d to be an opaque protocol, but it was not\", 3306)\n\t\t}\n\n\t})\n\n\tt.Run(\"Return profile with non-opaque protocol when using an arbitrary port with an external IP\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tstream := profileStream(t, server, externalIP, 80, \"\")\n\t\tdefer stream.Cancel()\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\tif profile.OpaqueProtocol {\n\t\t\tt.Fatalf(\"Expected port %d to be a non-opaque protocol, but it was opaque\", 80)\n\t\t}\n\t})\n\n\tt.Run(\"Return profile for host port pods\", func(t *testing.T) {\n\t\thostPort := uint32(7777)\n\t\tcontainerPort := uint32(80)\n\t\tserver, l5dClient := getServerWithClient(t)\n\n\t\tstream := profileStream(t, server, externalIP, hostPort, \"\")\n\t\tdefer stream.Cancel()\n\n\t\t// HostPort maps to pod.\n\t\tprofile := assertSingleProfile(t, stream.Updates())\n\t\tdstPod := profile.Endpoint.MetricLabels[\"pod\"]\n\t\tif dstPod != \"hostport-mapping\" {\n\t\t\tt.Fatalf(\"Expected dst_pod to be %s got %s\", \"hostport-mapping\", dstPod)\n\t\t}\n\n\t\tip, err := addr.ParseProxyIP(externalIP)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error parsing IP: %s\", err)\n\t\t}\n\t\taddr := profile.Endpoint.Addr\n\t\tif addr.Ip.String() != ip.String() && addr.Port != hostPort {\n\t\t\tt.Fatalf(\"Expected endpoint addr to be %s port:%d got %s\", ip, hostPort, addr)\n\t\t}\n\n\t\t// HostPort pod is deleted.\n\t\terr = server.k8sAPI.Client.CoreV1().Pods(\"ns\").Delete(context.Background(), \"hostport-mapping\", metav1.DeleteOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to delete pod: %s\", err)\n\t\t}\n\t\tprofile = getLastProfileUpdate(t, stream, 2)\n\t\tdstPod = profile.Endpoint.MetricLabels[\"pod\"]\n\t\tif dstPod != \"\" {\n\t\t\tt.Fatalf(\"Expected no dst_pod but got %s\", dstPod)\n\t\t}\n\n\t\t// New HostPort pod is created.\n\t\t_, err = server.k8sAPI.Client.CoreV1().Pods(\"ns\").Create(context.Background(), &corev1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"hostport-mapping-2\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"app\": \"hostport-mapping-2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: corev1.PodSpec{\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: pkgk8s.ProxyContainerName,\n\t\t\t\t\t\tEnv: []corev1.EnvVar{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  \"LINKERD2_PROXY_INBOUND_LISTEN_ADDR\",\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4143\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  \"LINKERD2_PROXY_ADMIN_LISTEN_ADDR\",\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4191\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  \"LINKERD2_PROXY_CONTROL_LISTEN_ADDR\",\n\t\t\t\t\t\t\t\tValue: \"0.0.0.0:4190\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName:  \"nginx\",\n\t\t\t\t\t\tImage: \"nginx\",\n\t\t\t\t\t\tPorts: []corev1.ContainerPort{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:          \"nginx-7777\",\n\t\t\t\t\t\t\t\tContainerPort: (int32)(containerPort),\n\t\t\t\t\t\t\t\tHostPort:      (int32)(hostPort),\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\tStatus: corev1.PodStatus{\n\t\t\t\tPhase: \"Running\",\n\t\t\t\tConditions: []corev1.PodCondition{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:   corev1.PodReady,\n\t\t\t\t\t\tStatus: corev1.ConditionTrue,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tHostIP:  externalIP,\n\t\t\t\tHostIPs: []corev1.HostIP{{IP: externalIP}, {IP: externalIPv6}},\n\t\t\t\tPodIP:   \"172.17.0.55\",\n\t\t\t\tPodIPs:  []corev1.PodIP{{IP: \"172.17.0.55\"}},\n\t\t\t},\n\t\t}, metav1.CreateOptions{})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to create pod: %s\", err)\n\t\t}\n\n\t\tprofile = getLastProfileUpdate(t, stream, 3)\n\t\tdstPod = profile.Endpoint.MetricLabels[\"pod\"]\n\t\tif dstPod != \"hostport-mapping-2\" {\n\t\t\tt.Fatalf(\"Expected dst_pod to be %s got %s\", \"hostport-mapping-2\", dstPod)\n\t\t}\n\t\tif profile.OpaqueProtocol {\n\t\t\tt.Fatal(\"Expected OpaqueProtocol=false\")\n\t\t}\n\n\t\t// Server is created, setting the port to opaque\n\t\tl5dClient.ServerV1beta3().Servers(\"ns\").Create(context.Background(), &v1beta3.Server{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"srv-hostport-mapping-2\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t},\n\t\t\tSpec: v1beta3.ServerSpec{\n\t\t\t\tPodSelector: &metav1.LabelSelector{\n\t\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\t\"app\": \"hostport-mapping-2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPort: intstr.IntOrString{\n\t\t\t\t\tType:   intstr.String,\n\t\t\t\t\tStrVal: \"nginx-7777\",\n\t\t\t\t},\n\t\t\t\tProxyProtocol: \"opaque\",\n\t\t\t},\n\t\t}, metav1.CreateOptions{})\n\n\t\tprofile = getLastProfileUpdate(t, stream, 4)\n\t\tif !profile.OpaqueProtocol {\n\t\t\tt.Fatal(\"Expected OpaqueProtocol=true\")\n\t\t}\n\t})\n}\n\nfunc TestTokenStructure(t *testing.T) {\n\tt.Run(\"when JSON is valid\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tdest := &pb.GetDestination{ContextToken: \"{\\\"ns\\\":\\\"ns-1\\\",\\\"nodeName\\\":\\\"node-1\\\"}\\n\"}\n\t\ttoken := server.parseContextToken(dest.ContextToken)\n\n\t\tif token.Ns != \"ns-1\" {\n\t\t\tt.Fatalf(\"Expected token namespace to be %s got %s\", \"ns-1\", token.Ns)\n\t\t}\n\n\t\tif token.NodeName != \"node-1\" {\n\t\t\tt.Fatalf(\"Expected token nodeName to be %s got %s\", \"node-1\", token.NodeName)\n\t\t}\n\t})\n\n\tt.Run(\"when JSON is invalid and old token format used\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tdest := &pb.GetDestination{ContextToken: \"ns:ns-2\"}\n\t\ttoken := server.parseContextToken(dest.ContextToken)\n\t\tif token.Ns != \"ns-2\" {\n\t\t\tt.Fatalf(\"Expected %s got %s\", \"ns-2\", token.Ns)\n\t\t}\n\t})\n\n\tt.Run(\"when invalid JSON and invalid old format\", func(t *testing.T) {\n\t\tserver := makeServer(t)\n\n\t\tdest := &pb.GetDestination{ContextToken: \"123fa-test\"}\n\t\ttoken := server.parseContextToken(dest.ContextToken)\n\t\tif token.Ns != \"\" || token.NodeName != \"\" {\n\t\t\tt.Fatalf(\"Expected context token to be empty, got %v\", token)\n\t\t}\n\t})\n}\n\nfunc updateAddAddress(t *testing.T, update *pb.Update) []string {\n\tt.Helper()\n\tadd, ok := update.GetUpdate().(*pb.Update_Add)\n\tif !ok {\n\t\tt.Fatalf(\"Update expected to be an add, but was %+v\", update)\n\t}\n\tips := []string{}\n\tfor _, ip := range add.Add.Addrs {\n\t\tips = append(ips, addr.ProxyAddressToString(ip.GetAddr()))\n\t}\n\treturn ips\n}\n\nfunc updateRemoveAddress(t *testing.T, update *pb.Update) []string {\n\tt.Helper()\n\tadd, ok := update.GetUpdate().(*pb.Update_Remove)\n\tif !ok {\n\t\tt.Fatalf(\"Update expected to be a remove, but was %+v\", update)\n\t}\n\tips := []string{}\n\tfor _, ip := range add.Remove.Addrs {\n\t\tips = append(ips, addr.ProxyAddressToString(ip))\n\t}\n\treturn ips\n}\n\nfunc toAddress(path string, port uint32) (*net.TcpAddress, error) {\n\tip, err := addr.ParseProxyIP(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &net.TcpAddress{\n\t\tIp:   ip,\n\t\tPort: port,\n\t}, nil\n}\n\nfunc TestIpWatcherGetSvcID(t *testing.T) {\n\tname := \"service\"\n\tnamespace := \"test\"\n\tclusterIP := \"10.245.0.1\"\n\tk8sConfigs := `\napiVersion: v1\nkind: Service\nmetadata:\n  name: service\n  namespace: test\nspec:\n  type: ClusterIP\n  clusterIP: 10.245.0.1\n  clusterIPs:\n  - 10.245.0.1\n  - 2001:db8::88\n  ports:\n  - port: 1234`\n\n\tt.Run(\"get services IDs by IP address\", func(t *testing.T) {\n\t\tk8sAPI, err := k8s.NewFakeAPI(k8sConfigs)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t}\n\n\t\terr = watcher.InitializeIndexers(k8sAPI)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"InitializeIndexers returned an error: %s\", err)\n\t\t}\n\n\t\tk8sAPI.Sync(nil)\n\n\t\tsvc, err := getSvcID(k8sAPI, clusterIP, logging.WithFields(nil))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error getting service: %s\", err)\n\t\t}\n\t\tif svc == nil {\n\t\t\tt.Fatalf(\"Expected to find service mapped to [%s]\", clusterIP)\n\t\t}\n\t\tif svc.Name != name {\n\t\t\tt.Fatalf(\"Expected service name to be [%s], but got [%s]\", name, svc.Name)\n\t\t}\n\t\tif svc.Namespace != namespace {\n\t\t\tt.Fatalf(\"Expected service namespace to be [%s], but got [%s]\", namespace, svc.Namespace)\n\t\t}\n\n\t\tsvc6, err := getSvcID(k8sAPI, clusterIPv6, logging.WithFields(nil))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error getting service: %s\", err)\n\t\t}\n\t\tif svc6 == nil {\n\t\t\tt.Fatalf(\"Expected to find service mapped to [%s]\", clusterIPv6)\n\t\t}\n\t\tif svc.Name != name {\n\t\t\tt.Fatalf(\"Expected service name to be [%s], but got [%s]\", name, svc.Name)\n\t\t}\n\t\tif svc.Namespace != namespace {\n\t\t\tt.Fatalf(\"Expected service namespace to be [%s], but got [%s]\", namespace, svc.Namespace)\n\t\t}\n\n\t\tbadClusterIP := \"10.256.0.2\"\n\t\tsvc, err = getSvcID(k8sAPI, badClusterIP, logging.WithFields(nil))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error getting service: %s\", err)\n\t\t}\n\t\tif svc != nil {\n\t\t\tt.Fatalf(\"Expected not to find service mapped to [%s]\", badClusterIP)\n\t\t}\n\t})\n}\n\nfunc testReturnEndpoints(t *testing.T, fqdn, ip string, port uint32) {\n\tt.Helper()\n\n\tserver := makeServer(t)\n\n\tstream := &bufferingGetStream{\n\t\tupdates:          make(chan *pb.Update, 50),\n\t\tMockServerStream: util.NewMockServerStream(),\n\t}\n\tdefer stream.Cancel()\n\n\ttestReturnEndpointsForServer(t, server, stream, fqdn, ip, port)\n}\n\nfunc testReturnEndpointsForServer(t *testing.T, server *server, stream *bufferingGetStream, fqdn, ip string, port uint32) {\n\tt.Helper()\n\n\terrs := make(chan error)\n\t// server.Get blocks until the grpc stream is complete so we call it\n\t// in a goroutine and watch stream.updates for updates.\n\tgo func() {\n\t\terr := server.Get(&pb.GetDestination{Scheme: \"k8s\", Path: fmt.Sprintf(\"%s:%d\", fqdn, port)}, stream)\n\t\tif err != nil {\n\t\t\terrs <- err\n\t\t}\n\t}()\n\n\taddr := fmt.Sprintf(\"%s:%d\", ip, port)\n\tparsedIP, err := netip.ParseAddr(ip)\n\tif err != nil {\n\t\tt.Fatalf(\"Invalid IP [%s]: %s\", ip, err)\n\t}\n\tif parsedIP.Is6() {\n\t\taddr = fmt.Sprintf(\"[%s]:%d\", ip, port)\n\t}\n\n\tselect {\n\tcase update := <-stream.updates:\n\t\tif updateAddAddress(t, update)[0] != addr {\n\t\t\tt.Fatalf(\"Expected %s but got %s\", addr, updateAddAddress(t, update)[0])\n\t\t}\n\n\t\tif len(stream.updates) != 0 {\n\t\t\tt.Fatalf(\"Expected 1 update but got %d: %v\", 1+len(stream.updates), stream.updates)\n\t\t}\n\tcase err := <-errs:\n\t\tt.Fatalf(\"Got error: %s\", err)\n\t}\n}\n\nfunc assertSingleProfile(t *testing.T, updates []*pb.DestinationProfile) *pb.DestinationProfile {\n\tt.Helper()\n\t// Under normal conditions the creation of resources by the fake API will\n\t// generate notifications that are discarded after the stream.Cancel() call,\n\t// but very rarely those notifications might come after, in which case we'll\n\t// get a second update.\n\tif len(updates) != 1 {\n\t\tt.Fatalf(\"Expected 1 profile update but got %d: %v\", len(updates), updates)\n\t}\n\treturn updates[0]\n}\n\nfunc profileStream(t *testing.T, server *server, host string, port uint32, token string) *bufferingGetProfileStream {\n\tt.Helper()\n\n\tstream := &bufferingGetProfileStream{\n\t\tupdates:          []*pb.DestinationProfile{},\n\t\tMockServerStream: util.NewMockServerStream(),\n\t}\n\n\tgo func() {\n\t\terr := server.GetProfile(&pb.GetDestination{\n\t\t\tScheme:       \"k8s\",\n\t\t\tPath:         gonet.JoinHostPort(host, fmt.Sprintf(\"%d\", port)),\n\t\t\tContextToken: token,\n\t\t}, stream)\n\t\tif err != nil {\n\t\t\tlogging.Fatalf(\"Got error: %s\", err)\n\t\t}\n\t}()\n\t// Give GetProfile some slack\n\ttime.Sleep(50 * time.Millisecond)\n\n\treturn stream\n}\n\nfunc getLastProfileUpdate(t *testing.T, stream *bufferingGetProfileStream, expectedUpdates int) *pb.DestinationProfile {\n\tt.Helper()\n\n\terr := testutil.RetryFor(time.Second*10, func() error {\n\t\tupdates := stream.Updates()\n\t\tif len(updates) < expectedUpdates {\n\t\t\treturn fmt.Errorf(\"expected %d updates, got %d\", expectedUpdates, len(updates))\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn stream.Updates()[expectedUpdates-1]\n}\n"
  },
  {
    "path": "controller/api/destination/syncronized_get_stream.go",
    "content": "package destination\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\tlogging \"github.com/sirupsen/logrus\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\n// synchronizedGetStream is a wrapper around a pb.Destination_GetServer that\n// makes Send safe to call from multiple goroutines. It does this by using an\n// unbuffered channel to synchronize calls to Send. Since this channel is\n// unbuffered, calls to Send may block and callers should do their own queuing.\n// This type implemenets the pb.Destination_GetServer interface but only the\n// Send method is supported. Calls to other methods will panic.\ntype synchronizedGetStream struct {\n\tdone  chan struct{}\n\tch    chan *pb.Update\n\tinner pb.Destination_GetServer\n\tlog   *logging.Entry\n}\n\nvar errStreamStopped = errors.New(\"synchronized stream stopped\")\n\nfunc newSyncronizedGetStream(stream pb.Destination_GetServer, log *logging.Entry) *synchronizedGetStream {\n\treturn &synchronizedGetStream{\n\t\tdone:  make(chan struct{}),\n\t\tch:    make(chan *pb.Update),\n\t\tinner: stream,\n\t\tlog:   log,\n\t}\n}\n\nfunc (s *synchronizedGetStream) SetHeader(metadata.MD) error {\n\tpanic(\"SetHeader called on synchronizedGetStream\")\n}\nfunc (s *synchronizedGetStream) SendHeader(metadata.MD) error {\n\tpanic(\"SendHeader called on synchronizedGetStream\")\n}\nfunc (s *synchronizedGetStream) SetTrailer(metadata.MD) {\n\tpanic(\"SetTrailer called on synchronizedGetStream\")\n}\nfunc (s synchronizedGetStream) Context() context.Context {\n\tpanic(\"Conext called on synchronizedGetStream\")\n}\nfunc (s *synchronizedGetStream) SendMsg(m any) error {\n\tpanic(\"SendMsg called on synchronizedGetStream\")\n}\nfunc (s *synchronizedGetStream) RecvMsg(m any) error {\n\tpanic(\"RecvMsg called on synchronizedGetStream\")\n}\n\nfunc (s *synchronizedGetStream) Send(update *pb.Update) error {\n\tselect {\n\tcase <-s.done:\n\t\treturn errStreamStopped\n\tcase s.ch <- update:\n\t\treturn nil\n\t}\n}\n\nfunc (s *synchronizedGetStream) Start() {\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-s.done:\n\t\t\t\treturn\n\t\t\tcase update := <-s.ch:\n\t\t\t\terr := s.inner.Send(update)\n\t\t\t\tif err != nil {\n\t\t\t\t\ts.log.Errorf(\"Error sending update: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (s *synchronizedGetStream) Stop() {\n\tclose(s.done)\n}\n"
  },
  {
    "path": "controller/api/destination/syncronized_get_stream_test.go",
    "content": "package destination\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2/controller/api/util\"\n\tlogging \"github.com/sirupsen/logrus\"\n)\n\ntype blockingDestinationGetServer struct {\n\tutil.MockServerStream\n\tblock      chan struct{}\n\tsendCalled chan struct{}\n\tonce       sync.Once\n}\n\nfunc newBlockingDestinationGetServer() *blockingDestinationGetServer {\n\treturn &blockingDestinationGetServer{\n\t\tblock:      make(chan struct{}),\n\t\tsendCalled: make(chan struct{}),\n\t}\n}\n\nfunc (b *blockingDestinationGetServer) Send(update *pb.Update) error {\n\tb.once.Do(func() {\n\t\tclose(b.sendCalled)\n\t})\n\t<-b.block\n\treturn nil\n}\n\nfunc (b *blockingDestinationGetServer) unblock() {\n\tclose(b.block)\n}\n\n// TestSynchronizedGetStreamSendAfterStop ensures Send returns promptly once the\n// stream has been stopped so goroutines don't leak waiting on an unconsumed\n// channel send.\nfunc TestSynchronizedGetStreamSendAfterStop(t *testing.T) {\n\tmock := &mockDestinationGetServer{\n\t\tupdatesReceived: make(chan *pb.Update, 1),\n\t}\n\tstream := newSyncronizedGetStream(mock, logging.WithField(\"test\", t.Name()))\n\tstream.Start()\n\tstream.Stop()\n\n\terrCh := make(chan error, 1)\n\tgo func() {\n\t\terrCh <- stream.Send(&pb.Update{})\n\t}()\n\n\tselect {\n\tcase err := <-errCh:\n\t\tif !errors.Is(err, errStreamStopped) {\n\t\t\tt.Fatalf(\"expected errStreamStopped, got %v\", err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"Send blocked after Stop\")\n\t}\n}\n\nfunc TestSynchronizedGetStreamStopWhileInnerSendBlocked(t *testing.T) {\n\tmock := newBlockingDestinationGetServer()\n\tstream := newSyncronizedGetStream(mock, logging.WithField(\"test\", t.Name()))\n\tstream.Start()\n\n\tfirstSend := make(chan error, 1)\n\tgo func() {\n\t\tfirstSend <- stream.Send(&pb.Update{})\n\t}()\n\n\tselect {\n\tcase err := <-firstSend:\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unexpected error from initial Send: %v\", err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"initial Send did not complete\")\n\t}\n\n\tselect {\n\tcase <-mock.sendCalled:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"inner Send was not invoked\")\n\t}\n\n\tstream.Stop()\n\n\tsecondSend := make(chan error, 1)\n\tgo func() {\n\t\tsecondSend <- stream.Send(&pb.Update{})\n\t}()\n\n\tselect {\n\tcase err := <-secondSend:\n\t\tif !errors.Is(err, errStreamStopped) {\n\t\t\tt.Fatalf(\"expected errStreamStopped, got %v\", err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"second Send blocked after Stop\")\n\t}\n\n\tmock.unblock()\n}\n"
  },
  {
    "path": "controller/api/destination/test_util.go",
    "content": "package destination\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\t\"github.com/linkerd/linkerd2/controller/api/util\"\n\tl5dcrdclient \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\tlogging \"github.com/sirupsen/logrus\"\n)\n\nfunc makeServer(t *testing.T) *server {\n\tt.Helper()\n\tsrv, _ := getServerWithClient(t)\n\treturn srv\n}\n\nfunc getServerWithClient(t *testing.T) (*server, l5dcrdclient.Interface) {\n\tt.Helper()\n\tmeshedPodResources := []string{`\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: ns`,\n\t\t`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ipFamilies:\n  - IPv4\n  clusterIP: 172.17.12.0\n  clusterIPs:\n  - 172.17.12.0\n  ports:\n  - port: 8989`,\n\t\t`\napiVersion: discovery.k8s.io/v1\nkind: EndpointSlice\nmetadata:\n  name: name1-ipv4\n  namespace: ns\n  labels:\n    kubernetes.io/service-name: name1\naddressType: IPv4\nendpoints:\n- addresses:\n  - 172.17.0.12\n  targetRef:\n    kind: Pod\n    name: name1-1\n    namespace: ns\nports:\n- port: 8989\n  protocol: TCP`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: name1-1\n  namespace: ns\nstatus:\n  phase: Running\n  conditions:\n  - type: Ready\n    status: \"True\"\n  podIP: 172.17.0.12\n  podIPs:\n  - ip: 172.17.0.12\nspec:\n  containers:\n    - env:\n      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n        value: 0.0.0.0:4143\n      - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n        value: 0.0.0.0:4191\n      - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n        value: 0.0.0.0:4190\n      name: linkerd-proxy`,\n\t\t`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name2\n  namespace: ns\nspec:\n  type: LoadBalancer\n  clusterIP: 172.17.99.0\n  clusterIPs:\n  - 172.17.99.0\n  - 2001:db8::99\n  ports:\n  - port: 8989`,\n\t\t`\napiVersion: discovery.k8s.io/v1\nkind: EndpointSlice\nmetadata:\n  name: name2-ipv4\n  namespace: ns\n  labels:\n    kubernetes.io/service-name: name2\naddressType: IPv4\nendpoints:\n- addresses:\n  - 172.17.0.13\n  targetRef:\n    kind: Pod\n    name: name2-2\n    namespace: ns\nports:\n- port: 8989\n  protocol: TCP`,\n\t\t`\napiVersion: discovery.k8s.io/v1\nkind: EndpointSlice\nmetadata:\n  name: name2-ipv6\n  namespace: ns\n  labels:\n    kubernetes.io/service-name: name2\naddressType: IPv6\nendpoints:\n- addresses:\n  - 2001:db8::78\n  targetRef:\n    kind: Pod\n    name: name2-2\n    namespace: ns\nports:\n- port: 8989\n  protocol: TCP`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name2-2\n  namespace: ns\nstatus:\n  phase: Succeeded\n  podIP: 172.17.0.13\n  podIPs:\n  - ip: 172.17.0.13\n  - ip: 2001:db8::78`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name2-3\n  namespace: ns\nstatus:\n  phase: Failed\n  podIP: 172.17.0.13\n  podIPs:\n  - ip: 172.17.0.13`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name2-4\n  namespace: ns\n  deletionTimestamp: 2021-01-01T00:00:00Z\nstatus:\n  podIP: 172.17.0.13\n  podIPs:\n  - ip: 172.17.0.13`,\n\t\t`\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name1.ns.svc.mycluster.local\n  namespace: ns\nspec:\n  routes:\n  - name: route1\n    isRetryable: false\n    condition:\n      pathRegex: \"/a/b/c\"`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: name1-20\n  namespace: ns\nstatus:\n  phase: Pending\n  conditions:\n  - type: Ready\n    status: \"False\"\n  podIP: 172.17.0.20\n  podIPs:\n  - ip: 172.17.0.20\nspec:\n  containers:\n    - env:\n      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n        value: 0.0.0.0:4143\n      - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n        value: 0.0.0.0:4191\n      - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n        value: 0.0.0.0:4190\n      name: linkerd-proxy\n      ports:\n      - containerPort: 4191`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: name1-21\n  namespace: ns\nstatus:\n  phase: Pending\n  conditions:\n  - type: Ready\n    status: \"False\"\n  podIP: 172.17.0.21\n  podIPs:\n  - ip: 172.17.0.21\nspec:\n  initContainers:\n    - env:\n      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n        value: 0.0.0.0:4143\n      - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n        value: 0.0.0.0:4191\n      - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n        value: 0.0.0.0:4190\n      name: linkerd-proxy\n      ports:\n      - containerPort: 4191`,\n\t}\n\n\tclientSP := []string{\n\t\t`\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name1.ns.svc.mycluster.local\n  namespace: client-ns\nspec:\n  routes:\n  - name: route2\n    isRetryable: true\n    condition:\n      pathRegex: \"/x/y/z\"`,\n\t}\n\n\tunmeshedPod := `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name2\n  namespace: ns\nstatus:\n  phase: Running\n  conditions:\n  - type: Ready\n    status: \"True\"\n  podIP: 172.17.0.13\n  podIPs:\n  - ip: 172.17.0.13`\n\n\tmeshedOpaquePodResources := []string{\n\t\t`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name3\n  namespace: ns\nspec:\n  type: LoadBalancer\n  clusterIP: 172.17.12.1\n  ports:\n  - port: 4242`,\n\t\t`\napiVersion: discovery.k8s.io/v1\nkind: EndpointSlice\nmetadata:\n  name: name3\n  namespace: ns\n  labels:\n    kubernetes.io/service-name: name3\naddressType: IPv4\nendpoints:\n- addresses:\n  - 172.17.0.14\n  targetRef:\n    kind: Pod\n    name: name3\n    namespace: ns\nports:\n- port: 4242\n  protocol: TCP`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    config.linkerd.io/opaque-ports: \"4242\"\n  name: name3\n  namespace: ns\nstatus:\n  phase: Running\n  conditions:\n  - type: Ready\n    status: \"True\"\n  podIP: 172.17.0.14\n  podIPs:\n  - ip: 172.17.0.14\nspec:\n  containers:\n    - env:\n      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n        value: 0.0.0.0:4143\n      - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n        value: 0.0.0.0:4191\n      - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n        value: 0.0.0.0:4190\n      name: linkerd-proxy`,\n\t}\n\n\tmeshedOpaqueServiceResources := []string{\n\t\t`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name4\n  namespace: ns\n  annotations:\n    config.linkerd.io/opaque-ports: \"4242\"`,\n\t}\n\n\tmeshedSkippedPodResource := []string{\n\t\t`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name5\n  namespace: ns\nspec:\n  type: LoadBalancer\n  clusterIP: 172.17.13.1\n  ports:\n  - port: 24224`,\n\t\t`\napiVersion: discovery.k8s.io/v1\nkind: EndpointSlice\nmetadata:\n  name: name5\n  namespace: ns\n  labels:\n    kubernetes.io/service-name: name5\naddressType: IPv4\nendpoints:\n- addresses:\n  - 172.17.0.15\n  targetRef:\n    kind: Pod\n    name: name5\n    namespace: ns\nports:\n- port: 24224\n  protocol: TCP`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  annotations:\n    config.linkerd.io/skip-inbound-ports: \"24224\"\n  name: name5\n  namespace: ns\nstatus:\n  phase: Running\n  conditions:\n  - type: Ready\n    status: \"True\"\n  podIP: 172.17.0.15\n  podIPs:\n  - ip: 172.17.0.15\nspec:\n  containers:\n    - env:\n      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n        value: 0.0.0.0:4143\n      - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n        value: 0.0.0.0:4191\n      - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n        value: 0.0.0.0:4190\n      name: linkerd-proxy`,\n\t}\n\n\tmeshedStatefulSetPodResource := []string{\n\t\t`\napiVersion: v1\nkind: Service\nmetadata:\n  name: statefulset-svc\n  namespace: ns\nspec:\n  type: LoadBalancer\n  clusterIP: 172.17.13.5\n  ports:\n  - port: 8989`,\n\t\t`\napiVersion: discovery.k8s.io/v1\nkind: EndpointSlice\nmetadata:\n  name:\tstatefulset-svc\n  namespace: ns\n  labels:\n    kubernetes.io/service-name: statefulset-svc\naddressType: IPv4\nendpoints:\n- addresses:\n  - 172.17.13.14 # Endpoint without a targetRef or hostname\n- addresses:\n  - 172.17.13.15\n  hostname: pod-0\n  targetRef:\n    kind: Pod\n    name: pod-0\n    namespace: ns\nports:\n- port: 8989\n  protocol: TCP`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: pod-0\n  namespace: ns\nstatus:\n  phase: Running\n  conditions:\n  - type: Ready\n    status: \"True\"\n  podIP: 172.17.13.15\n  podIPs:\n  - ip: 172.17.13.15\nspec:\n  containers:\n    - env:\n      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n        value: 0.0.0.0:4143\n      - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n        value: 0.0.0.0:4191\n      - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n        value: 0.0.0.0:4190\n      name: linkerd-proxy`,\n\t}\n\n\tpolicyResources := []string{\n\t\t`\napiVersion: v1\nkind: Service\nmetadata:\n  name: policy-test\n  namespace: ns\nspec:\n  type: LoadBalancer\n  clusterIP: 172.17.12.2\n  ports:\n  - port: 80`,\n\t\t`\napiVersion: discovery.k8s.io/v1\nkind: EndpointSlice\nmetadata:\n  name: policy-test\n  namespace: ns\n  labels:\n    kubernetes.io/service-name: policy-test\naddressType: IPv4\nendpoints:\n- addresses:\n  - 172.17.0.16\n  targetRef:\n    kind: Pod\n    name: policy-test\n    namespace: ns\nports:\n- port: 80\n  protocol: TCP`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n    app: policy-test\n  name: policy-test\n  namespace: ns\nstatus:\n  phase: Running\n  conditions:\n  - type: Ready\n    status: \"True\"\n  podIP: 172.17.0.16\n  podIPs:\n  - ip: 172.17.0.16\nspec:\n  containers:\n    - name: linkerd-proxy\n      env:\n      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n        value: 0.0.0.0:4143\n      - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n        value: 0.0.0.0:4191\n      - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n        value: 0.0.0.0:4190\n    - name: app\n      image: nginx\n      ports:\n      - containerPort: 80\n        name: http\n        protocol: TCP`,\n\t\t`\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  name: policy-test\n  namespace: ns\nspec:\n  podSelector:\n    matchLabels:\n      app: policy-test\n  port: 80\n  proxyProtocol: opaque`,\n\t\t`\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  name: policy-test-external-workload\n  namespace: ns\nspec:\n  externalWorkloadSelector:\n    matchLabels:\n      app: external-workload-policy-test\n  port: 80\n  proxyProtocol: opaque`,\n\t}\n\n\tpolicyResourcesNativeSidecar := []string{\n\t\t`\napiVersion: v1\nkind: Service\nmetadata:\n  name: native\n  namespace: ns\nspec:\n  type: LoadBalancer\n  clusterIP: 172.17.12.4\n  ports:\n  - port: 80`,\n\t\t`\napiVersion: discovery.k8s.io/v1\nkind: EndpointSlice\nmetadata:\n  name: native\n  namespace: ns\n  labels:\n    kubernetes.io/service-name: native\naddressType: IPv4\nendpoints:\n- addresses:\n  - 172.17.0.18\n  targetRef:\n    kind: Pod\n    name: native\n    namespace: ns\nports:\n- port: 80\n  protocol: TCP`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n    app: native\n  name: native\n  namespace: ns\nstatus:\n  phase: Running\n  conditions:\n  - type: Ready\n    status: \"True\"\n  podIP: 172.17.0.18\n  podIPs:\n  - ip: 172.17.0.18\nspec:\n  initContainers:\n    - name: linkerd-proxy\n      env:\n      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n        value: 0.0.0.0:4143\n      - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n        value: 0.0.0.0:4191\n      - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n        value: 0.0.0.0:4190\n    - name: app\n      image: nginx\n      ports:\n      - containerPort: 80\n        name: http\n        protocol: TCP`,\n\t\t`\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  name: native\n  namespace: ns\nspec:\n  podSelector:\n    matchLabels:\n      app: native\n  port: http\n  proxyProtocol: opaque`,\n\t}\n\n\thostPortMapping := []string{\n\t\t`\nkind: Pod\napiVersion: v1\nmetadata:\n  name: hostport-mapping\n  namespace: ns\nstatus:\n  phase: Running\n  conditions:\n  - type: Ready\n    status: \"True\"\n  hostIP: 192.168.1.20\n  podIP: 172.17.0.17\n  podIPs:\n  - ip: 172.17.0.17\nspec:\n  containers:\n  - name: nginx\n    image: nginx\n    ports:\n    - containerPort: 80\n      hostPort: 7777\n      name: nginx-7777`,\n\t}\n\n\texportedServiceResources := []string{`\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: ns`,\n\t\t`\napiVersion: v1\nkind: Service\nmetadata:\n  name: foo\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 80`,\n\t\t`\napiVersion: discovery.k8s.io/v1\nkind: EndpointSlice\nmetadata:\n  name: foo\n  namespace: ns\n  labels:\n    kubernetes.io/service-name: foo\naddressType: IPv4\nendpoints:\n- addresses:\n  - 172.17.55.1\n  targetRef:\n    kind: Pod\n    name: foo-1\n    namespace: ns\nports:\n- port: 80\n  protocol: TCP`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: foo-1\n  namespace: ns\nstatus:\n  phase: Running\n  conditions:\n  - type: Ready\n    status: \"True\"\n  podIP: 172.17.55.1\n  podIPs:\n  - ip: 172.17.55.1\nspec:\n  containers:\n    - name: linkerd-proxy\n      env:\n      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n        value: 0.0.0.0:4143\n      - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n        value: 0.0.0.0:4191\n      - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n        value: 0.0.0.0:4190`,\n\t}\n\n\tdestinationCredentialsResources := []string{`\napiVersion: v1\ndata:\n  kubeconfig: V2UncmUgbm8gc3RyYW5nZXJzIHRvIGxvdmUKWW91IGtub3cgdGhlIHJ1bGVzIGFuZCBzbyBkbyBJIChkbyBJKQpBIGZ1bGwgY29tbWl0bWVudCdzIHdoYXQgSSdtIHRoaW5raW5nIG9mCllvdSB3b3VsZG4ndCBnZXQgdGhpcyBmcm9tIGFueSBvdGhlciBndXkKSSBqdXN0IHdhbm5hIHRlbGwgeW91IGhvdyBJJ20gZmVlbGluZwpHb3R0YSBtYWtlIHlvdSB1bmRlcnN0YW5kCk5ldmVyIGdvbm5hIGdpdmUgeW91IHVwCk5ldmVyIGdvbm5hIGxldCB5b3UgZG93bgpOZXZlciBnb25uYSBydW4gYXJvdW5kIGFuZCBkZXNlcnQgeW91Ck5ldmVyIGdvbm5hIG1ha2UgeW91IGNyeQpOZXZlciBnb25uYSBzYXkgZ29vZGJ5ZQpOZXZlciBnb25uYSB0ZWxsIGEgbGllIGFuZCBodXJ0IHlvdQpXZSd2ZSBrbm93biBlYWNoIG90aGVyIGZvciBzbyBsb25nCllvdXIgaGVhcnQncyBiZWVuIGFjaGluZywgYnV0IHlvdSdyZSB0b28gc2h5IHRvIHNheSBpdCAoc2F5IGl0KQpJbnNpZGUsIHdlIGJvdGgga25vdyB3aGF0J3MgYmVlbiBnb2luZyBvbiAoZ29pbmcgb24pCldlIGtub3cgdGhlIGdhbWUgYW5kIHdlJ3JlIGdvbm5hIHBsYXkgaXQKQW5kIGlmIHlvdSBhc2sgbWUgaG93IEknbSBmZWVsaW5nCkRvbid0IHRlbGwgbWUgeW91J3JlIHRvbyBibGluZCB0byBzZWUKTmV2ZXIgZ29ubmEgZ2l2ZSB5b3UgdXAKTmV2ZXIgZ29ubmEgbGV0IHlvdSBkb3duCk5ldmVyIGdvbm5hIHJ1biBhcm91bmQgYW5kIGRlc2VydCB5b3UKTmV2ZXIgZ29ubmEgbWFrZSB5b3UgY3J5Ck5ldmVyIGdvbm5hIHNheSBnb29kYnllCk5ldmVyIGdvbm5hIHRlbGwgYSBsaWUgYW5kIGh1cnQgeW91\nkind: Secret\nmetadata:\n  annotations:\n    multicluster.linkerd.io/cluster-domain: cluster.local\n    multicluster.linkerd.io/trust-domain: cluster.local\n  labels:\n    multicluster.linkerd.io/cluster-name: target\n  name: cluster-credentials-target\n  namespace: linkerd\ntype: mirror.linkerd.io/remote-kubeconfig`}\n\n\tmirrorServiceResources := []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: foo-target\n  namespace: ns\n  labels:\n    multicluster.linkerd.io/remote-discovery: target\n    multicluster.linkerd.io/remote-service: foo\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 80`,\n\t}\n\n\texternalWorkloads := []string{`\napiVersion: workload.linkerd.io/v1beta1\nkind: ExternalWorkload\nmetadata:\n  name: my-cool-workload\n  namespace: ns\n  annotations:\n    config.linkerd.io/opaque-ports: \"4242\"\nspec:\n  meshTLS:\n    identity: spiffe://some-domain/cool\n    serverName: server.local\n  workloadIPs:\n  - ip: 200.1.1.1\n  ports:\n  - port: 8989\n  - port: 4242\n  - name: linkerd-proxy\n    port: 4143\nstatus:\n  conditions:\n  - ready: true`,\n\t\t`\napiVersion: workload.linkerd.io/v1beta1\nkind: ExternalWorkload\nmetadata:\n  name: policy-test-workload\n  namespace: ns\n  labels:\n    app: external-workload-policy-test\nspec:\n  meshTLS:\n    identity: spiffe://some-domain/cool\n    serverName: server.local\n  workloadIPs:\n  - ip: 200.1.1.2\n  ports:\n  - port: 80\n  - name: linkerd-proxy\n    port: 4143\nstatus:\n  conditions:\n  ready: true`,\n\t\t`\napiVersion: v1\nkind: Service\nmetadata:\n  name: policy-test-external-workload\n  namespace: ns\nspec:\n  type: LoadBalancer\n  clusterIP: 172.17.12.3\n  ports:\n  - port: 80`,\n\t\t`\napiVersion: discovery.k8s.io/v1\nkind: EndpointSlice\nmetadata:\n  name: policy-test-external-workload\n  namespace: ns\n  labels:\n    kubernetes.io/service-name: policy-test-external-workload\naddressType: IPv4\nendpoints:\n- addresses:\n  - 200.1.1.2\n  targetRef:\n    kind: ExternalWorkload\n    name: policy-test-workload\n    namespace: ns\nports:\n- port: 80\n  protocol: TCP`,\n\t}\n\n\texternalNameResources := []string{\n\t\t`\napiVersion: v1\nkind: Service\nmetadata:\n  name: externalname\n  namespace: ns\nspec:\n  type: ExternalName\n  externalName: linkerd.io`,\n\t}\n\n\tipv6 := []string{\n\t\t`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name-ipv6\n  namespace: ns\nspec:\n  type: ClusterIP\n  ipFamilies:\n  - IPv6\n  clusterIP: 2001:db8::93\n  clusterIPs:\n  - 2001:db8::93\n  ports:\n  - port: 8989`,\n\t\t`\napiVersion: discovery.k8s.io/v1\nkind: EndpointSlice\nmetadata:\n  name: name-ipv6\n  namespace: ns\n  labels:\n    kubernetes.io/service-name: name-ipv6\naddressType: IPv6\nendpoints:\n- addresses:\n  - 2001:db8::68\n  targetRef:\n    kind: Pod\n    name: name-ipv6\n    namespace: ns\nports:\n- port: 8989\n  protocol: TCP`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: name-ipv6\n  namespace: ns\nstatus:\n  phase: Running\n  conditions:\n  - type: Ready\n    status: \"True\"\n  podIP: 2001:db8::68\n  podIPs:\n  - ip: 2001:db8::68\nspec:\n  containers:\n    - env:\n      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n        value: 0.0.0.0:4143\n      - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n        value: 0.0.0.0:4191\n      - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n        value: 0.0.0.0:4190\n      name: linkerd-proxy`,\n\t}\n\n\tdualStack := []string{\n\t\t`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name-ds\n  namespace: ns\nspec:\n  type: ClusterIP\n  ipFamilies:\n  - IPv4\n  - IPv6\n  clusterIP: 172.17.13.0\n  clusterIPs:\n  - 172.17.13.0\n  - 2001:db8::88\n  ports:\n  - port: 8989`,\n\t\t`\napiVersion: discovery.k8s.io/v1\nkind: EndpointSlice\nmetadata:\n  name: name-ds-ipv4\n  namespace: ns\n  labels:\n    kubernetes.io/service-name: name-ds\naddressType: IPv4\nendpoints:\n- addresses:\n  - 172.17.0.19\n  targetRef:\n    kind: Pod\n    name: name-ds\n    namespace: ns\nports:\n- port: 8989\n  protocol: TCP`,\n\t\t`\napiVersion: discovery.k8s.io/v1\nkind: EndpointSlice\nmetadata:\n  name: name-ds-ipv6\n  namespace: ns\n  labels:\n    kubernetes.io/service-name: name-ds\naddressType: IPv6\nendpoints:\n- addresses:\n  - 2001:db8::94\n  targetRef:\n    kind: Pod\n    name: name-ds\n    namespace: ns\nports:\n- port: 8989\n  protocol: TCP`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    linkerd.io/control-plane-ns: linkerd\n  name: name-ds\n  namespace: ns\nstatus:\n  phase: Running\n  conditions:\n  - type: Ready\n    status: \"True\"\n  podIP: 172.17.0.19\n  podIPs:\n  - ip: 172.17.0.19\n  - ip: 2001:db8::94\nspec:\n  containers:\n    - env:\n      - name: LINKERD2_PROXY_INBOUND_LISTEN_ADDR\n        value: 0.0.0.0:4143\n      - name: LINKERD2_PROXY_ADMIN_LISTEN_ADDR\n        value: 0.0.0.0:4191\n      - name: LINKERD2_PROXY_CONTROL_LISTEN_ADDR\n        value: 0.0.0.0:4190\n      name: linkerd-proxy`,\n\t\t`\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name-ds.ns.svc.mycluster.local\n  namespace: ns\nspec:\n  routes:\n  - name: route1\n    isRetryable: false\n    condition:\n      pathRegex: \"/a/b/c\"`,\n\t}\n\n\tres := append(meshedPodResources, clientSP...)\n\tres = append(res, unmeshedPod)\n\tres = append(res, meshedOpaquePodResources...)\n\tres = append(res, meshedOpaqueServiceResources...)\n\tres = append(res, meshedSkippedPodResource...)\n\tres = append(res, meshedStatefulSetPodResource...)\n\tres = append(res, policyResources...)\n\tres = append(res, policyResourcesNativeSidecar...)\n\tres = append(res, hostPortMapping...)\n\tres = append(res, mirrorServiceResources...)\n\tres = append(res, destinationCredentialsResources...)\n\tres = append(res, externalWorkloads...)\n\tres = append(res, externalNameResources...)\n\tres = append(res, ipv6...)\n\tres = append(res, dualStack...)\n\tk8sAPI, err := k8s.NewFakeAPIWithL5dClient(res...)\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeAPIWithL5dClient returned an error: %s\", err)\n\t}\n\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t}\n\tlog := logging.WithField(\"test\", t.Name())\n\t// logging.SetLevel(logging.TraceLevel)\n\tdefaultOpaquePorts := map[uint32]struct{}{\n\t\t25:    {},\n\t\t443:   {},\n\t\t587:   {},\n\t\t3306:  {},\n\t\t5432:  {},\n\t\t11211: {},\n\t}\n\n\terr = watcher.InitializeIndexers(k8sAPI)\n\tif err != nil {\n\t\tt.Fatalf(\"initializeIndexers returned an error: %s\", err)\n\t}\n\n\tworkloads, err := watcher.NewWorkloadWatcher(k8sAPI, metadataAPI, log, true, defaultOpaquePorts)\n\tif err != nil {\n\t\tt.Fatalf(\"can't create Workloads watcher: %s\", err)\n\t}\n\tendpoints, err := watcher.NewEndpointsWatcher(k8sAPI, metadataAPI, log, true, \"local\")\n\tif err != nil {\n\t\tt.Fatalf(\"can't create Endpoints watcher: %s\", err)\n\t}\n\topaquePorts, err := watcher.NewOpaquePortsWatcher(k8sAPI, log, defaultOpaquePorts)\n\tif err != nil {\n\t\tt.Fatalf(\"can't create opaque ports watcher: %s\", err)\n\t}\n\tprofiles, err := watcher.NewProfileWatcher(k8sAPI, log)\n\tif err != nil {\n\t\tt.Fatalf(\"can't create profile watcher: %s\", err)\n\t}\n\n\tprom := prometheus.NewRegistry()\n\tclusterStore, err := watcher.NewClusterStoreWithDecoder(k8sAPI.Client, \"linkerd\", true, watcher.CreateMockDecoder(exportedServiceResources...), prom)\n\tif err != nil {\n\t\tt.Fatalf(\"can't create cluster store: %s\", err)\n\t}\n\n\tfederatedServices, err := newFederatedServiceWatcher(k8sAPI, metadataAPI, &Config{StreamQueueCapacity: DefaultStreamQueueCapacity}, clusterStore, endpoints, log)\n\tif err != nil {\n\t\tt.Fatalf(\"can't create federated service watcher: %s\", err)\n\t}\n\n\t// Sync after creating watchers so that the indexers added get updated\n\t// properly\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\tclusterStore.Sync(nil)\n\n\treturn &server{\n\t\tpb.UnimplementedDestinationServer{},\n\t\tConfig{\n\t\t\tEnableH2Upgrade:     true,\n\t\t\tEnableIPv6:          true,\n\t\t\tControllerNS:        \"linkerd\",\n\t\t\tClusterDomain:       \"mycluster.local\",\n\t\t\tIdentityTrustDomain: \"trust.domain\",\n\t\t\tDefaultOpaquePorts:  defaultOpaquePorts,\n\t\t\tStreamQueueCapacity: DefaultStreamQueueCapacity,\n\t\t},\n\t\tworkloads,\n\t\tendpoints,\n\t\topaquePorts,\n\t\tprofiles,\n\t\tclusterStore,\n\t\tfederatedServices,\n\t\tk8sAPI,\n\t\tmetadataAPI,\n\t\tlog,\n\t\tmake(<-chan struct{}),\n\t}, k8sAPI.L5dClient\n}\n\ntype bufferingGetStream struct {\n\tupdates chan *pb.Update\n\tutil.MockServerStream\n}\n\nfunc (bgs *bufferingGetStream) Send(update *pb.Update) error {\n\tbgs.updates <- update\n\treturn nil\n}\n\ntype bufferingGetProfileStream struct {\n\tupdates []*pb.DestinationProfile\n\tutil.MockServerStream\n\tmu sync.Mutex\n}\n\nfunc (bgps *bufferingGetProfileStream) Send(profile *pb.DestinationProfile) error {\n\tbgps.mu.Lock()\n\tdefer bgps.mu.Unlock()\n\tbgps.updates = append(bgps.updates, profile)\n\treturn nil\n}\n\nfunc (bgps *bufferingGetProfileStream) Updates() []*pb.DestinationProfile {\n\tbgps.mu.Lock()\n\tdefer bgps.mu.Unlock()\n\treturn bgps.updates\n}\n\ntype mockDestinationGetServer struct {\n\tutil.MockServerStream\n\tupdatesReceived chan *pb.Update\n}\n\nfunc (m *mockDestinationGetServer) Send(update *pb.Update) error {\n\tm.updatesReceived <- update\n\treturn nil\n}\n\ntype mockDestinationGetProfileServer struct {\n\tutil.MockServerStream\n\tprofilesReceived chan *pb.DestinationProfile\n}\n\nfunc (m *mockDestinationGetProfileServer) Send(profile *pb.DestinationProfile) error {\n\tm.profilesReceived <- profile\n\treturn nil\n}\n\nfunc makeEndpointTranslator(t *testing.T) (*mockDestinationGetServer, *endpointTranslator) {\n\tt.Helper()\n\treturn makeEndpointTranslatorWithOpaqueTransport(t, false)\n}\n\nfunc makeEndpointTranslatorWithOpaqueTransport(t *testing.T, forceOpaqueTransport bool) (*mockDestinationGetServer, *endpointTranslator) {\n\tt.Helper()\n\tnode := `apiVersion: v1\nkind: Node\nmetadata:\n  annotations:\n    kubeadm.alpha.kubernetes.io/cri-socket: /run/containerd/containerd.sock\n    node.alpha.kubernetes.io/ttl: \"0\"\n  labels:\n    beta.kubernetes.io/arch: amd64\n    kubernetes.io/os: linux\n    kubernetes.io/arch: amd64\n    kubernetes.io/hostname: kind-worker\n    kubernetes.io/os: linux\n    topology.kubernetes.io/region: west\n    topology.kubernetes.io/zone: west-1a\n  name: test-123\n`\n\tmetadataAPI, err := k8s.NewFakeMetadataAPI([]string{node})\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t}\n\tmetadataAPI.Sync(nil)\n\n\tmockGetServer := &mockDestinationGetServer{updatesReceived: make(chan *pb.Update, 50)}\n\ttranslator, err := newEndpointTranslator(\n\t\t\"linkerd\",\n\t\t\"trust.domain\",\n\t\tforceOpaqueTransport,\n\t\ttrue,  // enableH2Upgrade\n\t\ttrue,  // enableEndpointFiltering\n\t\ttrue,  // enableIPv6\n\t\tfalse, // extEndpointZoneWeights\n\t\tnil,   // meshedHttp2ClientParams\n\t\t\"service-name.service-ns\",\n\t\t\"test-123\",\n\t\tmap[uint32]struct{}{},\n\t\tmetadataAPI,\n\t\tmockGetServer,\n\t\tnil,\n\t\tlogging.WithField(\"test\", t.Name()),\n\t\tDefaultStreamQueueCapacity,\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create endpoint translator: %s\", err)\n\t}\n\treturn mockGetServer, translator\n}\n"
  },
  {
    "path": "controller/api/destination/watcher/cluster_store.go",
    "content": "package watcher\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tpkgK8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/tools/cache\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n)\n\ntype (\n\t// ClusterStore indexes clusters in which remote service discovery may be\n\t// performed. For each store item, an EndpointsWatcher is created to read\n\t// state directly from the respective cluster's API Server. In addition,\n\t// each store item has some associated and immutable configuration that is\n\t// required for service discovery.\n\tClusterStore struct {\n\t\t// Protects against illegal accesses\n\t\tsync.RWMutex\n\n\t\tapi                  *k8s.API\n\t\tstore                map[string]remoteCluster\n\t\tenableEndpointSlices bool\n\t\tlog                  *logging.Entry\n\n\t\t// Function used to parse a kubeconfig from a byte buffer. Based on the\n\t\t// kubeconfig, it creates API Server clients\n\t\tdecodeFn configDecoder\n\t}\n\n\t// remoteCluster is a helper struct that represents a store item\n\tremoteCluster struct {\n\t\twatcher *EndpointsWatcher\n\t\tconfig  ClusterConfig\n\n\t\t// Used to signal shutdown to the associated watcher's informers\n\t\tstopCh chan<- struct{}\n\t}\n\n\t// clusterConfig holds immutable configuration for a given cluster\n\tClusterConfig struct {\n\t\tTrustDomain   string\n\t\tClusterDomain string\n\t}\n\n\t// configDecoder is the type of a function that given a byte buffer, returns\n\t// a pair of API Server clients. The cache uses this function to dynamically\n\t// create clients after discovering a Secret.\n\tconfigDecoder = func(data []byte, cluster string, enableEndpointSlices bool) (*k8s.API, *k8s.MetadataAPI, error)\n)\n\nconst (\n\tclusterNameLabel        = \"multicluster.linkerd.io/cluster-name\"\n\ttrustDomainAnnotation   = \"multicluster.linkerd.io/trust-domain\"\n\tclusterDomainAnnotation = \"multicluster.linkerd.io/cluster-domain\"\n)\n\n// NewClusterStore creates a new (empty) ClusterStore. It\n// requires a Kubernetes API Server client instantiated for the local cluster.\n//\n// When created, a pair of event handlers are registered for the local cluster's\n// Secret informer. The event handlers are responsible for driving the discovery\n// of remote clusters and their configuration\nfunc NewClusterStore(client kubernetes.Interface, namespace string, enableEndpointSlices bool) (*ClusterStore, error) {\n\treturn NewClusterStoreWithDecoder(client, namespace, enableEndpointSlices, decodeK8sConfigFromSecret, prometheus.DefaultRegisterer)\n}\n\nfunc (cs *ClusterStore) Sync(stopCh <-chan struct{}) {\n\tcs.api.Sync(stopCh)\n}\n\n// newClusterStoreWithDecoder is a helper function that allows the creation of a\n// store with an arbitrary `configDecoder` function.\nfunc NewClusterStoreWithDecoder(\n\tclient kubernetes.Interface,\n\tnamespace string, enableEndpointSlices bool,\n\tdecodeFn configDecoder,\n\tprom prometheus.Registerer,\n) (*ClusterStore, error) {\n\tapi := k8s.NewNamespacedAPI(client, nil, nil, namespace, \"local\", k8s.Secret)\n\n\tcs := &ClusterStore{\n\t\tstore: make(map[string]remoteCluster),\n\t\tlog: logging.WithFields(logging.Fields{\n\t\t\t\"component\": \"cluster-store\",\n\t\t}),\n\t\tenableEndpointSlices: enableEndpointSlices,\n\t\tapi:                  api,\n\t\tdecodeFn:             decodeFn,\n\t}\n\n\tif prom != nil {\n\t\tsizeGauge := prometheus.NewGaugeFunc(prometheus.GaugeOpts{\n\t\t\tName: \"cluster_store_size\",\n\t\t\tHelp: \"The number of linked clusters in the remote discovery cluster store\",\n\t\t}, func() float64 { return (float64)(len(cs.store)) })\n\t\tif err := prom.Register(sizeGauge); err != nil {\n\t\t\t// If we can't register the metric, log the error but continue\n\t\t\tcs.log.Warnf(\"Failed to register cluster_store_size metric: %v\", err)\n\t\t}\n\t}\n\n\t_, err := cs.api.Secret().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc: func(obj interface{}) {\n\t\t\tsecret, ok := obj.(*v1.Secret)\n\t\t\tif !ok {\n\t\t\t\tcs.log.Errorf(\"Error processing 'Secret' object: got %#v, expected *corev1.Secret\", secret)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif secret.Type != pkgK8s.MirrorSecretType {\n\t\t\t\tcs.log.Tracef(\"Skipping Add event for 'Secret' object %s/%s: invalid type %s\", secret.Namespace, secret.Name, secret.Type)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tclusterName, found := secret.GetLabels()[clusterNameLabel]\n\t\t\tif !found {\n\t\t\t\tcs.log.Tracef(\"Skipping Add event for 'Secret' object %s/%s: missing \\\"%s\\\" label\", secret.Namespace, secret.Name, clusterNameLabel)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err := cs.addCluster(clusterName, secret); err != nil {\n\t\t\t\tcs.log.Errorf(\"Error adding cluster %s to store: %v\", clusterName, err)\n\t\t\t}\n\t\t},\n\t\tDeleteFunc: func(obj interface{}) {\n\t\t\tsecret, ok := obj.(*v1.Secret)\n\t\t\tif !ok {\n\t\t\t\t// If the Secret was deleted when the watch was disconnected\n\t\t\t\t// (for whatever reason) and the event was missed, the object is\n\t\t\t\t// added with 'DeletedFinalStateUnknown'. Its state may be\n\t\t\t\t// stale, so it should be cleaned-up.\n\t\t\t\ttombstone, ok := obj.(cache.DeletedFinalStateUnknown)\n\t\t\t\tif !ok {\n\t\t\t\t\tcs.log.Debugf(\"Unable to get object from DeletedFinalStateUnknown %#v\", obj)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// If the zombie object is a `Secret` we can delete it.\n\t\t\t\tsecret, ok = tombstone.Obj.(*v1.Secret)\n\t\t\t\tif !ok {\n\t\t\t\t\tcs.log.Debugf(\"DeletedFinalStateUnknown contained object that is not a Secret %#v\", obj)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tclusterName, found := secret.GetLabels()[clusterNameLabel]\n\t\t\tif !found {\n\t\t\t\tcs.log.Tracef(\"Skipping Delete event for 'Secret' object %s/%s: missing \\\"%s\\\" label\", secret.Namespace, secret.Name, clusterNameLabel)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tcs.removeCluster(clusterName)\n\n\t\t},\n\t})\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cs, nil\n}\n\n// Get safely retrieves a store item given a cluster name.\nfunc (cs *ClusterStore) Get(clusterName string) (*EndpointsWatcher, ClusterConfig, bool) {\n\tcs.RLock()\n\tdefer cs.RUnlock()\n\tcw, found := cs.store[clusterName]\n\treturn cw.watcher, cw.config, found\n}\n\n// removeCluster is triggered by the cache's Secret informer when a secret is\n// removed. Given a cluster name, it removes the entry from the cache after\n// stopping the associated watcher.\nfunc (cs *ClusterStore) removeCluster(clusterName string) {\n\tcs.Lock()\n\tdefer cs.Unlock()\n\tr, found := cs.store[clusterName]\n\tif !found {\n\t\treturn\n\t}\n\tr.watcher.removeHandlers()\n\tr.watcher.k8sAPI.UnregisterGauges()\n\tr.watcher.metadataAPI.UnregisterGauges()\n\tclose(r.stopCh)\n\tdelete(cs.store, clusterName)\n\tcs.log.Infof(\"Removed cluster %s from ClusterStore\", clusterName)\n}\n\n// addCluster is triggered by the cache's Secret informer when a secret is\n// discovered for the first time. Given a cluster name and a Secret\n// object, it creates an EndpointsWatcher for a remote cluster and syncs its\n// informers before returning.\nfunc (cs *ClusterStore) addCluster(clusterName string, secret *v1.Secret) error {\n\tdata, found := secret.Data[pkgK8s.ConfigKeyName]\n\tif !found {\n\t\treturn errors.New(\"missing kubeconfig file\")\n\t}\n\n\tclusterDomain, found := secret.GetAnnotations()[clusterDomainAnnotation]\n\tif !found {\n\t\treturn fmt.Errorf(\"missing \\\"%s\\\" annotation\", clusterDomainAnnotation)\n\t}\n\n\ttrustDomain, found := secret.GetAnnotations()[trustDomainAnnotation]\n\tif !found {\n\t\treturn fmt.Errorf(\"missing \\\"%s\\\" annotation\", trustDomainAnnotation)\n\t}\n\n\tremoteAPI, metadataAPI, err := cs.decodeFn(data, clusterName, cs.enableEndpointSlices)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstopCh := make(chan struct{}, 1)\n\twatcher, err := NewEndpointsWatcher(\n\t\tremoteAPI,\n\t\tmetadataAPI,\n\t\tlogging.WithFields(logging.Fields{\n\t\t\t\"remote-cluster\": clusterName,\n\t\t}),\n\t\tcs.enableEndpointSlices,\n\t\tclusterName,\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcs.Lock()\n\tdefer cs.Unlock()\n\tcs.store[clusterName] = remoteCluster{\n\t\twatcher,\n\t\tClusterConfig{\n\t\t\ttrustDomain,\n\t\t\tclusterDomain,\n\t\t},\n\t\tstopCh,\n\t}\n\n\tgo remoteAPI.Sync(stopCh)\n\tgo metadataAPI.Sync(stopCh)\n\n\tcs.log.Infof(\"Added cluster %s to ClusterStore\", clusterName)\n\n\treturn nil\n}\n\n// decodeK8sConfigFromSecret implements the decoder function type. Given a byte\n// buffer, it attempts to parse it as a kubeconfig file. If successful, returns\n// a pair of API Server clients.\nfunc decodeK8sConfigFromSecret(data []byte, cluster string, enableEndpointSlices bool) (*k8s.API, *k8s.MetadataAPI, error) {\n\tcfg, err := clientcmd.RESTConfigFromKubeConfig(data)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tctx := context.Background()\n\tvar remoteAPI *k8s.API\n\tif enableEndpointSlices {\n\t\tremoteAPI, err = k8s.InitializeAPIForConfig(\n\t\t\tctx,\n\t\t\tcfg,\n\t\t\ttrue,\n\t\t\tcluster,\n\t\t\tk8s.ES, k8s.Pod, k8s.Svc, k8s.Srv,\n\t\t)\n\t} else {\n\t\tremoteAPI, err = k8s.InitializeAPIForConfig(\n\t\t\tctx,\n\t\t\tcfg,\n\t\t\ttrue,\n\t\t\tcluster,\n\t\t\tk8s.Endpoint, k8s.Pod, k8s.Svc, k8s.Srv,\n\t\t)\n\t}\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tmetadataAPI, err := k8s.InitializeMetadataAPIForConfig(cfg, cluster, k8s.RS)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn remoteAPI, metadataAPI, nil\n}\n"
  },
  {
    "path": "controller/api/destination/watcher/cluster_store_test.go",
    "content": "package watcher\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nfunc TestClusterStoreHandlers(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tname                 string\n\t\tk8sConfigs           []string\n\t\tenableEndpointSlices bool\n\t\texpectedClusters     map[string]ClusterConfig\n\t\tdeleteClusters       map[string]struct{}\n\t}{\n\t\t{\n\t\t\tname: \"add and remove remote watcher when Secret is valid\",\n\t\t\tk8sConfigs: []string{\n\t\t\t\tvalidRemoteSecret,\n\t\t\t},\n\t\t\tenableEndpointSlices: true,\n\t\t\texpectedClusters: map[string]ClusterConfig{\n\t\t\t\t\"remote\": {\n\t\t\t\t\tTrustDomain:   \"identity.org\",\n\t\t\t\t\tClusterDomain: \"cluster.local\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tdeleteClusters: map[string]struct{}{\n\t\t\t\t\"remote\": {},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"add and remove more than one watcher when Secrets are valid\",\n\t\t\tk8sConfigs: []string{\n\t\t\t\tvalidRemoteSecret,\n\t\t\t\tvalidTargetSecret,\n\t\t\t},\n\t\t\tenableEndpointSlices: false,\n\t\t\texpectedClusters: map[string]ClusterConfig{\n\t\t\t\t\"remote\": {\n\t\t\t\t\tTrustDomain:   \"identity.org\",\n\t\t\t\t\tClusterDomain: \"cluster.local\",\n\t\t\t\t},\n\t\t\t\t\"target\": {\n\t\t\t\t\tTrustDomain:   \"cluster.target.local\",\n\t\t\t\t\tClusterDomain: \"cluster.target.local\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tdeleteClusters: map[string]struct{}{\n\t\t\t\t\"remote\": {},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"malformed secrets shouldn't result in created watchers\",\n\t\t\tk8sConfigs: []string{\n\t\t\t\tvalidRemoteSecret,\n\t\t\t\tnoClusterSecret,\n\t\t\t\tnoDomainSecret,\n\t\t\t\tnoIdentitySecret,\n\t\t\t\tinvalidTypeSecret,\n\t\t\t},\n\t\t\tenableEndpointSlices: true,\n\t\t\texpectedClusters: map[string]ClusterConfig{\n\t\t\t\t\"remote\": {\n\t\t\t\t\tTrustDomain:   \"identity.org\",\n\t\t\t\t\tClusterDomain: \"cluster.local\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tdeleteClusters: map[string]struct{}{\n\t\t\t\t\"remote\": {},\n\t\t\t},\n\t\t},\n\t} {\n\t\ttt := tt // Pin\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tprom := prometheus.NewRegistry()\n\t\t\tcs, err := NewClusterStoreWithDecoder(k8sAPI.Client, \"linkerd\", tt.enableEndpointSlices, CreateMockDecoder(), prom)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error when starting watcher cache: %s\", err)\n\t\t\t}\n\n\t\t\tcs.Sync(nil)\n\n\t\t\t// Wait for the update to be processed because there is no blocking call currently in k8s that we can wait on\n\t\t\terr = testutil.RetryFor(time.Second*30, func() error {\n\n\t\t\t\tcs.RLock()\n\t\t\t\tactualLen := len(cs.store)\n\t\t\t\tcs.RUnlock()\n\n\t\t\t\tif actualLen != len(tt.expectedClusters) {\n\t\t\t\t\treturn fmt.Errorf(\"expected to see %d cache entries, got: %d\", len(tt.expectedClusters), actualLen)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\tfor k, expected := range tt.expectedClusters {\n\t\t\t\t_, cfg, found := cs.Get(k)\n\t\t\t\tif !found {\n\t\t\t\t\tt.Fatalf(\"Unexpected error: cluster %s is missing from the cache\", k)\n\t\t\t\t}\n\n\t\t\t\tif cfg.ClusterDomain != expected.ClusterDomain {\n\t\t\t\t\tt.Fatalf(\"Unexpected error: expected cluster domain %s for cluster '%s', got: %s\", expected.ClusterDomain, k, cfg.ClusterDomain)\n\t\t\t\t}\n\n\t\t\t\tif cfg.TrustDomain != expected.TrustDomain {\n\t\t\t\t\tt.Fatalf(\"Unexpected error: expected cluster domain %s for cluster '%s', got: %s\", expected.TrustDomain, k, cfg.TrustDomain)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle delete events\n\t\t\tfor k := range tt.deleteClusters {\n\t\t\t\twatcher, _, found := cs.Get(k)\n\t\t\t\tif !found {\n\t\t\t\t\tt.Fatalf(\"Unexpected error: watcher %s should exist in the cache\", k)\n\t\t\t\t}\n\t\t\t\t// Unfortunately, mock k8s client does not propagate\n\t\t\t\t// deletes, so we have to call remove directly.\n\t\t\t\tcs.removeCluster(k)\n\t\t\t\t// Leave it to do its thing and gracefully shutdown\n\t\t\t\terr = testutil.RetryFor(time.Second*30, func() error {\n\t\t\t\t\tvar hasStopped bool\n\t\t\t\t\tif tt.enableEndpointSlices {\n\t\t\t\t\t\thasStopped = watcher.k8sAPI.ES().Informer().IsStopped()\n\t\t\t\t\t} else {\n\t\t\t\t\t\thasStopped = watcher.k8sAPI.Endpoint().Informer().IsStopped()\n\t\t\t\t\t}\n\t\t\t\t\tif !hasStopped {\n\t\t\t\t\t\treturn fmt.Errorf(\"informers for watcher %s should be stopped\", k)\n\t\t\t\t\t}\n\n\t\t\t\t\tif _, _, found := cs.Get(k); found {\n\t\t\t\t\t\treturn fmt.Errorf(\"watcher %s should have been removed from the cache\", k)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t\t}\n\n\t\t\t}\n\t\t})\n\t}\n}\n\nvar validRemoteSecret = `\napiVersion: v1\nkind: Secret\ntype: mirror.linkerd.io/remote-kubeconfig\nmetadata:\n  namespace: linkerd\n  name: remote-cluster-credentials\n  labels:\n    multicluster.linkerd.io/cluster-name: remote\n  annotations:\n    multicluster.linkerd.io/trust-domain: identity.org\n    multicluster.linkerd.io/cluster-domain: cluster.local\ndata:\n  kubeconfig: dmVyeSB0b3Agc2VjcmV0IGluZm9ybWF0aW9uIGhlcmUK\n`\n\nvar validTargetSecret = `\napiversion: v1\nkind: Secret\ntype: mirror.linkerd.io/remote-kubeconfig\nmetadata:\n  namespace: linkerd\n  name: target-cluster-credentials\n  labels:\n    multicluster.linkerd.io/cluster-name: target\n  annotations:\n    multicluster.linkerd.io/trust-domain: cluster.target.local\n    multicluster.linkerd.io/cluster-domain: cluster.target.local\ndata:\n  kubeconfig: dmvyesb0b3agc2vjcmv0igluzm9ybwf0aw9uighlcmuk\n`\n\nvar noDomainSecret = `\napiVersion: v1\nkind: Secret\ntype: mirror.linkerd.io/remote-kubeconfig\nmetadata:\n  namespace: linkerd\n  name: target-1-cluster-credentials\n  labels:\n    multicluster.linkerd.io/cluster-name: target-1\n  annotations:\n    multicluster.linkerd.io/trust-domain: cluster.local\ndata:\n  kubeconfig: dmVyeSB0b3Agc2VjcmV0IGluZm9ybWF0aW9uIGhlcmUK\n`\n\nvar noClusterSecret = `\napiVersion: v1\nkind: Secret\ntype: mirror.linkerd.io/remote-kubeconfig\nmetadata:\n  namespace: linkerd\n  name: target-2-cluster-credentials\n  annotations:\n    multicluster.linkerd.io/trust-domain: cluster.local\n    multicluster.linkerd.io/cluster-domain: cluster.local\ndata:\n  kubeconfig: dmVyeSB0b3Agc2VjcmV0IGluZm9ybWF0aW9uIGhlcmUK\n`\n\nvar noIdentitySecret = `\napiversion: v1\nkind: Secret\ntype: mirror.linkerd.io/remote-kubeconfig\nmetadata:\n  namespace: linkerd\n  name: target-3-cluster-credentials\n  labels:\n    multicluster.linkerd.io/cluster-name: target-3\n  annotations:\n    multicluster.linkerd.io/cluster-domain: cluster.local\ndata:\n  kubeconfig: dmvyesb0b3agc2vjcmv0igluzm9ybwf0aw9uighlcmuk\n`\nvar invalidTypeSecret = `\napiversion: v1\nkind: Secret\ntype: kubernetes.io/tls\nmetadata:\n  namespace: linkerd\n  name: target-cluster-credentials\n  labels:\n    multicluster.linkerd.io/cluster-name: target\n  annotations:\n    multicluster.linkerd.io/trust-domain: cluster.local\n    multicluster.linkerd.io/cluster-domain: cluster.local\ndata:\n  kubeconfig: dmvyesb0b3agc2vjcmv0igluzm9ybwf0aw9uighlcmuk\n`\n"
  },
  {
    "path": "controller/api/destination/watcher/endpoints_watcher.go",
    "content": "package watcher\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tewv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta3\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tconsts \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tdiscovery \"k8s.io/api/discovery/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"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/client-go/tools/cache\"\n)\n\nconst (\n\t// metrics labels\n\tservice                = \"service\"\n\tnamespace              = \"namespace\"\n\ttargetCluster          = \"target_cluster\"\n\ttargetService          = \"target_service\"\n\ttargetServiceNamespace = \"target_service_namespace\"\n\n\topaqueProtocol = \"opaque\"\n)\n\nconst endpointTargetRefPod = \"Pod\"\nconst endpointTargetRefExternalWorkload = \"ExternalWorkload\"\n\ntype (\n\t// Address represents an individual port on a specific endpoint.\n\t// This endpoint might be the result of a the existence of a pod\n\t// that is targeted by this service; alternatively it can be the\n\t// case that this endpoint is not associated with a pod and maps\n\t// to some other IP (i.e. a remote service gateway)\n\tAddress struct {\n\t\tIP                string\n\t\tPort              Port\n\t\tPod               *corev1.Pod\n\t\tExternalWorkload  *ewv1beta1.ExternalWorkload\n\t\tOwnerName         string\n\t\tOwnerKind         string\n\t\tIdentity          string\n\t\tAuthorityOverride string\n\t\tZone              *string\n\t\tForZones          []discovery.ForZone\n\t\tOpaqueProtocol    bool\n\t}\n\n\t// AddressSet is a set of Address, indexed by ID.\n\t// The ID can be either:\n\t// 1) A reference to service: id.Name contains both the service name and\n\t// the target IP and port (see newServiceRefAddress)\n\t// 2) A reference to a pod: id.Name refers to the pod's name, and\n\t// id.IPFamily refers to the ES AddressType (see newPodRefAddress).\n\t// 3) A reference to an ExternalWorkload: id.Name refers to the EW's name.\n\tAddressSet struct {\n\t\tAddresses          map[ID]Address\n\t\tLabels             map[string]string\n\t\tLocalTrafficPolicy bool\n\t}\n\n\tportAndHostname struct {\n\t\tport     Port\n\t\thostname string\n\t}\n\n\t// EndpointsWatcher watches all endpoints and services in the Kubernetes\n\t// cluster.  Listeners can subscribe to a particular service and port and\n\t// EndpointsWatcher will publish the address set and all future changes for\n\t// that service:port.\n\tEndpointsWatcher struct {\n\t\tpublishers  map[ServiceID]*servicePublisher\n\t\tk8sAPI      *k8s.API\n\t\tmetadataAPI *k8s.MetadataAPI\n\n\t\tcluster              string\n\t\tlog                  *logging.Entry\n\t\tenableEndpointSlices bool\n\t\tsync.RWMutex         // This mutex protects modification of the map itself.\n\n\t\tinformerHandlers\n\t}\n\n\t// informerHandlers holds a registration handle for each informer handler\n\t// that has been registered for the EndpointsWatcher. The registration\n\t// handles are used to re-deregister informer handlers when the\n\t// EndpointsWatcher stops.\n\tinformerHandlers struct {\n\t\tepHandle  cache.ResourceEventHandlerRegistration\n\t\tsvcHandle cache.ResourceEventHandlerRegistration\n\t\tsrvHandle cache.ResourceEventHandlerRegistration\n\t}\n\n\t// servicePublisher represents a service.  It keeps a map of portPublishers\n\t// keyed by port and hostname.  This is because each watch on a service\n\t// will have a port and optionally may specify a hostname.  The port\n\t// and hostname will influence the endpoint set which is why a separate\n\t// portPublisher is required for each port and hostname combination.  The\n\t// service's port mapping will be applied to the requested port and the\n\t// mapped port will be used in the addresses set.  If a hostname is\n\t// requested, the address set will be filtered to only include addresses\n\t// with the requested hostname.\n\tservicePublisher struct {\n\t\tid                   ServiceID\n\t\tlog                  *logging.Entry\n\t\tk8sAPI               *k8s.API\n\t\tmetadataAPI          *k8s.MetadataAPI\n\t\tenableEndpointSlices bool\n\t\tlocalTrafficPolicy   bool\n\t\tcluster              string\n\t\tports                map[portAndHostname]*portPublisher\n\t\t// All access to the servicePublisher and its portPublishers is explicitly synchronized by\n\t\t// this mutex.\n\t\tsync.Mutex\n\t}\n\n\t// portPublisher represents a service along with a port and optionally a\n\t// hostname.  Multiple listeners may be subscribed to a portPublisher.\n\t// portPublisher maintains the current state of the address set and\n\t// publishes diffs to all listeners when updates come from either the\n\t// endpoints API or the service API.\n\tportPublisher struct {\n\t\tid                   ServiceID\n\t\ttargetPort           namedPort\n\t\tsrcPort              Port\n\t\thostname             string\n\t\tlog                  *logging.Entry\n\t\tk8sAPI               *k8s.API\n\t\tmetadataAPI          *k8s.MetadataAPI\n\t\tenableEndpointSlices bool\n\t\texists               bool\n\t\taddresses            AddressSet\n\t\tlisteners            []EndpointUpdateListener\n\t\tmetrics              endpointsMetrics\n\t\tlocalTrafficPolicy   bool\n\t}\n\n\t// EndpointUpdateListener is the interface that subscribers must implement.\n\tEndpointUpdateListener interface {\n\t\tAdd(set AddressSet)\n\t\tRemove(set AddressSet)\n\t\tNoEndpoints(exists bool)\n\t}\n)\n\nvar endpointsVecs = newEndpointsMetricsVecs()\n\nvar undefinedEndpointPort = Port(0)\n\n// shallowCopy returns a shallow copy of addr, in the sense that the Pod and\n// ExternalWorkload fields of the Addresses map values still point to the\n// locations of the original variable\nfunc (addr AddressSet) shallowCopy() AddressSet {\n\taddresses := make(map[ID]Address)\n\tfor k, v := range addr.Addresses {\n\t\taddresses[k] = v\n\t}\n\n\tlabels := make(map[string]string)\n\tfor k, v := range addr.Labels {\n\t\tlabels[k] = v\n\t}\n\n\treturn AddressSet{\n\t\tAddresses:          addresses,\n\t\tLabels:             labels,\n\t\tLocalTrafficPolicy: addr.LocalTrafficPolicy,\n\t}\n}\n\n// NewEndpointsWatcher creates an EndpointsWatcher and begins watching the\n// k8sAPI for pod, service, and endpoint changes. An EndpointsWatcher will\n// watch on Endpoints or EndpointSlice resources, depending on cluster configuration.\nfunc NewEndpointsWatcher(k8sAPI *k8s.API, metadataAPI *k8s.MetadataAPI, log *logging.Entry, enableEndpointSlices bool, cluster string) (*EndpointsWatcher, error) {\n\tew := &EndpointsWatcher{\n\t\tpublishers:           make(map[ServiceID]*servicePublisher),\n\t\tk8sAPI:               k8sAPI,\n\t\tmetadataAPI:          metadataAPI,\n\t\tenableEndpointSlices: enableEndpointSlices,\n\t\tcluster:              cluster,\n\t\tlog: log.WithFields(logging.Fields{\n\t\t\t\"component\": \"endpoints-watcher\",\n\t\t}),\n\t}\n\n\tvar err error\n\tew.svcHandle, err = k8sAPI.Svc().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc:    ew.addService,\n\t\tDeleteFunc: ew.deleteService,\n\t\tUpdateFunc: ew.updateService,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tew.srvHandle, err = k8sAPI.Srv().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc:    ew.addServer,\n\t\tDeleteFunc: ew.deleteServer,\n\t\tUpdateFunc: ew.updateServer,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif ew.enableEndpointSlices {\n\t\tew.log.Debugf(\"Watching EndpointSlice resources\")\n\t\tew.epHandle, err = k8sAPI.ES().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\t\tAddFunc:    ew.addEndpointSlice,\n\t\t\tDeleteFunc: ew.deleteEndpointSlice,\n\t\t\tUpdateFunc: ew.updateEndpointSlice,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t} else {\n\t\tew.log.Debugf(\"Watching Endpoints resources\")\n\t\tew.epHandle, err = k8sAPI.Endpoint().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\t\tAddFunc:    ew.addEndpoints,\n\t\t\tDeleteFunc: ew.deleteEndpoints,\n\t\t\tUpdateFunc: ew.updateEndpoints,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn ew, nil\n}\n\n////////////////////////\n/// EndpointsWatcher ///\n////////////////////////\n\n// Subscribe to an authority.\n// The provided listener will be updated each time the address set for the\n// given authority is changed.\nfunc (ew *EndpointsWatcher) Subscribe(id ServiceID, port Port, hostname string, listener EndpointUpdateListener) error {\n\tsvc, _ := ew.k8sAPI.Svc().Lister().Services(id.Namespace).Get(id.Name)\n\tif svc != nil && svc.Spec.Type == corev1.ServiceTypeExternalName {\n\t\treturn invalidService(id.String())\n\t}\n\n\tif hostname == \"\" {\n\t\tew.log.Debugf(\"Establishing watch on endpoint [%s:%d]\", id, port)\n\t} else {\n\t\tew.log.Debugf(\"Establishing watch on endpoint [%s.%s:%d]\", hostname, id, port)\n\t}\n\n\tsp := ew.getOrNewServicePublisher(id)\n\n\treturn sp.subscribe(port, hostname, listener)\n}\n\n// Unsubscribe removes a listener from the subscribers list for this authority.\nfunc (ew *EndpointsWatcher) Unsubscribe(id ServiceID, port Port, hostname string, listener EndpointUpdateListener) {\n\tif hostname == \"\" {\n\t\tew.log.Debugf(\"Stopping watch on endpoint [%s:%d]\", id, port)\n\t} else {\n\t\tew.log.Debugf(\"Stopping watch on endpoint [%s.%s:%d]\", hostname, id, port)\n\t}\n\n\tsp, ok := ew.getServicePublisher(id)\n\tif !ok {\n\t\tew.log.Errorf(\"Cannot unsubscribe from unknown service [%s:%d]\", id, port)\n\t\treturn\n\t}\n\tsp.unsubscribe(port, hostname, listener)\n}\n\n// removeHandlers will de-register any event handlers used by the\n// EndpointsWatcher's informers.\nfunc (ew *EndpointsWatcher) removeHandlers() {\n\tew.Lock()\n\tdefer ew.Unlock()\n\tif ew.svcHandle != nil {\n\t\tif err := ew.k8sAPI.Svc().Informer().RemoveEventHandler(ew.svcHandle); err != nil {\n\t\t\tew.log.Errorf(\"Failed to remove Service informer event handlers: %s\", err)\n\t\t}\n\t}\n\n\tif ew.srvHandle != nil {\n\t\tif err := ew.k8sAPI.Srv().Informer().RemoveEventHandler(ew.srvHandle); err != nil {\n\t\t\tew.log.Errorf(\"Failed to remove Server informer event handlers: %s\", err)\n\t\t}\n\t}\n\n\tif ew.epHandle != nil {\n\t\tif ew.enableEndpointSlices {\n\t\t\tif err := ew.k8sAPI.ES().Informer().RemoveEventHandler(ew.epHandle); err != nil {\n\n\t\t\t\tew.log.Errorf(\"Failed to remove EndpointSlice informer event handlers: %s\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tif err := ew.k8sAPI.Endpoint().Informer().RemoveEventHandler(ew.epHandle); err != nil {\n\t\t\t\tew.log.Errorf(\"Failed to remove Endpoints informer event handlers: %s\", err)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (ew *EndpointsWatcher) addService(obj interface{}) {\n\tservice := obj.(*corev1.Service)\n\tid := ServiceID{\n\t\tNamespace: service.Namespace,\n\t\tName:      service.Name,\n\t}\n\n\tsp := ew.getOrNewServicePublisher(id)\n\n\tsp.updateService(service)\n}\n\nfunc (ew *EndpointsWatcher) updateService(oldObj interface{}, newObj interface{}) {\n\toldService := oldObj.(*corev1.Service)\n\tnewService := newObj.(*corev1.Service)\n\n\toldUpdated := latestUpdated(oldService.ManagedFields)\n\tupdated := latestUpdated(newService.ManagedFields)\n\tif !updated.IsZero() && updated != oldUpdated {\n\t\tdelta := time.Since(updated)\n\t\tserviceInformerLag.Observe(delta.Seconds())\n\t}\n\n\tew.addService(newObj)\n}\n\nfunc (ew *EndpointsWatcher) deleteService(obj interface{}) {\n\tservice, ok := obj.(*corev1.Service)\n\tif !ok {\n\t\ttombstone, ok := obj.(cache.DeletedFinalStateUnknown)\n\t\tif !ok {\n\t\t\tew.log.Errorf(\"couldn't get object from DeletedFinalStateUnknown %#v\", obj)\n\t\t\treturn\n\t\t}\n\t\tservice, ok = tombstone.Obj.(*corev1.Service)\n\t\tif !ok {\n\t\t\tew.log.Errorf(\"DeletedFinalStateUnknown contained object that is not a Service %#v\", obj)\n\t\t\treturn\n\t\t}\n\t}\n\n\tid := ServiceID{\n\t\tNamespace: service.Namespace,\n\t\tName:      service.Name,\n\t}\n\n\tsp, ok := ew.getServicePublisher(id)\n\tif ok {\n\t\tsp.deleteEndpoints()\n\t}\n}\n\nfunc (ew *EndpointsWatcher) addEndpoints(obj interface{}) {\n\tendpoints, ok := obj.(*corev1.Endpoints)\n\tif !ok {\n\t\tew.log.Errorf(\"error processing endpoints resource, got %#v expected *corev1.Endpoints\", obj)\n\t\treturn\n\t}\n\n\tid := ServiceID{Namespace: endpoints.Namespace, Name: endpoints.Name}\n\tsp := ew.getOrNewServicePublisher(id)\n\tsp.updateEndpoints(endpoints)\n}\n\nfunc (ew *EndpointsWatcher) updateEndpoints(oldObj interface{}, newObj interface{}) {\n\toldEndpoints, ok := oldObj.(*corev1.Endpoints)\n\tif !ok {\n\t\tew.log.Errorf(\"error processing endpoints resource, got %#v expected *corev1.Endpoints\", oldObj)\n\t\treturn\n\t}\n\tnewEndpoints, ok := newObj.(*corev1.Endpoints)\n\tif !ok {\n\t\tew.log.Errorf(\"error processing endpoints resource, got %#v expected *corev1.Endpoints\", newObj)\n\t\treturn\n\t}\n\n\toldUpdated := latestUpdated(oldEndpoints.ManagedFields)\n\tupdated := latestUpdated(newEndpoints.ManagedFields)\n\tif !updated.IsZero() && updated != oldUpdated {\n\t\tdelta := time.Since(updated)\n\t\tendpointsInformerLag.Observe(delta.Seconds())\n\t}\n\n\tid := ServiceID{Namespace: newEndpoints.Namespace, Name: newEndpoints.Name}\n\tsp := ew.getOrNewServicePublisher(id)\n\tsp.updateEndpoints(newEndpoints)\n}\n\nfunc (ew *EndpointsWatcher) deleteEndpoints(obj interface{}) {\n\tendpoints, ok := obj.(*corev1.Endpoints)\n\tif !ok {\n\t\ttombstone, ok := obj.(cache.DeletedFinalStateUnknown)\n\t\tif !ok {\n\t\t\tew.log.Errorf(\"couldn't get object from DeletedFinalStateUnknown %#v\", obj)\n\t\t\treturn\n\t\t}\n\t\tendpoints, ok = tombstone.Obj.(*corev1.Endpoints)\n\t\tif !ok {\n\t\t\tew.log.Errorf(\"DeletedFinalStateUnknown contained object that is not an Endpoints %#v\", obj)\n\t\t\treturn\n\t\t}\n\t}\n\n\tid := ServiceID{\n\t\tNamespace: endpoints.Namespace,\n\t\tName:      endpoints.Name,\n\t}\n\n\tsp, ok := ew.getServicePublisher(id)\n\tif ok {\n\t\tsp.deleteEndpoints()\n\t}\n}\n\nfunc (ew *EndpointsWatcher) addEndpointSlice(obj interface{}) {\n\tnewSlice, ok := obj.(*discovery.EndpointSlice)\n\tif !ok {\n\t\tew.log.Errorf(\"error processing EndpointSlice resource, got %#v expected *discovery.EndpointSlice\", obj)\n\t\treturn\n\t}\n\n\tid, err := getEndpointSliceServiceID(newSlice)\n\tif err != nil {\n\t\tew.log.Errorf(\"Could not fetch resource service name:%v\", err)\n\t\treturn\n\t}\n\n\tsp := ew.getOrNewServicePublisher(id)\n\tsp.addEndpointSlice(newSlice)\n}\n\nfunc (ew *EndpointsWatcher) updateEndpointSlice(oldObj interface{}, newObj interface{}) {\n\toldSlice, ok := oldObj.(*discovery.EndpointSlice)\n\tif !ok {\n\t\tew.log.Errorf(\"error processing EndpointSlice resource, got %#v expected *discovery.EndpointSlice\", oldObj)\n\t\treturn\n\t}\n\tnewSlice, ok := newObj.(*discovery.EndpointSlice)\n\tif !ok {\n\t\tew.log.Errorf(\"error processing EndpointSlice resource, got %#v expected *discovery.EndpointSlice\", newObj)\n\t\treturn\n\t}\n\toldUpdated := latestUpdated(oldSlice.ManagedFields)\n\tupdated := latestUpdated(newSlice.ManagedFields)\n\tif !updated.IsZero() && updated != oldUpdated {\n\t\tdelta := time.Since(updated)\n\t\tendpointsliceInformerLag.Observe(delta.Seconds())\n\t}\n\n\tid, err := getEndpointSliceServiceID(newSlice)\n\tif err != nil {\n\t\tew.log.Errorf(\"Could not fetch resource service name:%v\", err)\n\t\treturn\n\t}\n\n\tsp, ok := ew.getServicePublisher(id)\n\tif ok {\n\t\tsp.updateEndpointSlice(oldSlice, newSlice)\n\t}\n}\n\nfunc (ew *EndpointsWatcher) deleteEndpointSlice(obj interface{}) {\n\tes, ok := obj.(*discovery.EndpointSlice)\n\tif !ok {\n\t\ttombstone, ok := obj.(cache.DeletedFinalStateUnknown)\n\t\tif !ok {\n\t\t\tew.log.Errorf(\"couldn't get object from DeletedFinalStateUnknown %#v\", obj)\n\t\t}\n\t\tes, ok = tombstone.Obj.(*discovery.EndpointSlice)\n\t\tif !ok {\n\t\t\tew.log.Errorf(\"DeletedFinalStateUnknown contained object that is not an EndpointSlice %#v\", obj)\n\t\t\treturn\n\t\t}\n\t}\n\n\tid, err := getEndpointSliceServiceID(es)\n\tif err != nil {\n\t\tew.log.Errorf(\"Could not fetch resource service name:%v\", err)\n\t}\n\n\tsp, ok := ew.getServicePublisher(id)\n\tif ok {\n\t\tsp.deleteEndpointSlice(es)\n\t}\n}\n\n// Returns the servicePublisher for the given id if it exists.  Otherwise,\n// create a new one and return it.\nfunc (ew *EndpointsWatcher) getOrNewServicePublisher(id ServiceID) *servicePublisher {\n\tew.Lock()\n\tdefer ew.Unlock()\n\n\t// If the service doesn't yet exist, create a stub for it so the listener can\n\t// be registered.\n\tsp, ok := ew.publishers[id]\n\tif !ok {\n\t\tsp = &servicePublisher{\n\t\t\tid: id,\n\t\t\tlog: ew.log.WithFields(logging.Fields{\n\t\t\t\t\"component\": \"service-publisher\",\n\t\t\t\t\"ns\":        id.Namespace,\n\t\t\t\t\"svc\":       id.Name,\n\t\t\t}),\n\t\t\tk8sAPI:               ew.k8sAPI,\n\t\t\tmetadataAPI:          ew.metadataAPI,\n\t\t\tcluster:              ew.cluster,\n\t\t\tports:                make(map[portAndHostname]*portPublisher),\n\t\t\tenableEndpointSlices: ew.enableEndpointSlices,\n\t\t}\n\t\tew.publishers[id] = sp\n\t}\n\treturn sp\n}\n\nfunc (ew *EndpointsWatcher) getServicePublisher(id ServiceID) (sp *servicePublisher, ok bool) {\n\tew.RLock()\n\tdefer ew.RUnlock()\n\tsp, ok = ew.publishers[id]\n\treturn\n}\n\nfunc (ew *EndpointsWatcher) addServer(obj interface{}) {\n\tew.Lock()\n\tdefer ew.Unlock()\n\tserver := obj.(*v1beta3.Server)\n\tfor _, sp := range ew.publishers {\n\t\tsp.updateServer(nil, server)\n\t}\n}\n\nfunc (ew *EndpointsWatcher) updateServer(oldObj interface{}, newObj interface{}) {\n\tew.Lock()\n\tdefer ew.Unlock()\n\n\toldServer := oldObj.(*v1beta3.Server)\n\tnewServer := newObj.(*v1beta3.Server)\n\tif oldServer != nil && newServer != nil {\n\t\toldUpdated := latestUpdated(oldServer.ManagedFields)\n\t\tupdated := latestUpdated(newServer.ManagedFields)\n\t\tif !updated.IsZero() && updated != oldUpdated {\n\t\t\tdelta := time.Since(updated)\n\t\t\tserverInformerLag.Observe(delta.Seconds())\n\t\t}\n\t}\n\n\tnamespace := \"\"\n\tif oldServer != nil {\n\t\tnamespace = oldServer.GetNamespace()\n\t}\n\tif newServer != nil {\n\t\tnamespace = newServer.GetNamespace()\n\t}\n\n\tfor id, sp := range ew.publishers {\n\t\t// Servers may only select workloads in their namespace.\n\t\tif id.Namespace == namespace {\n\t\t\tsp.updateServer(oldServer, newServer)\n\t\t}\n\t}\n}\n\nfunc (ew *EndpointsWatcher) deleteServer(obj interface{}) {\n\tew.Lock()\n\tdefer ew.Unlock()\n\tserver := obj.(*v1beta3.Server)\n\tfor _, sp := range ew.publishers {\n\t\tsp.updateServer(server, nil)\n\t}\n}\n\n////////////////////////\n/// servicePublisher ///\n////////////////////////\n\nfunc (sp *servicePublisher) updateEndpoints(newEndpoints *corev1.Endpoints) {\n\tsp.Lock()\n\tdefer sp.Unlock()\n\tsp.log.Debugf(\"Updating endpoints for %s\", sp.id)\n\tfor _, port := range sp.ports {\n\t\tport.updateEndpoints(newEndpoints)\n\t}\n}\n\nfunc (sp *servicePublisher) deleteEndpoints() {\n\tsp.Lock()\n\tdefer sp.Unlock()\n\tsp.log.Debugf(\"Deleting endpoints for %s\", sp.id)\n\tfor _, port := range sp.ports {\n\t\tport.noEndpoints(false)\n\t}\n}\n\nfunc (sp *servicePublisher) addEndpointSlice(newSlice *discovery.EndpointSlice) {\n\tsp.Lock()\n\tdefer sp.Unlock()\n\n\tsp.log.Debugf(\"Adding ES %s/%s\", newSlice.Namespace, newSlice.Name)\n\tfor _, port := range sp.ports {\n\t\tport.addEndpointSlice(newSlice)\n\t}\n}\n\nfunc (sp *servicePublisher) updateEndpointSlice(oldSlice *discovery.EndpointSlice, newSlice *discovery.EndpointSlice) {\n\tsp.Lock()\n\tdefer sp.Unlock()\n\n\tsp.log.Debugf(\"Updating ES %s/%s\", oldSlice.Namespace, oldSlice.Name)\n\tfor _, port := range sp.ports {\n\t\tport.updateEndpointSlice(oldSlice, newSlice)\n\t}\n}\n\nfunc (sp *servicePublisher) deleteEndpointSlice(es *discovery.EndpointSlice) {\n\tsp.Lock()\n\tdefer sp.Unlock()\n\n\tsp.log.Debugf(\"Deleting ES %s/%s\", es.Namespace, es.Name)\n\tfor _, port := range sp.ports {\n\t\tport.deleteEndpointSlice(es)\n\t}\n}\n\nfunc (sp *servicePublisher) updateService(newService *corev1.Service) {\n\tsp.Lock()\n\tdefer sp.Unlock()\n\tsp.log.Debugf(\"Updating service for %s\", sp.id)\n\n\t// set localTrafficPolicy to true if InternalTrafficPolicy is set to local\n\tif newService.Spec.InternalTrafficPolicy != nil {\n\t\tsp.localTrafficPolicy = *newService.Spec.InternalTrafficPolicy == corev1.ServiceInternalTrafficPolicyLocal\n\t} else {\n\t\tsp.localTrafficPolicy = false\n\t}\n\n\tfor key, port := range sp.ports {\n\t\tnewTargetPort := getTargetPort(newService, key.port)\n\t\tif newTargetPort != port.targetPort {\n\t\t\tport.updatePort(newTargetPort)\n\t\t}\n\t\t// update service endpoints with new localTrafficPolicy\n\t\tif port.localTrafficPolicy != sp.localTrafficPolicy {\n\t\t\tport.updateLocalTrafficPolicy(sp.localTrafficPolicy)\n\t\t}\n\t}\n\n}\n\nfunc (sp *servicePublisher) subscribe(srcPort Port, hostname string, listener EndpointUpdateListener) error {\n\tsp.Lock()\n\tdefer sp.Unlock()\n\n\tkey := portAndHostname{\n\t\tport:     srcPort,\n\t\thostname: hostname,\n\t}\n\tport, ok := sp.ports[key]\n\tif !ok {\n\t\tvar err error\n\t\tport, err = sp.newPortPublisher(srcPort, hostname)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsp.ports[key] = port\n\t}\n\tport.subscribe(listener)\n\treturn nil\n}\n\nfunc (sp *servicePublisher) unsubscribe(srcPort Port, hostname string, listener EndpointUpdateListener) {\n\tsp.Lock()\n\tdefer sp.Unlock()\n\n\tkey := portAndHostname{\n\t\tport:     srcPort,\n\t\thostname: hostname,\n\t}\n\tport, ok := sp.ports[key]\n\tif ok {\n\t\tport.unsubscribe(listener)\n\t\tif len(port.listeners) == 0 {\n\t\t\tendpointsVecs.unregister(sp.metricsLabels(srcPort, hostname))\n\t\t\tdelete(sp.ports, key)\n\t\t}\n\t}\n}\n\nfunc (sp *servicePublisher) newPortPublisher(srcPort Port, hostname string) (*portPublisher, error) {\n\ttargetPort := intstr.FromInt(int(srcPort))\n\tsvc, err := sp.k8sAPI.Svc().Lister().Services(sp.id.Namespace).Get(sp.id.Name)\n\tif err != nil && !apierrors.IsNotFound(err) {\n\t\tsp.log.Errorf(\"error getting service: %s\", err)\n\t}\n\texists := false\n\tif err == nil {\n\t\ttargetPort = getTargetPort(svc, srcPort)\n\t\texists = true\n\t}\n\n\tlog := sp.log.WithField(\"port\", srcPort)\n\n\tmetrics, err := endpointsVecs.newEndpointsMetrics(sp.metricsLabels(srcPort, hostname))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tport := &portPublisher{\n\t\tlisteners:            []EndpointUpdateListener{},\n\t\ttargetPort:           targetPort,\n\t\tsrcPort:              srcPort,\n\t\thostname:             hostname,\n\t\texists:               exists,\n\t\tk8sAPI:               sp.k8sAPI,\n\t\tmetadataAPI:          sp.metadataAPI,\n\t\tlog:                  log,\n\t\tmetrics:              metrics,\n\t\tenableEndpointSlices: sp.enableEndpointSlices,\n\t\tlocalTrafficPolicy:   sp.localTrafficPolicy,\n\t}\n\n\tif port.enableEndpointSlices {\n\t\tmatchLabels := map[string]string{discovery.LabelServiceName: sp.id.Name}\n\t\tselector := labels.Set(matchLabels).AsSelector()\n\n\t\tsliceList, err := sp.k8sAPI.ES().Lister().EndpointSlices(sp.id.Namespace).List(selector)\n\t\tif err != nil && !apierrors.IsNotFound(err) {\n\t\t\tsp.log.Errorf(\"error getting endpointSlice list: %s\", err)\n\t\t}\n\t\tif err == nil {\n\t\t\tfor _, slice := range sliceList {\n\t\t\t\tport.addEndpointSlice(slice)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tendpoints, err := sp.k8sAPI.Endpoint().Lister().Endpoints(sp.id.Namespace).Get(sp.id.Name)\n\t\tif err != nil && !apierrors.IsNotFound(err) {\n\t\t\tsp.log.Errorf(\"error getting endpoints: %s\", err)\n\t\t}\n\t\tif err == nil {\n\t\t\tport.updateEndpoints(endpoints)\n\t\t}\n\t}\n\n\treturn port, nil\n}\n\nfunc (sp *servicePublisher) metricsLabels(port Port, hostname string) prometheus.Labels {\n\treturn endpointsLabels(sp.cluster, sp.id.Namespace, sp.id.Name, strconv.Itoa(int(port)), hostname)\n}\n\nfunc (sp *servicePublisher) updateServer(oldServer, newServer *v1beta3.Server) {\n\tsp.Lock()\n\tdefer sp.Unlock()\n\n\tfor _, pp := range sp.ports {\n\t\tpp.updateServer(oldServer, newServer)\n\t}\n}\n\n/////////////////////\n/// portPublisher ///\n/////////////////////\n\n// Note that portPublishers methods are generally NOT thread-safe.  You should\n// hold the parent servicePublisher's mutex before calling methods on a\n// portPublisher.\n\nfunc (pp *portPublisher) updateEndpoints(endpoints *corev1.Endpoints) {\n\tnewAddressSet := pp.endpointsToAddresses(endpoints)\n\tif len(newAddressSet.Addresses) == 0 {\n\t\tfor _, listener := range pp.listeners {\n\t\t\tlistener.NoEndpoints(true)\n\t\t}\n\t} else {\n\t\tadd, remove := diffAddresses(pp.addresses, newAddressSet)\n\t\tfor _, listener := range pp.listeners {\n\t\t\tif len(remove.Addresses) > 0 {\n\t\t\t\tlistener.Remove(remove)\n\t\t\t}\n\t\t\tif len(add.Addresses) > 0 {\n\t\t\t\tlistener.Add(add)\n\t\t\t}\n\t\t}\n\t}\n\tpp.addresses = newAddressSet\n\tpp.exists = true\n\tpp.metrics.incUpdates()\n\tpp.metrics.setPods(len(pp.addresses.Addresses))\n\tpp.metrics.setExists(true)\n}\n\nfunc (pp *portPublisher) addEndpointSlice(slice *discovery.EndpointSlice) {\n\tnewAddressSet := pp.endpointSliceToAddresses(slice)\n\tfor id, addr := range pp.addresses.Addresses {\n\t\tif _, ok := newAddressSet.Addresses[id]; !ok {\n\t\t\tnewAddressSet.Addresses[id] = addr\n\t\t}\n\t}\n\n\tadd, _ := diffAddresses(pp.addresses, newAddressSet)\n\tif len(add.Addresses) > 0 {\n\t\tfor _, listener := range pp.listeners {\n\t\t\tlistener.Add(add)\n\t\t}\n\t}\n\n\t// even if the ES doesn't have addresses yet we need to create a new\n\t// pp.addresses entry with the appropriate Labels and LocalTrafficPolicy,\n\t// which isn't going to be captured during the ES update event when\n\t// addresses get added\n\n\tpp.addresses = newAddressSet\n\tpp.exists = true\n\tpp.metrics.incUpdates()\n\tpp.metrics.setPods(len(pp.addresses.Addresses))\n\tpp.metrics.setExists(true)\n}\n\nfunc (pp *portPublisher) updateEndpointSlice(oldSlice *discovery.EndpointSlice, newSlice *discovery.EndpointSlice) {\n\tupdatedAddressSet := AddressSet{\n\t\tAddresses:          make(map[ID]Address),\n\t\tLabels:             pp.addresses.Labels,\n\t\tLocalTrafficPolicy: pp.localTrafficPolicy,\n\t}\n\n\tfor id, address := range pp.addresses.Addresses {\n\t\tupdatedAddressSet.Addresses[id] = address\n\t}\n\n\tfor _, id := range pp.endpointSliceToIDs(oldSlice) {\n\t\tdelete(updatedAddressSet.Addresses, id)\n\t}\n\n\tnewAddressSet := pp.endpointSliceToAddresses(newSlice)\n\tfor id, address := range newAddressSet.Addresses {\n\t\tupdatedAddressSet.Addresses[id] = address\n\t}\n\n\tadd, remove := diffAddresses(pp.addresses, updatedAddressSet)\n\tfor _, listener := range pp.listeners {\n\t\tif len(remove.Addresses) > 0 {\n\t\t\tlistener.Remove(remove)\n\t\t}\n\t\tif len(add.Addresses) > 0 {\n\t\t\tlistener.Add(add)\n\t\t}\n\t}\n\n\tpp.addresses = updatedAddressSet\n\tpp.exists = true\n\tpp.metrics.incUpdates()\n\tpp.metrics.setPods(len(pp.addresses.Addresses))\n\tpp.metrics.setExists(true)\n}\n\nfunc metricLabels(resource interface{}) map[string]string {\n\tvar serviceName, ns string\n\tvar resLabels, resAnnotations map[string]string\n\tswitch res := resource.(type) {\n\tcase *corev1.Endpoints:\n\t\t{\n\t\t\tserviceName, ns = res.Name, res.Namespace\n\t\t\tresLabels, resAnnotations = res.Labels, res.Annotations\n\t\t}\n\tcase *discovery.EndpointSlice:\n\t\t{\n\t\t\tserviceName, ns = res.Labels[discovery.LabelServiceName], res.Namespace\n\t\t\tresLabels, resAnnotations = res.Labels, res.Annotations\n\t\t}\n\t}\n\n\tlabels := map[string]string{service: serviceName, namespace: ns}\n\n\tremoteClusterName, hasRemoteClusterName := resLabels[consts.RemoteClusterNameLabel]\n\tserviceFqn, hasServiceFqn := resAnnotations[consts.RemoteServiceFqName]\n\n\tif hasRemoteClusterName {\n\t\t// this means we are looking at Endpoints created for the purpose of mirroring\n\t\t// an out of cluster service.\n\t\tlabels[targetCluster] = remoteClusterName\n\t\tif hasServiceFqn {\n\t\t\tfqParts := strings.Split(serviceFqn, \".\")\n\t\t\tif len(fqParts) >= 2 {\n\t\t\t\tlabels[targetService] = fqParts[0]\n\t\t\t\tlabels[targetServiceNamespace] = fqParts[1]\n\t\t\t}\n\t\t}\n\t}\n\treturn labels\n}\n\nfunc (pp *portPublisher) endpointSliceToAddresses(es *discovery.EndpointSlice) AddressSet {\n\tresolvedPort := pp.resolveESTargetPort(es.Ports)\n\tif resolvedPort == undefinedEndpointPort {\n\t\treturn AddressSet{\n\t\t\tLabels:             metricLabels(es),\n\t\t\tAddresses:          make(map[ID]Address),\n\t\t\tLocalTrafficPolicy: pp.localTrafficPolicy,\n\t\t}\n\t}\n\n\tserviceID, err := getEndpointSliceServiceID(es)\n\tif err != nil {\n\t\tpp.log.Errorf(\"Could not fetch resource service name:%v\", err)\n\t}\n\n\taddresses := make(map[ID]Address)\n\tfor _, endpoint := range es.Endpoints {\n\t\tif endpoint.Hostname != nil {\n\t\t\tif pp.hostname != \"\" && pp.hostname != *endpoint.Hostname {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif endpoint.Conditions.Ready != nil && !*endpoint.Conditions.Ready {\n\t\t\tcontinue\n\t\t}\n\n\t\tif endpoint.TargetRef == nil {\n\t\t\tfor _, IPAddr := range endpoint.Addresses {\n\t\t\t\tvar authorityOverride string\n\t\t\t\tif fqName, ok := es.Annotations[consts.RemoteServiceFqName]; ok {\n\t\t\t\t\tauthorityOverride = net.JoinHostPort(fqName, fmt.Sprintf(\"%d\", pp.srcPort))\n\t\t\t\t}\n\n\t\t\t\tidentity := es.Annotations[consts.RemoteGatewayIdentity]\n\t\t\t\taddress, id := pp.newServiceRefAddress(resolvedPort, IPAddr, serviceID.Name, es.Namespace)\n\t\t\t\taddress.Identity, address.AuthorityOverride = identity, authorityOverride\n\n\t\t\t\tif endpoint.Hints != nil {\n\t\t\t\t\tzones := make([]discovery.ForZone, len(endpoint.Hints.ForZones))\n\t\t\t\t\tcopy(zones, endpoint.Hints.ForZones)\n\t\t\t\t\taddress.ForZones = zones\n\t\t\t\t}\n\t\t\t\taddresses[id] = address\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif endpoint.TargetRef.Kind == endpointTargetRefPod {\n\t\t\tfor _, IPAddr := range endpoint.Addresses {\n\t\t\t\taddress, id, err := pp.newPodRefAddress(\n\t\t\t\t\tresolvedPort,\n\t\t\t\t\tes.AddressType,\n\t\t\t\t\tIPAddr,\n\t\t\t\t\tendpoint.TargetRef.Name,\n\t\t\t\t\tendpoint.TargetRef.Namespace,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpp.log.Errorf(\"Unable to create new address:%v\", err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\terr = SetToServerProtocol(pp.k8sAPI, &address, pp.log)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpp.log.Errorf(\"failed to set address OpaqueProtocol: %s\", err)\n\t\t\t\t}\n\n\t\t\t\taddress.Zone = endpoint.Zone\n\t\t\t\tif endpoint.Hints != nil {\n\t\t\t\t\tzones := make([]discovery.ForZone, len(endpoint.Hints.ForZones))\n\t\t\t\t\tcopy(zones, endpoint.Hints.ForZones)\n\t\t\t\t\taddress.ForZones = zones\n\t\t\t\t}\n\t\t\t\taddresses[id] = address\n\t\t\t}\n\t\t}\n\n\t\tif endpoint.TargetRef.Kind == endpointTargetRefExternalWorkload {\n\t\t\tfor _, IPAddr := range endpoint.Addresses {\n\t\t\t\taddress, id, err := pp.newExtRefAddress(resolvedPort, IPAddr, endpoint.TargetRef.Name, es.Namespace)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpp.log.Errorf(\"Unable to create new address: %v\", err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\terr = SetToServerProtocolExternalWorkload(pp.k8sAPI, &address)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpp.log.Errorf(\"failed to set address OpaqueProtocol: %s\", err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\taddress.Zone = endpoint.Zone\n\t\t\t\tif endpoint.Hints != nil {\n\t\t\t\t\tzones := make([]discovery.ForZone, len(endpoint.Hints.ForZones))\n\t\t\t\t\tcopy(zones, endpoint.Hints.ForZones)\n\t\t\t\t\taddress.ForZones = zones\n\t\t\t\t}\n\n\t\t\t\taddresses[id] = address\n\t\t\t}\n\n\t\t}\n\n\t}\n\treturn AddressSet{\n\t\tAddresses:          addresses,\n\t\tLabels:             metricLabels(es),\n\t\tLocalTrafficPolicy: pp.localTrafficPolicy,\n\t}\n}\n\n// endpointSliceToIDs is similar to endpointSliceToAddresses but instead returns\n// only the IDs of the endpoints rather than the addresses themselves.\nfunc (pp *portPublisher) endpointSliceToIDs(es *discovery.EndpointSlice) []ID {\n\tresolvedPort := pp.resolveESTargetPort(es.Ports)\n\tif resolvedPort == undefinedEndpointPort {\n\t\treturn []ID{}\n\t}\n\n\tserviceID, err := getEndpointSliceServiceID(es)\n\tif err != nil {\n\t\tpp.log.Errorf(\"Could not fetch resource service name:%v\", err)\n\t}\n\n\tids := []ID{}\n\tfor _, endpoint := range es.Endpoints {\n\t\tif endpoint.Hostname != nil {\n\t\t\tif pp.hostname != \"\" && pp.hostname != *endpoint.Hostname {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif endpoint.Conditions.Ready != nil && !*endpoint.Conditions.Ready {\n\t\t\tcontinue\n\t\t}\n\n\t\tif endpoint.TargetRef == nil {\n\t\t\tfor _, IPAddr := range endpoint.Addresses {\n\t\t\t\tids = append(ids, ServiceID{\n\t\t\t\t\tName: strings.Join([]string{\n\t\t\t\t\t\tserviceID.Name,\n\t\t\t\t\t\tIPAddr,\n\t\t\t\t\t\tfmt.Sprint(resolvedPort),\n\t\t\t\t\t}, \"-\"),\n\t\t\t\t\tNamespace: es.Namespace,\n\t\t\t\t})\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif endpoint.TargetRef.Kind == endpointTargetRefPod {\n\t\t\tids = append(ids, PodID{\n\t\t\t\tName:      endpoint.TargetRef.Name,\n\t\t\t\tNamespace: endpoint.TargetRef.Namespace,\n\t\t\t\tIPFamily:  corev1.IPFamily(es.AddressType),\n\t\t\t})\n\t\t} else if endpoint.TargetRef.Kind == endpointTargetRefExternalWorkload {\n\t\t\tids = append(ids, ExternalWorkloadID{\n\t\t\t\tName:      endpoint.TargetRef.Name,\n\t\t\t\tNamespace: endpoint.TargetRef.Namespace,\n\t\t\t})\n\t\t}\n\n\t}\n\treturn ids\n}\n\nfunc (pp *portPublisher) endpointsToAddresses(endpoints *corev1.Endpoints) AddressSet {\n\taddresses := make(map[ID]Address)\n\tfor _, subset := range endpoints.Subsets {\n\t\tresolvedPort := pp.resolveTargetPort(subset)\n\t\tif resolvedPort == undefinedEndpointPort {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, endpoint := range subset.Addresses {\n\t\t\tif pp.hostname != \"\" && pp.hostname != endpoint.Hostname {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif endpoint.TargetRef == nil {\n\t\t\t\tvar authorityOverride string\n\t\t\t\tif fqName, ok := endpoints.Annotations[consts.RemoteServiceFqName]; ok {\n\t\t\t\t\tauthorityOverride = fmt.Sprintf(\"%s:%d\", fqName, pp.srcPort)\n\t\t\t\t}\n\n\t\t\t\tidentity := endpoints.Annotations[consts.RemoteGatewayIdentity]\n\t\t\t\taddress, id := pp.newServiceRefAddress(resolvedPort, endpoint.IP, endpoints.Name, endpoints.Namespace)\n\t\t\t\taddress.Identity, address.AuthorityOverride = identity, authorityOverride\n\n\t\t\t\taddresses[id] = address\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif endpoint.TargetRef.Kind == endpointTargetRefPod {\n\t\t\t\taddress, id, err := pp.newPodRefAddress(\n\t\t\t\t\tresolvedPort,\n\t\t\t\t\t\"\",\n\t\t\t\t\tendpoint.IP,\n\t\t\t\t\tendpoint.TargetRef.Name,\n\t\t\t\t\tendpoint.TargetRef.Namespace,\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpp.log.Errorf(\"Unable to create new address:%v\", err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\terr = SetToServerProtocol(pp.k8sAPI, &address, pp.log)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpp.log.Errorf(\"failed to set address OpaqueProtocol: %s\", err)\n\t\t\t\t}\n\t\t\t\taddresses[id] = address\n\t\t\t}\n\t\t}\n\t}\n\treturn AddressSet{\n\t\tAddresses: addresses,\n\t\tLabels:    metricLabels(endpoints),\n\t}\n}\n\nfunc (pp *portPublisher) newServiceRefAddress(endpointPort Port, endpointIP, serviceName, serviceNamespace string) (Address, ServiceID) {\n\tid := ServiceID{\n\t\tName: strings.Join([]string{\n\t\t\tserviceName,\n\t\t\tendpointIP,\n\t\t\tfmt.Sprint(endpointPort),\n\t\t}, \"-\"),\n\t\tNamespace: serviceNamespace,\n\t}\n\n\treturn Address{IP: endpointIP, Port: endpointPort}, id\n}\n\nfunc (pp *portPublisher) newPodRefAddress(\n\tendpointPort Port,\n\tipFamily discovery.AddressType,\n\tendpointIP,\n\tpodName,\n\tpodNamespace string,\n) (Address, PodID, error) {\n\tid := PodID{\n\t\tName:      podName,\n\t\tNamespace: podNamespace,\n\t\tIPFamily:  corev1.IPFamily(ipFamily),\n\t}\n\tpod, err := pp.k8sAPI.Pod().Lister().Pods(id.Namespace).Get(id.Name)\n\tif err != nil {\n\t\treturn Address{}, PodID{}, fmt.Errorf(\"unable to fetch pod %v: %w\", id, err)\n\t}\n\townerKind, ownerName, err := pp.metadataAPI.GetOwnerKindAndName(context.Background(), pod, false)\n\tif err != nil {\n\t\treturn Address{}, PodID{}, err\n\t}\n\taddr := Address{\n\t\tIP:        endpointIP,\n\t\tPort:      endpointPort,\n\t\tPod:       pod,\n\t\tOwnerName: ownerName,\n\t\tOwnerKind: ownerKind,\n\t}\n\n\treturn addr, id, nil\n}\n\nfunc (pp *portPublisher) newExtRefAddress(endpointPort Port, endpointIP, externalWorkloadName, externalWorkloadNamespace string) (Address, ExternalWorkloadID, error) {\n\tid := ExternalWorkloadID{\n\t\tName:      externalWorkloadName,\n\t\tNamespace: externalWorkloadNamespace,\n\t}\n\n\tew, err := pp.k8sAPI.ExtWorkload().Lister().ExternalWorkloads(id.Namespace).Get(id.Name)\n\tif err != nil {\n\t\treturn Address{}, ExternalWorkloadID{}, fmt.Errorf(\"unable to fetch ExternalWorkload %v: %w\", id, err)\n\t}\n\n\taddr := Address{\n\t\tIP:               endpointIP,\n\t\tPort:             endpointPort,\n\t\tExternalWorkload: ew,\n\t}\n\n\townerRefs := ew.GetOwnerReferences()\n\tif len(ownerRefs) == 1 {\n\t\tparent := ownerRefs[0]\n\t\taddr.OwnerName = parent.Name\n\t\taddr.OwnerName = strings.ToLower(parent.Kind)\n\t}\n\n\treturn addr, id, nil\n}\n\nfunc (pp *portPublisher) resolveESTargetPort(slicePorts []discovery.EndpointPort) Port {\n\tif slicePorts == nil {\n\t\treturn undefinedEndpointPort\n\t}\n\n\tswitch pp.targetPort.Type {\n\tcase intstr.Int:\n\t\treturn Port(pp.targetPort.IntVal)\n\tcase intstr.String:\n\t\tfor _, p := range slicePorts {\n\t\t\tname := \"\"\n\t\t\tif p.Name != nil {\n\t\t\t\tname = *p.Name\n\t\t\t}\n\t\t\tif name == pp.targetPort.StrVal {\n\t\t\t\treturn Port(*p.Port)\n\t\t\t}\n\t\t}\n\t}\n\treturn undefinedEndpointPort\n}\n\nfunc (pp *portPublisher) resolveTargetPort(subset corev1.EndpointSubset) Port {\n\tswitch pp.targetPort.Type {\n\tcase intstr.Int:\n\t\treturn Port(pp.targetPort.IntVal)\n\tcase intstr.String:\n\t\tfor _, p := range subset.Ports {\n\t\t\tif p.Name == pp.targetPort.StrVal {\n\t\t\t\treturn Port(p.Port)\n\t\t\t}\n\t\t}\n\t}\n\treturn undefinedEndpointPort\n}\n\nfunc (pp *portPublisher) updateLocalTrafficPolicy(localTrafficPolicy bool) {\n\tpp.localTrafficPolicy = localTrafficPolicy\n\tpp.addresses.LocalTrafficPolicy = localTrafficPolicy\n\tfor _, listener := range pp.listeners {\n\t\tlistener.Add(pp.addresses.shallowCopy())\n\t}\n}\n\nfunc (pp *portPublisher) updatePort(targetPort namedPort) {\n\tpp.targetPort = targetPort\n\n\tif pp.enableEndpointSlices {\n\t\tmatchLabels := map[string]string{discovery.LabelServiceName: pp.id.Name}\n\t\tselector := labels.Set(matchLabels).AsSelector()\n\n\t\tendpointSlices, err := pp.k8sAPI.ES().Lister().EndpointSlices(pp.id.Namespace).List(selector)\n\t\tif err == nil {\n\t\t\tpp.addresses = AddressSet{}\n\t\t\tfor _, slice := range endpointSlices {\n\t\t\t\tpp.addEndpointSlice(slice)\n\t\t\t}\n\t\t} else {\n\t\t\tpp.log.Errorf(\"Unable to get EndpointSlices during port update: %s\", err)\n\t\t}\n\t} else {\n\t\tendpoints, err := pp.k8sAPI.Endpoint().Lister().Endpoints(pp.id.Namespace).Get(pp.id.Name)\n\t\tif err == nil {\n\t\t\tpp.updateEndpoints(endpoints)\n\t\t} else {\n\t\t\tpp.log.Errorf(\"Unable to get endpoints during port update: %s\", err)\n\t\t}\n\t}\n}\n\nfunc (pp *portPublisher) deleteEndpointSlice(es *discovery.EndpointSlice) {\n\taddrSet := pp.endpointSliceToAddresses(es)\n\tfor id := range addrSet.Addresses {\n\t\tdelete(pp.addresses.Addresses, id)\n\t}\n\n\tfor _, listener := range pp.listeners {\n\t\tlistener.Remove(addrSet)\n\t}\n\n\tif len(pp.addresses.Addresses) == 0 {\n\t\tpp.noEndpoints(false)\n\t} else {\n\t\tpp.exists = true\n\t\tpp.metrics.incUpdates()\n\t\tpp.metrics.setPods(len(pp.addresses.Addresses))\n\t\tpp.metrics.setExists(true)\n\t}\n}\n\nfunc (pp *portPublisher) noEndpoints(exists bool) {\n\tpp.exists = exists\n\tpp.addresses = AddressSet{}\n\tfor _, listener := range pp.listeners {\n\t\tlistener.NoEndpoints(exists)\n\t}\n\n\tpp.metrics.incUpdates()\n\tpp.metrics.setExists(exists)\n\tpp.metrics.setPods(0)\n}\n\nfunc (pp *portPublisher) subscribe(listener EndpointUpdateListener) {\n\tif pp.exists {\n\t\tif len(pp.addresses.Addresses) > 0 {\n\t\t\tlistener.Add(pp.addresses.shallowCopy())\n\t\t} else {\n\t\t\tlistener.NoEndpoints(true)\n\t\t}\n\t} else {\n\t\tlistener.NoEndpoints(false)\n\t}\n\tpp.listeners = append(pp.listeners, listener)\n\n\tpp.metrics.setSubscribers(len(pp.listeners))\n}\n\nfunc (pp *portPublisher) unsubscribe(listener EndpointUpdateListener) {\n\tfor i, e := range pp.listeners {\n\t\tif e == listener {\n\t\t\tn := len(pp.listeners)\n\t\t\tpp.listeners[i] = pp.listeners[n-1]\n\t\t\tpp.listeners[n-1] = nil\n\t\t\tpp.listeners = pp.listeners[:n-1]\n\t\t\tbreak\n\t\t}\n\t}\n\n\tpp.metrics.setSubscribers(len(pp.listeners))\n}\nfunc (pp *portPublisher) updateServer(oldServer, newServer *v1beta3.Server) {\n\tupdated := false\n\tfor id, address := range pp.addresses.Addresses {\n\n\t\tif pp.isAddressSelected(address, oldServer) || pp.isAddressSelected(address, newServer) {\n\t\t\tif newServer != nil && pp.isAddressSelected(address, newServer) && newServer.Spec.ProxyProtocol == opaqueProtocol {\n\t\t\t\taddress.OpaqueProtocol = true\n\t\t\t} else {\n\t\t\t\taddress.OpaqueProtocol = false\n\t\t\t}\n\t\t\tif pp.addresses.Addresses[id].OpaqueProtocol != address.OpaqueProtocol {\n\t\t\t\tpp.addresses.Addresses[id] = address\n\t\t\t\tupdated = true\n\t\t\t}\n\t\t}\n\t}\n\tif updated {\n\t\tfor _, listener := range pp.listeners {\n\t\t\tlistener.Add(pp.addresses.shallowCopy())\n\t\t}\n\t\tpp.metrics.incUpdates()\n\t}\n}\n\nfunc (pp *portPublisher) isAddressSelected(address Address, server *v1beta3.Server) bool {\n\tif server == nil {\n\t\treturn false\n\t}\n\n\tif address.Pod != nil {\n\t\tselector, err := metav1.LabelSelectorAsSelector(server.Spec.PodSelector)\n\t\tif err != nil {\n\t\t\tpp.log.Errorf(\"failed to create Selector: %s\", err)\n\t\t\treturn false\n\t\t}\n\n\t\tif !selector.Matches(labels.Set(address.Pod.Labels)) {\n\t\t\treturn false\n\t\t}\n\n\t\tswitch server.Spec.Port.Type {\n\t\tcase intstr.Int:\n\t\t\tif server.Spec.Port.IntVal == int32(address.Port) {\n\t\t\t\treturn true\n\t\t\t}\n\t\tcase intstr.String:\n\t\t\tfor _, c := range append(address.Pod.Spec.InitContainers, address.Pod.Spec.Containers...) {\n\t\t\t\tfor _, p := range c.Ports {\n\t\t\t\t\tif p.ContainerPort == int32(address.Port) && p.Name == server.Spec.Port.StrVal {\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t} else if address.ExternalWorkload != nil {\n\t\tselector, err := metav1.LabelSelectorAsSelector(server.Spec.ExternalWorkloadSelector)\n\t\tif err != nil {\n\t\t\tpp.log.Errorf(\"failed to create Selector: %s\", err)\n\t\t\treturn false\n\t\t}\n\n\t\tif !selector.Matches(labels.Set(address.ExternalWorkload.Labels)) {\n\t\t\treturn false\n\t\t}\n\n\t\tswitch server.Spec.Port.Type {\n\t\tcase intstr.Int:\n\t\t\tif server.Spec.Port.IntVal == int32(address.Port) {\n\t\t\t\treturn true\n\t\t\t}\n\t\tcase intstr.String:\n\t\t\tfor _, p := range address.ExternalWorkload.Spec.Ports {\n\t\t\t\tif p.Port == int32(address.Port) && p.Name == server.Spec.Port.StrVal {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n////////////\n/// util ///\n////////////\n\n// getTargetPort returns the port specified as an argument if no service is\n// present. If the service is present and it has a port spec matching the\n// specified port, it returns the name of the service's port (not the name\n// of the target pod port), so that it can be looked up in the endpoints API\n// response, which uses service port names.\nfunc getTargetPort(service *corev1.Service, port Port) namedPort {\n\t// Use the specified port as the target port by default\n\ttargetPort := intstr.FromInt(int(port))\n\n\tif service == nil {\n\t\treturn targetPort\n\t}\n\n\t// If a port spec exists with a port matching the specified port use that\n\t// port spec's name as the target port\n\tfor _, portSpec := range service.Spec.Ports {\n\t\tif portSpec.Port == int32(port) {\n\t\t\treturn intstr.FromString(portSpec.Name)\n\t\t}\n\t}\n\n\treturn targetPort\n}\n\nfunc addressChanged(oldAddress Address, newAddress Address) bool {\n\n\tif oldAddress.Identity != newAddress.Identity {\n\t\t// in this case the identity could have changed; this can happen when for\n\t\t// example a mirrored service is reassigned to a new gateway with a different\n\t\t// identity and the service mirroring controller picks that and updates the\n\t\t// identity\n\t\treturn true\n\t}\n\n\t// If the zone hints have changed, then the address has changed\n\tif len(newAddress.ForZones) != len(oldAddress.ForZones) {\n\t\treturn true\n\t}\n\n\t// Sort the zone information so that we can compare them accurately\n\t// We can't use `sort.StringSlice` because these are arrays of structs and not just strings\n\tsort.Slice(oldAddress.ForZones, func(i, j int) bool {\n\t\treturn oldAddress.ForZones[i].Name < (oldAddress.ForZones[j].Name)\n\t})\n\tsort.Slice(newAddress.ForZones, func(i, j int) bool {\n\t\treturn newAddress.ForZones[i].Name < (newAddress.ForZones[j].Name)\n\t})\n\n\t// Both old and new addresses have the same number of zones, so we can just compare them directly\n\tfor k := range oldAddress.ForZones {\n\t\tif oldAddress.ForZones[k].Name != newAddress.ForZones[k].Name {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif oldAddress.Pod != nil && newAddress.Pod != nil {\n\t\t// if these addresses are owned by pods we can check the resource versions\n\t\treturn oldAddress.Pod.ResourceVersion != newAddress.Pod.ResourceVersion\n\t}\n\treturn false\n}\n\nfunc diffAddresses(oldAddresses, newAddresses AddressSet) (add, remove AddressSet) {\n\t// TODO: this detects pods which have been added or removed, but does not\n\t// detect addresses which have been modified.  A modified address should trigger\n\t// an add of the new version.\n\taddAddresses := make(map[ID]Address)\n\tremoveAddresses := make(map[ID]Address)\n\tfor id, newAddress := range newAddresses.Addresses {\n\t\tif oldAddress, ok := oldAddresses.Addresses[id]; ok {\n\t\t\tif addressChanged(oldAddress, newAddress) {\n\t\t\t\taddAddresses[id] = newAddress\n\t\t\t}\n\t\t} else {\n\t\t\t// this is a new address, we need to add it\n\t\t\taddAddresses[id] = newAddress\n\t\t}\n\t}\n\tfor id, address := range oldAddresses.Addresses {\n\t\tif _, ok := newAddresses.Addresses[id]; !ok {\n\t\t\tremoveAddresses[id] = address\n\t\t}\n\t}\n\tadd = AddressSet{\n\t\tAddresses:          addAddresses,\n\t\tLabels:             newAddresses.Labels,\n\t\tLocalTrafficPolicy: newAddresses.LocalTrafficPolicy,\n\t}\n\tremove = AddressSet{\n\t\tAddresses: removeAddresses,\n\t}\n\treturn add, remove\n}\n\nfunc getEndpointSliceServiceID(es *discovery.EndpointSlice) (ServiceID, error) {\n\tif !isValidSlice(es) {\n\t\treturn ServiceID{}, fmt.Errorf(\"EndpointSlice [%s/%s] is invalid\", es.Namespace, es.Name)\n\t}\n\n\tif svc, ok := es.Labels[discovery.LabelServiceName]; ok {\n\t\treturn ServiceID{Namespace: es.Namespace, Name: svc}, nil\n\t}\n\n\tfor _, ref := range es.OwnerReferences {\n\t\tif ref.Kind == \"Service\" && ref.Name != \"\" {\n\t\t\treturn ServiceID{Namespace: es.Namespace, Name: ref.Name}, nil\n\t\t}\n\t}\n\n\treturn ServiceID{}, fmt.Errorf(\"EndpointSlice [%s/%s] is invalid\", es.Namespace, es.Name)\n}\n\nfunc isValidSlice(es *discovery.EndpointSlice) bool {\n\tserviceName, ok := es.Labels[discovery.LabelServiceName]\n\tif !ok && len(es.OwnerReferences) == 0 {\n\t\treturn false\n\t} else if len(es.OwnerReferences) == 0 && serviceName == \"\" {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// SetToServerProtocol sets the address's OpaqueProtocol field based off any\n// Servers that select it and override the expected protocol.\nfunc SetToServerProtocol(k8sAPI *k8s.API, address *Address, log *logging.Entry) error {\n\tif address.Pod == nil {\n\t\treturn fmt.Errorf(\"endpoint not backed by Pod: %s:%d\", address.IP, address.Port)\n\t}\n\tservers, err := k8sAPI.Srv().Lister().Servers(\"\").List(labels.Everything())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to list Servers: %w\", err)\n\t}\n\tfor _, server := range servers {\n\t\tselector, err := metav1.LabelSelectorAsSelector(server.Spec.PodSelector)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"failed to create Selector: %q\", err)\n\t\t\tcontinue\n\t\t}\n\t\tif server.Spec.ProxyProtocol == opaqueProtocol && selector.Matches(labels.Set(address.Pod.Labels)) {\n\t\t\tvar portMatch bool\n\t\t\tswitch server.Spec.Port.Type {\n\t\t\tcase intstr.Int:\n\t\t\t\tif server.Spec.Port.IntVal == int32(address.Port) {\n\t\t\t\t\tportMatch = true\n\t\t\t\t}\n\t\t\tcase intstr.String:\n\t\t\t\tfor _, c := range append(address.Pod.Spec.InitContainers, address.Pod.Spec.Containers...) {\n\t\t\t\t\tfor _, p := range c.Ports {\n\t\t\t\t\t\tif (p.ContainerPort == int32(address.Port) || p.HostPort == int32(address.Port)) &&\n\t\t\t\t\t\t\tp.Name == server.Spec.Port.StrVal {\n\t\t\t\t\t\t\tportMatch = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif portMatch {\n\t\t\t\taddress.OpaqueProtocol = true\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// setToServerProtocolExternalWorkload sets the address's OpaqueProtocol field based off any\n// Servers that select it and override the expected protocol for ExternalWorkloads.\nfunc SetToServerProtocolExternalWorkload(k8sAPI *k8s.API, address *Address) error {\n\tif address.ExternalWorkload == nil {\n\t\treturn fmt.Errorf(\"endpoint not backed by ExternalWorkload: %s:%d\", address.IP, address.Port)\n\t}\n\tservers, err := k8sAPI.Srv().Lister().Servers(\"\").List(labels.Everything())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to list Servers: %w\", err)\n\t}\n\tfor _, server := range servers {\n\t\tselector, err := metav1.LabelSelectorAsSelector(server.Spec.ExternalWorkloadSelector)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create Selector: %w\", err)\n\t\t}\n\t\tif server.Spec.ProxyProtocol == opaqueProtocol && selector.Matches(labels.Set(address.ExternalWorkload.Labels)) {\n\t\t\tvar portMatch bool\n\t\t\tswitch server.Spec.Port.Type {\n\t\t\tcase intstr.Int:\n\t\t\t\tif server.Spec.Port.IntVal == int32(address.Port) {\n\t\t\t\t\tportMatch = true\n\t\t\t\t}\n\t\t\tcase intstr.String:\n\t\t\t\tfor _, p := range address.ExternalWorkload.Spec.Ports {\n\t\t\t\t\tif p.Port == int32(address.Port) && p.Name == server.Spec.Port.StrVal {\n\t\t\t\t\t\tportMatch = true\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif portMatch {\n\t\t\t\taddress.OpaqueProtocol = true\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc latestUpdated(managedFields []metav1.ManagedFieldsEntry) time.Time {\n\tvar latest time.Time\n\tfor _, field := range managedFields {\n\t\tif field.Time == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif field.Operation == metav1.ManagedFieldsOperationUpdate {\n\t\t\tif latest.IsZero() || field.Time.After(latest) {\n\t\t\t\tlatest = field.Time.Time\n\t\t\t}\n\t\t}\n\t}\n\treturn latest\n}\n"
  },
  {
    "path": "controller/api/destination/watcher/endpoints_watcher_test.go",
    "content": "package watcher\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sort\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tconsts \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tdv1 \"k8s.io/api/discovery/v1\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\ntype bufferingEndpointListener struct {\n\tadded              []string\n\tremoved            []string\n\tlocalTrafficPolicy bool\n\tnoEndpointsCalled  bool\n\tnoEndpointsExist   bool\n\tsync.Mutex\n}\n\nfunc newBufferingEndpointListener() *bufferingEndpointListener {\n\treturn &bufferingEndpointListener{\n\t\tadded:   []string{},\n\t\tremoved: []string{},\n\t\tMutex:   sync.Mutex{},\n\t}\n}\n\nfunc addressString(address Address) string {\n\taddressString := fmt.Sprintf(\"%s:%d\", address.IP, address.Port)\n\tif address.Identity != \"\" {\n\t\taddressString = fmt.Sprintf(\"%s/%s\", addressString, address.Identity)\n\t}\n\tif address.AuthorityOverride != \"\" {\n\t\taddressString = fmt.Sprintf(\"%s/%s\", addressString, address.AuthorityOverride)\n\t}\n\treturn addressString\n}\n\nfunc (bel *bufferingEndpointListener) ExpectAdded(expected []string, t *testing.T) {\n\tbel.Lock()\n\tdefer bel.Unlock()\n\tt.Helper()\n\tsort.Strings(bel.added)\n\ttestCompare(t, expected, bel.added)\n}\n\nfunc (bel *bufferingEndpointListener) ExpectRemoved(expected []string, t *testing.T) {\n\tbel.Lock()\n\tdefer bel.Unlock()\n\tt.Helper()\n\tsort.Strings(bel.removed)\n\ttestCompare(t, expected, bel.removed)\n}\n\nfunc (bel *bufferingEndpointListener) endpointsAreNotCalled() bool {\n\tbel.Lock()\n\tdefer bel.Unlock()\n\treturn bel.noEndpointsCalled\n}\n\nfunc (bel *bufferingEndpointListener) endpointsDoNotExist() bool {\n\tbel.Lock()\n\tdefer bel.Unlock()\n\treturn bel.noEndpointsExist\n}\n\nfunc (bel *bufferingEndpointListener) Add(set AddressSet) {\n\tbel.Lock()\n\tdefer bel.Unlock()\n\tfor _, address := range set.Addresses {\n\t\tbel.added = append(bel.added, addressString(address))\n\t}\n\tbel.localTrafficPolicy = set.LocalTrafficPolicy\n}\n\nfunc (bel *bufferingEndpointListener) Remove(set AddressSet) {\n\tbel.Lock()\n\tdefer bel.Unlock()\n\tfor _, address := range set.Addresses {\n\t\tbel.removed = append(bel.removed, addressString(address))\n\t}\n\tbel.localTrafficPolicy = set.LocalTrafficPolicy\n}\n\nfunc (bel *bufferingEndpointListener) NoEndpoints(exists bool) {\n\tbel.Lock()\n\tdefer bel.Unlock()\n\tbel.noEndpointsCalled = true\n\tbel.noEndpointsExist = exists\n}\n\ntype bufferingEndpointListenerWithResVersion struct {\n\tadded   []string\n\tremoved []string\n\tsync.Mutex\n}\n\nfunc newBufferingEndpointListenerWithResVersion() *bufferingEndpointListenerWithResVersion {\n\treturn &bufferingEndpointListenerWithResVersion{\n\t\tadded:   []string{},\n\t\tremoved: []string{},\n\t\tMutex:   sync.Mutex{},\n\t}\n}\n\nfunc addressStringWithResVersion(address Address) string {\n\treturn fmt.Sprintf(\"%s:%d:%s\", address.IP, address.Port, address.Pod.ResourceVersion)\n}\n\nfunc (bel *bufferingEndpointListenerWithResVersion) ExpectAdded(expected []string, t *testing.T) {\n\tbel.Lock()\n\tdefer bel.Unlock()\n\tsort.Strings(bel.added)\n\ttestCompare(t, expected, bel.added)\n}\n\nfunc (bel *bufferingEndpointListenerWithResVersion) ExpectRemoved(expected []string, t *testing.T) {\n\tbel.Lock()\n\tdefer bel.Unlock()\n\tsort.Strings(bel.removed)\n\ttestCompare(t, expected, bel.removed)\n}\n\nfunc (bel *bufferingEndpointListenerWithResVersion) Add(set AddressSet) {\n\tbel.Lock()\n\tdefer bel.Unlock()\n\tfor _, address := range set.Addresses {\n\t\tbel.added = append(bel.added, addressStringWithResVersion(address))\n\t}\n}\n\nfunc (bel *bufferingEndpointListenerWithResVersion) Remove(set AddressSet) {\n\tbel.Lock()\n\tdefer bel.Unlock()\n\tfor _, address := range set.Addresses {\n\t\tbel.removed = append(bel.removed, addressStringWithResVersion(address))\n\t}\n}\n\nfunc (bel *bufferingEndpointListenerWithResVersion) NoEndpoints(exists bool) {}\n\nfunc TestEndpointsWatcher(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tserviceType                      string\n\t\tk8sConfigs                       []string\n\t\tid                               ServiceID\n\t\thostname                         string\n\t\tport                             Port\n\t\texpectedAddresses                []string\n\t\texpectedNoEndpoints              bool\n\t\texpectedNoEndpointsServiceExists bool\n\t\texpectedError                    bool\n\t}{\n\t\t{\n\t\t\tserviceType: \"local services\",\n\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: name1\n  namespace: ns\nsubsets:\n- addresses:\n  - ip: 172.17.0.12\n    targetRef:\n      kind: Pod\n      name: name1-1\n      namespace: ns\n  - ip: 172.17.0.19\n    targetRef:\n      kind: Pod\n      name: name1-2\n      namespace: ns\n  - ip: 172.17.0.20\n    targetRef:\n      kind: Pod\n      name: name1-3\n      namespace: ns\n  - ip: 172.17.0.21\n  ports:\n  - port: 8989`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-1\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 172.17.0.12`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-2\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 172.17.0.19`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-3\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 172.17.0.20`,\n\t\t\t},\n\t\t\tid:   ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport: 8989,\n\t\t\texpectedAddresses: []string{\n\t\t\t\t\"172.17.0.12:8989\",\n\t\t\t\t\"172.17.0.19:8989\",\n\t\t\t\t\"172.17.0.20:8989\",\n\t\t\t\t\"172.17.0.21:8989\",\n\t\t\t},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t\texpectedError:                    false,\n\t\t},\n\t\t{\n\t\t\t// Test for the issue described in linkerd/linkerd2#1405.\n\t\t\tserviceType: \"local NodePort service with unnamed port\",\n\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1\n  namespace: ns\nspec:\n  type: NodePort\n  ports:\n  - port: 8989\n    targetPort: port1`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: name1\n  namespace: ns\nsubsets:\n- addresses:\n  - ip: 10.233.66.239\n    targetRef:\n      kind: Pod\n      name: name1-f748fb6b4-hpwpw\n      namespace: ns\n  - ip: 10.233.88.244\n    targetRef:\n      kind: Pod\n      name: name1-f748fb6b4-6vcmw\n      namespace: ns\n  ports:\n  - port: 8990\n    protocol: TCP`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-f748fb6b4-hpwpw\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  podIp: 10.233.66.239\n  phase: Running`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-f748fb6b4-6vcmw\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  podIp: 10.233.88.244\n  phase: Running`,\n\t\t\t},\n\t\t\tid:   ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport: 8989,\n\t\t\texpectedAddresses: []string{\n\t\t\t\t\"10.233.66.239:8990\",\n\t\t\t\t\"10.233.88.244:8990\",\n\t\t\t},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t\texpectedError:                    false,\n\t\t},\n\t\t{\n\t\t\t// Test for the issue described in linkerd/linkerd2#1853.\n\t\t\tserviceType: \"local service with named target port and differently-named service port\",\n\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: world\n  namespace: ns\nspec:\n  type: ClusterIP\n  ports:\n    - name: app\n      port: 7778\n      targetPort: http`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: world\n  namespace: ns\nsubsets:\n- addresses:\n  - ip: 10.1.30.135\n    targetRef:\n      kind: Pod\n      name: world-575bf846b4-tp4hw\n      namespace: ns\n  ports:\n  - name: app\n    port: 7779\n    protocol: TCP`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: world-575bf846b4-tp4hw\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  podIp: 10.1.30.135\n  phase: Running`,\n\t\t\t},\n\t\t\tid:   ServiceID{Name: \"world\", Namespace: \"ns\"},\n\t\t\tport: 7778,\n\t\t\texpectedAddresses: []string{\n\t\t\t\t\"10.1.30.135:7779\",\n\t\t\t},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t\texpectedError:                    false,\n\t\t},\n\t\t{\n\t\t\tserviceType: \"local services with missing addresses\",\n\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: name1\n  namespace: ns\nsubsets:\n- addresses:\n  - ip: 172.17.0.23\n    targetRef:\n      kind: Pod\n      name: name1-1\n      namespace: ns\n  - ip: 172.17.0.24\n    targetRef:\n      kind: Pod\n      name: name1-2\n      namespace: ns\n  - ip: 172.17.0.25\n    targetRef:\n      kind: Pod\n      name: name1-3\n      namespace: ns\n  ports:\n  - port: 8989`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-3\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 172.17.0.25`,\n\t\t\t},\n\t\t\tid:   ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport: 8989,\n\t\t\texpectedAddresses: []string{\n\t\t\t\t\"172.17.0.25:8989\",\n\t\t\t},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t\texpectedError:                    false,\n\t\t},\n\t\t{\n\t\t\tserviceType: \"local services with no endpoints\",\n\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name2\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 7979`,\n\t\t\t},\n\t\t\tid:                               ServiceID{Name: \"name2\", Namespace: \"ns\"},\n\t\t\tport:                             7979,\n\t\t\texpectedAddresses:                []string{},\n\t\t\texpectedNoEndpoints:              true,\n\t\t\texpectedNoEndpointsServiceExists: true,\n\t\t\texpectedError:                    false,\n\t\t},\n\t\t{\n\t\t\tserviceType: \"external name services\",\n\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name3\n  namespace: ns\nspec:\n  type: ExternalName\n  externalName: foo`,\n\t\t\t},\n\t\t\tid:                               ServiceID{Name: \"name3\", Namespace: \"ns\"},\n\t\t\tport:                             6969,\n\t\t\texpectedAddresses:                []string{},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t\texpectedError:                    true,\n\t\t},\n\t\t{\n\t\t\tserviceType:                      \"services that do not yet exist\",\n\t\t\tk8sConfigs:                       []string{},\n\t\t\tid:                               ServiceID{Name: \"name4\", Namespace: \"ns\"},\n\t\t\tport:                             5959,\n\t\t\texpectedAddresses:                []string{},\n\t\t\texpectedNoEndpoints:              true,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t\texpectedError:                    false,\n\t\t},\n\t\t{\n\t\t\tserviceType: \"stateful sets\",\n\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: name1\n  namespace: ns\nsubsets:\n- addresses:\n  - ip: 172.17.0.12\n    hostname: name1-1\n    targetRef:\n      kind: Pod\n      name: name1-1\n      namespace: ns\n  - ip: 172.17.0.19\n    hostname: name1-2\n    targetRef:\n      kind: Pod\n      name: name1-2\n      namespace: ns\n  - ip: 172.17.0.20\n    hostname: name1-3\n    targetRef:\n      kind: Pod\n      name: name1-3\n      namespace: ns\n  ports:\n  - port: 8989`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-1\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 172.17.0.12`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-2\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 172.17.0.19`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-3\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 172.17.0.20`,\n\t\t\t},\n\t\t\tid:                               ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\thostname:                         \"name1-3\",\n\t\t\tport:                             5959,\n\t\t\texpectedAddresses:                []string{\"172.17.0.20:5959\"},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t},\n\t\t{\n\t\t\tserviceType: \"local service with new named port mid rollout and two subsets but only first subset is relevant\",\n\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1\n  namespace: ns\nspec:\n  type: ClusterIP\n  ports:\n    - name: port1\n      port: 8989\n      targetPort: port1\n    - name: port2\n      port: 9999\n      targetPort: port2`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  labels:\n    app: name1\n  name: name1\n  namespace: ns\nsubsets:\n- addresses:\n  - ip: 172.17.0.1\n    nodeName: name1-1\n    targetRef:\n      kind: Pod\n      name: name1-1\n      namespace: ns\n  - ip: 172.17.0.2\n    nodeName: name1-2\n    targetRef:\n      kind: Pod\n      name: name1-2\n      namespace: ns\n  ports:\n  - name: port1\n    port: 8989\n    protocol: TCP\n- addresses:\n  - ip: 172.17.0.1\n    nodeName: name1-1\n    targetRef:\n      kind: Pod\n      name: name1-1\n      namespace: ns\n  notReadyAddresses:\n  - ip: 172.17.0.2\n    nodeName: name1-2\n    targetRef:\n      kind: Pod\n      name: name1-2\n      namespace: ns\n  ports:\n  - name: port2\n    port: 9999\n    protocol: TCP\n    `,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-1\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 172.17.0.1`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-2\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-2\nstatus:\n  phase: Running\n  podIP: 172.17.0.2`,\n\t\t\t},\n\t\t\tid:   ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport: 8989,\n\t\t\texpectedAddresses: []string{\n\t\t\t\t\"172.17.0.1:8989\",\n\t\t\t\t\"172.17.0.2:8989\",\n\t\t\t},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t\texpectedError:                    false,\n\t\t},\n\t} {\n\t\ttt := tt // pin\n\t\tt.Run(\"subscribes listener to \"+tt.serviceType, func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\twatcher, err := NewEndpointsWatcher(k8sAPI, metadataAPI, logging.WithField(\"test\", t.Name()), false, \"local\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"can't create Endpoints watcher: %s\", err)\n\t\t\t}\n\n\t\t\tk8sAPI.Sync(nil)\n\t\t\tmetadataAPI.Sync(nil)\n\n\t\t\tlistener := newBufferingEndpointListener()\n\n\t\t\terr = watcher.Subscribe(tt.id, tt.port, tt.hostname, listener)\n\t\t\tif tt.expectedError && err == nil {\n\t\t\t\tt.Fatal(\"Expected error but was ok\")\n\t\t\t}\n\t\t\tif !tt.expectedError && err != nil {\n\t\t\t\tt.Fatalf(\"Expected no error, got [%s]\", err)\n\t\t\t}\n\n\t\t\tlistener.ExpectAdded(tt.expectedAddresses, t)\n\n\t\t\tif listener.endpointsAreNotCalled() != tt.expectedNoEndpoints {\n\t\t\t\tt.Fatalf(\"Expected noEndpointsCalled to be [%t], got [%t]\",\n\t\t\t\t\ttt.expectedNoEndpoints, listener.endpointsAreNotCalled())\n\t\t\t}\n\n\t\t\tif listener.endpointsDoNotExist() != tt.expectedNoEndpointsServiceExists {\n\t\t\t\tt.Fatalf(\"Expected noEndpointsExist to be [%t], got [%t]\",\n\t\t\t\t\ttt.expectedNoEndpointsServiceExists, listener.endpointsDoNotExist())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestEndpointsWatcherWithEndpointSlices(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tserviceType                      string\n\t\tk8sConfigs                       []string\n\t\tid                               ServiceID\n\t\thostname                         string\n\t\tport                             Port\n\t\texpectedAddresses                []string\n\t\texpectedNoEndpoints              bool\n\t\texpectedNoEndpointsServiceExists bool\n\t\texpectedError                    bool\n\t\texpectedLocalTrafficPolicy       bool\n\t}{\n\t\t{\n\t\t\tserviceType: \"local services with EndpointSlice\",\n\t\t\tk8sConfigs: []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n  - name: endpointslices\n    singularName: endpointslice\n    namespaced: true\n    kind: EndpointSlice\n    verbs:\n      - delete\n      - deletecollection\n      - get\n      - list\n      - patch\n      - create\n      - update\n      - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name-1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989\n  internalTrafficPolicy: Local`,\n\t\t\t\t`\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.12\n  conditions:\n    ready: true\n  targetRef:\n    kind: Pod\n    name: name-1-1\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\n- addresses:\n  - 172.17.0.19\n  conditions:\n    ready: true\n  targetRef:\n    kind: Pod\n    name: name-1-2\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\n- addresses:\n  - 172.17.0.20\n  conditions:\n    ready: true\n  targetRef:\n    kind: Pod\n    name: name-1-3\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-2\n- addresses:\n  - 172.17.0.21\n  conditions:\n    ready: true\n  topology:\n    kubernetes.io/hostname: node-2\nkind: EndpointSlice\nmetadata:\n  labels:\n    kubernetes.io/service-name: name-1\n  name: name-1-bhnqh\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name-1-1\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 172.17.0.12`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name-1-2\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 172.17.0.19`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name-1-3\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 172.17.0.20`,\n\t\t\t},\n\t\t\tid:   ServiceID{Name: \"name-1\", Namespace: \"ns\"},\n\t\t\tport: 8989,\n\t\t\texpectedAddresses: []string{\n\t\t\t\t\"172.17.0.12:8989\",\n\t\t\t\t\"172.17.0.19:8989\",\n\t\t\t\t\"172.17.0.20:8989\",\n\t\t\t\t\"172.17.0.21:8989\",\n\t\t\t},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t\texpectedError:                    false,\n\t\t\texpectedLocalTrafficPolicy:       true,\n\t\t},\n\t\t{\n\t\t\tserviceType: \"local services with missing addresses and EndpointSlice\",\n\t\t\tk8sConfigs: []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n  - name: endpointslices\n    singularName: endpointslice\n    namespaced: true\n    kind: EndpointSlice\n    verbs:\n      - delete\n      - deletecollection\n      - get\n      - list\n      - patch\n      - create\n      - update\n      - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name-1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`, `\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.23\n  conditions:\n    ready: true\n  targetRef:\n    kind: Pod\n    name: name-1-1\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\n- addresses:\n  - 172.17.0.24\n  conditions:\n    ready: true\n  targetRef:\n    kind: Pod\n    name: name-1-2\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\n- addresses:\n  - 172.17.0.25\n  conditions:\n    ready: true\n  targetRef:\n    kind: Pod\n    name: name-1-3\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-2\nkind: EndpointSlice\nmetadata:\n  labels:\n    kubernetes.io/service-name: name-1\n  name: name1-f5fad\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name-1-3\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  podIP: 172.17.0.25\n  phase: Running`,\n\t\t\t},\n\t\t\tid:                               ServiceID{Name: \"name-1\", Namespace: \"ns\"},\n\t\t\tport:                             8989,\n\t\t\texpectedAddresses:                []string{\"172.17.0.25:8989\"},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t\texpectedError:                    false,\n\t\t},\n\t\t{\n\t\t\tserviceType: \"local services with no EndpointSlices\",\n\t\t\tk8sConfigs: []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n  - name: endpointslices\n    singularName: endpointslice\n    namespaced: true\n    kind: EndpointSlice\n    verbs:\n      - delete\n      - deletecollection\n      - get\n      - list\n      - patch\n      - create\n      - update\n      - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name-2\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 7979`,\n\t\t\t},\n\t\t\tid:                               ServiceID{Name: \"name-2\", Namespace: \"ns\"},\n\t\t\tport:                             7979,\n\t\t\texpectedAddresses:                []string{},\n\t\t\texpectedNoEndpoints:              true,\n\t\t\texpectedNoEndpointsServiceExists: true,\n\t\t\texpectedError:                    false,\n\t\t},\n\t\t{\n\t\t\tserviceType: \"external name services with EndpointSlices\",\n\t\t\tk8sConfigs: []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n  - name: endpointslices\n    singularName: endpointslice\n    namespaced: true\n    kind: EndpointSlice\n    verbs:\n      - delete\n      - deletecollection\n      - get\n      - list\n      - patch\n      - create\n      - update\n      - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name-3-external-svc\n  namespace: ns\nspec:\n  type: ExternalName\n  externalName: foo`,\n\t\t\t},\n\t\t\tid:                               ServiceID{Name: \"name-3-external-svc\", Namespace: \"ns\"},\n\t\t\tport:                             7777,\n\t\t\texpectedAddresses:                []string{},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t\texpectedError:                    true,\n\t\t},\n\t\t{\n\t\t\tserviceType:                      \"services that do not exist\",\n\t\t\tk8sConfigs:                       []string{},\n\t\t\tid:                               ServiceID{Name: \"name-4-inexistent-svc\", Namespace: \"ns\"},\n\t\t\tport:                             5555,\n\t\t\texpectedAddresses:                []string{},\n\t\t\texpectedNoEndpoints:              true,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t\texpectedError:                    false,\n\t\t},\n\t\t{\n\t\t\tserviceType: \"stateful sets with EndpointSlices\",\n\t\t\tk8sConfigs: []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n  - name: endpointslices\n    singularName: endpointslice\n    namespaced: true\n    kind: EndpointSlice\n    verbs:\n      - delete\n      - deletecollection\n      - get\n      - list\n      - patch\n      - create\n      - update\n      - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name-1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`, `\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.12\n  conditions:\n    ready: true\n  hostname: name-1-1\n  targetRef:\n    kind: Pod\n    name: name-1-1\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\n- addresses:\n  - 172.17.0.19\n  hostname: name-1-2\n  conditions:\n    ready: true\n  targetRef:\n    kind: Pod\n    name: name-1-2\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\n- addresses:\n  - 172.17.0.20\n  hostname: name-1-3\n  conditions:\n    ready: true\n  targetRef:\n    kind: Pod\n    name: name-1-3\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-2\nkind: EndpointSlice\nmetadata:\n  labels:\n    kubernetes.io/service-name: name-1\n  name: name-1-f5fad\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name-1-1\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 172.17.0.12`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name-1-2\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 172.17.0.19`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name-1-3\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 172.17.0.20`,\n\t\t\t},\n\t\t\tid:                               ServiceID{Name: \"name-1\", Namespace: \"ns\"},\n\t\t\thostname:                         \"name-1-3\",\n\t\t\tport:                             6000,\n\t\t\texpectedAddresses:                []string{\"172.17.0.20:6000\"},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t\texpectedError:                    false,\n\t\t},\n\t\t{\n\t\t\tserviceType: \"service with EndpointSlice without labels\",\n\t\t\tk8sConfigs: []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n  - name: endpointslices\n    singularName: endpointslice\n    namespaced: true\n    kind: EndpointSlice\n    verbs:\n      - delete\n      - deletecollection\n      - get\n      - list\n      - patch\n      - create\n      - update\n      - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name-5\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`, `\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.12\n  conditions:\n    ready: true\n  hostname: name-1-1\n  targetRef:\n    kind: Pod\n    name: name-1-1\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\nkind: EndpointSlice\nmetadata:\n  labels:\n  name: name-1-f5fad\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name-1-1\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 172.17.0.12`,\n\t\t\t},\n\t\t\tid:                               ServiceID{Name: \"name-5\", Namespace: \"ns\"},\n\t\t\tport:                             8989,\n\t\t\texpectedAddresses:                []string{},\n\t\t\texpectedNoEndpoints:              true,\n\t\t\texpectedNoEndpointsServiceExists: true,\n\t\t\texpectedError:                    false,\n\t\t},\n\t\t{\n\t\t\tserviceType: \"service with IPv6 address type EndpointSlice\",\n\t\t\tk8sConfigs: []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n  - name: endpointslices\n    singularName: endpointslice\n    namespaced: true\n    kind: EndpointSlice\n    verbs:\n      - delete\n      - deletecollection\n      - get\n      - list\n      - patch\n      - create\n      - update\n      - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name-5\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 9000`, `\naddressType: IPv6\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 0:0:0:0:0:0:0:1\n  conditions:\n    ready: true\n  targetRef:\n    kind: Pod\n    name: name-5-1\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\nkind: EndpointSlice\nmetadata:\n  labels:\n  name: name-5-f65dv\n  namespace: ns\n  ownerReferences:\n  - apiVersion: v1\n    kind: Service\n    name: name-5\nports:\n- name: \"\"\n  port: 9000`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name-5-1\n  namespace: ns\n  ownerReferences:\n  - kind: ReplicaSet\n    name: rs-1\nstatus:\n  phase: Running\n  podIP: 0:0:0:0:0:0:0:1`,\n\t\t\t},\n\t\t\tid:                               ServiceID{Name: \"name-5\", Namespace: \"ns\"},\n\t\t\tport:                             9000,\n\t\t\texpectedAddresses:                []string{},\n\t\t\texpectedNoEndpoints:              true,\n\t\t\texpectedNoEndpointsServiceExists: true,\n\t\t\texpectedError:                    false,\n\t\t}} {\n\t\ttt := tt // pin\n\t\tt.Run(\"subscribes listener to \"+tt.serviceType, func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\twatcher, err := NewEndpointsWatcher(k8sAPI, metadataAPI, logging.WithField(\"test\", t.Name()), true, \"local\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"can't create Endpoints watcher: %s\", err)\n\t\t\t}\n\n\t\t\tk8sAPI.Sync(nil)\n\t\t\tmetadataAPI.Sync(nil)\n\n\t\t\tlistener := newBufferingEndpointListener()\n\n\t\t\terr = watcher.Subscribe(tt.id, tt.port, tt.hostname, listener)\n\t\t\tif tt.expectedError && err == nil {\n\t\t\t\tt.Fatal(\"Expected error but was ok\")\n\t\t\t}\n\t\t\tif !tt.expectedError && err != nil {\n\t\t\t\tt.Fatalf(\"Expected no error, got [%s]\", err)\n\t\t\t}\n\n\t\t\tif listener.localTrafficPolicy != tt.expectedLocalTrafficPolicy {\n\t\t\t\tt.Fatalf(\"Expected localTrafficPolicy [%v], got [%v]\", tt.expectedLocalTrafficPolicy, listener.localTrafficPolicy)\n\t\t\t}\n\n\t\t\tlistener.ExpectAdded(tt.expectedAddresses, t)\n\n\t\t\tif listener.endpointsAreNotCalled() != tt.expectedNoEndpoints {\n\t\t\t\tt.Fatalf(\"Expected noEndpointsCalled to be [%t], got [%t]\",\n\t\t\t\t\ttt.expectedNoEndpoints, listener.endpointsAreNotCalled())\n\t\t\t}\n\n\t\t\tif listener.endpointsDoNotExist() != tt.expectedNoEndpointsServiceExists {\n\t\t\t\tt.Fatalf(\"Expected noEndpointsExist to be [%t], got [%t]\",\n\t\t\t\t\ttt.expectedNoEndpointsServiceExists, listener.endpointsDoNotExist())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestEndpointsWatcherWithEndpointSlicesExternalWorkload(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tserviceType                      string\n\t\tk8sConfigs                       []string\n\t\tid                               ServiceID\n\t\thostname                         string\n\t\tport                             Port\n\t\texpectedAddresses                []string\n\t\texpectedNoEndpoints              bool\n\t\texpectedNoEndpointsServiceExists bool\n\t\texpectedError                    bool\n\t\texpectedLocalTrafficPolicy       bool\n\t}{\n\t\t{\n\t\t\tserviceType: \"local services with EndpointSlice\",\n\t\t\tk8sConfigs: []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n  - name: endpointslices\n    singularName: endpointslice\n    namespaced: true\n    kind: EndpointSlice\n    verbs:\n      - delete\n      - deletecollection\n      - get\n      - list\n      - patch\n      - create\n      - update\n      - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name-1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989\n  internalTrafficPolicy: Local`,\n\t\t\t\t`\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.12\n  conditions:\n    ready: true\n  targetRef:\n    kind: ExternalWorkload\n    name: name-1-1\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\n- addresses:\n  - 172.17.0.19\n  conditions:\n    ready: true\n  targetRef:\n    kind: ExternalWorkload\n    name: name-1-2\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\n- addresses:\n  - 172.17.0.20\n  conditions:\n    ready: true\n  targetRef:\n    kind: ExternalWorkload\n    name: name-1-3\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-2\n- addresses:\n  - 172.17.0.21\n  conditions:\n    ready: true\n  topology:\n    kubernetes.io/hostname: node-2\nkind: EndpointSlice\nmetadata:\n  labels:\n    kubernetes.io/service-name: name-1\n  name: name-1-bhnqh\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`,\n\t\t\t\t`\napiVersion: workload.linkerd.io/v1beta1\nkind: ExternalWorkload\nmetadata:\n  name: name-1-1\n  namespace: ns\nstatus:\n  conditions:\n  ready: true`,\n\t\t\t\t`\napiVersion: workload.linkerd.io/v1beta1\nkind: ExternalWorkload\nmetadata:\n  name: name-1-2\n  namespace: ns\nstatus:\n  conditions:\n  ready: true`,\n\t\t\t\t`\napiVersion: workload.linkerd.io/v1beta1\nkind: ExternalWorkload\nmetadata:\n  name: name-1-3\n  namespace: ns\nstatus:\n  conditions:\n  ready: true`,\n\t\t\t},\n\t\t\tid:   ExternalWorkloadID{Name: \"name-1\", Namespace: \"ns\"},\n\t\t\tport: 8989,\n\t\t\texpectedAddresses: []string{\n\t\t\t\t\"172.17.0.12:8989\",\n\t\t\t\t\"172.17.0.19:8989\",\n\t\t\t\t\"172.17.0.20:8989\",\n\t\t\t\t\"172.17.0.21:8989\",\n\t\t\t},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t\texpectedError:                    false,\n\t\t\texpectedLocalTrafficPolicy:       true,\n\t\t},\n\t\t{\n\t\t\tserviceType: \"local services with missing addresses and EndpointSlice\",\n\t\t\tk8sConfigs: []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n  - name: endpointslices\n    singularName: endpointslice\n    namespaced: true\n    kind: EndpointSlice\n    verbs:\n      - delete\n      - deletecollection\n      - get\n      - list\n      - patch\n      - create\n      - update\n      - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name-1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`, `\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.23\n  conditions:\n    ready: true\n  targetRef:\n    kind: ExternalWorkload\n    name: name-1-1\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\n- addresses:\n  - 172.17.0.24\n  conditions:\n    ready: true\n  targetRef:\n    kind: ExternalWorkload\n    name: name-1-2\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\n- addresses:\n  - 172.17.0.25\n  conditions:\n    ready: true\n  targetRef:\n    kind: ExternalWorkload\n    name: name-1-3\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-2\nkind: EndpointSlice\nmetadata:\n  labels:\n    kubernetes.io/service-name: name-1\n  name: name1-f5fad\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`, `\napiVersion: workload.linkerd.io/v1beta1\nkind: ExternalWorkload\nmetadata:\n  name: name-1-3\n  namespace: ns\nstatus:\n  conditions:\n  ready: true`,\n\t\t\t},\n\t\t\tid:                               ServiceID{Name: \"name-1\", Namespace: \"ns\"},\n\t\t\tport:                             8989,\n\t\t\texpectedAddresses:                []string{\"172.17.0.25:8989\"},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t\texpectedError:                    false,\n\t\t},\n\t\t{\n\t\t\tserviceType: \"service with EndpointSlice without labels\",\n\t\t\tk8sConfigs: []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n  - name: endpointslices\n    singularName: endpointslice\n    namespaced: true\n    kind: EndpointSlice\n    verbs:\n      - delete\n      - deletecollection\n      - get\n      - list\n      - patch\n      - create\n      - update\n      - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name-5\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`, `\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.12\n  conditions:\n    ready: true\n  hostname: name-1-1\n  targetRef:\n    kind: ExternalWorkload\n    name: name-1-1\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\nkind: EndpointSlice\nmetadata:\n  labels:\n  name: name-1-f5fad\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`, `\napiVersion: workload.linkerd.io/v1beta1\nkind: ExternalWorkload\nmetadata:\n  name: name-1-1\n  namespace: ns\nstatus:\n  conditions:\n  ready: true`,\n\t\t\t},\n\t\t\tid:                               ServiceID{Name: \"name-5\", Namespace: \"ns\"},\n\t\t\tport:                             8989,\n\t\t\texpectedAddresses:                []string{},\n\t\t\texpectedNoEndpoints:              true,\n\t\t\texpectedNoEndpointsServiceExists: true,\n\t\t\texpectedError:                    false,\n\t\t},\n\n\t\t{\n\t\t\tserviceType: \"service with IPv6 address type EndpointSlice\",\n\t\t\tk8sConfigs: []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n  - name: endpointslices\n    singularName: endpointslice\n    namespaced: true\n    kind: EndpointSlice\n    verbs:\n      - delete\n      - deletecollection\n      - get\n      - list\n      - patch\n      - create\n      - update\n      - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name-5\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 9000`, `\naddressType: IPv6\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 0:0:0:0:0:0:0:1\n  conditions:\n    ready: true\n  targetRef:\n    kind: ExternalWorkload\n    name: name-5-1\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\nkind: EndpointSlice\nmetadata:\n  labels:\n  name: name-5-f65dv\n  namespace: ns\n  ownerReferences:\n  - apiVersion: v1\n    kind: Service\n    name: name-5\nports:\n- name: \"\"\n  port: 9000`, `\napiVersion: workload.linkerd.io/v1beta1\nkind: ExternalWorkload\nmetadata:\n  name: name-5-1\n  namespace: ns\nstatus:\n  conditions:\n  ready: true`,\n\t\t\t},\n\t\t\tid:                               ServiceID{Name: \"name-5\", Namespace: \"ns\"},\n\t\t\tport:                             9000,\n\t\t\texpectedAddresses:                []string{},\n\t\t\texpectedNoEndpoints:              true,\n\t\t\texpectedNoEndpointsServiceExists: true,\n\t\t\texpectedError:                    false,\n\t\t},\n\t} {\n\t\ttt := tt // pin\n\t\tt.Run(\"subscribes listener to \"+tt.serviceType, func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\twatcher, err := NewEndpointsWatcher(k8sAPI, metadataAPI, logging.WithField(\"test\", t.Name()), true, \"local\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"can't create Endpoints watcher: %s\", err)\n\t\t\t}\n\n\t\t\tk8sAPI.Sync(nil)\n\t\t\tmetadataAPI.Sync(nil)\n\n\t\t\tlistener := newBufferingEndpointListener()\n\n\t\t\terr = watcher.Subscribe(tt.id, tt.port, tt.hostname, listener)\n\t\t\tif tt.expectedError && err == nil {\n\t\t\t\tt.Fatal(\"Expected error but was ok\")\n\t\t\t}\n\t\t\tif !tt.expectedError && err != nil {\n\t\t\t\tt.Fatalf(\"Expected no error, got [%s]\", err)\n\t\t\t}\n\n\t\t\tif listener.localTrafficPolicy != tt.expectedLocalTrafficPolicy {\n\t\t\t\tt.Fatalf(\"Expected localTrafficPolicy [%v], got [%v]\", tt.expectedLocalTrafficPolicy, listener.localTrafficPolicy)\n\t\t\t}\n\n\t\t\tlistener.ExpectAdded(tt.expectedAddresses, t)\n\n\t\t\tif listener.endpointsAreNotCalled() != tt.expectedNoEndpoints {\n\t\t\t\tt.Fatalf(\"Expected noEndpointsCalled to be [%t], got [%t]\",\n\t\t\t\t\ttt.expectedNoEndpoints, listener.endpointsAreNotCalled())\n\t\t\t}\n\n\t\t\tif listener.endpointsDoNotExist() != tt.expectedNoEndpointsServiceExists {\n\t\t\t\tt.Fatalf(\"Expected noEndpointsExist to be [%t], got [%t]\",\n\t\t\t\t\ttt.expectedNoEndpointsServiceExists, listener.endpointsDoNotExist())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestEndpointsWatcherDeletion(t *testing.T) {\n\tk8sConfigs := []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`,\n\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: name1\n  namespace: ns\nsubsets:\n- addresses:\n  - ip: 172.17.0.12\n    targetRef:\n      kind: Pod\n      name: name1-1\n      namespace: ns\n  ports:\n  - port: 8989`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-1\n  namespace: ns\nstatus:\n  phase: Running\n  podIP: 172.17.0.12`}\n\n\tfor _, tt := range []struct {\n\t\tserviceType      string\n\t\tk8sConfigs       []string\n\t\tid               ServiceID\n\t\thostname         string\n\t\tport             Port\n\t\tobjectToDelete   interface{}\n\t\tdeletingServices bool\n\t}{\n\t\t{\n\t\t\tserviceType:    \"can delete endpoints\",\n\t\t\tk8sConfigs:     k8sConfigs,\n\t\t\tid:             ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport:           8989,\n\t\t\thostname:       \"name1-1\",\n\t\t\tobjectToDelete: &corev1.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: \"name1\", Namespace: \"ns\"}},\n\t\t},\n\t\t{\n\t\t\tserviceType:    \"can delete endpoints when wrapped in a DeletedFinalStateUnknown\",\n\t\t\tk8sConfigs:     k8sConfigs,\n\t\t\tid:             ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport:           8989,\n\t\t\thostname:       \"name1-1\",\n\t\t\tobjectToDelete: &corev1.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: \"name1\", Namespace: \"ns\"}},\n\t\t},\n\t\t{\n\t\t\tserviceType:      \"can delete services\",\n\t\t\tk8sConfigs:       k8sConfigs,\n\t\t\tid:               ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport:             8989,\n\t\t\thostname:         \"name1-1\",\n\t\t\tobjectToDelete:   &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: \"name1\", Namespace: \"ns\"}},\n\t\t\tdeletingServices: true,\n\t\t},\n\t\t{\n\t\t\tserviceType:      \"can delete services when wrapped in a DeletedFinalStateUnknown\",\n\t\t\tk8sConfigs:       k8sConfigs,\n\t\t\tid:               ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport:             8989,\n\t\t\thostname:         \"name1-1\",\n\t\t\tobjectToDelete:   &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: \"name1\", Namespace: \"ns\"}},\n\t\t\tdeletingServices: true,\n\t\t},\n\t} {\n\n\t\ttt := tt // pin\n\t\tt.Run(\"subscribes listener to \"+tt.serviceType, func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\twatcher, err := NewEndpointsWatcher(k8sAPI, metadataAPI, logging.WithField(\"test\", t.Name()), false, \"local\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"can't create Endpoints watcher: %s\", err)\n\t\t\t}\n\n\t\t\tk8sAPI.Sync(nil)\n\t\t\tmetadataAPI.Sync(nil)\n\n\t\t\tlistener := newBufferingEndpointListener()\n\n\t\t\terr = watcher.Subscribe(tt.id, tt.port, tt.hostname, listener)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tif tt.deletingServices {\n\t\t\t\twatcher.deleteService(tt.objectToDelete)\n\t\t\t} else {\n\t\t\t\twatcher.deleteEndpoints(tt.objectToDelete)\n\t\t\t}\n\n\t\t\tif !listener.endpointsAreNotCalled() {\n\t\t\t\tt.Fatal(\"Expected NoEndpoints to be Called\")\n\t\t\t}\n\t\t})\n\n\t}\n}\n\nfunc TestEndpointsWatcherDeletionWithEndpointSlices(t *testing.T) {\n\tk8sConfigsWithES := []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n  - name: endpointslices\n    singularName: endpointslice\n    namespaced: true\n    kind: EndpointSlice\n    verbs:\n      - delete\n      - deletecollection\n      - get\n      - list\n      - patch\n      - create\n      - update\n      - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`, `\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.12\n  conditions:\n    ready: true\n  targetRef:\n    kind: Pod\n    name: name1-1\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\nkind: EndpointSlice\nmetadata:\n  labels:\n    kubernetes.io/service-name: name1\n  name: name1-del\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-1\n  namespace: ns\nstatus:\n  phase: Running\n  podIP: 172.17.0.12`}\n\n\tk8sConfigWithMultipleES := append(k8sConfigsWithES, []string{`\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.13\n  conditions:\n    ready: true\n  targetRef:\n    kind: Pod\n    name: name1-2\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\nkind: EndpointSlice\nmetadata:\n  labels:\n    kubernetes.io/service-name: name1\n  name: name1-live\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`, `apiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-2\n  namespace: ns\nstatus:\n  phase: Running\n  podIP: 172.17.0.13`}...)\n\n\tfor _, tt := range []struct {\n\t\tserviceType       string\n\t\tk8sConfigs        []string\n\t\tid                ServiceID\n\t\thostname          string\n\t\tport              Port\n\t\tobjectToDelete    interface{}\n\t\tdeletingServices  bool\n\t\thasSliceAccess    bool\n\t\tnoEndpointsCalled bool\n\t}{\n\t\t{\n\t\t\tserviceType:       \"can delete an EndpointSlice\",\n\t\t\tk8sConfigs:        k8sConfigsWithES,\n\t\t\tid:                ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport:              8989,\n\t\t\thostname:          \"name1-1\",\n\t\t\tobjectToDelete:    createTestEndpointSlice(consts.PodKind),\n\t\t\thasSliceAccess:    true,\n\t\t\tnoEndpointsCalled: true,\n\t\t},\n\t\t{\n\t\t\tserviceType:       \"can delete an EndpointSlice when wrapped in a DeletedFinalStateUnknown\",\n\t\t\tk8sConfigs:        k8sConfigsWithES,\n\t\t\tid:                ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport:              8989,\n\t\t\thostname:          \"name1-1\",\n\t\t\tobjectToDelete:    createTestEndpointSlice(consts.PodKind),\n\t\t\thasSliceAccess:    true,\n\t\t\tnoEndpointsCalled: true,\n\t\t},\n\t\t{\n\t\t\tserviceType:       \"can delete an EndpointSlice when there are multiple ones\",\n\t\t\tk8sConfigs:        k8sConfigWithMultipleES,\n\t\t\tid:                ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport:              8989,\n\t\t\thostname:          \"name1-1\",\n\t\t\tobjectToDelete:    createTestEndpointSlice(consts.PodKind),\n\t\t\thasSliceAccess:    true,\n\t\t\tnoEndpointsCalled: false,\n\t\t},\n\t} {\n\t\ttt := tt // pin\n\t\tt.Run(\"subscribes listener to \"+tt.serviceType, func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\twatcher, err := NewEndpointsWatcher(k8sAPI, metadataAPI, logging.WithField(\"test\", t.Name()), true, \"local\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"can't create Endpoints watcher: %s\", err)\n\t\t\t}\n\n\t\t\tk8sAPI.Sync(nil)\n\t\t\tmetadataAPI.Sync(nil)\n\n\t\t\tlistener := newBufferingEndpointListener()\n\n\t\t\terr = watcher.Subscribe(tt.id, tt.port, tt.hostname, listener)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\twatcher.deleteEndpointSlice(tt.objectToDelete)\n\n\t\t\tif listener.endpointsAreNotCalled() != tt.noEndpointsCalled {\n\t\t\t\tt.Fatalf(\"Expected noEndpointsCalled to be [%t], got [%t]\",\n\t\t\t\t\ttt.noEndpointsCalled, listener.endpointsAreNotCalled())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestEndpointsWatcherDeletionWithEndpointSlicesExternalWorkload(t *testing.T) {\n\tk8sConfigsWithES := []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n  - name: endpointslices\n    singularName: endpointslice\n    namespaced: true\n    kind: EndpointSlice\n    verbs:\n      - delete\n      - deletecollection\n      - get\n      - list\n      - patch\n      - create\n      - update\n      - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`, `\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.12\n  conditions:\n    ready: true\n  targetRef:\n    kind: ExternalWorkload\n    name: name1-1\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\nkind: EndpointSlice\nmetadata:\n  labels:\n    kubernetes.io/service-name: name1\n  name: name1-del\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`, `\napiVersion: workload.linkerd.io/v1beta1\nkind: ExternalWorkload\nmetadata:\n  name: name1-1\n  namespace: ns\nstatus:\n  conditions:\n  ready: true`}\n\n\tk8sConfigWithMultipleES := append(k8sConfigsWithES, []string{`\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.13\n  conditions:\n    ready: true\n  targetRef:\n    kind: ExternalWorkload\n    name: name1-2\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\nkind: EndpointSlice\nmetadata:\n  labels:\n    kubernetes.io/service-name: name1\n  name: name1-live\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`, `apiVersion: workload.linkerd.io/v1beta1\nkind: ExternalWorkload\nmetadata:\n  name: name1-2\n  namespace: ns\nstatus:\n  conditions:\n  ready: true`}...)\n\n\tfor _, tt := range []struct {\n\t\tserviceType       string\n\t\tk8sConfigs        []string\n\t\tid                ServiceID\n\t\thostname          string\n\t\tport              Port\n\t\tobjectToDelete    interface{}\n\t\tdeletingServices  bool\n\t\thasSliceAccess    bool\n\t\tnoEndpointsCalled bool\n\t}{\n\t\t{\n\t\t\tserviceType:       \"can delete an EndpointSlice\",\n\t\t\tk8sConfigs:        k8sConfigsWithES,\n\t\t\tid:                ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport:              8989,\n\t\t\thostname:          \"name1-1\",\n\t\t\tobjectToDelete:    createTestEndpointSlice(consts.ExtWorkloadKind),\n\t\t\thasSliceAccess:    true,\n\t\t\tnoEndpointsCalled: true,\n\t\t},\n\t\t{\n\t\t\tserviceType:       \"can delete an EndpointSlice when wrapped in a DeletedFinalStateUnknown\",\n\t\t\tk8sConfigs:        k8sConfigsWithES,\n\t\t\tid:                ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport:              8989,\n\t\t\thostname:          \"name1-1\",\n\t\t\tobjectToDelete:    createTestEndpointSlice(consts.ExtWorkloadKind),\n\t\t\thasSliceAccess:    true,\n\t\t\tnoEndpointsCalled: true,\n\t\t},\n\t\t{\n\t\t\tserviceType:       \"can delete an EndpointSlice when there are multiple ones\",\n\t\t\tk8sConfigs:        k8sConfigWithMultipleES,\n\t\t\tid:                ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport:              8989,\n\t\t\thostname:          \"name1-1\",\n\t\t\tobjectToDelete:    createTestEndpointSlice(consts.ExtWorkloadKind),\n\t\t\thasSliceAccess:    true,\n\t\t\tnoEndpointsCalled: false,\n\t\t},\n\t} {\n\t\ttt := tt // pin\n\t\tt.Run(\"subscribes listener to \"+tt.serviceType, func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\twatcher, err := NewEndpointsWatcher(k8sAPI, metadataAPI, logging.WithField(\"test\", t.Name()), true, \"local\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"can't create Endpoints watcher: %s\", err)\n\t\t\t}\n\n\t\t\tk8sAPI.Sync(nil)\n\t\t\tmetadataAPI.Sync(nil)\n\n\t\t\tlistener := newBufferingEndpointListener()\n\n\t\t\terr = watcher.Subscribe(tt.id, tt.port, tt.hostname, listener)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\twatcher.deleteEndpointSlice(tt.objectToDelete)\n\n\t\t\tif listener.endpointsAreNotCalled() != tt.noEndpointsCalled {\n\t\t\t\tt.Fatalf(\"Expected noEndpointsCalled to be [%t], got [%t]\",\n\t\t\t\t\ttt.noEndpointsCalled, listener.endpointsAreNotCalled())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestEndpointsWatcherServiceMirrors(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tserviceType                      string\n\t\tk8sConfigs                       []string\n\t\tid                               ServiceID\n\t\thostname                         string\n\t\tport                             Port\n\t\texpectedAddresses                []string\n\t\texpectedNoEndpoints              bool\n\t\texpectedNoEndpointsServiceExists bool\n\t\tenableEndpointSlices             bool\n\t}{\n\t\t{\n\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1-remote\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: name1-remote\n  namespace: ns\n  annotations:\n    mirror.linkerd.io/remote-gateway-identity: \"gateway-identity-1\"\n    mirror.linkerd.io/remote-svc-fq-name: \"name1-remote-fq\"\n  labels:\n    mirror.linkerd.io/mirrored-service: \"true\"\nsubsets:\n- addresses:\n  - ip: 172.17.0.12\n  ports:\n  - port: 8989`,\n\t\t\t},\n\t\t\tserviceType: \"mirrored service with identity\",\n\t\t\tid:          ServiceID{Name: \"name1-remote\", Namespace: \"ns\"},\n\t\t\tport:        8989,\n\t\t\texpectedAddresses: []string{\n\t\t\t\t\"172.17.0.12:8989/gateway-identity-1/name1-remote-fq:8989\",\n\t\t\t},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t},\n\t\t{\n\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1-remote\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`,\n\t\t\t\t`\napiVersion: discovery.k8s.io/v1\nkind: EndpointSlice\nmetadata:\n  name: name1-remote-xxxx\n  namespace: ns\n  annotations:\n    mirror.linkerd.io/remote-gateway-identity: \"gateway-identity-1\"\n    mirror.linkerd.io/remote-svc-fq-name: \"name1-remote-fq\"\n  labels:\n    mirror.linkerd.io/mirrored-service: \"true\"\n    kubernetes.io/service-name: name1-remote\nendpoints:\n- addresses:\n  - 172.17.0.12\nports:\n- port: 8989`,\n\t\t\t},\n\t\t\tserviceType: \"mirrored service with identity and endpoint slices\",\n\t\t\tid:          ServiceID{Name: \"name1-remote\", Namespace: \"ns\"},\n\t\t\tport:        8989,\n\t\t\texpectedAddresses: []string{\n\t\t\t\t\"172.17.0.12:8989/gateway-identity-1/name1-remote-fq:8989\",\n\t\t\t},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t\tenableEndpointSlices:             true,\n\t\t},\n\t\t{\n\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1-remote\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: name1-remote\n  namespace: ns\n  annotations:\n    mirror.linkerd.io/remote-svc-fq-name: \"name1-remote-fq\"\n  labels:\n    mirror.linkerd.io/mirrored-service: \"true\"\nsubsets:\n- addresses:\n  - ip: 172.17.0.12\n  ports:\n  - port: 8989`,\n\t\t\t},\n\t\t\tserviceType: \"mirrored service without identity\",\n\t\t\tid:          ServiceID{Name: \"name1-remote\", Namespace: \"ns\"},\n\t\t\tport:        8989,\n\t\t\texpectedAddresses: []string{\n\t\t\t\t\"172.17.0.12:8989/name1-remote-fq:8989\",\n\t\t\t},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t},\n\n\t\t{\n\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1-remote\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: name1-remote\n  namespace: ns\n  annotations:\n    mirror.linkerd.io/remote-gateway-identity: \"gateway-identity-1\"\n    mirror.linkerd.io/remote-svc-fq-name: \"name1-remote-fq\"\n  labels:\n    mirror.linkerd.io/mirrored-service: \"true\"\nsubsets:\n- addresses:\n  - ip: 172.17.0.12\n  ports:\n  - port: 9999`,\n\t\t\t},\n\t\t\tserviceType: \"mirrored service with remapped port in endpoints\",\n\t\t\tid:          ServiceID{Name: \"name1-remote\", Namespace: \"ns\"},\n\t\t\tport:        8989,\n\t\t\texpectedAddresses: []string{\n\t\t\t\t\"172.17.0.12:9999/gateway-identity-1/name1-remote-fq:8989\",\n\t\t\t},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t},\n\t\t{\n\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1-remote\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: name1-remote\n  namespace: ns\n  annotations:\n    mirror.linkerd.io/remote-gateway-identity: \"\"\n    mirror.linkerd.io/remote-svc-fq-name: \"name1-remote-fq\"\n  labels:\n    mirror.linkerd.io/mirrored-service: \"true\"\nsubsets:\n- addresses:\n  - ip: 172.17.0.12\n  ports:\n  - port: 9999`,\n\t\t\t},\n\t\t\tserviceType: \"mirrored service with empty identity and remapped port in endpoints\",\n\t\t\tid:          ServiceID{Name: \"name1-remote\", Namespace: \"ns\"},\n\t\t\tport:        8989,\n\t\t\texpectedAddresses: []string{\n\t\t\t\t\"172.17.0.12:9999/name1-remote-fq:8989\",\n\t\t\t},\n\t\t\texpectedNoEndpoints:              false,\n\t\t\texpectedNoEndpointsServiceExists: false,\n\t\t},\n\t} {\n\t\ttt := tt // pin\n\t\tt.Run(\"subscribes listener to \"+tt.serviceType, func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\twatcher, err := NewEndpointsWatcher(k8sAPI, metadataAPI, logging.WithField(\"test\", t.Name()), tt.enableEndpointSlices, \"local\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"can't create Endpoints watcher: %s\", err)\n\t\t\t}\n\n\t\t\tk8sAPI.Sync(nil)\n\t\t\tmetadataAPI.Sync(nil)\n\n\t\t\tlistener := newBufferingEndpointListener()\n\n\t\t\terr = watcher.Subscribe(tt.id, tt.port, tt.hostname, listener)\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tlistener.ExpectAdded(tt.expectedAddresses, t)\n\n\t\t\tif listener.endpointsAreNotCalled() != tt.expectedNoEndpoints {\n\t\t\t\tt.Fatalf(\"Expected noEndpointsCalled to be [%t], got [%t]\",\n\t\t\t\t\ttt.expectedNoEndpoints, listener.endpointsAreNotCalled())\n\t\t\t}\n\n\t\t\tif listener.endpointsDoNotExist() != tt.expectedNoEndpointsServiceExists {\n\t\t\t\tt.Fatalf(\"Expected noEndpointsExist to be [%t], got [%t]\",\n\t\t\t\t\ttt.expectedNoEndpointsServiceExists, listener.endpointsDoNotExist())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc testPod(resVersion string) *corev1.Pod {\n\treturn &corev1.Pod{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Pod\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tResourceVersion: resVersion,\n\t\t\tName:            \"name1-1\",\n\t\t\tNamespace:       \"ns\",\n\t\t},\n\t\tStatus: corev1.PodStatus{\n\t\t\tPhase: corev1.PodRunning,\n\t\t\tPodIP: \"172.17.0.12\",\n\t\t},\n\t}\n}\n\nfunc endpoints(identity string) *corev1.Endpoints {\n\treturn &corev1.Endpoints{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Endpoints\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"remote-service\",\n\t\t\tNamespace: \"ns\",\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tconsts.RemoteGatewayIdentity: identity,\n\t\t\t\tconsts.RemoteServiceFqName:   \"remote-service.svc.default.cluster.local\",\n\t\t\t},\n\t\t\tLabels: map[string]string{\n\t\t\t\tconsts.MirroredResourceLabel: \"true\",\n\t\t\t},\n\t\t},\n\t\tSubsets: []corev1.EndpointSubset{\n\t\t\t{\n\t\t\t\tAddresses: []corev1.EndpointAddress{\n\t\t\t\t\t{\n\t\t\t\t\t\tIP: \"1.2.3.4\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPorts: []corev1.EndpointPort{\n\t\t\t\t\t{\n\t\t\t\t\t\tPort: 80,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc createTestEndpointSlice(targetRefKind string) *dv1.EndpointSlice {\n\treturn &dv1.EndpointSlice{\n\t\tAddressType: \"IPv4\",\n\t\tObjectMeta:  metav1.ObjectMeta{Name: \"name1-del\", Namespace: \"ns\", Labels: map[string]string{dv1.LabelServiceName: \"name1\"}},\n\t\tEndpoints: []dv1.Endpoint{\n\t\t\t{\n\t\t\t\tAddresses:  []string{\"172.17.0.12\"},\n\t\t\t\tConditions: dv1.EndpointConditions{Ready: func(b bool) *bool { return &b }(true)},\n\t\t\t\tTargetRef:  &corev1.ObjectReference{Name: \"name1-1\", Namespace: \"ns\", Kind: targetRefKind},\n\t\t\t},\n\t\t},\n\t\tPorts: []dv1.EndpointPort{\n\t\t\t{\n\t\t\t\tName: func(s string) *string { return &s }(\"\"),\n\t\t\t\tPort: func(i int32) *int32 { return &i }(8989),\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc TestEndpointsChangeDetection(t *testing.T) {\n\n\tk8sConfigs := []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: remote-service\n  namespace: ns\nspec:\n  ports:\n  - port: 80\n    targetPort: 80`,\n\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: remote-service\n  namespace: ns\n  annotations:\n    mirror.linkerd.io/remote-gateway-identity: \"gateway-identity-1\"\n    mirror.linkerd.io/remote-svc-fq-name: \"remote-service.svc.default.cluster.local\"\n  labels:\n    mirror.linkerd.io/mirrored-service: \"true\"\nsubsets:\n- addresses:\n  - ip: 1.2.3.4\n  ports:\n  - port: 80`,\n\t}\n\n\tfor _, tt := range []struct {\n\t\tserviceType       string\n\t\tid                ServiceID\n\t\tport              Port\n\t\tnewEndpoints      *corev1.Endpoints\n\t\texpectedAddresses []string\n\t}{\n\t\t{\n\t\t\tserviceType:       \"will update endpoints if identity is different\",\n\t\t\tid:                ServiceID{Name: \"remote-service\", Namespace: \"ns\"},\n\t\t\tport:              80,\n\t\t\tnewEndpoints:      endpoints(\"gateway-identity-2\"),\n\t\t\texpectedAddresses: []string{\"1.2.3.4:80/gateway-identity-1/remote-service.svc.default.cluster.local:80\", \"1.2.3.4:80/gateway-identity-2/remote-service.svc.default.cluster.local:80\"},\n\t\t},\n\n\t\t{\n\t\t\tserviceType:       \"will not update endpoints if identity is the same\",\n\t\t\tid:                ServiceID{Name: \"remote-service\", Namespace: \"ns\"},\n\t\t\tport:              80,\n\t\t\tnewEndpoints:      endpoints(\"gateway-identity-1\"),\n\t\t\texpectedAddresses: []string{\"1.2.3.4:80/gateway-identity-1/remote-service.svc.default.cluster.local:80\"},\n\t\t},\n\t} {\n\n\t\ttt := tt // pin\n\t\tt.Run(\"subscribes listener to \"+tt.serviceType, func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\twatcher, err := NewEndpointsWatcher(k8sAPI, metadataAPI, logging.WithField(\"test\", t.Name()), false, \"local\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"can't create Endpoints watcher: %s\", err)\n\t\t\t}\n\n\t\t\tk8sAPI.Sync(nil)\n\t\t\tmetadataAPI.Sync(nil)\n\n\t\t\tlistener := newBufferingEndpointListener()\n\n\t\t\terr = watcher.Subscribe(tt.id, tt.port, \"\", listener)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tk8sAPI.Sync(nil)\n\n\t\t\twatcher.addEndpoints(tt.newEndpoints)\n\n\t\t\tlistener.ExpectAdded(tt.expectedAddresses, t)\n\t\t})\n\t}\n}\n\nfunc TestPodChangeDetection(t *testing.T) {\n\tendpoints := &corev1.Endpoints{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Endpoints\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"name1\",\n\t\t\tNamespace: \"ns\",\n\t\t},\n\t\tSubsets: []corev1.EndpointSubset{\n\t\t\t{\n\t\t\t\tAddresses: []corev1.EndpointAddress{\n\t\t\t\t\t{\n\t\t\t\t\t\tIP:       \"172.17.0.12\",\n\t\t\t\t\t\tHostname: \"name1-1\",\n\t\t\t\t\t\tTargetRef: &corev1.ObjectReference{\n\t\t\t\t\t\t\tKind:      \"Pod\",\n\t\t\t\t\t\t\tNamespace: \"ns\",\n\t\t\t\t\t\t\tName:      \"name1-1\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPorts: []corev1.EndpointPort{\n\t\t\t\t\t{\n\t\t\t\t\t\tPort: 8989,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tk8sConfigs := []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`,\n\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: name1\n  namespace: ns\nsubsets:\n- addresses:\n  - ip: 172.17.0.12\n    hostname: name1-1\n    targetRef:\n      kind: Pod\n      name: name1-1\n      namespace: ns\n  ports:\n  - port: 8989`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-1\n  namespace: ns\n  resourceVersion: \"1\"\nstatus:\n  phase: Running\n  podIP: 172.17.0.12`}\n\n\tfor _, tt := range []struct {\n\t\tserviceType       string\n\t\tid                ServiceID\n\t\thostname          string\n\t\tport              Port\n\t\tnewPod            *corev1.Pod\n\t\texpectedAddresses []string\n\t}{\n\t\t{\n\t\t\tserviceType: \"will update pod if resource version is different\",\n\t\t\tid:          ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport:        8989,\n\t\t\thostname:    \"name1-1\",\n\t\t\tnewPod:      testPod(\"2\"),\n\n\t\t\texpectedAddresses: []string{\"172.17.0.12:8989:1\", \"172.17.0.12:8989:2\"},\n\t\t},\n\t\t{\n\t\t\tserviceType: \"will not update pod if resource version is the same\",\n\t\t\tid:          ServiceID{Name: \"name1\", Namespace: \"ns\"},\n\t\t\tport:        8989,\n\t\t\thostname:    \"name1-1\",\n\t\t\tnewPod:      testPod(\"1\"),\n\n\t\t\texpectedAddresses: []string{\"172.17.0.12:8989:1\"},\n\t\t},\n\t} {\n\t\ttt := tt // pin\n\t\tt.Run(\"subscribes listener to \"+tt.serviceType, func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\twatcher, err := NewEndpointsWatcher(k8sAPI, metadataAPI, logging.WithField(\"test\", t.Name()), false, \"local\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"can't create Endpoints watcher: %s\", err)\n\t\t\t}\n\n\t\t\tk8sAPI.Sync(nil)\n\t\t\tmetadataAPI.Sync(nil)\n\n\t\t\tlistener := newBufferingEndpointListenerWithResVersion()\n\n\t\t\terr = watcher.Subscribe(tt.id, tt.port, tt.hostname, listener)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\terr = k8sAPI.Pod().Informer().GetStore().Add(tt.newPod)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tk8sAPI.Sync(nil)\n\n\t\t\twatcher.addEndpoints(endpoints)\n\t\t\tlistener.ExpectAdded(tt.expectedAddresses, t)\n\t\t})\n\t}\n}\n\n// Test that when an EndpointSlice is scaled down, the EndpointsWatcher sends\n// all of the Remove events, even if the associated pod / workload is no longer available\n// from the API.\nfunc TestEndpointSliceScaleDown(t *testing.T) {\n\tk8sConfigsWithES := []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n- name: endpointslices\n  singularName: endpointslice\n  namespaced: true\n  kind: EndpointSlice\n  verbs:\n    - delete\n    - deletecollection\n    - get\n    - list\n    - patch\n    - create\n    - update\n    - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`, `\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.12\n  conditions:\n  ready: true\n  targetRef:\n    kind: Pod\n    name: name1-1\n    namespace: ns\n  topology:\n  kubernetes.io/hostname: node-1\nkind: EndpointSlice\nmetadata:\n  labels:\n    kubernetes.io/service-name: name1\n  name: name1-es\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-1\n  namespace: ns\nstatus:\n  phase: Running\n  podIP: 172.17.0.12`}\n\n\t// Create an EndpointSlice with one endpoint, backed by a pod.\n\n\tk8sAPI, err := k8s.NewFakeAPI(k8sConfigsWithES...)\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t}\n\n\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t}\n\n\twatcher, err := NewEndpointsWatcher(k8sAPI, metadataAPI, logging.WithField(\"test\", t.Name()), true, \"local\")\n\tif err != nil {\n\t\tt.Fatalf(\"can't create Endpoints watcher: %s\", err)\n\t}\n\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\n\tlistener := newBufferingEndpointListener()\n\n\terr = watcher.Subscribe(ServiceID{Name: \"name1\", Namespace: \"ns\"}, 8989, \"\", listener)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tk8sAPI.Sync(nil)\n\n\tlistener.ExpectAdded([]string{\"172.17.0.12:8989\"}, t)\n\n\t// Delete the backing pod and scale the EndpointSlice to 0 endpoints.\n\n\terr = k8sAPI.Client.CoreV1().Pods(\"ns\").Delete(context.Background(), \"name1-1\", metav1.DeleteOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// It may take some time before the pod deletion is recognized by the\n\t// lister. We wait until the lister sees the pod as deleted.\n\terr = testutil.RetryFor(time.Second*30, func() error {\n\t\t_, err := k8sAPI.Pod().Lister().Pods(\"ns\").Get(\"name1-1\")\n\t\tif kerrors.IsNotFound(err) {\n\t\t\treturn nil\n\t\t}\n\t\tif err == nil {\n\t\t\treturn errors.New(\"pod should be deleted, but still exists in lister\")\n\t\t}\n\t\treturn err\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tES, err := k8sAPI.Client.DiscoveryV1().EndpointSlices(\"ns\").Get(context.Background(), \"name1-es\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\temptyES := &dv1.EndpointSlice{\n\t\tAddressType: \"IPv4\",\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"name1-es\", Namespace: \"ns\",\n\t\t\tLabels: map[string]string{dv1.LabelServiceName: \"name1\"},\n\t\t},\n\t\tEndpoints: []dv1.Endpoint{},\n\t\tPorts:     []dv1.EndpointPort{},\n\t}\n\n\twatcher.updateEndpointSlice(ES, emptyES)\n\n\t// Ensure the watcher emits a remove event.\n\n\tlistener.ExpectRemoved([]string{\"172.17.0.12:8989\"}, t)\n}\n\n// Test that when an endpointslice's endpoints change their readiness status to\n// not ready, this is correctly picked up by the subscribers\nfunc TestEndpointSliceChangeNotReady(t *testing.T) {\n\tk8sConfigsWithES := []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n- name: endpointslices\n  singularName: endpointslice\n  namespaced: true\n  kind: EndpointSlice\n  verbs:\n    - delete\n    - deletecollection\n    - get\n    - list\n    - patch\n    - create\n    - update\n    - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`, `\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.12\n  conditions:\n    ready: true\n  targetRef:\n    kind: Pod\n    name: name1-1\n    namespace: ns\n- addresses:\n  - 192.0.2.0\n  conditions:\n    ready: true\n  targetRef:\n    kind: ExternalWorkload\n    name: wlkd1\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\nkind: EndpointSlice\nmetadata:\n  labels:\n    kubernetes.io/service-name: name1\n  name: name1-es\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-1\n  namespace: ns\nstatus:\n  phase: Running\n  podIP: 172.17.0.12`, `\napiVersion: workload.linkerd.io/v1beta1\nkind: ExternalWorkload\nmetadata:\n  name: wlkd1\n  namespace: ns\nspec:\n  meshTLS:\n    identity: foo\n    serverName: foo\n  ports:\n  - port: 8989\n  workloadIPs:\n  - ip: 192.0.2.0\nstatus:\n  conditions:\n  - type: Ready\n    status: \"True\"\n`,\n\t}\n\n\tk8sAPI, err := k8s.NewFakeAPI(k8sConfigsWithES...)\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t}\n\n\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t}\n\n\twatcher, err := NewEndpointsWatcher(k8sAPI, metadataAPI, logging.WithField(\"test\", t.Name()), true, \"local\")\n\tif err != nil {\n\t\tt.Fatalf(\"can't create Endpoints watcher: %s\", err)\n\t}\n\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\n\tlistener := newBufferingEndpointListener()\n\n\terr = watcher.Subscribe(ServiceID{Name: \"name1\", Namespace: \"ns\"}, 8989, \"\", listener)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\n\tlistener.ExpectAdded([]string{\"172.17.0.12:8989\", \"192.0.2.0:8989\"}, t)\n\n\t// Change readiness status for pod and for external workload\n\tes, err := k8sAPI.Client.DiscoveryV1().EndpointSlices(\"ns\").Get(context.Background(), \"name1-es\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tunready := false\n\tes.Endpoints[0].Conditions.Ready = &unready\n\tes.Endpoints[1].Conditions.Ready = &unready\n\n\t_, err = k8sAPI.Client.DiscoveryV1().EndpointSlices(\"ns\").Update(context.Background(), es, metav1.UpdateOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\n\t// Wait for the update to be processed because there is no blocking call currently in k8s that we can wait on\n\ttime.Sleep(50 * time.Millisecond)\n\n\tlistener.ExpectRemoved([]string{\"172.17.0.12:8989\", \"192.0.2.0:8989\"}, t)\n}\n\n// Test that when an endpointslice's endpoints change their readiness status to\n// ready, this is correctly picked up by the subscribers\nfunc TestEndpointSliceChangeToReady(t *testing.T) {\n\tk8sConfigsWithES := []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n- name: endpointslices\n  singularName: endpointslice\n  namespaced: true\n  kind: EndpointSlice\n  verbs:\n    - delete\n    - deletecollection\n    - get\n    - list\n    - patch\n    - create\n    - update\n    - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`, `\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.12\n  conditions:\n    ready: true\n  targetRef:\n    kind: Pod\n    name: name1-1\n    namespace: ns\n- addresses:\n  - 172.17.0.13\n  conditions:\n    ready: false\n  targetRef:\n    kind: Pod\n    name: name1-2\n    namespace: ns\n- addresses:\n  - 192.0.2.0\n  conditions:\n    ready: true\n  targetRef:\n    kind: ExternalWorkload\n    name: wlkd1\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\n- addresses:\n  - 192.0.2.1\n  conditions:\n    ready: false\n  targetRef:\n    kind: ExternalWorkload\n    name: wlkd2\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\nkind: EndpointSlice\nmetadata:\n  labels:\n    kubernetes.io/service-name: name1\n  name: name1-es\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-1\n  namespace: ns\nstatus:\n  phase: Running\n  podIP: 172.17.0.12`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-2\n  namespace: ns\nstatus:\n  phase: Running\n  podIP: 172.17.0.13`, `\napiVersion: workload.linkerd.io/v1beta1\nkind: ExternalWorkload\nmetadata:\n  name: wlkd1\n  namespace: ns\nspec:\n  meshTLS:\n    identity: foo\n    serverName: foo\n  ports:\n  - port: 8989\n  workloadIPs:\n  - ip: 192.0.2.0\nstatus:\n  conditions:\n  - type: Ready\n    status: \"True\"\n`, `\napiVersion: workload.linkerd.io/v1beta1\nkind: ExternalWorkload\nmetadata:\n  name: wlkd2\n  namespace: ns\nspec:\n  meshTLS:\n    identity: foo\n    serverName: foo\n  ports:\n  - port: 8989\n  workloadIPs:\n  - ip: 192.0.2.1\nstatus:\n  conditions:\n  - type: Ready\n    status: \"True\"\n`,\n\t}\n\n\tk8sAPI, err := k8s.NewFakeAPI(k8sConfigsWithES...)\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t}\n\n\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t}\n\n\twatcher, err := NewEndpointsWatcher(k8sAPI, metadataAPI, logging.WithField(\"test\", t.Name()), true, \"local\")\n\tif err != nil {\n\t\tt.Fatalf(\"can't create Endpoints watcher: %s\", err)\n\t}\n\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\n\tlistener := newBufferingEndpointListener()\n\n\terr = watcher.Subscribe(ServiceID{Name: \"name1\", Namespace: \"ns\"}, 8989, \"\", listener)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\n\t// Expect only two endpoints to be added, the rest are not ready\n\tlistener.ExpectAdded([]string{\"172.17.0.12:8989\", \"192.0.2.0:8989\"}, t)\n\n\tes, err := k8sAPI.Client.DiscoveryV1().EndpointSlices(\"ns\").Get(context.Background(), \"name1-es\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Change readiness status for pod and for external workload only if they\n\t// are unready\n\trdy := true\n\tes.Endpoints[1].Conditions.Ready = &rdy\n\tes.Endpoints[3].Conditions.Ready = &rdy\n\n\t_, err = k8sAPI.Client.DiscoveryV1().EndpointSlices(\"ns\").Update(context.Background(), es, metav1.UpdateOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\n\t// Wait for the update to be processed because there is no blocking call currently in k8s that we can wait on\n\ttime.Sleep(50 * time.Millisecond)\n\n\tlistener.ExpectAdded([]string{\"172.17.0.12:8989\", \"172.17.0.13:8989\", \"192.0.2.0:8989\", \"192.0.2.1:8989\"}, t)\n\n}\n\n// Test that when an endpointslice gets a hint added, then mark it as a change\nfunc TestEndpointSliceAddHints(t *testing.T) {\n\tk8sConfigsWithES := []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n- name: endpointslices\n  singularName: endpointslice\n  namespaced: true\n  kind: EndpointSlice\n  verbs:\n    - delete\n    - deletecollection\n    - get\n    - list\n    - patch\n    - create\n    - update\n    - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`, `\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.12\n  conditions:\n  ready: true\n  targetRef:\n    kind: Pod\n    name: name1-1\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\nkind: EndpointSlice\nmetadata:\n  labels:\n    kubernetes.io/service-name: name1\n  name: name1-es\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-1\n  namespace: ns\nstatus:\n  phase: Running\n  podIP: 172.17.0.12`}\n\n\t// Create an EndpointSlice with one endpoint, backed by a pod.\n\n\tk8sAPI, err := k8s.NewFakeAPI(k8sConfigsWithES...)\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t}\n\n\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t}\n\n\twatcher, err := NewEndpointsWatcher(k8sAPI, metadataAPI, logging.WithField(\"test\", t.Name()), true, \"local\")\n\tif err != nil {\n\t\tt.Fatalf(\"can't create Endpoints watcher: %s\", err)\n\t}\n\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\n\tlistener := newBufferingEndpointListener()\n\n\terr = watcher.Subscribe(ServiceID{Name: \"name1\", Namespace: \"ns\"}, 8989, \"\", listener)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\n\tlistener.ExpectAdded([]string{\"172.17.0.12:8989\"}, t)\n\n\t// Add a hint to the EndpointSlice\n\tes, err := k8sAPI.Client.DiscoveryV1().EndpointSlices(\"ns\").Get(context.Background(), \"name1-es\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tes.Endpoints[0].Hints = &dv1.EndpointHints{\n\t\tForZones: []dv1.ForZone{{Name: \"zone1\"}},\n\t}\n\n\t_, err = k8sAPI.Client.DiscoveryV1().EndpointSlices(\"ns\").Update(context.Background(), es, metav1.UpdateOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\n\t// Wait for the update to be processed because there is no blocking call currently in k8s that we can wait on\n\ttime.Sleep(50 * time.Millisecond)\n\n\tlistener.ExpectAdded([]string{\"172.17.0.12:8989\", \"172.17.0.12:8989\"}, t)\n}\n\n// Test that when an endpointslice loses a hint, then mark it as a change\nfunc TestEndpointSliceRemoveHints(t *testing.T) {\n\tk8sConfigsWithES := []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: discovery.k8s.io/v1\nresources:\n- name: endpointslices\n  singularName: endpointslice\n  namespaced: true\n  kind: EndpointSlice\n  verbs:\n    - delete\n    - deletecollection\n    - get\n    - list\n    - patch\n    - create\n    - update\n    - watch\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: name1\n  namespace: ns\nspec:\n  type: LoadBalancer\n  ports:\n  - port: 8989`, `\naddressType: IPv4\napiVersion: discovery.k8s.io/v1\nendpoints:\n- addresses:\n  - 172.17.0.12\n  conditions:\n  hints:\n    forZones:\n    - name: zone1\n  ready: true\n  targetRef:\n    kind: Pod\n    name: name1-1\n    namespace: ns\n  topology:\n    kubernetes.io/hostname: node-1\nkind: EndpointSlice\nmetadata:\n  labels:\n    kubernetes.io/service-name: name1\n  name: name1-es\n  namespace: ns\nports:\n- name: \"\"\n  port: 8989`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: name1-1\n  namespace: ns\nstatus:\n  phase: Running\n  podIP: 172.17.0.12`}\n\n\t// Create an EndpointSlice with one endpoint, backed by a pod.\n\n\tk8sAPI, err := k8s.NewFakeAPI(k8sConfigsWithES...)\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t}\n\n\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeMetadataAPI returned an error: %s\", err)\n\t}\n\n\twatcher, err := NewEndpointsWatcher(k8sAPI, metadataAPI, logging.WithField(\"test\", t.Name()), true, \"local\")\n\tif err != nil {\n\t\tt.Fatalf(\"can't create Endpoints watcher: %s\", err)\n\t}\n\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\n\tlistener := newBufferingEndpointListener()\n\n\terr = watcher.Subscribe(ServiceID{Name: \"name1\", Namespace: \"ns\"}, 8989, \"\", listener)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\n\tlistener.ExpectAdded([]string{\"172.17.0.12:8989\"}, t)\n\n\t// Remove a hint from the EndpointSlice\n\tes, err := k8sAPI.Client.DiscoveryV1().EndpointSlices(\"ns\").Get(context.Background(), \"name1-es\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tes.Endpoints[0].Hints = &dv1.EndpointHints{\n\t\t//ForZones: []dv1.ForZone{{Name: \"zone1\"}},\n\t}\n\n\t_, err = k8sAPI.Client.DiscoveryV1().EndpointSlices(\"ns\").Update(context.Background(), es, metav1.UpdateOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\n\t// Wait for the update to be processed because there is no blocking call currently in k8s that we can wait on\n\ttime.Sleep(50 * time.Millisecond)\n\n\tlistener.ExpectAdded([]string{\"172.17.0.12:8989\", \"172.17.0.12:8989\"}, t)\n}\n"
  },
  {
    "path": "controller/api/destination/watcher/k8s.go",
    "content": "package watcher\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\n\text \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\nconst (\n\t// PodIPIndex is the key for the index based on Pod IPs\n\tPodIPIndex = \"ip\"\n\t// HostIPIndex is the key for the index based on Host IP of pods with host network enabled\n\tHostIPIndex = \"hostIP\"\n\t// ExternalWorkloadIPIndex is the key for the index based on IP of externalworkloads\n\tExternalWorkloadIPIndex = \"externalWorkloadIP\"\n)\n\ntype (\n\t// IPPort holds the IP and port for some destination\n\tIPPort struct {\n\t\tIP   string\n\t\tPort Port\n\t}\n\n\t// ID is a namespace-qualified name.\n\tID struct {\n\t\tNamespace string\n\t\tName      string\n\n\t\t// Only used for PodID\n\t\tIPFamily corev1.IPFamily\n\t}\n\t// ServiceID is the namespace-qualified name of a service.\n\tServiceID = ID\n\t// PodID is the namespace-qualified name of a pod.\n\tPodID = ID\n\t// ProfileID is the namespace-qualified name of a service profile.\n\tProfileID = ID\n\t// PodID is the namespace-qualified name of an ExternalWorkload.\n\tExternalWorkloadID = ID\n\n\t// Port is a numeric port.\n\tPort      = uint32\n\tnamedPort = intstr.IntOrString\n\n\t// InvalidService is an error which indicates that the authority is not a\n\t// valid service.\n\tInvalidService struct {\n\t\tauthority string\n\t}\n)\n\n// Labels returns the labels for prometheus metrics associated to the service\nfunc (id ServiceID) Labels() prometheus.Labels {\n\treturn prometheus.Labels{\"namespace\": id.Namespace, \"name\": id.Name}\n}\n\nfunc (is InvalidService) Error() string {\n\treturn fmt.Sprintf(\"Invalid k8s service %s\", is.authority)\n}\n\nfunc invalidService(authority string) InvalidService {\n\treturn InvalidService{authority}\n}\n\nfunc (i ID) String() string {\n\treturn fmt.Sprintf(\"%s/%s\", i.Namespace, i.Name)\n}\n\n// InitializeIndexers is used to initialize indexers on k8s informers, to be used across watchers\nfunc InitializeIndexers(k8sAPI *k8s.API) error {\n\terr := k8sAPI.Svc().Informer().AddIndexers(cache.Indexers{PodIPIndex: func(obj interface{}) ([]string, error) {\n\t\tsvc, ok := obj.(*corev1.Service)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"object is not a service\")\n\t\t}\n\n\t\tif len(svc.Spec.ClusterIPs) != 0 {\n\t\t\treturn svc.Spec.ClusterIPs, nil\n\t\t}\n\n\t\tif svc.Spec.ClusterIP != \"\" {\n\t\t\treturn []string{svc.Spec.ClusterIP}, nil\n\t\t}\n\n\t\treturn nil, nil\n\t}})\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not create an indexer for services: %w\", err)\n\t}\n\n\terr = k8sAPI.Pod().Informer().AddIndexers(cache.Indexers{PodIPIndex: func(obj interface{}) ([]string, error) {\n\t\tif pod, ok := obj.(*corev1.Pod); ok {\n\t\t\t// Pods that run in the host network are indexed by the host IP\n\t\t\t// indexer in the IP watcher; they should be skipped by the pod\n\t\t\t// IP indexer which is responsible only for indexing pod network\n\t\t\t// pods.\n\t\t\tif pod.Spec.HostNetwork {\n\t\t\t\treturn nil, nil\n\t\t\t}\n\t\t\tips := []string{}\n\t\t\tfor _, pip := range pod.Status.PodIPs {\n\t\t\t\tif pip.IP != \"\" {\n\t\t\t\t\tips = append(ips, pip.IP)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif len(ips) == 0 && pod.Status.PodIP != \"\" {\n\t\t\t\tips = append(ips, pod.Status.PodIP)\n\t\t\t}\n\t\t\treturn ips, nil\n\t\t}\n\t\treturn nil, fmt.Errorf(\"object is not a pod\")\n\t}})\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not create an indexer for pods: %w\", err)\n\t}\n\n\terr = k8sAPI.Pod().Informer().AddIndexers(cache.Indexers{HostIPIndex: func(obj interface{}) ([]string, error) {\n\t\tpod, ok := obj.(*corev1.Pod)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"object is not a pod\")\n\t\t}\n\n\t\tips := []string{}\n\t\tfor _, hip := range pod.Status.HostIPs {\n\t\t\tips = append(ips, hip.IP)\n\t\t}\n\t\tif len(ips) == 0 && pod.Status.HostIP != \"\" {\n\t\t\tips = append(ips, pod.Status.HostIP)\n\t\t}\n\t\tif len(ips) == 0 {\n\t\t\treturn []string{}, nil\n\t\t}\n\n\t\t// If the pod is reachable from the host network, then for\n\t\t// each of its containers' ports that exposes a host port, add\n\t\t// that hostIP:hostPort endpoint to the indexer.\n\t\taddrs := []string{}\n\t\tfor _, c := range append(pod.Spec.InitContainers, pod.Spec.Containers...) {\n\t\t\tfor _, p := range c.Ports {\n\t\t\t\tif p.HostPort == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfor _, ip := range ips {\n\t\t\t\t\taddrs = append(addrs, net.JoinHostPort(ip, fmt.Sprintf(\"%d\", p.HostPort)))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn addrs, nil\n\t}})\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not create an indexer for pods: %w\", err)\n\t}\n\n\terr = k8sAPI.ExtWorkload().Informer().AddIndexers(cache.Indexers{ExternalWorkloadIPIndex: func(obj interface{}) ([]string, error) {\n\t\tew, ok := obj.(*ext.ExternalWorkload)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"object is not an externalworkload\")\n\t\t}\n\n\t\taddrs := []string{}\n\t\tfor _, ip := range ew.Spec.WorkloadIPs {\n\t\t\tfor _, port := range ew.Spec.Ports {\n\t\t\t\taddrs = append(addrs, net.JoinHostPort(ip.Ip, fmt.Sprintf(\"%d\", port.Port)))\n\t\t\t}\n\t\t}\n\t\treturn addrs, nil\n\t}})\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not create an indexer for externalworkloads: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc getIndexedPods(k8sAPI *k8s.API, indexName string, key string) ([]*corev1.Pod, error) {\n\tobjs, err := k8sAPI.Pod().Informer().GetIndexer().ByIndex(indexName, key)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed getting %s indexed pods: %w\", indexName, err)\n\t}\n\tpods := make([]*corev1.Pod, 0)\n\tfor _, obj := range objs {\n\t\tpod := obj.(*corev1.Pod)\n\t\tif !podNotTerminating(pod) {\n\t\t\tcontinue\n\t\t}\n\t\tpods = append(pods, pod)\n\t}\n\treturn pods, nil\n}\n\nfunc getIndexedExternalWorkloads(k8sAPI *k8s.API, indexName string, key string) ([]*ext.ExternalWorkload, error) {\n\tobjs, err := k8sAPI.ExtWorkload().Informer().GetIndexer().ByIndex(indexName, key)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed getting %s indexed externalworkloads: %w\", indexName, err)\n\t}\n\tworkloads := make([]*ext.ExternalWorkload, 0)\n\tfor _, obj := range objs {\n\t\tworkload := obj.(*ext.ExternalWorkload)\n\t\tworkloads = append(workloads, workload)\n\t}\n\treturn workloads, nil\n}\n\nfunc podNotTerminating(pod *corev1.Pod) bool {\n\tphase := pod.Status.Phase\n\tpodTerminated := phase == corev1.PodSucceeded || phase == corev1.PodFailed\n\tpodTerminating := pod.DeletionTimestamp != nil\n\treturn !podTerminating && !podTerminated\n}\n"
  },
  {
    "path": "controller/api/destination/watcher/opaque_ports_watcher.go",
    "content": "package watcher\n\nimport (\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tlabels \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/util\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\ntype (\n\t// OpaquePortsWatcher watches all the services in the cluster. If the\n\t// opaque ports annotation is added to a service, the watcher will update\n\t// listeners—if any—subscribed to that service.\n\tOpaquePortsWatcher struct {\n\t\tsubscriptions      map[ServiceID]*svcSubscriptions\n\t\tk8sAPI             *k8s.API\n\t\tsubscribersGauge   *prometheus.GaugeVec\n\t\tlog                *logging.Entry\n\t\tdefaultOpaquePorts map[uint32]struct{}\n\t\tsync.RWMutex\n\t}\n\n\tsvcSubscriptions struct {\n\t\topaquePorts map[uint32]struct{}\n\t\tlisteners   []OpaquePortsUpdateListener\n\t}\n\n\t// OpaquePortsUpdateListener is the interface that subscribers must implement.\n\tOpaquePortsUpdateListener interface {\n\t\tUpdateService(ports map[uint32]struct{})\n\t}\n)\n\nvar opaquePortsMetrics = promauto.NewGaugeVec(\n\tprometheus.GaugeOpts{\n\t\tName: \"service_subscribers\",\n\t\tHelp: \"Number of subscribers to Service changes.\",\n\t},\n\t[]string{\"namespace\", \"name\"},\n)\n\n// NewOpaquePortsWatcher creates a OpaquePortsWatcher and begins watching for\n// k8sAPI for service changes.\nfunc NewOpaquePortsWatcher(k8sAPI *k8s.API, log *logging.Entry, opaquePorts map[uint32]struct{}) (*OpaquePortsWatcher, error) {\n\topw := &OpaquePortsWatcher{\n\t\tsubscriptions:      make(map[ServiceID]*svcSubscriptions),\n\t\tk8sAPI:             k8sAPI,\n\t\tsubscribersGauge:   opaquePortsMetrics,\n\t\tlog:                log.WithField(\"component\", \"opaque-ports-watcher\"),\n\t\tdefaultOpaquePorts: opaquePorts,\n\t}\n\t_, err := k8sAPI.Svc().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc:    opw.addService,\n\t\tDeleteFunc: opw.deleteService,\n\t\tUpdateFunc: opw.updateService,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn opw, nil\n}\n\n// Subscribe subscribes a listener to a service; each time the service\n// changes, the listener will be updated if the list of opaque ports\n// changes.\nfunc (opw *OpaquePortsWatcher) Subscribe(id ServiceID, listener OpaquePortsUpdateListener) error {\n\topw.Lock()\n\tdefer opw.Unlock()\n\tsvc, _ := opw.k8sAPI.Svc().Lister().Services(id.Namespace).Get(id.Name)\n\tif svc != nil && svc.Spec.Type == corev1.ServiceTypeExternalName {\n\t\treturn invalidService(id.String())\n\t}\n\topw.log.Debugf(\"Starting watch on service %s\", id)\n\tvar numListeners float64\n\tss, ok := opw.subscriptions[id]\n\tif !ok {\n\t\t// If there is no watched service, create a subscription for the service\n\t\t// and no opaque ports\n\t\topw.subscriptions[id] = &svcSubscriptions{\n\t\t\topaquePorts: opw.defaultOpaquePorts,\n\t\t\tlisteners:   []OpaquePortsUpdateListener{listener},\n\t\t}\n\t\tnumListeners = 1\n\t} else {\n\t\t// There are subscriptions for this service, so add the listener to the\n\t\t// service listeners. If there are opaque ports for the service, update\n\t\t// the listener with that value.\n\t\tss.listeners = append(ss.listeners, listener)\n\t\tlistener.UpdateService(ss.opaquePorts)\n\t\tnumListeners = float64(len(ss.listeners))\n\t}\n\n\tgauge, err := opw.subscribersGauge.GetMetricWith(id.Labels())\n\tif err != nil {\n\t\topw.log.Errorf(\"failed to get service_subscribers metric: %q\", err)\n\t} else {\n\t\tgauge.Set(numListeners)\n\t}\n\n\treturn nil\n}\n\n// Unsubscribe unsubscribes a listener from service.\nfunc (opw *OpaquePortsWatcher) Unsubscribe(id ServiceID, listener OpaquePortsUpdateListener) {\n\topw.Lock()\n\tdefer opw.Unlock()\n\topw.log.Debugf(\"Stopping watch on service %s\", id)\n\tss, ok := opw.subscriptions[id]\n\tif !ok {\n\t\topw.log.Errorf(\"Cannot unsubscribe from unknown service %s\", id)\n\t\treturn\n\t}\n\tfor i, l := range ss.listeners {\n\t\tif l == listener {\n\t\t\tn := len(ss.listeners)\n\t\t\tss.listeners[i] = ss.listeners[n-1]\n\t\t\tss.listeners[n-1] = nil\n\t\t\tss.listeners = ss.listeners[:n-1]\n\t\t}\n\t}\n\n\tlabels := id.Labels()\n\tif len(ss.listeners) > 0 {\n\t\tgauge, err := opw.subscribersGauge.GetMetricWith(labels)\n\t\tif err != nil {\n\t\t\topw.log.Errorf(\"failed to get service_subscribers metric: %q\", err)\n\t\t} else {\n\t\t\tgauge.Set(float64(len(ss.listeners)))\n\t\t}\n\t} else {\n\t\tif !opw.subscribersGauge.Delete(labels) {\n\t\t\topw.log.Warnf(\"unable to delete service_subscribers metric with labels %s\", labels)\n\t\t}\n\t\tdelete(opw.subscriptions, id)\n\t}\n}\n\nfunc (opw *OpaquePortsWatcher) updateService(oldObj interface{}, newObj interface{}) {\n\tnewSvc := newObj.(*corev1.Service)\n\toldSvc := oldObj.(*corev1.Service)\n\n\toldUpdated := latestUpdated(oldSvc.ManagedFields)\n\tupdated := latestUpdated(newSvc.ManagedFields)\n\tif !updated.IsZero() && updated != oldUpdated {\n\t\tdelta := time.Since(updated)\n\t\tserviceInformerLag.Observe(delta.Seconds())\n\t}\n\topw.addService(newObj)\n}\n\nfunc (opw *OpaquePortsWatcher) addService(obj interface{}) {\n\topw.Lock()\n\tdefer opw.Unlock()\n\tsvc := obj.(*corev1.Service)\n\tid := ServiceID{\n\t\tNamespace: svc.Namespace,\n\t\tName:      svc.Name,\n\t}\n\topaquePorts, ok, err := getServiceOpaquePortsAnnotation(svc)\n\tif err != nil {\n\t\topw.log.Errorf(\"failed to get %s service opaque ports annotation: %s\", id, err)\n\t\treturn\n\t}\n\t// If the opaque ports annotation was not set, then set the service's\n\t// opaque ports to the default value.\n\tif !ok {\n\t\topaquePorts = opw.defaultOpaquePorts\n\t}\n\tss, ok := opw.subscriptions[id]\n\t// If there are no subscriptions for this service, create one with the\n\t// opaque ports.\n\tif !ok {\n\t\topw.subscriptions[id] = &svcSubscriptions{\n\t\t\topaquePorts: opaquePorts,\n\t\t\tlisteners:   []OpaquePortsUpdateListener{},\n\t\t}\n\t\treturn\n\t}\n\t// Do not send updates if there was no change in the opaque ports; if\n\t// there was, send an update to each listener.\n\tif portsEqual(ss.opaquePorts, opaquePorts) {\n\t\treturn\n\t}\n\tss.opaquePorts = opaquePorts\n\tfor _, listener := range ss.listeners {\n\t\tlistener.UpdateService(ss.opaquePorts)\n\t}\n}\n\nfunc (opw *OpaquePortsWatcher) deleteService(obj interface{}) {\n\topw.Lock()\n\tdefer opw.Unlock()\n\tservice, ok := obj.(*corev1.Service)\n\tif !ok {\n\t\ttombstone, ok := obj.(cache.DeletedFinalStateUnknown)\n\t\tif !ok {\n\t\t\topw.log.Errorf(\"could not get object from DeletedFinalStateUnknown %#v\", obj)\n\t\t\treturn\n\t\t}\n\t\tservice, ok = tombstone.Obj.(*corev1.Service)\n\t\tif !ok {\n\t\t\topw.log.Errorf(\"DeletedFinalStateUnknown contained object that is not a Service %#v\", obj)\n\t\t\treturn\n\t\t}\n\t}\n\tid := ServiceID{\n\t\tNamespace: service.Namespace,\n\t\tName:      service.Name,\n\t}\n\tss, ok := opw.subscriptions[id]\n\tif !ok {\n\t\treturn\n\t}\n\told := ss.opaquePorts\n\tss.opaquePorts = opw.defaultOpaquePorts\n\t// Do not send an update if the service already had the default opaque ports\n\tif portsEqual(old, ss.opaquePorts) {\n\t\treturn\n\t}\n\tfor _, listener := range ss.listeners {\n\t\tlistener.UpdateService(ss.opaquePorts)\n\t}\n}\n\nfunc getServiceOpaquePortsAnnotation(svc *corev1.Service) (map[uint32]struct{}, bool, error) {\n\tannotation, ok := svc.Annotations[labels.ProxyOpaquePortsAnnotation]\n\tif !ok {\n\t\treturn nil, false, nil\n\t}\n\topaquePorts := make(map[uint32]struct{})\n\tif annotation != \"\" {\n\t\tfor _, portStr := range parseServiceOpaquePorts(annotation, svc.Spec.Ports) {\n\t\t\tport, err := strconv.ParseUint(portStr, 10, 32)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, true, err\n\t\t\t}\n\t\t\topaquePorts[uint32(port)] = struct{}{}\n\t\t}\n\t}\n\treturn opaquePorts, true, nil\n}\n\nfunc parseServiceOpaquePorts(annotation string, sps []corev1.ServicePort) []string {\n\tportRanges := util.GetPortRanges(annotation)\n\tvar values []string\n\tfor _, pr := range portRanges {\n\t\tport, named := isNamed(pr, sps)\n\t\tif named {\n\t\t\tvalues = append(values, strconv.Itoa(int(port)))\n\t\t} else {\n\t\t\tpr, err := util.ParsePortRange(pr)\n\t\t\tif err != nil {\n\t\t\t\tlogging.Warnf(\"Invalid port range [%v]: %s\", pr, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor i := pr.LowerBound; i <= pr.UpperBound; i++ {\n\t\t\t\tvalues = append(values, strconv.Itoa(i))\n\t\t\t}\n\t\t}\n\t}\n\treturn values\n}\n\n// isNamed checks if a port range is actually a service named port (e.g.\n// `123-456` is a valid name, but also is a valid range); all port names must\n// be checked before making it a list.\nfunc isNamed(pr string, sps []corev1.ServicePort) (int32, bool) {\n\tfor _, sp := range sps {\n\t\tif sp.Name == pr {\n\t\t\treturn sp.Port, true\n\t\t}\n\t}\n\treturn 0, false\n}\n\nfunc portsEqual(x, y map[uint32]struct{}) bool {\n\tif len(x) != len(y) {\n\t\treturn false\n\t}\n\tfor port := range x {\n\t\t_, ok := y[port]\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "controller/api/destination/watcher/opaque_ports_watcher_test.go",
    "content": "package watcher\n\nimport (\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nvar (\n\ttestNS = `\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: ns`\n\ttestNSObject = corev1.Namespace{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"ns\",\n\t\t},\n\t}\n\tbaseService = `\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc\n  namespace: ns`\n\tbaseServiceObject = corev1.Service{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"svc\",\n\t\t\tNamespace: \"ns\",\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tPorts: []corev1.ServicePort{{Port: 8080}},\n\t\t},\n\t}\n\topaqueService = `\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc\n  namespace: ns\n  annotations:\n    config.linkerd.io/opaque-ports: \"3306\"`\n\topaqueServiceObject = corev1.Service{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        \"svc\",\n\t\t\tNamespace:   \"ns\",\n\t\t\tAnnotations: map[string]string{\"config.linkerd.io/opaque-ports\": \"3306\"},\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tPorts: []corev1.ServicePort{{Port: 3306}},\n\t\t},\n\t}\n\topaqueServiceMultiPort = `\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc\n  namespace: ns\n  annotations:\n    config.linkerd.io/opaque-ports: \"3306, 665\"`\n\topaqueServiceMultiPortObject = corev1.Service{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        \"svc\",\n\t\t\tNamespace:   \"ns\",\n\t\t\tAnnotations: map[string]string{\"config.linkerd.io/opaque-ports\": \"3306, 665\"},\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tPorts: []corev1.ServicePort{{Port: 3306}, {Port: 665}},\n\t\t},\n\t}\n\texplicitlyNotOpaqueService = `\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc\n  namespace: ns\n  annotations:\n    config.linkerd.io/opaque-ports: \"\"`\n\texplicitlyNotOpaqueServiceObject = corev1.Service{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        \"svc\",\n\t\t\tNamespace:   \"ns\",\n\t\t\tAnnotations: map[string]string{\"config.linkerd.io/opaque-ports\": \"\"},\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tPorts: []corev1.ServicePort{{Port: 3306}},\n\t\t},\n\t}\n)\n\ntype testOpaquePortsListener struct {\n\tupdates []map[uint32]struct{}\n}\n\nfunc newTestOpaquePortsListener() *testOpaquePortsListener {\n\treturn &testOpaquePortsListener{\n\t\tupdates: []map[uint32]struct{}{},\n\t}\n}\n\nfunc (bopl *testOpaquePortsListener) UpdateService(ports map[uint32]struct{}) {\n\tbopl.updates = append(bopl.updates, ports)\n}\n\nfunc TestOpaquePortsWatcher(t *testing.T) {\n\tdefaultOpaquePorts := map[uint32]struct{}{\n\t\t25:    {},\n\t\t443:   {},\n\t\t587:   {},\n\t\t3306:  {},\n\t\t5432:  {},\n\t\t11211: {},\n\t}\n\n\tfor _, tt := range []struct {\n\t\tname                string\n\t\tinitialState        []string\n\t\tnsObject            interface{}\n\t\tsvcObject           interface{}\n\t\tservice             ServiceID\n\t\texpectedOpaquePorts []map[uint32]struct{}\n\t}{\n\t\t{\n\t\t\tname:         \"namespace and service\",\n\t\t\tinitialState: []string{testNS, baseService},\n\t\t\tnsObject:     &testNSObject,\n\t\t\tsvcObject:    &baseServiceObject,\n\t\t\tservice: ServiceID{\n\t\t\t\tName:      \"svc\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t},\n\t\t\t// 1. default opaque ports\n\t\t\t// 2. svc updated: no update\n\t\t\t// 3. svc deleted: no update\n\t\t\t// 4. svc created: ?\n\t\t\texpectedOpaquePorts: []map[uint32]struct{}{{11211: {}, 25: {}, 3306: {}, 443: {}, 5432: {}, 587: {}}},\n\t\t},\n\t\t{\n\t\t\tname:         \"namespace with opaque service\",\n\t\t\tinitialState: []string{testNS, opaqueService},\n\t\t\tnsObject:     &testNSObject,\n\t\t\tsvcObject:    &opaqueServiceObject,\n\t\t\tservice: ServiceID{\n\t\t\t\tName:      \"svc\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t},\n\t\t\t// 1: svc annotation 3306\n\t\t\t// 2: svc updated: no update\n\t\t\t// 2: svc deleted: update with default ports\n\t\t\t// 3. svc created: update with port 3306\n\t\t\texpectedOpaquePorts: []map[uint32]struct{}{{3306: {}}, {11211: {}, 25: {}, 3306: {}, 443: {}, 5432: {}, 587: {}}, {3306: {}}},\n\t\t},\n\t\t{\n\t\t\tname:         \"namespace with multi port opaque service\",\n\t\t\tinitialState: []string{testNS, opaqueServiceMultiPort},\n\t\t\tnsObject:     &testNSObject,\n\t\t\tsvcObject:    &opaqueServiceMultiPortObject,\n\t\t\tservice: ServiceID{\n\t\t\t\tName:      \"svc\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t},\n\t\t\t// 1: svc annotation 3306, 665 (with whitespace)\n\t\t\t// 2: svc updated: no update\n\t\t\t// 2: svc deleted: update with default ports\n\t\t\t// 3. svc created: update with port 3306, 665\n\t\t\texpectedOpaquePorts: []map[uint32]struct{}{{3306: {}, 665: {}}, {11211: {}, 25: {}, 3306: {}, 443: {}, 5432: {}, 587: {}}, {3306: {}, 665: {}}},\n\t\t},\n\t\t{\n\t\t\tname:         \"namespace and service, create opaque service\",\n\t\t\tinitialState: []string{testNS, baseService},\n\t\t\tnsObject:     &testNSObject,\n\t\t\tsvcObject:    &opaqueServiceObject,\n\t\t\tservice: ServiceID{\n\t\t\t\tName:      \"svc\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t},\n\t\t\t// 1: default opaque ports\n\t\t\t// 2: svc updated: update with port 3306\n\t\t\t// 3: svc deleted: update with default ports\n\t\t\t// 4. svc created: update with port 3306\n\t\t\texpectedOpaquePorts: []map[uint32]struct{}{{11211: {}, 25: {}, 3306: {}, 443: {}, 5432: {}, 587: {}}, {3306: {}}, {11211: {}, 25: {}, 3306: {}, 443: {}, 5432: {}, 587: {}}, {3306: {}}},\n\t\t},\n\t\t{\n\t\t\tname:         \"namespace and opaque service, create base service\",\n\t\t\tinitialState: []string{testNS, opaqueService},\n\t\t\tnsObject:     &testNSObject,\n\t\t\tsvcObject:    &baseServiceObject,\n\t\t\tservice: ServiceID{\n\t\t\t\tName:      \"svc\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t},\n\t\t\t// 1: svc annotation 3306\n\t\t\t// 2. svc updated: update with default ports\n\t\t\t// 3. svc deleted: no update\n\t\t\t// 4. svc added: no update\n\t\t\texpectedOpaquePorts: []map[uint32]struct{}{{3306: {}}, {11211: {}, 25: {}, 3306: {}, 443: {}, 5432: {}, 587: {}}},\n\t\t},\n\t\t{\n\t\t\tname:         \"namespace and explicitly not opaque service, create explicitly not opaque service\",\n\t\t\tinitialState: []string{testNS, explicitlyNotOpaqueService},\n\t\t\tnsObject:     &testNSObject,\n\t\t\tsvcObject:    &explicitlyNotOpaqueServiceObject,\n\t\t\tservice: ServiceID{\n\t\t\t\tName:      \"svc\",\n\t\t\t\tNamespace: \"ns\",\n\t\t\t},\n\t\t\t// 1: svc annotation empty\n\t\t\t// 2. svc updated: no update\n\t\t\t// 3. svc deleted: update with default ports\n\t\t\t// 4. svc added: update with no ports\n\t\t\texpectedOpaquePorts: []map[uint32]struct{}{{}, {11211: {}, 25: {}, 3306: {}, 443: {}, 5432: {}, 587: {}}, {}},\n\t\t},\n\t} {\n\t\tk8sAPI, err := k8s.NewFakeAPI(tt.initialState...)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t}\n\t\twatcher, err := NewOpaquePortsWatcher(k8sAPI, logging.WithField(\"test\", t.Name()), defaultOpaquePorts)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"can't create opaque ports watcher: %s\", err)\n\t\t}\n\t\tk8sAPI.Sync(nil)\n\t\tlistener := newTestOpaquePortsListener()\n\t\twatcher.Subscribe(tt.service, listener)\n\t\twatcher.addService(tt.svcObject)\n\t\twatcher.deleteService(tt.svcObject)\n\t\twatcher.addService(tt.svcObject)\n\t\ttestCompare(t, tt.expectedOpaquePorts, listener.updates)\n\t}\n}\n"
  },
  {
    "path": "controller/api/destination/watcher/profile_watcher.go",
    "content": "package watcher\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tsplisters \"github.com/linkerd/linkerd2/controller/gen/client/listers/serviceprofile/v1alpha2\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\ntype (\n\t// ProfileWatcher watches all service profiles in the Kubernetes cluster.\n\t// Listeners can subscribe to a particular profile and profileWatcher will\n\t// publish the service profile and all future changes for that profile.\n\tProfileWatcher struct {\n\t\tprofileLister splisters.ServiceProfileLister\n\t\tprofiles      map[ProfileID]*profilePublisher // <-- intentional formatting error to test CI\n\n\t\tlog          *logging.Entry\n\t\tsync.RWMutex // This mutex protects modification of the map itself.\n\t}\n\n\tprofilePublisher struct {\n\t\tprofile   *sp.ServiceProfile\n\t\tlisteners []ProfileUpdateListener\n\n\t\tlog            *logging.Entry\n\t\tprofileMetrics metrics\n\t\t// All access to the profilePublisher is explicitly synchronized by this mutex.\n\t\tsync.Mutex\n\t}\n\n\t// ProfileUpdateListener is the interface that subscribers must implement.\n\tProfileUpdateListener interface {\n\t\tUpdate(profile *sp.ServiceProfile)\n\t}\n)\n\nvar profileVecs = newMetricsVecs(\"profile\", []string{\"namespace\", \"profile\"})\n\n// NewProfileWatcher creates a ProfileWatcher and begins watching the k8sAPI for\n// service profile changes.\nfunc NewProfileWatcher(k8sAPI *k8s.API, log *logging.Entry) (*ProfileWatcher, error) {\n\twatcher := &ProfileWatcher{\n\t\tprofileLister: k8sAPI.SP().Lister(),\n\t\tprofiles:      make(map[ProfileID]*profilePublisher),\n\t\tlog:           log.WithField(\"component\", \"profile-watcher\"),\n\t}\n\n\t_, err := k8sAPI.SP().Informer().AddEventHandler(\n\t\tcache.ResourceEventHandlerFuncs{\n\t\t\tAddFunc:    watcher.addProfile,\n\t\t\tUpdateFunc: watcher.updateProfile,\n\t\t\tDeleteFunc: watcher.deleteProfile,\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn watcher, nil\n}\n\n//////////////////////\n/// ProfileWatcher ///\n//////////////////////\n\n// Subscribe to an authority.\n// The provided listener will be updated each time the service profile for the\n// given authority is changed.\nfunc (pw *ProfileWatcher) Subscribe(id ProfileID, listener ProfileUpdateListener) error {\n\tpw.log.Debugf(\"Establishing watch on profile %s\", id)\n\n\tpublisher, err := pw.getOrNewProfilePublisher(id, nil)\n\tif err != nil {\n\t\tpw.log.Errorf(\"error getting or creating profile publisher %s: %s\", id, err)\n\t\treturn err\n\t}\n\n\tpublisher.subscribe(listener)\n\treturn nil\n}\n\n// Unsubscribe removes a listener from the subscribers list for this authority.\nfunc (pw *ProfileWatcher) Unsubscribe(id ProfileID, listener ProfileUpdateListener) {\n\tpw.log.Debugf(\"Stopping watch on profile %s\", id)\n\n\tpublisher, ok := pw.getProfilePublisher(id)\n\tif !ok {\n\t\tpw.log.Errorf(\"cannot unsubscribe from unknown service [%s]\", id)\n\t}\n\tpublisher.unsubscribe(listener)\n}\n\nfunc (pw *ProfileWatcher) addProfile(obj interface{}) {\n\tprofile := obj.(*sp.ServiceProfile)\n\tid := ProfileID{\n\t\tNamespace: profile.Namespace,\n\t\tName:      profile.Name,\n\t}\n\n\tpublisher, err := pw.getOrNewProfilePublisher(id, profile)\n\tif err != nil {\n\t\tpw.log.Errorf(\"error getting or creating profile publisher: %s\", err)\n\t\treturn\n\t}\n\n\tpublisher.update(profile)\n}\n\nfunc (pw *ProfileWatcher) updateProfile(old interface{}, new interface{}) {\n\toldProfile := old.(*sp.ServiceProfile)\n\tnewProfile := new.(*sp.ServiceProfile)\n\n\toldUpdated := latestUpdated(oldProfile.ManagedFields)\n\tupdated := latestUpdated(newProfile.ManagedFields)\n\tif !updated.IsZero() && updated != oldUpdated {\n\t\tdelta := time.Since(updated)\n\t\tserviceProfileInformerLag.Observe(delta.Seconds())\n\t}\n\n\tpw.addProfile(new)\n}\n\nfunc (pw *ProfileWatcher) deleteProfile(obj interface{}) {\n\tprofile, ok := obj.(*sp.ServiceProfile)\n\tif !ok {\n\t\ttombstone, ok := obj.(cache.DeletedFinalStateUnknown)\n\t\tif !ok {\n\t\t\tpw.log.Errorf(\"couldn't get object from DeletedFinalStateUnknown %#v\", obj)\n\t\t\treturn\n\t\t}\n\t\tprofile, ok = tombstone.Obj.(*sp.ServiceProfile)\n\t\tif !ok {\n\t\t\tpw.log.Errorf(\"DeletedFinalStateUnknown contained object that is not a ServiceProfile %#v\", obj)\n\t\t\treturn\n\t\t}\n\t}\n\n\tid := ProfileID{\n\t\tNamespace: profile.Namespace,\n\t\tName:      profile.Name,\n\t}\n\n\tpublisher, ok := pw.getProfilePublisher(id)\n\tif ok {\n\t\tpublisher.update(nil)\n\t}\n}\n\nfunc (pw *ProfileWatcher) getOrNewProfilePublisher(id ProfileID, profile *sp.ServiceProfile) (*profilePublisher, error) {\n\tpw.Lock()\n\tdefer pw.Unlock()\n\n\tpublisher, ok := pw.profiles[id]\n\tif !ok {\n\t\tif profile == nil {\n\t\t\tvar err error\n\t\t\tprofile, err = pw.profileLister.ServiceProfiles(id.Namespace).Get(id.Name)\n\t\t\tif err != nil && !apierrors.IsNotFound(err) {\n\t\t\t\tpw.log.Errorf(\"error getting service profile: %s\", err)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tprofile = nil\n\t\t\t}\n\t\t}\n\n\t\tprofileMetrics, err := profileVecs.newMetrics(prometheus.Labels{\n\t\t\t\"namespace\": id.Namespace,\n\t\t\t\"profile\":   id.Name,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpublisher = &profilePublisher{\n\t\t\tprofile:   profile,\n\t\t\tlisteners: make([]ProfileUpdateListener, 0),\n\t\t\tlog: pw.log.WithFields(logging.Fields{\n\t\t\t\t\"component\": \"profile-publisher\",\n\t\t\t\t\"ns\":        id.Namespace,\n\t\t\t\t\"profile\":   id.Name,\n\t\t\t}),\n\t\t\tprofileMetrics: profileMetrics,\n\t\t}\n\t\tpw.profiles[id] = publisher\n\t}\n\n\treturn publisher, nil\n}\n\nfunc (pw *ProfileWatcher) getProfilePublisher(id ProfileID) (publisher *profilePublisher, ok bool) {\n\tpw.RLock()\n\tdefer pw.RUnlock()\n\tpublisher, ok = pw.profiles[id]\n\treturn\n}\n\n////////////////////////\n/// profilePublisher ///\n////////////////////////\n\nfunc (pp *profilePublisher) subscribe(listener ProfileUpdateListener) {\n\tpp.Lock()\n\tdefer pp.Unlock()\n\n\tpp.listeners = append(pp.listeners, listener)\n\tlistener.Update(pp.profile)\n\n\tpp.profileMetrics.setSubscribers(len(pp.listeners))\n}\n\n// unsubscribe returns true if and only if the listener was found and removed.\n// it also returns the number of listeners remaining after unsubscribing.\nfunc (pp *profilePublisher) unsubscribe(listener ProfileUpdateListener) {\n\tpp.Lock()\n\tdefer pp.Unlock()\n\n\tfor i, item := range pp.listeners {\n\t\tif item == listener {\n\t\t\t// delete the item from the slice\n\t\t\tn := len(pp.listeners)\n\t\t\tpp.listeners[i] = pp.listeners[n-1]\n\t\t\tpp.listeners[n-1] = nil\n\t\t\tpp.listeners = pp.listeners[:n-1]\n\t\t\tbreak\n\t\t}\n\t}\n\n\tpp.profileMetrics.setSubscribers(len(pp.listeners))\n}\n\nfunc (pp *profilePublisher) update(profile *sp.ServiceProfile) {\n\tpp.Lock()\n\tdefer pp.Unlock()\n\tpp.log.Debug(\"Updating profile\")\n\n\tpp.profile = profile\n\tfor _, listener := range pp.listeners {\n\t\tlistener.Update(profile)\n\t}\n\n\tpp.profileMetrics.incUpdates()\n}\n"
  },
  {
    "path": "controller/api/destination/watcher/profile_watcher_test.go",
    "content": "package watcher\n\nimport (\n\t\"testing\"\n\n\t\"k8s.io/client-go/tools/cache\"\n\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nvar testServiceProfile = sp.ServiceProfile{\n\tObjectMeta: metav1.ObjectMeta{\n\t\tName:      \"foobar.ns.svc.cluster.local\",\n\t\tNamespace: \"linkerd\",\n\t},\n\tSpec: sp.ServiceProfileSpec{\n\t\tRoutes: []*sp.RouteSpec{\n\t\t\t{\n\t\t\t\tCondition: &sp.RequestMatch{\n\t\t\t\t\tPathRegex: \"/x/y/z\",\n\t\t\t\t},\n\t\t\t\tResponseClasses: []*sp.ResponseClass{\n\t\t\t\t\t{\n\t\t\t\t\t\tCondition: &sp.ResponseMatch{\n\t\t\t\t\t\t\tStatus: &sp.Range{\n\t\t\t\t\t\t\t\tMin: 500,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tIsFailure: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n}\n\nvar testServiceProfileResource = `\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: foobar.ns.svc.cluster.local\n  namespace: linkerd\nspec:\n  routes:\n  - condition:\n      pathRegex: \"/x/y/z\"\n    responseClasses:\n    - condition:\n        status:\n          min: 500\n      isFailure: true`\n\nfunc TestProfileWatcherUpdates(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tname             string\n\t\tk8sConfigs       []string\n\t\tid               ProfileID\n\t\texpectedProfiles []*sp.ServiceProfileSpec\n\t}{\n\t\t{\n\t\t\tname:       \"service profile\",\n\t\t\tk8sConfigs: []string{testServiceProfileResource},\n\t\t\tid:         ProfileID{Name: testServiceProfile.Name, Namespace: testServiceProfile.Namespace},\n\t\t\texpectedProfiles: []*sp.ServiceProfileSpec{\n\t\t\t\t&testServiceProfile.Spec,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:       \"service without profile\",\n\t\t\tk8sConfigs: []string{},\n\t\t\tid:         ProfileID{Name: \"foobar.ns.svc.cluster.local\", Namespace: \"ns\"},\n\t\t\texpectedProfiles: []*sp.ServiceProfileSpec{\n\t\t\t\tnil,\n\t\t\t},\n\t\t},\n\t} {\n\t\ttt := tt // pin\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\twatcher, err := NewProfileWatcher(k8sAPI, logging.WithField(\"test\", t.Name()))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"can't create profile watcher: %s\", err)\n\t\t\t}\n\n\t\t\tk8sAPI.Sync(nil)\n\n\t\t\tlistener := NewBufferingProfileListener()\n\n\t\t\twatcher.Subscribe(tt.id, listener)\n\n\t\t\tactualProfiles := make([]*sp.ServiceProfileSpec, 0)\n\n\t\t\tlistener.mu.RLock()\n\t\t\tdefer listener.mu.RUnlock()\n\t\t\tfor _, profile := range listener.Profiles {\n\t\t\t\tif profile == nil {\n\t\t\t\t\tactualProfiles = append(actualProfiles, nil)\n\t\t\t\t} else {\n\t\t\t\t\tactualProfiles = append(actualProfiles, &profile.Spec)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttestCompare(t, tt.expectedProfiles, actualProfiles)\n\t\t})\n\t}\n}\n\nfunc TestProfileWatcherDeletes(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tname           string\n\t\tk8sConfigs     []string\n\t\tid             ProfileID\n\t\tobjectToDelete interface{}\n\t}{\n\t\t{\n\t\t\tname:           \"can delete service profiles\",\n\t\t\tk8sConfigs:     []string{testServiceProfileResource},\n\t\t\tid:             ProfileID{Name: testServiceProfile.Name, Namespace: testServiceProfile.Namespace},\n\t\t\tobjectToDelete: &testServiceProfile,\n\t\t},\n\t\t{\n\t\t\tname:           \"can delete service profiles wrapped in a DeletedFinalStateUnknown\",\n\t\t\tk8sConfigs:     []string{testServiceProfileResource},\n\t\t\tid:             ProfileID{Name: testServiceProfile.Name, Namespace: testServiceProfile.Namespace},\n\t\t\tobjectToDelete: cache.DeletedFinalStateUnknown{Obj: &testServiceProfile},\n\t\t},\n\t} {\n\t\ttt := tt // pin\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\twatcher, err := NewProfileWatcher(k8sAPI, logging.WithField(\"test\", t.Name()))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"can't create profile watcher: %s\", err)\n\t\t\t}\n\t\t\tk8sAPI.Sync(nil)\n\n\t\t\tlistener := NewDeletingProfileListener()\n\n\t\t\twatcher.Subscribe(tt.id, listener)\n\n\t\t\twatcher.deleteProfile(tt.objectToDelete)\n\n\t\t\tif listener.NumDeletes != 1 {\n\t\t\t\tt.Fatalf(\"Expected to get 1 deletes but got %v\", listener.NumDeletes)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "controller/api/destination/watcher/prometheus.go",
    "content": "package watcher\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\ntype (\n\tmetricsVecs struct {\n\t\tlabelNames  []string\n\t\tsubscribers *prometheus.GaugeVec\n\t\tupdates     *prometheus.CounterVec\n\t}\n\n\tmetrics struct {\n\t\tlabels      prometheus.Labels\n\t\tsubscribers prometheus.Gauge\n\t\tupdates     prometheus.Counter\n\t}\n\n\tendpointsMetricsVecs struct {\n\t\tmetricsVecs\n\t\tpods   *prometheus.GaugeVec\n\t\texists *prometheus.GaugeVec\n\t}\n\n\tendpointsMetrics struct {\n\t\tmetrics\n\t\tpods   prometheus.Gauge\n\t\texists prometheus.Gauge\n\t}\n)\n\nvar (\n\tinformer_lag_seconds_buckets = []float64{\n\t\t0.5,  // 500ms\n\t\t1,    // 1s\n\t\t2.5,  // 2.5s\n\t\t5,    // 5s\n\t\t10,   // 10s\n\t\t25,   // 25s\n\t\t50,   // 50s\n\t\t100,  // 1m 40s\n\t\t250,  // 4m 10s\n\t\t1000, // 16m 40s\n\t}\n\tendpointsInformerLag = promauto.NewHistogram(\n\t\tprometheus.HistogramOpts{\n\t\t\tName:    \"endpoints_informer_lag_seconds\",\n\t\t\tHelp:    \"The amount of time between when an Endpoints resource is updated and when an informer observes it\",\n\t\t\tBuckets: informer_lag_seconds_buckets,\n\t\t},\n\t)\n\n\tendpointsliceInformerLag = promauto.NewHistogram(\n\t\tprometheus.HistogramOpts{\n\t\t\tName:    \"endpointslices_informer_lag_seconds\",\n\t\t\tHelp:    \"The amount of time between when an EndpointSlice resource is updated and when an informer observes it\",\n\t\t\tBuckets: informer_lag_seconds_buckets,\n\t\t},\n\t)\n\n\tserviceInformerLag = promauto.NewHistogram(\n\t\tprometheus.HistogramOpts{\n\t\t\tName:    \"services_informer_lag_seconds\",\n\t\t\tHelp:    \"The amount of time between when a Service resource is updated and when an informer observes it\",\n\t\t\tBuckets: informer_lag_seconds_buckets,\n\t\t},\n\t)\n\n\tserverInformerLag = promauto.NewHistogram(\n\t\tprometheus.HistogramOpts{\n\t\t\tName:    \"servers_informer_lag_seconds\",\n\t\t\tHelp:    \"The amount of time between when a Server resource is updated and when an informer observes it\",\n\t\t\tBuckets: informer_lag_seconds_buckets,\n\t\t},\n\t)\n\n\tpodInformerLag = promauto.NewHistogram(\n\t\tprometheus.HistogramOpts{\n\t\t\tName:    \"pods_informer_lag_seconds\",\n\t\t\tHelp:    \"The amount of time between when a Pod resource is updated and when an informer observes it\",\n\t\t\tBuckets: informer_lag_seconds_buckets,\n\t\t},\n\t)\n\n\texternalWorkloadInformerLag = promauto.NewHistogram(\n\t\tprometheus.HistogramOpts{\n\t\t\tName:    \"externalworkload_informer_lag_seconds\",\n\t\t\tHelp:    \"The amount of time between when an ExternalWorkload resource is updated and when an informer observes it\",\n\t\t\tBuckets: informer_lag_seconds_buckets,\n\t\t},\n\t)\n\n\tserviceProfileInformerLag = promauto.NewHistogram(\n\t\tprometheus.HistogramOpts{\n\t\t\tName:    \"serviceprofiles_informer_lag_seconds\",\n\t\t\tHelp:    \"The amount of time between when a ServiceProfile resource is updated and when an informer observes it\",\n\t\t\tBuckets: informer_lag_seconds_buckets,\n\t\t},\n\t)\n)\n\nfunc newMetricsVecs(name string, labels []string) metricsVecs {\n\tsubscribers := promauto.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tName: fmt.Sprintf(\"%s_subscribers\", name),\n\t\t\tHelp: fmt.Sprintf(\"A gauge for the current number of subscribers to a %s.\", name),\n\t\t},\n\t\tlabels,\n\t)\n\n\tupdates := promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tName: fmt.Sprintf(\"%s_updates\", name),\n\t\t\tHelp: fmt.Sprintf(\"A counter for number of updates to a %s.\", name),\n\t\t},\n\t\tlabels,\n\t)\n\n\treturn metricsVecs{\n\t\tlabelNames:  labels,\n\t\tsubscribers: subscribers,\n\t\tupdates:     updates,\n\t}\n}\n\nfunc endpointsLabels(cluster, namespace, service, port string, hostname string) prometheus.Labels {\n\treturn prometheus.Labels{\n\t\t\"cluster\":   cluster,\n\t\t\"namespace\": namespace,\n\t\t\"service\":   service,\n\t\t\"port\":      port,\n\t\t\"hostname\":  hostname,\n\t}\n}\n\nfunc labelNames(labels prometheus.Labels) []string {\n\tnames := []string{}\n\tfor label := range labels {\n\t\tnames = append(names, label)\n\t}\n\treturn names\n}\n\nfunc newEndpointsMetricsVecs() endpointsMetricsVecs {\n\tlabels := labelNames(endpointsLabels(\"\", \"\", \"\", \"\", \"\"))\n\tvecs := newMetricsVecs(\"endpoints\", labels)\n\n\tpods := promauto.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tName: \"endpoints_pods\",\n\t\t\tHelp: \"A gauge for the current number of pods in a endpoints.\",\n\t\t},\n\t\tlabels,\n\t)\n\n\texists := promauto.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tName: \"endpoints_exists\",\n\t\t\tHelp: \"A gauge which is 1 if the endpoints exists and 0 if it does not.\",\n\t\t},\n\t\tlabels,\n\t)\n\n\treturn endpointsMetricsVecs{\n\t\tmetricsVecs: vecs,\n\t\tpods:        pods,\n\t\texists:      exists,\n\t}\n}\n\nfunc (mv metricsVecs) newMetrics(labels prometheus.Labels) (metrics, error) {\n\tsubscribers, err := mv.subscribers.GetMetricWith(labels)\n\tif err != nil {\n\t\treturn metrics{}, fmt.Errorf(\"failed to get subscribers metric: %w\", err)\n\t}\n\n\tupdates, err := mv.updates.GetMetricWith(labels)\n\tif err != nil {\n\t\treturn metrics{}, fmt.Errorf(\"failed to get updates metric: %w\", err)\n\t}\n\n\treturn metrics{\n\t\tlabels,\n\t\tsubscribers,\n\t\tupdates,\n\t}, nil\n}\n\nfunc (emv endpointsMetricsVecs) newEndpointsMetrics(labels prometheus.Labels) (endpointsMetrics, error) {\n\tmetrics, err := emv.newMetrics(labels)\n\tif err != nil {\n\t\treturn endpointsMetrics{}, err\n\t}\n\n\tpods, err := emv.pods.GetMetricWith(labels)\n\tif err != nil {\n\t\treturn endpointsMetrics{}, fmt.Errorf(\"failed to get pods metric: %w\", err)\n\t}\n\n\texists, err := emv.exists.GetMetricWith(labels)\n\tif err != nil {\n\t\treturn endpointsMetrics{}, fmt.Errorf(\"failed to get exists metric: %w\", err)\n\t}\n\n\treturn endpointsMetrics{\n\t\tmetrics,\n\t\tpods,\n\t\texists,\n\t}, nil\n}\n\nfunc (emv endpointsMetricsVecs) unregister(labels prometheus.Labels) {\n\tif !emv.metricsVecs.subscribers.Delete(labels) {\n\t\tlog.Warnf(\"unable to delete endpoints_subscribers metric with labels %s\", labels)\n\t}\n\tif !emv.metricsVecs.updates.Delete(labels) {\n\t\tlog.Warnf(\"unable to delete endpoints_updates metric with labels %s\", labels)\n\t}\n\tif !emv.pods.Delete(labels) {\n\t\tlog.Warnf(\"unable to delete endpoints_pods metric with labels %s\", labels)\n\t}\n\tif !emv.exists.Delete(labels) {\n\t\tlog.Warnf(\"unable to delete endpoints_exists metric with labels %s\", labels)\n\t}\n}\n\nfunc (m metrics) setSubscribers(n int) {\n\tm.subscribers.Set(float64(n))\n}\n\nfunc (m metrics) incUpdates() {\n\tm.updates.Inc()\n}\n\nfunc (em endpointsMetrics) setPods(n int) {\n\tem.pods.Set(float64(n))\n}\n\nfunc (em endpointsMetrics) setExists(exists bool) {\n\tif exists {\n\t\tem.exists.Set(1.0)\n\t} else {\n\t\tem.exists.Set(0.0)\n\t}\n}\n"
  },
  {
    "path": "controller/api/destination/watcher/test_util.go",
    "content": "package watcher\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n)\n\n// DeletingProfileListener implements ProfileUpdateListener and registers\n// deletions. Useful for unit testing\ntype DeletingProfileListener struct {\n\tNumDeletes int\n}\n\n// NewDeletingProfileListener creates a new NewDeletingProfileListener.\nfunc NewDeletingProfileListener() *DeletingProfileListener {\n\treturn &DeletingProfileListener{\n\t\tNumDeletes: 0,\n\t}\n}\n\n// Update registers a deletion\nfunc (dpl *DeletingProfileListener) Update(profile *sp.ServiceProfile) {\n\tif profile == nil {\n\t\tdpl.NumDeletes++\n\t}\n}\n\n// BufferingProfileListener implements ProfileUpdateListener and stores updates\n// in a slice.  Useful for unit tests.\ntype BufferingProfileListener struct {\n\tProfiles []*sp.ServiceProfile\n\tmu       sync.RWMutex\n}\n\n// NewBufferingProfileListener creates a new BufferingProfileListener.\nfunc NewBufferingProfileListener() *BufferingProfileListener {\n\treturn &BufferingProfileListener{\n\t\tProfiles: []*sp.ServiceProfile{},\n\t}\n}\n\nfunc CreateMockDecoder(configs ...string) configDecoder {\n\t// Create a mock decoder with some random objs to satisfy client creation\n\treturn func(data []byte, cluster string, enableEndpointSlices bool) (*k8s.API, *k8s.MetadataAPI, error) {\n\t\tremoteAPI, err := k8s.NewFakeAPI(configs...)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\treturn remoteAPI, metadataAPI, nil\n\t}\n\n}\n\nfunc CreateMulticlusterDecoder(configs map[string][]string) configDecoder {\n\treturn func(data []byte, cluster string, enableEndpointSlices bool) (*k8s.API, *k8s.MetadataAPI, error) {\n\t\tconfigs, ok := configs[cluster]\n\t\tif !ok {\n\t\t\treturn nil, nil, fmt.Errorf(\"cluster %s not found in configs\", cluster)\n\t\t}\n\t\tremoteAPI, err := k8s.NewFakeAPI(configs...)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\tmetadataAPI, err := k8s.NewFakeMetadataAPI(nil)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\treturn remoteAPI, metadataAPI, nil\n\t}\n}\n\n// Update stores the update in the internal buffer.\nfunc (bpl *BufferingProfileListener) Update(profile *sp.ServiceProfile) {\n\tbpl.mu.Lock()\n\tdefer bpl.mu.Unlock()\n\tbpl.Profiles = append(bpl.Profiles, profile)\n}\n\nfunc testCompare(t *testing.T, expected interface{}, actual interface{}) {\n\tt.Helper()\n\tif diff := deep.Equal(expected, actual); diff != nil {\n\t\tt.Fatalf(\"%v\", diff)\n\t}\n}\n"
  },
  {
    "path": "controller/api/destination/watcher/workload_watcher.go",
    "content": "package watcher\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\text \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta3\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tconsts \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/util\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\tlogging \"github.com/sirupsen/logrus\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tdiscovery \"k8s.io/api/discovery/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\ntype (\n\t// WorkloadWatcher watches all pods and externalworkloads in the cluster.\n\t// It keeps a map of publishers keyed by IP and port.\n\tWorkloadWatcher struct {\n\t\tdefaultOpaquePorts   map[uint32]struct{}\n\t\tk8sAPI               *k8s.API\n\t\tmetadataAPI          *k8s.MetadataAPI\n\t\tpublishers           map[IPPort]*workloadPublisher\n\t\tmetrics              metrics\n\t\tsubscriberCount      atomic.Int32\n\t\tlog                  *logging.Entry\n\t\tenableEndpointSlices bool\n\n\t\tmu sync.RWMutex\n\t}\n\n\t// workloadPublisher represents an address including ip:port, the backing\n\t// pod or externalworkload (if any), and if the protocol is opaque. It keeps\n\t// a list of listeners to be notified whenever the workload or the\n\t// associated opaque protocol config changes.\n\tworkloadPublisher struct {\n\t\tdefaultOpaquePorts map[uint32]struct{}\n\t\tk8sAPI             *k8s.API\n\t\tmetadataAPI        *k8s.MetadataAPI\n\t\taddr               Address\n\t\tlisteners          []WorkloadUpdateListener\n\t\tmetrics            metrics\n\t\tsubscriberCount    *atomic.Int32\n\t\tlog                *logging.Entry\n\n\t\tmu sync.RWMutex\n\t}\n\n\t// PodUpdateListener is the interface subscribers must implement.\n\tWorkloadUpdateListener interface {\n\t\tUpdate(*Address) error\n\t}\n)\n\nvar workloadVecs = newMetricsVecs(\"workload\", []string{})\n\nfunc NewWorkloadWatcher(k8sAPI *k8s.API, metadataAPI *k8s.MetadataAPI, log *logging.Entry, enableEndpointSlices bool, defaultOpaquePorts map[uint32]struct{}) (*WorkloadWatcher, error) {\n\t// Omit high-cardinality IP:port labels.\n\tmetrics, err := workloadVecs.newMetrics(prometheus.Labels{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tww := &WorkloadWatcher{\n\t\tdefaultOpaquePorts: defaultOpaquePorts,\n\t\tk8sAPI:             k8sAPI,\n\t\tmetadataAPI:        metadataAPI,\n\t\tpublishers:         make(map[IPPort]*workloadPublisher),\n\t\tmetrics:            metrics,\n\t\tsubscriberCount:    atomic.Int32{},\n\t\tlog: log.WithFields(logging.Fields{\n\t\t\t\"component\": \"workload-watcher\",\n\t\t}),\n\t\tenableEndpointSlices: enableEndpointSlices,\n\t}\n\n\t_, err = k8sAPI.Pod().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc:    ww.addPod,\n\t\tDeleteFunc: ww.deletePod,\n\t\tUpdateFunc: ww.updatePod,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, err = k8sAPI.ExtWorkload().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc:    ww.addExternalWorkload,\n\t\tDeleteFunc: ww.deleteExternalWorkload,\n\t\tUpdateFunc: ww.updateExternalWorkload,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, err = k8sAPI.Srv().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc:    ww.addOrDeleteServer,\n\t\tDeleteFunc: ww.addOrDeleteServer,\n\t\tUpdateFunc: ww.updateServer,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ww, nil\n}\n\n// Subscribe notifies the listener on changes on any workload backing the passed\n// host/ip:port or changes to its associated opaque protocol config. If service\n// and hostname are empty then ip should be set and vice-versa. If ip is empty,\n// the corresponding ip is found for the given service/hostname, and returned.\nfunc (ww *WorkloadWatcher) Subscribe(service *ServiceID, hostname, ip string, port Port, listener WorkloadUpdateListener) (string, error) {\n\tif hostname != \"\" {\n\t\tww.log.Debugf(\"Establishing watch on workload %s.%s.%s:%d\", hostname, service.Name, service.Namespace, port)\n\t} else if service != nil {\n\t\tww.log.Debugf(\"Establishing watch on workload %s.%s:%d\", service.Name, service.Namespace, port)\n\t} else {\n\t\tww.log.Debugf(\"Establishing watch on workload %s:%d\", ip, port)\n\t}\n\twp, err := ww.getOrNewWorkloadPublisher(service, hostname, ip, port)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif err = wp.subscribe(listener); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tww.updateSubscriberCount()\n\n\treturn wp.addr.IP, nil\n}\n\n// Subscribe stops notifying the listener on chages on any pod backing the\n// passed ip:port or its associated protocol config\nfunc (ww *WorkloadWatcher) Unsubscribe(ip string, port Port, listener WorkloadUpdateListener) {\n\tww.mu.Lock()\n\tdefer ww.mu.Unlock()\n\n\tww.log.Debugf(\"Stopping watch on %s:%d\", ip, port)\n\twp, ok := ww.getWorkloadPublisher(ip, port)\n\tif !ok {\n\t\tww.log.Errorf(\"Cannot unsubscribe from unknown ip:port [%s:%d]\", ip, port)\n\t\treturn\n\t}\n\twp.unsubscribe(listener)\n\n\tif len(wp.listeners) == 0 {\n\t\tdelete(ww.publishers, IPPort{wp.addr.IP, wp.addr.Port})\n\t}\n\n\tww.updateSubscriberCount()\n}\n\nfunc (ww *WorkloadWatcher) updateSubscriberCount() {\n\tww.metrics.setSubscribers(int(ww.subscriberCount.Load()))\n}\n\n// addPod is an event handler so it cannot block\nfunc (ww *WorkloadWatcher) addPod(obj any) {\n\tpod := obj.(*corev1.Pod)\n\tww.log.Tracef(\"Added pod %s.%s\", pod.Name, pod.Namespace)\n\tgo ww.submitPodUpdate(pod, false)\n}\n\n// deletePod is an event handler so it cannot block\nfunc (ww *WorkloadWatcher) deletePod(obj any) {\n\tpod, ok := obj.(*corev1.Pod)\n\tif !ok {\n\t\ttombstone, ok := obj.(cache.DeletedFinalStateUnknown)\n\t\tif !ok {\n\t\t\tww.log.Errorf(\"Couldn't get object from DeletedFinalStateUnknown %#v\", obj)\n\t\t\treturn\n\t\t}\n\t\tpod, ok = tombstone.Obj.(*corev1.Pod)\n\t\tif !ok {\n\t\t\tww.log.Errorf(\"DeletedFinalStateUnknown contained object that is not a Pod %#v\", obj)\n\t\t\treturn\n\t\t}\n\t}\n\tww.log.Tracef(\"Deleted pod %s.%s\", pod.Name, pod.Namespace)\n\tgo ww.submitPodUpdate(pod, true)\n}\n\n// updatePod is an event handler so it cannot block\nfunc (ww *WorkloadWatcher) updatePod(oldObj any, newObj any) {\n\toldPod := oldObj.(*corev1.Pod)\n\tnewPod := newObj.(*corev1.Pod)\n\tif oldPod.DeletionTimestamp == nil && newPod.DeletionTimestamp != nil {\n\t\t// this is just a mark, wait for actual deletion event\n\t\treturn\n\t}\n\n\toldUpdated := latestUpdated(oldPod.ManagedFields)\n\tupdated := latestUpdated(newPod.ManagedFields)\n\tif !updated.IsZero() && updated != oldUpdated {\n\t\tdelta := time.Since(updated)\n\t\tpodInformerLag.Observe(delta.Seconds())\n\t}\n\n\tww.log.Tracef(\"Updated pod %s.%s\", newPod.Name, newPod.Namespace)\n\tgo ww.submitPodUpdate(newPod, false)\n}\n\n// addExternalWorkload is an event handler so it cannot block\nfunc (ww *WorkloadWatcher) addExternalWorkload(obj any) {\n\texternalWorkload := obj.(*ext.ExternalWorkload)\n\tww.log.Tracef(\"Added externalworkload %s.%s\", externalWorkload.Name, externalWorkload.Namespace)\n\tgo ww.submitExternalWorkloadUpdate(externalWorkload, false)\n}\n\n// deleteExternalWorkload is an event handler so it cannot block\nfunc (ww *WorkloadWatcher) deleteExternalWorkload(obj any) {\n\texternalWorkload, ok := obj.(*ext.ExternalWorkload)\n\tif !ok {\n\t\ttombstone, ok := obj.(cache.DeletedFinalStateUnknown)\n\t\tif !ok {\n\t\t\tww.log.Errorf(\"Couldn't get object from DeletedFinalStateUnknown %#v\", obj)\n\t\t\treturn\n\t\t}\n\t\texternalWorkload, ok = tombstone.Obj.(*ext.ExternalWorkload)\n\t\tif !ok {\n\t\t\tww.log.Errorf(\"DeletedFinalStateUnknown contained object that is not an ExternalWorkload %#v\", obj)\n\t\t\treturn\n\t\t}\n\t}\n\tww.log.Tracef(\"Deleted externalworklod %s.%s\", externalWorkload.Name, externalWorkload.Namespace)\n\tgo ww.submitExternalWorkloadUpdate(externalWorkload, true)\n}\n\n// updateExternalWorkload is an event handler so it cannot block\nfunc (ww *WorkloadWatcher) updateExternalWorkload(oldObj any, newObj any) {\n\toldExternalWorkload := oldObj.(*ext.ExternalWorkload)\n\tnewExternalWorkload := newObj.(*ext.ExternalWorkload)\n\tif oldExternalWorkload.DeletionTimestamp == nil && newExternalWorkload.DeletionTimestamp != nil {\n\t\t// this is just a mark, wait for actual deletion event\n\t\treturn\n\t}\n\n\toldUpdated := latestUpdated(oldExternalWorkload.ManagedFields)\n\tupdated := latestUpdated(newExternalWorkload.ManagedFields)\n\tif !updated.IsZero() && updated != oldUpdated {\n\t\tdelta := time.Since(updated)\n\t\texternalWorkloadInformerLag.Observe(delta.Seconds())\n\t}\n\n\tww.log.Tracef(\"Updated pod %s.%s\", newExternalWorkload.Name, newExternalWorkload.Namespace)\n\tgo ww.submitExternalWorkloadUpdate(newExternalWorkload, false)\n}\n\nfunc (ww *WorkloadWatcher) submitPodUpdate(pod *corev1.Pod, remove bool) {\n\tww.mu.RLock()\n\tdefer ww.mu.RUnlock()\n\n\tsubmitPod := pod\n\tif remove {\n\t\tsubmitPod = nil\n\t}\n\n\tfor _, container := range append(pod.Spec.InitContainers, pod.Spec.Containers...) {\n\t\tfor _, containerPort := range container.Ports {\n\t\t\tif containerPort.ContainerPort != 0 {\n\t\t\t\tfor _, pip := range pod.Status.PodIPs {\n\t\t\t\t\tif wp, ok := ww.getWorkloadPublisher(pip.IP, Port(containerPort.ContainerPort)); ok {\n\t\t\t\t\t\twp.updatePod(submitPod)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(pod.Status.PodIPs) == 0 && pod.Status.PodIP != \"\" {\n\t\t\t\t\tif wp, ok := ww.getWorkloadPublisher(pod.Status.PodIP, Port(containerPort.ContainerPort)); ok {\n\t\t\t\t\t\twp.updatePod(submitPod)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif containerPort.HostPort != 0 {\n\t\t\t\tfor _, hip := range pod.Status.HostIPs {\n\t\t\t\t\tif pp, ok := ww.getWorkloadPublisher(hip.IP, Port(containerPort.HostPort)); ok {\n\t\t\t\t\t\tpp.updatePod(submitPod)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(pod.Status.HostIPs) == 0 && pod.Status.HostIP != \"\" {\n\t\t\t\t\tif pp, ok := ww.getWorkloadPublisher(pod.Status.HostIP, Port(containerPort.HostPort)); ok {\n\t\t\t\t\t\tpp.updatePod(submitPod)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (ww *WorkloadWatcher) submitExternalWorkloadUpdate(externalWorkload *ext.ExternalWorkload, remove bool) {\n\tww.mu.RLock()\n\tdefer ww.mu.RUnlock()\n\n\tsubmitWorkload := externalWorkload\n\tif remove {\n\t\tsubmitWorkload = nil\n\t}\n\n\tfor _, port := range externalWorkload.Spec.Ports {\n\t\tfor _, ip := range externalWorkload.Spec.WorkloadIPs {\n\t\t\tif wp, ok := ww.getWorkloadPublisher(ip.Ip, Port(port.Port)); ok {\n\t\t\t\twp.updateExternalWorkload(submitWorkload)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (ww *WorkloadWatcher) updateServer(oldObj interface{}, newObj interface{}) {\n\toldServer := oldObj.(*v1beta3.Server)\n\tnewServer := newObj.(*v1beta3.Server)\n\n\toldUpdated := latestUpdated(oldServer.ManagedFields)\n\tupdated := latestUpdated(newServer.ManagedFields)\n\n\tif !updated.IsZero() && updated != oldUpdated {\n\t\tdelta := time.Since(updated)\n\t\tserverInformerLag.Observe(delta.Seconds())\n\t}\n\n\tww.updateServers(oldServer, newServer)\n}\n\nfunc (ww *WorkloadWatcher) addOrDeleteServer(obj interface{}) {\n\tserver, ok := obj.(*v1beta3.Server)\n\tif !ok {\n\t\ttombstone, ok := obj.(cache.DeletedFinalStateUnknown)\n\t\tif !ok {\n\t\t\tww.log.Errorf(\"Couldn't get object from DeletedFinalStateUnknown %#v\", obj)\n\t\t\treturn\n\t\t}\n\t\tserver, ok = tombstone.Obj.(*v1beta3.Server)\n\t\tif !ok {\n\t\t\tww.log.Errorf(\"DeletedFinalStateUnknown contained object that is not a Server %#v\", obj)\n\t\t\treturn\n\t\t}\n\t}\n\tww.updateServers(server)\n}\n\n// updateServers triggers an Update() call to the listeners of the workloadPublishers\n// whose pod matches the any of the Servers' podSelector or whose\n// externalworkload matches any of the Servers' externalworkload selection. This\n// function is an event handler so it cannot block.\nfunc (ww *WorkloadWatcher) updateServers(servers ...*v1beta3.Server) {\n\tww.mu.RLock()\n\tdefer ww.mu.RUnlock()\n\n\tfor _, wp := range ww.publishers {\n\t\tvar opaquePorts map[uint32]struct{}\n\t\tif wp.addr.Pod != nil {\n\t\t\tif !ww.isPodSelectedByAny(wp.addr.Pod, servers...) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\topaquePorts = GetAnnotatedOpaquePorts(wp.addr.Pod, ww.defaultOpaquePorts)\n\t\t} else if wp.addr.ExternalWorkload != nil {\n\t\t\tif !ww.isExternalWorkloadSelectedByAny(wp.addr.ExternalWorkload, servers...) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\topaquePorts = GetAnnotatedOpaquePortsForExternalWorkload(wp.addr.ExternalWorkload, ww.defaultOpaquePorts)\n\t\t} else {\n\t\t\tcontinue\n\t\t}\n\n\t\t_, annotatedOpaque := opaquePorts[wp.addr.Port]\n\t\t// if port is annotated to be always opaque we can disregard Server updates\n\t\tif annotatedOpaque {\n\t\t\tcontinue\n\t\t}\n\n\t\topaque := wp.addr.OpaqueProtocol\n\t\tname := net.JoinHostPort(wp.addr.IP, fmt.Sprintf(\"%d\", wp.addr.Port))\n\t\tif wp.addr.Pod != nil {\n\t\t\tname = wp.addr.Pod.GetName()\n\t\t} else if wp.addr.ExternalWorkload != nil {\n\t\t\tname = wp.addr.ExternalWorkload.GetName()\n\t\t}\n\t\tif err := SetToServerProtocol(wp.k8sAPI, &wp.addr, wp.log); err != nil {\n\t\t\twp.log.Errorf(\"Error computing opaque protocol for %s: %q\", name, err)\n\t\t}\n\t\tif wp.addr.OpaqueProtocol == opaque {\n\t\t\t// OpaqueProtocol has not changed. No need to update the listeners.\n\t\t\tcontinue\n\t\t}\n\n\t\tgo func(wp *workloadPublisher) {\n\t\t\twp.mu.RLock()\n\t\t\tdefer wp.mu.RUnlock()\n\n\t\t\tfor _, listener := range wp.listeners {\n\t\t\t\tif err := listener.Update(&wp.addr); err != nil {\n\t\t\t\t\tww.log.Warnf(\"Error sending update to listener: %s\", err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\twp.metrics.incUpdates()\n\t\t}(wp)\n\t}\n}\n\nfunc (ww *WorkloadWatcher) isPodSelectedByAny(pod *corev1.Pod, servers ...*v1beta3.Server) bool {\n\tfor _, s := range servers {\n\t\tselector, err := metav1.LabelSelectorAsSelector(s.Spec.PodSelector)\n\t\tif err != nil {\n\t\t\tww.log.Errorf(\"failed to parse PodSelector of Server %s.%s: %q\", s.GetName(), s.GetNamespace(), err)\n\t\t\tcontinue\n\t\t}\n\t\tif selector.Matches(labels.Set(pod.Labels)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (ww *WorkloadWatcher) isExternalWorkloadSelectedByAny(ew *ext.ExternalWorkload, servers ...*v1beta3.Server) bool {\n\tfor _, s := range servers {\n\t\tselector, err := metav1.LabelSelectorAsSelector(s.Spec.ExternalWorkloadSelector)\n\t\tif err != nil {\n\t\t\tww.log.Errorf(\"failed to parse ExternalWorkloadSelector of Server %s.%s: %q\", s.GetName(), s.GetNamespace(), err)\n\t\t\tcontinue\n\t\t}\n\t\tif selector.Matches(labels.Set(ew.Labels)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// getOrNewWorkloadPublisher returns the workloadPublisher for the given target if it\n// exists. Otherwise, it creates a new one and returns it.\nfunc (ww *WorkloadWatcher) getOrNewWorkloadPublisher(service *ServiceID, hostname, ip string, port Port) (*workloadPublisher, error) {\n\tww.mu.Lock()\n\tdefer ww.mu.Unlock()\n\n\tvar pod *corev1.Pod\n\tvar externalWorkload *ext.ExternalWorkload\n\tvar err error\n\tif hostname != \"\" {\n\t\tpod, err = ww.getEndpointByHostname(hostname, service)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tip = pod.Status.PodIP\n\t} else {\n\t\tpod, err = ww.getPodByPodIP(ip, port)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif pod == nil {\n\t\t\tpod, err = ww.getPodByHostIP(ip, port)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tif pod == nil {\n\t\t\texternalWorkload, err = ww.getExternalWorkloadByIP(ip, port)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\tipPort := IPPort{ip, port}\n\twp, ok := ww.publishers[ipPort]\n\tif !ok {\n\t\twp = &workloadPublisher{\n\t\t\tdefaultOpaquePorts: ww.defaultOpaquePorts,\n\t\t\tk8sAPI:             ww.k8sAPI,\n\t\t\tmetadataAPI:        ww.metadataAPI,\n\t\t\taddr: Address{\n\t\t\t\tIP:   ip,\n\t\t\t\tPort: port,\n\t\t\t},\n\t\t\tmetrics:         ww.metrics,\n\t\t\tsubscriberCount: &ww.subscriberCount,\n\t\t\tlog: ww.log.WithFields(logging.Fields{\n\t\t\t\t\"component\": \"workload-publisher\",\n\t\t\t\t\"ip\":        ip,\n\t\t\t\t\"port\":      port,\n\t\t\t}),\n\t\t}\n\t\tif pod != nil {\n\t\t\twp.updatePod(pod)\n\t\t}\n\t\tif externalWorkload != nil {\n\t\t\twp.updateExternalWorkload(externalWorkload)\n\t\t}\n\t\tww.publishers[ipPort] = wp\n\t}\n\treturn wp, nil\n}\n\nfunc (ww *WorkloadWatcher) getWorkloadPublisher(ip string, port Port) (wp *workloadPublisher, ok bool) {\n\tipPort := IPPort{ip, port}\n\twp, ok = ww.publishers[ipPort]\n\treturn\n}\n\n// getPodByPodIP returns a pod that maps to the given IP address in the pod network\nfunc (ww *WorkloadWatcher) getPodByPodIP(podIP string, port uint32) (*corev1.Pod, error) {\n\tpodIPPods, err := getIndexedPods(ww.k8sAPI, PodIPIndex, podIP)\n\tif err != nil {\n\t\treturn nil, status.Error(codes.Unknown, err.Error())\n\t}\n\tif len(podIPPods) == 1 {\n\t\tww.log.Debugf(\"found %s on the pod network\", podIP)\n\t\treturn podIPPods[0], nil\n\t}\n\tif len(podIPPods) > 1 {\n\t\tconflictingPods := []string{}\n\t\tfor _, pod := range podIPPods {\n\t\t\tconflictingPods = append(conflictingPods, fmt.Sprintf(\"%s:%s\", pod.Namespace, pod.Name))\n\t\t}\n\t\tww.log.Warnf(\"found conflicting %s IP on the pod network: %s\", podIP, strings.Join(conflictingPods, \",\"))\n\t\treturn nil, status.Errorf(codes.FailedPrecondition, \"found %d pods with a conflicting pod network IP %s\", len(podIPPods), podIP)\n\t}\n\n\tww.log.Debugf(\"no pod found for %s:%d\", podIP, port)\n\treturn nil, nil\n}\n\n// getPodByHostIP returns a pod that maps to the given IP address in the host\n// network. It must have a container port that exposes `port` as a host port.\nfunc (ww *WorkloadWatcher) getPodByHostIP(hostIP string, port uint32) (*corev1.Pod, error) {\n\taddr := net.JoinHostPort(hostIP, fmt.Sprintf(\"%d\", port))\n\thostIPPods, err := getIndexedPods(ww.k8sAPI, HostIPIndex, addr)\n\tif err != nil {\n\t\treturn nil, status.Error(codes.Unknown, err.Error())\n\t}\n\tif len(hostIPPods) == 1 {\n\t\tww.log.Debugf(\"found %s:%d on the host network\", hostIP, port)\n\t\treturn hostIPPods[0], nil\n\t}\n\tif len(hostIPPods) > 1 {\n\t\tconflictingPods := []string{}\n\t\tfor _, pod := range hostIPPods {\n\t\t\tconflictingPods = append(conflictingPods, fmt.Sprintf(\"%s:%s\", pod.Namespace, pod.Name))\n\t\t}\n\t\tww.log.Warnf(\"found conflicting %s:%d endpoint on the host network: %s\", hostIP, port, strings.Join(conflictingPods, \",\"))\n\t\treturn nil, status.Errorf(codes.FailedPrecondition, \"found %d pods with a conflicting host network endpoint %s:%d\", len(hostIPPods), hostIP, port)\n\t}\n\n\treturn nil, nil\n}\n\n// getExternalWorkloadByIP returns an externalworkload with the given IP\n// address.\nfunc (ww *WorkloadWatcher) getExternalWorkloadByIP(ip string, port uint32) (*ext.ExternalWorkload, error) {\n\taddr := net.JoinHostPort(ip, fmt.Sprintf(\"%d\", port))\n\tworkloads, err := getIndexedExternalWorkloads(ww.k8sAPI, ExternalWorkloadIPIndex, addr)\n\tif err != nil {\n\t\treturn nil, status.Error(codes.Unknown, err.Error())\n\t}\n\tif len(workloads) == 0 {\n\t\tww.log.Debugf(\"no externalworkload found for %s:%d\", ip, port)\n\t\treturn nil, nil\n\t}\n\tif len(workloads) == 1 {\n\t\tww.log.Debugf(\"found externalworkload %s:%d\", ip, port)\n\t\treturn workloads[0], nil\n\t}\n\tif len(workloads) > 1 {\n\t\tconflictingWorkloads := []string{}\n\t\tfor _, ew := range workloads {\n\t\t\tconflictingWorkloads = append(conflictingWorkloads, fmt.Sprintf(\"%s:%s\", ew.Namespace, ew.Name))\n\t\t}\n\t\tww.log.Warnf(\"found conflicting %s:%d externalworkload: %s\", ip, port, strings.Join(conflictingWorkloads, \",\"))\n\t\treturn nil, status.Errorf(codes.FailedPrecondition, \"found %d externalworkloads with a conflicting ip %s:%d\", len(workloads), ip, port)\n\t}\n\n\treturn nil, nil\n}\n\n// getEndpointByHostname returns a pod that maps to the given hostname (or an\n// instanceID). The hostname is generally the prefix of the pod's DNS name;\n// since it may be arbitrary we need to look at the corresponding service's\n// Endpoints object to see whether the hostname matches a pod.\nfunc (ww *WorkloadWatcher) getEndpointByHostname(hostname string, svcID *ServiceID) (*corev1.Pod, error) {\n\tif ww.enableEndpointSlices {\n\t\tmatchLabels := map[string]string{discovery.LabelServiceName: svcID.Name}\n\t\tselector := labels.Set(matchLabels).AsSelector()\n\n\t\tsliceList, err := ww.k8sAPI.ES().Lister().EndpointSlices(svcID.Namespace).List(selector)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, slice := range sliceList {\n\t\t\tfor _, ep := range slice.Endpoints {\n\t\t\t\tif ep.Hostname != nil && hostname == *ep.Hostname {\n\t\t\t\t\tif ep.TargetRef != nil && ep.TargetRef.Kind == \"Pod\" {\n\t\t\t\t\t\tpodName := ep.TargetRef.Name\n\t\t\t\t\t\tpodNamespace := ep.TargetRef.Namespace\n\t\t\t\t\t\tpod, err := ww.k8sAPI.Pod().Lister().Pods(podNamespace).Get(podName)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn pod, nil\n\t\t\t\t\t}\n\t\t\t\t\treturn nil, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn nil, status.Errorf(codes.NotFound, \"no pod found in EndpointSlices of Service %s/%s for hostname %s\", svcID.Namespace, svcID.Name, hostname)\n\t}\n\n\tep, err := ww.k8sAPI.Endpoint().Lister().Endpoints(svcID.Namespace).Get(svcID.Name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, subset := range ep.Subsets {\n\t\tfor _, addr := range subset.Addresses {\n\n\t\t\tif hostname == addr.Hostname {\n\t\t\t\tif addr.TargetRef != nil && addr.TargetRef.Kind == \"Pod\" {\n\t\t\t\t\tpodName := addr.TargetRef.Name\n\t\t\t\t\tpodNamespace := addr.TargetRef.Namespace\n\t\t\t\t\tpod, err := ww.k8sAPI.Pod().Lister().Pods(podNamespace).Get(podName)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\treturn pod, nil\n\t\t\t\t}\n\t\t\t\treturn nil, nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, status.Errorf(codes.NotFound, \"no pod found in Endpoints %s/%s for hostname %s\", svcID.Namespace, svcID.Name, hostname)\n}\n\nfunc (wp *workloadPublisher) subscribe(listener WorkloadUpdateListener) error {\n\twp.mu.Lock()\n\tdefer wp.mu.Unlock()\n\n\twp.listeners = append(wp.listeners, listener)\n\n\tif err := listener.Update(&wp.addr); err != nil {\n\t\treturn fmt.Errorf(\"failed to send initial update: %w\", err)\n\t}\n\twp.metrics.incUpdates()\n\twp.subscriberCount.Add(1)\n\treturn nil\n}\n\nfunc (wp *workloadPublisher) unsubscribe(listener WorkloadUpdateListener) {\n\twp.mu.Lock()\n\tdefer wp.mu.Unlock()\n\n\tfor i, e := range wp.listeners {\n\t\tif e == listener {\n\t\t\tn := len(wp.listeners)\n\t\t\twp.listeners[i] = wp.listeners[n-1]\n\t\t\twp.listeners[n-1] = nil\n\t\t\twp.listeners = wp.listeners[:n-1]\n\t\t\twp.subscriberCount.Add(-1)\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n// updatePod creates an Address instance for the given pod, that is passed to\n// the listener's Update() method, only if the pod's running state has\n// changed. If the passed pod is nil, it means the pod (still referred to in\n// wp.pod) has been deleted.\n// Note that we care only about the running state instead of a stronger\n// requirement on readiness state because this is used in the context of\n// _endpoint_ profile subscriptions, as opposed to _service_ profile\n// subscriptions. The former is used when calling GetProfile for a specific\n// pod, usually when hitting instances of a StatefulSet, with IPs possibly\n// derived from a headless service. An example of this is a Cassandra cluster,\n// where a new node won't become ready until it's connected from other members\n// of the cluster. For such connections to work inside the mesh, we need\n// GetProfile to return the endpoint profile for the pod, even if it's not\n// ready.\n// See https://github.com/linkerd/linkerd2/issues/13247\nfunc (wp *workloadPublisher) updatePod(pod *corev1.Pod) {\n\twp.mu.Lock()\n\tdefer wp.mu.Unlock()\n\n\t// pod wasn't running or there was no backing pod - check if passed pod is running\n\tif wp.addr.Pod == nil {\n\t\tif pod == nil {\n\t\t\twp.log.Trace(\"Pod deletion event already consumed - ignore\")\n\t\t\treturn\n\t\t}\n\n\t\tif !isRunning(pod) {\n\t\t\twp.log.Tracef(\"Pod %s.%s not running - ignore\", pod.Name, pod.Namespace)\n\t\t\treturn\n\t\t}\n\n\t\twp.log.Debugf(\"Pod %s.%s started running\", pod.Name, pod.Namespace)\n\t\twp.addr.Pod = pod\n\n\t\t// Fill in ownership.\n\t\tif wp.addr.Pod != nil {\n\t\t\townerKind, ownerName, err := wp.metadataAPI.GetOwnerKindAndName(context.Background(), wp.addr.Pod, true)\n\t\t\tif err != nil {\n\t\t\t\twp.log.Errorf(\"Error getting pod owner for pod %s: %q\", wp.addr.Pod.GetName(), err)\n\t\t\t} else {\n\t\t\t\twp.addr.OwnerKind = ownerKind\n\t\t\t\twp.addr.OwnerName = ownerName\n\t\t\t}\n\t\t}\n\n\t\t// Compute opaque protocol.\n\t\tif err := SetToServerProtocol(wp.k8sAPI, &wp.addr, wp.log); err != nil {\n\t\t\twp.log.Errorf(\"Error computing opaque protocol for pod %s: %q\", wp.addr.Pod.GetName(), err)\n\t\t}\n\n\t\tfor _, l := range wp.listeners {\n\t\t\tif err := l.Update(&wp.addr); err != nil {\n\t\t\t\twp.log.Warnf(\"Error sending update to listener: %s\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\twp.metrics.incUpdates()\n\n\t\treturn\n\t}\n\n\t// backing pod stopped running or getting deleted\n\tif pod == nil || !isRunning(pod) {\n\t\twp.log.Debugf(\"Pod %s.%s deleted or it stopped running - remove\", wp.addr.Pod.Name, wp.addr.Pod.Namespace)\n\t\twp.addr.Pod = nil\n\t\twp.addr.OwnerKind = \"\"\n\t\twp.addr.OwnerName = \"\"\n\t\twp.addr.OpaqueProtocol = false\n\t\tfor _, l := range wp.listeners {\n\t\t\tif err := l.Update(&wp.addr); err != nil {\n\t\t\t\twp.log.Warnf(\"Error sending update to listener: %s\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\twp.metrics.incUpdates()\n\n\t\treturn\n\t}\n\n\twp.log.Tracef(\"Ignored event on pod %s.%s\", pod.Name, pod.Namespace)\n}\n\n// updateExternalWorkload creates an Address instance for the given externalworkload,\n// that is passed to the listener's Update() method, only if the workload's\n// readiness state has changed. If the passed workload is nil, it means the\n// workload (still referred to in wp.externalWorkload) has been deleted.\nfunc (wp *workloadPublisher) updateExternalWorkload(externalWorkload *ext.ExternalWorkload) {\n\twp.mu.Lock()\n\tdefer wp.mu.Unlock()\n\n\twp.addr.ExternalWorkload = externalWorkload\n\n\t// Fill in ownership.\n\tif wp.addr.ExternalWorkload != nil && len(wp.addr.ExternalWorkload.GetOwnerReferences()) == 1 {\n\t\twp.addr.OwnerKind = wp.addr.ExternalWorkload.GetOwnerReferences()[0].Kind\n\t\twp.addr.OwnerName = wp.addr.ExternalWorkload.GetOwnerReferences()[0].Name\n\t}\n\n\t// Compute opaque protocol.\n\tif err := SetToServerProtocolExternalWorkload(wp.k8sAPI, &wp.addr); err != nil {\n\t\twp.log.Errorf(\"Error computing opaque protocol for externalworkload %s: %q\", wp.addr.ExternalWorkload.GetName(), err)\n\t}\n\n\tfor _, l := range wp.listeners {\n\t\tif err := l.Update(&wp.addr); err != nil {\n\t\t\twp.log.Warnf(\"Error sending update to listener: %s\", err)\n\t\t\tcontinue\n\t\t}\n\t}\n\twp.metrics.incUpdates()\n}\n\n// GetAnnotatedOpaquePorts returns the opaque ports for the pod given its\n// annotations, or the default opaque ports if it's not annotated\nfunc GetAnnotatedOpaquePorts(pod *corev1.Pod, defaultPorts map[uint32]struct{}) map[uint32]struct{} {\n\tif pod == nil {\n\t\treturn defaultPorts\n\t}\n\tannotation, ok := pod.Annotations[consts.ProxyOpaquePortsAnnotation]\n\tif !ok {\n\t\treturn defaultPorts\n\t}\n\topaquePorts := make(map[uint32]struct{})\n\tnamedPorts := util.GetNamedPorts(append(pod.Spec.InitContainers, pod.Spec.Containers...))\n\tif annotation != \"\" {\n\t\tfor _, pr := range util.ParseContainerOpaquePorts(annotation, namedPorts) {\n\t\t\tfor _, port := range pr.Ports() {\n\t\t\t\topaquePorts[uint32(port)] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\treturn opaquePorts\n}\n\n// GetAnnotatedOpaquePortsForExternalWorkload returns the opaque ports for the external workload given its\n// annotations, or the default opaque ports if it's not annotated\nfunc GetAnnotatedOpaquePortsForExternalWorkload(ew *ext.ExternalWorkload, defaultPorts map[uint32]struct{}) map[uint32]struct{} {\n\tif ew == nil {\n\t\treturn defaultPorts\n\t}\n\tannotation, ok := ew.Annotations[consts.ProxyOpaquePortsAnnotation]\n\tif !ok {\n\t\treturn defaultPorts\n\t}\n\topaquePorts := make(map[uint32]struct{})\n\tif annotation != \"\" {\n\t\tfor _, pr := range parseExternalWorkloadOpaquePorts(annotation, ew) {\n\t\t\tfor _, port := range pr.Ports() {\n\t\t\t\topaquePorts[uint32(port)] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\treturn opaquePorts\n}\n\nfunc parseExternalWorkloadOpaquePorts(override string, ew *ext.ExternalWorkload) []util.PortRange {\n\tportRanges := util.GetPortRanges(override)\n\tvar values []util.PortRange\n\tfor _, pr := range portRanges {\n\t\tport, named := isNamedInExternalWorkload(pr, ew)\n\t\tif named {\n\t\t\tvalues = append(values, util.PortRange{UpperBound: int(port), LowerBound: int(port)})\n\t\t} else {\n\t\t\tpr, err := util.ParsePortRange(pr)\n\t\t\tif err != nil {\n\t\t\t\tlogging.Warnf(\"Invalid port range [%v]: %s\", pr, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvalues = append(values, pr)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc isNamedInExternalWorkload(pr string, ew *ext.ExternalWorkload) (int32, bool) {\n\tfor _, p := range ew.Spec.Ports {\n\t\tif p.Name == pr {\n\t\t\treturn p.Port, true\n\t\t}\n\t}\n\n\treturn 0, false\n}\n\nfunc isRunning(pod *corev1.Pod) bool {\n\treturn pod != nil && pod.Status.Phase == corev1.PodRunning\n}\n"
  },
  {
    "path": "controller/api/destination/watcher/workload_watcher_test.go",
    "content": "package watcher\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nfunc TestIpWatcherGetPod(t *testing.T) {\n\tpodIP := \"10.255.0.1\"\n\thostIP := \"172.0.0.1\"\n\tvar hostPort0 uint32 = 22344\n\tvar hostPort1 uint32 = 22345\n\tvar hostPort2 uint32 = 22346\n\texpectedPodName := \"hostPortPod1\"\n\tk8sConfigs := []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: hostPortPod1\n  namespace: ns\nspec:\n  initContainers:\n  - image: test\n    name: hostPortInitContainer\n    ports:\n    - containerPort: 12344\n      hostPort: 22344\n  containers:\n  - image: test\n    name: hostPortContainer1\n    restartPolicy: Always\n    ports:\n    - containerPort: 12345\n      hostIP: 172.0.0.1\n      hostPort: 22345\n  - image: test\n    name: hostPortContainer2\n    ports:\n    - containerPort: 12346\n      hostIP: 172.0.0.1\n      hostPort: 22346\nstatus:\n  phase: Running\n  podIP: 10.255.0.1\n  hostIP: 172.0.0.1`,\n\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: pod\n  namespace: ns\nstatus:\n  phase: Running\n  podIP: 10.255.0.1`,\n\t}\n\tt.Run(\"get pod by host IP and host port\", func(t *testing.T) {\n\t\tk8sAPI, err := k8s.NewFakeAPI(k8sConfigs...)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to create new fake API: %s\", err)\n\t\t}\n\n\t\terr = InitializeIndexers(k8sAPI)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"initializeIndexers returned an error: %s\", err)\n\t\t}\n\n\t\tk8sAPI.Sync(nil)\n\t\tww := WorkloadWatcher{\n\t\t\tk8sAPI: k8sAPI,\n\t\t\tlog: log.WithFields(log.Fields{\n\t\t\t\t\"component\": \"pod-watcher\",\n\t\t\t}),\n\t\t}\n\n\t\t// Get host IP pod that is mapped to the port `hostPort0` (in an init container)\n\t\tpod, err := ww.getPodByHostIP(hostIP, hostPort0)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to get pod: %s\", err)\n\t\t}\n\t\tif pod == nil {\n\t\t\tt.Fatalf(\"failed to find pod mapped to %s:%d\", hostIP, hostPort1)\n\t\t}\n\t\tif pod.Name != expectedPodName {\n\t\t\tt.Fatalf(\"expected pod name to be %s, but got %s\", expectedPodName, pod.Name)\n\t\t}\n\n\t\t// Get host IP pod that is mapped to the port `hostPort1`\n\t\tpod, err = ww.getPodByHostIP(hostIP, hostPort1)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to get pod: %s\", err)\n\t\t}\n\t\tif pod == nil {\n\t\t\tt.Fatalf(\"failed to find pod mapped to %s:%d\", hostIP, hostPort1)\n\t\t}\n\t\tif pod.Name != expectedPodName {\n\t\t\tt.Fatalf(\"expected pod name to be %s, but got %s\", expectedPodName, pod.Name)\n\t\t}\n\t\t// Get host IP pod that is mapped to the port `hostPort2`; this tests\n\t\t// that the indexer properly adds multiple containers from a single\n\t\t// pod.\n\t\tpod, err = ww.getPodByHostIP(hostIP, hostPort2)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to get pod: %s\", err)\n\t\t}\n\t\tif pod == nil {\n\t\t\tt.Fatalf(\"failed to find pod mapped to %s:%d\", hostIP, hostPort2)\n\t\t}\n\t\tif pod.Name != expectedPodName {\n\t\t\tt.Fatalf(\"expected pod name to be %s, but got %s\", expectedPodName, pod.Name)\n\t\t}\n\t\t// Get host IP pod with unmapped host port\n\t\tpod, err = ww.getPodByHostIP(hostIP, 12347)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"expected no error when getting host IP pod with unmapped host port, but got: %s\", err)\n\t\t}\n\t\tif pod != nil {\n\t\t\tt.Fatal(\"expected no pod to be found with unmapped host port\")\n\t\t}\n\t\t// Get pod IP pod and expect an error\n\t\t_, err = ww.getPodByPodIP(podIP, 12346)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expected error when getting by pod IP and unmapped host port, but got none\")\n\t\t}\n\t\tif !strings.Contains(err.Error(), \"pods with a conflicting pod network IP\") {\n\t\t\tt.Fatalf(\"expected error to be pod IP address conflict, but got: %s\", err)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "controller/api/util/test_util.go",
    "content": "package util\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"sync\"\n\n\tdestinationPb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2-proxy-api/go/net\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/metadata\"\n)\n\ntype mockStream struct {\n\tctx    context.Context\n\tCancel context.CancelFunc\n}\n\nfunc newMockStream() mockStream {\n\tctx, cancel := context.WithCancel(context.Background())\n\treturn mockStream{ctx, cancel}\n}\n\nfunc (ms mockStream) Context() context.Context    { return ms.ctx }\nfunc (ms mockStream) SendMsg(m interface{}) error { return nil }\nfunc (ms mockStream) RecvMsg(m interface{}) error { return nil }\n\n// MockServerStream satisfies the grpc.ServerStream interface\ntype MockServerStream struct{ mockStream }\n\n// SetHeader satisfies the grpc.ServerStream interface\nfunc (mss MockServerStream) SetHeader(metadata.MD) error { return nil }\n\n// SendHeader satisfies the grpc.ServerStream interface\nfunc (mss MockServerStream) SendHeader(metadata.MD) error { return nil }\n\n// SetTrailer satisfies the grpc.ServerStream interface\nfunc (mss MockServerStream) SetTrailer(metadata.MD) {}\n\n// NewMockServerStream instantiates a MockServerStream\nfunc NewMockServerStream() MockServerStream {\n\treturn MockServerStream{newMockStream()}\n}\n\n// MockAPIClient satisfies the destination API's interfaces\ntype MockAPIClient struct {\n\tErrorToReturn                error\n\tDestinationGetClientToReturn destinationPb.Destination_GetClient\n}\n\n// Get provides a mock of a destination API method.\nfunc (c *MockAPIClient) Get(ctx context.Context, in *destinationPb.GetDestination, opts ...grpc.CallOption) (destinationPb.Destination_GetClient, error) {\n\treturn c.DestinationGetClientToReturn, c.ErrorToReturn\n}\n\n// GetProfile provides a mock of a destination API method\nfunc (c *MockAPIClient) GetProfile(ctx context.Context, _ *destinationPb.GetDestination, _ ...grpc.CallOption) (destinationPb.Destination_GetProfileClient, error) {\n\t// Not implemented through this client. The proxies use the gRPC server directly instead.\n\treturn nil, errors.New(\"not implemented\")\n}\n\n// MockDestinationGetClient satisfies the Destination_GetClient gRPC interface.\ntype MockDestinationGetClient struct {\n\tUpdatesToReturn []destinationPb.Update\n\tErrorsToReturn  []error\n\tgrpc.ClientStream\n\tsync.Mutex\n}\n\n// Recv satisfies the Destination_GetClient.Recv() gRPC method.\nfunc (a *MockDestinationGetClient) Recv() (*destinationPb.Update, error) {\n\ta.Lock()\n\tdefer a.Unlock()\n\tvar updatePopped *destinationPb.Update\n\tvar errorPopped error\n\tif len(a.UpdatesToReturn) == 0 && len(a.ErrorsToReturn) == 0 {\n\t\treturn nil, io.EOF\n\t}\n\tif len(a.UpdatesToReturn) != 0 {\n\t\tupdatePopped, a.UpdatesToReturn = &a.UpdatesToReturn[0], a.UpdatesToReturn[1:]\n\t}\n\tif len(a.ErrorsToReturn) != 0 {\n\t\terrorPopped, a.ErrorsToReturn = a.ErrorsToReturn[0], a.ErrorsToReturn[1:]\n\t}\n\n\treturn updatePopped, errorPopped\n}\n\n// AuthorityEndpoints holds the details for the Endpoints associated to an authority\ntype AuthorityEndpoints struct {\n\tNamespace string\n\tServiceID string\n\tPods      []PodDetails\n}\n\n// PodDetails holds the details for pod associated to an Endpoint\ntype PodDetails struct {\n\tName string\n\tIP   uint32\n\tPort uint32\n}\n\n// BuildAddrSet converts AuthorityEndpoints into its protobuf representation\nfunc BuildAddrSet(endpoint AuthorityEndpoints) *destinationPb.WeightedAddrSet {\n\taddrs := make([]*destinationPb.WeightedAddr, 0)\n\tfor _, pod := range endpoint.Pods {\n\t\taddr := &net.TcpAddress{\n\t\t\tIp:   &net.IPAddress{Ip: &net.IPAddress_Ipv4{Ipv4: pod.IP}},\n\t\t\tPort: pod.Port,\n\t\t}\n\t\tlabels := map[string]string{\"pod\": pod.Name}\n\t\tweightedAddr := &destinationPb.WeightedAddr{Addr: addr, MetricLabels: labels}\n\t\taddrs = append(addrs, weightedAddr)\n\t}\n\tlabels := map[string]string{\"namespace\": endpoint.Namespace, \"service\": endpoint.ServiceID}\n\treturn &destinationPb.WeightedAddrSet{Addrs: addrs, MetricLabels: labels}\n}\n"
  },
  {
    "path": "controller/cmd/destination/main.go",
    "content": "package destination\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"flag\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination\"\n\texternalworkload \"github.com/linkerd/linkerd2/controller/api/destination/external-workload\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination/watcher\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/admin\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\tpkgK8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/trace\"\n\t\"github.com/linkerd/linkerd2/pkg/util\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\n// Main executes the destination subcommand\nfunc Main(args []string) {\n\tcmd := flag.NewFlagSet(\"destination\", flag.ExitOnError)\n\n\taddr := cmd.String(\"addr\", \":8086\", \"address to serve on\")\n\tmetricsAddr := cmd.String(\"metrics-addr\", \":9996\", \"address to serve scrapable metrics on\")\n\tkubeConfigPath := cmd.String(\"kubeconfig\", \"\", \"path to kube config\")\n\tcontrollerNamespace := cmd.String(\"controller-namespace\", \"linkerd\", \"namespace in which Linkerd is installed\")\n\toutboundTransportMode := cmd.String(\"outbound-transport-mode\", \"transparent\",\n\t\t\"Force proxies to use the legacy transport for meshed traffic, i.e. transparently add TLS to the destination instead of routing to the proxy's inbound port\")\n\tenableH2Upgrade := cmd.Bool(\"enable-h2-upgrade\", true,\n\t\t\"Enable transparently upgraded HTTP2 connections among pods in the service mesh\")\n\tenableEndpointSlices := cmd.Bool(\"enable-endpoint-slices\", true,\n\t\t\"Enable the usage of EndpointSlice informers and resources\")\n\tenableIPv6 := cmd.Bool(\"enable-ipv6\", true,\n\t\t\"Set to true to allow discovering IPv6 endpoints and preferring IPv6 when both IPv4 and IPv6 are available\")\n\ttrustDomain := cmd.String(\"identity-trust-domain\", \"\", \"configures the name suffix used for identities\")\n\tclusterDomain := cmd.String(\"cluster-domain\", \"\", \"kubernetes cluster domain\")\n\tdefaultOpaquePorts := cmd.String(\"default-opaque-ports\", \"\", \"configures the default opaque ports\")\n\tenablePprof := cmd.Bool(\"enable-pprof\", false, \"Enable pprof endpoints on the admin server\")\n\t// This will default to true. It can be overridden with experimental CLI\n\t// flags. Currently not exposed as a configuration value through Helm.\n\texportControllerQueueMetrics := cmd.Bool(\"export-queue-metrics\", true, \"Exports queue metrics for the external workload controller\")\n\tstreamQueueCapacity := cmd.Int(\"stream-queue-capacity\", destination.DefaultStreamQueueCapacity, \"Maximum number of updates buffered per stream before the stream is closed\")\n\n\ttraceCollector := flags.AddTraceFlags(cmd)\n\n\t// Zone weighting is disabled by default because it is not consumed by\n\t// proxies. This feature exists to support experimentation on top of the\n\t// Linkerd control plane API.\n\textEndpointZoneWeights := cmd.Bool(\"ext-endpoint-zone-weights\", false,\n\t\t\"Enable setting endpoint weighting based on zone locality\")\n\n\t// Cluster-wide defaults for meshed HTTP/2 client parameters.. These only\n\t// apply to meshed connections, as we don't want to conflict with HTTP/2\n\t// servers that enforce policies that limit client keep-alive behavior. The\n\t// inbound proxy does not enforce such policies, so we're free to use\n\t// defaults for meshed HTTP/2 connections.\n\tmeshedHTTP2ClientParamsJSON := cmd.String(\"meshed-http2-client-params\", \"\",\n\t\t\"HTTP/2 client parameters for meshed connections in JSON format\")\n\n\tflags.ConfigureAndParse(cmd, args)\n\n\tif *streamQueueCapacity <= 0 {\n\t\tlog.Fatalf(\"--stream-queue-capacity must be greater than 0\")\n\t}\n\n\tif *enableIPv6 && !*enableEndpointSlices {\n\t\tlog.Fatal(\"If --enable-ipv6=true then --enable-endpoint-slices needs to be true\")\n\t}\n\n\tvar meshedHTTP2ClientParams *pb.Http2ClientParams\n\tif meshedHTTP2ClientParamsJSON != nil && *meshedHTTP2ClientParamsJSON != \"\" {\n\t\tmeshedHTTP2ClientParams = &pb.Http2ClientParams{}\n\t\tif err := json.Unmarshal([]byte(*meshedHTTP2ClientParamsJSON), meshedHTTP2ClientParams); err != nil {\n\t\t\tlog.Fatalf(\"Failed to parse meshed HTTP/2 client parameters: %s\", err)\n\t\t}\n\t}\n\n\tready := false\n\tadminServer := admin.NewServer(*metricsAddr, *enablePprof, &ready)\n\n\tgo func() {\n\t\tlog.Infof(\"starting admin server on %s\", *metricsAddr)\n\t\tif err := adminServer.ListenAndServe(); err != nil {\n\t\t\tif errors.Is(err, http.ErrServerClosed) {\n\t\t\t\tlog.Infof(\"Admin server closed (%s)\", *metricsAddr)\n\t\t\t} else {\n\t\t\t\tlog.Errorf(\"Admin server error (%s): %s\", *metricsAddr, err)\n\t\t\t}\n\t\t}\n\t}()\n\n\tstop := make(chan os.Signal, 1)\n\tsignal.Notify(stop, os.Interrupt, syscall.SIGTERM)\n\n\tdone := make(chan struct{})\n\n\tlis, err := net.Listen(\"tcp\", *addr)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to listen on %s: %s\", *addr, err)\n\t}\n\n\tif *trustDomain == \"\" {\n\t\t*trustDomain = \"cluster.local\"\n\t\tlog.Warnf(\" expected trust domain through args (falling back to %s)\", *trustDomain)\n\t}\n\n\tif *clusterDomain == \"\" {\n\t\t*clusterDomain = \"cluster.local\"\n\t\tlog.Warnf(\"expected cluster domain through args (falling back to %s)\", *clusterDomain)\n\t}\n\n\topaquePorts := util.ParsePorts(*defaultOpaquePorts)\n\n\tlog.Infof(\"Using default opaque ports: %v\", opaquePorts)\n\n\tif *traceCollector != \"\" {\n\t\tif err := trace.InitializeTracing(\"linkerd-destination\", *traceCollector); err != nil {\n\t\t\tlog.Warnf(\"failed to initialize tracing: %s\", err)\n\t\t}\n\t}\n\n\t// we need to create a separate client to check for EndpointSlice access in k8s cluster\n\t// when slices are enabled and registered, k8sAPI is initialized with 'ES' resource\n\tk8Client, err := pkgK8s.NewAPI(*kubeConfigPath, \"\", \"\", []string{}, 0)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to initialize K8s API Client: %s\", err)\n\t}\n\n\tctx := context.Background()\n\n\terr = pkgK8s.EndpointSliceAccess(ctx, k8Client)\n\tif *enableEndpointSlices && err != nil {\n\t\tlog.Fatalf(\"Failed to start with EndpointSlices enabled: %s\", err)\n\t}\n\n\tvar k8sAPI *k8s.API\n\tif *enableEndpointSlices {\n\t\tk8sAPI, err = k8s.InitializeAPI(\n\t\t\tctx,\n\t\t\t*kubeConfigPath,\n\t\t\ttrue,\n\t\t\t\"local\",\n\t\t\tk8s.Endpoint, k8s.ES, k8s.Pod, k8s.Svc, k8s.SP, k8s.Job, k8s.Srv, k8s.ExtWorkload,\n\t\t)\n\t} else {\n\t\tk8sAPI, err = k8s.InitializeAPI(\n\t\t\tctx,\n\t\t\t*kubeConfigPath,\n\t\t\ttrue,\n\t\t\t\"local\",\n\t\t\tk8s.Endpoint, k8s.Pod, k8s.Svc, k8s.SP, k8s.Job, k8s.Srv, k8s.ExtWorkload,\n\t\t)\n\t}\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to initialize K8s API: %s\", err)\n\t}\n\n\tmetadataAPI, err := k8s.InitializeMetadataAPI(*kubeConfigPath, \"local\", k8s.Node, k8s.RS, k8s.Job)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to initialize Kubernetes metadata API: %s\", err)\n\t}\n\n\tclusterStore, err := watcher.NewClusterStore(k8Client, *controllerNamespace, *enableEndpointSlices)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to initialize Cluster Store: %s\", err)\n\t}\n\n\tvar forceOpaqueTransport bool\n\tswitch *outboundTransportMode {\n\tcase \"transport-header\":\n\t\tforceOpaqueTransport = true\n\tcase \"transparent\":\n\t\tforceOpaqueTransport = false\n\tdefault:\n\t\tlog.Errorf(\"Unknown value for 'outboundTransportMode': %s, defaulting to \\\"transparent\\\"\", *outboundTransportMode)\n\t\tforceOpaqueTransport = false\n\t}\n\n\tconfig := destination.Config{\n\t\tControllerNS:            *controllerNamespace,\n\t\tIdentityTrustDomain:     *trustDomain,\n\t\tClusterDomain:           *clusterDomain,\n\t\tDefaultOpaquePorts:      opaquePorts,\n\t\tForceOpaqueTransport:    forceOpaqueTransport,\n\t\tEnableH2Upgrade:         *enableH2Upgrade,\n\t\tEnableEndpointSlices:    *enableEndpointSlices,\n\t\tEnableIPv6:              *enableIPv6,\n\t\tExtEndpointZoneWeights:  *extEndpointZoneWeights,\n\t\tMeshedHttp2ClientParams: meshedHTTP2ClientParams,\n\t\tStreamQueueCapacity:     *streamQueueCapacity,\n\t}\n\tserver, err := destination.NewServer(\n\t\t*addr,\n\t\tconfig,\n\t\tk8sAPI,\n\t\tmetadataAPI,\n\t\tclusterStore,\n\t\tdone,\n\t)\n\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to initialize destination server: %s\", err)\n\t}\n\n\t// blocks until caches are synced\n\tk8sAPI.Sync(nil)\n\tmetadataAPI.Sync(nil)\n\tclusterStore.Sync(nil)\n\n\t// Start mesh expansion external workload controller to write endpointslices\n\t// to API Server.\n\tif *enableEndpointSlices {\n\t\thostname, ok := os.LookupEnv(\"HOSTNAME\")\n\t\tif !ok {\n\t\t\tlog.Fatal(\"Failed to initialize External Workload Endpoints Controller, \\\"HOSTNAME\\\" value not found\")\n\t\t}\n\t\texternalWorkloadController, err := externalworkload.NewEndpointsController(k8sAPI, hostname, *controllerNamespace, done, *exportControllerQueueMetrics)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Failed to initialize External Workload Endpoints Controller: %v\", err)\n\t\t}\n\n\t\texternalWorkloadController.Start()\n\t}\n\n\tgo func() {\n\t\tlog.Infof(\"starting gRPC server on %s\", *addr)\n\t\tif err := server.Serve(lis); err != nil {\n\t\t\tlog.Errorf(\"failed to start destination gRPC server: %s\", err)\n\t\t}\n\t}()\n\n\tready = true\n\n\t<-stop\n\n\tlog.Infof(\"shutting down gRPC server on %s\", *addr)\n\tclose(done)\n\tserver.GracefulStop()\n\tadminServer.Shutdown(ctx)\n}\n"
  },
  {
    "path": "controller/cmd/heartbeat/main.go",
    "content": "package heartbeat\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"net/url\"\n\t\"runtime\"\n\n\t\"github.com/linkerd/linkerd2/controller/heartbeat\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\tpromApi \"github.com/prometheus/client_golang/api\"\n\tpromv1 \"github.com/prometheus/client_golang/api/prometheus/v1\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\n// Main executes the heartbeat subcommand\nfunc Main(args []string) {\n\tcmd := flag.NewFlagSet(\"heartbeat\", flag.ExitOnError)\n\n\tkubeConfigPath := cmd.String(\"kubeconfig\", \"\", \"path to kube config\")\n\tprometheusURL := cmd.String(\"prometheus-url\", \"http://127.0.0.1:9090\", \"prometheus url\")\n\tcontrollerNamespace := cmd.String(\"controller-namespace\", \"linkerd\", \"namespace in which Linkerd is installed\")\n\n\tflags.ConfigureAndParse(cmd, args)\n\n\t// Gather the following fields:\n\t// - version\n\t// - source\n\t// - uuid\n\t// - k8s-version\n\t// - install-time\n\t// - rps\n\t// - meshed-pods\n\t// - proxy-injector-injections\n\t// TODO:\n\t// - k8s-env\n\tv := url.Values{}\n\tv.Set(\"arch\", runtime.GOARCH)\n\tv.Set(\"version\", version.Version)\n\tv.Set(\"source\", \"heartbeat\")\n\n\tkubeAPI, err := k8s.NewAPI(*kubeConfigPath, \"\", \"\", []string{}, 0)\n\tif err != nil {\n\t\tlog.Errorf(\"Failed to initialize k8s API: %s\", err)\n\t} else {\n\t\tk8sV := heartbeat.K8sValues(context.Background(), kubeAPI, *controllerNamespace)\n\t\tv = heartbeat.MergeValues(v, k8sV)\n\t}\n\n\tprometheusClient, err := promApi.NewClient(promApi.Config{Address: *prometheusURL})\n\tif err != nil {\n\t\tlog.Warnf(\"Failed to initialize Prometheus client: %s\", err)\n\t} else {\n\t\tpromAPI := promv1.NewAPI(prometheusClient)\n\t\tpromV := heartbeat.PromValues(promAPI, *controllerNamespace)\n\t\tv = heartbeat.MergeValues(v, promV)\n\t}\n\n\terr = heartbeat.Send(v)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to send heartbeat: %s\", err)\n\t}\n}\n"
  },
  {
    "path": "controller/cmd/identity/main.go",
    "content": "package identity\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"syscall\"\n\t\"time\"\n\n\tidctl \"github.com/linkerd/linkerd2/controller/identity\"\n\t\"github.com/linkerd/linkerd2/pkg/admin\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/identity\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/prometheus\"\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n\t\"github.com/linkerd/linkerd2/pkg/trace\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"google.golang.org/grpc\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\ttypedcorev1 \"k8s.io/client-go/kubernetes/typed/core/v1\"\n\t\"k8s.io/client-go/tools/record\"\n)\n\n// Main executes the identity subcommand\nfunc Main(args []string) {\n\tcmd := flag.NewFlagSet(\"identity\", flag.ExitOnError)\n\n\taddr := cmd.String(\"addr\", \":8080\", \"address to serve on\")\n\tadminAddr := cmd.String(\"admin-addr\", \":9990\", \"address of HTTP admin server\")\n\tkubeConfigPath := cmd.String(\"kubeconfig\", \"\", \"path to kube config\")\n\tcontrollerNS := cmd.String(\"controller-namespace\", \"\", \"namespace in which Linkerd is installed\")\n\tidentityScheme := cmd.String(\"identity-scheme\", \"\", \"scheme used for the identity issuer secret format\")\n\ttrustDomain := cmd.String(\"identity-trust-domain\", \"\", \"configures the name suffix used for identities\")\n\tidentityIssuanceLifeTime := cmd.String(\"identity-issuance-lifetime\", \"\", \"the amount of time for which the Identity issuer should certify identity\")\n\tidentityClockSkewAllowance := cmd.String(\"identity-clock-skew-allowance\", \"\", \"the amount of time to allow for clock skew within a Linkerd cluster\")\n\tenablePprof := cmd.Bool(\"enable-pprof\", false, \"Enable pprof endpoints on the admin server\")\n\tqps := cmd.Float64(\"kube-apiclient-qps\", 100, \"Maximum QPS sent to the kube-apiserver before throttling\")\n\tburst := cmd.Int(\"kube-apiclient-burst\", 200, \"Burst value over kube-apiclient-qps\")\n\n\tissuerPath := cmd.String(\"issuer\",\n\t\t\"/var/run/linkerd/identity/issuer\",\n\t\t\"path to directory containing issuer credentials\")\n\n\tvar issuerPathCrt string\n\tvar issuerPathKey string\n\ttraceCollector := flags.AddTraceFlags(cmd)\n\tcomponentName := \"linkerd-identity\"\n\n\tflags.ConfigureAndParse(cmd, args)\n\n\tready := false\n\tadminServer := admin.NewServer(*adminAddr, *enablePprof, &ready)\n\n\tgo func() {\n\t\tlog.Infof(\"starting admin server on %s\", *adminAddr)\n\t\tif err := adminServer.ListenAndServe(); err != nil {\n\t\t\tlog.Errorf(\"failed to start identity admin server: %s\", err)\n\t\t}\n\t}()\n\n\tidentityTrustAnchorPEM, err := os.ReadFile(k8s.MountPathTrustRootsPEM)\n\tif err != nil {\n\t\tlog.Fatalf(\"could not read identity trust anchors PEM: %s\", err.Error())\n\t}\n\n\tstop := make(chan os.Signal, 1)\n\tsignal.Notify(stop, os.Interrupt, syscall.SIGTERM)\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tif *identityScheme == \"\" || *trustDomain == \"\" {\n\t\tlog.Infof(\"Identity disabled in control plane configuration.\")\n\t\t//nolint:gocritic\n\t\tos.Exit(0)\n\t}\n\n\tif *identityScheme == k8s.IdentityIssuerSchemeLinkerd {\n\t\tissuerPathCrt = filepath.Join(*issuerPath, k8s.IdentityIssuerCrtName)\n\t\tissuerPathKey = filepath.Join(*issuerPath, k8s.IdentityIssuerKeyName)\n\t} else {\n\t\tissuerPathCrt = filepath.Join(*issuerPath, corev1.TLSCertKey)\n\t\tissuerPathKey = filepath.Join(*issuerPath, corev1.TLSPrivateKeyKey)\n\t}\n\n\tdom, err := idctl.NewTrustDomain(*controllerNS, *trustDomain)\n\tif err != nil {\n\t\t//nolint:gocritic\n\t\tlog.Fatalf(\"Invalid trust domain: %s\", err.Error())\n\t}\n\n\ttrustAnchors, err := tls.DecodePEMCertPool(string(identityTrustAnchorPEM))\n\tif err != nil {\n\t\t//nolint:gocritic\n\t\tlog.Fatalf(\"Failed to read trust anchors: %s\", err)\n\t}\n\n\tvalidity := tls.Validity{\n\t\tClockSkewAllowance: tls.DefaultClockSkewAllowance,\n\t\tLifetime:           identity.DefaultIssuanceLifetime,\n\t}\n\tif pbd := *identityClockSkewAllowance; pbd != \"\" {\n\t\tcsa, err := time.ParseDuration(pbd)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"Invalid clock skew allowance: %s\", err)\n\t\t} else {\n\t\t\tvalidity.ClockSkewAllowance = csa\n\t\t}\n\t}\n\tif pbd := *identityIssuanceLifeTime; pbd != \"\" {\n\t\til, err := time.ParseDuration(pbd)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"Invalid issuance lifetime: %s\", err)\n\t\t} else {\n\t\t\tvalidity.Lifetime = il\n\t\t}\n\t}\n\n\texpectedName := fmt.Sprintf(\"identity.%s.%s\", *controllerNS, *trustDomain)\n\tissuerEvent := make(chan struct{})\n\tissuerError := make(chan error)\n\n\t//\n\t// Create and start FS creds watcher\n\t//\n\twatcher := tls.NewFsCredsWatcher(*issuerPath, issuerEvent, issuerError)\n\tgo func() {\n\t\tif err := watcher.StartWatching(ctx); err != nil {\n\t\t\t//nolint:gocritic\n\t\t\tlog.Fatalf(\"Failed to start creds watcher: %s\", err)\n\t\t}\n\t}()\n\n\t//\n\t// Create k8s API\n\t//\n\tconfig, err := k8s.GetConfig(*kubeConfigPath, \"\")\n\tif err != nil {\n\t\tlog.Fatalf(\"Error configuring Kubernetes API client: %s\", err)\n\t}\n\tk8sAPI, err := k8s.NewAPIForConfig(config, \"\", []string{}, 0, float32(*qps), *burst)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to load kubeconfig: %s: %s\", *kubeConfigPath, err)\n\t}\n\tlog.Infof(\"Using k8s client with QPS=%.2f Burst=%d\", config.QPS, config.Burst)\n\n\tv, err := idctl.NewK8sTokenValidator(ctx, k8sAPI, dom)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to initialize identity service: %s\", err)\n\t}\n\n\t// Create K8s event recorder\n\teventBroadcaster := record.NewBroadcaster()\n\teventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{\n\t\tInterface: k8sAPI.CoreV1().Events(\"\"),\n\t})\n\trecorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: componentName})\n\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to construct k8s event recorder: %s\", err)\n\t}\n\n\trecordEventFunc := func(parent runtime.Object, eventType, reason, message string) {\n\t\tif parent == nil {\n\t\t\tparent = &corev1.ObjectReference{\n\t\t\t\tAPIVersion: \"apps/v1\",\n\t\t\t\tKind:       \"Deployment\",\n\t\t\t\tNamespace:  *controllerNS,\n\t\t\t\tName:       componentName,\n\t\t\t}\n\t\t}\n\t\trecorder.Event(parent, eventType, reason, message)\n\t}\n\n\t//\n\t// Create, initialize and run service\n\t//\n\tsvc := identity.NewService(v, trustAnchors, &validity, recordEventFunc, expectedName, issuerPathCrt, issuerPathKey)\n\tif err = svc.Initialize(); err != nil {\n\t\t//nolint:gocritic\n\t\tlog.Fatalf(\"Failed to initialize identity service: %s\", err)\n\t}\n\tgo func() {\n\t\tsvc.Run(issuerEvent, issuerError)\n\t}()\n\n\t//\n\t// Bind and serve\n\t//\n\tlis, err := net.Listen(\"tcp\", *addr)\n\tif err != nil {\n\t\t//nolint:gocritic\n\t\tlog.Fatalf(\"Failed to listen on %s: %s\", *addr, err)\n\t}\n\n\tif *traceCollector != \"\" {\n\t\tif err := trace.InitializeTracing(componentName, *traceCollector); err != nil {\n\t\t\tlog.Warnf(\"failed to initialize tracing: %s\", err)\n\t\t}\n\t}\n\tsrv := prometheus.NewGrpcServer(grpc.MaxConcurrentStreams(0))\n\tidentity.Register(srv, svc)\n\tgo func() {\n\t\tlog.Infof(\"starting gRPC server on %s\", *addr)\n\t\tif err := srv.Serve(lis); err != nil {\n\t\t\tlog.Errorf(\"failed to start identity gRPC server: %s\", err)\n\t\t}\n\t}()\n\n\tready = true\n\n\t<-stop\n\tlog.Infof(\"shutting down gRPC server on %s\", *addr)\n\tsrv.GracefulStop()\n\tadminServer.Shutdown(ctx)\n}\n"
  },
  {
    "path": "controller/cmd/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/linkerd/linkerd2/controller/cmd/destination\"\n\t\"github.com/linkerd/linkerd2/controller/cmd/heartbeat\"\n\t\"github.com/linkerd/linkerd2/controller/cmd/identity\"\n\tproxyinjector \"github.com/linkerd/linkerd2/controller/cmd/proxy-injector\"\n\tspvalidator \"github.com/linkerd/linkerd2/controller/cmd/sp-validator\"\n\tservicemirror \"github.com/linkerd/linkerd2/multicluster/cmd/service-mirror\"\n)\n\nfunc main() {\n\tif len(os.Args) < 2 {\n\t\tfmt.Println(\"expected a subcommand\")\n\t\tos.Exit(1)\n\t}\n\n\tswitch os.Args[1] {\n\tcase \"destination\":\n\t\tdestination.Main(os.Args[2:])\n\tcase \"heartbeat\":\n\t\theartbeat.Main(os.Args[2:])\n\tcase \"identity\":\n\t\tidentity.Main(os.Args[2:])\n\tcase \"proxy-injector\":\n\t\tproxyinjector.Main(os.Args[2:])\n\tcase \"sp-validator\":\n\t\tspvalidator.Main(os.Args[2:])\n\tcase \"service-mirror\":\n\t\tservicemirror.Main(os.Args[2:])\n\tdefault:\n\t\tfmt.Printf(\"unknown subcommand: %s\", os.Args[1])\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "controller/cmd/proxy-injector/main.go",
    "content": "package proxyinjector\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tinjector \"github.com/linkerd/linkerd2/controller/proxy-injector\"\n\t\"github.com/linkerd/linkerd2/controller/webhook\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/inject\"\n)\n\n// Main executes the proxy-injector subcommand\nfunc Main(args []string) {\n\tcmd := flag.NewFlagSet(\"proxy-injector\", flag.ExitOnError)\n\tmetricsAddr := cmd.String(\"metrics-addr\", fmt.Sprintf(\":%d\", 9995), \"address to serve scrapable metrics on\")\n\taddr := cmd.String(\"addr\", \":8443\", \"address to serve on\")\n\tkubeconfig := cmd.String(\"kubeconfig\", \"\", \"path to kubeconfig\")\n\tlinkerdNamespace := cmd.String(\"linkerd-namespace\", \"linkerd\", \"control plane namespace\")\n\tenablePprof := cmd.Bool(\"enable-pprof\", false, \"Enable pprof endpoints on the admin server\")\n\tflags.ConfigureAndParse(cmd, args)\n\n\twebhook.Launch(\n\t\tcontext.Background(),\n\t\t[]k8s.APIResource{k8s.NS, k8s.Deploy, k8s.RC, k8s.RS, k8s.Job, k8s.DS, k8s.SS, k8s.Pod, k8s.CJ},\n\t\tinjector.Inject(*linkerdNamespace, inject.GetOverriddenValues),\n\t\t\"linkerd-proxy-injector\",\n\t\t*metricsAddr,\n\t\t*addr,\n\t\t*kubeconfig,\n\t\t*enablePprof,\n\t)\n}\n"
  },
  {
    "path": "controller/cmd/sp-validator/main.go",
    "content": "package spvalidator\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\n\tvalidator \"github.com/linkerd/linkerd2/controller/sp-validator\"\n\t\"github.com/linkerd/linkerd2/controller/webhook\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n)\n\n// Main executes the sp-validator subcommand\nfunc Main(args []string) {\n\tcmd := flag.NewFlagSet(\"sp-validator\", flag.ExitOnError)\n\tmetricsAddr := cmd.String(\"metrics-addr\", fmt.Sprintf(\":%d\", 9997), \"address to serve scrapable metrics on\")\n\taddr := cmd.String(\"addr\", \":8443\", \"address to serve on\")\n\tkubeconfig := cmd.String(\"kubeconfig\", \"\", \"path to kubeconfig\")\n\tenablePprof := cmd.Bool(\"enable-pprof\", false, \"Enable pprof endpoints on the admin server\")\n\tflags.ConfigureAndParse(cmd, args)\n\n\twebhook.Launch(\n\t\tcontext.Background(),\n\t\tnil,\n\t\tvalidator.AdmitSP,\n\t\t\"linkerd-sp-validator\",\n\t\t*metricsAddr,\n\t\t*addr,\n\t\t*kubeconfig,\n\t\t*enablePprof,\n\t)\n}\n"
  },
  {
    "path": "controller/gen/apis/externalworkload/register.go",
    "content": "package externalworkload\n\n// GroupName identifies the API Group name for an ExternalWorkload\nconst GroupName = \"workload.linkerd.io\"\n"
  },
  {
    "path": "controller/gen/apis/externalworkload/v1beta1/doc.go",
    "content": "// +k8s:deepcopy-gen=package\n\npackage v1beta1\n"
  },
  {
    "path": "controller/gen/apis/externalworkload/v1beta1/register.go",
    "content": "package v1beta1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload\"\n)\n\nvar (\n\t// SchemeGroupVersion is the identifier for the API which includes the name\n\t// of the group and the version of the API.\n\tSchemeGroupVersion = schema.GroupVersion{\n\t\tGroup:   externalworkload.GroupName,\n\t\tVersion: \"v1beta1\",\n\t}\n\n\t// SchemeBuilder collects functions that add things to a scheme. It's to\n\t// allow code to compile without explicitly referencing generated types.\n\t// You should declare one in each package that will have generated deep\n\t// copy or conversion functions.\n\tSchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)\n\n\t// AddToScheme applies all the stored functions to the scheme. A non-nil error\n\t// indicates that one function failed and the attempt was abandoned.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n\n// Kind takes an unqualified kind and returns back a Group qualified GroupKind\nfunc Kind(kind string) schema.GroupKind {\n\treturn SchemeGroupVersion.WithKind(kind).GroupKind()\n}\n\n// Resource takes an unqualified resource and returns a Group qualified\n// GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\n// Adds the list of known types to Scheme.\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&ExternalWorkload{},\n\t\t&ExternalWorkloadList{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "controller/gen/apis/externalworkload/v1beta1/types.go",
    "content": "package v1beta1\n\nimport (\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +genclient:noStatus\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +groupName=workload.linkerd.io\n\n// ExternalWorkload describes a single workload (i.e. a deployable unit,\n// conceptually similar to a Kubernetes Pod) that is running outside of a\n// Kubernetes cluster. An ExternalWorkload should be enrolled in the mesh and\n// typically represents a virtual machine.\ntype ExternalWorkload struct {\n\t// TypeMeta is the metadata for the resource, like kind and apiversion\n\tmetav1.TypeMeta `json:\",inline\"`\n\t// ObjectMeta contains the metadata for the particular object, including\n\t// things like...\n\t//  - name\n\t//  - namespace\n\t//  - self link\n\t//  - labels\n\t//  - ... etc ...\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Spec defines the desired state of an external workload instance\n\tSpec ExternalWorkloadSpec `json:\"spec\"`\n\n\t// Status defines the current state of an external workload instance\n\tStatus ExternalWorkloadStatus `json:\"status,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// ExternalWorkloadList contains a list of ExternalWorkload resources.\ntype ExternalWorkloadList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata\"`\n\n\tItems []ExternalWorkload `json:\"items\"`\n}\n\n// ExternalWorkloadSpec represents the desired state of an external workload\ntype ExternalWorkloadSpec struct {\n\t// MeshTls describes TLS settings associated with an external workload\n\tMeshTLS MeshTLS `json:\"meshTLS\"`\n\t// Ports describes a set of ports exposed by the workload\n\t//\n\t// +optional\n\tPorts []PortSpec `json:\"ports,omitempty\"`\n\t// List of IP addresses that can be used to send traffic to an external\n\t// workload\n\t//\n\t// +optional\n\tWorkloadIPs []WorkloadIP `json:\"workloadIPs,omitempty\"`\n}\n\n// MeshTls describes TLS settings associated with an external workload\ntype MeshTLS struct {\n\t// Identity associated with the workload. Used by peers to perform\n\t// verification in the mTLS handshake\n\tIdentity string `json:\"identity\"`\n\t// ServerName is the DNS formatted name associated with the workload. Used\n\t// to terminate TLS using the SNI extension.\n\tServerName string `json:\"serverName\"`\n}\n\n// PortSpec represents a network port in a single workload.\ntype PortSpec struct {\n\t// If specified, must be an IANA_SVC_NAME and unique within the exposed\n\t// ports set. Each named port must have a unique name. The name may be\n\t// referred to by services\n\t// +optional\n\tName string `json:\"name,omitempty\"`\n\t// Number of port exposed on the workload's IP address.\n\t// Must be a valid port number, i.e. 0 < x < 65536.\n\tPort int32 `json:\"port\"`\n\t// Protocol defines network protocols supported. One of UDP, TCP, or SCTP.\n\t// Should coincide with Service selecting the workload.\n\t// Defaults to \"TCP\" if unspecified.\n\t// +optional\n\t// +default=\"TCP\"\n\tProtocol v1.Protocol `json:\"protocol,omitempty\"`\n}\n\n// WorkloadIPs contains a list of IP addresses exposed by an ExternalWorkload\ntype WorkloadIP struct {\n\tIp string `json:\"ip\"`\n}\n\n// WorkloadStatus holds information about the status of an external workload.\n// The status describes the state of the workload.\ntype ExternalWorkloadStatus struct {\n\t// Current service state of an ExternalWorkload\n\t// +optional\n\tConditions []WorkloadCondition `json:\"conditions,omitempty\"`\n}\n\n// WorkloadCondition represents the service state of an ExternalWorkload\ntype WorkloadCondition struct {\n\t// Type of the condition\n\t// see: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions\n\tType WorkloadConditionType `json:\"type\"`\n\t// Status of the condition.\n\t// Can be True, False, Unknown\n\tStatus WorkloadConditionStatus `json:\"status\"`\n\t// Last time an ExternalWorkload was probed for a condition.\n\t// +optional\n\tLastProbeTime metav1.Time `json:\"lastProbeTime,omitempty\"`\n\t// Last time a condition transitioned from one status to another.\n\t// +optional\n\tLastTransitionTime metav1.Time `json:\"lastTransitionTime,omitempty\"`\n\t// Unique one word reason in CamelCase that describes the reason for a\n\t// transition.\n\t// +optional\n\tReason string `json:\"reason,omitempty\"`\n\t// Human readable message that describes details about last transition.\n\t// +optional\n\tMessage string `json:\"message,omitempty\"`\n}\n\n// WorkloadConditionType is a value for the type of a condition in an\n// ExternalWorkload's status\ntype WorkloadConditionType string\n\nconst (\n\t// Ready to serve traffic\n\tWorkloadReady WorkloadConditionType = \"Ready\"\n)\n\n// WorkloadConditionStatus\ntype WorkloadConditionStatus string\n\nconst (\n\tConditionTrue    WorkloadConditionStatus = \"True\"\n\tConditionFalse   WorkloadConditionStatus = \"False\"\n\tConditionUnknown WorkloadConditionStatus = \"Unknown\"\n)\n"
  },
  {
    "path": "controller/gen/apis/externalworkload/v1beta1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ExternalWorkload) DeepCopyInto(out *ExternalWorkload) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalWorkload.\nfunc (in *ExternalWorkload) DeepCopy() *ExternalWorkload {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ExternalWorkload)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *ExternalWorkload) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ExternalWorkloadList) DeepCopyInto(out *ExternalWorkloadList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]ExternalWorkload, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalWorkloadList.\nfunc (in *ExternalWorkloadList) DeepCopy() *ExternalWorkloadList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ExternalWorkloadList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *ExternalWorkloadList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ExternalWorkloadSpec) DeepCopyInto(out *ExternalWorkloadSpec) {\n\t*out = *in\n\tout.MeshTLS = in.MeshTLS\n\tif in.Ports != nil {\n\t\tin, out := &in.Ports, &out.Ports\n\t\t*out = make([]PortSpec, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.WorkloadIPs != nil {\n\t\tin, out := &in.WorkloadIPs, &out.WorkloadIPs\n\t\t*out = make([]WorkloadIP, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalWorkloadSpec.\nfunc (in *ExternalWorkloadSpec) DeepCopy() *ExternalWorkloadSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ExternalWorkloadSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ExternalWorkloadStatus) DeepCopyInto(out *ExternalWorkloadStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]WorkloadCondition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalWorkloadStatus.\nfunc (in *ExternalWorkloadStatus) DeepCopy() *ExternalWorkloadStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ExternalWorkloadStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MeshTLS) DeepCopyInto(out *MeshTLS) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshTLS.\nfunc (in *MeshTLS) DeepCopy() *MeshTLS {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MeshTLS)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *PortSpec) DeepCopyInto(out *PortSpec) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortSpec.\nfunc (in *PortSpec) DeepCopy() *PortSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(PortSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *WorkloadCondition) DeepCopyInto(out *WorkloadCondition) {\n\t*out = *in\n\tin.LastProbeTime.DeepCopyInto(&out.LastProbeTime)\n\tin.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadCondition.\nfunc (in *WorkloadCondition) DeepCopy() *WorkloadCondition {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(WorkloadCondition)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *WorkloadIP) DeepCopyInto(out *WorkloadIP) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadIP.\nfunc (in *WorkloadIP) DeepCopy() *WorkloadIP {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(WorkloadIP)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "controller/gen/apis/link/register.go",
    "content": "package link\n\n// GroupName identifies the API Group Name for a Server.\nconst GroupName = \"multicluster.linkerd.io\"\n"
  },
  {
    "path": "controller/gen/apis/link/v1alpha1/doc.go",
    "content": "// +k8s:deepcopy-gen=package\n\npackage v1alpha1\n"
  },
  {
    "path": "controller/gen/apis/link/v1alpha1/register.go",
    "content": "package v1alpha1\n\nimport (\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/link\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\nvar (\n\t// SchemeGroupVersion is the identifier for the API which includes the name\n\t// of the group and the version of the API.\n\tSchemeGroupVersion = schema.GroupVersion{\n\t\tGroup:   link.GroupName,\n\t\tVersion: \"v1alpha1\",\n\t}\n\n\t// SchemeBuilder collects functions that add things to a scheme. It's to\n\t// allow code to compile without explicitly referencing generated types.\n\t// You should declare one in each package that will have generated deep\n\t// copy or conversion functions.\n\tSchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)\n\n\t// AddToScheme applies all the stored functions to the scheme. A non-nil error\n\t// indicates that one function failed and the attempt was abandoned.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n\n// Kind takes an unqualified kind and returns back a Group qualified GroupKind\nfunc Kind(kind string) schema.GroupKind {\n\treturn SchemeGroupVersion.WithKind(kind).GroupKind()\n}\n\n// Resource takes an unqualified resource and returns a Group qualified\n// GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\n// Adds the list of known types to Scheme.\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&Link{},\n\t\t&LinkList{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "controller/gen/apis/link/v1alpha1/types.go",
    "content": "package v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +genclient:noStatus\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +groupName=multicluster.linkerd.io\n\ntype Link struct {\n\t// TypeMeta is the metadata for the resource, like kind and apiversion\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// ObjectMeta contains the metadata for the particular object, including\n\t// things like...\n\t//  - name\n\t//  - namespace\n\t//  - self link\n\t//  - labels\n\t//  - ... etc ...\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Spec is the custom resource spec\n\tSpec LinkSpec `json:\"spec\"`\n}\n\n// LinkSpec specifies a LinkSpec resource.\ntype LinkSpec struct {\n\tTargetClusterName             string               `json:\"targetClusterName,omitempty\"`\n\tTargetClusterDomain           string               `json:\"targetClusterDomain,omitempty\"`\n\tTargetClusterLinkerdNamespace string               `json:\"targetClusterLinkerdNamespace,omitempty\"`\n\tClusterCredentialsSecret      string               `json:\"clusterCredentialsSecret,omitempty\"`\n\tGatewayAddress                string               `json:\"gatewayAddress,omitempty\"`\n\tGatewayPort                   string               `json:\"gatewayPort,omitempty\"`\n\tGatewayIdentity               string               `json:\"gatewayIdentity,omitempty\"`\n\tProbeSpec                     ProbeSpec            `json:\"probeSpec,omitempty\"`\n\tSelector                      metav1.LabelSelector `json:\"selector,omitempty\"`\n\tRemoteDiscoverySelector       metav1.LabelSelector `json:\"remoteDiscoverySelector,omitempty\"`\n}\n\n// ProbeSpec for gateway health probe\ntype ProbeSpec struct {\n\tPath             string `json:\"path,omitempty\"`\n\tPort             string `json:\"port,omitempty\"`\n\tPeriod           string `json:\"period,omitempty\"`\n\tTimeout          string `json:\"timeout,omitempty\"`\n\tFailureThreshold string `json:\"failureThreshold,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// LinkList is a list of LinkList resources.\ntype LinkList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata\"`\n\n\tItems []Link `json:\"items\"`\n}\n"
  },
  {
    "path": "controller/gen/apis/link/v1alpha1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Link) DeepCopyInto(out *Link) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Link.\nfunc (in *Link) DeepCopy() *Link {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Link)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Link) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LinkList) DeepCopyInto(out *LinkList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Link, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkList.\nfunc (in *LinkList) DeepCopy() *LinkList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LinkList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *LinkList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LinkSpec) DeepCopyInto(out *LinkSpec) {\n\t*out = *in\n\tout.ProbeSpec = in.ProbeSpec\n\tin.Selector.DeepCopyInto(&out.Selector)\n\tin.RemoteDiscoverySelector.DeepCopyInto(&out.RemoteDiscoverySelector)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkSpec.\nfunc (in *LinkSpec) DeepCopy() *LinkSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LinkSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ProbeSpec) DeepCopyInto(out *ProbeSpec) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProbeSpec.\nfunc (in *ProbeSpec) DeepCopy() *ProbeSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ProbeSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "controller/gen/apis/link/v1alpha2/doc.go",
    "content": "// +k8s:deepcopy-gen=package\n\npackage v1alpha2\n"
  },
  {
    "path": "controller/gen/apis/link/v1alpha2/register.go",
    "content": "package v1alpha2\n\nimport (\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/link\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\nvar (\n\t// SchemeGroupVersion is the identifier for the API which includes the name\n\t// of the group and the version of the API.\n\tSchemeGroupVersion = schema.GroupVersion{\n\t\tGroup:   link.GroupName,\n\t\tVersion: \"v1alpha2\",\n\t}\n\n\t// SchemeBuilder collects functions that add things to a scheme. It's to\n\t// allow code to compile without explicitly referencing generated types.\n\t// You should declare one in each package that will have generated deep\n\t// copy or conversion functions.\n\tSchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)\n\n\t// AddToScheme applies all the stored functions to the scheme. A non-nil error\n\t// indicates that one function failed and the attempt was abandoned.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n\n// Kind takes an unqualified kind and returns back a Group qualified GroupKind\nfunc Kind(kind string) schema.GroupKind {\n\treturn SchemeGroupVersion.WithKind(kind).GroupKind()\n}\n\n// Resource takes an unqualified resource and returns a Group qualified\n// GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\n// Adds the list of known types to Scheme.\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&Link{},\n\t\t&LinkList{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "controller/gen/apis/link/v1alpha2/types.go",
    "content": "package v1alpha2\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +genclient:noStatus\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +groupName=multicluster.linkerd.io\n\ntype Link struct {\n\t// TypeMeta is the metadata for the resource, like kind and apiversion\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// ObjectMeta contains the metadata for the particular object, including\n\t// things like...\n\t//  - name\n\t//  - namespace\n\t//  - self link\n\t//  - labels\n\t//  - ... etc ...\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Spec is the custom resource spec\n\tSpec LinkSpec `json:\"spec\"`\n\n\t// Status defines the current state of a Link\n\tStatus LinkStatus `json:\"status,omitempty\"`\n}\n\n// LinkSpec specifies a LinkSpec resource.\ntype LinkSpec struct {\n\tTargetClusterName             string                `json:\"targetClusterName,omitempty\"`\n\tTargetClusterDomain           string                `json:\"targetClusterDomain,omitempty\"`\n\tTargetClusterLinkerdNamespace string                `json:\"targetClusterLinkerdNamespace,omitempty\"`\n\tClusterCredentialsSecret      string                `json:\"clusterCredentialsSecret,omitempty\"`\n\tGatewayAddress                string                `json:\"gatewayAddress,omitempty\"`\n\tGatewayPort                   string                `json:\"gatewayPort,omitempty\"`\n\tGatewayIdentity               string                `json:\"gatewayIdentity,omitempty\"`\n\tProbeSpec                     ProbeSpec             `json:\"probeSpec,omitempty\"`\n\tSelector                      *metav1.LabelSelector `json:\"selector,omitempty\"`\n\tRemoteDiscoverySelector       *metav1.LabelSelector `json:\"remoteDiscoverySelector,omitempty\"`\n\tFederatedServiceSelector      *metav1.LabelSelector `json:\"federatedServiceSelector,omitempty\"`\n}\n\n// ProbeSpec for gateway health probe\ntype ProbeSpec struct {\n\tPath             string `json:\"path,omitempty\"`\n\tPort             string `json:\"port,omitempty\"`\n\tPeriod           string `json:\"period,omitempty\"`\n\tTimeout          string `json:\"timeout,omitempty\"`\n\tFailureThreshold string `json:\"failureThreshold,omitempty\"`\n}\n\n// LinkStatus holds information about the status services mirrored with this\n// Link.\ntype LinkStatus struct {\n\t// +optional\n\tMirrorServices []ServiceStatus `json:\"mirrorServices,omitempty\"`\n\t// +optional\n\tFederatedServices []ServiceStatus `json:\"federatedServices,omitempty\"`\n}\n\ntype ServiceStatus struct {\n\tConditions     []LinkCondition `json:\"conditions,omitempty\"`\n\tControllerName string          `json:\"controllerName,omitempty\"`\n\tRemoteRef      ObjectRef       `json:\"remoteRef,omitempty\"`\n}\n\n// LinkCondition represents the service state of an ExternalWorkload\ntype LinkCondition struct {\n\t// Type of the condition\n\tType string `json:\"type\"`\n\t// Status of the condition.\n\t// Can be True, False, Unknown\n\tStatus metav1.ConditionStatus `json:\"status\"`\n\t// Last time an ExternalWorkload was probed for a condition.\n\t// +optional\n\tLastProbeTime metav1.Time `json:\"lastProbeTime,omitempty\"`\n\t// Last time a condition transitioned from one status to another.\n\t// +optional\n\tLastTransitionTime metav1.Time `json:\"lastTransitionTime,omitempty\"`\n\t// Unique one word reason in CamelCase that describes the reason for a\n\t// transition.\n\t// +optional\n\tReason string `json:\"reason,omitempty\"`\n\t// Human readable message that describes details about last transition.\n\t// +optional\n\tMessage string `json:\"message\"`\n\t// LocalRef is a reference to the local mirror or federated service.\n\tLocalRef ObjectRef `json:\"localRef,omitempty\"`\n}\n\ntype ObjectRef struct {\n\tGroup     string `json:\"group,omitempty\"`\n\tKind      string `json:\"kind,omitempty\"`\n\tName      string `json:\"name,omitempty\"`\n\tNamespace string `json:\"namespace,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// LinkList is a list of LinkList resources.\ntype LinkList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata\"`\n\n\tItems []Link `json:\"items\"`\n}\n"
  },
  {
    "path": "controller/gen/apis/link/v1alpha2/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1alpha2\n\nimport (\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Link) DeepCopyInto(out *Link) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Link.\nfunc (in *Link) DeepCopy() *Link {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Link)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Link) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LinkCondition) DeepCopyInto(out *LinkCondition) {\n\t*out = *in\n\tin.LastProbeTime.DeepCopyInto(&out.LastProbeTime)\n\tin.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)\n\tout.LocalRef = in.LocalRef\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkCondition.\nfunc (in *LinkCondition) DeepCopy() *LinkCondition {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LinkCondition)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LinkList) DeepCopyInto(out *LinkList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Link, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkList.\nfunc (in *LinkList) DeepCopy() *LinkList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LinkList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *LinkList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LinkSpec) DeepCopyInto(out *LinkSpec) {\n\t*out = *in\n\tout.ProbeSpec = in.ProbeSpec\n\tif in.Selector != nil {\n\t\tin, out := &in.Selector, &out.Selector\n\t\t*out = new(v1.LabelSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.RemoteDiscoverySelector != nil {\n\t\tin, out := &in.RemoteDiscoverySelector, &out.RemoteDiscoverySelector\n\t\t*out = new(v1.LabelSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.FederatedServiceSelector != nil {\n\t\tin, out := &in.FederatedServiceSelector, &out.FederatedServiceSelector\n\t\t*out = new(v1.LabelSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkSpec.\nfunc (in *LinkSpec) DeepCopy() *LinkSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LinkSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LinkStatus) DeepCopyInto(out *LinkStatus) {\n\t*out = *in\n\tif in.MirrorServices != nil {\n\t\tin, out := &in.MirrorServices, &out.MirrorServices\n\t\t*out = make([]ServiceStatus, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tif in.FederatedServices != nil {\n\t\tin, out := &in.FederatedServices, &out.FederatedServices\n\t\t*out = make([]ServiceStatus, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkStatus.\nfunc (in *LinkStatus) DeepCopy() *LinkStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LinkStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ObjectRef) DeepCopyInto(out *ObjectRef) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectRef.\nfunc (in *ObjectRef) DeepCopy() *ObjectRef {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ObjectRef)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ProbeSpec) DeepCopyInto(out *ProbeSpec) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProbeSpec.\nfunc (in *ProbeSpec) DeepCopy() *ProbeSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ProbeSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ServiceStatus) DeepCopyInto(out *ServiceStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]LinkCondition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tout.RemoteRef = in.RemoteRef\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceStatus.\nfunc (in *ServiceStatus) DeepCopy() *ServiceStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ServiceStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "controller/gen/apis/link/v1alpha3/doc.go",
    "content": "// +k8s:deepcopy-gen=package\n\npackage v1alpha3\n"
  },
  {
    "path": "controller/gen/apis/link/v1alpha3/register.go",
    "content": "package v1alpha3\n\nimport (\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/link\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\nvar (\n\t// SchemeGroupVersion is the identifier for the API which includes the name\n\t// of the group and the version of the API.\n\tSchemeGroupVersion = schema.GroupVersion{\n\t\tGroup:   link.GroupName,\n\t\tVersion: \"v1alpha3\",\n\t}\n\n\t// SchemeBuilder collects functions that add things to a scheme. It's to\n\t// allow code to compile without explicitly referencing generated types.\n\t// You should declare one in each package that will have generated deep\n\t// copy or conversion functions.\n\tSchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)\n\n\t// AddToScheme applies all the stored functions to the scheme. A non-nil error\n\t// indicates that one function failed and the attempt was abandoned.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n\n// Kind takes an unqualified kind and returns back a Group qualified GroupKind\nfunc Kind(kind string) schema.GroupKind {\n\treturn SchemeGroupVersion.WithKind(kind).GroupKind()\n}\n\n// Resource takes an unqualified resource and returns a Group qualified\n// GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\n// Adds the list of known types to Scheme.\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&Link{},\n\t\t&LinkList{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "controller/gen/apis/link/v1alpha3/types.go",
    "content": "package v1alpha3\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +genclient:noStatus\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +groupName=multicluster.linkerd.io\n\ntype Link struct {\n\t// TypeMeta is the metadata for the resource, like kind and apiversion\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// ObjectMeta contains the metadata for the particular object, including\n\t// things like...\n\t//  - name\n\t//  - namespace\n\t//  - self link\n\t//  - labels\n\t//  - ... etc ...\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Spec is the custom resource spec\n\tSpec LinkSpec `json:\"spec\"`\n\n\t// Status defines the current state of a Link\n\tStatus LinkStatus `json:\"status,omitempty\"`\n}\n\n// LinkSpec specifies a LinkSpec resource.\ntype LinkSpec struct {\n\tTargetClusterName             string                `json:\"targetClusterName,omitempty\"`\n\tTargetClusterDomain           string                `json:\"targetClusterDomain,omitempty\"`\n\tTargetClusterLinkerdNamespace string                `json:\"targetClusterLinkerdNamespace,omitempty\"`\n\tClusterCredentialsSecret      string                `json:\"clusterCredentialsSecret,omitempty\"`\n\tGatewayAddress                string                `json:\"gatewayAddress,omitempty\"`\n\tGatewayPort                   string                `json:\"gatewayPort,omitempty\"`\n\tGatewayIdentity               string                `json:\"gatewayIdentity,omitempty\"`\n\tProbeSpec                     ProbeSpec             `json:\"probeSpec,omitempty\"`\n\tSelector                      *metav1.LabelSelector `json:\"selector,omitempty\"`\n\tRemoteDiscoverySelector       *metav1.LabelSelector `json:\"remoteDiscoverySelector,omitempty\"`\n\tFederatedServiceSelector      *metav1.LabelSelector `json:\"federatedServiceSelector,omitempty\"`\n\tExcludedAnnotations           []string              `json:\"excludedAnnotations,omitempty\"`\n\tExcludedLabels                []string              `json:\"excludedLabels,omitempty\"`\n}\n\n// ProbeSpec for gateway health probe\ntype ProbeSpec struct {\n\tPath             string `json:\"path,omitempty\"`\n\tPort             string `json:\"port,omitempty\"`\n\tPeriod           string `json:\"period,omitempty\"`\n\tTimeout          string `json:\"timeout,omitempty\"`\n\tFailureThreshold string `json:\"failureThreshold,omitempty\"`\n}\n\n// LinkStatus holds information about the status services mirrored with this\n// Link.\ntype LinkStatus struct {\n\t// +optional\n\tMirrorServices []ServiceStatus `json:\"mirrorServices,omitempty\"`\n\t// +optional\n\tFederatedServices []ServiceStatus `json:\"federatedServices,omitempty\"`\n}\n\ntype ServiceStatus struct {\n\tConditions     []LinkCondition `json:\"conditions,omitempty\"`\n\tControllerName string          `json:\"controllerName,omitempty\"`\n\tRemoteRef      ObjectRef       `json:\"remoteRef,omitempty\"`\n}\n\n// LinkCondition represents the service state of an ExternalWorkload\ntype LinkCondition struct {\n\t// Type of the condition\n\tType string `json:\"type\"`\n\t// Status of the condition.\n\t// Can be True, False, Unknown\n\tStatus metav1.ConditionStatus `json:\"status\"`\n\t// Last time an ExternalWorkload was probed for a condition.\n\t// +optional\n\tLastProbeTime metav1.Time `json:\"lastProbeTime,omitempty\"`\n\t// Last time a condition transitioned from one status to another.\n\t// +optional\n\tLastTransitionTime metav1.Time `json:\"lastTransitionTime,omitempty\"`\n\t// Unique one word reason in CamelCase that describes the reason for a\n\t// transition.\n\t// +optional\n\tReason string `json:\"reason,omitempty\"`\n\t// Human readable message that describes details about last transition.\n\t// +optional\n\tMessage string `json:\"message\"`\n\t// LocalRef is a reference to the local mirror or federated service.\n\tLocalRef *ObjectRef `json:\"localRef,omitempty\"`\n}\n\ntype ObjectRef struct {\n\tGroup     string `json:\"group,omitempty\"`\n\tKind      string `json:\"kind,omitempty\"`\n\tName      string `json:\"name,omitempty\"`\n\tNamespace string `json:\"namespace,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// LinkList is a list of LinkList resources.\ntype LinkList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata\"`\n\n\tItems []Link `json:\"items\"`\n}\n"
  },
  {
    "path": "controller/gen/apis/link/v1alpha3/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1alpha3\n\nimport (\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Link) DeepCopyInto(out *Link) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Link.\nfunc (in *Link) DeepCopy() *Link {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Link)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Link) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LinkCondition) DeepCopyInto(out *LinkCondition) {\n\t*out = *in\n\tin.LastProbeTime.DeepCopyInto(&out.LastProbeTime)\n\tin.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)\n\tif in.LocalRef != nil {\n\t\tin, out := &in.LocalRef, &out.LocalRef\n\t\t*out = new(ObjectRef)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkCondition.\nfunc (in *LinkCondition) DeepCopy() *LinkCondition {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LinkCondition)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LinkList) DeepCopyInto(out *LinkList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Link, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkList.\nfunc (in *LinkList) DeepCopy() *LinkList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LinkList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *LinkList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LinkSpec) DeepCopyInto(out *LinkSpec) {\n\t*out = *in\n\tout.ProbeSpec = in.ProbeSpec\n\tif in.Selector != nil {\n\t\tin, out := &in.Selector, &out.Selector\n\t\t*out = new(v1.LabelSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.RemoteDiscoverySelector != nil {\n\t\tin, out := &in.RemoteDiscoverySelector, &out.RemoteDiscoverySelector\n\t\t*out = new(v1.LabelSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.FederatedServiceSelector != nil {\n\t\tin, out := &in.FederatedServiceSelector, &out.FederatedServiceSelector\n\t\t*out = new(v1.LabelSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.ExcludedAnnotations != nil {\n\t\tin, out := &in.ExcludedAnnotations, &out.ExcludedAnnotations\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.ExcludedLabels != nil {\n\t\tin, out := &in.ExcludedLabels, &out.ExcludedLabels\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkSpec.\nfunc (in *LinkSpec) DeepCopy() *LinkSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LinkSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LinkStatus) DeepCopyInto(out *LinkStatus) {\n\t*out = *in\n\tif in.MirrorServices != nil {\n\t\tin, out := &in.MirrorServices, &out.MirrorServices\n\t\t*out = make([]ServiceStatus, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tif in.FederatedServices != nil {\n\t\tin, out := &in.FederatedServices, &out.FederatedServices\n\t\t*out = make([]ServiceStatus, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkStatus.\nfunc (in *LinkStatus) DeepCopy() *LinkStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LinkStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ObjectRef) DeepCopyInto(out *ObjectRef) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectRef.\nfunc (in *ObjectRef) DeepCopy() *ObjectRef {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ObjectRef)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ProbeSpec) DeepCopyInto(out *ProbeSpec) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProbeSpec.\nfunc (in *ProbeSpec) DeepCopy() *ProbeSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ProbeSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ServiceStatus) DeepCopyInto(out *ServiceStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]LinkCondition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tout.RemoteRef = in.RemoteRef\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceStatus.\nfunc (in *ServiceStatus) DeepCopy() *ServiceStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ServiceStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "controller/gen/apis/policy/register.go",
    "content": "package policy\n\n// GroupName identifies the API Group Name for an AuthorizationPolicy.\nconst GroupName = \"policy.linkerd.io\"\n"
  },
  {
    "path": "controller/gen/apis/policy/v1alpha1/doc.go",
    "content": "// +k8s:deepcopy-gen=package\n\npackage v1alpha1\n"
  },
  {
    "path": "controller/gen/apis/policy/v1alpha1/httproute.go",
    "content": "/*\nAdapted from https://github.com/kubernetes-sigs/gateway-api/blob/main/apis/v1alpha2/httproute_types.go\nCopyright 2020 The Kubernetes Authors.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tgatewayapiv1alpha2 \"sigs.k8s.io/gateway-api/apis/v1alpha2\"\n)\n\n// +genclient\n// +genclient:noStatus\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +groupName=policy.linkerd.io\n\n// HTTPRoute provides a way to route HTTP requests. This includes the capability\n// to match requests by hostname, path, header, or query param. Filters can be\n// used to specify additional processing steps. Backends specify where matching\n// requests should be routed.\ntype HTTPRoute struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Spec defines the desired state of HTTPRoute.\n\tSpec HTTPRouteSpec `json:\"spec\"`\n\n\t// Status defines the current state of HTTPRoute.\n\tStatus HTTPRouteStatus `json:\"status,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// HTTPRouteList contains a list of HTTPRoute.\ntype HTTPRouteList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitempty\"`\n\tItems           []HTTPRoute `json:\"items\"`\n}\n\n// HTTPRouteSpec defines the desired state of HTTPRoute\ntype HTTPRouteSpec struct {\n\tgatewayapiv1alpha2.CommonRouteSpec `json:\",inline\"`\n\n\t// Hostnames defines a set of hostname that should match against the HTTP\n\t// Host header to select a HTTPRoute to process the request. This matches\n\t// the RFC 1123 definition of a hostname with 2 notable exceptions:\n\t//\n\t// 1. IPs are not allowed.\n\t// 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n\t//    label must appear by itself as the first label.\n\t//\n\t// If a hostname is specified by both the Listener and HTTPRoute, there\n\t// must be at least one intersecting hostname for the HTTPRoute to be\n\t// attached to the Listener. For example:\n\t//\n\t// * A Listener with `test.example.com` as the hostname matches HTTPRoutes\n\t//   that have either not specified any hostnames, or have specified at\n\t//   least one of `test.example.com` or `*.example.com`.\n\t// * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n\t//   that have either not specified any hostnames or have specified at least\n\t//   one hostname that matches the Listener hostname. For example,\n\t//   `*.example.com`, `test.example.com`, and `foo.test.example.com` would\n\t//   all match. On the other hand, `example.com` and `test.example.net` would\n\t//   not match.\n\t//\n\t// Hostnames that are prefixed with a wildcard label (`*.`) are interpreted\n\t// as a suffix match. That means that a match for `*.example.com` would match\n\t// both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\t//\n\t// If both the Listener and HTTPRoute have specified hostnames, any\n\t// HTTPRoute hostnames that do not match the Listener hostname MUST be\n\t// ignored. For example, if a Listener specified `*.example.com`, and the\n\t// HTTPRoute specified `test.example.com` and `test.example.net`,\n\t// `test.example.net` must not be considered for a match.\n\t//\n\t// If both the Listener and HTTPRoute have specified hostnames, and none\n\t// match with the criteria above, then the HTTPRoute is not accepted. The\n\t// implementation must raise an 'Accepted' Condition with a status of\n\t// `False` in the corresponding RouteParentStatus.\n\t//\n\t// Support: Core\n\t//\n\t// +optional\n\t// +kubebuilder:validation:MaxItems=16\n\tHostnames []gatewayapiv1alpha2.Hostname `json:\"hostnames,omitempty\"`\n\n\t// Rules are a list of HTTP matchers, filters and actions.\n\t//\n\t// +optional\n\t// +kubebuilder:validation:MaxItems=16\n\t// +kubebuilder:default={{matches: {{path: {type: \"PathPrefix\", value: \"/\"}}}}}\n\tRules []HTTPRouteRule `json:\"rules,omitempty\"`\n}\n\n// HTTPRouteRule defines semantics for matching an HTTP request based on\n// conditions (matches), processing it (filters), and forwarding the request to\n// an API object (backendRefs).\ntype HTTPRouteRule struct {\n\t// Matches define conditions used for matching the rule against incoming\n\t// HTTP requests. Each match is independent, i.e. this rule will be matched\n\t// if **any** one of the matches is satisfied.\n\t//\n\t// For example, take the following matches configuration:\n\t//\n\t// ```\n\t// matches:\n\t// - path:\n\t//     value: \"/foo\"\n\t//   headers:\n\t//   - name: \"version\"\n\t//     value: \"v2\"\n\t// - path:\n\t//     value: \"/v2/foo\"\n\t// ```\n\t//\n\t// For a request to match against this rule, a request must satisfy\n\t// EITHER of the two conditions:\n\t//\n\t// - path prefixed with `/foo` AND contains the header `version: v2`\n\t// - path prefix of `/v2/foo`\n\t//\n\t// See the documentation for HTTPRouteMatch on how to specify multiple\n\t// match conditions that should be ANDed together.\n\t//\n\t// If no matches are specified, the default is a prefix\n\t// path match on \"/\", which has the effect of matching every\n\t// HTTP request.\n\t//\n\t// Proxy or Load Balancer routing configuration generated from HTTPRoutes\n\t// MUST prioritize rules based on the following criteria, continuing on\n\t// ties. Precedence must be given to the Rule with the largest number\n\t// of:\n\t//\n\t// * Characters in a matching non-wildcard hostname.\n\t// * Characters in a matching hostname.\n\t// * Characters in a matching path.\n\t// * Header matches.\n\t// * Query param matches.\n\t//\n\t// If ties still exist across multiple Routes, matching precedence MUST be\n\t// determined in order of the following criteria, continuing on ties:\n\t//\n\t// * The oldest Route based on creation timestamp.\n\t// * The Route appearing first in alphabetical order by\n\t//   \"{namespace}/{name}\".\n\t//\n\t// If ties still exist within the Route that has been given precedence,\n\t// matching precedence MUST be granted to the first matching rule meeting\n\t// the above criteria.\n\t//\n\t// When no rules matching a request have been successfully attached to the\n\t// parent a request is coming from, a HTTP 404 status code MUST be returned.\n\t//\n\t// +optional\n\t// +kubebuilder:validation:MaxItems=8\n\t// +kubebuilder:default={{path:{ type: \"PathPrefix\", value: \"/\"}}}\n\tMatches []HTTPRouteMatch `json:\"matches,omitempty\"`\n\n\t// Filters define the filters that are applied to requests that match\n\t// this rule.\n\t//\n\t// The effects of ordering of multiple behaviors are currently unspecified.\n\t// This can change in the future based on feedback during the alpha stage.\n\t//\n\t// Conformance-levels at this level are defined based on the type of filter:\n\t//\n\t// - ALL core filters MUST be supported by all implementations.\n\t// - Implementers are encouraged to support extended filters.\n\t// - Implementation-specific custom filters have no API guarantees across\n\t//   implementations.\n\t//\n\t// Specifying a core filter multiple times has unspecified or custom\n\t// conformance.\n\t//\n\t// All filters are expected to be compatible with each other except for the\n\t// URLRewrite and RequestRedirect filters, which may not be combined. If an\n\t// implementation can not support other combinations of filters, they must clearly\n\t// document that limitation. In all cases where incompatible or unsupported\n\t// filters are specified, implementations MUST add a warning condition to status.\n\t//\n\t// Support: Core\n\t//\n\t// +optional\n\t// +kubebuilder:validation:MaxItems=16\n\tFilters []HTTPRouteFilter `json:\"filters,omitempty\"`\n}\n\n// PathMatchType specifies the semantics of how HTTP paths should be compared.\n// Valid PathMatchType values are:\n//\n// * \"Exact\"\n// * \"PathPrefix\"\n// * \"RegularExpression\"\n//\n// PathPrefix and Exact paths must be syntactically valid:\n//\n// - Must begin with the `/` character\n// - Must not contain consecutive `/` characters (e.g. `/foo///`, `//`).\n//\n// Note that values may be added to this enum, implementations\n// must ensure that unknown values will not cause a crash.\n//\n// Unknown values here must result in the implementation setting the\n// Attached Condition for the Route to `status: False`, with a\n// Reason of `UnsupportedValue`.\n//\n// +kubebuilder:validation:Enum=Exact;PathPrefix;RegularExpression\ntype PathMatchType string\n\nconst (\n\t// Matches the URL path exactly and with case sensitivity.\n\tPathMatchExact PathMatchType = \"Exact\"\n\n\t// Matches based on a URL path prefix split by `/`. Matching is\n\t// case sensitive and done on a path element by element basis. A\n\t// path element refers to the list of labels in the path split by\n\t// the `/` separator. When specified, a trailing `/` is ignored.\n\t//\n\t// For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match\n\t// the prefix `/abc`, but the path `/abcd` would not.\n\t//\n\t// \"PathPrefix\" is semantically equivalent to the \"Prefix\" path type in the\n\t// Kubernetes Ingress API.\n\tPathMatchPathPrefix PathMatchType = \"PathPrefix\"\n\n\t// Matches if the URL path matches the given regular expression with\n\t// case sensitivity.\n\t//\n\t// Since `\"RegularExpression\"` has custom conformance, implementations\n\t// can support POSIX, PCRE, RE2 or any other regular expression dialect.\n\t// Please read the implementation's documentation to determine the supported\n\t// dialect.\n\tPathMatchRegularExpression PathMatchType = \"RegularExpression\"\n)\n\n// HTTPPathMatch describes how to select a HTTP route by matching the HTTP request path.\ntype HTTPPathMatch struct {\n\t// Type specifies how to match against the path Value.\n\t//\n\t// Support: Core (Exact, PathPrefix)\n\t//\n\t// Support: Custom (RegularExpression)\n\t//\n\t// +optional\n\t// +kubebuilder:default=PathPrefix\n\tType *PathMatchType `json:\"type,omitempty\"`\n\n\t// Value of the HTTP path to match against.\n\t//\n\t// +optional\n\t// +kubebuilder:default=\"/\"\n\t// +kubebuilder:validation:MaxLength=1024\n\tValue *string `json:\"value,omitempty\"`\n}\n\n// HeaderMatchType specifies the semantics of how HTTP header values should be\n// compared. Valid HeaderMatchType values are:\n//\n// * \"Exact\"\n// * \"RegularExpression\"\n//\n// Note that values may be added to this enum, implementations\n// must ensure that unknown values will not cause a crash.\n//\n// Unknown values here must result in the implementation setting the\n// Attached Condition for the Route to `status: False`, with a\n// Reason of `UnsupportedValue`.\n//\n// +kubebuilder:validation:Enum=Exact;RegularExpression\ntype HeaderMatchType string\n\n// HeaderMatchType constants.\nconst (\n\tHeaderMatchExact             HeaderMatchType = \"Exact\"\n\tHeaderMatchRegularExpression HeaderMatchType = \"RegularExpression\"\n)\n\n// HTTPHeaderName is the name of an HTTP header.\n//\n// Valid values include:\n//\n// * \"Authorization\"\n// * \"Set-Cookie\"\n//\n// Invalid values include:\n//\n// * \":method\" - \":\" is an invalid character. This means that HTTP/2 pseudo\n//   headers are not currently supported by this type.\n// * \"/invalid\" - \"/\" is an invalid character\n//\n// +kubebuilder:validation:MinLength=1\n// +kubebuilder:validation:MaxLength=256\n// +kubebuilder:validation:Pattern=`^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$`\ntype HTTPHeaderName string\n\n// HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request\n// headers.\ntype HTTPHeaderMatch struct {\n\t// Type specifies how to match against the value of the header.\n\t//\n\t// Support: Core (Exact)\n\t//\n\t// Support: Custom (RegularExpression)\n\t//\n\t// Since RegularExpression HeaderMatchType has custom conformance,\n\t// implementations can support POSIX, PCRE or any other dialects of regular\n\t// expressions. Please read the implementation's documentation to determine\n\t// the supported dialect.\n\t//\n\t// +optional\n\t// +kubebuilder:default=Exact\n\tType *HeaderMatchType `json:\"type,omitempty\"`\n\n\t// Name is the name of the HTTP Header to be matched. Name matching MUST be\n\t// case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\t//\n\t// If multiple entries specify equivalent header names, only the first\n\t// entry with an equivalent name MUST be considered for a match. Subsequent\n\t// entries with an equivalent header name MUST be ignored. Due to the\n\t// case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n\t// equivalent.\n\t//\n\t// When a header is repeated in an HTTP request, it is\n\t// implementation-specific behavior as to how this is represented.\n\t// Generally, proxies should follow the guidance from the RFC:\n\t// https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding\n\t// processing a repeated header, with special handling for \"Set-Cookie\".\n\tName HTTPHeaderName `json:\"name\"`\n\n\t// Value is the value of HTTP Header to be matched.\n\t//\n\t// +kubebuilder:validation:MinLength=1\n\t// +kubebuilder:validation:MaxLength=4096\n\tValue string `json:\"value\"`\n}\n\n// QueryParamMatchType specifies the semantics of how HTTP query parameter\n// values should be compared. Valid QueryParamMatchType values are:\n//\n// * \"Exact\"\n// * \"RegularExpression\"\n//\n// Note that values may be added to this enum, implementations\n// must ensure that unknown values will not cause a crash.\n//\n// Unknown values here must result in the implementation setting the\n// Attached Condition for the Route to `status: False`, with a\n// Reason of `UnsupportedValue`.\n//\n// +kubebuilder:validation:Enum=Exact;RegularExpression\ntype QueryParamMatchType string\n\n// QueryParamMatchType constants.\nconst (\n\tQueryParamMatchExact             QueryParamMatchType = \"Exact\"\n\tQueryParamMatchRegularExpression QueryParamMatchType = \"RegularExpression\"\n)\n\n// HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP\n// query parameters.\ntype HTTPQueryParamMatch struct {\n\t// Type specifies how to match against the value of the query parameter.\n\t//\n\t// Support: Extended (Exact)\n\t//\n\t// Support: Custom (RegularExpression)\n\t//\n\t// Since RegularExpression QueryParamMatchType has custom conformance,\n\t// implementations can support POSIX, PCRE or any other dialects of regular\n\t// expressions. Please read the implementation's documentation to determine\n\t// the supported dialect.\n\t//\n\t// +optional\n\t// +kubebuilder:default=Exact\n\tType *QueryParamMatchType `json:\"type,omitempty\"`\n\n\t// Name is the name of the HTTP query param to be matched. This must be an\n\t// exact string match. (See\n\t// https://tools.ietf.org/html/rfc7230#section-2.7.3).\n\t//\n\t// If multiple entries specify equivalent query param names, only the first\n\t// entry with an equivalent name MUST be considered for a match. Subsequent\n\t// entries with an equivalent query param name MUST be ignored.\n\t//\n\t// +kubebuilder:validation:MinLength=1\n\t// +kubebuilder:validation:MaxLength=256\n\tName string `json:\"name\"`\n\n\t// Value is the value of HTTP query param to be matched.\n\t//\n\t// +kubebuilder:validation:MinLength=1\n\t// +kubebuilder:validation:MaxLength=1024\n\tValue string `json:\"value\"`\n}\n\n// HTTPMethod describes how to select a HTTP route by matching the HTTP\n// method as defined by\n// [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231#section-4) and\n// [RFC 5789](https://datatracker.ietf.org/doc/html/rfc5789#section-2).\n// The value is expected in upper case.\n//\n// Note that values may be added to this enum, implementations\n// must ensure that unknown values will not cause a crash.\n//\n// Unknown values here must result in the implementation setting the\n// Attached Condition for the Route to `status: False`, with a\n// Reason of `UnsupportedValue`.\n//\n// +kubebuilder:validation:Enum=GET;HEAD;POST;PUT;DELETE;CONNECT;OPTIONS;TRACE;PATCH\ntype HTTPMethod string\n\nconst (\n\tHTTPMethodGet     HTTPMethod = \"GET\"\n\tHTTPMethodHead    HTTPMethod = \"HEAD\"\n\tHTTPMethodPost    HTTPMethod = \"POST\"\n\tHTTPMethodPut     HTTPMethod = \"PUT\"\n\tHTTPMethodDelete  HTTPMethod = \"DELETE\"\n\tHTTPMethodConnect HTTPMethod = \"CONNECT\"\n\tHTTPMethodOptions HTTPMethod = \"OPTIONS\"\n\tHTTPMethodTrace   HTTPMethod = \"TRACE\"\n\tHTTPMethodPatch   HTTPMethod = \"PATCH\"\n)\n\n// HTTPRouteMatch defines the predicate used to match requests to a given\n// action. Multiple match types are ANDed together, i.e. the match will\n// evaluate to true only if all conditions are satisfied.\n//\n// For example, the match below will match a HTTP request only if its path\n// starts with `/foo` AND it contains the `version: v1` header:\n//\n// ```\n// match:\n//   path:\n//     value: \"/foo\"\n//   headers:\n//   - name: \"version\"\n//     value \"v1\"\n// ```\ntype HTTPRouteMatch struct {\n\t// Path specifies a HTTP request path matcher. If this field is not\n\t// specified, a default prefix match on the \"/\" path is provided.\n\t//\n\t// +optional\n\t// +kubebuilder:default={type: \"PathPrefix\", value: \"/\"}\n\tPath *HTTPPathMatch `json:\"path,omitempty\"`\n\n\t// Headers specifies HTTP request header matchers. Multiple match values are\n\t// ANDed together, meaning, a request must match all the specified headers\n\t// to select the route.\n\t//\n\t// +listType=map\n\t// +listMapKey=name\n\t// +optional\n\t// +kubebuilder:validation:MaxItems=16\n\tHeaders []HTTPHeaderMatch `json:\"headers,omitempty\"`\n\n\t// QueryParams specifies HTTP query parameter matchers. Multiple match\n\t// values are ANDed together, meaning, a request must match all the\n\t// specified query parameters to select the route.\n\t//\n\t// +listType=map\n\t// +listMapKey=name\n\t// +optional\n\t// +kubebuilder:validation:MaxItems=16\n\tQueryParams []HTTPQueryParamMatch `json:\"queryParams,omitempty\"`\n\n\t// Method specifies HTTP method matcher.\n\t// When specified, this route will be matched only if the request has the\n\t// specified method.\n\t//\n\t// Support: Extended\n\t//\n\t// +optional\n\tMethod *HTTPMethod `json:\"method,omitempty\"`\n}\n\n// HTTPRouteFilter defines processing steps that must be completed during the\n// request or response lifecycle. HTTPRouteFilters are meant as an extension\n// point to express processing that may be done in Gateway implementations. Some\n// examples include request or response modification, implementing\n// authentication strategies, rate-limiting, and traffic shaping. API\n// guarantee/conformance is defined based on the type of the filter.\ntype HTTPRouteFilter struct {\n\t// Type identifies the type of filter to apply. As with other API fields,\n\t// types are classified into three conformance levels:\n\t//\n\t// - Core: Filter types and their corresponding configuration defined by\n\t//   \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n\t//   implementations must support core filters.\n\t//\n\t// - Extended: Filter types and their corresponding configuration defined by\n\t//   \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n\t//   are encouraged to support extended filters.\n\t//\n\t// - Custom: Filters that are defined and supported by specific vendors.\n\t//   In the future, filters showing convergence in behavior across multiple\n\t//   implementations will be considered for inclusion in extended or core\n\t//   conformance levels. Filter-specific configuration for such filters\n\t//   is specified using the ExtensionRef field. `Type` should be set to\n\t//   \"ExtensionRef\" for custom filters.\n\t//\n\t// Implementers are encouraged to define custom implementation types to\n\t// extend the core API with implementation-specific behavior.\n\t//\n\t// If a reference to a custom filter type cannot be resolved, the filter\n\t// MUST NOT be skipped. Instead, requests that would have been processed by\n\t// that filter MUST receive a HTTP error response.\n\t//\n\t// Note that values may be added to this enum, implementations\n\t// must ensure that unknown values will not cause a crash.\n\t//\n\t// Unknown values here must result in the implementation setting the\n\t// Attached Condition for the Route to `status: False`, with a\n\t// Reason of `UnsupportedValue`.\n\t//\n\t// +unionDiscriminator\n\t// +kubebuilder:validation:Enum=RequestHeaderModifier;RequestMirror;RequestRedirect;ExtensionRef\n\t// <gateway:experimental:validation:Enum=RequestHeaderModifier;RequestMirror;RequestRedirect;URLRewrite;ExtensionRef>\n\tType HTTPRouteFilterType `json:\"type\"`\n\n\t// RequestHeaderModifier defines a schema for a filter that modifies request\n\t// headers.\n\t//\n\t// Support: Core\n\t//\n\t// +optional\n\tRequestHeaderModifier *HTTPRequestHeaderFilter `json:\"requestHeaderModifier,omitempty\"`\n\n\t// RequestRedirect defines a schema for a filter that responds to the\n\t// request with an HTTP redirection.\n\t//\n\t// Support: Core\n\t//\n\t// +optional\n\tRequestRedirect *HTTPRequestRedirectFilter `json:\"requestRedirect,omitempty\"`\n}\n\n// HTTPRouteFilterType identifies a type of HTTPRoute filter.\ntype HTTPRouteFilterType string\n\nconst (\n\t// HTTPRouteFilterRequestHeaderModifier can be used to add or remove an HTTP\n\t// header from an HTTP request before it is sent to the upstream target.\n\t//\n\t// Support in HTTPRouteRule: Core\n\t//\n\t// Support in HTTPBackendRef: Extended\n\tHTTPRouteFilterRequestHeaderModifier HTTPRouteFilterType = \"RequestHeaderModifier\"\n\n\t// HTTPRouteFilterRequestRedirect can be used to redirect a request to\n\t// another location. This filter can also be used for HTTP to HTTPS\n\t// redirects. This may not be used on the same Route rule or BackendRef as a\n\t// URLRewrite filter.\n\t//\n\t// Support in HTTPRouteRule: Core\n\t//\n\t// Support in HTTPBackendRef: Extended\n\tHTTPRouteFilterRequestRedirect HTTPRouteFilterType = \"RequestRedirect\"\n)\n\n// HTTPHeader represents an HTTP Header name and value as defined by RFC 7230.\ntype HTTPHeader struct {\n\t// Name is the name of the HTTP Header to be matched. Name matching MUST be\n\t// case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\t//\n\t// If multiple entries specify equivalent header names, the first entry with\n\t// an equivalent name MUST be considered for a match. Subsequent entries\n\t// with an equivalent header name MUST be ignored. Due to the\n\t// case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n\t// equivalent.\n\tName HTTPHeaderName `json:\"name\"`\n\n\t// Value is the value of HTTP Header to be matched.\n\t//\n\t// +kubebuilder:validation:MinLength=1\n\t// +kubebuilder:validation:MaxLength=4096\n\tValue string `json:\"value\"`\n}\n\n// HTTPRequestHeaderFilter defines configuration for the RequestHeaderModifier\n// filter.\ntype HTTPRequestHeaderFilter struct {\n\t// Set overwrites the request with the given header (name, value)\n\t// before the action.\n\t//\n\t// Input:\n\t//   GET /foo HTTP/1.1\n\t//   my-header: foo\n\t//\n\t// Config:\n\t//   set:\n\t//   - name: \"my-header\"\n\t//     value: \"bar\"\n\t//\n\t// Output:\n\t//   GET /foo HTTP/1.1\n\t//   my-header: bar\n\t//\n\t// +optional\n\t// +listType=map\n\t// +listMapKey=name\n\t// +kubebuilder:validation:MaxItems=16\n\tSet []HTTPHeader `json:\"set,omitempty\"`\n\n\t// Add adds the given header(s) (name, value) to the request\n\t// before the action. It appends to any existing values associated\n\t// with the header name.\n\t//\n\t// Input:\n\t//   GET /foo HTTP/1.1\n\t//   my-header: foo\n\t//\n\t// Config:\n\t//   add:\n\t//   - name: \"my-header\"\n\t//     value: \"bar\"\n\t//\n\t// Output:\n\t//   GET /foo HTTP/1.1\n\t//   my-header: foo\n\t//   my-header: bar\n\t//\n\t// +optional\n\t// +listType=map\n\t// +listMapKey=name\n\t// +kubebuilder:validation:MaxItems=16\n\tAdd []HTTPHeader `json:\"add,omitempty\"`\n\n\t// Remove the given header(s) from the HTTP request before the action. The\n\t// value of Remove is a list of HTTP header names. Note that the header\n\t// names are case-insensitive (see\n\t// https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\t//\n\t// Input:\n\t//   GET /foo HTTP/1.1\n\t//   my-header1: foo\n\t//   my-header2: bar\n\t//   my-header3: baz\n\t//\n\t// Config:\n\t//   remove: [\"my-header1\", \"my-header3\"]\n\t//\n\t// Output:\n\t//   GET /foo HTTP/1.1\n\t//   my-header2: bar\n\t//\n\t// +optional\n\t// +kubebuilder:validation:MaxItems=16\n\tRemove []string `json:\"remove,omitempty\"`\n}\n\n// HTTPPathModifierType defines the type of path redirect or rewrite.\ntype HTTPPathModifierType string\n\n// HTTPPathModifier defines configuration for path modifiers.\n// <gateway:experimental>\ntype HTTPPathModifier struct {\n\t// Type defines the type of path modifier. Additional types may be\n\t// added in a future release of the API.\n\t//\n\t// Note that values may be added to this enum, implementations\n\t// must ensure that unknown values will not cause a crash.\n\t//\n\t// Unknown values here must result in the implementation setting the\n\t// Attached Condition for the Route to `status: False`, with a\n\t// Reason of `UnsupportedValue`.\n\t//\n\t// <gateway:experimental>\n\t// +kubebuilder:validation:Enum=ReplaceFullPath;ReplacePrefixMatch\n\tType HTTPPathModifierType `json:\"type\"`\n\n\t// ReplaceFullPath specifies the value with which to replace the full path\n\t// of a request during a rewrite or redirect.\n\t//\n\t// <gateway:experimental>\n\t// +kubebuilder:validation:MaxLength=1024\n\t// +optional\n\tReplaceFullPath *string `json:\"replaceFullPath,omitempty\"`\n\n\t// ReplacePrefixMatch specifies the value with which to replace the prefix\n\t// match of a request during a rewrite or redirect. For example, a request\n\t// to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\".\n\t//\n\t// Note that this matches the behavior of the PathPrefix match type. This\n\t// matches full path elements. A path element refers to the list of labels\n\t// in the path split by the `/` separator. When specified, a trailing `/` is\n\t// ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n\t// match the prefix `/abc`, but the path `/abcd` would not.\n\t//\n\t// <gateway:experimental>\n\t// +kubebuilder:validation:MaxLength=1024\n\t// +optional\n\tReplacePrefixMatch *string `json:\"replacePrefixMatch,omitempty\"`\n}\n\n// HTTPRequestRedirect defines a filter that redirects a request. This filter\n// MUST NOT be used on the same Route rule as a HTTPURLRewrite filter.\ntype HTTPRequestRedirectFilter struct {\n\t// Scheme is the scheme to be used in the value of the `Location`\n\t// header in the response.\n\t// When empty, the scheme of the request is used.\n\t//\n\t// Support: Extended\n\t//\n\t// Note that values may be added to this enum, implementations\n\t// must ensure that unknown values will not cause a crash.\n\t//\n\t// Unknown values here must result in the implementation setting the\n\t// Attached Condition for the Route to `status: False`, with a\n\t// Reason of `UnsupportedValue`.\n\t//\n\t// +optional\n\t// +kubebuilder:validation:Enum=http;https\n\tScheme *string `json:\"scheme,omitempty\"`\n\n\t// Hostname is the hostname to be used in the value of the `Location`\n\t// header in the response.\n\t// When empty, the hostname of the request is used.\n\t//\n\t// Support: Core\n\t//\n\t// +optional\n\tHostname *gatewayapiv1alpha2.PreciseHostname `json:\"hostname,omitempty\"`\n\n\t// Path defines parameters used to modify the path of the incoming request.\n\t// The modified path is then used to construct the `Location` header. When\n\t// empty, the request path is used as-is.\n\t//\n\t// Support: Extended\n\t//\n\t// <gateway:experimental>\n\t// +optional\n\tPath *HTTPPathModifier `json:\"path,omitempty\"`\n\n\t// Port is the port to be used in the value of the `Location`\n\t// header in the response.\n\t// When empty, port (if specified) of the request is used.\n\t//\n\t// Support: Extended\n\t//\n\t// +optional\n\tPort *gatewayapiv1alpha2.PortNumber `json:\"port,omitempty\"`\n\n\t// StatusCode is the HTTP status code to be used in response.\n\t//\n\t// Support: Core\n\t//\n\t// Note that values may be added to this enum, implementations\n\t// must ensure that unknown values will not cause a crash.\n\t//\n\t// Unknown values here must result in the implementation setting the\n\t// Attached Condition for the Route to `status: False`, with a\n\t// Reason of `UnsupportedValue`.\n\t//\n\t// +optional\n\t// +kubebuilder:default=302\n\t// +kubebuilder:validation:Enum=301;302\n\tStatusCode *int `json:\"statusCode,omitempty\"`\n}\n\n// HTTPRouteStatus defines the observed state of HTTPRoute.\ntype HTTPRouteStatus struct {\n\tgatewayapiv1alpha2.RouteStatus `json:\",inline\"`\n}\n"
  },
  {
    "path": "controller/gen/apis/policy/v1alpha1/register.go",
    "content": "package v1alpha1\n\nimport (\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/policy\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\nvar (\n\t// SchemeGroupVersion is the identifier for the API which includes the name\n\t// of the group and the version of the API.\n\tSchemeGroupVersion = schema.GroupVersion{\n\t\tGroup:   policy.GroupName,\n\t\tVersion: \"v1alpha1\",\n\t}\n\n\t// SchemeBuilder collects functions that add things to a scheme. It's to\n\t// allow code to compile without explicitly referencing generated types.\n\t// You should declare one in each package that will have generated deep\n\t// copy or conversion functions.\n\tSchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)\n\n\t// AddToScheme applies all the stored functions to the scheme. A non-nil error\n\t// indicates that one function failed and the attempt was abandoned.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n\n// Kind takes an unqualified kind and returns back a Group qualified GroupKind\nfunc Kind(kind string) schema.GroupKind {\n\treturn SchemeGroupVersion.WithKind(kind).GroupKind()\n}\n\n// Resource takes an unqualified resource and returns a Group qualified\n// GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\n// Adds the list of known types to Scheme.\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&AuthorizationPolicy{},\n\t\t&AuthorizationPolicyList{},\n\t\t&HTTPRoute{},\n\t\t&HTTPRouteList{},\n\t\t&MeshTLSAuthentication{},\n\t\t&MeshTLSAuthenticationList{},\n\t\t&NetworkAuthentication{},\n\t\t&NetworkAuthenticationList{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "controller/gen/apis/policy/v1alpha1/types.go",
    "content": "package v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tgatewayapiv1alpha2 \"sigs.k8s.io/gateway-api/apis/v1alpha2\"\n)\n\n// +genclient\n// +genclient:noStatus\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +groupName=policy.linkerd.io\n\ntype AuthorizationPolicy struct {\n\t// TypeMeta is the metadata for the resource, like kind and apiversion\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// ObjectMeta contains the metadata for the particular object, including\n\t// things like...\n\t//  - name\n\t//  - namespace\n\t//  - self link\n\t//  - labels\n\t//  - ... etc ...\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Spec is the custom resource spec\n\tSpec AuthorizationPolicySpec `json:\"spec,\"`\n}\n\n// AuthorizationPolicySpec specifies a, AuthorizationPolicySpec resource.\ntype AuthorizationPolicySpec struct {\n\t// TargetRef references a resource to which the authorization policy applies.\n\tTargetRef gatewayapiv1alpha2.PolicyTargetReference `json:\"targetRef,omitempty\"`\n\n\t// RequiredAuthenticationRefs enumerates a set of required authentications\n\tRequiredAuthenticationRefs []gatewayapiv1alpha2.PolicyTargetReference `json:\"requiredAuthenticationRefs,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// AuthorizationPolicyList is a list of AuthorizationPolicy resources.\ntype AuthorizationPolicyList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata\"`\n\n\tItems []AuthorizationPolicy `json:\"items\"`\n}\n\n// +genclient\n// +genclient:noStatus\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +groupName=policy.linkerd.io\n\n// MeshTLSAuthentication defines a list of authenticated client IDs\n// to be referenced by an `AuthenticationPolicy`\ntype MeshTLSAuthentication struct {\n\t// TypeMeta is the metadata for the resource, like kind and apiversion\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// ObjectMeta contains the metadata for the particular object, including\n\t// things like...\n\t//  - name\n\t//  - namespace\n\t//  - self link\n\t//  - labels\n\t//  - ... etc ...\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Spec is the custom resource spec\n\tSpec MeshTLSAuthenticationSpec `json:\"spec\"`\n}\n\ntype MeshTLSAuthenticationSpec struct {\n\tIdentities   []string                                   `json:\"identities,omitempty\"`\n\tIdentityRefs []gatewayapiv1alpha2.PolicyTargetReference `json:\"identityRefs,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// MeshTLSAuthenticationList is a list of MeshTLSAuthentication resources.\ntype MeshTLSAuthenticationList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata\"`\n\n\tItems []MeshTLSAuthentication `json:\"items\"`\n}\n\n// +genclient\n// +genclient:noStatus\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +groupName=policy.linkerd.io\n\n// NetworkAuthentication defines a list of authenticated client\n// networks to be referenced by an `AuthenticationPolicy`.\ntype NetworkAuthentication struct {\n\t// TypeMeta is the metadata for the resource, like kind and apiversion\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// ObjectMeta contains the metadata for the particular object, including\n\t// things like...\n\t//  - name\n\t//  - namespace\n\t//  - self link\n\t//  - labels\n\t//  - ... etc ...\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Spec is the custom resource spec\n\tSpec NetworkAuthenticationSpec `json:\"spec,omitempty\"`\n}\n\n// NetworkAuthentication defines a list of authenticated client\n// networks to be referenced by an `AuthenticationPolicy`.\ntype NetworkAuthenticationSpec struct {\n\tNetworks []*Network `json:\"networks,omitempty\"`\n}\n\ntype Network struct {\n\tCidr   string   `json:\"cidr,omitempty\"`\n\tExcept []string `json:\"except,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// NetworkAuthenticationList is a list of NetworkAuthentication resources.\ntype NetworkAuthenticationList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata\"`\n\n\tItems []NetworkAuthentication `json:\"items\"`\n}\n"
  },
  {
    "path": "controller/gen/apis/policy/v1alpha1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tv1alpha2 \"sigs.k8s.io/gateway-api/apis/v1alpha2\"\n\tv1beta1 \"sigs.k8s.io/gateway-api/apis/v1beta1\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AuthorizationPolicy) DeepCopyInto(out *AuthorizationPolicy) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationPolicy.\nfunc (in *AuthorizationPolicy) DeepCopy() *AuthorizationPolicy {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AuthorizationPolicy)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *AuthorizationPolicy) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AuthorizationPolicyList) DeepCopyInto(out *AuthorizationPolicyList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]AuthorizationPolicy, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationPolicyList.\nfunc (in *AuthorizationPolicyList) DeepCopy() *AuthorizationPolicyList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AuthorizationPolicyList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *AuthorizationPolicyList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AuthorizationPolicySpec) DeepCopyInto(out *AuthorizationPolicySpec) {\n\t*out = *in\n\tin.TargetRef.DeepCopyInto(&out.TargetRef)\n\tif in.RequiredAuthenticationRefs != nil {\n\t\tin, out := &in.RequiredAuthenticationRefs, &out.RequiredAuthenticationRefs\n\t\t*out = make([]v1alpha2.PolicyTargetReference, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationPolicySpec.\nfunc (in *AuthorizationPolicySpec) DeepCopy() *AuthorizationPolicySpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AuthorizationPolicySpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPHeader) DeepCopyInto(out *HTTPHeader) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPHeader.\nfunc (in *HTTPHeader) DeepCopy() *HTTPHeader {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPHeader)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPHeaderMatch) DeepCopyInto(out *HTTPHeaderMatch) {\n\t*out = *in\n\tif in.Type != nil {\n\t\tin, out := &in.Type, &out.Type\n\t\t*out = new(HeaderMatchType)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPHeaderMatch.\nfunc (in *HTTPHeaderMatch) DeepCopy() *HTTPHeaderMatch {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPHeaderMatch)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPPathMatch) DeepCopyInto(out *HTTPPathMatch) {\n\t*out = *in\n\tif in.Type != nil {\n\t\tin, out := &in.Type, &out.Type\n\t\t*out = new(PathMatchType)\n\t\t**out = **in\n\t}\n\tif in.Value != nil {\n\t\tin, out := &in.Value, &out.Value\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPPathMatch.\nfunc (in *HTTPPathMatch) DeepCopy() *HTTPPathMatch {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPPathMatch)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPPathModifier) DeepCopyInto(out *HTTPPathModifier) {\n\t*out = *in\n\tif in.ReplaceFullPath != nil {\n\t\tin, out := &in.ReplaceFullPath, &out.ReplaceFullPath\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n\tif in.ReplacePrefixMatch != nil {\n\t\tin, out := &in.ReplacePrefixMatch, &out.ReplacePrefixMatch\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPPathModifier.\nfunc (in *HTTPPathModifier) DeepCopy() *HTTPPathModifier {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPPathModifier)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPQueryParamMatch) DeepCopyInto(out *HTTPQueryParamMatch) {\n\t*out = *in\n\tif in.Type != nil {\n\t\tin, out := &in.Type, &out.Type\n\t\t*out = new(QueryParamMatchType)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPQueryParamMatch.\nfunc (in *HTTPQueryParamMatch) DeepCopy() *HTTPQueryParamMatch {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPQueryParamMatch)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRequestHeaderFilter) DeepCopyInto(out *HTTPRequestHeaderFilter) {\n\t*out = *in\n\tif in.Set != nil {\n\t\tin, out := &in.Set, &out.Set\n\t\t*out = make([]HTTPHeader, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.Add != nil {\n\t\tin, out := &in.Add, &out.Add\n\t\t*out = make([]HTTPHeader, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.Remove != nil {\n\t\tin, out := &in.Remove, &out.Remove\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRequestHeaderFilter.\nfunc (in *HTTPRequestHeaderFilter) DeepCopy() *HTTPRequestHeaderFilter {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRequestHeaderFilter)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRequestRedirectFilter) DeepCopyInto(out *HTTPRequestRedirectFilter) {\n\t*out = *in\n\tif in.Scheme != nil {\n\t\tin, out := &in.Scheme, &out.Scheme\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n\tif in.Hostname != nil {\n\t\tin, out := &in.Hostname, &out.Hostname\n\t\t*out = new(v1beta1.PreciseHostname)\n\t\t**out = **in\n\t}\n\tif in.Path != nil {\n\t\tin, out := &in.Path, &out.Path\n\t\t*out = new(HTTPPathModifier)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.Port != nil {\n\t\tin, out := &in.Port, &out.Port\n\t\t*out = new(v1beta1.PortNumber)\n\t\t**out = **in\n\t}\n\tif in.StatusCode != nil {\n\t\tin, out := &in.StatusCode, &out.StatusCode\n\t\t*out = new(int)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRequestRedirectFilter.\nfunc (in *HTTPRequestRedirectFilter) DeepCopy() *HTTPRequestRedirectFilter {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRequestRedirectFilter)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRoute.\nfunc (in *HTTPRoute) DeepCopy() *HTTPRoute {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRoute)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *HTTPRoute) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRouteFilter) DeepCopyInto(out *HTTPRouteFilter) {\n\t*out = *in\n\tif in.RequestHeaderModifier != nil {\n\t\tin, out := &in.RequestHeaderModifier, &out.RequestHeaderModifier\n\t\t*out = new(HTTPRequestHeaderFilter)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.RequestRedirect != nil {\n\t\tin, out := &in.RequestRedirect, &out.RequestRedirect\n\t\t*out = new(HTTPRequestRedirectFilter)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteFilter.\nfunc (in *HTTPRouteFilter) DeepCopy() *HTTPRouteFilter {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRouteFilter)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRouteList) DeepCopyInto(out *HTTPRouteList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]HTTPRoute, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteList.\nfunc (in *HTTPRouteList) DeepCopy() *HTTPRouteList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRouteList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *HTTPRouteList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRouteMatch) DeepCopyInto(out *HTTPRouteMatch) {\n\t*out = *in\n\tif in.Path != nil {\n\t\tin, out := &in.Path, &out.Path\n\t\t*out = new(HTTPPathMatch)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.Headers != nil {\n\t\tin, out := &in.Headers, &out.Headers\n\t\t*out = make([]HTTPHeaderMatch, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tif in.QueryParams != nil {\n\t\tin, out := &in.QueryParams, &out.QueryParams\n\t\t*out = make([]HTTPQueryParamMatch, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tif in.Method != nil {\n\t\tin, out := &in.Method, &out.Method\n\t\t*out = new(HTTPMethod)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteMatch.\nfunc (in *HTTPRouteMatch) DeepCopy() *HTTPRouteMatch {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRouteMatch)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRouteRule) DeepCopyInto(out *HTTPRouteRule) {\n\t*out = *in\n\tif in.Matches != nil {\n\t\tin, out := &in.Matches, &out.Matches\n\t\t*out = make([]HTTPRouteMatch, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tif in.Filters != nil {\n\t\tin, out := &in.Filters, &out.Filters\n\t\t*out = make([]HTTPRouteFilter, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteRule.\nfunc (in *HTTPRouteRule) DeepCopy() *HTTPRouteRule {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRouteRule)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRouteSpec) DeepCopyInto(out *HTTPRouteSpec) {\n\t*out = *in\n\tin.CommonRouteSpec.DeepCopyInto(&out.CommonRouteSpec)\n\tif in.Hostnames != nil {\n\t\tin, out := &in.Hostnames, &out.Hostnames\n\t\t*out = make([]v1beta1.Hostname, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.Rules != nil {\n\t\tin, out := &in.Rules, &out.Rules\n\t\t*out = make([]HTTPRouteRule, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteSpec.\nfunc (in *HTTPRouteSpec) DeepCopy() *HTTPRouteSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRouteSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRouteStatus) DeepCopyInto(out *HTTPRouteStatus) {\n\t*out = *in\n\tin.RouteStatus.DeepCopyInto(&out.RouteStatus)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteStatus.\nfunc (in *HTTPRouteStatus) DeepCopy() *HTTPRouteStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRouteStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MeshTLSAuthentication) DeepCopyInto(out *MeshTLSAuthentication) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshTLSAuthentication.\nfunc (in *MeshTLSAuthentication) DeepCopy() *MeshTLSAuthentication {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MeshTLSAuthentication)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *MeshTLSAuthentication) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MeshTLSAuthenticationList) DeepCopyInto(out *MeshTLSAuthenticationList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]MeshTLSAuthentication, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshTLSAuthenticationList.\nfunc (in *MeshTLSAuthenticationList) DeepCopy() *MeshTLSAuthenticationList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MeshTLSAuthenticationList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *MeshTLSAuthenticationList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MeshTLSAuthenticationSpec) DeepCopyInto(out *MeshTLSAuthenticationSpec) {\n\t*out = *in\n\tif in.Identities != nil {\n\t\tin, out := &in.Identities, &out.Identities\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.IdentityRefs != nil {\n\t\tin, out := &in.IdentityRefs, &out.IdentityRefs\n\t\t*out = make([]v1alpha2.PolicyTargetReference, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshTLSAuthenticationSpec.\nfunc (in *MeshTLSAuthenticationSpec) DeepCopy() *MeshTLSAuthenticationSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MeshTLSAuthenticationSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Network) DeepCopyInto(out *Network) {\n\t*out = *in\n\tif in.Except != nil {\n\t\tin, out := &in.Except, &out.Except\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Network.\nfunc (in *Network) DeepCopy() *Network {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Network)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *NetworkAuthentication) DeepCopyInto(out *NetworkAuthentication) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkAuthentication.\nfunc (in *NetworkAuthentication) DeepCopy() *NetworkAuthentication {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(NetworkAuthentication)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *NetworkAuthentication) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *NetworkAuthenticationList) DeepCopyInto(out *NetworkAuthenticationList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]NetworkAuthentication, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkAuthenticationList.\nfunc (in *NetworkAuthenticationList) DeepCopy() *NetworkAuthenticationList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(NetworkAuthenticationList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *NetworkAuthenticationList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *NetworkAuthenticationSpec) DeepCopyInto(out *NetworkAuthenticationSpec) {\n\t*out = *in\n\tif in.Networks != nil {\n\t\tin, out := &in.Networks, &out.Networks\n\t\t*out = make([]*Network, len(*in))\n\t\tfor i := range *in {\n\t\t\tif (*in)[i] != nil {\n\t\t\t\tin, out := &(*in)[i], &(*out)[i]\n\t\t\t\t*out = new(Network)\n\t\t\t\t(*in).DeepCopyInto(*out)\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkAuthenticationSpec.\nfunc (in *NetworkAuthenticationSpec) DeepCopy() *NetworkAuthenticationSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(NetworkAuthenticationSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "controller/gen/apis/policy/v1beta3/doc.go",
    "content": "// +k8s:deepcopy-gen=package\n\npackage v1beta3\n"
  },
  {
    "path": "controller/gen/apis/policy/v1beta3/register.go",
    "content": "package v1beta3\n\nimport (\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/policy\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\nvar (\n\t// SchemeGroupVersion is the identifier for the API which includes the name\n\t// of the group and the version of the API.\n\tSchemeGroupVersion = schema.GroupVersion{\n\t\tGroup:   policy.GroupName,\n\t\tVersion: \"v1beta3\",\n\t}\n\n\t// SchemeBuilder collects functions that add things to a scheme. It's to\n\t// allow code to compile without explicitly referencing generated types.\n\t// You should declare one in each package that will have generated deep\n\t// copy or conversion functions.\n\tSchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)\n\n\t// AddToScheme applies all the stored functions to the scheme. A non-nil error\n\t// indicates that one function failed and the attempt was abandoned.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n\n// Kind takes an unqualified kind and returns back a Group qualified GroupKind\nfunc Kind(kind string) schema.GroupKind {\n\treturn SchemeGroupVersion.WithKind(kind).GroupKind()\n}\n\n// Resource takes an unqualified resource and returns a Group qualified\n// GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\n// Adds the list of known types to Scheme.\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&HTTPRoute{},\n\t\t&HTTPRouteList{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "controller/gen/apis/policy/v1beta3/types.go",
    "content": "/*\nAdapted from:\n- https://github.com/kubernetes-sigs/gateway-api/blob/main/apis/v1alpha2/httproute_types.go\n- https://github.com/kubernetes-sigs/gateway-api/pull/2013\n\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1beta3\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tgatewayapiv1alpha2 \"sigs.k8s.io/gateway-api/apis/v1alpha2\"\n)\n\n// +genclient\n// +genclient:noStatus\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +groupName=policy.linkerd.io\n\n// HTTPRoute provides a way to route HTTP requests. This includes the capability\n// to match requests by hostname, path, header, or query param. Filters can be\n// used to specify additional processing steps. Backends specify where matching\n// requests should be routed.\ntype HTTPRoute struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Spec defines the desired state of HTTPRoute.\n\tSpec HTTPRouteSpec `json:\"spec\"`\n\n\t// Status defines the current state of HTTPRoute.\n\tStatus HTTPRouteStatus `json:\"status,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// HTTPRouteList contains a list of HTTPRoute.\ntype HTTPRouteList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitempty\"`\n\tItems           []HTTPRoute `json:\"items\"`\n}\n\n// HTTPRouteSpec defines the desired state of HTTPRoute\ntype HTTPRouteSpec struct {\n\tgatewayapiv1alpha2.CommonRouteSpec `json:\",inline\"`\n\n\t// Hostnames defines a set of hostname that should match against the HTTP\n\t// Host header to select a HTTPRoute to process the request. This matches\n\t// the RFC 1123 definition of a hostname with 2 notable exceptions:\n\t//\n\t// 1. IPs are not allowed.\n\t// 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n\t//    label must appear by itself as the first label.\n\t//\n\t// If a hostname is specified by both the Listener and HTTPRoute, there\n\t// must be at least one intersecting hostname for the HTTPRoute to be\n\t// attached to the Listener. For example:\n\t//\n\t// * A Listener with `test.example.com` as the hostname matches HTTPRoutes\n\t//   that have either not specified any hostnames, or have specified at\n\t//   least one of `test.example.com` or `*.example.com`.\n\t// * A Listener with `*.example.com` as the hostname matches HTTPRoutes\n\t//   that have either not specified any hostnames or have specified at least\n\t//   one hostname that matches the Listener hostname. For example,\n\t//   `*.example.com`, `test.example.com`, and `foo.test.example.com` would\n\t//   all match. On the other hand, `example.com` and `test.example.net` would\n\t//   not match.\n\t//\n\t// Hostnames that are prefixed with a wildcard label (`*.`) are interpreted\n\t// as a suffix match. That means that a match for `*.example.com` would match\n\t// both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\t//\n\t// If both the Listener and HTTPRoute have specified hostnames, any\n\t// HTTPRoute hostnames that do not match the Listener hostname MUST be\n\t// ignored. For example, if a Listener specified `*.example.com`, and the\n\t// HTTPRoute specified `test.example.com` and `test.example.net`,\n\t// `test.example.net` must not be considered for a match.\n\t//\n\t// If both the Listener and HTTPRoute have specified hostnames, and none\n\t// match with the criteria above, then the HTTPRoute is not accepted. The\n\t// implementation must raise an 'Accepted' Condition with a status of\n\t// `False` in the corresponding RouteParentStatus.\n\t//\n\t// Support: Core\n\t//\n\t// +optional\n\t// +kubebuilder:validation:MaxItems=16\n\tHostnames []gatewayapiv1alpha2.Hostname `json:\"hostnames,omitempty\"`\n\n\t// Rules are a list of HTTP matchers, filters and actions.\n\t//\n\t// +optional\n\t// +kubebuilder:validation:MaxItems=16\n\t// +kubebuilder:default={{matches: {{path: {type: \"PathPrefix\", value: \"/\"}}}}}\n\tRules []HTTPRouteRule `json:\"rules,omitempty\"`\n}\n\n// HTTPRouteRule defines semantics for matching an HTTP request based on\n// conditions (matches), processing it (filters), and forwarding the request to\n// an API object (backendRefs).\ntype HTTPRouteRule struct {\n\t// Matches define conditions used for matching the rule against incoming\n\t// HTTP requests. Each match is independent, i.e. this rule will be matched\n\t// if **any** one of the matches is satisfied.\n\t//\n\t// For example, take the following matches configuration:\n\t//\n\t// ```\n\t// matches:\n\t// - path:\n\t//     value: \"/foo\"\n\t//   headers:\n\t//   - name: \"version\"\n\t//     value: \"v2\"\n\t// - path:\n\t//     value: \"/v2/foo\"\n\t// ```\n\t//\n\t// For a request to match against this rule, a request must satisfy\n\t// EITHER of the two conditions:\n\t//\n\t// - path prefixed with `/foo` AND contains the header `version: v2`\n\t// - path prefix of `/v2/foo`\n\t//\n\t// See the documentation for HTTPRouteMatch on how to specify multiple\n\t// match conditions that should be ANDed together.\n\t//\n\t// If no matches are specified, the default is a prefix\n\t// path match on \"/\", which has the effect of matching every\n\t// HTTP request.\n\t//\n\t// Proxy or Load Balancer routing configuration generated from HTTPRoutes\n\t// MUST prioritize rules based on the following criteria, continuing on\n\t// ties. Precedence must be given to the Rule with the largest number\n\t// of:\n\t//\n\t// * Characters in a matching non-wildcard hostname.\n\t// * Characters in a matching hostname.\n\t// * Characters in a matching path.\n\t// * Header matches.\n\t// * Query param matches.\n\t//\n\t// If ties still exist across multiple Routes, matching precedence MUST be\n\t// determined in order of the following criteria, continuing on ties:\n\t//\n\t// * The oldest Route based on creation timestamp.\n\t// * The Route appearing first in alphabetical order by\n\t//   \"{namespace}/{name}\".\n\t//\n\t// If ties still exist within the Route that has been given precedence,\n\t// matching precedence MUST be granted to the first matching rule meeting\n\t// the above criteria.\n\t//\n\t// When no rules matching a request have been successfully attached to the\n\t// parent a request is coming from, a HTTP 404 status code MUST be returned.\n\t//\n\t// +optional\n\t// +kubebuilder:validation:MaxItems=8\n\t// +kubebuilder:default={{path:{ type: \"PathPrefix\", value: \"/\"}}}\n\tMatches []HTTPRouteMatch `json:\"matches,omitempty\"`\n\n\t// Filters define the filters that are applied to requests that match\n\t// this rule.\n\t//\n\t// The effects of ordering of multiple behaviors are currently unspecified.\n\t// This can change in the future based on feedback during the alpha stage.\n\t//\n\t// Conformance-levels at this level are defined based on the type of filter:\n\t//\n\t// - ALL core filters MUST be supported by all implementations.\n\t// - Implementers are encouraged to support extended filters.\n\t// - Implementation-specific custom filters have no API guarantees across\n\t//   implementations.\n\t//\n\t// Specifying a core filter multiple times has unspecified or custom\n\t// conformance.\n\t//\n\t// All filters are expected to be compatible with each other except for the\n\t// URLRewrite and RequestRedirect filters, which may not be combined. If an\n\t// implementation can not support other combinations of filters, they must clearly\n\t// document that limitation. In all cases where incompatible or unsupported\n\t// filters are specified, implementations MUST add a warning condition to status.\n\t//\n\t// Support: Core\n\t//\n\t// +optional\n\t// +kubebuilder:validation:MaxItems=16\n\tFilters []HTTPRouteFilter `json:\"filters,omitempty\"`\n\n\t// BackendRefs defines the backend(s) where matching requests should be sent.\n\t//\n\t// A 500 status code MUST be returned if there are no BackendRefs or filters\n\t// specified that would result in a response being sent.\n\t//\n\t// A BackendRef is considered invalid when it refers to:\n\t//\n\t// * an unknown or unsupported kind of resource\n\t// * a resource that does not exist\n\t// * a resource in another namespace when the reference has not been\n\t//   explicitly allowed by a ReferencePolicy (or equivalent concept).\n\t//\n\t// When a BackendRef is invalid, 500 status codes MUST be returned for\n\t// requests that would have otherwise been routed to an invalid backend. If\n\t// multiple backends are specified, and some are invalid, the proportion of\n\t// requests that would otherwise have been routed to an invalid backend\n\t// MUST receive a 500 status code.\n\t//\n\t// When a BackendRef refers to a Service that has no ready endpoints, it is\n\t// recommended to return a 503 status code.\n\t//\n\t// Support: Core for Kubernetes Service\n\t// Support: Custom for any other resource\n\t//\n\t// Support for weight: Core\n\t//\n\t// +optional\n\t// +kubebuilder:validation:MaxItems=16\n\tBackendRefs []gatewayapiv1alpha2.HTTPBackendRef `json:\"backendRefs,omitempty\"`\n\n\t// Timeouts defines the timeouts that can be configured for an HTTP request.\n\t//\n\t// Support: Extended\n\t//\n\t// +optional\n\t// <gateway:experimental>\n\tTimeouts *HTTPRouteTimeouts `json:\"timeouts,omitempty\"`\n}\n\n// PathMatchType specifies the semantics of how HTTP paths should be compared.\n// Valid PathMatchType values are:\n//\n// * \"Exact\"\n// * \"PathPrefix\"\n// * \"RegularExpression\"\n//\n// PathPrefix and Exact paths must be syntactically valid:\n//\n// - Must begin with the `/` character\n// - Must not contain consecutive `/` characters (e.g. `/foo///`, `//`).\n//\n// Note that values may be added to this enum, implementations\n// must ensure that unknown values will not cause a crash.\n//\n// Unknown values here must result in the implementation setting the\n// Attached Condition for the Route to `status: False`, with a\n// Reason of `UnsupportedValue`.\n//\n// +kubebuilder:validation:Enum=Exact;PathPrefix;RegularExpression\ntype PathMatchType string\n\nconst (\n\t// Matches the URL path exactly and with case sensitivity.\n\tPathMatchExact PathMatchType = \"Exact\"\n\n\t// Matches based on a URL path prefix split by `/`. Matching is\n\t// case sensitive and done on a path element by element basis. A\n\t// path element refers to the list of labels in the path split by\n\t// the `/` separator. When specified, a trailing `/` is ignored.\n\t//\n\t// For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match\n\t// the prefix `/abc`, but the path `/abcd` would not.\n\t//\n\t// \"PathPrefix\" is semantically equivalent to the \"Prefix\" path type in the\n\t// Kubernetes Ingress API.\n\tPathMatchPathPrefix PathMatchType = \"PathPrefix\"\n\n\t// Matches if the URL path matches the given regular expression with\n\t// case sensitivity.\n\t//\n\t// Since `\"RegularExpression\"` has custom conformance, implementations\n\t// can support POSIX, PCRE, RE2 or any other regular expression dialect.\n\t// Please read the implementation's documentation to determine the supported\n\t// dialect.\n\tPathMatchRegularExpression PathMatchType = \"RegularExpression\"\n)\n\n// HTTPPathMatch describes how to select a HTTP route by matching the HTTP request path.\ntype HTTPPathMatch struct {\n\t// Type specifies how to match against the path Value.\n\t//\n\t// Support: Core (Exact, PathPrefix)\n\t//\n\t// Support: Custom (RegularExpression)\n\t//\n\t// +optional\n\t// +kubebuilder:default=PathPrefix\n\tType *PathMatchType `json:\"type,omitempty\"`\n\n\t// Value of the HTTP path to match against.\n\t//\n\t// +optional\n\t// +kubebuilder:default=\"/\"\n\t// +kubebuilder:validation:MaxLength=1024\n\tValue *string `json:\"value,omitempty\"`\n}\n\n// HeaderMatchType specifies the semantics of how HTTP header values should be\n// compared. Valid HeaderMatchType values are:\n//\n// * \"Exact\"\n// * \"RegularExpression\"\n//\n// Note that values may be added to this enum, implementations\n// must ensure that unknown values will not cause a crash.\n//\n// Unknown values here must result in the implementation setting the\n// Attached Condition for the Route to `status: False`, with a\n// Reason of `UnsupportedValue`.\n//\n// +kubebuilder:validation:Enum=Exact;RegularExpression\ntype HeaderMatchType string\n\n// HeaderMatchType constants.\nconst (\n\tHeaderMatchExact             HeaderMatchType = \"Exact\"\n\tHeaderMatchRegularExpression HeaderMatchType = \"RegularExpression\"\n)\n\n// HTTPHeaderName is the name of an HTTP header.\n//\n// Valid values include:\n//\n// * \"Authorization\"\n// * \"Set-Cookie\"\n//\n// Invalid values include:\n//\n// * \":method\" - \":\" is an invalid character. This means that HTTP/2 pseudo\n//   headers are not currently supported by this type.\n// * \"/invalid\" - \"/\" is an invalid character\n//\n// +kubebuilder:validation:MinLength=1\n// +kubebuilder:validation:MaxLength=256\n// +kubebuilder:validation:Pattern=`^[A-Za-z0-9!#$%&'*+\\-.^_\\x60|~]+$`\ntype HTTPHeaderName string\n\n// HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request\n// headers.\ntype HTTPHeaderMatch struct {\n\t// Type specifies how to match against the value of the header.\n\t//\n\t// Support: Core (Exact)\n\t//\n\t// Support: Custom (RegularExpression)\n\t//\n\t// Since RegularExpression HeaderMatchType has custom conformance,\n\t// implementations can support POSIX, PCRE or any other dialects of regular\n\t// expressions. Please read the implementation's documentation to determine\n\t// the supported dialect.\n\t//\n\t// +optional\n\t// +kubebuilder:default=Exact\n\tType *HeaderMatchType `json:\"type,omitempty\"`\n\n\t// Name is the name of the HTTP Header to be matched. Name matching MUST be\n\t// case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\t//\n\t// If multiple entries specify equivalent header names, only the first\n\t// entry with an equivalent name MUST be considered for a match. Subsequent\n\t// entries with an equivalent header name MUST be ignored. Due to the\n\t// case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n\t// equivalent.\n\t//\n\t// When a header is repeated in an HTTP request, it is\n\t// implementation-specific behavior as to how this is represented.\n\t// Generally, proxies should follow the guidance from the RFC:\n\t// https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding\n\t// processing a repeated header, with special handling for \"Set-Cookie\".\n\tName HTTPHeaderName `json:\"name\"`\n\n\t// Value is the value of HTTP Header to be matched.\n\t//\n\t// +kubebuilder:validation:MinLength=1\n\t// +kubebuilder:validation:MaxLength=4096\n\tValue string `json:\"value\"`\n}\n\n// QueryParamMatchType specifies the semantics of how HTTP query parameter\n// values should be compared. Valid QueryParamMatchType values are:\n//\n// * \"Exact\"\n// * \"RegularExpression\"\n//\n// Note that values may be added to this enum, implementations\n// must ensure that unknown values will not cause a crash.\n//\n// Unknown values here must result in the implementation setting the\n// Attached Condition for the Route to `status: False`, with a\n// Reason of `UnsupportedValue`.\n//\n// +kubebuilder:validation:Enum=Exact;RegularExpression\ntype QueryParamMatchType string\n\n// QueryParamMatchType constants.\nconst (\n\tQueryParamMatchExact             QueryParamMatchType = \"Exact\"\n\tQueryParamMatchRegularExpression QueryParamMatchType = \"RegularExpression\"\n)\n\n// HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP\n// query parameters.\ntype HTTPQueryParamMatch struct {\n\t// Type specifies how to match against the value of the query parameter.\n\t//\n\t// Support: Extended (Exact)\n\t//\n\t// Support: Custom (RegularExpression)\n\t//\n\t// Since RegularExpression QueryParamMatchType has custom conformance,\n\t// implementations can support POSIX, PCRE or any other dialects of regular\n\t// expressions. Please read the implementation's documentation to determine\n\t// the supported dialect.\n\t//\n\t// +optional\n\t// +kubebuilder:default=Exact\n\tType *QueryParamMatchType `json:\"type,omitempty\"`\n\n\t// Name is the name of the HTTP query param to be matched. This must be an\n\t// exact string match. (See\n\t// https://tools.ietf.org/html/rfc7230#section-2.7.3).\n\t//\n\t// If multiple entries specify equivalent query param names, only the first\n\t// entry with an equivalent name MUST be considered for a match. Subsequent\n\t// entries with an equivalent query param name MUST be ignored.\n\t//\n\t// +kubebuilder:validation:MinLength=1\n\t// +kubebuilder:validation:MaxLength=256\n\tName string `json:\"name\"`\n\n\t// Value is the value of HTTP query param to be matched.\n\t//\n\t// +kubebuilder:validation:MinLength=1\n\t// +kubebuilder:validation:MaxLength=1024\n\tValue string `json:\"value\"`\n}\n\n// HTTPMethod describes how to select a HTTP route by matching the HTTP\n// method as defined by\n// [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231#section-4) and\n// [RFC 5789](https://datatracker.ietf.org/doc/html/rfc5789#section-2).\n// The value is expected in upper case.\n//\n// Note that values may be added to this enum, implementations\n// must ensure that unknown values will not cause a crash.\n//\n// Unknown values here must result in the implementation setting the\n// Attached Condition for the Route to `status: False`, with a\n// Reason of `UnsupportedValue`.\n//\n// +kubebuilder:validation:Enum=GET;HEAD;POST;PUT;DELETE;CONNECT;OPTIONS;TRACE;PATCH\ntype HTTPMethod string\n\nconst (\n\tHTTPMethodGet     HTTPMethod = \"GET\"\n\tHTTPMethodHead    HTTPMethod = \"HEAD\"\n\tHTTPMethodPost    HTTPMethod = \"POST\"\n\tHTTPMethodPut     HTTPMethod = \"PUT\"\n\tHTTPMethodDelete  HTTPMethod = \"DELETE\"\n\tHTTPMethodConnect HTTPMethod = \"CONNECT\"\n\tHTTPMethodOptions HTTPMethod = \"OPTIONS\"\n\tHTTPMethodTrace   HTTPMethod = \"TRACE\"\n\tHTTPMethodPatch   HTTPMethod = \"PATCH\"\n)\n\n// HTTPRouteMatch defines the predicate used to match requests to a given\n// action. Multiple match types are ANDed together, i.e. the match will\n// evaluate to true only if all conditions are satisfied.\n//\n// For example, the match below will match a HTTP request only if its path\n// starts with `/foo` AND it contains the `version: v1` header:\n//\n// ```\n// match:\n//   path:\n//     value: \"/foo\"\n//   headers:\n//   - name: \"version\"\n//     value \"v1\"\n//\n// ```\ntype HTTPRouteMatch struct {\n\t// Path specifies a HTTP request path matcher. If this field is not\n\t// specified, a default prefix match on the \"/\" path is provided.\n\t//\n\t// +optional\n\t// +kubebuilder:default={type: \"PathPrefix\", value: \"/\"}\n\tPath *HTTPPathMatch `json:\"path,omitempty\"`\n\n\t// Headers specifies HTTP request header matchers. Multiple match values are\n\t// ANDed together, meaning, a request must match all the specified headers\n\t// to select the route.\n\t//\n\t// +listType=map\n\t// +listMapKey=name\n\t// +optional\n\t// +kubebuilder:validation:MaxItems=16\n\tHeaders []HTTPHeaderMatch `json:\"headers,omitempty\"`\n\n\t// QueryParams specifies HTTP query parameter matchers. Multiple match\n\t// values are ANDed together, meaning, a request must match all the\n\t// specified query parameters to select the route.\n\t//\n\t// +listType=map\n\t// +listMapKey=name\n\t// +optional\n\t// +kubebuilder:validation:MaxItems=16\n\tQueryParams []HTTPQueryParamMatch `json:\"queryParams,omitempty\"`\n\n\t// Method specifies HTTP method matcher.\n\t// When specified, this route will be matched only if the request has the\n\t// specified method.\n\t//\n\t// Support: Extended\n\t//\n\t// +optional\n\tMethod *HTTPMethod `json:\"method,omitempty\"`\n}\n\n// HTTPRouteFilter defines processing steps that must be completed during the\n// request or response lifecycle. HTTPRouteFilters are meant as an extension\n// point to express processing that may be done in Gateway implementations. Some\n// examples include request or response modification, implementing\n// authentication strategies, rate-limiting, and traffic shaping. API\n// guarantee/conformance is defined based on the type of the filter.\ntype HTTPRouteFilter struct {\n\t// Type identifies the type of filter to apply. As with other API fields,\n\t// types are classified into three conformance levels:\n\t//\n\t// - Core: Filter types and their corresponding configuration defined by\n\t//   \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n\t//   implementations must support core filters.\n\t//\n\t// - Extended: Filter types and their corresponding configuration defined by\n\t//   \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n\t//   are encouraged to support extended filters.\n\t//\n\t// - Custom: Filters that are defined and supported by specific vendors.\n\t//   In the future, filters showing convergence in behavior across multiple\n\t//   implementations will be considered for inclusion in extended or core\n\t//   conformance levels. Filter-specific configuration for such filters\n\t//   is specified using the ExtensionRef field. `Type` should be set to\n\t//   \"ExtensionRef\" for custom filters.\n\t//\n\t// Implementers are encouraged to define custom implementation types to\n\t// extend the core API with implementation-specific behavior.\n\t//\n\t// If a reference to a custom filter type cannot be resolved, the filter\n\t// MUST NOT be skipped. Instead, requests that would have been processed by\n\t// that filter MUST receive a HTTP error response.\n\t//\n\t// Note that values may be added to this enum, implementations\n\t// must ensure that unknown values will not cause a crash.\n\t//\n\t// Unknown values here must result in the implementation setting the\n\t// Attached Condition for the Route to `status: False`, with a\n\t// Reason of `UnsupportedValue`.\n\t//\n\t// +unionDiscriminator\n\t// +kubebuilder:validation:Enum=RequestHeaderModifier;RequestMirror;RequestRedirect;ExtensionRef\n\t// <gateway:experimental:validation:Enum=RequestHeaderModifier;RequestMirror;RequestRedirect;URLRewrite;ExtensionRef>\n\tType HTTPRouteFilterType `json:\"type\"`\n\n\t// RequestHeaderModifier defines a schema for a filter that modifies request\n\t// headers.\n\t//\n\t// Support: Core\n\t//\n\t// +optional\n\tRequestHeaderModifier *HTTPRequestHeaderFilter `json:\"requestHeaderModifier,omitempty\"`\n\n\t// RequestRedirect defines a schema for a filter that responds to the\n\t// request with an HTTP redirection.\n\t//\n\t// Support: Core\n\t//\n\t// +optional\n\tRequestRedirect *HTTPRequestRedirectFilter `json:\"requestRedirect,omitempty\"`\n}\n\n// HTTPRouteFilterType identifies a type of HTTPRoute filter.\ntype HTTPRouteFilterType string\n\nconst (\n\t// HTTPRouteFilterRequestHeaderModifier can be used to add or remove an HTTP\n\t// header from an HTTP request before it is sent to the upstream target.\n\t//\n\t// Support in HTTPRouteRule: Core\n\t//\n\t// Support in HTTPBackendRef: Extended\n\tHTTPRouteFilterRequestHeaderModifier HTTPRouteFilterType = \"RequestHeaderModifier\"\n\n\t// HTTPRouteFilterRequestRedirect can be used to redirect a request to\n\t// another location. This filter can also be used for HTTP to HTTPS\n\t// redirects. This may not be used on the same Route rule or BackendRef as a\n\t// URLRewrite filter.\n\t//\n\t// Support in HTTPRouteRule: Core\n\t//\n\t// Support in HTTPBackendRef: Extended\n\tHTTPRouteFilterRequestRedirect HTTPRouteFilterType = \"RequestRedirect\"\n)\n\n// HTTPHeader represents an HTTP Header name and value as defined by RFC 7230.\ntype HTTPHeader struct {\n\t// Name is the name of the HTTP Header to be matched. Name matching MUST be\n\t// case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\t//\n\t// If multiple entries specify equivalent header names, the first entry with\n\t// an equivalent name MUST be considered for a match. Subsequent entries\n\t// with an equivalent header name MUST be ignored. Due to the\n\t// case-insensitivity of header names, \"foo\" and \"Foo\" are considered\n\t// equivalent.\n\tName HTTPHeaderName `json:\"name\"`\n\n\t// Value is the value of HTTP Header to be matched.\n\t//\n\t// +kubebuilder:validation:MinLength=1\n\t// +kubebuilder:validation:MaxLength=4096\n\tValue string `json:\"value\"`\n}\n\n// HTTPRequestHeaderFilter defines configuration for the RequestHeaderModifier\n// filter.\ntype HTTPRequestHeaderFilter struct {\n\t// Set overwrites the request with the given header (name, value)\n\t// before the action.\n\t//\n\t// Input:\n\t//   GET /foo HTTP/1.1\n\t//   my-header: foo\n\t//\n\t// Config:\n\t//   set:\n\t//   - name: \"my-header\"\n\t//     value: \"bar\"\n\t//\n\t// Output:\n\t//   GET /foo HTTP/1.1\n\t//   my-header: bar\n\t//\n\t// +optional\n\t// +listType=map\n\t// +listMapKey=name\n\t// +kubebuilder:validation:MaxItems=16\n\tSet []HTTPHeader `json:\"set,omitempty\"`\n\n\t// Add adds the given header(s) (name, value) to the request\n\t// before the action. It appends to any existing values associated\n\t// with the header name.\n\t//\n\t// Input:\n\t//   GET /foo HTTP/1.1\n\t//   my-header: foo\n\t//\n\t// Config:\n\t//   add:\n\t//   - name: \"my-header\"\n\t//     value: \"bar\"\n\t//\n\t// Output:\n\t//   GET /foo HTTP/1.1\n\t//   my-header: foo\n\t//   my-header: bar\n\t//\n\t// +optional\n\t// +listType=map\n\t// +listMapKey=name\n\t// +kubebuilder:validation:MaxItems=16\n\tAdd []HTTPHeader `json:\"add,omitempty\"`\n\n\t// Remove the given header(s) from the HTTP request before the action. The\n\t// value of Remove is a list of HTTP header names. Note that the header\n\t// names are case-insensitive (see\n\t// https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\t//\n\t// Input:\n\t//   GET /foo HTTP/1.1\n\t//   my-header1: foo\n\t//   my-header2: bar\n\t//   my-header3: baz\n\t//\n\t// Config:\n\t//   remove: [\"my-header1\", \"my-header3\"]\n\t//\n\t// Output:\n\t//   GET /foo HTTP/1.1\n\t//   my-header2: bar\n\t//\n\t// +optional\n\t// +kubebuilder:validation:MaxItems=16\n\tRemove []string `json:\"remove,omitempty\"`\n}\n\n// HTTPPathModifierType defines the type of path redirect or rewrite.\ntype HTTPPathModifierType string\n\n// HTTPPathModifier defines configuration for path modifiers.\n// <gateway:experimental>\ntype HTTPPathModifier struct {\n\t// Type defines the type of path modifier. Additional types may be\n\t// added in a future release of the API.\n\t//\n\t// Note that values may be added to this enum, implementations\n\t// must ensure that unknown values will not cause a crash.\n\t//\n\t// Unknown values here must result in the implementation setting the\n\t// Attached Condition for the Route to `status: False`, with a\n\t// Reason of `UnsupportedValue`.\n\t//\n\t// <gateway:experimental>\n\t// +kubebuilder:validation:Enum=ReplaceFullPath;ReplacePrefixMatch\n\tType HTTPPathModifierType `json:\"type\"`\n\n\t// ReplaceFullPath specifies the value with which to replace the full path\n\t// of a request during a rewrite or redirect.\n\t//\n\t// <gateway:experimental>\n\t// +kubebuilder:validation:MaxLength=1024\n\t// +optional\n\tReplaceFullPath *string `json:\"replaceFullPath,omitempty\"`\n\n\t// ReplacePrefixMatch specifies the value with which to replace the prefix\n\t// match of a request during a rewrite or redirect. For example, a request\n\t// to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\".\n\t//\n\t// Note that this matches the behavior of the PathPrefix match type. This\n\t// matches full path elements. A path element refers to the list of labels\n\t// in the path split by the `/` separator. When specified, a trailing `/` is\n\t// ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all\n\t// match the prefix `/abc`, but the path `/abcd` would not.\n\t//\n\t// <gateway:experimental>\n\t// +kubebuilder:validation:MaxLength=1024\n\t// +optional\n\tReplacePrefixMatch *string `json:\"replacePrefixMatch,omitempty\"`\n}\n\n// HTTPRequestRedirect defines a filter that redirects a request. This filter\n// MUST NOT be used on the same Route rule as a HTTPURLRewrite filter.\ntype HTTPRequestRedirectFilter struct {\n\t// Scheme is the scheme to be used in the value of the `Location`\n\t// header in the response.\n\t// When empty, the scheme of the request is used.\n\t//\n\t// Support: Extended\n\t//\n\t// Note that values may be added to this enum, implementations\n\t// must ensure that unknown values will not cause a crash.\n\t//\n\t// Unknown values here must result in the implementation setting the\n\t// Attached Condition for the Route to `status: False`, with a\n\t// Reason of `UnsupportedValue`.\n\t//\n\t// +optional\n\t// +kubebuilder:validation:Enum=http;https\n\tScheme *string `json:\"scheme,omitempty\"`\n\n\t// Hostname is the hostname to be used in the value of the `Location`\n\t// header in the response.\n\t// When empty, the hostname of the request is used.\n\t//\n\t// Support: Core\n\t//\n\t// +optional\n\tHostname *gatewayapiv1alpha2.PreciseHostname `json:\"hostname,omitempty\"`\n\n\t// Path defines parameters used to modify the path of the incoming request.\n\t// The modified path is then used to construct the `Location` header. When\n\t// empty, the request path is used as-is.\n\t//\n\t// Support: Extended\n\t//\n\t// <gateway:experimental>\n\t// +optional\n\tPath *HTTPPathModifier `json:\"path,omitempty\"`\n\n\t// Port is the port to be used in the value of the `Location`\n\t// header in the response.\n\t// When empty, port (if specified) of the request is used.\n\t//\n\t// Support: Extended\n\t//\n\t// +optional\n\tPort *gatewayapiv1alpha2.PortNumber `json:\"port,omitempty\"`\n\n\t// StatusCode is the HTTP status code to be used in response.\n\t//\n\t// Support: Core\n\t//\n\t// Note that values may be added to this enum, implementations\n\t// must ensure that unknown values will not cause a crash.\n\t//\n\t// Unknown values here must result in the implementation setting the\n\t// Attached Condition for the Route to `status: False`, with a\n\t// Reason of `UnsupportedValue`.\n\t//\n\t// +optional\n\t// +kubebuilder:default=302\n\t// +kubebuilder:validation:Enum=301;302\n\tStatusCode *int `json:\"statusCode,omitempty\"`\n}\n\n// HTTPRouteStatus defines the observed state of HTTPRoute.\ntype HTTPRouteStatus struct {\n\tgatewayapiv1alpha2.RouteStatus `json:\",inline\"`\n}\n\n// HTTPRouteTimeouts defines timeouts that can be configured for an HTTPRoute.\n// Timeout values are formatted like 1h/1m/1s/1ms as parsed by Golang time.ParseDuration\n// and MUST BE >= 1ms or 0 to disable (no timeout).\ntype HTTPRouteTimeouts struct {\n\t// Request specifies the duration for processing an HTTP client request after which the\n\t// gateway will time out if unable to send a response.\n\t//\n\t// For example, setting this field to the value `10s` in an HTTPRoute will cause\n\t// a timeout if a client request is taking longer than 10 seconds to complete.\n\t//\n\t// This timeout is intended to cover as close to the whole request-response transaction\n\t// as possible although an implementation MAY choose to start the timeout after the entire\n\t// request stream has been received instead of immediately after the transaction is\n\t// initiated by the client.\n\t//\n\t// When this field is unspecified, request timeout behavior is implementation-dependent.\n\t//\n\t// Support: Extended\n\t//\n\t// +optional\n\t// +kubebuilder:validation:Format=duration\n\tRequest *metav1.Duration `json:\"request,omitempty\"`\n\n\t// BackendRequest specifies a timeout for an individual request from the gateway\n\t// to a backend service. This covers the time from when the request first starts being\n\t// sent from the gateway to when the full response has been received from the backend.\n\t//\n\t// An entire client HTTP transaction with a gateway, covered by the Request timeout,\n\t// may result in more than one call from the gateway to the destination backend service,\n\t// for example, if automatic retries are supported.\n\t//\n\t// Because the Request timeout encompasses the BackendRequest timeout,\n\t// the value of BackendRequest defaults to and must be <= the value of Request timeout.\n\t//\n\t// Support: Extended\n\t//\n\t// +optional\n\t// +kubebuilder:validation:Format=duration\n\tBackendRequest *metav1.Duration `json:\"backendRequest,omitempty\"`\n}\n"
  },
  {
    "path": "controller/gen/apis/policy/v1beta3/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1beta3\n\nimport (\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tv1beta1 \"sigs.k8s.io/gateway-api/apis/v1beta1\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPHeader) DeepCopyInto(out *HTTPHeader) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPHeader.\nfunc (in *HTTPHeader) DeepCopy() *HTTPHeader {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPHeader)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPHeaderMatch) DeepCopyInto(out *HTTPHeaderMatch) {\n\t*out = *in\n\tif in.Type != nil {\n\t\tin, out := &in.Type, &out.Type\n\t\t*out = new(HeaderMatchType)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPHeaderMatch.\nfunc (in *HTTPHeaderMatch) DeepCopy() *HTTPHeaderMatch {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPHeaderMatch)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPPathMatch) DeepCopyInto(out *HTTPPathMatch) {\n\t*out = *in\n\tif in.Type != nil {\n\t\tin, out := &in.Type, &out.Type\n\t\t*out = new(PathMatchType)\n\t\t**out = **in\n\t}\n\tif in.Value != nil {\n\t\tin, out := &in.Value, &out.Value\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPPathMatch.\nfunc (in *HTTPPathMatch) DeepCopy() *HTTPPathMatch {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPPathMatch)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPPathModifier) DeepCopyInto(out *HTTPPathModifier) {\n\t*out = *in\n\tif in.ReplaceFullPath != nil {\n\t\tin, out := &in.ReplaceFullPath, &out.ReplaceFullPath\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n\tif in.ReplacePrefixMatch != nil {\n\t\tin, out := &in.ReplacePrefixMatch, &out.ReplacePrefixMatch\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPPathModifier.\nfunc (in *HTTPPathModifier) DeepCopy() *HTTPPathModifier {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPPathModifier)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPQueryParamMatch) DeepCopyInto(out *HTTPQueryParamMatch) {\n\t*out = *in\n\tif in.Type != nil {\n\t\tin, out := &in.Type, &out.Type\n\t\t*out = new(QueryParamMatchType)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPQueryParamMatch.\nfunc (in *HTTPQueryParamMatch) DeepCopy() *HTTPQueryParamMatch {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPQueryParamMatch)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRequestHeaderFilter) DeepCopyInto(out *HTTPRequestHeaderFilter) {\n\t*out = *in\n\tif in.Set != nil {\n\t\tin, out := &in.Set, &out.Set\n\t\t*out = make([]HTTPHeader, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.Add != nil {\n\t\tin, out := &in.Add, &out.Add\n\t\t*out = make([]HTTPHeader, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.Remove != nil {\n\t\tin, out := &in.Remove, &out.Remove\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRequestHeaderFilter.\nfunc (in *HTTPRequestHeaderFilter) DeepCopy() *HTTPRequestHeaderFilter {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRequestHeaderFilter)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRequestRedirectFilter) DeepCopyInto(out *HTTPRequestRedirectFilter) {\n\t*out = *in\n\tif in.Scheme != nil {\n\t\tin, out := &in.Scheme, &out.Scheme\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n\tif in.Hostname != nil {\n\t\tin, out := &in.Hostname, &out.Hostname\n\t\t*out = new(v1beta1.PreciseHostname)\n\t\t**out = **in\n\t}\n\tif in.Path != nil {\n\t\tin, out := &in.Path, &out.Path\n\t\t*out = new(HTTPPathModifier)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.Port != nil {\n\t\tin, out := &in.Port, &out.Port\n\t\t*out = new(v1beta1.PortNumber)\n\t\t**out = **in\n\t}\n\tif in.StatusCode != nil {\n\t\tin, out := &in.StatusCode, &out.StatusCode\n\t\t*out = new(int)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRequestRedirectFilter.\nfunc (in *HTTPRequestRedirectFilter) DeepCopy() *HTTPRequestRedirectFilter {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRequestRedirectFilter)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRoute.\nfunc (in *HTTPRoute) DeepCopy() *HTTPRoute {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRoute)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *HTTPRoute) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRouteFilter) DeepCopyInto(out *HTTPRouteFilter) {\n\t*out = *in\n\tif in.RequestHeaderModifier != nil {\n\t\tin, out := &in.RequestHeaderModifier, &out.RequestHeaderModifier\n\t\t*out = new(HTTPRequestHeaderFilter)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.RequestRedirect != nil {\n\t\tin, out := &in.RequestRedirect, &out.RequestRedirect\n\t\t*out = new(HTTPRequestRedirectFilter)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteFilter.\nfunc (in *HTTPRouteFilter) DeepCopy() *HTTPRouteFilter {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRouteFilter)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRouteList) DeepCopyInto(out *HTTPRouteList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]HTTPRoute, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteList.\nfunc (in *HTTPRouteList) DeepCopy() *HTTPRouteList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRouteList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *HTTPRouteList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRouteMatch) DeepCopyInto(out *HTTPRouteMatch) {\n\t*out = *in\n\tif in.Path != nil {\n\t\tin, out := &in.Path, &out.Path\n\t\t*out = new(HTTPPathMatch)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.Headers != nil {\n\t\tin, out := &in.Headers, &out.Headers\n\t\t*out = make([]HTTPHeaderMatch, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tif in.QueryParams != nil {\n\t\tin, out := &in.QueryParams, &out.QueryParams\n\t\t*out = make([]HTTPQueryParamMatch, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tif in.Method != nil {\n\t\tin, out := &in.Method, &out.Method\n\t\t*out = new(HTTPMethod)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteMatch.\nfunc (in *HTTPRouteMatch) DeepCopy() *HTTPRouteMatch {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRouteMatch)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRouteRule) DeepCopyInto(out *HTTPRouteRule) {\n\t*out = *in\n\tif in.Matches != nil {\n\t\tin, out := &in.Matches, &out.Matches\n\t\t*out = make([]HTTPRouteMatch, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tif in.Filters != nil {\n\t\tin, out := &in.Filters, &out.Filters\n\t\t*out = make([]HTTPRouteFilter, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tif in.BackendRefs != nil {\n\t\tin, out := &in.BackendRefs, &out.BackendRefs\n\t\t*out = make([]v1beta1.HTTPBackendRef, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tif in.Timeouts != nil {\n\t\tin, out := &in.Timeouts, &out.Timeouts\n\t\t*out = new(HTTPRouteTimeouts)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteRule.\nfunc (in *HTTPRouteRule) DeepCopy() *HTTPRouteRule {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRouteRule)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRouteSpec) DeepCopyInto(out *HTTPRouteSpec) {\n\t*out = *in\n\tin.CommonRouteSpec.DeepCopyInto(&out.CommonRouteSpec)\n\tif in.Hostnames != nil {\n\t\tin, out := &in.Hostnames, &out.Hostnames\n\t\t*out = make([]v1beta1.Hostname, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.Rules != nil {\n\t\tin, out := &in.Rules, &out.Rules\n\t\t*out = make([]HTTPRouteRule, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteSpec.\nfunc (in *HTTPRouteSpec) DeepCopy() *HTTPRouteSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRouteSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRouteStatus) DeepCopyInto(out *HTTPRouteStatus) {\n\t*out = *in\n\tin.RouteStatus.DeepCopyInto(&out.RouteStatus)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteStatus.\nfunc (in *HTTPRouteStatus) DeepCopy() *HTTPRouteStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRouteStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HTTPRouteTimeouts) DeepCopyInto(out *HTTPRouteTimeouts) {\n\t*out = *in\n\tif in.Request != nil {\n\t\tin, out := &in.Request, &out.Request\n\t\t*out = new(v1.Duration)\n\t\t**out = **in\n\t}\n\tif in.BackendRequest != nil {\n\t\tin, out := &in.BackendRequest, &out.BackendRequest\n\t\t*out = new(v1.Duration)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteTimeouts.\nfunc (in *HTTPRouteTimeouts) DeepCopy() *HTTPRouteTimeouts {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HTTPRouteTimeouts)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "controller/gen/apis/server/register.go",
    "content": "package server\n\n// GroupName identifies the API Group Name for a Server.\nconst GroupName = \"policy.linkerd.io\"\n"
  },
  {
    "path": "controller/gen/apis/server/v1beta1/doc.go",
    "content": "// +k8s:deepcopy-gen=package\n\npackage v1beta1\n"
  },
  {
    "path": "controller/gen/apis/server/v1beta1/register.go",
    "content": "package v1beta1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/server\"\n)\n\nvar (\n\t// SchemeGroupVersion is the identifier for the API which includes the name\n\t// of the group and the version of the API.\n\tSchemeGroupVersion = schema.GroupVersion{\n\t\tGroup:   server.GroupName,\n\t\tVersion: \"v1beta1\",\n\t}\n\n\t// SchemeBuilder collects functions that add things to a scheme. It's to\n\t// allow code to compile without explicitly referencing generated types.\n\t// You should declare one in each package that will have generated deep\n\t// copy or conversion functions.\n\tSchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)\n\n\t// AddToScheme applies all the stored functions to the scheme. A non-nil error\n\t// indicates that one function failed and the attempt was abandoned.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n\n// Kind takes an unqualified kind and returns back a Group qualified GroupKind\nfunc Kind(kind string) schema.GroupKind {\n\treturn SchemeGroupVersion.WithKind(kind).GroupKind()\n}\n\n// Resource takes an unqualified resource and returns a Group qualified\n// GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\n// Adds the list of known types to Scheme.\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&Server{},\n\t\t&ServerList{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "controller/gen/apis/server/v1beta1/types.go",
    "content": "package v1beta1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n)\n\n// +genclient\n// +genclient:noStatus\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +groupName=policy.linkerd.io\n\ntype Server struct {\n\t// TypeMeta is the metadata for the resource, like kind and apiversion\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// ObjectMeta contains the metadata for the particular object, including\n\t// things like...\n\t//  - name\n\t//  - namespace\n\t//  - self link\n\t//  - labels\n\t//  - ... etc ...\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Spec is the custom resource spec\n\tSpec ServerSpec `json:\"spec\"`\n}\n\n// ServerSpec specifies a Server resource.\ntype ServerSpec struct {\n\tPodSelector   *metav1.LabelSelector `json:\"podSelector\"`\n\tPort          intstr.IntOrString    `json:\"port,omitempty\"`\n\tProxyProtocol string                `json:\"proxyProtocol,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// ServerList is a list of Server resources.\ntype ServerList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata\"`\n\n\tItems []Server `json:\"items\"`\n}\n"
  },
  {
    "path": "controller/gen/apis/server/v1beta1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Server) DeepCopyInto(out *Server) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Server.\nfunc (in *Server) DeepCopy() *Server {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Server)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Server) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ServerList) DeepCopyInto(out *ServerList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Server, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerList.\nfunc (in *ServerList) DeepCopy() *ServerList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ServerList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *ServerList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ServerSpec) DeepCopyInto(out *ServerSpec) {\n\t*out = *in\n\tif in.PodSelector != nil {\n\t\tin, out := &in.PodSelector, &out.PodSelector\n\t\t*out = new(v1.LabelSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tout.Port = in.Port\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerSpec.\nfunc (in *ServerSpec) DeepCopy() *ServerSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ServerSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "controller/gen/apis/server/v1beta2/doc.go",
    "content": "// +k8s:deepcopy-gen=package\n\npackage v1beta2\n"
  },
  {
    "path": "controller/gen/apis/server/v1beta2/register.go",
    "content": "package v1beta2\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/server\"\n)\n\nvar (\n\t// SchemeGroupVersion is the identifier for the API which includes the name\n\t// of the group and the version of the API.\n\tSchemeGroupVersion = schema.GroupVersion{\n\t\tGroup:   server.GroupName,\n\t\tVersion: \"v1beta2\",\n\t}\n\n\t// SchemeBuilder collects functions that add things to a scheme. It's to\n\t// allow code to compile without explicitly referencing generated types.\n\t// You should declare one in each package that will have generated deep\n\t// copy or conversion functions.\n\tSchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)\n\n\t// AddToScheme applies all the stored functions to the scheme. A non-nil error\n\t// indicates that one function failed and the attempt was abandoned.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n\n// Kind takes an unqualified kind and returns back a Group qualified GroupKind\nfunc Kind(kind string) schema.GroupKind {\n\treturn SchemeGroupVersion.WithKind(kind).GroupKind()\n}\n\n// Resource takes an unqualified resource and returns a Group qualified\n// GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\n// Adds the list of known types to Scheme.\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&Server{},\n\t\t&ServerList{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "controller/gen/apis/server/v1beta2/types.go",
    "content": "package v1beta2\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n)\n\n// +genclient\n// +genclient:noStatus\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +groupName=policy.linkerd.io\n\ntype Server struct {\n\t// TypeMeta is the metadata for the resource, like kind and apiversion\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// ObjectMeta contains the metadata for the particular object, including\n\t// things like...\n\t//  - name\n\t//  - namespace\n\t//  - self link\n\t//  - labels\n\t//  - ... etc ...\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Spec is the custom resource spec\n\tSpec ServerSpec `json:\"spec\"`\n}\n\n// ServerSpec specifies a Server resource.\ntype ServerSpec struct {\n\tPodSelector              *metav1.LabelSelector `json:\"podSelector,omitempty\"`\n\tExternalWorkloadSelector *metav1.LabelSelector `json:\"externalWorkloadSelector,omitempty\"`\n\tPort                     intstr.IntOrString    `json:\"port,omitempty\"`\n\tProxyProtocol            string                `json:\"proxyProtocol,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// ServerList is a list of Server resources.\ntype ServerList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata\"`\n\n\tItems []Server `json:\"items\"`\n}\n"
  },
  {
    "path": "controller/gen/apis/server/v1beta2/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Server) DeepCopyInto(out *Server) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Server.\nfunc (in *Server) DeepCopy() *Server {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Server)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Server) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ServerList) DeepCopyInto(out *ServerList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Server, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerList.\nfunc (in *ServerList) DeepCopy() *ServerList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ServerList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *ServerList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ServerSpec) DeepCopyInto(out *ServerSpec) {\n\t*out = *in\n\tif in.PodSelector != nil {\n\t\tin, out := &in.PodSelector, &out.PodSelector\n\t\t*out = new(v1.LabelSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.ExternalWorkloadSelector != nil {\n\t\tin, out := &in.ExternalWorkloadSelector, &out.ExternalWorkloadSelector\n\t\t*out = new(v1.LabelSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tout.Port = in.Port\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerSpec.\nfunc (in *ServerSpec) DeepCopy() *ServerSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ServerSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "controller/gen/apis/server/v1beta3/doc.go",
    "content": "// +k8s:deepcopy-gen=package\n\npackage v1beta3\n"
  },
  {
    "path": "controller/gen/apis/server/v1beta3/register.go",
    "content": "package v1beta3\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/server\"\n)\n\nvar (\n\t// SchemeGroupVersion is the identifier for the API which includes the name\n\t// of the group and the version of the API.\n\tSchemeGroupVersion = schema.GroupVersion{\n\t\tGroup:   server.GroupName,\n\t\tVersion: \"v1beta3\",\n\t}\n\n\t// SchemeBuilder collects functions that add things to a scheme. It's to\n\t// allow code to compile without explicitly referencing generated types.\n\t// You should declare one in each package that will have generated deep\n\t// copy or conversion functions.\n\tSchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)\n\n\t// AddToScheme applies all the stored functions to the scheme. A non-nil error\n\t// indicates that one function failed and the attempt was abandoned.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n\n// Kind takes an unqualified kind and returns back a Group qualified GroupKind\nfunc Kind(kind string) schema.GroupKind {\n\treturn SchemeGroupVersion.WithKind(kind).GroupKind()\n}\n\n// Resource takes an unqualified resource and returns a Group qualified\n// GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\n// Adds the list of known types to Scheme.\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&Server{},\n\t\t&ServerList{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "controller/gen/apis/server/v1beta3/types.go",
    "content": "package v1beta3\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n)\n\n// +genclient\n// +genclient:noStatus\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +groupName=policy.linkerd.io\n\ntype Server struct {\n\t// TypeMeta is the metadata for the resource, like kind and apiversion\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// ObjectMeta contains the metadata for the particular object, including\n\t// things like...\n\t//  - name\n\t//  - namespace\n\t//  - self link\n\t//  - labels\n\t//  - ... etc ...\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Spec is the custom resource spec\n\tSpec ServerSpec `json:\"spec\"`\n}\n\n// ServerSpec specifies a Server resource.\ntype ServerSpec struct {\n\tAccessPolicy             string                `json:\"accessPolicy,omitempty\"`\n\tPodSelector              *metav1.LabelSelector `json:\"podSelector,omitempty\"`\n\tExternalWorkloadSelector *metav1.LabelSelector `json:\"externalWorkloadSelector,omitempty\"`\n\tPort                     intstr.IntOrString    `json:\"port,omitempty\"`\n\tProxyProtocol            string                `json:\"proxyProtocol,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// ServerList is a list of Server resources.\ntype ServerList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata\"`\n\n\tItems []Server `json:\"items\"`\n}\n"
  },
  {
    "path": "controller/gen/apis/server/v1beta3/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1beta3\n\nimport (\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Server) DeepCopyInto(out *Server) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Server.\nfunc (in *Server) DeepCopy() *Server {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Server)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Server) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ServerList) DeepCopyInto(out *ServerList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Server, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerList.\nfunc (in *ServerList) DeepCopy() *ServerList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ServerList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *ServerList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ServerSpec) DeepCopyInto(out *ServerSpec) {\n\t*out = *in\n\tif in.PodSelector != nil {\n\t\tin, out := &in.PodSelector, &out.PodSelector\n\t\t*out = new(v1.LabelSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.ExternalWorkloadSelector != nil {\n\t\tin, out := &in.ExternalWorkloadSelector, &out.ExternalWorkloadSelector\n\t\t*out = new(v1.LabelSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tout.Port = in.Port\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerSpec.\nfunc (in *ServerSpec) DeepCopy() *ServerSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ServerSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "controller/gen/apis/serverauthorization/register.go",
    "content": "package serverauthorization\n\n// GroupName identifies the API Group Name for a ServerAuthorization.\nconst GroupName = \"policy.linkerd.io\"\n"
  },
  {
    "path": "controller/gen/apis/serverauthorization/v1beta1/doc.go",
    "content": "// +k8s:deepcopy-gen=package\n\npackage v1beta1\n"
  },
  {
    "path": "controller/gen/apis/serverauthorization/v1beta1/register.go",
    "content": "package v1beta1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization\"\n)\n\nvar (\n\t// SchemeGroupVersion is the identifier for the API which includes the name\n\t// of the group and the version of the API.\n\tSchemeGroupVersion = schema.GroupVersion{\n\t\tGroup:   serverauthorization.GroupName,\n\t\tVersion: \"v1beta1\",\n\t}\n\n\t// SchemeBuilder collects functions that add things to a scheme. It's to\n\t// allow code to compile without explicitly referencing generated types.\n\t// You should declare one in each package that will have generated deep\n\t// copy or conversion functions.\n\tSchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)\n\n\t// AddToScheme applies all the stored functions to the scheme. A non-nil error\n\t// indicates that one function failed and the attempt was abandoned.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n\n// Kind takes an unqualified kind and returns back a Group qualified GroupKind\nfunc Kind(kind string) schema.GroupKind {\n\treturn SchemeGroupVersion.WithKind(kind).GroupKind()\n}\n\n// Resource takes an unqualified resource and returns a Group qualified\n// GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\n// Adds the list of known types to Scheme.\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&ServerAuthorization{},\n\t\t&ServerAuthorizationList{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "controller/gen/apis/serverauthorization/v1beta1/types.go",
    "content": "package v1beta1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +genclient:noStatus\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +groupName=policy.linkerd.io\n\ntype ServerAuthorization struct {\n\t// TypeMeta is the metadata for the resource, like kind and apiversion\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// ObjectMeta contains the metadata for the particular object, including\n\t// things like...\n\t//  - name\n\t//  - namespace\n\t//  - self link\n\t//  - labels\n\t//  - ... etc ...\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Spec is the custom resource spec\n\tSpec ServerAuthorizationSpec `json:\"spec\"`\n}\n\n// ServerAuthorizationSpec specifies a ServerAuthorization resource.\ntype ServerAuthorizationSpec struct {\n\tServer Server `json:\"server,omitempty\"`\n\tClient Client `json:\"client,omitempty\"`\n}\n\n// Server is the Server that a ServerAuthorization uses.\ntype Server struct {\n\tName     string                `json:\"name,omitempty\"`\n\tSelector *metav1.LabelSelector `json:\"selector,omitempty\"`\n}\n\n// Client describes which clients a ServerAuthorization authorizes.\ntype Client struct {\n\tNetworks        []*Cidr  `json:\"networks,omitempty\"`\n\tMeshTLS         *MeshTLS `json:\"meshTLS,omitempty\"`\n\tUnauthenticated bool     `json:\"unauthenticated,omitempty\"`\n}\n\n// Cidr describes which client CIDRs a ServerAuthorization authorizes.\ntype Cidr struct {\n\tCidr   string   `json:\"cidr,omitempty\"`\n\tExcept []string `json:\"except,omitempty\"`\n}\n\n// MeshTLS describes which meshed clients are authorized.\ntype MeshTLS struct {\n\tUnauthenticatedTLS bool                  `json:\"unauthenticatedTLS,omitempty\"`\n\tIdentities         []string              `json:\"identities,omitempty\"`\n\tServiceAccounts    []*ServiceAccountName `json:\"serviceAccounts,omitempty\"`\n}\n\n// ServiceAccountName is the structure of a service account name.\n\ntype ServiceAccountName struct {\n\tName      string `json:\"name,omitempty\"`\n\tNamespace string `json:\"namespace,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// ServerAuthorizationList is a list of Server resources.\ntype ServerAuthorizationList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata\"`\n\n\tItems []ServerAuthorization `json:\"items\"`\n}\n"
  },
  {
    "path": "controller/gen/apis/serverauthorization/v1beta1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Cidr) DeepCopyInto(out *Cidr) {\n\t*out = *in\n\tif in.Except != nil {\n\t\tin, out := &in.Except, &out.Except\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cidr.\nfunc (in *Cidr) DeepCopy() *Cidr {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Cidr)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Client) DeepCopyInto(out *Client) {\n\t*out = *in\n\tif in.Networks != nil {\n\t\tin, out := &in.Networks, &out.Networks\n\t\t*out = make([]*Cidr, len(*in))\n\t\tfor i := range *in {\n\t\t\tif (*in)[i] != nil {\n\t\t\t\tin, out := &(*in)[i], &(*out)[i]\n\t\t\t\t*out = new(Cidr)\n\t\t\t\t(*in).DeepCopyInto(*out)\n\t\t\t}\n\t\t}\n\t}\n\tif in.MeshTLS != nil {\n\t\tin, out := &in.MeshTLS, &out.MeshTLS\n\t\t*out = new(MeshTLS)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Client.\nfunc (in *Client) DeepCopy() *Client {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Client)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MeshTLS) DeepCopyInto(out *MeshTLS) {\n\t*out = *in\n\tif in.Identities != nil {\n\t\tin, out := &in.Identities, &out.Identities\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.ServiceAccounts != nil {\n\t\tin, out := &in.ServiceAccounts, &out.ServiceAccounts\n\t\t*out = make([]*ServiceAccountName, len(*in))\n\t\tfor i := range *in {\n\t\t\tif (*in)[i] != nil {\n\t\t\t\tin, out := &(*in)[i], &(*out)[i]\n\t\t\t\t*out = new(ServiceAccountName)\n\t\t\t\t**out = **in\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MeshTLS.\nfunc (in *MeshTLS) DeepCopy() *MeshTLS {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MeshTLS)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Server) DeepCopyInto(out *Server) {\n\t*out = *in\n\tif in.Selector != nil {\n\t\tin, out := &in.Selector, &out.Selector\n\t\t*out = new(v1.LabelSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Server.\nfunc (in *Server) DeepCopy() *Server {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Server)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ServerAuthorization) DeepCopyInto(out *ServerAuthorization) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerAuthorization.\nfunc (in *ServerAuthorization) DeepCopy() *ServerAuthorization {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ServerAuthorization)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *ServerAuthorization) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ServerAuthorizationList) DeepCopyInto(out *ServerAuthorizationList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]ServerAuthorization, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerAuthorizationList.\nfunc (in *ServerAuthorizationList) DeepCopy() *ServerAuthorizationList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ServerAuthorizationList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *ServerAuthorizationList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ServerAuthorizationSpec) DeepCopyInto(out *ServerAuthorizationSpec) {\n\t*out = *in\n\tin.Server.DeepCopyInto(&out.Server)\n\tin.Client.DeepCopyInto(&out.Client)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerAuthorizationSpec.\nfunc (in *ServerAuthorizationSpec) DeepCopy() *ServerAuthorizationSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ServerAuthorizationSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ServiceAccountName) DeepCopyInto(out *ServiceAccountName) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountName.\nfunc (in *ServiceAccountName) DeepCopy() *ServiceAccountName {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ServiceAccountName)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "controller/gen/apis/serviceprofile/register.go",
    "content": "package serviceprofile\n\n// GroupName identifies the API Group Name for a ServiceProfile.\nconst GroupName = \"linkerd.io\"\n"
  },
  {
    "path": "controller/gen/apis/serviceprofile/v1alpha2/doc.go",
    "content": "// +k8s:deepcopy-gen=package\n// +groupName=linkerd.io\n\npackage v1alpha2\n"
  },
  {
    "path": "controller/gen/apis/serviceprofile/v1alpha2/register.go",
    "content": "package v1alpha2\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile\"\n)\n\n// SchemeGroupVersion is the identifier for the API which includes\n// the name of the group and the version of the API\nvar SchemeGroupVersion = schema.GroupVersion{\n\tGroup:   serviceprofile.GroupName,\n\tVersion: \"v1alpha2\",\n}\n\n// Kind takes an unqualified kind and returns back a Group qualified GroupKind\nfunc Kind(kind string) schema.GroupKind {\n\treturn SchemeGroupVersion.WithKind(kind).GroupKind()\n}\n\n// Resource takes an unqualified resource and returns a Group qualified GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\nvar (\n\t// SchemeBuilder collects functions that add things to a scheme. It's to allow\n\t// code to compile without explicitly referencing generated types. You should\n\t// declare one in each package that will have generated deep copy or conversion\n\t// functions.\n\tSchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)\n\n\t// AddToScheme applies all the stored functions to the scheme. A non-nil error\n\t// indicates that one function failed and the attempt was abandoned.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n\n// Adds the list of known types to Scheme.\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&ServiceProfile{},\n\t\t&ServiceProfileList{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "controller/gen/apis/serviceprofile/v1alpha2/types.go",
    "content": "package v1alpha2\n\nimport (\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +genclient:noStatus\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// ServiceProfile describes a serviceProfile resource\ntype ServiceProfile struct {\n\t// TypeMeta is the metadata for the resource, like kind and apiversion\n\tmetav1.TypeMeta `json:\",inline\"`\n\t// ObjectMeta contains the metadata for the particular object, including\n\t// things like...\n\t//  - name\n\t//  - namespace\n\t//  - self link\n\t//  - labels\n\t//  - ... etc ...\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Spec is the custom resource spec\n\tSpec ServiceProfileSpec `json:\"spec\"`\n}\n\n// ServiceProfileSpec specifies a ServiceProfile resource.\ntype ServiceProfileSpec struct {\n\tRoutes       []*RouteSpec        `json:\"routes,omitempty\"`\n\tRetryBudget  *RetryBudget        `json:\"retryBudget,omitempty\"`\n\tDstOverrides []*WeightedDst      `json:\"dstOverrides,omitempty\"`\n\tOpaquePorts  map[uint32]struct{} `json:\"opaquePorts,omitempty\"`\n}\n\n// RouteSpec specifies a Route resource.\ntype RouteSpec struct {\n\tName            string           `json:\"name\"`\n\tCondition       *RequestMatch    `json:\"condition\"`\n\tResponseClasses []*ResponseClass `json:\"responseClasses,omitempty\"`\n\tIsRetryable     bool             `json:\"isRetryable,omitempty\"`\n\tTimeout         string           `json:\"timeout,omitempty\"`\n}\n\n// RequestMatch describes the conditions under which to match a Route.\ntype RequestMatch struct {\n\tAll       []*RequestMatch `json:\"all,omitempty\"`\n\tNot       *RequestMatch   `json:\"not,omitempty\"`\n\tAny       []*RequestMatch `json:\"any,omitempty\"`\n\tPathRegex string          `json:\"pathRegex,omitempty\"`\n\tMethod    string          `json:\"method,omitempty\"`\n}\n\n// ResponseClass describes how to classify a response (e.g. success or\n// failures).\ntype ResponseClass struct {\n\tCondition *ResponseMatch `json:\"condition\"`\n\tIsFailure bool           `json:\"isFailure,omitempty\"`\n}\n\n// ResponseMatch describes the conditions under which to classify a response.\ntype ResponseMatch struct {\n\tAll    []*ResponseMatch `json:\"all,omitempty\"`\n\tNot    *ResponseMatch   `json:\"not,omitempty\"`\n\tAny    []*ResponseMatch `json:\"any,omitempty\"`\n\tStatus *Range           `json:\"status,omitempty\"`\n}\n\n// Range describes a range of integers (e.g. status codes).\ntype Range struct {\n\tMin uint32 `json:\"min,omitempty\"`\n\tMax uint32 `json:\"max,omitempty\"`\n}\n\n// RetryBudget describes the maximum number of retries that should be issued to\n// this service.\ntype RetryBudget struct {\n\tRetryRatio          float32 `json:\"retryRatio\"`\n\tMinRetriesPerSecond uint32  `json:\"minRetriesPerSecond\"`\n\tTTL                 string  `json:\"ttl\"`\n}\n\n// WeightedDst is a weighted alternate destination.\ntype WeightedDst struct {\n\tAuthority string            `json:\"authority\"`\n\tWeight    resource.Quantity `json:\"weight\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// ServiceProfileList is a list of ServiceProfile resources.\ntype ServiceProfileList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata\"`\n\n\tItems []ServiceProfile `json:\"items\"`\n}\n"
  },
  {
    "path": "controller/gen/apis/serviceprofile/v1alpha2/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1alpha2\n\nimport (\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Range) DeepCopyInto(out *Range) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Range.\nfunc (in *Range) DeepCopy() *Range {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Range)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *RequestMatch) DeepCopyInto(out *RequestMatch) {\n\t*out = *in\n\tif in.All != nil {\n\t\tin, out := &in.All, &out.All\n\t\t*out = make([]*RequestMatch, len(*in))\n\t\tfor i := range *in {\n\t\t\tif (*in)[i] != nil {\n\t\t\t\tin, out := &(*in)[i], &(*out)[i]\n\t\t\t\t*out = new(RequestMatch)\n\t\t\t\t(*in).DeepCopyInto(*out)\n\t\t\t}\n\t\t}\n\t}\n\tif in.Not != nil {\n\t\tin, out := &in.Not, &out.Not\n\t\t*out = new(RequestMatch)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.Any != nil {\n\t\tin, out := &in.Any, &out.Any\n\t\t*out = make([]*RequestMatch, len(*in))\n\t\tfor i := range *in {\n\t\t\tif (*in)[i] != nil {\n\t\t\t\tin, out := &(*in)[i], &(*out)[i]\n\t\t\t\t*out = new(RequestMatch)\n\t\t\t\t(*in).DeepCopyInto(*out)\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestMatch.\nfunc (in *RequestMatch) DeepCopy() *RequestMatch {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(RequestMatch)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ResponseClass) DeepCopyInto(out *ResponseClass) {\n\t*out = *in\n\tif in.Condition != nil {\n\t\tin, out := &in.Condition, &out.Condition\n\t\t*out = new(ResponseMatch)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResponseClass.\nfunc (in *ResponseClass) DeepCopy() *ResponseClass {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ResponseClass)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ResponseMatch) DeepCopyInto(out *ResponseMatch) {\n\t*out = *in\n\tif in.All != nil {\n\t\tin, out := &in.All, &out.All\n\t\t*out = make([]*ResponseMatch, len(*in))\n\t\tfor i := range *in {\n\t\t\tif (*in)[i] != nil {\n\t\t\t\tin, out := &(*in)[i], &(*out)[i]\n\t\t\t\t*out = new(ResponseMatch)\n\t\t\t\t(*in).DeepCopyInto(*out)\n\t\t\t}\n\t\t}\n\t}\n\tif in.Not != nil {\n\t\tin, out := &in.Not, &out.Not\n\t\t*out = new(ResponseMatch)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.Any != nil {\n\t\tin, out := &in.Any, &out.Any\n\t\t*out = make([]*ResponseMatch, len(*in))\n\t\tfor i := range *in {\n\t\t\tif (*in)[i] != nil {\n\t\t\t\tin, out := &(*in)[i], &(*out)[i]\n\t\t\t\t*out = new(ResponseMatch)\n\t\t\t\t(*in).DeepCopyInto(*out)\n\t\t\t}\n\t\t}\n\t}\n\tif in.Status != nil {\n\t\tin, out := &in.Status, &out.Status\n\t\t*out = new(Range)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResponseMatch.\nfunc (in *ResponseMatch) DeepCopy() *ResponseMatch {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ResponseMatch)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *RetryBudget) DeepCopyInto(out *RetryBudget) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RetryBudget.\nfunc (in *RetryBudget) DeepCopy() *RetryBudget {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(RetryBudget)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *RouteSpec) DeepCopyInto(out *RouteSpec) {\n\t*out = *in\n\tif in.Condition != nil {\n\t\tin, out := &in.Condition, &out.Condition\n\t\t*out = new(RequestMatch)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.ResponseClasses != nil {\n\t\tin, out := &in.ResponseClasses, &out.ResponseClasses\n\t\t*out = make([]*ResponseClass, len(*in))\n\t\tfor i := range *in {\n\t\t\tif (*in)[i] != nil {\n\t\t\t\tin, out := &(*in)[i], &(*out)[i]\n\t\t\t\t*out = new(ResponseClass)\n\t\t\t\t(*in).DeepCopyInto(*out)\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteSpec.\nfunc (in *RouteSpec) DeepCopy() *RouteSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(RouteSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ServiceProfile) DeepCopyInto(out *ServiceProfile) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceProfile.\nfunc (in *ServiceProfile) DeepCopy() *ServiceProfile {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ServiceProfile)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *ServiceProfile) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ServiceProfileList) DeepCopyInto(out *ServiceProfileList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]ServiceProfile, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceProfileList.\nfunc (in *ServiceProfileList) DeepCopy() *ServiceProfileList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ServiceProfileList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *ServiceProfileList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ServiceProfileSpec) DeepCopyInto(out *ServiceProfileSpec) {\n\t*out = *in\n\tif in.Routes != nil {\n\t\tin, out := &in.Routes, &out.Routes\n\t\t*out = make([]*RouteSpec, len(*in))\n\t\tfor i := range *in {\n\t\t\tif (*in)[i] != nil {\n\t\t\t\tin, out := &(*in)[i], &(*out)[i]\n\t\t\t\t*out = new(RouteSpec)\n\t\t\t\t(*in).DeepCopyInto(*out)\n\t\t\t}\n\t\t}\n\t}\n\tif in.RetryBudget != nil {\n\t\tin, out := &in.RetryBudget, &out.RetryBudget\n\t\t*out = new(RetryBudget)\n\t\t**out = **in\n\t}\n\tif in.DstOverrides != nil {\n\t\tin, out := &in.DstOverrides, &out.DstOverrides\n\t\t*out = make([]*WeightedDst, len(*in))\n\t\tfor i := range *in {\n\t\t\tif (*in)[i] != nil {\n\t\t\t\tin, out := &(*in)[i], &(*out)[i]\n\t\t\t\t*out = new(WeightedDst)\n\t\t\t\t(*in).DeepCopyInto(*out)\n\t\t\t}\n\t\t}\n\t}\n\tif in.OpaquePorts != nil {\n\t\tin, out := &in.OpaquePorts, &out.OpaquePorts\n\t\t*out = make(map[uint32]struct{}, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceProfileSpec.\nfunc (in *ServiceProfileSpec) DeepCopy() *ServiceProfileSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ServiceProfileSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *WeightedDst) DeepCopyInto(out *WeightedDst) {\n\t*out = *in\n\tout.Weight = in.Weight.DeepCopy()\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WeightedDst.\nfunc (in *WeightedDst) DeepCopy() *WeightedDst {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(WeightedDst)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "controller/gen/boilerplate.go.txt",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/clientset.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage versioned\n\nimport (\n\tfmt \"fmt\"\n\thttp \"net/http\"\n\n\texternalworkloadv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1\"\n\tlinkv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha1\"\n\tlinkv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha2\"\n\tlinkv1alpha3 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha3\"\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1\"\n\tpolicyv1beta3 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1beta3\"\n\tserverv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta1\"\n\tserverv1beta2 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta2\"\n\tserverv1beta3 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta3\"\n\tserverauthorizationv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1\"\n\tlinkerdv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2\"\n\tdiscovery \"k8s.io/client-go/discovery\"\n\trest \"k8s.io/client-go/rest\"\n\tflowcontrol \"k8s.io/client-go/util/flowcontrol\"\n)\n\ntype Interface interface {\n\tDiscovery() discovery.DiscoveryInterface\n\tExternalworkloadV1beta1() externalworkloadv1beta1.ExternalworkloadV1beta1Interface\n\tLinkV1alpha1() linkv1alpha1.LinkV1alpha1Interface\n\tLinkV1alpha2() linkv1alpha2.LinkV1alpha2Interface\n\tLinkV1alpha3() linkv1alpha3.LinkV1alpha3Interface\n\tPolicyV1alpha1() policyv1alpha1.PolicyV1alpha1Interface\n\tPolicyV1beta3() policyv1beta3.PolicyV1beta3Interface\n\tServerV1beta1() serverv1beta1.ServerV1beta1Interface\n\tServerV1beta2() serverv1beta2.ServerV1beta2Interface\n\tServerV1beta3() serverv1beta3.ServerV1beta3Interface\n\tServerauthorizationV1beta1() serverauthorizationv1beta1.ServerauthorizationV1beta1Interface\n\tLinkerdV1alpha2() linkerdv1alpha2.LinkerdV1alpha2Interface\n}\n\n// Clientset contains the clients for groups.\ntype Clientset struct {\n\t*discovery.DiscoveryClient\n\texternalworkloadV1beta1    *externalworkloadv1beta1.ExternalworkloadV1beta1Client\n\tlinkV1alpha1               *linkv1alpha1.LinkV1alpha1Client\n\tlinkV1alpha2               *linkv1alpha2.LinkV1alpha2Client\n\tlinkV1alpha3               *linkv1alpha3.LinkV1alpha3Client\n\tpolicyV1alpha1             *policyv1alpha1.PolicyV1alpha1Client\n\tpolicyV1beta3              *policyv1beta3.PolicyV1beta3Client\n\tserverV1beta1              *serverv1beta1.ServerV1beta1Client\n\tserverV1beta2              *serverv1beta2.ServerV1beta2Client\n\tserverV1beta3              *serverv1beta3.ServerV1beta3Client\n\tserverauthorizationV1beta1 *serverauthorizationv1beta1.ServerauthorizationV1beta1Client\n\tlinkerdV1alpha2            *linkerdv1alpha2.LinkerdV1alpha2Client\n}\n\n// ExternalworkloadV1beta1 retrieves the ExternalworkloadV1beta1Client\nfunc (c *Clientset) ExternalworkloadV1beta1() externalworkloadv1beta1.ExternalworkloadV1beta1Interface {\n\treturn c.externalworkloadV1beta1\n}\n\n// LinkV1alpha1 retrieves the LinkV1alpha1Client\nfunc (c *Clientset) LinkV1alpha1() linkv1alpha1.LinkV1alpha1Interface {\n\treturn c.linkV1alpha1\n}\n\n// LinkV1alpha2 retrieves the LinkV1alpha2Client\nfunc (c *Clientset) LinkV1alpha2() linkv1alpha2.LinkV1alpha2Interface {\n\treturn c.linkV1alpha2\n}\n\n// LinkV1alpha3 retrieves the LinkV1alpha3Client\nfunc (c *Clientset) LinkV1alpha3() linkv1alpha3.LinkV1alpha3Interface {\n\treturn c.linkV1alpha3\n}\n\n// PolicyV1alpha1 retrieves the PolicyV1alpha1Client\nfunc (c *Clientset) PolicyV1alpha1() policyv1alpha1.PolicyV1alpha1Interface {\n\treturn c.policyV1alpha1\n}\n\n// PolicyV1beta3 retrieves the PolicyV1beta3Client\nfunc (c *Clientset) PolicyV1beta3() policyv1beta3.PolicyV1beta3Interface {\n\treturn c.policyV1beta3\n}\n\n// ServerV1beta1 retrieves the ServerV1beta1Client\nfunc (c *Clientset) ServerV1beta1() serverv1beta1.ServerV1beta1Interface {\n\treturn c.serverV1beta1\n}\n\n// ServerV1beta2 retrieves the ServerV1beta2Client\nfunc (c *Clientset) ServerV1beta2() serverv1beta2.ServerV1beta2Interface {\n\treturn c.serverV1beta2\n}\n\n// ServerV1beta3 retrieves the ServerV1beta3Client\nfunc (c *Clientset) ServerV1beta3() serverv1beta3.ServerV1beta3Interface {\n\treturn c.serverV1beta3\n}\n\n// ServerauthorizationV1beta1 retrieves the ServerauthorizationV1beta1Client\nfunc (c *Clientset) ServerauthorizationV1beta1() serverauthorizationv1beta1.ServerauthorizationV1beta1Interface {\n\treturn c.serverauthorizationV1beta1\n}\n\n// LinkerdV1alpha2 retrieves the LinkerdV1alpha2Client\nfunc (c *Clientset) LinkerdV1alpha2() linkerdv1alpha2.LinkerdV1alpha2Interface {\n\treturn c.linkerdV1alpha2\n}\n\n// Discovery retrieves the DiscoveryClient\nfunc (c *Clientset) Discovery() discovery.DiscoveryInterface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.DiscoveryClient\n}\n\n// NewForConfig creates a new Clientset for the given config.\n// If config's RateLimiter is not set and QPS and Burst are acceptable,\n// NewForConfig will generate a rate-limiter in configShallowCopy.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*Clientset, error) {\n\tconfigShallowCopy := *c\n\n\tif configShallowCopy.UserAgent == \"\" {\n\t\tconfigShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n\n\t// share the transport between all clients\n\thttpClient, err := rest.HTTPClientFor(&configShallowCopy)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn NewForConfigAndClient(&configShallowCopy, httpClient)\n}\n\n// NewForConfigAndClient creates a new Clientset for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\n// If config's RateLimiter is not set and QPS and Burst are acceptable,\n// NewForConfigAndClient will generate a rate-limiter in configShallowCopy.\nfunc NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) {\n\tconfigShallowCopy := *c\n\tif configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {\n\t\tif configShallowCopy.Burst <= 0 {\n\t\t\treturn nil, fmt.Errorf(\"burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0\")\n\t\t}\n\t\tconfigShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)\n\t}\n\n\tvar cs Clientset\n\tvar err error\n\tcs.externalworkloadV1beta1, err = externalworkloadv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcs.linkV1alpha1, err = linkv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcs.linkV1alpha2, err = linkv1alpha2.NewForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcs.linkV1alpha3, err = linkv1alpha3.NewForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcs.policyV1alpha1, err = policyv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcs.policyV1beta3, err = policyv1beta3.NewForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcs.serverV1beta1, err = serverv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcs.serverV1beta2, err = serverv1beta2.NewForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcs.serverV1beta3, err = serverv1beta3.NewForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcs.serverauthorizationV1beta1, err = serverauthorizationv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcs.linkerdV1alpha2, err = linkerdv1alpha2.NewForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &cs, nil\n}\n\n// NewForConfigOrDie creates a new Clientset for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *Clientset {\n\tcs, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn cs\n}\n\n// New creates a new Clientset for the given RESTClient.\nfunc New(c rest.Interface) *Clientset {\n\tvar cs Clientset\n\tcs.externalworkloadV1beta1 = externalworkloadv1beta1.New(c)\n\tcs.linkV1alpha1 = linkv1alpha1.New(c)\n\tcs.linkV1alpha2 = linkv1alpha2.New(c)\n\tcs.linkV1alpha3 = linkv1alpha3.New(c)\n\tcs.policyV1alpha1 = policyv1alpha1.New(c)\n\tcs.policyV1beta3 = policyv1beta3.New(c)\n\tcs.serverV1beta1 = serverv1beta1.New(c)\n\tcs.serverV1beta2 = serverv1beta2.New(c)\n\tcs.serverV1beta3 = serverv1beta3.New(c)\n\tcs.serverauthorizationV1beta1 = serverauthorizationv1beta1.New(c)\n\tcs.linkerdV1alpha2 = linkerdv1alpha2.New(c)\n\n\tcs.DiscoveryClient = discovery.NewDiscoveryClient(c)\n\treturn &cs\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/fake/clientset_generated.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tclientset \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\texternalworkloadv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1\"\n\tfakeexternalworkloadv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/fake\"\n\tlinkv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha1\"\n\tfakelinkv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha1/fake\"\n\tlinkv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha2\"\n\tfakelinkv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha2/fake\"\n\tlinkv1alpha3 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha3\"\n\tfakelinkv1alpha3 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha3/fake\"\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1\"\n\tfakepolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1/fake\"\n\tpolicyv1beta3 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1beta3\"\n\tfakepolicyv1beta3 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1beta3/fake\"\n\tserverv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta1\"\n\tfakeserverv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta1/fake\"\n\tserverv1beta2 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta2\"\n\tfakeserverv1beta2 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta2/fake\"\n\tserverv1beta3 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta3\"\n\tfakeserverv1beta3 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta3/fake\"\n\tserverauthorizationv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1\"\n\tfakeserverauthorizationv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1/fake\"\n\tlinkerdv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2\"\n\tfakelinkerdv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2/fake\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\t\"k8s.io/client-go/discovery\"\n\tfakediscovery \"k8s.io/client-go/discovery/fake\"\n\t\"k8s.io/client-go/testing\"\n)\n\n// NewSimpleClientset returns a clientset that will respond with the provided objects.\n// It's backed by a very simple object tracker that processes creates, updates and deletions as-is,\n// without applying any field management, validations and/or defaults. It shouldn't be considered a replacement\n// for a real clientset and is mostly useful in simple unit tests.\n//\n// Deprecated: NewClientset replaces this with support for field management, which significantly improves\n// server side apply testing. NewClientset is only available when apply configurations are generated (e.g.\n// via --with-applyconfig).\nfunc NewSimpleClientset(objects ...runtime.Object) *Clientset {\n\to := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())\n\tfor _, obj := range objects {\n\t\tif err := o.Add(obj); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\tcs := &Clientset{tracker: o}\n\tcs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}\n\tcs.AddReactor(\"*\", \"*\", testing.ObjectReaction(o))\n\tcs.AddWatchReactor(\"*\", func(action testing.Action) (handled bool, ret watch.Interface, err error) {\n\t\tvar opts metav1.ListOptions\n\t\tif watchAction, ok := action.(testing.WatchActionImpl); ok {\n\t\t\topts = watchAction.ListOptions\n\t\t}\n\t\tgvr := action.GetResource()\n\t\tns := action.GetNamespace()\n\t\twatch, err := o.Watch(gvr, ns, opts)\n\t\tif err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\t\treturn true, watch, nil\n\t})\n\n\treturn cs\n}\n\n// Clientset implements clientset.Interface. Meant to be embedded into a\n// struct to get a default implementation. This makes faking out just the method\n// you want to test easier.\ntype Clientset struct {\n\ttesting.Fake\n\tdiscovery *fakediscovery.FakeDiscovery\n\ttracker   testing.ObjectTracker\n}\n\nfunc (c *Clientset) Discovery() discovery.DiscoveryInterface {\n\treturn c.discovery\n}\n\nfunc (c *Clientset) Tracker() testing.ObjectTracker {\n\treturn c.tracker\n}\n\n// IsWatchListSemanticsSupported informs the reflector that this client\n// doesn't support WatchList semantics.\n//\n// This is a synthetic method whose sole purpose is to satisfy the optional\n// interface check performed by the reflector.\n// Returning true signals that WatchList can NOT be used.\n// No additional logic is implemented here.\nfunc (c *Clientset) IsWatchListSemanticsUnSupported() bool {\n\treturn true\n}\n\nvar (\n\t_ clientset.Interface = &Clientset{}\n\t_ testing.FakeClient  = &Clientset{}\n)\n\n// ExternalworkloadV1beta1 retrieves the ExternalworkloadV1beta1Client\nfunc (c *Clientset) ExternalworkloadV1beta1() externalworkloadv1beta1.ExternalworkloadV1beta1Interface {\n\treturn &fakeexternalworkloadv1beta1.FakeExternalworkloadV1beta1{Fake: &c.Fake}\n}\n\n// LinkV1alpha1 retrieves the LinkV1alpha1Client\nfunc (c *Clientset) LinkV1alpha1() linkv1alpha1.LinkV1alpha1Interface {\n\treturn &fakelinkv1alpha1.FakeLinkV1alpha1{Fake: &c.Fake}\n}\n\n// LinkV1alpha2 retrieves the LinkV1alpha2Client\nfunc (c *Clientset) LinkV1alpha2() linkv1alpha2.LinkV1alpha2Interface {\n\treturn &fakelinkv1alpha2.FakeLinkV1alpha2{Fake: &c.Fake}\n}\n\n// LinkV1alpha3 retrieves the LinkV1alpha3Client\nfunc (c *Clientset) LinkV1alpha3() linkv1alpha3.LinkV1alpha3Interface {\n\treturn &fakelinkv1alpha3.FakeLinkV1alpha3{Fake: &c.Fake}\n}\n\n// PolicyV1alpha1 retrieves the PolicyV1alpha1Client\nfunc (c *Clientset) PolicyV1alpha1() policyv1alpha1.PolicyV1alpha1Interface {\n\treturn &fakepolicyv1alpha1.FakePolicyV1alpha1{Fake: &c.Fake}\n}\n\n// PolicyV1beta3 retrieves the PolicyV1beta3Client\nfunc (c *Clientset) PolicyV1beta3() policyv1beta3.PolicyV1beta3Interface {\n\treturn &fakepolicyv1beta3.FakePolicyV1beta3{Fake: &c.Fake}\n}\n\n// ServerV1beta1 retrieves the ServerV1beta1Client\nfunc (c *Clientset) ServerV1beta1() serverv1beta1.ServerV1beta1Interface {\n\treturn &fakeserverv1beta1.FakeServerV1beta1{Fake: &c.Fake}\n}\n\n// ServerV1beta2 retrieves the ServerV1beta2Client\nfunc (c *Clientset) ServerV1beta2() serverv1beta2.ServerV1beta2Interface {\n\treturn &fakeserverv1beta2.FakeServerV1beta2{Fake: &c.Fake}\n}\n\n// ServerV1beta3 retrieves the ServerV1beta3Client\nfunc (c *Clientset) ServerV1beta3() serverv1beta3.ServerV1beta3Interface {\n\treturn &fakeserverv1beta3.FakeServerV1beta3{Fake: &c.Fake}\n}\n\n// ServerauthorizationV1beta1 retrieves the ServerauthorizationV1beta1Client\nfunc (c *Clientset) ServerauthorizationV1beta1() serverauthorizationv1beta1.ServerauthorizationV1beta1Interface {\n\treturn &fakeserverauthorizationv1beta1.FakeServerauthorizationV1beta1{Fake: &c.Fake}\n}\n\n// LinkerdV1alpha2 retrieves the LinkerdV1alpha2Client\nfunc (c *Clientset) LinkerdV1alpha2() linkerdv1alpha2.LinkerdV1alpha2Interface {\n\treturn &fakelinkerdv1alpha2.FakeLinkerdV1alpha2{Fake: &c.Fake}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/fake/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated fake clientset.\npackage fake\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/fake/register.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\texternalworkloadv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\tlinkv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1\"\n\tlinkv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha2\"\n\tlinkv1alpha3 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tpolicyv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3\"\n\tserverv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1\"\n\tserverv1beta2 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2\"\n\tserverv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta3\"\n\tserverauthorizationv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1\"\n\tlinkerdv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tserializer \"k8s.io/apimachinery/pkg/runtime/serializer\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n)\n\nvar scheme = runtime.NewScheme()\nvar codecs = serializer.NewCodecFactory(scheme)\n\nvar localSchemeBuilder = runtime.SchemeBuilder{\n\texternalworkloadv1beta1.AddToScheme,\n\tlinkv1alpha1.AddToScheme,\n\tlinkv1alpha2.AddToScheme,\n\tlinkv1alpha3.AddToScheme,\n\tpolicyv1alpha1.AddToScheme,\n\tpolicyv1beta3.AddToScheme,\n\tserverv1beta1.AddToScheme,\n\tserverv1beta2.AddToScheme,\n\tserverv1beta3.AddToScheme,\n\tserverauthorizationv1beta1.AddToScheme,\n\tlinkerdv1alpha2.AddToScheme,\n}\n\n// AddToScheme adds all types of this clientset into the given scheme. This allows composition\n// of clientsets, like in:\n//\n//\timport (\n//\t  \"k8s.io/client-go/kubernetes\"\n//\t  clientsetscheme \"k8s.io/client-go/kubernetes/scheme\"\n//\t  aggregatorclientsetscheme \"k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme\"\n//\t)\n//\n//\tkclientset, _ := kubernetes.NewForConfig(c)\n//\t_ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)\n//\n// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types\n// correctly.\nvar AddToScheme = localSchemeBuilder.AddToScheme\n\nfunc init() {\n\tv1.AddToGroupVersion(scheme, schema.GroupVersion{Version: \"v1\"})\n\tutilruntime.Must(AddToScheme(scheme))\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/scheme/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package contains the scheme of the automatically generated clientset.\npackage scheme\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/scheme/register.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage scheme\n\nimport (\n\texternalworkloadv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\tlinkv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1\"\n\tlinkv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha2\"\n\tlinkv1alpha3 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tpolicyv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3\"\n\tserverv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1\"\n\tserverv1beta2 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2\"\n\tserverv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta3\"\n\tserverauthorizationv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1\"\n\tlinkerdv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tserializer \"k8s.io/apimachinery/pkg/runtime/serializer\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n)\n\nvar Scheme = runtime.NewScheme()\nvar Codecs = serializer.NewCodecFactory(Scheme)\nvar ParameterCodec = runtime.NewParameterCodec(Scheme)\nvar localSchemeBuilder = runtime.SchemeBuilder{\n\texternalworkloadv1beta1.AddToScheme,\n\tlinkv1alpha1.AddToScheme,\n\tlinkv1alpha2.AddToScheme,\n\tlinkv1alpha3.AddToScheme,\n\tpolicyv1alpha1.AddToScheme,\n\tpolicyv1beta3.AddToScheme,\n\tserverv1beta1.AddToScheme,\n\tserverv1beta2.AddToScheme,\n\tserverv1beta3.AddToScheme,\n\tserverauthorizationv1beta1.AddToScheme,\n\tlinkerdv1alpha2.AddToScheme,\n}\n\n// AddToScheme adds all types of this clientset into the given scheme. This allows composition\n// of clientsets, like in:\n//\n//\timport (\n//\t  \"k8s.io/client-go/kubernetes\"\n//\t  clientsetscheme \"k8s.io/client-go/kubernetes/scheme\"\n//\t  aggregatorclientsetscheme \"k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme\"\n//\t)\n//\n//\tkclientset, _ := kubernetes.NewForConfig(c)\n//\t_ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)\n//\n// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types\n// correctly.\nvar AddToScheme = localSchemeBuilder.AddToScheme\n\nfunc init() {\n\tv1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: \"v1\"})\n\tutilruntime.Must(AddToScheme(Scheme))\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated typed clients.\npackage v1beta1\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/externalworkload.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\tcontext \"context\"\n\n\texternalworkloadv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// ExternalWorkloadsGetter has a method to return a ExternalWorkloadInterface.\n// A group's client should implement this interface.\ntype ExternalWorkloadsGetter interface {\n\tExternalWorkloads(namespace string) ExternalWorkloadInterface\n}\n\n// ExternalWorkloadInterface has methods to work with ExternalWorkload resources.\ntype ExternalWorkloadInterface interface {\n\tCreate(ctx context.Context, externalWorkload *externalworkloadv1beta1.ExternalWorkload, opts v1.CreateOptions) (*externalworkloadv1beta1.ExternalWorkload, error)\n\tUpdate(ctx context.Context, externalWorkload *externalworkloadv1beta1.ExternalWorkload, opts v1.UpdateOptions) (*externalworkloadv1beta1.ExternalWorkload, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*externalworkloadv1beta1.ExternalWorkload, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*externalworkloadv1beta1.ExternalWorkloadList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *externalworkloadv1beta1.ExternalWorkload, err error)\n\tExternalWorkloadExpansion\n}\n\n// externalWorkloads implements ExternalWorkloadInterface\ntype externalWorkloads struct {\n\t*gentype.ClientWithList[*externalworkloadv1beta1.ExternalWorkload, *externalworkloadv1beta1.ExternalWorkloadList]\n}\n\n// newExternalWorkloads returns a ExternalWorkloads\nfunc newExternalWorkloads(c *ExternalworkloadV1beta1Client, namespace string) *externalWorkloads {\n\treturn &externalWorkloads{\n\t\tgentype.NewClientWithList[*externalworkloadv1beta1.ExternalWorkload, *externalworkloadv1beta1.ExternalWorkloadList](\n\t\t\t\"externalworkloads\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *externalworkloadv1beta1.ExternalWorkload { return &externalworkloadv1beta1.ExternalWorkload{} },\n\t\t\tfunc() *externalworkloadv1beta1.ExternalWorkloadList {\n\t\t\t\treturn &externalworkloadv1beta1.ExternalWorkloadList{}\n\t\t\t},\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/externalworkload_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\thttp \"net/http\"\n\n\texternalworkloadv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\trest \"k8s.io/client-go/rest\"\n)\n\ntype ExternalworkloadV1beta1Interface interface {\n\tRESTClient() rest.Interface\n\tExternalWorkloadsGetter\n}\n\n// ExternalworkloadV1beta1Client is used to interact with features provided by the externalworkload group.\ntype ExternalworkloadV1beta1Client struct {\n\trestClient rest.Interface\n}\n\nfunc (c *ExternalworkloadV1beta1Client) ExternalWorkloads(namespace string) ExternalWorkloadInterface {\n\treturn newExternalWorkloads(c, namespace)\n}\n\n// NewForConfig creates a new ExternalworkloadV1beta1Client for the given config.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*ExternalworkloadV1beta1Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\thttpClient, err := rest.HTTPClientFor(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewForConfigAndClient(&config, httpClient)\n}\n\n// NewForConfigAndClient creates a new ExternalworkloadV1beta1Client for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\nfunc NewForConfigAndClient(c *rest.Config, h *http.Client) (*ExternalworkloadV1beta1Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\tclient, err := rest.RESTClientForConfigAndClient(&config, h)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &ExternalworkloadV1beta1Client{client}, nil\n}\n\n// NewForConfigOrDie creates a new ExternalworkloadV1beta1Client for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *ExternalworkloadV1beta1Client {\n\tclient, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\n// New creates a new ExternalworkloadV1beta1Client for the given RESTClient.\nfunc New(c rest.Interface) *ExternalworkloadV1beta1Client {\n\treturn &ExternalworkloadV1beta1Client{c}\n}\n\nfunc setConfigDefaults(config *rest.Config) {\n\tgv := externalworkloadv1beta1.SchemeGroupVersion\n\tconfig.GroupVersion = &gv\n\tconfig.APIPath = \"/apis\"\n\tconfig.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()\n\n\tif config.UserAgent == \"\" {\n\t\tconfig.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *ExternalworkloadV1beta1Client) RESTClient() rest.Interface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.restClient\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/fake/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// Package fake has the automatically generated clients.\npackage fake\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/fake/fake_externalworkload.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\texternalworkloadv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeExternalWorkloads implements ExternalWorkloadInterface\ntype fakeExternalWorkloads struct {\n\t*gentype.FakeClientWithList[*v1beta1.ExternalWorkload, *v1beta1.ExternalWorkloadList]\n\tFake *FakeExternalworkloadV1beta1\n}\n\nfunc newFakeExternalWorkloads(fake *FakeExternalworkloadV1beta1, namespace string) externalworkloadv1beta1.ExternalWorkloadInterface {\n\treturn &fakeExternalWorkloads{\n\t\tgentype.NewFakeClientWithList[*v1beta1.ExternalWorkload, *v1beta1.ExternalWorkloadList](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1beta1.SchemeGroupVersion.WithResource(\"externalworkloads\"),\n\t\t\tv1beta1.SchemeGroupVersion.WithKind(\"ExternalWorkload\"),\n\t\t\tfunc() *v1beta1.ExternalWorkload { return &v1beta1.ExternalWorkload{} },\n\t\t\tfunc() *v1beta1.ExternalWorkloadList { return &v1beta1.ExternalWorkloadList{} },\n\t\t\tfunc(dst, src *v1beta1.ExternalWorkloadList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1beta1.ExternalWorkloadList) []*v1beta1.ExternalWorkload {\n\t\t\t\treturn gentype.ToPointerSlice(list.Items)\n\t\t\t},\n\t\t\tfunc(list *v1beta1.ExternalWorkloadList, items []*v1beta1.ExternalWorkload) {\n\t\t\t\tlist.Items = gentype.FromPointerSlice(items)\n\t\t\t},\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/fake/fake_externalworkload_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1\"\n\trest \"k8s.io/client-go/rest\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\ntype FakeExternalworkloadV1beta1 struct {\n\t*testing.Fake\n}\n\nfunc (c *FakeExternalworkloadV1beta1) ExternalWorkloads(namespace string) v1beta1.ExternalWorkloadInterface {\n\treturn newFakeExternalWorkloads(c, namespace)\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *FakeExternalworkloadV1beta1) RESTClient() rest.Interface {\n\tvar ret *rest.RESTClient\n\treturn ret\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/externalworkload/v1beta1/generated_expansion.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta1\n\ntype ExternalWorkloadExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha1/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated typed clients.\npackage v1alpha1\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha1/fake/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// Package fake has the automatically generated clients.\npackage fake\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha1/fake/fake_link.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1\"\n\tlinkv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha1\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeLinks implements LinkInterface\ntype fakeLinks struct {\n\t*gentype.FakeClientWithList[*v1alpha1.Link, *v1alpha1.LinkList]\n\tFake *FakeLinkV1alpha1\n}\n\nfunc newFakeLinks(fake *FakeLinkV1alpha1, namespace string) linkv1alpha1.LinkInterface {\n\treturn &fakeLinks{\n\t\tgentype.NewFakeClientWithList[*v1alpha1.Link, *v1alpha1.LinkList](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1alpha1.SchemeGroupVersion.WithResource(\"links\"),\n\t\t\tv1alpha1.SchemeGroupVersion.WithKind(\"Link\"),\n\t\t\tfunc() *v1alpha1.Link { return &v1alpha1.Link{} },\n\t\t\tfunc() *v1alpha1.LinkList { return &v1alpha1.LinkList{} },\n\t\t\tfunc(dst, src *v1alpha1.LinkList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1alpha1.LinkList) []*v1alpha1.Link { return gentype.ToPointerSlice(list.Items) },\n\t\t\tfunc(list *v1alpha1.LinkList, items []*v1alpha1.Link) { list.Items = gentype.FromPointerSlice(items) },\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha1/fake/fake_link_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha1\"\n\trest \"k8s.io/client-go/rest\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\ntype FakeLinkV1alpha1 struct {\n\t*testing.Fake\n}\n\nfunc (c *FakeLinkV1alpha1) Links(namespace string) v1alpha1.LinkInterface {\n\treturn newFakeLinks(c, namespace)\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *FakeLinkV1alpha1) RESTClient() rest.Interface {\n\tvar ret *rest.RESTClient\n\treturn ret\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha1/generated_expansion.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\ntype LinkExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha1/link.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tcontext \"context\"\n\n\tlinkv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// LinksGetter has a method to return a LinkInterface.\n// A group's client should implement this interface.\ntype LinksGetter interface {\n\tLinks(namespace string) LinkInterface\n}\n\n// LinkInterface has methods to work with Link resources.\ntype LinkInterface interface {\n\tCreate(ctx context.Context, link *linkv1alpha1.Link, opts v1.CreateOptions) (*linkv1alpha1.Link, error)\n\tUpdate(ctx context.Context, link *linkv1alpha1.Link, opts v1.UpdateOptions) (*linkv1alpha1.Link, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*linkv1alpha1.Link, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*linkv1alpha1.LinkList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *linkv1alpha1.Link, err error)\n\tLinkExpansion\n}\n\n// links implements LinkInterface\ntype links struct {\n\t*gentype.ClientWithList[*linkv1alpha1.Link, *linkv1alpha1.LinkList]\n}\n\n// newLinks returns a Links\nfunc newLinks(c *LinkV1alpha1Client, namespace string) *links {\n\treturn &links{\n\t\tgentype.NewClientWithList[*linkv1alpha1.Link, *linkv1alpha1.LinkList](\n\t\t\t\"links\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *linkv1alpha1.Link { return &linkv1alpha1.Link{} },\n\t\t\tfunc() *linkv1alpha1.LinkList { return &linkv1alpha1.LinkList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha1/link_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\thttp \"net/http\"\n\n\tlinkv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\trest \"k8s.io/client-go/rest\"\n)\n\ntype LinkV1alpha1Interface interface {\n\tRESTClient() rest.Interface\n\tLinksGetter\n}\n\n// LinkV1alpha1Client is used to interact with features provided by the link group.\ntype LinkV1alpha1Client struct {\n\trestClient rest.Interface\n}\n\nfunc (c *LinkV1alpha1Client) Links(namespace string) LinkInterface {\n\treturn newLinks(c, namespace)\n}\n\n// NewForConfig creates a new LinkV1alpha1Client for the given config.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*LinkV1alpha1Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\thttpClient, err := rest.HTTPClientFor(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewForConfigAndClient(&config, httpClient)\n}\n\n// NewForConfigAndClient creates a new LinkV1alpha1Client for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\nfunc NewForConfigAndClient(c *rest.Config, h *http.Client) (*LinkV1alpha1Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\tclient, err := rest.RESTClientForConfigAndClient(&config, h)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &LinkV1alpha1Client{client}, nil\n}\n\n// NewForConfigOrDie creates a new LinkV1alpha1Client for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *LinkV1alpha1Client {\n\tclient, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\n// New creates a new LinkV1alpha1Client for the given RESTClient.\nfunc New(c rest.Interface) *LinkV1alpha1Client {\n\treturn &LinkV1alpha1Client{c}\n}\n\nfunc setConfigDefaults(config *rest.Config) {\n\tgv := linkv1alpha1.SchemeGroupVersion\n\tconfig.GroupVersion = &gv\n\tconfig.APIPath = \"/apis\"\n\tconfig.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()\n\n\tif config.UserAgent == \"\" {\n\t\tconfig.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *LinkV1alpha1Client) RESTClient() rest.Interface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.restClient\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha2/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated typed clients.\npackage v1alpha2\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha2/fake/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// Package fake has the automatically generated clients.\npackage fake\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha2/fake/fake_link.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha2\"\n\tlinkv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha2\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeLinks implements LinkInterface\ntype fakeLinks struct {\n\t*gentype.FakeClientWithList[*v1alpha2.Link, *v1alpha2.LinkList]\n\tFake *FakeLinkV1alpha2\n}\n\nfunc newFakeLinks(fake *FakeLinkV1alpha2, namespace string) linkv1alpha2.LinkInterface {\n\treturn &fakeLinks{\n\t\tgentype.NewFakeClientWithList[*v1alpha2.Link, *v1alpha2.LinkList](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1alpha2.SchemeGroupVersion.WithResource(\"links\"),\n\t\t\tv1alpha2.SchemeGroupVersion.WithKind(\"Link\"),\n\t\t\tfunc() *v1alpha2.Link { return &v1alpha2.Link{} },\n\t\t\tfunc() *v1alpha2.LinkList { return &v1alpha2.LinkList{} },\n\t\t\tfunc(dst, src *v1alpha2.LinkList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1alpha2.LinkList) []*v1alpha2.Link { return gentype.ToPointerSlice(list.Items) },\n\t\t\tfunc(list *v1alpha2.LinkList, items []*v1alpha2.Link) { list.Items = gentype.FromPointerSlice(items) },\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha2/fake/fake_link_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha2\"\n\trest \"k8s.io/client-go/rest\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\ntype FakeLinkV1alpha2 struct {\n\t*testing.Fake\n}\n\nfunc (c *FakeLinkV1alpha2) Links(namespace string) v1alpha2.LinkInterface {\n\treturn newFakeLinks(c, namespace)\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *FakeLinkV1alpha2) RESTClient() rest.Interface {\n\tvar ret *rest.RESTClient\n\treturn ret\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha2/generated_expansion.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha2\n\ntype LinkExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha2/link.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha2\n\nimport (\n\tcontext \"context\"\n\n\tlinkv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha2\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// LinksGetter has a method to return a LinkInterface.\n// A group's client should implement this interface.\ntype LinksGetter interface {\n\tLinks(namespace string) LinkInterface\n}\n\n// LinkInterface has methods to work with Link resources.\ntype LinkInterface interface {\n\tCreate(ctx context.Context, link *linkv1alpha2.Link, opts v1.CreateOptions) (*linkv1alpha2.Link, error)\n\tUpdate(ctx context.Context, link *linkv1alpha2.Link, opts v1.UpdateOptions) (*linkv1alpha2.Link, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*linkv1alpha2.Link, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*linkv1alpha2.LinkList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *linkv1alpha2.Link, err error)\n\tLinkExpansion\n}\n\n// links implements LinkInterface\ntype links struct {\n\t*gentype.ClientWithList[*linkv1alpha2.Link, *linkv1alpha2.LinkList]\n}\n\n// newLinks returns a Links\nfunc newLinks(c *LinkV1alpha2Client, namespace string) *links {\n\treturn &links{\n\t\tgentype.NewClientWithList[*linkv1alpha2.Link, *linkv1alpha2.LinkList](\n\t\t\t\"links\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *linkv1alpha2.Link { return &linkv1alpha2.Link{} },\n\t\t\tfunc() *linkv1alpha2.LinkList { return &linkv1alpha2.LinkList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha2/link_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha2\n\nimport (\n\thttp \"net/http\"\n\n\tlinkv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha2\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\trest \"k8s.io/client-go/rest\"\n)\n\ntype LinkV1alpha2Interface interface {\n\tRESTClient() rest.Interface\n\tLinksGetter\n}\n\n// LinkV1alpha2Client is used to interact with features provided by the link group.\ntype LinkV1alpha2Client struct {\n\trestClient rest.Interface\n}\n\nfunc (c *LinkV1alpha2Client) Links(namespace string) LinkInterface {\n\treturn newLinks(c, namespace)\n}\n\n// NewForConfig creates a new LinkV1alpha2Client for the given config.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*LinkV1alpha2Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\thttpClient, err := rest.HTTPClientFor(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewForConfigAndClient(&config, httpClient)\n}\n\n// NewForConfigAndClient creates a new LinkV1alpha2Client for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\nfunc NewForConfigAndClient(c *rest.Config, h *http.Client) (*LinkV1alpha2Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\tclient, err := rest.RESTClientForConfigAndClient(&config, h)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &LinkV1alpha2Client{client}, nil\n}\n\n// NewForConfigOrDie creates a new LinkV1alpha2Client for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *LinkV1alpha2Client {\n\tclient, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\n// New creates a new LinkV1alpha2Client for the given RESTClient.\nfunc New(c rest.Interface) *LinkV1alpha2Client {\n\treturn &LinkV1alpha2Client{c}\n}\n\nfunc setConfigDefaults(config *rest.Config) {\n\tgv := linkv1alpha2.SchemeGroupVersion\n\tconfig.GroupVersion = &gv\n\tconfig.APIPath = \"/apis\"\n\tconfig.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()\n\n\tif config.UserAgent == \"\" {\n\t\tconfig.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *LinkV1alpha2Client) RESTClient() rest.Interface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.restClient\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha3/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated typed clients.\npackage v1alpha3\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha3/fake/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// Package fake has the automatically generated clients.\npackage fake\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha3/fake/fake_link.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha3 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\tlinkv1alpha3 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha3\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeLinks implements LinkInterface\ntype fakeLinks struct {\n\t*gentype.FakeClientWithList[*v1alpha3.Link, *v1alpha3.LinkList]\n\tFake *FakeLinkV1alpha3\n}\n\nfunc newFakeLinks(fake *FakeLinkV1alpha3, namespace string) linkv1alpha3.LinkInterface {\n\treturn &fakeLinks{\n\t\tgentype.NewFakeClientWithList[*v1alpha3.Link, *v1alpha3.LinkList](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1alpha3.SchemeGroupVersion.WithResource(\"links\"),\n\t\t\tv1alpha3.SchemeGroupVersion.WithKind(\"Link\"),\n\t\t\tfunc() *v1alpha3.Link { return &v1alpha3.Link{} },\n\t\t\tfunc() *v1alpha3.LinkList { return &v1alpha3.LinkList{} },\n\t\t\tfunc(dst, src *v1alpha3.LinkList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1alpha3.LinkList) []*v1alpha3.Link { return gentype.ToPointerSlice(list.Items) },\n\t\t\tfunc(list *v1alpha3.LinkList, items []*v1alpha3.Link) { list.Items = gentype.FromPointerSlice(items) },\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha3/fake/fake_link_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha3 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/link/v1alpha3\"\n\trest \"k8s.io/client-go/rest\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\ntype FakeLinkV1alpha3 struct {\n\t*testing.Fake\n}\n\nfunc (c *FakeLinkV1alpha3) Links(namespace string) v1alpha3.LinkInterface {\n\treturn newFakeLinks(c, namespace)\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *FakeLinkV1alpha3) RESTClient() rest.Interface {\n\tvar ret *rest.RESTClient\n\treturn ret\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha3/generated_expansion.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha3\n\ntype LinkExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha3/link.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha3\n\nimport (\n\tcontext \"context\"\n\n\tlinkv1alpha3 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// LinksGetter has a method to return a LinkInterface.\n// A group's client should implement this interface.\ntype LinksGetter interface {\n\tLinks(namespace string) LinkInterface\n}\n\n// LinkInterface has methods to work with Link resources.\ntype LinkInterface interface {\n\tCreate(ctx context.Context, link *linkv1alpha3.Link, opts v1.CreateOptions) (*linkv1alpha3.Link, error)\n\tUpdate(ctx context.Context, link *linkv1alpha3.Link, opts v1.UpdateOptions) (*linkv1alpha3.Link, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*linkv1alpha3.Link, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*linkv1alpha3.LinkList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *linkv1alpha3.Link, err error)\n\tLinkExpansion\n}\n\n// links implements LinkInterface\ntype links struct {\n\t*gentype.ClientWithList[*linkv1alpha3.Link, *linkv1alpha3.LinkList]\n}\n\n// newLinks returns a Links\nfunc newLinks(c *LinkV1alpha3Client, namespace string) *links {\n\treturn &links{\n\t\tgentype.NewClientWithList[*linkv1alpha3.Link, *linkv1alpha3.LinkList](\n\t\t\t\"links\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *linkv1alpha3.Link { return &linkv1alpha3.Link{} },\n\t\t\tfunc() *linkv1alpha3.LinkList { return &linkv1alpha3.LinkList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/link/v1alpha3/link_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha3\n\nimport (\n\thttp \"net/http\"\n\n\tlinkv1alpha3 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\trest \"k8s.io/client-go/rest\"\n)\n\ntype LinkV1alpha3Interface interface {\n\tRESTClient() rest.Interface\n\tLinksGetter\n}\n\n// LinkV1alpha3Client is used to interact with features provided by the link group.\ntype LinkV1alpha3Client struct {\n\trestClient rest.Interface\n}\n\nfunc (c *LinkV1alpha3Client) Links(namespace string) LinkInterface {\n\treturn newLinks(c, namespace)\n}\n\n// NewForConfig creates a new LinkV1alpha3Client for the given config.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*LinkV1alpha3Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\thttpClient, err := rest.HTTPClientFor(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewForConfigAndClient(&config, httpClient)\n}\n\n// NewForConfigAndClient creates a new LinkV1alpha3Client for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\nfunc NewForConfigAndClient(c *rest.Config, h *http.Client) (*LinkV1alpha3Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\tclient, err := rest.RESTClientForConfigAndClient(&config, h)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &LinkV1alpha3Client{client}, nil\n}\n\n// NewForConfigOrDie creates a new LinkV1alpha3Client for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *LinkV1alpha3Client {\n\tclient, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\n// New creates a new LinkV1alpha3Client for the given RESTClient.\nfunc New(c rest.Interface) *LinkV1alpha3Client {\n\treturn &LinkV1alpha3Client{c}\n}\n\nfunc setConfigDefaults(config *rest.Config) {\n\tgv := linkv1alpha3.SchemeGroupVersion\n\tconfig.GroupVersion = &gv\n\tconfig.APIPath = \"/apis\"\n\tconfig.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()\n\n\tif config.UserAgent == \"\" {\n\t\tconfig.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *LinkV1alpha3Client) RESTClient() rest.Interface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.restClient\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1alpha1/authorizationpolicy.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tcontext \"context\"\n\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// AuthorizationPoliciesGetter has a method to return a AuthorizationPolicyInterface.\n// A group's client should implement this interface.\ntype AuthorizationPoliciesGetter interface {\n\tAuthorizationPolicies(namespace string) AuthorizationPolicyInterface\n}\n\n// AuthorizationPolicyInterface has methods to work with AuthorizationPolicy resources.\ntype AuthorizationPolicyInterface interface {\n\tCreate(ctx context.Context, authorizationPolicy *policyv1alpha1.AuthorizationPolicy, opts v1.CreateOptions) (*policyv1alpha1.AuthorizationPolicy, error)\n\tUpdate(ctx context.Context, authorizationPolicy *policyv1alpha1.AuthorizationPolicy, opts v1.UpdateOptions) (*policyv1alpha1.AuthorizationPolicy, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*policyv1alpha1.AuthorizationPolicy, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*policyv1alpha1.AuthorizationPolicyList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *policyv1alpha1.AuthorizationPolicy, err error)\n\tAuthorizationPolicyExpansion\n}\n\n// authorizationPolicies implements AuthorizationPolicyInterface\ntype authorizationPolicies struct {\n\t*gentype.ClientWithList[*policyv1alpha1.AuthorizationPolicy, *policyv1alpha1.AuthorizationPolicyList]\n}\n\n// newAuthorizationPolicies returns a AuthorizationPolicies\nfunc newAuthorizationPolicies(c *PolicyV1alpha1Client, namespace string) *authorizationPolicies {\n\treturn &authorizationPolicies{\n\t\tgentype.NewClientWithList[*policyv1alpha1.AuthorizationPolicy, *policyv1alpha1.AuthorizationPolicyList](\n\t\t\t\"authorizationpolicies\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *policyv1alpha1.AuthorizationPolicy { return &policyv1alpha1.AuthorizationPolicy{} },\n\t\t\tfunc() *policyv1alpha1.AuthorizationPolicyList { return &policyv1alpha1.AuthorizationPolicyList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1alpha1/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated typed clients.\npackage v1alpha1\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1alpha1/fake/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// Package fake has the automatically generated clients.\npackage fake\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1alpha1/fake/fake_authorizationpolicy.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeAuthorizationPolicies implements AuthorizationPolicyInterface\ntype fakeAuthorizationPolicies struct {\n\t*gentype.FakeClientWithList[*v1alpha1.AuthorizationPolicy, *v1alpha1.AuthorizationPolicyList]\n\tFake *FakePolicyV1alpha1\n}\n\nfunc newFakeAuthorizationPolicies(fake *FakePolicyV1alpha1, namespace string) policyv1alpha1.AuthorizationPolicyInterface {\n\treturn &fakeAuthorizationPolicies{\n\t\tgentype.NewFakeClientWithList[*v1alpha1.AuthorizationPolicy, *v1alpha1.AuthorizationPolicyList](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1alpha1.SchemeGroupVersion.WithResource(\"authorizationpolicies\"),\n\t\t\tv1alpha1.SchemeGroupVersion.WithKind(\"AuthorizationPolicy\"),\n\t\t\tfunc() *v1alpha1.AuthorizationPolicy { return &v1alpha1.AuthorizationPolicy{} },\n\t\t\tfunc() *v1alpha1.AuthorizationPolicyList { return &v1alpha1.AuthorizationPolicyList{} },\n\t\t\tfunc(dst, src *v1alpha1.AuthorizationPolicyList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1alpha1.AuthorizationPolicyList) []*v1alpha1.AuthorizationPolicy {\n\t\t\t\treturn gentype.ToPointerSlice(list.Items)\n\t\t\t},\n\t\t\tfunc(list *v1alpha1.AuthorizationPolicyList, items []*v1alpha1.AuthorizationPolicy) {\n\t\t\t\tlist.Items = gentype.FromPointerSlice(items)\n\t\t\t},\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1alpha1/fake/fake_httproute.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeHTTPRoutes implements HTTPRouteInterface\ntype fakeHTTPRoutes struct {\n\t*gentype.FakeClientWithList[*v1alpha1.HTTPRoute, *v1alpha1.HTTPRouteList]\n\tFake *FakePolicyV1alpha1\n}\n\nfunc newFakeHTTPRoutes(fake *FakePolicyV1alpha1, namespace string) policyv1alpha1.HTTPRouteInterface {\n\treturn &fakeHTTPRoutes{\n\t\tgentype.NewFakeClientWithList[*v1alpha1.HTTPRoute, *v1alpha1.HTTPRouteList](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1alpha1.SchemeGroupVersion.WithResource(\"httproutes\"),\n\t\t\tv1alpha1.SchemeGroupVersion.WithKind(\"HTTPRoute\"),\n\t\t\tfunc() *v1alpha1.HTTPRoute { return &v1alpha1.HTTPRoute{} },\n\t\t\tfunc() *v1alpha1.HTTPRouteList { return &v1alpha1.HTTPRouteList{} },\n\t\t\tfunc(dst, src *v1alpha1.HTTPRouteList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1alpha1.HTTPRouteList) []*v1alpha1.HTTPRoute { return gentype.ToPointerSlice(list.Items) },\n\t\t\tfunc(list *v1alpha1.HTTPRouteList, items []*v1alpha1.HTTPRoute) {\n\t\t\t\tlist.Items = gentype.FromPointerSlice(items)\n\t\t\t},\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1alpha1/fake/fake_meshtlsauthentication.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeMeshTLSAuthentications implements MeshTLSAuthenticationInterface\ntype fakeMeshTLSAuthentications struct {\n\t*gentype.FakeClientWithList[*v1alpha1.MeshTLSAuthentication, *v1alpha1.MeshTLSAuthenticationList]\n\tFake *FakePolicyV1alpha1\n}\n\nfunc newFakeMeshTLSAuthentications(fake *FakePolicyV1alpha1, namespace string) policyv1alpha1.MeshTLSAuthenticationInterface {\n\treturn &fakeMeshTLSAuthentications{\n\t\tgentype.NewFakeClientWithList[*v1alpha1.MeshTLSAuthentication, *v1alpha1.MeshTLSAuthenticationList](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1alpha1.SchemeGroupVersion.WithResource(\"meshtlsauthentications\"),\n\t\t\tv1alpha1.SchemeGroupVersion.WithKind(\"MeshTLSAuthentication\"),\n\t\t\tfunc() *v1alpha1.MeshTLSAuthentication { return &v1alpha1.MeshTLSAuthentication{} },\n\t\t\tfunc() *v1alpha1.MeshTLSAuthenticationList { return &v1alpha1.MeshTLSAuthenticationList{} },\n\t\t\tfunc(dst, src *v1alpha1.MeshTLSAuthenticationList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1alpha1.MeshTLSAuthenticationList) []*v1alpha1.MeshTLSAuthentication {\n\t\t\t\treturn gentype.ToPointerSlice(list.Items)\n\t\t\t},\n\t\t\tfunc(list *v1alpha1.MeshTLSAuthenticationList, items []*v1alpha1.MeshTLSAuthentication) {\n\t\t\t\tlist.Items = gentype.FromPointerSlice(items)\n\t\t\t},\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1alpha1/fake/fake_networkauthentication.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeNetworkAuthentications implements NetworkAuthenticationInterface\ntype fakeNetworkAuthentications struct {\n\t*gentype.FakeClientWithList[*v1alpha1.NetworkAuthentication, *v1alpha1.NetworkAuthenticationList]\n\tFake *FakePolicyV1alpha1\n}\n\nfunc newFakeNetworkAuthentications(fake *FakePolicyV1alpha1, namespace string) policyv1alpha1.NetworkAuthenticationInterface {\n\treturn &fakeNetworkAuthentications{\n\t\tgentype.NewFakeClientWithList[*v1alpha1.NetworkAuthentication, *v1alpha1.NetworkAuthenticationList](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1alpha1.SchemeGroupVersion.WithResource(\"networkauthentications\"),\n\t\t\tv1alpha1.SchemeGroupVersion.WithKind(\"NetworkAuthentication\"),\n\t\t\tfunc() *v1alpha1.NetworkAuthentication { return &v1alpha1.NetworkAuthentication{} },\n\t\t\tfunc() *v1alpha1.NetworkAuthenticationList { return &v1alpha1.NetworkAuthenticationList{} },\n\t\t\tfunc(dst, src *v1alpha1.NetworkAuthenticationList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1alpha1.NetworkAuthenticationList) []*v1alpha1.NetworkAuthentication {\n\t\t\t\treturn gentype.ToPointerSlice(list.Items)\n\t\t\t},\n\t\t\tfunc(list *v1alpha1.NetworkAuthenticationList, items []*v1alpha1.NetworkAuthentication) {\n\t\t\t\tlist.Items = gentype.FromPointerSlice(items)\n\t\t\t},\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1alpha1/fake/fake_policy_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1alpha1\"\n\trest \"k8s.io/client-go/rest\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\ntype FakePolicyV1alpha1 struct {\n\t*testing.Fake\n}\n\nfunc (c *FakePolicyV1alpha1) AuthorizationPolicies(namespace string) v1alpha1.AuthorizationPolicyInterface {\n\treturn newFakeAuthorizationPolicies(c, namespace)\n}\n\nfunc (c *FakePolicyV1alpha1) HTTPRoutes(namespace string) v1alpha1.HTTPRouteInterface {\n\treturn newFakeHTTPRoutes(c, namespace)\n}\n\nfunc (c *FakePolicyV1alpha1) MeshTLSAuthentications(namespace string) v1alpha1.MeshTLSAuthenticationInterface {\n\treturn newFakeMeshTLSAuthentications(c, namespace)\n}\n\nfunc (c *FakePolicyV1alpha1) NetworkAuthentications(namespace string) v1alpha1.NetworkAuthenticationInterface {\n\treturn newFakeNetworkAuthentications(c, namespace)\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *FakePolicyV1alpha1) RESTClient() rest.Interface {\n\tvar ret *rest.RESTClient\n\treturn ret\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1alpha1/generated_expansion.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\ntype AuthorizationPolicyExpansion interface{}\n\ntype HTTPRouteExpansion interface{}\n\ntype MeshTLSAuthenticationExpansion interface{}\n\ntype NetworkAuthenticationExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1alpha1/httproute.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tcontext \"context\"\n\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// HTTPRoutesGetter has a method to return a HTTPRouteInterface.\n// A group's client should implement this interface.\ntype HTTPRoutesGetter interface {\n\tHTTPRoutes(namespace string) HTTPRouteInterface\n}\n\n// HTTPRouteInterface has methods to work with HTTPRoute resources.\ntype HTTPRouteInterface interface {\n\tCreate(ctx context.Context, hTTPRoute *policyv1alpha1.HTTPRoute, opts v1.CreateOptions) (*policyv1alpha1.HTTPRoute, error)\n\tUpdate(ctx context.Context, hTTPRoute *policyv1alpha1.HTTPRoute, opts v1.UpdateOptions) (*policyv1alpha1.HTTPRoute, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*policyv1alpha1.HTTPRoute, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*policyv1alpha1.HTTPRouteList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *policyv1alpha1.HTTPRoute, err error)\n\tHTTPRouteExpansion\n}\n\n// hTTPRoutes implements HTTPRouteInterface\ntype hTTPRoutes struct {\n\t*gentype.ClientWithList[*policyv1alpha1.HTTPRoute, *policyv1alpha1.HTTPRouteList]\n}\n\n// newHTTPRoutes returns a HTTPRoutes\nfunc newHTTPRoutes(c *PolicyV1alpha1Client, namespace string) *hTTPRoutes {\n\treturn &hTTPRoutes{\n\t\tgentype.NewClientWithList[*policyv1alpha1.HTTPRoute, *policyv1alpha1.HTTPRouteList](\n\t\t\t\"httproutes\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *policyv1alpha1.HTTPRoute { return &policyv1alpha1.HTTPRoute{} },\n\t\t\tfunc() *policyv1alpha1.HTTPRouteList { return &policyv1alpha1.HTTPRouteList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1alpha1/meshtlsauthentication.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tcontext \"context\"\n\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// MeshTLSAuthenticationsGetter has a method to return a MeshTLSAuthenticationInterface.\n// A group's client should implement this interface.\ntype MeshTLSAuthenticationsGetter interface {\n\tMeshTLSAuthentications(namespace string) MeshTLSAuthenticationInterface\n}\n\n// MeshTLSAuthenticationInterface has methods to work with MeshTLSAuthentication resources.\ntype MeshTLSAuthenticationInterface interface {\n\tCreate(ctx context.Context, meshTLSAuthentication *policyv1alpha1.MeshTLSAuthentication, opts v1.CreateOptions) (*policyv1alpha1.MeshTLSAuthentication, error)\n\tUpdate(ctx context.Context, meshTLSAuthentication *policyv1alpha1.MeshTLSAuthentication, opts v1.UpdateOptions) (*policyv1alpha1.MeshTLSAuthentication, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*policyv1alpha1.MeshTLSAuthentication, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*policyv1alpha1.MeshTLSAuthenticationList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *policyv1alpha1.MeshTLSAuthentication, err error)\n\tMeshTLSAuthenticationExpansion\n}\n\n// meshTLSAuthentications implements MeshTLSAuthenticationInterface\ntype meshTLSAuthentications struct {\n\t*gentype.ClientWithList[*policyv1alpha1.MeshTLSAuthentication, *policyv1alpha1.MeshTLSAuthenticationList]\n}\n\n// newMeshTLSAuthentications returns a MeshTLSAuthentications\nfunc newMeshTLSAuthentications(c *PolicyV1alpha1Client, namespace string) *meshTLSAuthentications {\n\treturn &meshTLSAuthentications{\n\t\tgentype.NewClientWithList[*policyv1alpha1.MeshTLSAuthentication, *policyv1alpha1.MeshTLSAuthenticationList](\n\t\t\t\"meshtlsauthentications\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *policyv1alpha1.MeshTLSAuthentication { return &policyv1alpha1.MeshTLSAuthentication{} },\n\t\t\tfunc() *policyv1alpha1.MeshTLSAuthenticationList { return &policyv1alpha1.MeshTLSAuthenticationList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1alpha1/networkauthentication.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tcontext \"context\"\n\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// NetworkAuthenticationsGetter has a method to return a NetworkAuthenticationInterface.\n// A group's client should implement this interface.\ntype NetworkAuthenticationsGetter interface {\n\tNetworkAuthentications(namespace string) NetworkAuthenticationInterface\n}\n\n// NetworkAuthenticationInterface has methods to work with NetworkAuthentication resources.\ntype NetworkAuthenticationInterface interface {\n\tCreate(ctx context.Context, networkAuthentication *policyv1alpha1.NetworkAuthentication, opts v1.CreateOptions) (*policyv1alpha1.NetworkAuthentication, error)\n\tUpdate(ctx context.Context, networkAuthentication *policyv1alpha1.NetworkAuthentication, opts v1.UpdateOptions) (*policyv1alpha1.NetworkAuthentication, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*policyv1alpha1.NetworkAuthentication, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*policyv1alpha1.NetworkAuthenticationList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *policyv1alpha1.NetworkAuthentication, err error)\n\tNetworkAuthenticationExpansion\n}\n\n// networkAuthentications implements NetworkAuthenticationInterface\ntype networkAuthentications struct {\n\t*gentype.ClientWithList[*policyv1alpha1.NetworkAuthentication, *policyv1alpha1.NetworkAuthenticationList]\n}\n\n// newNetworkAuthentications returns a NetworkAuthentications\nfunc newNetworkAuthentications(c *PolicyV1alpha1Client, namespace string) *networkAuthentications {\n\treturn &networkAuthentications{\n\t\tgentype.NewClientWithList[*policyv1alpha1.NetworkAuthentication, *policyv1alpha1.NetworkAuthenticationList](\n\t\t\t\"networkauthentications\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *policyv1alpha1.NetworkAuthentication { return &policyv1alpha1.NetworkAuthentication{} },\n\t\t\tfunc() *policyv1alpha1.NetworkAuthenticationList { return &policyv1alpha1.NetworkAuthenticationList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1alpha1/policy_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\thttp \"net/http\"\n\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\trest \"k8s.io/client-go/rest\"\n)\n\ntype PolicyV1alpha1Interface interface {\n\tRESTClient() rest.Interface\n\tAuthorizationPoliciesGetter\n\tHTTPRoutesGetter\n\tMeshTLSAuthenticationsGetter\n\tNetworkAuthenticationsGetter\n}\n\n// PolicyV1alpha1Client is used to interact with features provided by the policy group.\ntype PolicyV1alpha1Client struct {\n\trestClient rest.Interface\n}\n\nfunc (c *PolicyV1alpha1Client) AuthorizationPolicies(namespace string) AuthorizationPolicyInterface {\n\treturn newAuthorizationPolicies(c, namespace)\n}\n\nfunc (c *PolicyV1alpha1Client) HTTPRoutes(namespace string) HTTPRouteInterface {\n\treturn newHTTPRoutes(c, namespace)\n}\n\nfunc (c *PolicyV1alpha1Client) MeshTLSAuthentications(namespace string) MeshTLSAuthenticationInterface {\n\treturn newMeshTLSAuthentications(c, namespace)\n}\n\nfunc (c *PolicyV1alpha1Client) NetworkAuthentications(namespace string) NetworkAuthenticationInterface {\n\treturn newNetworkAuthentications(c, namespace)\n}\n\n// NewForConfig creates a new PolicyV1alpha1Client for the given config.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*PolicyV1alpha1Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\thttpClient, err := rest.HTTPClientFor(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewForConfigAndClient(&config, httpClient)\n}\n\n// NewForConfigAndClient creates a new PolicyV1alpha1Client for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\nfunc NewForConfigAndClient(c *rest.Config, h *http.Client) (*PolicyV1alpha1Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\tclient, err := rest.RESTClientForConfigAndClient(&config, h)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &PolicyV1alpha1Client{client}, nil\n}\n\n// NewForConfigOrDie creates a new PolicyV1alpha1Client for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *PolicyV1alpha1Client {\n\tclient, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\n// New creates a new PolicyV1alpha1Client for the given RESTClient.\nfunc New(c rest.Interface) *PolicyV1alpha1Client {\n\treturn &PolicyV1alpha1Client{c}\n}\n\nfunc setConfigDefaults(config *rest.Config) {\n\tgv := policyv1alpha1.SchemeGroupVersion\n\tconfig.GroupVersion = &gv\n\tconfig.APIPath = \"/apis\"\n\tconfig.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()\n\n\tif config.UserAgent == \"\" {\n\t\tconfig.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *PolicyV1alpha1Client) RESTClient() rest.Interface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.restClient\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1beta3/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated typed clients.\npackage v1beta3\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1beta3/fake/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// Package fake has the automatically generated clients.\npackage fake\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1beta3/fake/fake_httproute.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3\"\n\tpolicyv1beta3 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1beta3\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeHTTPRoutes implements HTTPRouteInterface\ntype fakeHTTPRoutes struct {\n\t*gentype.FakeClientWithList[*v1beta3.HTTPRoute, *v1beta3.HTTPRouteList]\n\tFake *FakePolicyV1beta3\n}\n\nfunc newFakeHTTPRoutes(fake *FakePolicyV1beta3, namespace string) policyv1beta3.HTTPRouteInterface {\n\treturn &fakeHTTPRoutes{\n\t\tgentype.NewFakeClientWithList[*v1beta3.HTTPRoute, *v1beta3.HTTPRouteList](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1beta3.SchemeGroupVersion.WithResource(\"httproutes\"),\n\t\t\tv1beta3.SchemeGroupVersion.WithKind(\"HTTPRoute\"),\n\t\t\tfunc() *v1beta3.HTTPRoute { return &v1beta3.HTTPRoute{} },\n\t\t\tfunc() *v1beta3.HTTPRouteList { return &v1beta3.HTTPRouteList{} },\n\t\t\tfunc(dst, src *v1beta3.HTTPRouteList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1beta3.HTTPRouteList) []*v1beta3.HTTPRoute { return gentype.ToPointerSlice(list.Items) },\n\t\t\tfunc(list *v1beta3.HTTPRouteList, items []*v1beta3.HTTPRoute) {\n\t\t\t\tlist.Items = gentype.FromPointerSlice(items)\n\t\t\t},\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1beta3/fake/fake_policy_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta3 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/policy/v1beta3\"\n\trest \"k8s.io/client-go/rest\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\ntype FakePolicyV1beta3 struct {\n\t*testing.Fake\n}\n\nfunc (c *FakePolicyV1beta3) HTTPRoutes(namespace string) v1beta3.HTTPRouteInterface {\n\treturn newFakeHTTPRoutes(c, namespace)\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *FakePolicyV1beta3) RESTClient() rest.Interface {\n\tvar ret *rest.RESTClient\n\treturn ret\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1beta3/generated_expansion.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta3\n\ntype HTTPRouteExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1beta3/httproute.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta3\n\nimport (\n\tcontext \"context\"\n\n\tpolicyv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// HTTPRoutesGetter has a method to return a HTTPRouteInterface.\n// A group's client should implement this interface.\ntype HTTPRoutesGetter interface {\n\tHTTPRoutes(namespace string) HTTPRouteInterface\n}\n\n// HTTPRouteInterface has methods to work with HTTPRoute resources.\ntype HTTPRouteInterface interface {\n\tCreate(ctx context.Context, hTTPRoute *policyv1beta3.HTTPRoute, opts v1.CreateOptions) (*policyv1beta3.HTTPRoute, error)\n\tUpdate(ctx context.Context, hTTPRoute *policyv1beta3.HTTPRoute, opts v1.UpdateOptions) (*policyv1beta3.HTTPRoute, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*policyv1beta3.HTTPRoute, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*policyv1beta3.HTTPRouteList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *policyv1beta3.HTTPRoute, err error)\n\tHTTPRouteExpansion\n}\n\n// hTTPRoutes implements HTTPRouteInterface\ntype hTTPRoutes struct {\n\t*gentype.ClientWithList[*policyv1beta3.HTTPRoute, *policyv1beta3.HTTPRouteList]\n}\n\n// newHTTPRoutes returns a HTTPRoutes\nfunc newHTTPRoutes(c *PolicyV1beta3Client, namespace string) *hTTPRoutes {\n\treturn &hTTPRoutes{\n\t\tgentype.NewClientWithList[*policyv1beta3.HTTPRoute, *policyv1beta3.HTTPRouteList](\n\t\t\t\"httproutes\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *policyv1beta3.HTTPRoute { return &policyv1beta3.HTTPRoute{} },\n\t\t\tfunc() *policyv1beta3.HTTPRouteList { return &policyv1beta3.HTTPRouteList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/policy/v1beta3/policy_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta3\n\nimport (\n\thttp \"net/http\"\n\n\tpolicyv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\trest \"k8s.io/client-go/rest\"\n)\n\ntype PolicyV1beta3Interface interface {\n\tRESTClient() rest.Interface\n\tHTTPRoutesGetter\n}\n\n// PolicyV1beta3Client is used to interact with features provided by the policy group.\ntype PolicyV1beta3Client struct {\n\trestClient rest.Interface\n}\n\nfunc (c *PolicyV1beta3Client) HTTPRoutes(namespace string) HTTPRouteInterface {\n\treturn newHTTPRoutes(c, namespace)\n}\n\n// NewForConfig creates a new PolicyV1beta3Client for the given config.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*PolicyV1beta3Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\thttpClient, err := rest.HTTPClientFor(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewForConfigAndClient(&config, httpClient)\n}\n\n// NewForConfigAndClient creates a new PolicyV1beta3Client for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\nfunc NewForConfigAndClient(c *rest.Config, h *http.Client) (*PolicyV1beta3Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\tclient, err := rest.RESTClientForConfigAndClient(&config, h)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &PolicyV1beta3Client{client}, nil\n}\n\n// NewForConfigOrDie creates a new PolicyV1beta3Client for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *PolicyV1beta3Client {\n\tclient, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\n// New creates a new PolicyV1beta3Client for the given RESTClient.\nfunc New(c rest.Interface) *PolicyV1beta3Client {\n\treturn &PolicyV1beta3Client{c}\n}\n\nfunc setConfigDefaults(config *rest.Config) {\n\tgv := policyv1beta3.SchemeGroupVersion\n\tconfig.GroupVersion = &gv\n\tconfig.APIPath = \"/apis\"\n\tconfig.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()\n\n\tif config.UserAgent == \"\" {\n\t\tconfig.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *PolicyV1beta3Client) RESTClient() rest.Interface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.restClient\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta1/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated typed clients.\npackage v1beta1\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta1/fake/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// Package fake has the automatically generated clients.\npackage fake\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta1/fake/fake_server.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1\"\n\tserverv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta1\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeServers implements ServerInterface\ntype fakeServers struct {\n\t*gentype.FakeClientWithList[*v1beta1.Server, *v1beta1.ServerList]\n\tFake *FakeServerV1beta1\n}\n\nfunc newFakeServers(fake *FakeServerV1beta1, namespace string) serverv1beta1.ServerInterface {\n\treturn &fakeServers{\n\t\tgentype.NewFakeClientWithList[*v1beta1.Server, *v1beta1.ServerList](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1beta1.SchemeGroupVersion.WithResource(\"servers\"),\n\t\t\tv1beta1.SchemeGroupVersion.WithKind(\"Server\"),\n\t\t\tfunc() *v1beta1.Server { return &v1beta1.Server{} },\n\t\t\tfunc() *v1beta1.ServerList { return &v1beta1.ServerList{} },\n\t\t\tfunc(dst, src *v1beta1.ServerList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1beta1.ServerList) []*v1beta1.Server { return gentype.ToPointerSlice(list.Items) },\n\t\t\tfunc(list *v1beta1.ServerList, items []*v1beta1.Server) { list.Items = gentype.FromPointerSlice(items) },\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta1/fake/fake_server_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta1\"\n\trest \"k8s.io/client-go/rest\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\ntype FakeServerV1beta1 struct {\n\t*testing.Fake\n}\n\nfunc (c *FakeServerV1beta1) Servers(namespace string) v1beta1.ServerInterface {\n\treturn newFakeServers(c, namespace)\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *FakeServerV1beta1) RESTClient() rest.Interface {\n\tvar ret *rest.RESTClient\n\treturn ret\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta1/generated_expansion.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta1\n\ntype ServerExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta1/server.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\tcontext \"context\"\n\n\tserverv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// ServersGetter has a method to return a ServerInterface.\n// A group's client should implement this interface.\ntype ServersGetter interface {\n\tServers(namespace string) ServerInterface\n}\n\n// ServerInterface has methods to work with Server resources.\ntype ServerInterface interface {\n\tCreate(ctx context.Context, server *serverv1beta1.Server, opts v1.CreateOptions) (*serverv1beta1.Server, error)\n\tUpdate(ctx context.Context, server *serverv1beta1.Server, opts v1.UpdateOptions) (*serverv1beta1.Server, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*serverv1beta1.Server, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*serverv1beta1.ServerList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *serverv1beta1.Server, err error)\n\tServerExpansion\n}\n\n// servers implements ServerInterface\ntype servers struct {\n\t*gentype.ClientWithList[*serverv1beta1.Server, *serverv1beta1.ServerList]\n}\n\n// newServers returns a Servers\nfunc newServers(c *ServerV1beta1Client, namespace string) *servers {\n\treturn &servers{\n\t\tgentype.NewClientWithList[*serverv1beta1.Server, *serverv1beta1.ServerList](\n\t\t\t\"servers\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *serverv1beta1.Server { return &serverv1beta1.Server{} },\n\t\t\tfunc() *serverv1beta1.ServerList { return &serverv1beta1.ServerList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta1/server_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\thttp \"net/http\"\n\n\tserverv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\trest \"k8s.io/client-go/rest\"\n)\n\ntype ServerV1beta1Interface interface {\n\tRESTClient() rest.Interface\n\tServersGetter\n}\n\n// ServerV1beta1Client is used to interact with features provided by the server group.\ntype ServerV1beta1Client struct {\n\trestClient rest.Interface\n}\n\nfunc (c *ServerV1beta1Client) Servers(namespace string) ServerInterface {\n\treturn newServers(c, namespace)\n}\n\n// NewForConfig creates a new ServerV1beta1Client for the given config.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*ServerV1beta1Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\thttpClient, err := rest.HTTPClientFor(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewForConfigAndClient(&config, httpClient)\n}\n\n// NewForConfigAndClient creates a new ServerV1beta1Client for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\nfunc NewForConfigAndClient(c *rest.Config, h *http.Client) (*ServerV1beta1Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\tclient, err := rest.RESTClientForConfigAndClient(&config, h)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &ServerV1beta1Client{client}, nil\n}\n\n// NewForConfigOrDie creates a new ServerV1beta1Client for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *ServerV1beta1Client {\n\tclient, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\n// New creates a new ServerV1beta1Client for the given RESTClient.\nfunc New(c rest.Interface) *ServerV1beta1Client {\n\treturn &ServerV1beta1Client{c}\n}\n\nfunc setConfigDefaults(config *rest.Config) {\n\tgv := serverv1beta1.SchemeGroupVersion\n\tconfig.GroupVersion = &gv\n\tconfig.APIPath = \"/apis\"\n\tconfig.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()\n\n\tif config.UserAgent == \"\" {\n\t\tconfig.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *ServerV1beta1Client) RESTClient() rest.Interface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.restClient\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta2/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated typed clients.\npackage v1beta2\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta2/fake/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// Package fake has the automatically generated clients.\npackage fake\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta2/fake/fake_server.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta2 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2\"\n\tserverv1beta2 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta2\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeServers implements ServerInterface\ntype fakeServers struct {\n\t*gentype.FakeClientWithList[*v1beta2.Server, *v1beta2.ServerList]\n\tFake *FakeServerV1beta2\n}\n\nfunc newFakeServers(fake *FakeServerV1beta2, namespace string) serverv1beta2.ServerInterface {\n\treturn &fakeServers{\n\t\tgentype.NewFakeClientWithList[*v1beta2.Server, *v1beta2.ServerList](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1beta2.SchemeGroupVersion.WithResource(\"servers\"),\n\t\t\tv1beta2.SchemeGroupVersion.WithKind(\"Server\"),\n\t\t\tfunc() *v1beta2.Server { return &v1beta2.Server{} },\n\t\t\tfunc() *v1beta2.ServerList { return &v1beta2.ServerList{} },\n\t\t\tfunc(dst, src *v1beta2.ServerList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1beta2.ServerList) []*v1beta2.Server { return gentype.ToPointerSlice(list.Items) },\n\t\t\tfunc(list *v1beta2.ServerList, items []*v1beta2.Server) { list.Items = gentype.FromPointerSlice(items) },\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta2/fake/fake_server_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta2 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta2\"\n\trest \"k8s.io/client-go/rest\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\ntype FakeServerV1beta2 struct {\n\t*testing.Fake\n}\n\nfunc (c *FakeServerV1beta2) Servers(namespace string) v1beta2.ServerInterface {\n\treturn newFakeServers(c, namespace)\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *FakeServerV1beta2) RESTClient() rest.Interface {\n\tvar ret *rest.RESTClient\n\treturn ret\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta2/generated_expansion.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta2\n\ntype ServerExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta2/server.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tcontext \"context\"\n\n\tserverv1beta2 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// ServersGetter has a method to return a ServerInterface.\n// A group's client should implement this interface.\ntype ServersGetter interface {\n\tServers(namespace string) ServerInterface\n}\n\n// ServerInterface has methods to work with Server resources.\ntype ServerInterface interface {\n\tCreate(ctx context.Context, server *serverv1beta2.Server, opts v1.CreateOptions) (*serverv1beta2.Server, error)\n\tUpdate(ctx context.Context, server *serverv1beta2.Server, opts v1.UpdateOptions) (*serverv1beta2.Server, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*serverv1beta2.Server, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*serverv1beta2.ServerList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *serverv1beta2.Server, err error)\n\tServerExpansion\n}\n\n// servers implements ServerInterface\ntype servers struct {\n\t*gentype.ClientWithList[*serverv1beta2.Server, *serverv1beta2.ServerList]\n}\n\n// newServers returns a Servers\nfunc newServers(c *ServerV1beta2Client, namespace string) *servers {\n\treturn &servers{\n\t\tgentype.NewClientWithList[*serverv1beta2.Server, *serverv1beta2.ServerList](\n\t\t\t\"servers\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *serverv1beta2.Server { return &serverv1beta2.Server{} },\n\t\t\tfunc() *serverv1beta2.ServerList { return &serverv1beta2.ServerList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta2/server_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\thttp \"net/http\"\n\n\tserverv1beta2 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\trest \"k8s.io/client-go/rest\"\n)\n\ntype ServerV1beta2Interface interface {\n\tRESTClient() rest.Interface\n\tServersGetter\n}\n\n// ServerV1beta2Client is used to interact with features provided by the server group.\ntype ServerV1beta2Client struct {\n\trestClient rest.Interface\n}\n\nfunc (c *ServerV1beta2Client) Servers(namespace string) ServerInterface {\n\treturn newServers(c, namespace)\n}\n\n// NewForConfig creates a new ServerV1beta2Client for the given config.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*ServerV1beta2Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\thttpClient, err := rest.HTTPClientFor(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewForConfigAndClient(&config, httpClient)\n}\n\n// NewForConfigAndClient creates a new ServerV1beta2Client for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\nfunc NewForConfigAndClient(c *rest.Config, h *http.Client) (*ServerV1beta2Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\tclient, err := rest.RESTClientForConfigAndClient(&config, h)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &ServerV1beta2Client{client}, nil\n}\n\n// NewForConfigOrDie creates a new ServerV1beta2Client for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *ServerV1beta2Client {\n\tclient, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\n// New creates a new ServerV1beta2Client for the given RESTClient.\nfunc New(c rest.Interface) *ServerV1beta2Client {\n\treturn &ServerV1beta2Client{c}\n}\n\nfunc setConfigDefaults(config *rest.Config) {\n\tgv := serverv1beta2.SchemeGroupVersion\n\tconfig.GroupVersion = &gv\n\tconfig.APIPath = \"/apis\"\n\tconfig.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()\n\n\tif config.UserAgent == \"\" {\n\t\tconfig.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *ServerV1beta2Client) RESTClient() rest.Interface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.restClient\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta3/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated typed clients.\npackage v1beta3\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta3/fake/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// Package fake has the automatically generated clients.\npackage fake\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta3/fake/fake_server.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta3\"\n\tserverv1beta3 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta3\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeServers implements ServerInterface\ntype fakeServers struct {\n\t*gentype.FakeClientWithList[*v1beta3.Server, *v1beta3.ServerList]\n\tFake *FakeServerV1beta3\n}\n\nfunc newFakeServers(fake *FakeServerV1beta3, namespace string) serverv1beta3.ServerInterface {\n\treturn &fakeServers{\n\t\tgentype.NewFakeClientWithList[*v1beta3.Server, *v1beta3.ServerList](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1beta3.SchemeGroupVersion.WithResource(\"servers\"),\n\t\t\tv1beta3.SchemeGroupVersion.WithKind(\"Server\"),\n\t\t\tfunc() *v1beta3.Server { return &v1beta3.Server{} },\n\t\t\tfunc() *v1beta3.ServerList { return &v1beta3.ServerList{} },\n\t\t\tfunc(dst, src *v1beta3.ServerList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1beta3.ServerList) []*v1beta3.Server { return gentype.ToPointerSlice(list.Items) },\n\t\t\tfunc(list *v1beta3.ServerList, items []*v1beta3.Server) { list.Items = gentype.FromPointerSlice(items) },\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta3/fake/fake_server_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta3 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/server/v1beta3\"\n\trest \"k8s.io/client-go/rest\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\ntype FakeServerV1beta3 struct {\n\t*testing.Fake\n}\n\nfunc (c *FakeServerV1beta3) Servers(namespace string) v1beta3.ServerInterface {\n\treturn newFakeServers(c, namespace)\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *FakeServerV1beta3) RESTClient() rest.Interface {\n\tvar ret *rest.RESTClient\n\treturn ret\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta3/generated_expansion.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta3\n\ntype ServerExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta3/server.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta3\n\nimport (\n\tcontext \"context\"\n\n\tserverv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta3\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// ServersGetter has a method to return a ServerInterface.\n// A group's client should implement this interface.\ntype ServersGetter interface {\n\tServers(namespace string) ServerInterface\n}\n\n// ServerInterface has methods to work with Server resources.\ntype ServerInterface interface {\n\tCreate(ctx context.Context, server *serverv1beta3.Server, opts v1.CreateOptions) (*serverv1beta3.Server, error)\n\tUpdate(ctx context.Context, server *serverv1beta3.Server, opts v1.UpdateOptions) (*serverv1beta3.Server, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*serverv1beta3.Server, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*serverv1beta3.ServerList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *serverv1beta3.Server, err error)\n\tServerExpansion\n}\n\n// servers implements ServerInterface\ntype servers struct {\n\t*gentype.ClientWithList[*serverv1beta3.Server, *serverv1beta3.ServerList]\n}\n\n// newServers returns a Servers\nfunc newServers(c *ServerV1beta3Client, namespace string) *servers {\n\treturn &servers{\n\t\tgentype.NewClientWithList[*serverv1beta3.Server, *serverv1beta3.ServerList](\n\t\t\t\"servers\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *serverv1beta3.Server { return &serverv1beta3.Server{} },\n\t\t\tfunc() *serverv1beta3.ServerList { return &serverv1beta3.ServerList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/server/v1beta3/server_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta3\n\nimport (\n\thttp \"net/http\"\n\n\tserverv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta3\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\trest \"k8s.io/client-go/rest\"\n)\n\ntype ServerV1beta3Interface interface {\n\tRESTClient() rest.Interface\n\tServersGetter\n}\n\n// ServerV1beta3Client is used to interact with features provided by the server group.\ntype ServerV1beta3Client struct {\n\trestClient rest.Interface\n}\n\nfunc (c *ServerV1beta3Client) Servers(namespace string) ServerInterface {\n\treturn newServers(c, namespace)\n}\n\n// NewForConfig creates a new ServerV1beta3Client for the given config.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*ServerV1beta3Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\thttpClient, err := rest.HTTPClientFor(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewForConfigAndClient(&config, httpClient)\n}\n\n// NewForConfigAndClient creates a new ServerV1beta3Client for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\nfunc NewForConfigAndClient(c *rest.Config, h *http.Client) (*ServerV1beta3Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\tclient, err := rest.RESTClientForConfigAndClient(&config, h)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &ServerV1beta3Client{client}, nil\n}\n\n// NewForConfigOrDie creates a new ServerV1beta3Client for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *ServerV1beta3Client {\n\tclient, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\n// New creates a new ServerV1beta3Client for the given RESTClient.\nfunc New(c rest.Interface) *ServerV1beta3Client {\n\treturn &ServerV1beta3Client{c}\n}\n\nfunc setConfigDefaults(config *rest.Config) {\n\tgv := serverv1beta3.SchemeGroupVersion\n\tconfig.GroupVersion = &gv\n\tconfig.APIPath = \"/apis\"\n\tconfig.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()\n\n\tif config.UserAgent == \"\" {\n\t\tconfig.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *ServerV1beta3Client) RESTClient() rest.Interface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.restClient\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated typed clients.\npackage v1beta1\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1/fake/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// Package fake has the automatically generated clients.\npackage fake\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1/fake/fake_serverauthorization.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1\"\n\tserverauthorizationv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeServerAuthorizations implements ServerAuthorizationInterface\ntype fakeServerAuthorizations struct {\n\t*gentype.FakeClientWithList[*v1beta1.ServerAuthorization, *v1beta1.ServerAuthorizationList]\n\tFake *FakeServerauthorizationV1beta1\n}\n\nfunc newFakeServerAuthorizations(fake *FakeServerauthorizationV1beta1, namespace string) serverauthorizationv1beta1.ServerAuthorizationInterface {\n\treturn &fakeServerAuthorizations{\n\t\tgentype.NewFakeClientWithList[*v1beta1.ServerAuthorization, *v1beta1.ServerAuthorizationList](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1beta1.SchemeGroupVersion.WithResource(\"serverauthorizations\"),\n\t\t\tv1beta1.SchemeGroupVersion.WithKind(\"ServerAuthorization\"),\n\t\t\tfunc() *v1beta1.ServerAuthorization { return &v1beta1.ServerAuthorization{} },\n\t\t\tfunc() *v1beta1.ServerAuthorizationList { return &v1beta1.ServerAuthorizationList{} },\n\t\t\tfunc(dst, src *v1beta1.ServerAuthorizationList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1beta1.ServerAuthorizationList) []*v1beta1.ServerAuthorization {\n\t\t\t\treturn gentype.ToPointerSlice(list.Items)\n\t\t\t},\n\t\t\tfunc(list *v1beta1.ServerAuthorizationList, items []*v1beta1.ServerAuthorization) {\n\t\t\t\tlist.Items = gentype.FromPointerSlice(items)\n\t\t\t},\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1/fake/fake_serverauthorization_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1\"\n\trest \"k8s.io/client-go/rest\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\ntype FakeServerauthorizationV1beta1 struct {\n\t*testing.Fake\n}\n\nfunc (c *FakeServerauthorizationV1beta1) ServerAuthorizations(namespace string) v1beta1.ServerAuthorizationInterface {\n\treturn newFakeServerAuthorizations(c, namespace)\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *FakeServerauthorizationV1beta1) RESTClient() rest.Interface {\n\tvar ret *rest.RESTClient\n\treturn ret\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1/generated_expansion.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta1\n\ntype ServerAuthorizationExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1/serverauthorization.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\tcontext \"context\"\n\n\tserverauthorizationv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// ServerAuthorizationsGetter has a method to return a ServerAuthorizationInterface.\n// A group's client should implement this interface.\ntype ServerAuthorizationsGetter interface {\n\tServerAuthorizations(namespace string) ServerAuthorizationInterface\n}\n\n// ServerAuthorizationInterface has methods to work with ServerAuthorization resources.\ntype ServerAuthorizationInterface interface {\n\tCreate(ctx context.Context, serverAuthorization *serverauthorizationv1beta1.ServerAuthorization, opts v1.CreateOptions) (*serverauthorizationv1beta1.ServerAuthorization, error)\n\tUpdate(ctx context.Context, serverAuthorization *serverauthorizationv1beta1.ServerAuthorization, opts v1.UpdateOptions) (*serverauthorizationv1beta1.ServerAuthorization, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*serverauthorizationv1beta1.ServerAuthorization, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*serverauthorizationv1beta1.ServerAuthorizationList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *serverauthorizationv1beta1.ServerAuthorization, err error)\n\tServerAuthorizationExpansion\n}\n\n// serverAuthorizations implements ServerAuthorizationInterface\ntype serverAuthorizations struct {\n\t*gentype.ClientWithList[*serverauthorizationv1beta1.ServerAuthorization, *serverauthorizationv1beta1.ServerAuthorizationList]\n}\n\n// newServerAuthorizations returns a ServerAuthorizations\nfunc newServerAuthorizations(c *ServerauthorizationV1beta1Client, namespace string) *serverAuthorizations {\n\treturn &serverAuthorizations{\n\t\tgentype.NewClientWithList[*serverauthorizationv1beta1.ServerAuthorization, *serverauthorizationv1beta1.ServerAuthorizationList](\n\t\t\t\"serverauthorizations\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *serverauthorizationv1beta1.ServerAuthorization {\n\t\t\t\treturn &serverauthorizationv1beta1.ServerAuthorization{}\n\t\t\t},\n\t\t\tfunc() *serverauthorizationv1beta1.ServerAuthorizationList {\n\t\t\t\treturn &serverauthorizationv1beta1.ServerAuthorizationList{}\n\t\t\t},\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/serverauthorization/v1beta1/serverauthorization_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\thttp \"net/http\"\n\n\tserverauthorizationv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\trest \"k8s.io/client-go/rest\"\n)\n\ntype ServerauthorizationV1beta1Interface interface {\n\tRESTClient() rest.Interface\n\tServerAuthorizationsGetter\n}\n\n// ServerauthorizationV1beta1Client is used to interact with features provided by the serverauthorization group.\ntype ServerauthorizationV1beta1Client struct {\n\trestClient rest.Interface\n}\n\nfunc (c *ServerauthorizationV1beta1Client) ServerAuthorizations(namespace string) ServerAuthorizationInterface {\n\treturn newServerAuthorizations(c, namespace)\n}\n\n// NewForConfig creates a new ServerauthorizationV1beta1Client for the given config.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*ServerauthorizationV1beta1Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\thttpClient, err := rest.HTTPClientFor(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewForConfigAndClient(&config, httpClient)\n}\n\n// NewForConfigAndClient creates a new ServerauthorizationV1beta1Client for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\nfunc NewForConfigAndClient(c *rest.Config, h *http.Client) (*ServerauthorizationV1beta1Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\tclient, err := rest.RESTClientForConfigAndClient(&config, h)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &ServerauthorizationV1beta1Client{client}, nil\n}\n\n// NewForConfigOrDie creates a new ServerauthorizationV1beta1Client for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *ServerauthorizationV1beta1Client {\n\tclient, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\n// New creates a new ServerauthorizationV1beta1Client for the given RESTClient.\nfunc New(c rest.Interface) *ServerauthorizationV1beta1Client {\n\treturn &ServerauthorizationV1beta1Client{c}\n}\n\nfunc setConfigDefaults(config *rest.Config) {\n\tgv := serverauthorizationv1beta1.SchemeGroupVersion\n\tconfig.GroupVersion = &gv\n\tconfig.APIPath = \"/apis\"\n\tconfig.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()\n\n\tif config.UserAgent == \"\" {\n\t\tconfig.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *ServerauthorizationV1beta1Client) RESTClient() rest.Interface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.restClient\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated typed clients.\npackage v1alpha2\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2/fake/doc.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\n// Package fake has the automatically generated clients.\npackage fake\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2/fake/fake_serviceprofile.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tserviceprofilev1alpha2 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeServiceProfiles implements ServiceProfileInterface\ntype fakeServiceProfiles struct {\n\t*gentype.FakeClientWithList[*v1alpha2.ServiceProfile, *v1alpha2.ServiceProfileList]\n\tFake *FakeLinkerdV1alpha2\n}\n\nfunc newFakeServiceProfiles(fake *FakeLinkerdV1alpha2, namespace string) serviceprofilev1alpha2.ServiceProfileInterface {\n\treturn &fakeServiceProfiles{\n\t\tgentype.NewFakeClientWithList[*v1alpha2.ServiceProfile, *v1alpha2.ServiceProfileList](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1alpha2.SchemeGroupVersion.WithResource(\"serviceprofiles\"),\n\t\t\tv1alpha2.SchemeGroupVersion.WithKind(\"ServiceProfile\"),\n\t\t\tfunc() *v1alpha2.ServiceProfile { return &v1alpha2.ServiceProfile{} },\n\t\t\tfunc() *v1alpha2.ServiceProfileList { return &v1alpha2.ServiceProfileList{} },\n\t\t\tfunc(dst, src *v1alpha2.ServiceProfileList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1alpha2.ServiceProfileList) []*v1alpha2.ServiceProfile {\n\t\t\t\treturn gentype.ToPointerSlice(list.Items)\n\t\t\t},\n\t\t\tfunc(list *v1alpha2.ServiceProfileList, items []*v1alpha2.ServiceProfile) {\n\t\t\t\tlist.Items = gentype.FromPointerSlice(items)\n\t\t\t},\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2/fake/fake_serviceprofile_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2\"\n\trest \"k8s.io/client-go/rest\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\ntype FakeLinkerdV1alpha2 struct {\n\t*testing.Fake\n}\n\nfunc (c *FakeLinkerdV1alpha2) ServiceProfiles(namespace string) v1alpha2.ServiceProfileInterface {\n\treturn newFakeServiceProfiles(c, namespace)\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *FakeLinkerdV1alpha2) RESTClient() rest.Interface {\n\tvar ret *rest.RESTClient\n\treturn ret\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2/generated_expansion.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha2\n\ntype ServiceProfileExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2/serviceprofile.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha2\n\nimport (\n\tcontext \"context\"\n\n\tserviceprofilev1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// ServiceProfilesGetter has a method to return a ServiceProfileInterface.\n// A group's client should implement this interface.\ntype ServiceProfilesGetter interface {\n\tServiceProfiles(namespace string) ServiceProfileInterface\n}\n\n// ServiceProfileInterface has methods to work with ServiceProfile resources.\ntype ServiceProfileInterface interface {\n\tCreate(ctx context.Context, serviceProfile *serviceprofilev1alpha2.ServiceProfile, opts v1.CreateOptions) (*serviceprofilev1alpha2.ServiceProfile, error)\n\tUpdate(ctx context.Context, serviceProfile *serviceprofilev1alpha2.ServiceProfile, opts v1.UpdateOptions) (*serviceprofilev1alpha2.ServiceProfile, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*serviceprofilev1alpha2.ServiceProfile, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*serviceprofilev1alpha2.ServiceProfileList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *serviceprofilev1alpha2.ServiceProfile, err error)\n\tServiceProfileExpansion\n}\n\n// serviceProfiles implements ServiceProfileInterface\ntype serviceProfiles struct {\n\t*gentype.ClientWithList[*serviceprofilev1alpha2.ServiceProfile, *serviceprofilev1alpha2.ServiceProfileList]\n}\n\n// newServiceProfiles returns a ServiceProfiles\nfunc newServiceProfiles(c *LinkerdV1alpha2Client, namespace string) *serviceProfiles {\n\treturn &serviceProfiles{\n\t\tgentype.NewClientWithList[*serviceprofilev1alpha2.ServiceProfile, *serviceprofilev1alpha2.ServiceProfileList](\n\t\t\t\"serviceprofiles\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *serviceprofilev1alpha2.ServiceProfile { return &serviceprofilev1alpha2.ServiceProfile{} },\n\t\t\tfunc() *serviceprofilev1alpha2.ServiceProfileList { return &serviceprofilev1alpha2.ServiceProfileList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "controller/gen/client/clientset/versioned/typed/serviceprofile/v1alpha2/serviceprofile_client.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha2\n\nimport (\n\thttp \"net/http\"\n\n\tserviceprofilev1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\trest \"k8s.io/client-go/rest\"\n)\n\ntype LinkerdV1alpha2Interface interface {\n\tRESTClient() rest.Interface\n\tServiceProfilesGetter\n}\n\n// LinkerdV1alpha2Client is used to interact with features provided by the linkerd.io group.\ntype LinkerdV1alpha2Client struct {\n\trestClient rest.Interface\n}\n\nfunc (c *LinkerdV1alpha2Client) ServiceProfiles(namespace string) ServiceProfileInterface {\n\treturn newServiceProfiles(c, namespace)\n}\n\n// NewForConfig creates a new LinkerdV1alpha2Client for the given config.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*LinkerdV1alpha2Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\thttpClient, err := rest.HTTPClientFor(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewForConfigAndClient(&config, httpClient)\n}\n\n// NewForConfigAndClient creates a new LinkerdV1alpha2Client for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\nfunc NewForConfigAndClient(c *rest.Config, h *http.Client) (*LinkerdV1alpha2Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\tclient, err := rest.RESTClientForConfigAndClient(&config, h)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &LinkerdV1alpha2Client{client}, nil\n}\n\n// NewForConfigOrDie creates a new LinkerdV1alpha2Client for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *LinkerdV1alpha2Client {\n\tclient, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\n// New creates a new LinkerdV1alpha2Client for the given RESTClient.\nfunc New(c rest.Interface) *LinkerdV1alpha2Client {\n\treturn &LinkerdV1alpha2Client{c}\n}\n\nfunc setConfigDefaults(config *rest.Config) {\n\tgv := serviceprofilev1alpha2.SchemeGroupVersion\n\tconfig.GroupVersion = &gv\n\tconfig.APIPath = \"/apis\"\n\tconfig.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()\n\n\tif config.UserAgent == \"\" {\n\t\tconfig.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *LinkerdV1alpha2Client) RESTClient() rest.Interface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.restClient\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/externalworkload/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage externalworkload\n\nimport (\n\tv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/externalworkload/v1beta1\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n)\n\n// Interface provides access to each of this group's versions.\ntype Interface interface {\n\t// V1beta1 provides access to shared informers for resources in V1beta1.\n\tV1beta1() v1beta1.Interface\n}\n\ntype group struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// V1beta1 returns a new v1beta1.Interface.\nfunc (g *group) V1beta1() v1beta1.Interface {\n\treturn v1beta1.New(g.factory, g.namespace, g.tweakListOptions)\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/externalworkload/v1beta1/externalworkload.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapisexternalworkloadv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\texternalworkloadv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/listers/externalworkload/v1beta1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ExternalWorkloadInformer provides access to a shared informer and lister for\n// ExternalWorkloads.\ntype ExternalWorkloadInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() externalworkloadv1beta1.ExternalWorkloadLister\n}\n\ntype externalWorkloadInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewExternalWorkloadInformer constructs a new informer for ExternalWorkload type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewExternalWorkloadInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredExternalWorkloadInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredExternalWorkloadInformer constructs a new informer for ExternalWorkload type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredExternalWorkloadInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\tcache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ExternalworkloadV1beta1().ExternalWorkloads(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ExternalworkloadV1beta1().ExternalWorkloads(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ExternalworkloadV1beta1().ExternalWorkloads(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ExternalworkloadV1beta1().ExternalWorkloads(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t}, client),\n\t\t&apisexternalworkloadv1beta1.ExternalWorkload{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *externalWorkloadInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredExternalWorkloadInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *externalWorkloadInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apisexternalworkloadv1beta1.ExternalWorkload{}, f.defaultInformer)\n}\n\nfunc (f *externalWorkloadInformer) Lister() externalworkloadv1beta1.ExternalWorkloadLister {\n\treturn externalworkloadv1beta1.NewExternalWorkloadLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/externalworkload/v1beta1/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n)\n\n// Interface provides access to all the informers in this group version.\ntype Interface interface {\n\t// ExternalWorkloads returns a ExternalWorkloadInformer.\n\tExternalWorkloads() ExternalWorkloadInformer\n}\n\ntype version struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// ExternalWorkloads returns a ExternalWorkloadInformer.\nfunc (v *version) ExternalWorkloads() ExternalWorkloadInformer {\n\treturn &externalWorkloadInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/factory.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage externalversions\n\nimport (\n\treflect \"reflect\"\n\tsync \"sync\"\n\ttime \"time\"\n\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\texternalworkload \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/externalworkload\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tlink \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/link\"\n\tpolicy \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/policy\"\n\tserver \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/server\"\n\tserverauthorization \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/serverauthorization\"\n\tserviceprofile \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/serviceprofile\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// SharedInformerOption defines the functional option type for SharedInformerFactory.\ntype SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory\n\ntype sharedInformerFactory struct {\n\tclient           versioned.Interface\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tlock             sync.Mutex\n\tdefaultResync    time.Duration\n\tcustomResync     map[reflect.Type]time.Duration\n\ttransform        cache.TransformFunc\n\n\tinformers map[reflect.Type]cache.SharedIndexInformer\n\t// startedInformers is used for tracking which informers have been started.\n\t// This allows Start() to be called multiple times safely.\n\tstartedInformers map[reflect.Type]bool\n\t// wg tracks how many goroutines were started.\n\twg sync.WaitGroup\n\t// shuttingDown is true when Shutdown has been called. It may still be running\n\t// because it needs to wait for goroutines.\n\tshuttingDown bool\n}\n\n// WithCustomResyncConfig sets a custom resync period for the specified informer types.\nfunc WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption {\n\treturn func(factory *sharedInformerFactory) *sharedInformerFactory {\n\t\tfor k, v := range resyncConfig {\n\t\t\tfactory.customResync[reflect.TypeOf(k)] = v\n\t\t}\n\t\treturn factory\n\t}\n}\n\n// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory.\nfunc WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption {\n\treturn func(factory *sharedInformerFactory) *sharedInformerFactory {\n\t\tfactory.tweakListOptions = tweakListOptions\n\t\treturn factory\n\t}\n}\n\n// WithNamespace limits the SharedInformerFactory to the specified namespace.\nfunc WithNamespace(namespace string) SharedInformerOption {\n\treturn func(factory *sharedInformerFactory) *sharedInformerFactory {\n\t\tfactory.namespace = namespace\n\t\treturn factory\n\t}\n}\n\n// WithTransform sets a transform on all informers.\nfunc WithTransform(transform cache.TransformFunc) SharedInformerOption {\n\treturn func(factory *sharedInformerFactory) *sharedInformerFactory {\n\t\tfactory.transform = transform\n\t\treturn factory\n\t}\n}\n\n// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces.\nfunc NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory {\n\treturn NewSharedInformerFactoryWithOptions(client, defaultResync)\n}\n\n// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory.\n// Listers obtained via this SharedInformerFactory will be subject to the same filters\n// as specified here.\n//\n// Deprecated: Please use NewSharedInformerFactoryWithOptions instead\nfunc NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory {\n\treturn NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions))\n}\n\n// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options.\nfunc NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory {\n\tfactory := &sharedInformerFactory{\n\t\tclient:           client,\n\t\tnamespace:        v1.NamespaceAll,\n\t\tdefaultResync:    defaultResync,\n\t\tinformers:        make(map[reflect.Type]cache.SharedIndexInformer),\n\t\tstartedInformers: make(map[reflect.Type]bool),\n\t\tcustomResync:     make(map[reflect.Type]time.Duration),\n\t}\n\n\t// Apply all options\n\tfor _, opt := range options {\n\t\tfactory = opt(factory)\n\t}\n\n\treturn factory\n}\n\nfunc (f *sharedInformerFactory) Start(stopCh <-chan struct{}) {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\n\tif f.shuttingDown {\n\t\treturn\n\t}\n\n\tfor informerType, informer := range f.informers {\n\t\tif !f.startedInformers[informerType] {\n\t\t\tf.wg.Add(1)\n\t\t\t// We need a new variable in each loop iteration,\n\t\t\t// otherwise the goroutine would use the loop variable\n\t\t\t// and that keeps changing.\n\t\t\tinformer := informer\n\t\t\tgo func() {\n\t\t\t\tdefer f.wg.Done()\n\t\t\t\tinformer.Run(stopCh)\n\t\t\t}()\n\t\t\tf.startedInformers[informerType] = true\n\t\t}\n\t}\n}\n\nfunc (f *sharedInformerFactory) Shutdown() {\n\tf.lock.Lock()\n\tf.shuttingDown = true\n\tf.lock.Unlock()\n\n\t// Will return immediately if there is nothing to wait for.\n\tf.wg.Wait()\n}\n\nfunc (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool {\n\tinformers := func() map[reflect.Type]cache.SharedIndexInformer {\n\t\tf.lock.Lock()\n\t\tdefer f.lock.Unlock()\n\n\t\tinformers := map[reflect.Type]cache.SharedIndexInformer{}\n\t\tfor informerType, informer := range f.informers {\n\t\t\tif f.startedInformers[informerType] {\n\t\t\t\tinformers[informerType] = informer\n\t\t\t}\n\t\t}\n\t\treturn informers\n\t}()\n\n\tres := map[reflect.Type]bool{}\n\tfor informType, informer := range informers {\n\t\tres[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced)\n\t}\n\treturn res\n}\n\n// InformerFor returns the SharedIndexInformer for obj using an internal\n// client.\nfunc (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\n\tinformerType := reflect.TypeOf(obj)\n\tinformer, exists := f.informers[informerType]\n\tif exists {\n\t\treturn informer\n\t}\n\n\tresyncPeriod, exists := f.customResync[informerType]\n\tif !exists {\n\t\tresyncPeriod = f.defaultResync\n\t}\n\n\tinformer = newFunc(f.client, resyncPeriod)\n\tinformer.SetTransform(f.transform)\n\tf.informers[informerType] = informer\n\n\treturn informer\n}\n\n// SharedInformerFactory provides shared informers for resources in all known\n// API group versions.\n//\n// It is typically used like this:\n//\n//\tctx, cancel := context.WithCancel(context.Background())\n//\tdefer cancel()\n//\tfactory := NewSharedInformerFactory(client, resyncPeriod)\n//\tdefer factory.WaitForStop()    // Returns immediately if nothing was started.\n//\tgenericInformer := factory.ForResource(resource)\n//\ttypedInformer := factory.SomeAPIGroup().V1().SomeType()\n//\tfactory.Start(ctx.Done())          // Start processing these informers.\n//\tsynced := factory.WaitForCacheSync(ctx.Done())\n//\tfor v, ok := range synced {\n//\t    if !ok {\n//\t        fmt.Fprintf(os.Stderr, \"caches failed to sync: %v\", v)\n//\t        return\n//\t    }\n//\t}\n//\n//\t// Creating informers can also be created after Start, but then\n//\t// Start must be called again:\n//\tanotherGenericInformer := factory.ForResource(resource)\n//\tfactory.Start(ctx.Done())\ntype SharedInformerFactory interface {\n\tinternalinterfaces.SharedInformerFactory\n\n\t// Start initializes all requested informers. They are handled in goroutines\n\t// which run until the stop channel gets closed.\n\t// Warning: Start does not block. When run in a go-routine, it will race with a later WaitForCacheSync.\n\tStart(stopCh <-chan struct{})\n\n\t// Shutdown marks a factory as shutting down. At that point no new\n\t// informers can be started anymore and Start will return without\n\t// doing anything.\n\t//\n\t// In addition, Shutdown blocks until all goroutines have terminated. For that\n\t// to happen, the close channel(s) that they were started with must be closed,\n\t// either before Shutdown gets called or while it is waiting.\n\t//\n\t// Shutdown may be called multiple times, even concurrently. All such calls will\n\t// block until all goroutines have terminated.\n\tShutdown()\n\n\t// WaitForCacheSync blocks until all started informers' caches were synced\n\t// or the stop channel gets closed.\n\tWaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool\n\n\t// ForResource gives generic access to a shared informer of the matching type.\n\tForResource(resource schema.GroupVersionResource) (GenericInformer, error)\n\n\t// InformerFor returns the SharedIndexInformer for obj using an internal\n\t// client.\n\tInformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer\n\n\tExternalworkload() externalworkload.Interface\n\tLink() link.Interface\n\tPolicy() policy.Interface\n\tServer() server.Interface\n\tServerauthorization() serverauthorization.Interface\n\tLinkerd() serviceprofile.Interface\n}\n\nfunc (f *sharedInformerFactory) Externalworkload() externalworkload.Interface {\n\treturn externalworkload.New(f, f.namespace, f.tweakListOptions)\n}\n\nfunc (f *sharedInformerFactory) Link() link.Interface {\n\treturn link.New(f, f.namespace, f.tweakListOptions)\n}\n\nfunc (f *sharedInformerFactory) Policy() policy.Interface {\n\treturn policy.New(f, f.namespace, f.tweakListOptions)\n}\n\nfunc (f *sharedInformerFactory) Server() server.Interface {\n\treturn server.New(f, f.namespace, f.tweakListOptions)\n}\n\nfunc (f *sharedInformerFactory) Serverauthorization() serverauthorization.Interface {\n\treturn serverauthorization.New(f, f.namespace, f.tweakListOptions)\n}\n\nfunc (f *sharedInformerFactory) Linkerd() serviceprofile.Interface {\n\treturn serviceprofile.New(f, f.namespace, f.tweakListOptions)\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/generic.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage externalversions\n\nimport (\n\tfmt \"fmt\"\n\n\tv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\tv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1\"\n\tv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha2\"\n\tv1alpha3 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3\"\n\tserverv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1\"\n\tv1beta2 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2\"\n\tserverv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta3\"\n\tserverauthorizationv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1\"\n\tserviceprofilev1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// GenericInformer is type of SharedIndexInformer which will locate and delegate to other\n// sharedInformers based on type\ntype GenericInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() cache.GenericLister\n}\n\ntype genericInformer struct {\n\tinformer cache.SharedIndexInformer\n\tresource schema.GroupResource\n}\n\n// Informer returns the SharedIndexInformer.\nfunc (f *genericInformer) Informer() cache.SharedIndexInformer {\n\treturn f.informer\n}\n\n// Lister returns the GenericLister.\nfunc (f *genericInformer) Lister() cache.GenericLister {\n\treturn cache.NewGenericLister(f.Informer().GetIndexer(), f.resource)\n}\n\n// ForResource gives generic access to a shared informer of the matching type\n// TODO extend this to unknown resources with a client pool\nfunc (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {\n\tswitch resource {\n\t// Group=externalworkload, Version=v1beta1\n\tcase v1beta1.SchemeGroupVersion.WithResource(\"externalworkloads\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Externalworkload().V1beta1().ExternalWorkloads().Informer()}, nil\n\n\t\t// Group=link, Version=v1alpha1\n\tcase v1alpha1.SchemeGroupVersion.WithResource(\"links\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Link().V1alpha1().Links().Informer()}, nil\n\n\t\t// Group=link, Version=v1alpha2\n\tcase v1alpha2.SchemeGroupVersion.WithResource(\"links\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Link().V1alpha2().Links().Informer()}, nil\n\n\t\t// Group=link, Version=v1alpha3\n\tcase v1alpha3.SchemeGroupVersion.WithResource(\"links\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Link().V1alpha3().Links().Informer()}, nil\n\n\t\t// Group=linkerd.io, Version=v1alpha2\n\tcase serviceprofilev1alpha2.SchemeGroupVersion.WithResource(\"serviceprofiles\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Linkerd().V1alpha2().ServiceProfiles().Informer()}, nil\n\n\t\t// Group=policy, Version=v1alpha1\n\tcase policyv1alpha1.SchemeGroupVersion.WithResource(\"authorizationpolicies\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Policy().V1alpha1().AuthorizationPolicies().Informer()}, nil\n\tcase policyv1alpha1.SchemeGroupVersion.WithResource(\"httproutes\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Policy().V1alpha1().HTTPRoutes().Informer()}, nil\n\tcase policyv1alpha1.SchemeGroupVersion.WithResource(\"meshtlsauthentications\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Policy().V1alpha1().MeshTLSAuthentications().Informer()}, nil\n\tcase policyv1alpha1.SchemeGroupVersion.WithResource(\"networkauthentications\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Policy().V1alpha1().NetworkAuthentications().Informer()}, nil\n\n\t\t// Group=policy, Version=v1beta3\n\tcase v1beta3.SchemeGroupVersion.WithResource(\"httproutes\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Policy().V1beta3().HTTPRoutes().Informer()}, nil\n\n\t\t// Group=server, Version=v1beta1\n\tcase serverv1beta1.SchemeGroupVersion.WithResource(\"servers\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Server().V1beta1().Servers().Informer()}, nil\n\n\t\t// Group=server, Version=v1beta2\n\tcase v1beta2.SchemeGroupVersion.WithResource(\"servers\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Server().V1beta2().Servers().Informer()}, nil\n\n\t\t// Group=server, Version=v1beta3\n\tcase serverv1beta3.SchemeGroupVersion.WithResource(\"servers\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Server().V1beta3().Servers().Informer()}, nil\n\n\t\t// Group=serverauthorization, Version=v1beta1\n\tcase serverauthorizationv1beta1.SchemeGroupVersion.WithResource(\"serverauthorizations\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Serverauthorization().V1beta1().ServerAuthorizations().Informer()}, nil\n\n\t}\n\n\treturn nil, fmt.Errorf(\"no informer found for %v\", resource)\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/internalinterfaces/factory_interfaces.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage internalinterfaces\n\nimport (\n\ttime \"time\"\n\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer.\ntype NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer\n\n// SharedInformerFactory a small interface to allow for adding an informer without an import cycle\ntype SharedInformerFactory interface {\n\tStart(stopCh <-chan struct{})\n\tInformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer\n}\n\n// TweakListOptionsFunc is a function that transforms a v1.ListOptions.\ntype TweakListOptionsFunc func(*v1.ListOptions)\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/link/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage link\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/link/v1alpha1\"\n\tv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/link/v1alpha2\"\n\tv1alpha3 \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/link/v1alpha3\"\n)\n\n// Interface provides access to each of this group's versions.\ntype Interface interface {\n\t// V1alpha1 provides access to shared informers for resources in V1alpha1.\n\tV1alpha1() v1alpha1.Interface\n\t// V1alpha2 provides access to shared informers for resources in V1alpha2.\n\tV1alpha2() v1alpha2.Interface\n\t// V1alpha3 provides access to shared informers for resources in V1alpha3.\n\tV1alpha3() v1alpha3.Interface\n}\n\ntype group struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// V1alpha1 returns a new v1alpha1.Interface.\nfunc (g *group) V1alpha1() v1alpha1.Interface {\n\treturn v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)\n}\n\n// V1alpha2 returns a new v1alpha2.Interface.\nfunc (g *group) V1alpha2() v1alpha2.Interface {\n\treturn v1alpha2.New(g.factory, g.namespace, g.tweakListOptions)\n}\n\n// V1alpha3 returns a new v1alpha3.Interface.\nfunc (g *group) V1alpha3() v1alpha3.Interface {\n\treturn v1alpha3.New(g.factory, g.namespace, g.tweakListOptions)\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/link/v1alpha1/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n)\n\n// Interface provides access to all the informers in this group version.\ntype Interface interface {\n\t// Links returns a LinkInformer.\n\tLinks() LinkInformer\n}\n\ntype version struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// Links returns a LinkInformer.\nfunc (v *version) Links() LinkInformer {\n\treturn &linkInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/link/v1alpha1/link.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapislinkv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1\"\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tlinkv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/listers/link/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// LinkInformer provides access to a shared informer and lister for\n// Links.\ntype LinkInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() linkv1alpha1.LinkLister\n}\n\ntype linkInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewLinkInformer constructs a new informer for Link type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewLinkInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredLinkInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredLinkInformer constructs a new informer for Link type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredLinkInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\tcache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkV1alpha1().Links(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkV1alpha1().Links(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkV1alpha1().Links(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkV1alpha1().Links(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t}, client),\n\t\t&apislinkv1alpha1.Link{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *linkInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredLinkInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *linkInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apislinkv1alpha1.Link{}, f.defaultInformer)\n}\n\nfunc (f *linkInformer) Lister() linkv1alpha1.LinkLister {\n\treturn linkv1alpha1.NewLinkLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/link/v1alpha2/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha2\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n)\n\n// Interface provides access to all the informers in this group version.\ntype Interface interface {\n\t// Links returns a LinkInformer.\n\tLinks() LinkInformer\n}\n\ntype version struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// Links returns a LinkInformer.\nfunc (v *version) Links() LinkInformer {\n\treturn &linkInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/link/v1alpha2/link.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha2\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapislinkv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha2\"\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tlinkv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/client/listers/link/v1alpha2\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// LinkInformer provides access to a shared informer and lister for\n// Links.\ntype LinkInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() linkv1alpha2.LinkLister\n}\n\ntype linkInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewLinkInformer constructs a new informer for Link type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewLinkInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredLinkInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredLinkInformer constructs a new informer for Link type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredLinkInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\tcache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkV1alpha2().Links(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkV1alpha2().Links(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkV1alpha2().Links(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkV1alpha2().Links(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t}, client),\n\t\t&apislinkv1alpha2.Link{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *linkInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredLinkInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *linkInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apislinkv1alpha2.Link{}, f.defaultInformer)\n}\n\nfunc (f *linkInformer) Lister() linkv1alpha2.LinkLister {\n\treturn linkv1alpha2.NewLinkLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/link/v1alpha3/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha3\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n)\n\n// Interface provides access to all the informers in this group version.\ntype Interface interface {\n\t// Links returns a LinkInformer.\n\tLinks() LinkInformer\n}\n\ntype version struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// Links returns a LinkInformer.\nfunc (v *version) Links() LinkInformer {\n\treturn &linkInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/link/v1alpha3/link.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha3\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapislinkv1alpha3 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tlinkv1alpha3 \"github.com/linkerd/linkerd2/controller/gen/client/listers/link/v1alpha3\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// LinkInformer provides access to a shared informer and lister for\n// Links.\ntype LinkInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() linkv1alpha3.LinkLister\n}\n\ntype linkInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewLinkInformer constructs a new informer for Link type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewLinkInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredLinkInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredLinkInformer constructs a new informer for Link type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredLinkInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\tcache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkV1alpha3().Links(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkV1alpha3().Links(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkV1alpha3().Links(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkV1alpha3().Links(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t}, client),\n\t\t&apislinkv1alpha3.Link{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *linkInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredLinkInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *linkInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apislinkv1alpha3.Link{}, f.defaultInformer)\n}\n\nfunc (f *linkInformer) Lister() linkv1alpha3.LinkLister {\n\treturn linkv1alpha3.NewLinkLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/policy/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage policy\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/policy/v1alpha1\"\n\tv1beta3 \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/policy/v1beta3\"\n)\n\n// Interface provides access to each of this group's versions.\ntype Interface interface {\n\t// V1alpha1 provides access to shared informers for resources in V1alpha1.\n\tV1alpha1() v1alpha1.Interface\n\t// V1beta3 provides access to shared informers for resources in V1beta3.\n\tV1beta3() v1beta3.Interface\n}\n\ntype group struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// V1alpha1 returns a new v1alpha1.Interface.\nfunc (g *group) V1alpha1() v1alpha1.Interface {\n\treturn v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)\n}\n\n// V1beta3 returns a new v1beta3.Interface.\nfunc (g *group) V1beta3() v1beta3.Interface {\n\treturn v1beta3.New(g.factory, g.namespace, g.tweakListOptions)\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/policy/v1alpha1/authorizationpolicy.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapispolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/listers/policy/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// AuthorizationPolicyInformer provides access to a shared informer and lister for\n// AuthorizationPolicies.\ntype AuthorizationPolicyInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() policyv1alpha1.AuthorizationPolicyLister\n}\n\ntype authorizationPolicyInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewAuthorizationPolicyInformer constructs a new informer for AuthorizationPolicy type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewAuthorizationPolicyInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredAuthorizationPolicyInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredAuthorizationPolicyInformer constructs a new informer for AuthorizationPolicy type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredAuthorizationPolicyInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\tcache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().AuthorizationPolicies(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().AuthorizationPolicies(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().AuthorizationPolicies(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().AuthorizationPolicies(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t}, client),\n\t\t&apispolicyv1alpha1.AuthorizationPolicy{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *authorizationPolicyInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredAuthorizationPolicyInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *authorizationPolicyInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apispolicyv1alpha1.AuthorizationPolicy{}, f.defaultInformer)\n}\n\nfunc (f *authorizationPolicyInformer) Lister() policyv1alpha1.AuthorizationPolicyLister {\n\treturn policyv1alpha1.NewAuthorizationPolicyLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/policy/v1alpha1/httproute.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapispolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/listers/policy/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// HTTPRouteInformer provides access to a shared informer and lister for\n// HTTPRoutes.\ntype HTTPRouteInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() policyv1alpha1.HTTPRouteLister\n}\n\ntype hTTPRouteInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewHTTPRouteInformer constructs a new informer for HTTPRoute type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewHTTPRouteInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredHTTPRouteInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredHTTPRouteInformer constructs a new informer for HTTPRoute type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredHTTPRouteInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\tcache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().HTTPRoutes(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().HTTPRoutes(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().HTTPRoutes(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().HTTPRoutes(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t}, client),\n\t\t&apispolicyv1alpha1.HTTPRoute{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *hTTPRouteInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredHTTPRouteInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *hTTPRouteInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apispolicyv1alpha1.HTTPRoute{}, f.defaultInformer)\n}\n\nfunc (f *hTTPRouteInformer) Lister() policyv1alpha1.HTTPRouteLister {\n\treturn policyv1alpha1.NewHTTPRouteLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/policy/v1alpha1/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n)\n\n// Interface provides access to all the informers in this group version.\ntype Interface interface {\n\t// AuthorizationPolicies returns a AuthorizationPolicyInformer.\n\tAuthorizationPolicies() AuthorizationPolicyInformer\n\t// HTTPRoutes returns a HTTPRouteInformer.\n\tHTTPRoutes() HTTPRouteInformer\n\t// MeshTLSAuthentications returns a MeshTLSAuthenticationInformer.\n\tMeshTLSAuthentications() MeshTLSAuthenticationInformer\n\t// NetworkAuthentications returns a NetworkAuthenticationInformer.\n\tNetworkAuthentications() NetworkAuthenticationInformer\n}\n\ntype version struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// AuthorizationPolicies returns a AuthorizationPolicyInformer.\nfunc (v *version) AuthorizationPolicies() AuthorizationPolicyInformer {\n\treturn &authorizationPolicyInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n\n// HTTPRoutes returns a HTTPRouteInformer.\nfunc (v *version) HTTPRoutes() HTTPRouteInformer {\n\treturn &hTTPRouteInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n\n// MeshTLSAuthentications returns a MeshTLSAuthenticationInformer.\nfunc (v *version) MeshTLSAuthentications() MeshTLSAuthenticationInformer {\n\treturn &meshTLSAuthenticationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n\n// NetworkAuthentications returns a NetworkAuthenticationInformer.\nfunc (v *version) NetworkAuthentications() NetworkAuthenticationInformer {\n\treturn &networkAuthenticationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/policy/v1alpha1/meshtlsauthentication.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapispolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/listers/policy/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// MeshTLSAuthenticationInformer provides access to a shared informer and lister for\n// MeshTLSAuthentications.\ntype MeshTLSAuthenticationInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() policyv1alpha1.MeshTLSAuthenticationLister\n}\n\ntype meshTLSAuthenticationInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewMeshTLSAuthenticationInformer constructs a new informer for MeshTLSAuthentication type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewMeshTLSAuthenticationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredMeshTLSAuthenticationInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredMeshTLSAuthenticationInformer constructs a new informer for MeshTLSAuthentication type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredMeshTLSAuthenticationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\tcache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().MeshTLSAuthentications(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().MeshTLSAuthentications(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().MeshTLSAuthentications(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().MeshTLSAuthentications(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t}, client),\n\t\t&apispolicyv1alpha1.MeshTLSAuthentication{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *meshTLSAuthenticationInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredMeshTLSAuthenticationInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *meshTLSAuthenticationInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apispolicyv1alpha1.MeshTLSAuthentication{}, f.defaultInformer)\n}\n\nfunc (f *meshTLSAuthenticationInformer) Lister() policyv1alpha1.MeshTLSAuthenticationLister {\n\treturn policyv1alpha1.NewMeshTLSAuthenticationLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/policy/v1alpha1/networkauthentication.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapispolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/client/listers/policy/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// NetworkAuthenticationInformer provides access to a shared informer and lister for\n// NetworkAuthentications.\ntype NetworkAuthenticationInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() policyv1alpha1.NetworkAuthenticationLister\n}\n\ntype networkAuthenticationInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewNetworkAuthenticationInformer constructs a new informer for NetworkAuthentication type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewNetworkAuthenticationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredNetworkAuthenticationInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredNetworkAuthenticationInformer constructs a new informer for NetworkAuthentication type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredNetworkAuthenticationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\tcache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().NetworkAuthentications(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().NetworkAuthentications(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().NetworkAuthentications(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1alpha1().NetworkAuthentications(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t}, client),\n\t\t&apispolicyv1alpha1.NetworkAuthentication{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *networkAuthenticationInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredNetworkAuthenticationInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *networkAuthenticationInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apispolicyv1alpha1.NetworkAuthentication{}, f.defaultInformer)\n}\n\nfunc (f *networkAuthenticationInformer) Lister() policyv1alpha1.NetworkAuthenticationLister {\n\treturn policyv1alpha1.NewNetworkAuthenticationLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/policy/v1beta3/httproute.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta3\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapispolicyv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3\"\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tpolicyv1beta3 \"github.com/linkerd/linkerd2/controller/gen/client/listers/policy/v1beta3\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// HTTPRouteInformer provides access to a shared informer and lister for\n// HTTPRoutes.\ntype HTTPRouteInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() policyv1beta3.HTTPRouteLister\n}\n\ntype hTTPRouteInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewHTTPRouteInformer constructs a new informer for HTTPRoute type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewHTTPRouteInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredHTTPRouteInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredHTTPRouteInformer constructs a new informer for HTTPRoute type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredHTTPRouteInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\tcache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1beta3().HTTPRoutes(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1beta3().HTTPRoutes(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1beta3().HTTPRoutes(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.PolicyV1beta3().HTTPRoutes(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t}, client),\n\t\t&apispolicyv1beta3.HTTPRoute{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *hTTPRouteInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredHTTPRouteInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *hTTPRouteInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apispolicyv1beta3.HTTPRoute{}, f.defaultInformer)\n}\n\nfunc (f *hTTPRouteInformer) Lister() policyv1beta3.HTTPRouteLister {\n\treturn policyv1beta3.NewHTTPRouteLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/policy/v1beta3/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta3\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n)\n\n// Interface provides access to all the informers in this group version.\ntype Interface interface {\n\t// HTTPRoutes returns a HTTPRouteInformer.\n\tHTTPRoutes() HTTPRouteInformer\n}\n\ntype version struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// HTTPRoutes returns a HTTPRouteInformer.\nfunc (v *version) HTTPRoutes() HTTPRouteInformer {\n\treturn &hTTPRouteInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/server/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage server\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/server/v1beta1\"\n\tv1beta2 \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/server/v1beta2\"\n\tv1beta3 \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/server/v1beta3\"\n)\n\n// Interface provides access to each of this group's versions.\ntype Interface interface {\n\t// V1beta1 provides access to shared informers for resources in V1beta1.\n\tV1beta1() v1beta1.Interface\n\t// V1beta2 provides access to shared informers for resources in V1beta2.\n\tV1beta2() v1beta2.Interface\n\t// V1beta3 provides access to shared informers for resources in V1beta3.\n\tV1beta3() v1beta3.Interface\n}\n\ntype group struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// V1beta1 returns a new v1beta1.Interface.\nfunc (g *group) V1beta1() v1beta1.Interface {\n\treturn v1beta1.New(g.factory, g.namespace, g.tweakListOptions)\n}\n\n// V1beta2 returns a new v1beta2.Interface.\nfunc (g *group) V1beta2() v1beta2.Interface {\n\treturn v1beta2.New(g.factory, g.namespace, g.tweakListOptions)\n}\n\n// V1beta3 returns a new v1beta3.Interface.\nfunc (g *group) V1beta3() v1beta3.Interface {\n\treturn v1beta3.New(g.factory, g.namespace, g.tweakListOptions)\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/server/v1beta1/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n)\n\n// Interface provides access to all the informers in this group version.\ntype Interface interface {\n\t// Servers returns a ServerInformer.\n\tServers() ServerInformer\n}\n\ntype version struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// Servers returns a ServerInformer.\nfunc (v *version) Servers() ServerInformer {\n\treturn &serverInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/server/v1beta1/server.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapisserverv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1\"\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tserverv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/listers/server/v1beta1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ServerInformer provides access to a shared informer and lister for\n// Servers.\ntype ServerInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() serverv1beta1.ServerLister\n}\n\ntype serverInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewServerInformer constructs a new informer for Server type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewServerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredServerInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredServerInformer constructs a new informer for Server type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredServerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\tcache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerV1beta1().Servers(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerV1beta1().Servers(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerV1beta1().Servers(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerV1beta1().Servers(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t}, client),\n\t\t&apisserverv1beta1.Server{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *serverInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredServerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *serverInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apisserverv1beta1.Server{}, f.defaultInformer)\n}\n\nfunc (f *serverInformer) Lister() serverv1beta1.ServerLister {\n\treturn serverv1beta1.NewServerLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/server/v1beta2/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n)\n\n// Interface provides access to all the informers in this group version.\ntype Interface interface {\n\t// Servers returns a ServerInformer.\n\tServers() ServerInformer\n}\n\ntype version struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// Servers returns a ServerInformer.\nfunc (v *version) Servers() ServerInformer {\n\treturn &serverInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/server/v1beta2/server.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapisserverv1beta2 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2\"\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tserverv1beta2 \"github.com/linkerd/linkerd2/controller/gen/client/listers/server/v1beta2\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ServerInformer provides access to a shared informer and lister for\n// Servers.\ntype ServerInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() serverv1beta2.ServerLister\n}\n\ntype serverInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewServerInformer constructs a new informer for Server type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewServerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredServerInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredServerInformer constructs a new informer for Server type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredServerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\tcache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerV1beta2().Servers(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerV1beta2().Servers(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerV1beta2().Servers(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerV1beta2().Servers(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t}, client),\n\t\t&apisserverv1beta2.Server{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *serverInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredServerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *serverInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apisserverv1beta2.Server{}, f.defaultInformer)\n}\n\nfunc (f *serverInformer) Lister() serverv1beta2.ServerLister {\n\treturn serverv1beta2.NewServerLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/server/v1beta3/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta3\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n)\n\n// Interface provides access to all the informers in this group version.\ntype Interface interface {\n\t// Servers returns a ServerInformer.\n\tServers() ServerInformer\n}\n\ntype version struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// Servers returns a ServerInformer.\nfunc (v *version) Servers() ServerInformer {\n\treturn &serverInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/server/v1beta3/server.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta3\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapisserverv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta3\"\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tserverv1beta3 \"github.com/linkerd/linkerd2/controller/gen/client/listers/server/v1beta3\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ServerInformer provides access to a shared informer and lister for\n// Servers.\ntype ServerInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() serverv1beta3.ServerLister\n}\n\ntype serverInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewServerInformer constructs a new informer for Server type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewServerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredServerInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredServerInformer constructs a new informer for Server type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredServerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\tcache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerV1beta3().Servers(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerV1beta3().Servers(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerV1beta3().Servers(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerV1beta3().Servers(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t}, client),\n\t\t&apisserverv1beta3.Server{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *serverInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredServerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *serverInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apisserverv1beta3.Server{}, f.defaultInformer)\n}\n\nfunc (f *serverInformer) Lister() serverv1beta3.ServerLister {\n\treturn serverv1beta3.NewServerLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/serverauthorization/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage serverauthorization\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/serverauthorization/v1beta1\"\n)\n\n// Interface provides access to each of this group's versions.\ntype Interface interface {\n\t// V1beta1 provides access to shared informers for resources in V1beta1.\n\tV1beta1() v1beta1.Interface\n}\n\ntype group struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// V1beta1 returns a new v1beta1.Interface.\nfunc (g *group) V1beta1() v1beta1.Interface {\n\treturn v1beta1.New(g.factory, g.namespace, g.tweakListOptions)\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/serverauthorization/v1beta1/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n)\n\n// Interface provides access to all the informers in this group version.\ntype Interface interface {\n\t// ServerAuthorizations returns a ServerAuthorizationInformer.\n\tServerAuthorizations() ServerAuthorizationInformer\n}\n\ntype version struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// ServerAuthorizations returns a ServerAuthorizationInformer.\nfunc (v *version) ServerAuthorizations() ServerAuthorizationInformer {\n\treturn &serverAuthorizationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/serverauthorization/v1beta1/serverauthorization.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapisserverauthorizationv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1\"\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tserverauthorizationv1beta1 \"github.com/linkerd/linkerd2/controller/gen/client/listers/serverauthorization/v1beta1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ServerAuthorizationInformer provides access to a shared informer and lister for\n// ServerAuthorizations.\ntype ServerAuthorizationInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() serverauthorizationv1beta1.ServerAuthorizationLister\n}\n\ntype serverAuthorizationInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewServerAuthorizationInformer constructs a new informer for ServerAuthorization type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewServerAuthorizationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredServerAuthorizationInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredServerAuthorizationInformer constructs a new informer for ServerAuthorization type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredServerAuthorizationInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\tcache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerauthorizationV1beta1().ServerAuthorizations(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerauthorizationV1beta1().ServerAuthorizations(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerauthorizationV1beta1().ServerAuthorizations(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.ServerauthorizationV1beta1().ServerAuthorizations(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t}, client),\n\t\t&apisserverauthorizationv1beta1.ServerAuthorization{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *serverAuthorizationInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredServerAuthorizationInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *serverAuthorizationInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apisserverauthorizationv1beta1.ServerAuthorization{}, f.defaultInformer)\n}\n\nfunc (f *serverAuthorizationInformer) Lister() serverauthorizationv1beta1.ServerAuthorizationLister {\n\treturn serverauthorizationv1beta1.NewServerAuthorizationLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/serviceprofile/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage serviceprofile\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/serviceprofile/v1alpha2\"\n)\n\n// Interface provides access to each of this group's versions.\ntype Interface interface {\n\t// V1alpha2 provides access to shared informers for resources in V1alpha2.\n\tV1alpha2() v1alpha2.Interface\n}\n\ntype group struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// V1alpha2 returns a new v1alpha2.Interface.\nfunc (g *group) V1alpha2() v1alpha2.Interface {\n\treturn v1alpha2.New(g.factory, g.namespace, g.tweakListOptions)\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/serviceprofile/v1alpha2/interface.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha2\n\nimport (\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n)\n\n// Interface provides access to all the informers in this group version.\ntype Interface interface {\n\t// ServiceProfiles returns a ServiceProfileInformer.\n\tServiceProfiles() ServiceProfileInformer\n}\n\ntype version struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// ServiceProfiles returns a ServiceProfileInformer.\nfunc (v *version) ServiceProfiles() ServiceProfileInformer {\n\treturn &serviceProfileInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n"
  },
  {
    "path": "controller/gen/client/informers/externalversions/serviceprofile/v1alpha2/serviceprofile.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha2\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapisserviceprofilev1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tversioned \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/internalinterfaces\"\n\tserviceprofilev1alpha2 \"github.com/linkerd/linkerd2/controller/gen/client/listers/serviceprofile/v1alpha2\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ServiceProfileInformer provides access to a shared informer and lister for\n// ServiceProfiles.\ntype ServiceProfileInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() serviceprofilev1alpha2.ServiceProfileLister\n}\n\ntype serviceProfileInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewServiceProfileInformer constructs a new informer for ServiceProfile type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewServiceProfileInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredServiceProfileInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredServiceProfileInformer constructs a new informer for ServiceProfile type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredServiceProfileInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\tcache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkerdV1alpha2().ServiceProfiles(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkerdV1alpha2().ServiceProfiles(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkerdV1alpha2().ServiceProfiles(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.LinkerdV1alpha2().ServiceProfiles(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t}, client),\n\t\t&apisserviceprofilev1alpha2.ServiceProfile{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *serviceProfileInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredServiceProfileInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *serviceProfileInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apisserviceprofilev1alpha2.ServiceProfile{}, f.defaultInformer)\n}\n\nfunc (f *serviceProfileInformer) Lister() serviceprofilev1alpha2.ServiceProfileLister {\n\treturn serviceprofilev1alpha2.NewServiceProfileLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "controller/gen/client/listers/externalworkload/v1beta1/expansion_generated.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta1\n\n// ExternalWorkloadListerExpansion allows custom methods to be added to\n// ExternalWorkloadLister.\ntype ExternalWorkloadListerExpansion interface{}\n\n// ExternalWorkloadNamespaceListerExpansion allows custom methods to be added to\n// ExternalWorkloadNamespaceLister.\ntype ExternalWorkloadNamespaceListerExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/listers/externalworkload/v1beta1/externalworkload.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\texternalworkloadv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ExternalWorkloadLister helps list ExternalWorkloads.\n// All objects returned here must be treated as read-only.\ntype ExternalWorkloadLister interface {\n\t// List lists all ExternalWorkloads in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*externalworkloadv1beta1.ExternalWorkload, err error)\n\t// ExternalWorkloads returns an object that can list and get ExternalWorkloads.\n\tExternalWorkloads(namespace string) ExternalWorkloadNamespaceLister\n\tExternalWorkloadListerExpansion\n}\n\n// externalWorkloadLister implements the ExternalWorkloadLister interface.\ntype externalWorkloadLister struct {\n\tlisters.ResourceIndexer[*externalworkloadv1beta1.ExternalWorkload]\n}\n\n// NewExternalWorkloadLister returns a new ExternalWorkloadLister.\nfunc NewExternalWorkloadLister(indexer cache.Indexer) ExternalWorkloadLister {\n\treturn &externalWorkloadLister{listers.New[*externalworkloadv1beta1.ExternalWorkload](indexer, externalworkloadv1beta1.Resource(\"externalworkload\"))}\n}\n\n// ExternalWorkloads returns an object that can list and get ExternalWorkloads.\nfunc (s *externalWorkloadLister) ExternalWorkloads(namespace string) ExternalWorkloadNamespaceLister {\n\treturn externalWorkloadNamespaceLister{listers.NewNamespaced[*externalworkloadv1beta1.ExternalWorkload](s.ResourceIndexer, namespace)}\n}\n\n// ExternalWorkloadNamespaceLister helps list and get ExternalWorkloads.\n// All objects returned here must be treated as read-only.\ntype ExternalWorkloadNamespaceLister interface {\n\t// List lists all ExternalWorkloads in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*externalworkloadv1beta1.ExternalWorkload, err error)\n\t// Get retrieves the ExternalWorkload from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*externalworkloadv1beta1.ExternalWorkload, error)\n\tExternalWorkloadNamespaceListerExpansion\n}\n\n// externalWorkloadNamespaceLister implements the ExternalWorkloadNamespaceLister\n// interface.\ntype externalWorkloadNamespaceLister struct {\n\tlisters.ResourceIndexer[*externalworkloadv1beta1.ExternalWorkload]\n}\n"
  },
  {
    "path": "controller/gen/client/listers/link/v1alpha1/expansion_generated.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\n// LinkListerExpansion allows custom methods to be added to\n// LinkLister.\ntype LinkListerExpansion interface{}\n\n// LinkNamespaceListerExpansion allows custom methods to be added to\n// LinkNamespaceLister.\ntype LinkNamespaceListerExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/listers/link/v1alpha1/link.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tlinkv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// LinkLister helps list Links.\n// All objects returned here must be treated as read-only.\ntype LinkLister interface {\n\t// List lists all Links in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*linkv1alpha1.Link, err error)\n\t// Links returns an object that can list and get Links.\n\tLinks(namespace string) LinkNamespaceLister\n\tLinkListerExpansion\n}\n\n// linkLister implements the LinkLister interface.\ntype linkLister struct {\n\tlisters.ResourceIndexer[*linkv1alpha1.Link]\n}\n\n// NewLinkLister returns a new LinkLister.\nfunc NewLinkLister(indexer cache.Indexer) LinkLister {\n\treturn &linkLister{listers.New[*linkv1alpha1.Link](indexer, linkv1alpha1.Resource(\"link\"))}\n}\n\n// Links returns an object that can list and get Links.\nfunc (s *linkLister) Links(namespace string) LinkNamespaceLister {\n\treturn linkNamespaceLister{listers.NewNamespaced[*linkv1alpha1.Link](s.ResourceIndexer, namespace)}\n}\n\n// LinkNamespaceLister helps list and get Links.\n// All objects returned here must be treated as read-only.\ntype LinkNamespaceLister interface {\n\t// List lists all Links in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*linkv1alpha1.Link, err error)\n\t// Get retrieves the Link from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*linkv1alpha1.Link, error)\n\tLinkNamespaceListerExpansion\n}\n\n// linkNamespaceLister implements the LinkNamespaceLister\n// interface.\ntype linkNamespaceLister struct {\n\tlisters.ResourceIndexer[*linkv1alpha1.Link]\n}\n"
  },
  {
    "path": "controller/gen/client/listers/link/v1alpha2/expansion_generated.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha2\n\n// LinkListerExpansion allows custom methods to be added to\n// LinkLister.\ntype LinkListerExpansion interface{}\n\n// LinkNamespaceListerExpansion allows custom methods to be added to\n// LinkNamespaceLister.\ntype LinkNamespaceListerExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/listers/link/v1alpha2/link.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha2\n\nimport (\n\tlinkv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha2\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// LinkLister helps list Links.\n// All objects returned here must be treated as read-only.\ntype LinkLister interface {\n\t// List lists all Links in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*linkv1alpha2.Link, err error)\n\t// Links returns an object that can list and get Links.\n\tLinks(namespace string) LinkNamespaceLister\n\tLinkListerExpansion\n}\n\n// linkLister implements the LinkLister interface.\ntype linkLister struct {\n\tlisters.ResourceIndexer[*linkv1alpha2.Link]\n}\n\n// NewLinkLister returns a new LinkLister.\nfunc NewLinkLister(indexer cache.Indexer) LinkLister {\n\treturn &linkLister{listers.New[*linkv1alpha2.Link](indexer, linkv1alpha2.Resource(\"link\"))}\n}\n\n// Links returns an object that can list and get Links.\nfunc (s *linkLister) Links(namespace string) LinkNamespaceLister {\n\treturn linkNamespaceLister{listers.NewNamespaced[*linkv1alpha2.Link](s.ResourceIndexer, namespace)}\n}\n\n// LinkNamespaceLister helps list and get Links.\n// All objects returned here must be treated as read-only.\ntype LinkNamespaceLister interface {\n\t// List lists all Links in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*linkv1alpha2.Link, err error)\n\t// Get retrieves the Link from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*linkv1alpha2.Link, error)\n\tLinkNamespaceListerExpansion\n}\n\n// linkNamespaceLister implements the LinkNamespaceLister\n// interface.\ntype linkNamespaceLister struct {\n\tlisters.ResourceIndexer[*linkv1alpha2.Link]\n}\n"
  },
  {
    "path": "controller/gen/client/listers/link/v1alpha3/expansion_generated.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha3\n\n// LinkListerExpansion allows custom methods to be added to\n// LinkLister.\ntype LinkListerExpansion interface{}\n\n// LinkNamespaceListerExpansion allows custom methods to be added to\n// LinkNamespaceLister.\ntype LinkNamespaceListerExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/listers/link/v1alpha3/link.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha3\n\nimport (\n\tlinkv1alpha3 \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// LinkLister helps list Links.\n// All objects returned here must be treated as read-only.\ntype LinkLister interface {\n\t// List lists all Links in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*linkv1alpha3.Link, err error)\n\t// Links returns an object that can list and get Links.\n\tLinks(namespace string) LinkNamespaceLister\n\tLinkListerExpansion\n}\n\n// linkLister implements the LinkLister interface.\ntype linkLister struct {\n\tlisters.ResourceIndexer[*linkv1alpha3.Link]\n}\n\n// NewLinkLister returns a new LinkLister.\nfunc NewLinkLister(indexer cache.Indexer) LinkLister {\n\treturn &linkLister{listers.New[*linkv1alpha3.Link](indexer, linkv1alpha3.Resource(\"link\"))}\n}\n\n// Links returns an object that can list and get Links.\nfunc (s *linkLister) Links(namespace string) LinkNamespaceLister {\n\treturn linkNamespaceLister{listers.NewNamespaced[*linkv1alpha3.Link](s.ResourceIndexer, namespace)}\n}\n\n// LinkNamespaceLister helps list and get Links.\n// All objects returned here must be treated as read-only.\ntype LinkNamespaceLister interface {\n\t// List lists all Links in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*linkv1alpha3.Link, err error)\n\t// Get retrieves the Link from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*linkv1alpha3.Link, error)\n\tLinkNamespaceListerExpansion\n}\n\n// linkNamespaceLister implements the LinkNamespaceLister\n// interface.\ntype linkNamespaceLister struct {\n\tlisters.ResourceIndexer[*linkv1alpha3.Link]\n}\n"
  },
  {
    "path": "controller/gen/client/listers/policy/v1alpha1/authorizationpolicy.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// AuthorizationPolicyLister helps list AuthorizationPolicies.\n// All objects returned here must be treated as read-only.\ntype AuthorizationPolicyLister interface {\n\t// List lists all AuthorizationPolicies in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*policyv1alpha1.AuthorizationPolicy, err error)\n\t// AuthorizationPolicies returns an object that can list and get AuthorizationPolicies.\n\tAuthorizationPolicies(namespace string) AuthorizationPolicyNamespaceLister\n\tAuthorizationPolicyListerExpansion\n}\n\n// authorizationPolicyLister implements the AuthorizationPolicyLister interface.\ntype authorizationPolicyLister struct {\n\tlisters.ResourceIndexer[*policyv1alpha1.AuthorizationPolicy]\n}\n\n// NewAuthorizationPolicyLister returns a new AuthorizationPolicyLister.\nfunc NewAuthorizationPolicyLister(indexer cache.Indexer) AuthorizationPolicyLister {\n\treturn &authorizationPolicyLister{listers.New[*policyv1alpha1.AuthorizationPolicy](indexer, policyv1alpha1.Resource(\"authorizationpolicy\"))}\n}\n\n// AuthorizationPolicies returns an object that can list and get AuthorizationPolicies.\nfunc (s *authorizationPolicyLister) AuthorizationPolicies(namespace string) AuthorizationPolicyNamespaceLister {\n\treturn authorizationPolicyNamespaceLister{listers.NewNamespaced[*policyv1alpha1.AuthorizationPolicy](s.ResourceIndexer, namespace)}\n}\n\n// AuthorizationPolicyNamespaceLister helps list and get AuthorizationPolicies.\n// All objects returned here must be treated as read-only.\ntype AuthorizationPolicyNamespaceLister interface {\n\t// List lists all AuthorizationPolicies in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*policyv1alpha1.AuthorizationPolicy, err error)\n\t// Get retrieves the AuthorizationPolicy from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*policyv1alpha1.AuthorizationPolicy, error)\n\tAuthorizationPolicyNamespaceListerExpansion\n}\n\n// authorizationPolicyNamespaceLister implements the AuthorizationPolicyNamespaceLister\n// interface.\ntype authorizationPolicyNamespaceLister struct {\n\tlisters.ResourceIndexer[*policyv1alpha1.AuthorizationPolicy]\n}\n"
  },
  {
    "path": "controller/gen/client/listers/policy/v1alpha1/expansion_generated.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\n// AuthorizationPolicyListerExpansion allows custom methods to be added to\n// AuthorizationPolicyLister.\ntype AuthorizationPolicyListerExpansion interface{}\n\n// AuthorizationPolicyNamespaceListerExpansion allows custom methods to be added to\n// AuthorizationPolicyNamespaceLister.\ntype AuthorizationPolicyNamespaceListerExpansion interface{}\n\n// HTTPRouteListerExpansion allows custom methods to be added to\n// HTTPRouteLister.\ntype HTTPRouteListerExpansion interface{}\n\n// HTTPRouteNamespaceListerExpansion allows custom methods to be added to\n// HTTPRouteNamespaceLister.\ntype HTTPRouteNamespaceListerExpansion interface{}\n\n// MeshTLSAuthenticationListerExpansion allows custom methods to be added to\n// MeshTLSAuthenticationLister.\ntype MeshTLSAuthenticationListerExpansion interface{}\n\n// MeshTLSAuthenticationNamespaceListerExpansion allows custom methods to be added to\n// MeshTLSAuthenticationNamespaceLister.\ntype MeshTLSAuthenticationNamespaceListerExpansion interface{}\n\n// NetworkAuthenticationListerExpansion allows custom methods to be added to\n// NetworkAuthenticationLister.\ntype NetworkAuthenticationListerExpansion interface{}\n\n// NetworkAuthenticationNamespaceListerExpansion allows custom methods to be added to\n// NetworkAuthenticationNamespaceLister.\ntype NetworkAuthenticationNamespaceListerExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/listers/policy/v1alpha1/httproute.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// HTTPRouteLister helps list HTTPRoutes.\n// All objects returned here must be treated as read-only.\ntype HTTPRouteLister interface {\n\t// List lists all HTTPRoutes in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*policyv1alpha1.HTTPRoute, err error)\n\t// HTTPRoutes returns an object that can list and get HTTPRoutes.\n\tHTTPRoutes(namespace string) HTTPRouteNamespaceLister\n\tHTTPRouteListerExpansion\n}\n\n// hTTPRouteLister implements the HTTPRouteLister interface.\ntype hTTPRouteLister struct {\n\tlisters.ResourceIndexer[*policyv1alpha1.HTTPRoute]\n}\n\n// NewHTTPRouteLister returns a new HTTPRouteLister.\nfunc NewHTTPRouteLister(indexer cache.Indexer) HTTPRouteLister {\n\treturn &hTTPRouteLister{listers.New[*policyv1alpha1.HTTPRoute](indexer, policyv1alpha1.Resource(\"httproute\"))}\n}\n\n// HTTPRoutes returns an object that can list and get HTTPRoutes.\nfunc (s *hTTPRouteLister) HTTPRoutes(namespace string) HTTPRouteNamespaceLister {\n\treturn hTTPRouteNamespaceLister{listers.NewNamespaced[*policyv1alpha1.HTTPRoute](s.ResourceIndexer, namespace)}\n}\n\n// HTTPRouteNamespaceLister helps list and get HTTPRoutes.\n// All objects returned here must be treated as read-only.\ntype HTTPRouteNamespaceLister interface {\n\t// List lists all HTTPRoutes in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*policyv1alpha1.HTTPRoute, err error)\n\t// Get retrieves the HTTPRoute from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*policyv1alpha1.HTTPRoute, error)\n\tHTTPRouteNamespaceListerExpansion\n}\n\n// hTTPRouteNamespaceLister implements the HTTPRouteNamespaceLister\n// interface.\ntype hTTPRouteNamespaceLister struct {\n\tlisters.ResourceIndexer[*policyv1alpha1.HTTPRoute]\n}\n"
  },
  {
    "path": "controller/gen/client/listers/policy/v1alpha1/meshtlsauthentication.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// MeshTLSAuthenticationLister helps list MeshTLSAuthentications.\n// All objects returned here must be treated as read-only.\ntype MeshTLSAuthenticationLister interface {\n\t// List lists all MeshTLSAuthentications in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*policyv1alpha1.MeshTLSAuthentication, err error)\n\t// MeshTLSAuthentications returns an object that can list and get MeshTLSAuthentications.\n\tMeshTLSAuthentications(namespace string) MeshTLSAuthenticationNamespaceLister\n\tMeshTLSAuthenticationListerExpansion\n}\n\n// meshTLSAuthenticationLister implements the MeshTLSAuthenticationLister interface.\ntype meshTLSAuthenticationLister struct {\n\tlisters.ResourceIndexer[*policyv1alpha1.MeshTLSAuthentication]\n}\n\n// NewMeshTLSAuthenticationLister returns a new MeshTLSAuthenticationLister.\nfunc NewMeshTLSAuthenticationLister(indexer cache.Indexer) MeshTLSAuthenticationLister {\n\treturn &meshTLSAuthenticationLister{listers.New[*policyv1alpha1.MeshTLSAuthentication](indexer, policyv1alpha1.Resource(\"meshtlsauthentication\"))}\n}\n\n// MeshTLSAuthentications returns an object that can list and get MeshTLSAuthentications.\nfunc (s *meshTLSAuthenticationLister) MeshTLSAuthentications(namespace string) MeshTLSAuthenticationNamespaceLister {\n\treturn meshTLSAuthenticationNamespaceLister{listers.NewNamespaced[*policyv1alpha1.MeshTLSAuthentication](s.ResourceIndexer, namespace)}\n}\n\n// MeshTLSAuthenticationNamespaceLister helps list and get MeshTLSAuthentications.\n// All objects returned here must be treated as read-only.\ntype MeshTLSAuthenticationNamespaceLister interface {\n\t// List lists all MeshTLSAuthentications in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*policyv1alpha1.MeshTLSAuthentication, err error)\n\t// Get retrieves the MeshTLSAuthentication from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*policyv1alpha1.MeshTLSAuthentication, error)\n\tMeshTLSAuthenticationNamespaceListerExpansion\n}\n\n// meshTLSAuthenticationNamespaceLister implements the MeshTLSAuthenticationNamespaceLister\n// interface.\ntype meshTLSAuthenticationNamespaceLister struct {\n\tlisters.ResourceIndexer[*policyv1alpha1.MeshTLSAuthentication]\n}\n"
  },
  {
    "path": "controller/gen/client/listers/policy/v1alpha1/networkauthentication.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tpolicyv1alpha1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// NetworkAuthenticationLister helps list NetworkAuthentications.\n// All objects returned here must be treated as read-only.\ntype NetworkAuthenticationLister interface {\n\t// List lists all NetworkAuthentications in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*policyv1alpha1.NetworkAuthentication, err error)\n\t// NetworkAuthentications returns an object that can list and get NetworkAuthentications.\n\tNetworkAuthentications(namespace string) NetworkAuthenticationNamespaceLister\n\tNetworkAuthenticationListerExpansion\n}\n\n// networkAuthenticationLister implements the NetworkAuthenticationLister interface.\ntype networkAuthenticationLister struct {\n\tlisters.ResourceIndexer[*policyv1alpha1.NetworkAuthentication]\n}\n\n// NewNetworkAuthenticationLister returns a new NetworkAuthenticationLister.\nfunc NewNetworkAuthenticationLister(indexer cache.Indexer) NetworkAuthenticationLister {\n\treturn &networkAuthenticationLister{listers.New[*policyv1alpha1.NetworkAuthentication](indexer, policyv1alpha1.Resource(\"networkauthentication\"))}\n}\n\n// NetworkAuthentications returns an object that can list and get NetworkAuthentications.\nfunc (s *networkAuthenticationLister) NetworkAuthentications(namespace string) NetworkAuthenticationNamespaceLister {\n\treturn networkAuthenticationNamespaceLister{listers.NewNamespaced[*policyv1alpha1.NetworkAuthentication](s.ResourceIndexer, namespace)}\n}\n\n// NetworkAuthenticationNamespaceLister helps list and get NetworkAuthentications.\n// All objects returned here must be treated as read-only.\ntype NetworkAuthenticationNamespaceLister interface {\n\t// List lists all NetworkAuthentications in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*policyv1alpha1.NetworkAuthentication, err error)\n\t// Get retrieves the NetworkAuthentication from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*policyv1alpha1.NetworkAuthentication, error)\n\tNetworkAuthenticationNamespaceListerExpansion\n}\n\n// networkAuthenticationNamespaceLister implements the NetworkAuthenticationNamespaceLister\n// interface.\ntype networkAuthenticationNamespaceLister struct {\n\tlisters.ResourceIndexer[*policyv1alpha1.NetworkAuthentication]\n}\n"
  },
  {
    "path": "controller/gen/client/listers/policy/v1beta3/expansion_generated.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta3\n\n// HTTPRouteListerExpansion allows custom methods to be added to\n// HTTPRouteLister.\ntype HTTPRouteListerExpansion interface{}\n\n// HTTPRouteNamespaceListerExpansion allows custom methods to be added to\n// HTTPRouteNamespaceLister.\ntype HTTPRouteNamespaceListerExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/listers/policy/v1beta3/httproute.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta3\n\nimport (\n\tpolicyv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1beta3\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// HTTPRouteLister helps list HTTPRoutes.\n// All objects returned here must be treated as read-only.\ntype HTTPRouteLister interface {\n\t// List lists all HTTPRoutes in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*policyv1beta3.HTTPRoute, err error)\n\t// HTTPRoutes returns an object that can list and get HTTPRoutes.\n\tHTTPRoutes(namespace string) HTTPRouteNamespaceLister\n\tHTTPRouteListerExpansion\n}\n\n// hTTPRouteLister implements the HTTPRouteLister interface.\ntype hTTPRouteLister struct {\n\tlisters.ResourceIndexer[*policyv1beta3.HTTPRoute]\n}\n\n// NewHTTPRouteLister returns a new HTTPRouteLister.\nfunc NewHTTPRouteLister(indexer cache.Indexer) HTTPRouteLister {\n\treturn &hTTPRouteLister{listers.New[*policyv1beta3.HTTPRoute](indexer, policyv1beta3.Resource(\"httproute\"))}\n}\n\n// HTTPRoutes returns an object that can list and get HTTPRoutes.\nfunc (s *hTTPRouteLister) HTTPRoutes(namespace string) HTTPRouteNamespaceLister {\n\treturn hTTPRouteNamespaceLister{listers.NewNamespaced[*policyv1beta3.HTTPRoute](s.ResourceIndexer, namespace)}\n}\n\n// HTTPRouteNamespaceLister helps list and get HTTPRoutes.\n// All objects returned here must be treated as read-only.\ntype HTTPRouteNamespaceLister interface {\n\t// List lists all HTTPRoutes in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*policyv1beta3.HTTPRoute, err error)\n\t// Get retrieves the HTTPRoute from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*policyv1beta3.HTTPRoute, error)\n\tHTTPRouteNamespaceListerExpansion\n}\n\n// hTTPRouteNamespaceLister implements the HTTPRouteNamespaceLister\n// interface.\ntype hTTPRouteNamespaceLister struct {\n\tlisters.ResourceIndexer[*policyv1beta3.HTTPRoute]\n}\n"
  },
  {
    "path": "controller/gen/client/listers/server/v1beta1/expansion_generated.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta1\n\n// ServerListerExpansion allows custom methods to be added to\n// ServerLister.\ntype ServerListerExpansion interface{}\n\n// ServerNamespaceListerExpansion allows custom methods to be added to\n// ServerNamespaceLister.\ntype ServerNamespaceListerExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/listers/server/v1beta1/server.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\tserverv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta1\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ServerLister helps list Servers.\n// All objects returned here must be treated as read-only.\ntype ServerLister interface {\n\t// List lists all Servers in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*serverv1beta1.Server, err error)\n\t// Servers returns an object that can list and get Servers.\n\tServers(namespace string) ServerNamespaceLister\n\tServerListerExpansion\n}\n\n// serverLister implements the ServerLister interface.\ntype serverLister struct {\n\tlisters.ResourceIndexer[*serverv1beta1.Server]\n}\n\n// NewServerLister returns a new ServerLister.\nfunc NewServerLister(indexer cache.Indexer) ServerLister {\n\treturn &serverLister{listers.New[*serverv1beta1.Server](indexer, serverv1beta1.Resource(\"server\"))}\n}\n\n// Servers returns an object that can list and get Servers.\nfunc (s *serverLister) Servers(namespace string) ServerNamespaceLister {\n\treturn serverNamespaceLister{listers.NewNamespaced[*serverv1beta1.Server](s.ResourceIndexer, namespace)}\n}\n\n// ServerNamespaceLister helps list and get Servers.\n// All objects returned here must be treated as read-only.\ntype ServerNamespaceLister interface {\n\t// List lists all Servers in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*serverv1beta1.Server, err error)\n\t// Get retrieves the Server from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*serverv1beta1.Server, error)\n\tServerNamespaceListerExpansion\n}\n\n// serverNamespaceLister implements the ServerNamespaceLister\n// interface.\ntype serverNamespaceLister struct {\n\tlisters.ResourceIndexer[*serverv1beta1.Server]\n}\n"
  },
  {
    "path": "controller/gen/client/listers/server/v1beta2/expansion_generated.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// ServerListerExpansion allows custom methods to be added to\n// ServerLister.\ntype ServerListerExpansion interface{}\n\n// ServerNamespaceListerExpansion allows custom methods to be added to\n// ServerNamespaceLister.\ntype ServerNamespaceListerExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/listers/server/v1beta2/server.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tserverv1beta2 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta2\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ServerLister helps list Servers.\n// All objects returned here must be treated as read-only.\ntype ServerLister interface {\n\t// List lists all Servers in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*serverv1beta2.Server, err error)\n\t// Servers returns an object that can list and get Servers.\n\tServers(namespace string) ServerNamespaceLister\n\tServerListerExpansion\n}\n\n// serverLister implements the ServerLister interface.\ntype serverLister struct {\n\tlisters.ResourceIndexer[*serverv1beta2.Server]\n}\n\n// NewServerLister returns a new ServerLister.\nfunc NewServerLister(indexer cache.Indexer) ServerLister {\n\treturn &serverLister{listers.New[*serverv1beta2.Server](indexer, serverv1beta2.Resource(\"server\"))}\n}\n\n// Servers returns an object that can list and get Servers.\nfunc (s *serverLister) Servers(namespace string) ServerNamespaceLister {\n\treturn serverNamespaceLister{listers.NewNamespaced[*serverv1beta2.Server](s.ResourceIndexer, namespace)}\n}\n\n// ServerNamespaceLister helps list and get Servers.\n// All objects returned here must be treated as read-only.\ntype ServerNamespaceLister interface {\n\t// List lists all Servers in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*serverv1beta2.Server, err error)\n\t// Get retrieves the Server from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*serverv1beta2.Server, error)\n\tServerNamespaceListerExpansion\n}\n\n// serverNamespaceLister implements the ServerNamespaceLister\n// interface.\ntype serverNamespaceLister struct {\n\tlisters.ResourceIndexer[*serverv1beta2.Server]\n}\n"
  },
  {
    "path": "controller/gen/client/listers/server/v1beta3/expansion_generated.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta3\n\n// ServerListerExpansion allows custom methods to be added to\n// ServerLister.\ntype ServerListerExpansion interface{}\n\n// ServerNamespaceListerExpansion allows custom methods to be added to\n// ServerNamespaceLister.\ntype ServerNamespaceListerExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/listers/server/v1beta3/server.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta3\n\nimport (\n\tserverv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta3\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ServerLister helps list Servers.\n// All objects returned here must be treated as read-only.\ntype ServerLister interface {\n\t// List lists all Servers in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*serverv1beta3.Server, err error)\n\t// Servers returns an object that can list and get Servers.\n\tServers(namespace string) ServerNamespaceLister\n\tServerListerExpansion\n}\n\n// serverLister implements the ServerLister interface.\ntype serverLister struct {\n\tlisters.ResourceIndexer[*serverv1beta3.Server]\n}\n\n// NewServerLister returns a new ServerLister.\nfunc NewServerLister(indexer cache.Indexer) ServerLister {\n\treturn &serverLister{listers.New[*serverv1beta3.Server](indexer, serverv1beta3.Resource(\"server\"))}\n}\n\n// Servers returns an object that can list and get Servers.\nfunc (s *serverLister) Servers(namespace string) ServerNamespaceLister {\n\treturn serverNamespaceLister{listers.NewNamespaced[*serverv1beta3.Server](s.ResourceIndexer, namespace)}\n}\n\n// ServerNamespaceLister helps list and get Servers.\n// All objects returned here must be treated as read-only.\ntype ServerNamespaceLister interface {\n\t// List lists all Servers in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*serverv1beta3.Server, err error)\n\t// Get retrieves the Server from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*serverv1beta3.Server, error)\n\tServerNamespaceListerExpansion\n}\n\n// serverNamespaceLister implements the ServerNamespaceLister\n// interface.\ntype serverNamespaceLister struct {\n\tlisters.ResourceIndexer[*serverv1beta3.Server]\n}\n"
  },
  {
    "path": "controller/gen/client/listers/serverauthorization/v1beta1/expansion_generated.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta1\n\n// ServerAuthorizationListerExpansion allows custom methods to be added to\n// ServerAuthorizationLister.\ntype ServerAuthorizationListerExpansion interface{}\n\n// ServerAuthorizationNamespaceListerExpansion allows custom methods to be added to\n// ServerAuthorizationNamespaceLister.\ntype ServerAuthorizationNamespaceListerExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/listers/serverauthorization/v1beta1/serverauthorization.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\tserverauthorizationv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ServerAuthorizationLister helps list ServerAuthorizations.\n// All objects returned here must be treated as read-only.\ntype ServerAuthorizationLister interface {\n\t// List lists all ServerAuthorizations in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*serverauthorizationv1beta1.ServerAuthorization, err error)\n\t// ServerAuthorizations returns an object that can list and get ServerAuthorizations.\n\tServerAuthorizations(namespace string) ServerAuthorizationNamespaceLister\n\tServerAuthorizationListerExpansion\n}\n\n// serverAuthorizationLister implements the ServerAuthorizationLister interface.\ntype serverAuthorizationLister struct {\n\tlisters.ResourceIndexer[*serverauthorizationv1beta1.ServerAuthorization]\n}\n\n// NewServerAuthorizationLister returns a new ServerAuthorizationLister.\nfunc NewServerAuthorizationLister(indexer cache.Indexer) ServerAuthorizationLister {\n\treturn &serverAuthorizationLister{listers.New[*serverauthorizationv1beta1.ServerAuthorization](indexer, serverauthorizationv1beta1.Resource(\"serverauthorization\"))}\n}\n\n// ServerAuthorizations returns an object that can list and get ServerAuthorizations.\nfunc (s *serverAuthorizationLister) ServerAuthorizations(namespace string) ServerAuthorizationNamespaceLister {\n\treturn serverAuthorizationNamespaceLister{listers.NewNamespaced[*serverauthorizationv1beta1.ServerAuthorization](s.ResourceIndexer, namespace)}\n}\n\n// ServerAuthorizationNamespaceLister helps list and get ServerAuthorizations.\n// All objects returned here must be treated as read-only.\ntype ServerAuthorizationNamespaceLister interface {\n\t// List lists all ServerAuthorizations in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*serverauthorizationv1beta1.ServerAuthorization, err error)\n\t// Get retrieves the ServerAuthorization from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*serverauthorizationv1beta1.ServerAuthorization, error)\n\tServerAuthorizationNamespaceListerExpansion\n}\n\n// serverAuthorizationNamespaceLister implements the ServerAuthorizationNamespaceLister\n// interface.\ntype serverAuthorizationNamespaceLister struct {\n\tlisters.ResourceIndexer[*serverauthorizationv1beta1.ServerAuthorization]\n}\n"
  },
  {
    "path": "controller/gen/client/listers/serviceprofile/v1alpha2/expansion_generated.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha2\n\n// ServiceProfileListerExpansion allows custom methods to be added to\n// ServiceProfileLister.\ntype ServiceProfileListerExpansion interface{}\n\n// ServiceProfileNamespaceListerExpansion allows custom methods to be added to\n// ServiceProfileNamespaceLister.\ntype ServiceProfileNamespaceListerExpansion interface{}\n"
  },
  {
    "path": "controller/gen/client/listers/serviceprofile/v1alpha2/serviceprofile.go",
    "content": "/*\nCopyright The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha2\n\nimport (\n\tserviceprofilev1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ServiceProfileLister helps list ServiceProfiles.\n// All objects returned here must be treated as read-only.\ntype ServiceProfileLister interface {\n\t// List lists all ServiceProfiles in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*serviceprofilev1alpha2.ServiceProfile, err error)\n\t// ServiceProfiles returns an object that can list and get ServiceProfiles.\n\tServiceProfiles(namespace string) ServiceProfileNamespaceLister\n\tServiceProfileListerExpansion\n}\n\n// serviceProfileLister implements the ServiceProfileLister interface.\ntype serviceProfileLister struct {\n\tlisters.ResourceIndexer[*serviceprofilev1alpha2.ServiceProfile]\n}\n\n// NewServiceProfileLister returns a new ServiceProfileLister.\nfunc NewServiceProfileLister(indexer cache.Indexer) ServiceProfileLister {\n\treturn &serviceProfileLister{listers.New[*serviceprofilev1alpha2.ServiceProfile](indexer, serviceprofilev1alpha2.Resource(\"serviceprofile\"))}\n}\n\n// ServiceProfiles returns an object that can list and get ServiceProfiles.\nfunc (s *serviceProfileLister) ServiceProfiles(namespace string) ServiceProfileNamespaceLister {\n\treturn serviceProfileNamespaceLister{listers.NewNamespaced[*serviceprofilev1alpha2.ServiceProfile](s.ResourceIndexer, namespace)}\n}\n\n// ServiceProfileNamespaceLister helps list and get ServiceProfiles.\n// All objects returned here must be treated as read-only.\ntype ServiceProfileNamespaceLister interface {\n\t// List lists all ServiceProfiles in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*serviceprofilev1alpha2.ServiceProfile, err error)\n\t// Get retrieves the ServiceProfile from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*serviceprofilev1alpha2.ServiceProfile, error)\n\tServiceProfileNamespaceListerExpansion\n}\n\n// serviceProfileNamespaceLister implements the ServiceProfileNamespaceLister\n// interface.\ntype serviceProfileNamespaceLister struct {\n\tlisters.ResourceIndexer[*serviceprofilev1alpha2.ServiceProfile]\n}\n"
  },
  {
    "path": "controller/gen/common/net/net.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.35.2\n// \tprotoc        v6.32.1\n// source: common/net.proto\n\npackage net\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)\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 IPAddress struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Ip:\n\t//\n\t//\t*IPAddress_Ipv4\n\t//\t*IPAddress_Ipv6\n\tIp isIPAddress_Ip `protobuf_oneof:\"ip\"`\n}\n\nfunc (x *IPAddress) Reset() {\n\t*x = IPAddress{}\n\tmi := &file_common_net_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *IPAddress) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*IPAddress) ProtoMessage() {}\n\nfunc (x *IPAddress) ProtoReflect() protoreflect.Message {\n\tmi := &file_common_net_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 IPAddress.ProtoReflect.Descriptor instead.\nfunc (*IPAddress) Descriptor() ([]byte, []int) {\n\treturn file_common_net_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (m *IPAddress) GetIp() isIPAddress_Ip {\n\tif m != nil {\n\t\treturn m.Ip\n\t}\n\treturn nil\n}\n\nfunc (x *IPAddress) GetIpv4() uint32 {\n\tif x, ok := x.GetIp().(*IPAddress_Ipv4); ok {\n\t\treturn x.Ipv4\n\t}\n\treturn 0\n}\n\nfunc (x *IPAddress) GetIpv6() *IPv6 {\n\tif x, ok := x.GetIp().(*IPAddress_Ipv6); ok {\n\t\treturn x.Ipv6\n\t}\n\treturn nil\n}\n\ntype isIPAddress_Ip interface {\n\tisIPAddress_Ip()\n}\n\ntype IPAddress_Ipv4 struct {\n\tIpv4 uint32 `protobuf:\"fixed32,1,opt,name=ipv4,proto3,oneof\"`\n}\n\ntype IPAddress_Ipv6 struct {\n\tIpv6 *IPv6 `protobuf:\"bytes,2,opt,name=ipv6,proto3,oneof\"`\n}\n\nfunc (*IPAddress_Ipv4) isIPAddress_Ip() {}\n\nfunc (*IPAddress_Ipv6) isIPAddress_Ip() {}\n\ntype IPv6 struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFirst uint64 `protobuf:\"fixed64,1,opt,name=first,proto3\" json:\"first,omitempty\"` // hextets 1-4\n\tLast  uint64 `protobuf:\"fixed64,2,opt,name=last,proto3\" json:\"last,omitempty\"`   // hextets 5-8\n}\n\nfunc (x *IPv6) Reset() {\n\t*x = IPv6{}\n\tmi := &file_common_net_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *IPv6) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*IPv6) ProtoMessage() {}\n\nfunc (x *IPv6) ProtoReflect() protoreflect.Message {\n\tmi := &file_common_net_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 IPv6.ProtoReflect.Descriptor instead.\nfunc (*IPv6) Descriptor() ([]byte, []int) {\n\treturn file_common_net_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *IPv6) GetFirst() uint64 {\n\tif x != nil {\n\t\treturn x.First\n\t}\n\treturn 0\n}\n\nfunc (x *IPv6) GetLast() uint64 {\n\tif x != nil {\n\t\treturn x.Last\n\t}\n\treturn 0\n}\n\ntype TcpAddress struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tIp   *IPAddress `protobuf:\"bytes,1,opt,name=ip,proto3\" json:\"ip,omitempty\"`\n\tPort uint32     `protobuf:\"varint,2,opt,name=port,proto3\" json:\"port,omitempty\"`\n}\n\nfunc (x *TcpAddress) Reset() {\n\t*x = TcpAddress{}\n\tmi := &file_common_net_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TcpAddress) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TcpAddress) ProtoMessage() {}\n\nfunc (x *TcpAddress) ProtoReflect() protoreflect.Message {\n\tmi := &file_common_net_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 TcpAddress.ProtoReflect.Descriptor instead.\nfunc (*TcpAddress) Descriptor() ([]byte, []int) {\n\treturn file_common_net_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *TcpAddress) GetIp() *IPAddress {\n\tif x != nil {\n\t\treturn x.Ip\n\t}\n\treturn nil\n}\n\nfunc (x *TcpAddress) GetPort() uint32 {\n\tif x != nil {\n\t\treturn x.Port\n\t}\n\treturn 0\n}\n\nvar File_common_net_proto protoreflect.FileDescriptor\n\nvar file_common_net_proto_rawDesc = []byte{\n\t0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x12, 0x13, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6d,\n\t0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x22, 0x58, 0x0a, 0x09, 0x49, 0x50, 0x41, 0x64, 0x64,\n\t0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x34, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x07, 0x48, 0x00, 0x52, 0x04, 0x69, 0x70, 0x76, 0x34, 0x12, 0x2f, 0x0a, 0x04, 0x69, 0x70,\n\t0x76, 0x36, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,\n\t0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49,\n\t0x50, 0x76, 0x36, 0x48, 0x00, 0x52, 0x04, 0x69, 0x70, 0x76, 0x36, 0x42, 0x04, 0x0a, 0x02, 0x69,\n\t0x70, 0x22, 0x30, 0x0a, 0x04, 0x49, 0x50, 0x76, 0x36, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x72,\n\t0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x06, 0x52, 0x05, 0x66, 0x69, 0x72, 0x73, 0x74, 0x12,\n\t0x12, 0x0a, 0x04, 0x6c, 0x61, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x06, 0x52, 0x04, 0x6c,\n\t0x61, 0x73, 0x74, 0x22, 0x50, 0x0a, 0x0a, 0x54, 0x63, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,\n\t0x73, 0x12, 0x2e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,\n\t0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x02, 0x69,\n\t0x70, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52,\n\t0x04, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,\n\t0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x2f, 0x6c, 0x69, 0x6e, 0x6b,\n\t0x65, 0x72, 0x64, 0x32, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f,\n\t0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x62, 0x06,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_common_net_proto_rawDescOnce sync.Once\n\tfile_common_net_proto_rawDescData = file_common_net_proto_rawDesc\n)\n\nfunc file_common_net_proto_rawDescGZIP() []byte {\n\tfile_common_net_proto_rawDescOnce.Do(func() {\n\t\tfile_common_net_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_net_proto_rawDescData)\n\t})\n\treturn file_common_net_proto_rawDescData\n}\n\nvar file_common_net_proto_msgTypes = make([]protoimpl.MessageInfo, 3)\nvar file_common_net_proto_goTypes = []any{\n\t(*IPAddress)(nil),  // 0: linkerd2.common.net.IPAddress\n\t(*IPv6)(nil),       // 1: linkerd2.common.net.IPv6\n\t(*TcpAddress)(nil), // 2: linkerd2.common.net.TcpAddress\n}\nvar file_common_net_proto_depIdxs = []int32{\n\t1, // 0: linkerd2.common.net.IPAddress.ipv6:type_name -> linkerd2.common.net.IPv6\n\t0, // 1: linkerd2.common.net.TcpAddress.ip:type_name -> linkerd2.common.net.IPAddress\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_common_net_proto_init() }\nfunc file_common_net_proto_init() {\n\tif File_common_net_proto != nil {\n\t\treturn\n\t}\n\tfile_common_net_proto_msgTypes[0].OneofWrappers = []any{\n\t\t(*IPAddress_Ipv4)(nil),\n\t\t(*IPAddress_Ipv6)(nil),\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: file_common_net_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   3,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_common_net_proto_goTypes,\n\t\tDependencyIndexes: file_common_net_proto_depIdxs,\n\t\tMessageInfos:      file_common_net_proto_msgTypes,\n\t}.Build()\n\tFile_common_net_proto = out.File\n\tfile_common_net_proto_rawDesc = nil\n\tfile_common_net_proto_goTypes = nil\n\tfile_common_net_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "controller/heartbeat/heartbeat.go",
    "content": "package heartbeat\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"time\"\n\n\tpkgK8s \"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/config\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/util\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\tpromv1 \"github.com/prometheus/client_golang/api/prometheus/v1\"\n\t\"github.com/prometheus/common/model\"\n\tlog \"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\ntype containerMeta struct {\n\tname model.LabelValue\n\tns   model.LabelValue\n}\n\n// K8sValues gathers relevant heartbeat information from Kubernetes\nfunc K8sValues(ctx context.Context, kubeAPI *k8s.KubernetesAPI, controlPlaneNamespace string) url.Values {\n\tv := url.Values{}\n\n\tcm, err := config.FetchLinkerdConfigMap(ctx, kubeAPI, controlPlaneNamespace)\n\tif err != nil {\n\t\tlog.Errorf(\"Failed to fetch linkerd-config: %s\", err)\n\t} else {\n\t\tv.Set(\"uuid\", string(cm.GetUID()))\n\t\tv.Set(\"install-time\", strconv.FormatInt(cm.GetCreationTimestamp().Unix(), 10))\n\t}\n\n\tversionInfo, err := kubeAPI.GetVersionInfo()\n\tif err != nil {\n\t\tlog.Errorf(\"Failed to fetch Kubernetes version info: %s\", err)\n\t} else {\n\t\tv.Set(\"k8s-version\", versionInfo.String())\n\t}\n\n\tnamespaces, err := kubeAPI.GetAllNamespacesWithExtensionLabel(ctx)\n\tif err != nil {\n\t\tlog.Errorf(\"Failed to fetch namespaces with %s label: %s\", k8s.LinkerdExtensionLabel, err)\n\t} else {\n\t\tfor _, ns := range namespaces {\n\t\t\textensionNameParam := fmt.Sprintf(\"ext-%s\", ns.Labels[k8s.LinkerdExtensionLabel])\n\t\t\tv.Set(extensionNameParam, \"1\")\n\t\t}\n\t}\n\n\terr = k8s.ServiceProfilesAccess(ctx, kubeAPI)\n\tif err != nil {\n\t\tlog.Errorf(\"Failed to verify service profile access: %s\", err)\n\t\treturn v\n\t}\n\n\tl5dCrdClient, err := pkgK8s.NewL5DCRDClient(kubeAPI.Config)\n\tif err != nil {\n\t\tlog.Errorf(\"Failed to create Linkerd CRD client: %s\", err)\n\t\treturn v\n\t}\n\n\tspList, err := l5dCrdClient.LinkerdV1alpha2().ServiceProfiles(\"\").List(ctx, v1.ListOptions{})\n\tif err != nil {\n\t\tlog.Errorf(\"Failed to get service profiles: %s\", err)\n\t\treturn v\n\t}\n\n\tv.Set(\"service-profile-count\", strconv.Itoa(len(spList.Items)))\n\n\treturn v\n}\n\n// PromValues gathers relevant heartbeat information from Prometheus\nfunc PromValues(promAPI promv1.API, controlPlaneNamespace string) url.Values {\n\tv := url.Values{}\n\n\tjobProxyLabels := model.LabelSet{\"job\": \"linkerd-proxy\"}\n\n\t// total-rps\n\tquery := fmt.Sprintf(\"sum(rate(request_total%s[30s]))\", jobProxyLabels.Merge(model.LabelSet{\"direction\": \"inbound\"}))\n\tvalue, err := promQuery(promAPI, query, 0)\n\tif err != nil {\n\t\tlog.Errorf(\"Prometheus query failed: %s\", err)\n\t} else {\n\t\tv.Set(\"total-rps\", value)\n\t}\n\n\t// meshed-pods\n\tquery = fmt.Sprintf(\"count(count by (pod) (request_total%s))\", jobProxyLabels)\n\tvalue, err = promQuery(promAPI, query, 0)\n\tif err != nil {\n\t\tlog.Errorf(\"Prometheus query failed: %s\", err)\n\t} else {\n\t\tv.Set(\"meshed-pods\", value)\n\t}\n\n\t// p95-handle-us\n\tquery = fmt.Sprintf(\"histogram_quantile(0.99, sum(rate(request_handle_us_bucket%s[24h])) by (le))\", jobProxyLabels)\n\tvalue, err = promQuery(promAPI, query, 0)\n\tif err != nil {\n\t\tlog.Errorf(\"Prometheus query failed: %s\", err)\n\t} else {\n\t\tv.Set(\"p99-handle-us\", value)\n\t}\n\n\t// proxy-injector-injections\n\tjobInjectorLabels := model.LabelSet{\n\t\t\"job\":  \"linkerd-controller\",\n\t\t\"skip\": \"false\",\n\t}\n\tquery = fmt.Sprintf(\"sum(proxy_inject_admission_responses_total%s)\", jobInjectorLabels)\n\tvalue, err = promQuery(promAPI, query, 0)\n\tif err != nil {\n\t\tlog.Errorf(\"Prometheus query failed: %s\", err)\n\t} else {\n\t\tv.Set(\"proxy-injector-injections\", value)\n\t}\n\n\t// container metrics\n\tfor _, container := range []containerMeta{\n\t\t{\n\t\t\tname: \"linkerd-proxy\",\n\t\t},\n\t\t{\n\t\t\tname: \"destination\",\n\t\t\tns:   \"linkerd\",\n\t\t},\n\t\t{\n\t\t\tname: \"prometheus\",\n\t\t\tns:   \"linkerd\",\n\t\t},\n\t} {\n\t\t// as of k8s 1.16 cadvisor labels container names with just `container`\n\t\tcontainerLabelsPre16 := getLabelSet(container, \"container_name\")\n\t\tcontainerLabelsPost16 := getLabelSet(container, \"container\")\n\n\t\t// max-mem\n\t\tquery = fmt.Sprintf(\"max(container_memory_working_set_bytes%s or container_memory_working_set_bytes%s)\",\n\t\t\tcontainerLabelsPre16, containerLabelsPost16)\n\t\tvalue, err = promQuery(promAPI, query, 0)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"Prometheus query failed: %s\", err)\n\t\t} else {\n\t\t\tparam := fmt.Sprintf(\"max-mem-%s\", container.name)\n\t\t\tv.Set(param, value)\n\t\t}\n\n\t\t// p95-cpu\n\t\tquery = fmt.Sprintf(\"max(quantile_over_time(0.95,rate(container_cpu_usage_seconds_total%s[5m])[24h:5m]) \"+\n\t\t\t\"or quantile_over_time(0.95,rate(container_cpu_usage_seconds_total%s[5m])[24h:5m]))\",\n\t\t\tcontainerLabelsPre16, containerLabelsPost16)\n\t\tvalue, err = promQuery(promAPI, query, 3)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"Prometheus query failed: %s\", err)\n\t\t} else {\n\t\t\tparam := fmt.Sprintf(\"p95-cpu-%s\", container.name)\n\t\t\tv.Set(param, value)\n\t\t}\n\t}\n\n\treturn v\n}\n\nfunc getLabelSet(container containerMeta, containerKey model.LabelName) model.LabelSet {\n\tcontainerLabels := model.LabelSet{\n\t\t\"job\":        \"kubernetes-nodes-cadvisor\",\n\t\tcontainerKey: container.name,\n\t}\n\tif container.ns != \"\" {\n\t\tcontainerLabels[\"namespace\"] = container.ns\n\t}\n\treturn containerLabels\n}\n\nfunc promQuery(promAPI promv1.API, query string, precision int) (string, error) {\n\tlog.Debugf(\"Prometheus query: %s\", query)\n\n\tres, warn, err := promAPI.Query(context.Background(), query, time.Time{})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif warn != nil {\n\t\tlog.Warnf(\"%v\", warn)\n\t}\n\n\tif result, ok := res.(model.Vector); ok {\n\t\tif len(result) != 1 {\n\t\t\treturn \"\", fmt.Errorf(\"unexpected result Prometheus result vector length: %d\", len(result))\n\t\t}\n\t\tf := float64(result[0].Value)\n\t\tif math.IsNaN(f) {\n\t\t\treturn \"\", fmt.Errorf(\"unexpected sample value: %v\", result[0].Value)\n\t\t}\n\n\t\treturn strconv.FormatFloat(f, 'f', precision, 64), nil\n\t}\n\n\treturn \"\", fmt.Errorf(\"unexpected query result type (expected Vector): %s\", res.Type())\n}\n\n// MergeValues merges two url.Values\nfunc MergeValues(v1, v2 url.Values) url.Values {\n\tv := url.Values{}\n\tfor key, val := range v1 {\n\t\tv[key] = val\n\t}\n\tfor key, val := range v2 {\n\t\tv[key] = val\n\t}\n\treturn v\n}\n\n// Send takes a map of url.Values and sends them to versioncheck.linkerd.io\nfunc Send(v url.Values) error {\n\treturn send(http.DefaultClient, version.CheckURL, v)\n}\n\nfunc send(client *http.Client, baseURL string, v url.Values) error {\n\treq, err := http.NewRequest(\"GET\", baseURL, nil)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create HTTP request for base URL [%s]: %w\", baseURL, err)\n\t}\n\treq.URL.RawQuery = v.Encode()\n\n\tlog.Infof(\"Sending heartbeat: %s\", req.URL.String())\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"check URL [%s] request failed with: %w\", req.URL.String(), err)\n\t}\n\n\tdefer resp.Body.Close()\n\n\tbody, err := util.ReadAllLimit(resp.Body, util.MB)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read response body: %w\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"request failed with code %d; response body: %s\", resp.StatusCode, string(body))\n\t}\n\n\tlog.Infof(\"Successfully sent heartbeat: %s\", string(body))\n\n\treturn nil\n}\n"
  },
  {
    "path": "controller/heartbeat/heartbeat_test.go",
    "content": "package heartbeat\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/prometheus\"\n\t\"github.com/prometheus/common/model\"\n)\n\nfunc TestK8sValues(t *testing.T) {\n\ttestCases := []struct {\n\t\tnamespace  string\n\t\tk8sConfigs []string\n\t\texpected   url.Values\n\t}{\n\t\t{\n\t\t\t\"linkerd\",\n\t\t\t[]string{`\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  creationTimestamp: 2019-02-15T12:34:56Z\n  uid: fake-uuid`,\n\t\t\t},\n\t\t\turl.Values{\n\t\t\t\t\"k8s-version\":  []string{\"v0.0.0-master+$Format:%H$\"},\n\t\t\t\t\"install-time\": []string{\"1550234096\"},\n\t\t\t\t\"uuid\":         []string{\"fake-uuid\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"bad-ns\",\n\t\t\t[]string{`\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  uid: fake-uuid`,\n\t\t\t},\n\t\t\turl.Values{\n\t\t\t\t\"k8s-version\": []string{\"v0.0.0-master+$Format:%H$\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"linkerd\",\n\t\t\t[]string{`\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\n  creationTimestamp: 2019-02-15T12:34:56Z\n  uid: fake-uuid\ndata:\n  values: |\n    linkerdVersion: stable-2.10`, `\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd-viz\n  labels:\n    linkerd.io/extension: viz`,\n\t\t\t},\n\t\t\turl.Values{\n\t\t\t\t\"k8s-version\":  []string{\"v0.0.0-master+$Format:%H$\"},\n\t\t\t\t\"install-time\": []string{\"1550234096\"},\n\t\t\t\t\"uuid\":         []string{\"fake-uuid\"},\n\t\t\t\t\"ext-viz\":      []string{\"1\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(tc.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tv := K8sValues(ctx, k8sAPI, tc.namespace)\n\t\t\tif diff := deep.Equal(v, tc.expected); diff != nil {\n\t\t\t\tt.Fatalf(\"K8sValues %v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPromValues(t *testing.T) {\n\ttestCases := []struct {\n\t\tnamespace string\n\t\tpromRes   model.Value\n\t\texpected  url.Values\n\t}{\n\t\t{\n\t\t\t\"linkerd\",\n\t\t\tmodel.Vector{\n\t\t\t\t&model.Sample{\n\t\t\t\t\tMetric:    model.Metric{\"pod\": \"emojivoto-meshed\"},\n\t\t\t\t\tValue:     100.1234,\n\t\t\t\t\tTimestamp: 456,\n\t\t\t\t},\n\t\t\t},\n\t\t\turl.Values{\n\t\t\t\t\"total-rps\":                 []string{\"100\"},\n\t\t\t\t\"meshed-pods\":               []string{\"100\"},\n\t\t\t\t\"p99-handle-us\":             []string{\"100\"},\n\t\t\t\t\"max-mem-linkerd-proxy\":     []string{\"100\"},\n\t\t\t\t\"max-mem-destination\":       []string{\"100\"},\n\t\t\t\t\"max-mem-prometheus\":        []string{\"100\"},\n\t\t\t\t\"p95-cpu-linkerd-proxy\":     []string{\"100.123\"},\n\t\t\t\t\"p95-cpu-destination\":       []string{\"100.123\"},\n\t\t\t\t\"p95-cpu-prometheus\":        []string{\"100.123\"},\n\t\t\t\t\"proxy-injector-injections\": []string{\"100\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"bad-ns\",\n\t\t\tmodel.Vector{},\n\t\t\turl.Values{},\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\tv := PromValues(&prometheus.MockProm{Res: tc.promRes}, tc.namespace)\n\t\t\tif diff := deep.Equal(v, tc.expected); diff != nil {\n\t\t\t\tt.Fatalf(\"PromValues %v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMergeValues(t *testing.T) {\n\ttestCases := []struct {\n\t\tv1, v2, expected url.Values\n\t}{\n\t\t{\n\t\t\turl.Values{\n\t\t\t\t\"a\": []string{\"b\"},\n\t\t\t\t\"c\": []string{\"d\"},\n\t\t\t},\n\t\t\turl.Values{\n\t\t\t\t\"e\": []string{\"f\"},\n\t\t\t\t\"g\": []string{\"h\"},\n\t\t\t},\n\t\t\turl.Values{\n\t\t\t\t\"a\": []string{\"b\"},\n\t\t\t\t\"c\": []string{\"d\"},\n\t\t\t\t\"e\": []string{\"f\"},\n\t\t\t\t\"g\": []string{\"h\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\turl.Values{},\n\t\t\turl.Values{},\n\t\t\turl.Values{},\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\tv := MergeValues(tc.v1, tc.v2)\n\t\t\tif diff := deep.Equal(v, tc.expected); diff != nil {\n\t\t\t\tt.Fatalf(\"MergeValues %v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSend(t *testing.T) {\n\ttestCases := []struct {\n\t\tv   url.Values\n\t\terr error\n\t}{\n\t\t{\n\t\t\turl.Values{\n\t\t\t\t\"a\": []string{\"b\"},\n\t\t\t\t\"c\": []string{\"d\"},\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\tts := httptest.NewServer(http.HandlerFunc(\n\t\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tif diff := deep.Equal(r.URL.Query(), tc.v); diff != nil {\n\t\t\t\t\t\tt.Fatalf(\"Send queried for: %+v, expected: %+v\", r.URL.Query(), tc.v)\n\t\t\t\t\t}\n\t\t\t\t\tw.Write([]byte(`{\"stable\":\"stable-a.b.c\",\"edge\":\"edge-d.e.f\"}`))\n\t\t\t\t}),\n\t\t\t)\n\t\t\tdefer ts.Close()\n\n\t\t\terr := send(ts.Client(), ts.URL, tc.v)\n\t\t\tif diff := deep.Equal(err, tc.err); diff != nil {\n\t\t\t\tt.Fatalf(\"Send: %+v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "controller/identity/domain.go",
    "content": "package identity\n\nimport (\n\t\"fmt\"\n\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n)\n\n// TrustDomain is a namespace for identities.\ntype TrustDomain struct {\n\tcontrolNS, domain string\n}\n\n// NewTrustDomain creates a new identity namespace.\nfunc NewTrustDomain(controlNS, domain string) (*TrustDomain, error) {\n\tif errs := validation.IsDNS1123Label(controlNS); len(errs) > 0 {\n\t\treturn nil, fmt.Errorf(\"invalid label '%s': %s\", controlNS, errs[0])\n\t}\n\tif errs := validation.IsDNS1123Subdomain(domain); len(errs) > 0 {\n\t\treturn nil, fmt.Errorf(\"invalid domain '%s': %s\", domain, errs[0])\n\t}\n\n\treturn &TrustDomain{controlNS, domain}, nil\n}\n\n// Identity formats the identity for a K8s user.\nfunc (d *TrustDomain) Identity(typ, nm, ns string) (string, error) {\n\tfor _, l := range []string{typ, nm, ns} {\n\t\tif errs := validation.IsDNS1123Label(l); len(errs) > 0 {\n\t\t\treturn \"\", fmt.Errorf(\"invalid label '%s': %s\", l, errs[0])\n\t\t}\n\t}\n\n\tid := fmt.Sprintf(\"%s.%s.%s.identity.%s.%s\", nm, ns, typ, d.controlNS, d.domain)\n\treturn id, nil\n}\n"
  },
  {
    "path": "controller/identity/validator.go",
    "content": "package identity\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/pkg/identity\"\n\tlog \"github.com/sirupsen/logrus\"\n\tkauthnApi \"k8s.io/api/authentication/v1\"\n\tkauthzApi \"k8s.io/api/authorization/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n\tk8s \"k8s.io/client-go/kubernetes\"\n\tkauthn \"k8s.io/client-go/kubernetes/typed/authentication/v1\"\n\tkauthz \"k8s.io/client-go/kubernetes/typed/authorization/v1\"\n)\n\nconst (\n\t// LinkerdAudienceKey is the audience key used for the Linkerd token creation\n\t// and  review requests.\n\tLinkerdAudienceKey = \"identity.l5d.io\"\n)\n\n// K8sTokenValidator implements Validator for Kubernetes bearer tokens.\ntype K8sTokenValidator struct {\n\tauthn  kauthn.AuthenticationV1Interface\n\tdomain *TrustDomain\n}\n\n// NewK8sTokenValidator takes a kubernetes client and trust domain to create a\n// K8sTokenValidator.\n//\n// The kubernetes client is used immediately to validate that the client has\n// sufficient privileges to perform token reviews. An error is returned if this\n// access check fails.\nfunc NewK8sTokenValidator(\n\tctx context.Context,\n\tk8s k8s.Interface,\n\tdomain *TrustDomain,\n) (identity.Validator, error) {\n\tif err := checkAccess(ctx, k8s.AuthorizationV1()); err != nil {\n\t\treturn nil, err\n\t}\n\n\tauthn := k8s.AuthenticationV1()\n\treturn &K8sTokenValidator{authn, domain}, nil\n}\n\n// Validate accepts kubernetes bearer tokens and returns a DNS-form linkerd ID.\nfunc (k *K8sTokenValidator) Validate(ctx context.Context, tok []byte) (string, error) {\n\ttr := kauthnApi.TokenReview{Spec: kauthnApi.TokenReviewSpec{Token: string(tok), Audiences: []string{LinkerdAudienceKey}}}\n\trvw, err := k.authn.TokenReviews().Create(ctx, &tr, metav1.CreateOptions{})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif rvw.Status.Error != \"\" {\n\t\tif strings.Contains(rvw.Status.Error, \"token audiences\") {\n\t\t\t// Fallback to the default service account token validation if the error is realted to audiences\n\t\t\tlog.Debugf(\"TokenReview with audiences Failed. Falling back to the default\")\n\t\t\ttr = kauthnApi.TokenReview{Spec: kauthnApi.TokenReviewSpec{Token: string(tok), Audiences: []string{}}}\n\t\t\trvw, err = k.authn.TokenReviews().Create(ctx, &tr, metav1.CreateOptions{})\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t}\n\n\t\tif rvw.Status.Error != \"\" {\n\t\t\treturn \"\", identity.InvalidToken{Reason: rvw.Status.Error}\n\t\t}\n\t}\n\n\tif !rvw.Status.Authenticated {\n\t\treturn \"\", identity.NotAuthenticated{}\n\t}\n\n\t// Determine the identity associated with the token's userinfo.\n\tuns := strings.Split(rvw.Status.User.Username, \":\")\n\tif len(uns) != 4 || uns[0] != \"system\" {\n\t\tmsg := fmt.Sprintf(\"Username must be in form system:TYPE:NS:SA: %s\", rvw.Status.User.Username)\n\t\treturn \"\", identity.InvalidToken{Reason: msg}\n\t}\n\tuns = uns[1:]\n\tfor _, l := range uns {\n\t\tif errs := validation.IsDNS1123Label(l); len(errs) > 0 {\n\t\t\treturn \"\", identity.InvalidToken{Reason: fmt.Sprintf(\"Not a label: %s\", l)}\n\t\t}\n\t}\n\n\treturn k.domain.Identity(uns[0], uns[2], uns[1])\n}\n\nfunc checkAccess(ctx context.Context, authz kauthz.AuthorizationV1Interface) error {\n\tr := &kauthzApi.SelfSubjectAccessReview{\n\t\tSpec: kauthzApi.SelfSubjectAccessReviewSpec{\n\t\t\tResourceAttributes: &kauthzApi.ResourceAttributes{\n\t\t\t\tGroup:    \"authentication.k8s.io\",\n\t\t\t\tVersion:  \"v1\",\n\t\t\t\tResource: \"tokenreviews\",\n\t\t\t\tVerb:     \"create\",\n\t\t\t},\n\t\t},\n\t}\n\trvw, err := authz.SelfSubjectAccessReviews().Create(ctx, r, metav1.CreateOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !rvw.Status.Allowed {\n\t\treturn fmt.Errorf(\"Unable to create kubernetes token reviews: %s\", rvw.Status.Reason)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "controller/k8s/api.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"k8s.io/client-go/dynamic\"\n\t\"k8s.io/client-go/rest\"\n\n\tspv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tl5dcrdclient \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tl5dcrdinformer \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions\"\n\tewinformers \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/externalworkload/v1beta1\"\n\tlinkinformers \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/link/v1alpha3\"\n\tsrvinformers \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/server/v1beta3\"\n\tspinformers \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions/serviceprofile/v1alpha2\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"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/types\"\n\t\"k8s.io/client-go/informers\"\n\tarinformers \"k8s.io/client-go/informers/admissionregistration/v1\"\n\tappv1informers \"k8s.io/client-go/informers/apps/v1\"\n\tbatchv1informers \"k8s.io/client-go/informers/batch/v1\"\n\tcoreinformers \"k8s.io/client-go/informers/core/v1\"\n\tdiscoveryinformers \"k8s.io/client-go/informers/discovery/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\n// API provides shared informers for all Kubernetes objects\ntype API struct {\n\tpromGauges\n\n\tClient        kubernetes.Interface\n\tL5dClient     l5dcrdclient.Interface\n\tDynamicClient dynamic.Interface\n\n\tcj       batchv1informers.CronJobInformer\n\tcm       coreinformers.ConfigMapInformer\n\tdeploy   appv1informers.DeploymentInformer\n\tds       appv1informers.DaemonSetInformer\n\tendpoint coreinformers.EndpointsInformer\n\tes       discoveryinformers.EndpointSliceInformer\n\tew       ewinformers.ExternalWorkloadInformer\n\tjob      batchv1informers.JobInformer\n\tlink     linkinformers.LinkInformer\n\tmwc      arinformers.MutatingWebhookConfigurationInformer\n\tns       coreinformers.NamespaceInformer\n\tpod      coreinformers.PodInformer\n\trc       coreinformers.ReplicationControllerInformer\n\trs       appv1informers.ReplicaSetInformer\n\tsp       spinformers.ServiceProfileInformer\n\tss       appv1informers.StatefulSetInformer\n\tsvc      coreinformers.ServiceInformer\n\tnode     coreinformers.NodeInformer\n\tsecret   coreinformers.SecretInformer\n\tsrv      srvinformers.ServerInformer\n\n\tsyncChecks            []cache.InformerSynced\n\tsharedInformers       informers.SharedInformerFactory\n\tl5dCrdSharedInformers l5dcrdinformer.SharedInformerFactory\n}\n\n// InitializeAPI creates Kubernetes clients and returns an initialized API\n// wrapper. This creates informers on each one of resources passed, registering\n// metrics on each one; don't forget to call UnregisterGauges() on the returned\n// API reference to clean them up!\nfunc InitializeAPI(ctx context.Context, kubeConfig string, ensureClusterWideAccess bool, cluster string, resources ...APIResource) (*API, error) {\n\tconfig, err := k8s.GetConfig(kubeConfig, \"\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error configuring Kubernetes API client: %w\", err)\n\t}\n\n\tdynamicClient, err := dynamic.NewForConfig(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tk8sClient, err := k8s.NewAPIForConfig(config, \"\", []string{}, 0, 0, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn initAPI(ctx, k8sClient, dynamicClient, config, ensureClusterWideAccess, cluster, resources...)\n}\n\n// InitializeAPIForConfig creates Kubernetes clients and returns an initialized\n// API wrapper. This creates informers on each one of resources passed,\n// registering metrics on each one; don't forget to call UnregisterGauges() on\n// the returned API reference to clean them up!\nfunc InitializeAPIForConfig(ctx context.Context, kubeConfig *rest.Config, ensureClusterWideAccess bool, cluster string, resources ...APIResource) (*API, error) {\n\tk8sClient, err := k8s.NewAPIForConfig(kubeConfig, \"\", []string{}, 0, 0, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn initAPI(ctx, k8sClient, nil, kubeConfig, ensureClusterWideAccess, cluster, resources...)\n}\n\nfunc initAPI(ctx context.Context, k8sClient *k8s.KubernetesAPI, dynamicClient dynamic.Interface, kubeConfig *rest.Config, ensureClusterWideAccess bool, cluster string, resources ...APIResource) (*API, error) {\n\t// check for cluster-wide access\n\tvar err error\n\n\tif ensureClusterWideAccess {\n\t\terr := k8s.ClusterAccess(ctx, k8sClient)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// check for need and access to Linkerd CRD clients\n\tvar l5dCrdClient *l5dcrdclient.Clientset\n\tfor _, res := range resources {\n\t\tswitch {\n\t\tcase res == SP:\n\t\t\terr := k8s.ServiceProfilesAccess(ctx, k8sClient)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\tcase res == Srv:\n\t\t\terr := k8s.ServersAccess(ctx, k8sClient)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\tcase res == ExtWorkload:\n\t\t\terr := k8s.ExtWorkloadAccess(ctx, k8sClient)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\t\tl5dCrdClient, err = NewL5DCRDClient(kubeConfig)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tbreak\n\t}\n\n\tapi := NewClusterScopedAPI(k8sClient, dynamicClient, l5dCrdClient, cluster, resources...)\n\tfor _, gauge := range api.gauges {\n\t\tif err := prometheus.Register(gauge); err != nil {\n\t\t\tlog.Warnf(\"failed to register Prometheus gauge %s: %s\", gauge.Desc().String(), err)\n\t\t}\n\t}\n\treturn api, nil\n}\n\n// NewClusterScopedAPI takes a Kubernetes client and returns an initialized\n// cluster-wide API. This creates informers on each one of resources passed,\n// registering metrics on each one; don't forget to call UnregisterGauges() on\n// the returned API reference to clean them up!\nfunc NewClusterScopedAPI(\n\tk8sClient kubernetes.Interface,\n\tdynamicClient dynamic.Interface,\n\tl5dCrdClient l5dcrdclient.Interface,\n\tcluster string,\n\tresources ...APIResource,\n) *API {\n\tsharedInformers := informers.NewSharedInformerFactory(k8sClient, ResyncTime)\n\treturn newAPI(k8sClient, dynamicClient, l5dCrdClient, sharedInformers, cluster, resources...)\n}\n\n// NewNamespacedAPI takes a Kubernetes client and returns an initialized API\n// scoped to namespace. This creates informers on each one of resources passed,\n// registering metrics on each one; don't forget to call UnregisterGauges() on\n// the returned API reference to clean them up!\nfunc NewNamespacedAPI(\n\tk8sClient kubernetes.Interface,\n\tdynamicClient dynamic.Interface,\n\tl5dCrdClient l5dcrdclient.Interface,\n\tnamespace string,\n\tcluster string,\n\tresources ...APIResource,\n) *API {\n\tsharedInformers := informers.NewSharedInformerFactoryWithOptions(k8sClient, ResyncTime, informers.WithNamespace(namespace))\n\treturn newAPI(k8sClient, dynamicClient, l5dCrdClient, sharedInformers, cluster, resources...)\n}\n\nfunc NewL5dNamespacedAPI(\n\tl5dCrdClient l5dcrdclient.Interface,\n\tnamespace string,\n\tcluster string,\n\tresources ...APIResource,\n) *API {\n\tl5dCrdSharedInformers := l5dcrdinformer.NewSharedInformerFactoryWithOptions(l5dCrdClient, ResyncTime, l5dcrdinformer.WithNamespace(namespace))\n\n\tapi := &API{\n\t\tL5dClient:             l5dCrdClient,\n\t\tsyncChecks:            make([]cache.InformerSynced, 0),\n\t\tl5dCrdSharedInformers: l5dCrdSharedInformers,\n\t}\n\n\tinformerLabels := prometheus.Labels{\n\t\t\"cluster\": cluster,\n\t}\n\n\tfor _, resource := range resources {\n\t\tswitch resource {\n\t\tcase ExtWorkload:\n\t\t\tapi.ew = l5dCrdSharedInformers.Externalworkload().V1beta1().ExternalWorkloads()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.ew.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.ExtWorkload, informerLabels, api.ew.Informer())\n\t\tcase Link:\n\t\t\tapi.link = l5dCrdSharedInformers.Link().V1alpha3().Links()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.link.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.Link, informerLabels, api.link.Informer())\n\t\tcase SP:\n\t\t\tapi.sp = l5dCrdSharedInformers.Linkerd().V1alpha2().ServiceProfiles()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.sp.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.ServiceProfile, informerLabels, api.sp.Informer())\n\t\tcase Srv:\n\t\t\tapi.srv = l5dCrdSharedInformers.Server().V1beta3().Servers()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.srv.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.Server, informerLabels, api.srv.Informer())\n\t\t}\n\t}\n\treturn api\n}\n\n// newAPI takes a Kubernetes client and returns an initialized API.\nfunc newAPI(\n\tk8sClient kubernetes.Interface,\n\tdynamicClient dynamic.Interface,\n\tl5dCrdClient l5dcrdclient.Interface,\n\tsharedInformers informers.SharedInformerFactory,\n\tcluster string,\n\tresources ...APIResource,\n) *API {\n\tvar l5dCrdSharedInformers l5dcrdinformer.SharedInformerFactory\n\tif l5dCrdClient != nil {\n\t\tl5dCrdSharedInformers = l5dcrdinformer.NewSharedInformerFactory(l5dCrdClient, ResyncTime)\n\t}\n\n\tapi := &API{\n\t\tClient:                k8sClient,\n\t\tL5dClient:             l5dCrdClient,\n\t\tDynamicClient:         dynamicClient,\n\t\tsyncChecks:            make([]cache.InformerSynced, 0),\n\t\tsharedInformers:       sharedInformers,\n\t\tl5dCrdSharedInformers: l5dCrdSharedInformers,\n\t}\n\n\tinformerLabels := prometheus.Labels{\n\t\t\"cluster\": cluster,\n\t}\n\n\tfor _, resource := range resources {\n\t\tswitch resource {\n\t\tcase CJ:\n\t\t\tapi.cj = sharedInformers.Batch().V1().CronJobs()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.cj.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.CronJob, informerLabels, api.cj.Informer())\n\t\tcase CM:\n\t\t\tapi.cm = sharedInformers.Core().V1().ConfigMaps()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.cm.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.ConfigMap, informerLabels, api.cm.Informer())\n\t\tcase Deploy:\n\t\t\tapi.deploy = sharedInformers.Apps().V1().Deployments()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.deploy.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.Deployment, informerLabels, api.deploy.Informer())\n\t\tcase DS:\n\t\t\tapi.ds = sharedInformers.Apps().V1().DaemonSets()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.ds.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.DaemonSet, informerLabels, api.ds.Informer())\n\t\tcase Endpoint:\n\t\t\tapi.endpoint = sharedInformers.Core().V1().Endpoints()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.endpoint.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.Endpoints, informerLabels, api.endpoint.Informer())\n\t\tcase ES:\n\t\t\tapi.es = sharedInformers.Discovery().V1().EndpointSlices()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.es.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.EndpointSlices, informerLabels, api.es.Informer())\n\t\tcase ExtWorkload:\n\t\t\tif l5dCrdSharedInformers == nil {\n\t\t\t\tpanic(\"Linkerd CRD shared informer not configured\")\n\t\t\t}\n\t\t\tapi.ew = l5dCrdSharedInformers.Externalworkload().V1beta1().ExternalWorkloads()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.ew.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.ExtWorkload, informerLabels, api.ew.Informer())\n\t\tcase Job:\n\t\t\tapi.job = sharedInformers.Batch().V1().Jobs()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.job.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.Job, informerLabels, api.job.Informer())\n\t\tcase Link:\n\t\t\tif l5dCrdSharedInformers == nil {\n\t\t\t\tpanic(\"Linkerd CRD shared informer not configured\")\n\t\t\t}\n\t\t\tapi.link = l5dCrdSharedInformers.Link().V1alpha3().Links()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.link.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.Link, informerLabels, api.link.Informer())\n\t\tcase MWC:\n\t\t\tapi.mwc = sharedInformers.Admissionregistration().V1().MutatingWebhookConfigurations()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.mwc.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.MutatingWebhookConfig, informerLabels, api.mwc.Informer())\n\t\tcase NS:\n\t\t\tapi.ns = sharedInformers.Core().V1().Namespaces()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.ns.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.Namespace, informerLabels, api.ns.Informer())\n\t\tcase Pod:\n\t\t\tapi.pod = sharedInformers.Core().V1().Pods()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.pod.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.Pod, informerLabels, api.pod.Informer())\n\t\tcase RC:\n\t\t\tapi.rc = sharedInformers.Core().V1().ReplicationControllers()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.rc.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.ReplicationController, informerLabels, api.rc.Informer())\n\t\tcase RS:\n\t\t\tapi.rs = sharedInformers.Apps().V1().ReplicaSets()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.rs.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.ReplicaSet, informerLabels, api.rs.Informer())\n\t\tcase SP:\n\t\t\tif l5dCrdSharedInformers == nil {\n\t\t\t\tpanic(\"Linkerd CRD shared informer not configured\")\n\t\t\t}\n\t\t\tapi.sp = l5dCrdSharedInformers.Linkerd().V1alpha2().ServiceProfiles()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.sp.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.ServiceProfile, informerLabels, api.sp.Informer())\n\t\tcase Srv:\n\t\t\tif l5dCrdSharedInformers == nil {\n\t\t\t\tpanic(\"Linkerd CRD shared informer not configured\")\n\t\t\t}\n\t\t\tapi.srv = l5dCrdSharedInformers.Server().V1beta3().Servers()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.srv.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.Server, informerLabels, api.srv.Informer())\n\t\tcase SS:\n\t\t\tapi.ss = sharedInformers.Apps().V1().StatefulSets()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.ss.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.StatefulSet, informerLabels, api.ss.Informer())\n\t\tcase Svc:\n\t\t\tapi.svc = sharedInformers.Core().V1().Services()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.svc.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.Service, informerLabels, api.svc.Informer())\n\t\tcase Node:\n\t\t\tapi.node = sharedInformers.Core().V1().Nodes()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.node.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.Node, informerLabels, api.node.Informer())\n\t\tcase Secret:\n\t\t\tapi.secret = sharedInformers.Core().V1().Secrets()\n\t\t\tapi.syncChecks = append(api.syncChecks, api.secret.Informer().HasSynced)\n\t\t\tapi.promGauges.addInformerSize(k8s.Secret, informerLabels, api.secret.Informer())\n\t\t}\n\t}\n\treturn api\n}\n\n// Sync waits for all informers to be synced.\nfunc (api *API) Sync(stopCh <-chan struct{}) {\n\tif api.sharedInformers != nil {\n\t\tapi.sharedInformers.Start(stopCh)\n\t}\n\n\tif api.l5dCrdSharedInformers != nil {\n\t\tapi.l5dCrdSharedInformers.Start(stopCh)\n\t}\n\n\twaitForCacheSync(api.syncChecks)\n}\n\n// UnregisterGauges unregisters all the prometheus cache gauges associated to this API\nfunc (api *API) UnregisterGauges() {\n\tapi.promGauges.unregister()\n}\n\n// NS provides access to a shared informer and lister for Namespaces.\nfunc (api *API) NS() coreinformers.NamespaceInformer {\n\tif api.ns == nil {\n\t\tpanic(\"NS informer not configured\")\n\t}\n\treturn api.ns\n}\n\n// Deploy provides access to a shared informer and lister for Deployments.\nfunc (api *API) Deploy() appv1informers.DeploymentInformer {\n\tif api.deploy == nil {\n\t\tpanic(\"Deploy informer not configured\")\n\t}\n\treturn api.deploy\n}\n\n// DS provides access to a shared informer and lister for Daemonsets.\nfunc (api *API) DS() appv1informers.DaemonSetInformer {\n\tif api.ds == nil {\n\t\tpanic(\"DS informer not configured\")\n\t}\n\treturn api.ds\n}\n\n// SS provides access to a shared informer and lister for Statefulsets.\nfunc (api *API) SS() appv1informers.StatefulSetInformer {\n\tif api.ss == nil {\n\t\tpanic(\"SS informer not configured\")\n\t}\n\treturn api.ss\n}\n\n// RS provides access to a shared informer and lister for ReplicaSets.\nfunc (api *API) RS() appv1informers.ReplicaSetInformer {\n\tif api.rs == nil {\n\t\tpanic(\"RS informer not configured\")\n\t}\n\treturn api.rs\n}\n\n// Pod provides access to a shared informer and lister for Pods.\nfunc (api *API) Pod() coreinformers.PodInformer {\n\tif api.pod == nil {\n\t\tpanic(\"Pod informer not configured\")\n\t}\n\treturn api.pod\n}\n\n// RC provides access to a shared informer and lister for\n// ReplicationControllers.\nfunc (api *API) RC() coreinformers.ReplicationControllerInformer {\n\tif api.rc == nil {\n\t\tpanic(\"RC informer not configured\")\n\t}\n\treturn api.rc\n}\n\n// Svc provides access to a shared informer and lister for Services.\nfunc (api *API) Svc() coreinformers.ServiceInformer {\n\tif api.svc == nil {\n\t\tpanic(\"Svc informer not configured\")\n\t}\n\treturn api.svc\n}\n\n// Endpoint provides access to a shared informer and lister for Endpoints.\nfunc (api *API) Endpoint() coreinformers.EndpointsInformer {\n\tif api.endpoint == nil {\n\t\tpanic(\"Endpoint informer not configured\")\n\t}\n\treturn api.endpoint\n}\n\n// ES provides access to a shared informer and lister for EndpointSlices\nfunc (api *API) ES() discoveryinformers.EndpointSliceInformer {\n\tif api.es == nil {\n\t\tpanic(\"EndpointSlices informer not configured\")\n\t}\n\treturn api.es\n}\n\n// ExtWorkload() provides access to a shared informer and lister for\n// ExternalWorkload CRDs\nfunc (api *API) ExtWorkload() ewinformers.ExternalWorkloadInformer {\n\tif api.ew == nil {\n\t\tpanic(\"ExternalWorkload informer not configured\")\n\t}\n\treturn api.ew\n}\n\n// CM provides access to a shared informer and lister for ConfigMaps.\nfunc (api *API) CM() coreinformers.ConfigMapInformer {\n\tif api.cm == nil {\n\t\tpanic(\"CM informer not configured\")\n\t}\n\treturn api.cm\n}\n\n// SP provides access to a shared informer and lister for ServiceProfiles.\nfunc (api *API) SP() spinformers.ServiceProfileInformer {\n\tif api.sp == nil {\n\t\tpanic(\"SP informer not configured\")\n\t}\n\treturn api.sp\n}\n\n// Srv provides access to a shared informer and lister for Servers.\nfunc (api *API) Srv() srvinformers.ServerInformer {\n\tif api.srv == nil {\n\t\tpanic(\"Srv informer not configured\")\n\t}\n\treturn api.srv\n}\n\n// MWC provides access to a shared informer and lister for MutatingWebhookConfigurations.\nfunc (api *API) MWC() arinformers.MutatingWebhookConfigurationInformer {\n\tif api.mwc == nil {\n\t\tpanic(\"MWC informer not configured\")\n\t}\n\treturn api.mwc\n}\n\n// Job provides access to a shared informer and lister for Jobs.\nfunc (api *API) Job() batchv1informers.JobInformer {\n\tif api.job == nil {\n\t\tpanic(\"Job informer not configured\")\n\t}\n\treturn api.job\n}\n\nfunc (api *API) Link() linkinformers.LinkInformer {\n\tif api.link == nil {\n\t\tpanic(\"Link informer not configured\")\n\t}\n\treturn api.link\n}\n\n// SPAvailable informs the caller whether this API is configured to retrieve\n// ServiceProfiles\nfunc (api *API) SPAvailable() bool {\n\treturn api.sp != nil\n}\n\n// Node provides access to a shared informer and lister for Nodes.\nfunc (api *API) Node() coreinformers.NodeInformer {\n\tif api.node == nil {\n\t\tpanic(\"Node informer not configured\")\n\t}\n\treturn api.node\n}\n\n// Secret provides access to a shared informer and lister for Secrets.\nfunc (api *API) Secret() coreinformers.SecretInformer {\n\tif api.secret == nil {\n\t\tpanic(\"Secret informer not configured\")\n\t}\n\treturn api.secret\n}\n\n// CJ provides access to a shared informer and lister for CronJobs.\nfunc (api *API) CJ() batchv1informers.CronJobInformer {\n\tif api.cj == nil {\n\t\tpanic(\"CJ informer not configured\")\n\t}\n\treturn api.cj\n}\n\n// GetObjects returns a list of Kubernetes objects, given a namespace, type, name and label selector.\n// If namespace is an empty string, match objects in all namespaces.\n// If name is an empty string, match all objects of the given type.\n// If label selector is an empty string, match all labels.\nfunc (api *API) GetObjects(namespace, restype, name string, label labels.Selector) ([]runtime.Object, error) {\n\tswitch restype {\n\tcase k8s.Namespace:\n\t\treturn api.getNamespaces(name, label)\n\tcase k8s.CronJob:\n\t\treturn api.getCronjobs(namespace, name, label)\n\tcase k8s.DaemonSet:\n\t\treturn api.getDaemonsets(namespace, name, label)\n\tcase k8s.Deployment:\n\t\treturn api.getDeployments(namespace, name, label)\n\tcase k8s.Job:\n\t\treturn api.getJobs(namespace, name, label)\n\tcase k8s.Pod:\n\t\treturn api.getPods(namespace, name, label)\n\tcase k8s.ReplicationController:\n\t\treturn api.getRCs(namespace, name, label)\n\tcase k8s.ReplicaSet:\n\t\treturn api.getReplicasets(namespace, name, label)\n\tcase k8s.Service:\n\t\treturn api.getServices(namespace, name)\n\tcase k8s.StatefulSet:\n\t\treturn api.getStatefulsets(namespace, name, label)\n\tdefault:\n\t\treturn nil, status.Errorf(codes.Unimplemented, \"unimplemented resource type: %s\", restype)\n\t}\n}\n\n// KindSupported returns true if there is an informer configured for the\n// specified resource type.\nfunc (api *API) KindSupported(restype string) bool {\n\tswitch restype {\n\tcase k8s.Namespace:\n\t\treturn api.ns != nil\n\tcase k8s.CronJob:\n\t\treturn api.cj != nil\n\tcase k8s.DaemonSet:\n\t\treturn api.ds != nil\n\tcase k8s.Deployment:\n\t\treturn api.deploy != nil\n\tcase k8s.Job:\n\t\treturn api.job != nil\n\tcase k8s.Pod:\n\t\treturn api.pod != nil\n\tcase k8s.ReplicationController:\n\t\treturn api.rc != nil\n\tcase k8s.ReplicaSet:\n\t\treturn api.rs != nil\n\tcase k8s.Service:\n\t\treturn api.svc != nil\n\tcase k8s.StatefulSet:\n\t\treturn api.ss != nil\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// GetOwnerKindAndName returns the pod owner's kind and name, using owner\n// references from the Kubernetes API. The kind is represented as the Kubernetes\n// singular resource type (e.g. deployment, daemonset, job, etc.).\n// If retry is true, when the shared informer cache doesn't return anything\n// we try again with a direct Kubernetes API call.\nfunc (api *API) GetOwnerKindAndName(ctx context.Context, pod *corev1.Pod, retry bool) (string, string) {\n\townerRefs := pod.GetOwnerReferences()\n\tif len(ownerRefs) == 0 {\n\t\t// pod without a parent\n\t\treturn k8s.Pod, pod.Name\n\t} else if len(ownerRefs) > 1 {\n\t\tlog.Debugf(\"unexpected owner reference count (%d): %+v\", len(ownerRefs), ownerRefs)\n\t\treturn k8s.Pod, pod.Name\n\t}\n\n\tparent := ownerRefs[0]\n\tvar parentObj metav1.Object\n\tvar err error\n\tswitch parent.Kind {\n\tcase \"Job\":\n\t\tparentObj, err = api.Job().Lister().Jobs(pod.Namespace).Get(parent.Name)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"failed to retrieve job from indexer %s/%s: %s\", pod.Namespace, parent.Name, err)\n\t\t\tif retry {\n\t\t\t\tparentObj, err = api.Client.BatchV1().Jobs(pod.Namespace).Get(ctx, parent.Name, metav1.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Warnf(\"failed to retrieve job from direct API call %s/%s: %s\", pod.Namespace, parent.Name, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tcase \"ReplicaSet\":\n\t\trsObj, err := api.RS().Lister().ReplicaSets(pod.Namespace).Get(parent.Name)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"failed to retrieve replicaset from indexer %s/%s: %s\", pod.Namespace, parent.Name, err)\n\t\t\tif retry {\n\t\t\t\trsObj, err = api.Client.AppsV1().ReplicaSets(pod.Namespace).Get(ctx, parent.Name, metav1.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Warnf(\"failed to retrieve replicaset from direct API call %s/%s: %s\", pod.Namespace, parent.Name, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif rsObj == nil || !isValidRSParent(rsObj.GetObjectMeta()) {\n\t\t\treturn strings.ToLower(parent.Kind), parent.Name\n\t\t}\n\t\tparentObj = rsObj\n\n\tdefault:\n\t\treturn strings.ToLower(parent.Kind), parent.Name\n\t}\n\n\tif err == nil && len(parentObj.GetOwnerReferences()) == 1 {\n\t\tgrandParent := parentObj.GetOwnerReferences()[0]\n\t\treturn strings.ToLower(grandParent.Kind), grandParent.Name\n\t}\n\treturn strings.ToLower(parent.Kind), parent.Name\n}\n\n// GetPodsFor returns all running and pending Pods associated with a given\n// Kubernetes object. Use includeFailed to also get failed Pods\nfunc (api *API) GetPodsFor(obj runtime.Object, includeFailed bool) ([]*corev1.Pod, error) {\n\tvar namespace string\n\tvar selector labels.Selector\n\tvar ownerUID types.UID\n\tvar err error\n\n\tpods := []*corev1.Pod{}\n\tswitch typed := obj.(type) {\n\tcase *corev1.Namespace:\n\t\tnamespace = typed.Name\n\t\tselector = labels.Everything()\n\n\tcase *batchv1.CronJob:\n\t\tnamespace = typed.Namespace\n\t\tselector = labels.Everything()\n\t\tjobs, err := api.Job().Lister().Jobs(namespace).List(selector)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, job := range jobs {\n\t\t\tif isOwner(typed.UID, job.GetOwnerReferences()) {\n\t\t\t\tjobPods, err := api.GetPodsFor(job, includeFailed)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tpods = append(pods, jobPods...)\n\t\t\t}\n\t\t}\n\t\treturn pods, nil\n\n\tcase *appsv1.DaemonSet:\n\t\tnamespace = typed.Namespace\n\t\tselector = labels.Set(typed.Spec.Selector.MatchLabels).AsSelector()\n\t\townerUID = typed.UID\n\n\tcase *appsv1.Deployment:\n\t\tnamespace = typed.Namespace\n\t\tselector = labels.Set(typed.Spec.Selector.MatchLabels).AsSelector()\n\t\tret, err := api.RS().Lister().ReplicaSets(namespace).List(selector)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, rs := range ret {\n\t\t\tif isOwner(typed.UID, rs.GetOwnerReferences()) {\n\t\t\t\tpodsRS, err := api.GetPodsFor(rs, includeFailed)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tpods = append(pods, podsRS...)\n\t\t\t}\n\t\t}\n\t\treturn pods, nil\n\n\tcase *appsv1.ReplicaSet:\n\t\tnamespace = typed.Namespace\n\t\tselector = labels.Set(typed.Spec.Selector.MatchLabels).AsSelector()\n\t\townerUID = typed.UID\n\n\tcase *batchv1.Job:\n\t\tnamespace = typed.Namespace\n\t\tselector = labels.Set(typed.Spec.Selector.MatchLabels).AsSelector()\n\t\townerUID = typed.UID\n\n\tcase *corev1.ReplicationController:\n\t\tnamespace = typed.Namespace\n\t\tselector = labels.Set(typed.Spec.Selector).AsSelector()\n\t\townerUID = typed.UID\n\n\tcase *corev1.Service:\n\t\tif typed.Spec.Type == corev1.ServiceTypeExternalName {\n\t\t\treturn []*corev1.Pod{}, nil\n\t\t}\n\t\tnamespace = typed.Namespace\n\t\tif typed.Spec.Selector == nil {\n\t\t\tselector = labels.Nothing()\n\t\t} else {\n\t\t\tselector = labels.Set(typed.Spec.Selector).AsSelector()\n\t\t}\n\n\tcase *appsv1.StatefulSet:\n\t\tnamespace = typed.Namespace\n\t\tselector = labels.Set(typed.Spec.Selector.MatchLabels).AsSelector()\n\t\townerUID = typed.UID\n\n\tcase *corev1.Pod:\n\t\t// Special case for pods:\n\t\t// GetPodsFor a pod should just return the pod itself\n\t\tnamespace = typed.Namespace\n\t\tpod, err := api.Pod().Lister().Pods(typed.Namespace).Get(typed.Name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpods = []*corev1.Pod{pod}\n\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"Cannot get object selector: %v\", obj)\n\t}\n\n\t// if obj.(type) is Pod, we've already retrieved it and put it in pods\n\t// for the other types, pods will still be empty\n\tif len(pods) == 0 {\n\t\tpods, err = api.Pod().Lister().Pods(namespace).List(selector)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tallPods := []*corev1.Pod{}\n\tfor _, pod := range pods {\n\t\tif isPendingOrRunning(pod) || (includeFailed && isFailed(pod)) {\n\t\t\tif ownerUID == \"\" || isOwner(ownerUID, pod.GetOwnerReferences()) {\n\t\t\t\tallPods = append(allPods, pod)\n\t\t\t}\n\t\t}\n\t}\n\treturn allPods, nil\n}\n\nfunc isOwner(u types.UID, ownerRefs []metav1.OwnerReference) bool {\n\tfor _, or := range ownerRefs {\n\t\tif u == or.UID {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// GetNameAndNamespaceOf returns the name and namespace of the given object.\nfunc GetNameAndNamespaceOf(obj runtime.Object) (string, string, error) {\n\tswitch typed := obj.(type) {\n\tcase *corev1.Namespace:\n\t\treturn typed.Name, typed.Name, nil\n\n\tcase *batchv1.CronJob:\n\t\treturn typed.Name, typed.Namespace, nil\n\n\tcase *appsv1.DaemonSet:\n\t\treturn typed.Name, typed.Namespace, nil\n\n\tcase *appsv1.Deployment:\n\t\treturn typed.Name, typed.Namespace, nil\n\n\tcase *batchv1.Job:\n\t\treturn typed.Name, typed.Namespace, nil\n\n\tcase *appsv1.ReplicaSet:\n\t\treturn typed.Name, typed.Namespace, nil\n\n\tcase *corev1.ReplicationController:\n\t\treturn typed.Name, typed.Namespace, nil\n\n\tcase *corev1.Service:\n\t\treturn typed.Name, typed.Namespace, nil\n\n\tcase *appsv1.StatefulSet:\n\t\treturn typed.Name, typed.Namespace, nil\n\n\tcase *corev1.Pod:\n\t\treturn typed.Name, typed.Namespace, nil\n\n\tdefault:\n\t\treturn \"\", \"\", fmt.Errorf(\"Cannot determine object type: %v\", obj)\n\t}\n}\n\n// GetNameOf returns the name of the given object.\nfunc GetNameOf(obj runtime.Object) (string, error) {\n\tname, _, err := GetNameAndNamespaceOf(obj)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn name, nil\n}\n\n// GetNamespaceOf returns the namespace of the given object.\nfunc GetNamespaceOf(obj runtime.Object) (string, error) {\n\t_, namespace, err := GetNameAndNamespaceOf(obj)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn namespace, nil\n}\n\n// getNamespaces returns the namespace matching the specified name. If no name\n// is given, it returns all namespaces.\nfunc (api *API) getNamespaces(name string, labelSelector labels.Selector) ([]runtime.Object, error) {\n\tvar namespaces []*corev1.Namespace\n\n\tif name == \"\" {\n\t\tvar err error\n\t\tnamespaces, err = api.NS().Lister().List(labelSelector)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tnamespace, err := api.NS().Lister().Get(name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnamespaces = []*corev1.Namespace{namespace}\n\t}\n\n\tobjects := []runtime.Object{}\n\tfor _, ns := range namespaces {\n\t\tobjects = append(objects, ns)\n\t}\n\n\treturn objects, nil\n}\n\nfunc (api *API) getDeployments(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {\n\tvar err error\n\tvar deploys []*appsv1.Deployment\n\n\tif namespace == \"\" {\n\t\tdeploys, err = api.Deploy().Lister().List(labelSelector)\n\t} else if name == \"\" {\n\t\tdeploys, err = api.Deploy().Lister().Deployments(namespace).List(labelSelector)\n\t} else {\n\t\tvar deploy *appsv1.Deployment\n\t\tdeploy, err = api.Deploy().Lister().Deployments(namespace).Get(name)\n\t\tdeploys = []*appsv1.Deployment{deploy}\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobjects := []runtime.Object{}\n\tfor _, deploy := range deploys {\n\t\tobjects = append(objects, deploy)\n\t}\n\n\treturn objects, nil\n}\n\nfunc (api *API) getPods(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {\n\tvar err error\n\tvar pods []*corev1.Pod\n\n\tif namespace == \"\" {\n\t\tpods, err = api.Pod().Lister().List(labelSelector)\n\t} else if name == \"\" {\n\t\tpods, err = api.Pod().Lister().Pods(namespace).List(labelSelector)\n\t} else {\n\t\tvar pod *corev1.Pod\n\t\tpod, err = api.Pod().Lister().Pods(namespace).Get(name)\n\t\tpods = []*corev1.Pod{pod}\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobjects := []runtime.Object{}\n\tfor _, pod := range pods {\n\t\tif !isPendingOrRunning(pod) {\n\t\t\tcontinue\n\t\t}\n\t\tobjects = append(objects, pod)\n\t}\n\n\treturn objects, nil\n}\n\nfunc (api *API) getRCs(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {\n\tvar err error\n\tvar rcs []*corev1.ReplicationController\n\n\tif namespace == \"\" {\n\t\trcs, err = api.RC().Lister().List(labelSelector)\n\t} else if name == \"\" {\n\t\trcs, err = api.RC().Lister().ReplicationControllers(namespace).List(labelSelector)\n\t} else {\n\t\tvar rc *corev1.ReplicationController\n\t\trc, err = api.RC().Lister().ReplicationControllers(namespace).Get(name)\n\t\trcs = []*corev1.ReplicationController{rc}\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobjects := []runtime.Object{}\n\tfor _, rc := range rcs {\n\t\tobjects = append(objects, rc)\n\t}\n\n\treturn objects, nil\n}\n\nfunc (api *API) getDaemonsets(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {\n\tvar err error\n\tvar daemonsets []*appsv1.DaemonSet\n\n\tif namespace == \"\" {\n\t\tdaemonsets, err = api.DS().Lister().List(labelSelector)\n\t} else if name == \"\" {\n\t\tdaemonsets, err = api.DS().Lister().DaemonSets(namespace).List(labelSelector)\n\t} else {\n\t\tvar ds *appsv1.DaemonSet\n\t\tds, err = api.DS().Lister().DaemonSets(namespace).Get(name)\n\t\tdaemonsets = []*appsv1.DaemonSet{ds}\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobjects := []runtime.Object{}\n\tfor _, ds := range daemonsets {\n\t\tobjects = append(objects, ds)\n\t}\n\n\treturn objects, nil\n}\n\nfunc (api *API) getStatefulsets(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {\n\tvar err error\n\tvar statefulsets []*appsv1.StatefulSet\n\n\tif namespace == \"\" {\n\t\tstatefulsets, err = api.SS().Lister().List(labelSelector)\n\t} else if name == \"\" {\n\t\tstatefulsets, err = api.SS().Lister().StatefulSets(namespace).List(labelSelector)\n\t} else {\n\t\tvar ss *appsv1.StatefulSet\n\t\tss, err = api.SS().Lister().StatefulSets(namespace).Get(name)\n\t\tstatefulsets = []*appsv1.StatefulSet{ss}\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobjects := []runtime.Object{}\n\tfor _, ss := range statefulsets {\n\t\tobjects = append(objects, ss)\n\t}\n\n\treturn objects, nil\n}\n\nfunc (api *API) getJobs(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {\n\tvar err error\n\tvar jobs []*batchv1.Job\n\n\tif namespace == \"\" {\n\t\tjobs, err = api.Job().Lister().List(labelSelector)\n\t} else if name == \"\" {\n\t\tjobs, err = api.Job().Lister().Jobs(namespace).List(labelSelector)\n\t} else {\n\t\tvar job *batchv1.Job\n\t\tjob, err = api.Job().Lister().Jobs(namespace).Get(name)\n\t\tjobs = []*batchv1.Job{job}\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobjects := []runtime.Object{}\n\tfor _, job := range jobs {\n\t\tobjects = append(objects, job)\n\t}\n\n\treturn objects, nil\n}\n\nfunc (api *API) getServices(namespace, name string) ([]runtime.Object, error) {\n\tservices, err := api.GetServices(namespace, name)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobjects := []runtime.Object{}\n\tfor _, svc := range services {\n\t\tobjects = append(objects, svc)\n\t}\n\n\treturn objects, nil\n}\n\nfunc (api *API) getCronjobs(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {\n\tvar err error\n\tvar cronjobs []*batchv1.CronJob\n\n\tif namespace == \"\" {\n\t\tcronjobs, err = api.CJ().Lister().List(labelSelector)\n\t} else if name == \"\" {\n\t\tcronjobs, err = api.CJ().Lister().CronJobs(namespace).List(labelSelector)\n\t} else {\n\t\tvar cronjob *batchv1.CronJob\n\t\tcronjob, err = api.CJ().Lister().CronJobs(namespace).Get(name)\n\t\tcronjobs = []*batchv1.CronJob{cronjob}\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobjects := []runtime.Object{}\n\tfor _, cronjob := range cronjobs {\n\t\tobjects = append(objects, cronjob)\n\t}\n\n\treturn objects, nil\n}\n\nfunc (api *API) getReplicasets(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {\n\tvar err error\n\tvar replicasets []*appsv1.ReplicaSet\n\n\tif namespace == \"\" {\n\t\treplicasets, err = api.RS().Lister().List(labelSelector)\n\t} else if name == \"\" {\n\t\treplicasets, err = api.RS().Lister().ReplicaSets(namespace).List(labelSelector)\n\t} else {\n\t\tvar replicaset *appsv1.ReplicaSet\n\t\treplicaset, err = api.RS().Lister().ReplicaSets(namespace).Get(name)\n\t\treplicasets = []*appsv1.ReplicaSet{replicaset}\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobjects := []runtime.Object{}\n\tfor _, replicaset := range replicasets {\n\t\tobjects = append(objects, replicaset)\n\t}\n\n\treturn objects, nil\n}\n\n// GetServices returns a list of Service resources, based on input namespace and\n// name.\nfunc (api *API) GetServices(namespace, name string) ([]*corev1.Service, error) {\n\tvar err error\n\tvar services []*corev1.Service\n\n\tif namespace == \"\" {\n\t\tservices, err = api.Svc().Lister().List(labels.Everything())\n\t} else if name == \"\" {\n\t\tservices, err = api.Svc().Lister().Services(namespace).List(labels.Everything())\n\t} else {\n\t\tvar svc *corev1.Service\n\t\tsvc, err = api.Svc().Lister().Services(namespace).Get(name)\n\t\tservices = []*corev1.Service{svc}\n\t}\n\n\treturn services, err\n}\n\n// GetServicesFor returns all Service resources which include a pod of the given\n// resource object.  In other words, it returns all Services of which the given\n// resource object is a part of.\nfunc (api *API) GetServicesFor(obj runtime.Object, includeFailed bool) ([]*corev1.Service, error) {\n\tif svc, ok := obj.(*corev1.Service); ok {\n\t\treturn []*corev1.Service{svc}, nil\n\t}\n\n\tpods, err := api.GetPodsFor(obj, includeFailed)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnamespace, err := GetNamespaceOf(obj)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tallServices, err := api.GetServices(namespace, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tservices := make([]*corev1.Service, 0)\n\tfor _, svc := range allServices {\n\t\tsvcPods, err := api.GetPodsFor(svc, includeFailed)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif hasOverlap(pods, svcPods) {\n\t\t\tservices = append(services, svc)\n\t\t}\n\t}\n\treturn services, nil\n}\n\n// GetServiceProfileFor returns the service profile for a given service.  We\n// first look for a matching service profile in the client's namespace.  If not\n// found, we then look in the service's namespace.  If no service profile is\n// found, we return the default service profile.\nfunc (api *API) GetServiceProfileFor(svc *corev1.Service, clientNs, clusterDomain string) *spv1alpha2.ServiceProfile {\n\tdst := fmt.Sprintf(\"%s.%s.svc.%s\", svc.Name, svc.Namespace, clusterDomain)\n\t// First attempt to lookup profile in client namespace\n\tif clientNs != \"\" {\n\t\tp, err := api.SP().Lister().ServiceProfiles(clientNs).Get(dst)\n\t\tif err == nil {\n\t\t\treturn p\n\t\t}\n\t\tif !apierrors.IsNotFound(err) {\n\t\t\tlog.Errorf(\"error getting service profile for %s in %s namespace: %s\", dst, clientNs, err)\n\t\t}\n\t}\n\t// Second, attempt to lookup profile in server namespace\n\tif svc.Namespace != clientNs {\n\t\tp, err := api.SP().Lister().ServiceProfiles(svc.Namespace).Get(dst)\n\t\tif err == nil {\n\t\t\treturn p\n\t\t}\n\t\tif !apierrors.IsNotFound(err) {\n\t\t\tlog.Errorf(\"error getting service profile for %s in %s namespace: %s\", dst, svc.Namespace, err)\n\t\t}\n\t}\n\t// Not found; return default.\n\tlog.Debugf(\"no Service Profile found for '%s' -- using default\", dst)\n\treturn &spv1alpha2.ServiceProfile{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: dst,\n\t\t},\n\t\tSpec: spv1alpha2.ServiceProfileSpec{\n\t\t\tRoutes: []*spv1alpha2.RouteSpec{},\n\t\t},\n\t}\n}\n\nfunc hasOverlap(as, bs []*corev1.Pod) bool {\n\tfor _, a := range as {\n\t\tfor _, b := range bs {\n\t\t\tif a.Name == b.Name {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc isPendingOrRunning(pod *corev1.Pod) bool {\n\tpending := pod.Status.Phase == corev1.PodPending\n\trunning := pod.Status.Phase == corev1.PodRunning\n\tterminating := pod.DeletionTimestamp != nil\n\treturn (pending || running) && !terminating\n}\n\nfunc isFailed(pod *corev1.Pod) bool {\n\treturn pod.Status.Phase == corev1.PodFailed\n}\n"
  },
  {
    "path": "controller/k8s/api_resource.go",
    "content": "package k8s\n\nimport (\n\t\"strings\"\n\n\tserverv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta3\"\n\tsazv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1\"\n\tspv1alpha2 \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tadmissionregistrationv1 \"k8s.io/api/admissionregistration/v1\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\tdiscoveryv1 \"k8s.io/api/discovery/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\n// APIResource is an enum for Kubernetes API resource types, for use when\n// initializing a K8s API, to describe which resource types to interact with.\ntype APIResource int\n\n// These constants enumerate Kubernetes resource types.\n// TODO: Unify with the resources listed in pkg/k8s/k8s.go\nconst (\n\tCJ APIResource = iota\n\tCM\n\tDeploy\n\tDS\n\tEndpoint\n\tES // EndpointSlice resource\n\tExtWorkload\n\tJob\n\tLink\n\tMWC\n\tNS\n\tPod\n\tRC\n\tRS\n\tSP\n\tSS\n\tSvc\n\tNode\n\tSecret\n\tSrv\n\tSaz\n)\n\n// GVK returns the GroupVersionKind corresponding for the provided APIResource\nfunc (res APIResource) GVK() (schema.GroupVersionKind, error) {\n\tswitch res {\n\tcase CJ:\n\t\treturn batchv1.SchemeGroupVersion.WithKind(\"CronJob\"), nil\n\tcase CM:\n\t\treturn v1.SchemeGroupVersion.WithKind(\"ConfigMap\"), nil\n\tcase Deploy:\n\t\treturn appsv1.SchemeGroupVersion.WithKind(\"Deployment\"), nil\n\tcase DS:\n\t\treturn appsv1.SchemeGroupVersion.WithKind(\"DaemonSet\"), nil\n\tcase Endpoint:\n\t\treturn v1.SchemeGroupVersion.WithKind(\"Endpoint\"), nil\n\tcase ES:\n\t\treturn discoveryv1.SchemeGroupVersion.WithKind(\"EndpointSlice\"), nil\n\tcase Job:\n\t\treturn batchv1.SchemeGroupVersion.WithKind(\"Job\"), nil\n\tcase MWC:\n\t\treturn admissionregistrationv1.SchemeGroupVersion.WithKind(\"MutatingWebhookConfiguration\"), nil\n\tcase Node:\n\t\treturn v1.SchemeGroupVersion.WithKind(\"Node\"), nil\n\tcase NS:\n\t\treturn v1.SchemeGroupVersion.WithKind(\"Namespace\"), nil\n\tcase Pod:\n\t\treturn v1.SchemeGroupVersion.WithKind(\"Pod\"), nil\n\tcase RC:\n\t\treturn v1.SchemeGroupVersion.WithKind(\"ReplicationController\"), nil\n\tcase RS:\n\t\treturn appsv1.SchemeGroupVersion.WithKind(\"ReplicaSet\"), nil\n\tcase Saz:\n\t\treturn sazv1beta1.SchemeGroupVersion.WithKind(\"ServerAuthorization\"), nil\n\tcase Secret:\n\t\treturn v1.SchemeGroupVersion.WithKind(\"Secret\"), nil\n\tcase SP:\n\t\treturn spv1alpha2.SchemeGroupVersion.WithKind(\"ServiceProfile\"), nil\n\tcase SS:\n\t\treturn appsv1.SchemeGroupVersion.WithKind(\"StatefulSet\"), nil\n\tcase Srv:\n\t\treturn serverv1beta3.SchemeGroupVersion.WithKind(\"Server\"), nil\n\tcase Svc:\n\t\treturn v1.SchemeGroupVersion.WithKind(\"Service\"), nil\n\tdefault:\n\t\treturn schema.GroupVersionKind{}, status.Errorf(codes.Unimplemented, \"unimplemented resource type: %d\", res)\n\t}\n}\n\n// GetAPIResource returns the APIResource for the provided kind\nfunc GetAPIResource(kind string) (APIResource, error) {\n\tswitch strings.ToLower(kind) {\n\tcase k8s.CronJob:\n\t\treturn CJ, nil\n\tcase k8s.ConfigMap:\n\t\treturn CM, nil\n\tcase k8s.Deployment:\n\t\treturn Deploy, nil\n\tcase k8s.DaemonSet:\n\t\treturn DS, nil\n\tcase k8s.Endpoints:\n\t\treturn Endpoint, nil\n\tcase k8s.EndpointSlices:\n\t\treturn ES, nil\n\tcase k8s.Job:\n\t\treturn Job, nil\n\tcase k8s.MutatingWebhookConfig:\n\t\treturn MWC, nil\n\tcase k8s.Namespace:\n\t\treturn NS, nil\n\tcase k8s.Node:\n\t\treturn Node, nil\n\tcase k8s.Pod:\n\t\treturn Pod, nil\n\tcase k8s.ReplicationController:\n\t\treturn RC, nil\n\tcase k8s.ReplicaSet:\n\t\treturn RS, nil\n\tcase k8s.ServerAuthorization:\n\t\treturn Saz, nil\n\tcase k8s.Secret:\n\t\treturn Secret, nil\n\tcase k8s.ServiceProfile:\n\t\treturn SP, nil\n\tcase k8s.Service:\n\t\treturn Svc, nil\n\tcase k8s.StatefulSet:\n\t\treturn SS, nil\n\tcase k8s.Server:\n\t\treturn Srv, nil\n\tdefault:\n\t\treturn 0, status.Errorf(codes.Unimplemented, \"unimplemented resource type: %s\", kind)\n\t}\n}\n"
  },
  {
    "path": "controller/k8s/api_test.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n)\n\ntype resources struct {\n\tresults []string\n\tmisc    []string\n}\n\n// newMockAPI constructs a mock controller/k8s.API object for testing If\n// useInformer is true, it forces informer indexing, enabling informer lookups\nfunc newMockAPI(useInformer bool, res resources) (\n\t*API,\n\t*MetadataAPI,\n\t[]runtime.Object,\n\terror,\n) {\n\tk8sConfigs := []string{}\n\tk8sResults := []runtime.Object{}\n\n\tfor _, config := range res.results {\n\t\tobj, err := k8s.ToRuntimeObject(config)\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, err\n\t\t}\n\t\tk8sConfigs = append(k8sConfigs, config)\n\t\tk8sResults = append(k8sResults, obj)\n\t}\n\n\tk8sConfigs = append(k8sConfigs, res.misc...)\n\n\tapi, err := NewFakeAPI(k8sConfigs...)\n\tif err != nil {\n\t\treturn nil, nil, nil, fmt.Errorf(\"NewFakeAPI returned an error: %w\", err)\n\t}\n\n\tmetadataAPI, err := NewFakeMetadataAPI(k8sConfigs)\n\tif err != nil {\n\t\treturn nil, nil, nil, fmt.Errorf(\"NewFakeMetadataAPI returned an error: %w\", err)\n\t}\n\n\tif useInformer {\n\t\tapi.Sync(nil)\n\t\tmetadataAPI.Sync(nil)\n\t}\n\n\treturn api, metadataAPI, k8sResults, nil\n}\n\n// TestGetObjects tests both api.GetObjects() and\n// metadataAPI.GetByNamespaceFiltered()\nfunc TestGetObjects(t *testing.T) {\n\n\ttype getObjectsExpected struct {\n\t\tresources\n\n\t\terr       error\n\t\tnamespace string\n\t\tresType   string\n\t\tname      string\n\t}\n\n\tt.Run(\"Returns expected objects based on input\", func(t *testing.T) {\n\t\texpectations := []getObjectsExpected{\n\t\t\t{\n\t\t\t\terr:       status.Errorf(codes.Unimplemented, \"unimplemented resource type: bar\"),\n\t\t\t\tnamespace: \"foo\",\n\t\t\t\tresType:   \"bar\",\n\t\t\t\tname:      \"baz\",\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{},\n\t\t\t\t\tmisc:    []string{},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\terr:       nil,\n\t\t\t\tnamespace: \"my-ns\",\n\t\t\t\tresType:   k8s.Pod,\n\t\t\t\tname:      \"my-pod\",\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: my-pod\n  namespace: my-ns\nspec:\n  containers:\n  - name: my-pod\nstatus:\n  phase: Running`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\terr:       errors.New(\"\\\"my-pod\\\" not found\"),\n\t\t\t\tnamespace: \"not-my-ns\",\n\t\t\t\tresType:   k8s.Pod,\n\t\t\t\tname:      \"my-pod\",\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{},\n\t\t\t\t\tmisc: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: my-pod\n  namespace: my-ns`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\terr:       nil,\n\t\t\t\tnamespace: \"\",\n\t\t\t\tresType:   k8s.ReplicationController,\n\t\t\t\tname:      \"\",\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: v1\nkind: ReplicationController\nmetadata:\n  name: my-rc\n  namespace: my-ns`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\terr:       nil,\n\t\t\t\tnamespace: \"my-ns\",\n\t\t\t\tresType:   k8s.Deployment,\n\t\t\t\tname:      \"\",\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: my-deploy\n  namespace: my-ns`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{`\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: my-deploy\n  namespace: not-my-ns`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\terr:       nil,\n\t\t\t\tnamespace: \"\",\n\t\t\t\tresType:   k8s.DaemonSet,\n\t\t\t\tname:      \"\",\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: my-ds\n  namespace: my-ns`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\terr:       nil,\n\t\t\t\tnamespace: \"my-ns\",\n\t\t\t\tresType:   k8s.DaemonSet,\n\t\t\t\tname:      \"my-ds\",\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: my-ds\n  namespace: my-ns`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{`\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: my-ds\n  namespace: not-my-ns`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\terr:       nil,\n\t\t\t\tnamespace: \"my-ns\",\n\t\t\t\tresType:   k8s.Job,\n\t\t\t\tname:      \"my-job\",\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: my-job\n  namespace: my-ns`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{`\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: my-job\n  namespace: not-my-ns`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\terr:       nil,\n\t\t\t\tnamespace: \"my-ns\",\n\t\t\t\tresType:   k8s.CronJob,\n\t\t\t\tname:      \"my-cronjob\",\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: my-cronjob\n  namespace: my-ns`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{`\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: my-cronjob\n  namespace: not-my-ns`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\terr:       nil,\n\t\t\t\tnamespace: \"\",\n\t\t\t\tresType:   k8s.StatefulSet,\n\t\t\t\tname:      \"\",\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: my-ss\n  namespace: my-ns`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\terr:       nil,\n\t\t\t\tnamespace: \"my-ns\",\n\t\t\t\tresType:   k8s.StatefulSet,\n\t\t\t\tname:      \"my-ss\",\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: my-ss\n  namespace: my-ns`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{`\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: my-ss\n  namespace: not-my-ns`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\terr:       nil,\n\t\t\t\tnamespace: \"\",\n\t\t\t\tresType:   k8s.Namespace,\n\t\t\t\tname:      \"\",\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: my-ns`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tfor _, exp := range expectations {\n\t\t\tapi, metadataAPI, k8sResults, err := newMockAPI(true, exp.resources)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"newMockAPI error: %s\", err)\n\t\t\t}\n\n\t\t\tpods, err := api.GetObjects(exp.namespace, exp.resType, exp.name, labels.Everything())\n\t\t\tif err != nil || exp.err != nil {\n\t\t\t\tif unexpectedErrors(err, exp.err) {\n\t\t\t\t\tt.Fatalf(\"api.GetObjects() unexpected error, expected [%s] got: [%s]\", exp.err, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif diff := deep.Equal(pods, k8sResults); diff != nil {\n\t\t\t\t\tt.Fatalf(\"Expected: %+v\", diff)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar objMetas []*metav1.PartialObjectMetadata\n\t\t\tres, err := GetAPIResource(exp.resType)\n\t\t\tif err == nil {\n\t\t\t\tobjMetas, err = metadataAPI.GetByNamespaceFiltered(res, exp.namespace, exp.name, labels.Everything())\n\t\t\t}\n\t\t\tif err != nil || exp.err != nil {\n\t\t\t\tif unexpectedErrors(err, exp.err) {\n\t\t\t\t\tfmt.Printf(\"objMetas: %#v\\n\", objMetas)\n\t\t\t\t\tt.Fatalf(\"metadataAPI.GetNamespaceFilteredCache() unexpected error, expected [%s] got: [%s]\", exp.err, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\texpMetas := []*metav1.PartialObjectMetadata{}\n\t\t\t\tfor _, obj := range k8sResults {\n\t\t\t\t\tobjMeta, err := toPartialObjectMetadata(obj)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"error converting Object to PartialObjectMetadata: %s\", err)\n\t\t\t\t\t}\n\t\t\t\t\texpMetas = append(expMetas, objMeta)\n\t\t\t\t}\n\t\t\t\tif diff := deep.Equal(objMetas, expMetas); diff != nil {\n\t\t\t\t\tt.Fatalf(\"Expected: %+v\", diff)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"If objects are pods\", func(t *testing.T) {\n\t\tt.Run(\"Return running or pending pods\", func(t *testing.T) {\n\t\t\texpectations := []getObjectsExpected{\n\t\t\t\t{\n\t\t\t\t\terr:       nil,\n\t\t\t\t\tnamespace: \"my-ns\",\n\t\t\t\t\tresType:   k8s.Pod,\n\t\t\t\t\tname:      \"my-pod\",\n\t\t\t\t\tresources: resources{\n\t\t\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: my-pod\n  namespace: my-ns\nspec:\n  containers:\n  - name: my-pod\nstatus:\n  phase: Running`,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\terr:       nil,\n\t\t\t\t\tnamespace: \"my-ns\",\n\t\t\t\t\tresType:   k8s.Pod,\n\t\t\t\t\tname:      \"my-pod\",\n\t\t\t\t\tresources: resources{\n\t\t\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: my-pod\n  namespace: my-ns\nspec:\n  containers:\n  - name: my-pod\nstatus:\n  phase: Pending`,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tfor _, exp := range expectations {\n\t\t\t\tapi, _, k8sResults, err := newMockAPI(true, exp.resources)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"newMockAPI error: %s\", err)\n\t\t\t\t}\n\n\t\t\t\tpods, err := api.GetObjects(exp.namespace, exp.resType, exp.name, labels.Everything())\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"api.GetObjects() unexpected error %s\", err)\n\t\t\t\t}\n\n\t\t\t\tif diff := deep.Equal(pods, k8sResults); diff != nil {\n\t\t\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"Don't return failed or succeeded pods\", func(t *testing.T) {\n\t\t\texpectations := []getObjectsExpected{\n\t\t\t\t{\n\t\t\t\t\terr:       nil,\n\t\t\t\t\tnamespace: \"my-ns\",\n\t\t\t\t\tresType:   k8s.Pod,\n\t\t\t\t\tname:      \"my-pod\",\n\t\t\t\t\tresources: resources{\n\t\t\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: my-pod\n  namespace: my-ns\nspec:\n  containers:\n  - name: my-pod\nstatus:\n  phase: Succeeded`,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\terr:       nil,\n\t\t\t\t\tnamespace: \"my-ns\",\n\t\t\t\t\tresType:   k8s.Pod,\n\t\t\t\t\tname:      \"my-pod\",\n\t\t\t\t\tresources: resources{\n\t\t\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: my-pod\n  namespace: my-ns\nspec:\n  containers:\n  - name: my-pod\nstatus:\n  phase: Failed`,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tfor _, exp := range expectations {\n\t\t\t\tapi, _, _, err := newMockAPI(true, exp.resources)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"newMockAPI error: %s\", err)\n\t\t\t\t}\n\n\t\t\t\tpods, err := api.GetObjects(exp.namespace, exp.resType, exp.name, labels.Everything())\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"api.GetObjects() unexpected error %s\", err)\n\t\t\t\t}\n\n\t\t\t\tif len(pods) != 0 {\n\t\t\t\t\tt.Errorf(\"Expected no terminating or failed pods to be returned but got %d pods\", len(pods))\n\t\t\t\t}\n\t\t\t}\n\n\t\t})\n\t})\n}\n\nfunc TestGetPodsFor(t *testing.T) {\n\n\ttype getPodsForExpected struct {\n\t\tresources\n\n\t\terr         error\n\t\tk8sResInput string // object used as input to GetPodFor()\n\t}\n\n\tt.Run(\"Returns expected pods based on input\", func(t *testing.T) {\n\t\texpectations := []getPodsForExpected{\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tk8sResInput: `\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: emoji\n  namespace: emojivoto\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc`,\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{},\n\t\t\t\t\tmisc: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed-finished\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n  ownerReferences:\n  - apiVersion: apps/v1\nstatus:\n  phase: Finished`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// Retrieve pods associated to a ClusterIP service\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tk8sResInput: `\napiVersion: v1\nkind: Service\nmetadata:\n  name: emoji-svc\n  namespace: emojivoto\n  uid: serviceUIDDoesNotMatter\nspec:\n  type: ClusterIP\n  selector:\n    app: emoji-svc`,\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed-finished\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n  ownerReferences:\n  - apiVersion: apps/v1\nstatus:\n  phase: Running`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// ExternalName services shouldn't return any pods\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tk8sResInput: `\napiVersion: v1\nkind: Service\nmetadata:\n  name: emoji-svc\n  namespace: emojivoto\nspec:\n  type: ExternalName\n  externalName: someapi.example.com`,\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{},\n\t\t\t\t\tmisc: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed-finished\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\nstatus:\n  phase: Running`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// Cronjob\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tk8sResInput: `\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  uid: cronjob`,\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n  ownerReferences:\n  - apiVersion: batch/v1\n    uid: job\nstatus:\n  phase: Running`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{`\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  uid: job\n  ownerReferences:\n  - apiVersion: batch/v1\n    uid: cronjob\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// Daemonset\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tk8sResInput: `\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  uid: daemonset\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc`,\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: daemonset\nstatus:\n  phase: Running`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// replicaset\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tk8sResInput: `\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  uid: replicaset\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc`,\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: replicaset\nstatus:\n  phase: Running`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed-finished\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: replicaset\nstatus:\n  phase: Finished`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// single pod\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tk8sResInput: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: singlePod\nstatus:\n  phase: Running`,\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: singlePod\nstatus:\n  phase: Running`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed_2\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\nstatus:\n  phase: Running`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// deployment\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tk8sResInput: `\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    deployment.kubernetes.io/revision: \"2\"\n  name: emojivoto-meshed\n  namespace: emojivoto\n  uid: deployment\n  labels:\n    app: emoji-svc\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc`,\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: deploymentRS\n  labels:\n    app: emoji-svc\n    pod-template-hash: deploymentPod\nstatus:\n  phase: Running`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{`\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  uid: deploymentRS\n  annotations:\n    deployment.kubernetes.io/revision: \"2\"\n  name: emojivoto-meshed_2\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    pod-template-hash: deploymentPod\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: deployment\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc\n      pod-template-hash: deploymentPod`,\n\t\t\t\t\t\t`apiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  uid: deploymentRSOld\n  annotations:\n    deployment.kubernetes.io/revision: \"1\"\n  name: emojivoto-meshed_1\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    pod-template-hash: deploymentPodOld\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: deployment\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc\n      pod-template-hash: deploymentPodOld`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// deployment without RS\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tk8sResInput: `\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    deployment.kubernetes.io/revision: \"2\"\n  name: emojivoto-meshed\n  namespace: emojivoto\n  uid: deploymentWithoutRS\n  labels:\n    app: emoji-svc\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc`,\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{},\n\t\t\t\t\tmisc: []string{`\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  uid: AnotherRS\n  annotations:\n    deployment.kubernetes.io/revision: \"2\"\n  name: emojivoto-meshed_2\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    pod-template-hash: doesntMatter\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: doesntMatch\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc\n      pod-template-hash: doesntMatter`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// Deployment with 2 replicasets\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tk8sResInput: `\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    deployment.kubernetes.io/revision: \"2\"\n  name: emojivoto-meshed\n  namespace: emojivoto\n  uid: deployment2RS\n  labels:\n    app: emoji-svc\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc`,\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed-pod1\n  namespace: emojivoto\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: RS1\n  labels:\n    app: emoji-svc\n    pod-template-hash: pod1\nstatus:\n  phase: Running`,\n\t\t\t\t\t\t`apiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed-pod2\n  namespace: emojivoto\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: RS2\n  labels:\n    app: emoji-svc\n    pod-template-hash: pod2\nstatus:\n  phase: Running`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{`\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  uid: RS1\n  annotations:\n    deployment.kubernetes.io/revision: \"2\"\n  name: emojivoto-meshed_2\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    pod-template-hash: pod1\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: deployment2RS\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc\n      pod-template-hash: pod1`,\n\t\t\t\t\t\t`apiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  uid: RS2\n  annotations:\n    deployment.kubernetes.io/revision: \"1\"\n  name: emojivoto-meshed_1\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    pod-template-hash: pod2\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: deployment2RS\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc\n      pod-template-hash: pod2`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// Deployment 2 Pods just one valid\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tk8sResInput: `\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    deployment.kubernetes.io/revision: \"2\"\n  name: emojivoto-meshed\n  namespace: emojivoto\n  uid: deployment2Pods\n  labels:\n    app: emoji-svc\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc`,\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`apiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed-with-RS\n  namespace: emojivoto\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: validRS\n  labels:\n    app: emoji-svc\n    pod-template-hash: podWithRS\nstatus:\n  phase: Running`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{`\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  uid: validRS\n  annotations:\n    deployment.kubernetes.io/revision: \"2\"\n  name: emojivoto-meshed_2\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    pod-template-hash: podWithRS\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: deployment2Pods\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc\n      pod-template-hash: podWithRS`,\n\t\t\t\t\t\t`apiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed-without-RS\n  namespace: emojivoto\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: notHere\n  labels:\n    app: emoji-svc\n    pod-template-hash: invalidPod\nstatus:\n  phase: Running`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tfor _, exp := range expectations {\n\t\t\tk8sInputObj, err := k8s.ToRuntimeObject(exp.k8sResInput)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not decode yml: %s\", err)\n\t\t\t}\n\n\t\t\tapi, _, k8sResults, err := newMockAPI(true, exp.resources)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"newMockAPI error: %s\", err)\n\t\t\t}\n\n\t\t\tk8sResultPods := []*corev1.Pod{}\n\t\t\tfor _, obj := range k8sResults {\n\t\t\t\tk8sResultPods = append(k8sResultPods, obj.(*corev1.Pod))\n\t\t\t}\n\n\t\t\tpods, err := api.GetPodsFor(k8sInputObj, false)\n\t\t\tif !errors.Is(err, exp.err) {\n\t\t\t\tt.Fatalf(\"api.GetPodsFor() unexpected error, expected [%s] got: [%s]\", exp.err, err)\n\t\t\t}\n\n\t\t\tif len(pods) != len(k8sResultPods) {\n\t\t\t\tt.Fatalf(\"Expected: %+v, Got: %+v\", k8sResultPods, pods)\n\t\t\t}\n\n\t\t\tfor _, pod := range pods {\n\t\t\t\tfound := false\n\t\t\t\tfor _, resultPod := range k8sResultPods {\n\t\t\t\t\tif reflect.DeepEqual(pod, resultPod) {\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\tif !found {\n\t\t\t\t\tt.Fatalf(\"Expected: %+v, Got: %+v\", k8sResultPods, pods)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n\n// TestGetOwnerKindAndName tests GetOwnerKindAndName for both api and\n// metadataAPI. Both return strings, so unlike TestGetObjects above, there's no\n// need to create []*metav1.PartialObjectMetadata fixtures\nfunc TestGetOwnerKindAndName(t *testing.T) {\n\tfor i, tt := range []struct {\n\t\tresources\n\n\t\texpectedOwnerKind string\n\t\texpectedOwnerName string\n\t}{\n\t\t{\n\t\t\texpectedOwnerKind: \"deployment\",\n\t\t\texpectedOwnerName: \"t2\",\n\t\t\tresources: resources{\n\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: t2-5f79f964bc-d5jvf\n  namespace: default\n  ownerReferences:\n  - apiVersion: apps/v1\n    kind: ReplicaSet\n    name: t2-5f79f964bc`,\n\t\t\t\t},\n\t\t\t\tmisc: []string{`\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  name: t2-5f79f964bc\n  namespace: default\n  ownerReferences:\n  - apiVersion: apps/v1\n    kind: Deployment\n    name: t2`,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\texpectedOwnerKind: \"replicaset\",\n\t\t\texpectedOwnerName: \"t1-b4f55d87f\",\n\t\t\tresources: resources{\n\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: t1-b4f55d87f-98dbz\n  namespace: default\n  ownerReferences:\n  - apiVersion: apps/v1\n    kind: ReplicaSet\n    name: t1-b4f55d87f`,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\texpectedOwnerKind: \"job\",\n\t\t\texpectedOwnerName: \"slow-cooker\",\n\t\t\tresources: resources{\n\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: slow-cooker-bxtnq\n  namespace: default\n  ownerReferences:\n  - apiVersion: batch/v1\n    kind: Job\n    name: slow-cooker`,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\texpectedOwnerKind: \"replicationcontroller\",\n\t\t\texpectedOwnerName: \"web\",\n\t\t\tresources: resources{\n\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: web-dcfq4\n  namespace: default\n  ownerReferences:\n  - apiVersion: v1\n    kind: ReplicationController\n    name: web`,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\texpectedOwnerKind: \"pod\",\n\t\t\texpectedOwnerName: \"vote-bot\",\n\t\t\tresources: resources{\n\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: vote-bot\n  namespace: default`,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\texpectedOwnerKind: \"cronjob\",\n\t\t\texpectedOwnerName: \"my-cronjob\",\n\t\t\tresources: resources{\n\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: my-pod\n  namespace: my-ns\n  ownerReferences:\n  - apiVersion: batch/v1\n    kind: Job\n    name: my-job`,\n\t\t\t\t},\n\t\t\t\tmisc: []string{`\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: my-job\n  namespace: my-ns\n  ownerReferences:\n  - apiVersion: batch/v1\n    kind: CronJob\n    name: my-cronjob`,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\texpectedOwnerKind: \"replicaset\",\n\t\t\texpectedOwnerName: \"invalid-rs-parent-2abdffa\",\n\t\t\tresources: resources{\n\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: invalid-rs-parent-dcfq4\n  namespace: default\n  ownerReferences:\n  - apiVersion: v1\n    kind: ReplicaSet\n    name: invalid-rs-parent-2abdffa`,\n\t\t\t\t},\n\t\t\t\tmisc: []string{`\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  name: invalid-rs-parent-2abdffa\n  namespace: default\n  ownerReferences:\n  - apiVersion: invalidParent/v1\n    kind: InvalidParentKind\n    name: invalid-parent`,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t} {\n\t\ttt := tt // pin\n\t\tfor _, retry := range []bool{\n\t\t\tfalse,\n\t\t\ttrue,\n\t\t} {\n\t\t\tretry := retry // pin\n\t\t\tt.Run(fmt.Sprintf(\"%d/retry:%t\", i, retry), func(t *testing.T) {\n\t\t\t\tapi, metadataAPI, objs, err := newMockAPI(!retry, tt.resources)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"newMockAPI error: %s\", err)\n\t\t\t\t}\n\n\t\t\t\tpod := objs[0].(*corev1.Pod)\n\t\t\t\townerKind, ownerName := api.GetOwnerKindAndName(context.Background(), pod, retry)\n\n\t\t\t\tif ownerKind != tt.expectedOwnerKind {\n\t\t\t\t\tt.Fatalf(\"Expected kind to be [%s], got [%s]\", tt.expectedOwnerKind, ownerKind)\n\t\t\t\t}\n\n\t\t\t\tif ownerName != tt.expectedOwnerName {\n\t\t\t\t\tt.Fatalf(\"Expected name to be [%s], got [%s]\", tt.expectedOwnerName, ownerName)\n\t\t\t\t}\n\n\t\t\t\townerKind, ownerName, err = metadataAPI.GetOwnerKindAndName(context.Background(), pod, retry)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t\t}\n\n\t\t\t\tif ownerKind != tt.expectedOwnerKind {\n\t\t\t\t\tt.Fatalf(\"Expected kind to be [%s], got [%s]\", tt.expectedOwnerKind, ownerKind)\n\t\t\t\t}\n\n\t\t\t\tif ownerName != tt.expectedOwnerName {\n\t\t\t\t\tt.Fatalf(\"Expected name to be [%s], got [%s]\", tt.expectedOwnerName, ownerName)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc TestGetServiceProfileFor(t *testing.T) {\n\tfor _, tt := range []struct {\n\t\tresources\n\n\t\texpectedRouteNames []string\n\t}{\n\t\t// No service profiles -> default service profile\n\t\t{\n\t\t\texpectedRouteNames: []string{},\n\t\t\tresources:          resources{},\n\t\t},\n\t\t// Service profile in unrelated namespace -> default service profile\n\t\t{\n\t\t\texpectedRouteNames: []string{},\n\t\t\tresources: resources{\n\t\t\t\tresults: []string{`\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: books.server.svc.cluster.local\n  namespace: linkerd\nspec:\n  routes:\n  - condition:\n      pathRegex: /server\n    name: server`,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t// Uses service profile in server namespace\n\t\t{\n\t\t\texpectedRouteNames: []string{\"server\"},\n\t\t\tresources: resources{\n\t\t\t\tresults: []string{`\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: books.server.svc.cluster.local\n  namespace: server\nspec:\n  routes:\n  - condition:\n      pathRegex: /server\n    name: server`,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t// Uses service profile in client namespace\n\t\t{\n\t\t\texpectedRouteNames: []string{\"client\"},\n\t\t\tresources: resources{\n\t\t\t\tresults: []string{`\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: books.server.svc.cluster.local\n  namespace: client\nspec:\n  routes:\n  - condition:\n      pathRegex: /client\n    name: client`,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t// Service profile in client namespace takes priority\n\t\t{\n\t\t\texpectedRouteNames: []string{\"client\"},\n\t\t\tresources: resources{\n\t\t\t\tresults: []string{`\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: books.server.svc.cluster.local\n  namespace: server\nspec:\n  routes:\n  - condition:\n      pathRegex: /server\n    name: server`,\n\t\t\t\t\t`\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: books.server.svc.cluster.local\n  namespace: client\nspec:\n  routes:\n  - condition:\n      pathRegex: /client\n    name: client`,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t} {\n\t\tapi, _, _, err := newMockAPI(true, tt.resources)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"newMockAPI error: %s\", err)\n\t\t}\n\n\t\tsvc := corev1.Service{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"books\",\n\t\t\t\tNamespace: \"server\",\n\t\t\t},\n\t\t}\n\n\t\tsp := api.GetServiceProfileFor(&svc, \"client\", \"cluster.local\")\n\n\t\tif len(sp.Spec.Routes) != len(tt.expectedRouteNames) {\n\t\t\tt.Fatalf(\"Expected %d routes, got %d\", len(tt.expectedRouteNames), len(sp.Spec.Routes))\n\t\t}\n\n\t\tfor i, route := range sp.Spec.Routes {\n\t\t\tif tt.expectedRouteNames[i] != route.Name {\n\t\t\t\tt.Fatalf(\"Expected route [%s], got [%s]\", tt.expectedRouteNames[i], route.Name)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestGetServicesFor(t *testing.T) {\n\n\ttype getServicesForExpected struct {\n\t\tresources\n\n\t\terr         error\n\t\tk8sResInput string // object used as input to GetServicesFor()\n\t}\n\n\tt.Run(\"GetServicesFor\", func(t *testing.T) {\n\t\texpectations := []getServicesForExpected{\n\t\t\t// If a service contains a pod, GetPodsFor should return the service.\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tk8sResInput: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: my-pod\n  namespace: emojivoto\n  labels:\n    app: my-pod\nstatus:\n  phase: Running`,\n\t\t\t\tresources: resources{\n\t\t\t\t\tresults: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: my-svc\n  namespace: emojivoto\nspec:\n  type: ClusterIP\n  selector:\n    app: my-pod`,\n\t\t\t\t\t},\n\t\t\t\t\tmisc: []string{},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tfor _, exp := range expectations {\n\t\t\tk8sInputObj, err := k8s.ToRuntimeObject(exp.k8sResInput)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not decode yml: %s\", err)\n\t\t\t}\n\n\t\t\texp.misc = append(exp.misc, exp.k8sResInput)\n\t\t\tapi, _, k8sResults, err := newMockAPI(true, exp.resources)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"newMockAPI error: %s\", err)\n\t\t\t}\n\n\t\t\tk8sResultServices := []*corev1.Service{}\n\t\t\tfor _, obj := range k8sResults {\n\t\t\t\tk8sResultServices = append(k8sResultServices, obj.(*corev1.Service))\n\t\t\t}\n\n\t\t\tservices, err := api.GetServicesFor(k8sInputObj, false)\n\t\t\tif !errors.Is(err, exp.err) {\n\t\t\t\tt.Fatalf(\"api.GetServicesFor() unexpected error, expected [%s] got: [%s]\", exp.err, err)\n\t\t\t}\n\n\t\t\tif len(services) != len(k8sResultServices) {\n\t\t\t\tt.Fatalf(\"Expected: %+v, Got: %+v\", k8sResultServices, services)\n\t\t\t}\n\n\t\t\tfor _, service := range services {\n\t\t\t\tfound := false\n\t\t\t\tfor _, resultService := range k8sResultServices {\n\t\t\t\t\tif reflect.DeepEqual(service, resultService) {\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\tif !found {\n\t\t\t\t\tt.Fatalf(\"Expected: %+v, Got: %+v\", k8sResultServices, services)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t})\n}\n\nfunc unexpectedErrors(err, expErr error) bool {\n\treturn (err == nil && expErr != nil) ||\n\t\t(err != nil && expErr == nil) ||\n\t\t!strings.Contains(err.Error(), expErr.Error())\n}\n"
  },
  {
    "path": "controller/k8s/clientset.go",
    "content": "package k8s\n\nimport (\n\t\"fmt\"\n\n\tl5dcrdclient \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\t\"github.com/linkerd/linkerd2/pkg/prometheus\"\n\t\"k8s.io/client-go/rest\"\n\n\t// Load all the auth plugins for the cloud providers.\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n)\n\nfunc wrapTransport(config *rest.Config, telemetryName string) (*rest.Config, error) {\n\twt := config.WrapTransport\n\twrapped, err := prometheus.ClientWithTelemetry(telemetryName, wt)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to wrap transport: %w\", err)\n\t}\n\tconfig.WrapTransport = wrapped\n\treturn config, nil\n}\n\n// NewL5DCRDClient returns a Linkerd controller client for the given\n// configuration.\nfunc NewL5DCRDClient(kubeConfig *rest.Config) (*l5dcrdclient.Clientset, error) {\n\tconfig, err := wrapTransport(kubeConfig, \"l5dCrd\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn l5dcrdclient.NewForConfig(config)\n}\n"
  },
  {
    "path": "controller/k8s/k8s.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tlog \"github.com/sirupsen/logrus\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\nconst ResyncTime = 10 * time.Minute\n\nfunc waitForCacheSync(syncChecks []cache.InformerSynced) {\n\tctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)\n\tdefer cancel()\n\n\tlog.Infof(\"waiting for caches to sync\")\n\tif !cache.WaitForCacheSync(ctx.Done(), syncChecks...) {\n\t\t//nolint:gocritic\n\t\tlog.Fatal(\"failed to sync caches\")\n\t}\n\tlog.Infof(\"caches synced\")\n}\n\nfunc isValidRSParent(rs metav1.Object) bool {\n\tif len(rs.GetOwnerReferences()) != 1 {\n\t\treturn false\n\t}\n\n\tvalidParentKinds := []string{\n\t\tk8s.Job,\n\t\tk8s.StatefulSet,\n\t\tk8s.DaemonSet,\n\t\tk8s.Deployment,\n\t}\n\n\trsOwner := rs.GetOwnerReferences()[0]\n\trsOwnerKind := strings.ToLower(rsOwner.Kind)\n\tfor _, kind := range validParentKinds {\n\t\tif rsOwnerKind == kind {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "controller/k8s/metadata_api.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\tlog \"github.com/sirupsen/logrus\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"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/client-go/informers\"\n\t\"k8s.io/client-go/metadata\"\n\t\"k8s.io/client-go/metadata/metadatainformer\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\n// MetadataAPI provides shared metadata informers for some Kubernetes resources\ntype MetadataAPI struct {\n\tpromGauges\n\n\tclient          metadata.Interface\n\tinf             map[APIResource]informers.GenericInformer\n\tsyncChecks      []cache.InformerSynced\n\tsharedInformers metadatainformer.SharedInformerFactory\n}\n\n// InitializeMetadataAPI returns an instance of MetadataAPI with metadata\n// informers for the provided resources\nfunc InitializeMetadataAPI(kubeConfig string, cluster string, resources ...APIResource) (*MetadataAPI, error) {\n\tconfig, err := k8s.GetConfig(kubeConfig, \"\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error configuring Kubernetes API client: %w\", err)\n\t}\n\treturn InitializeMetadataAPIForConfig(config, cluster, resources...)\n}\n\nfunc InitializeMetadataAPIForConfig(kubeConfig *rest.Config, cluster string, resources ...APIResource) (*MetadataAPI, error) {\n\tclient, err := metadata.NewForConfig(kubeConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tapi, err := newClusterScopedMetadataAPI(client, cluster, resources...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, gauge := range api.gauges {\n\t\tif err := prometheus.Register(gauge); err != nil {\n\t\t\tlog.Warnf(\"failed to register Prometheus gauge %s: %s\", gauge.Desc().String(), err)\n\t\t}\n\t}\n\treturn api, nil\n\n}\n\nfunc newClusterScopedMetadataAPI(\n\tmetadataClient metadata.Interface,\n\tcluster string,\n\tresources ...APIResource,\n) (*MetadataAPI, error) {\n\tsharedInformers := metadatainformer.NewFilteredSharedInformerFactory(\n\t\tmetadataClient,\n\t\tResyncTime,\n\t\tmetav1.NamespaceAll,\n\t\tnil,\n\t)\n\n\tapi := &MetadataAPI{\n\t\tclient:          metadataClient,\n\t\tinf:             make(map[APIResource]informers.GenericInformer),\n\t\tsyncChecks:      make([]cache.InformerSynced, 0),\n\t\tsharedInformers: sharedInformers,\n\t}\n\n\tinformerLabels := prometheus.Labels{\n\t\t\"cluster\": cluster,\n\t}\n\n\tfor _, resource := range resources {\n\t\tif err := api.addInformer(resource, informerLabels); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn api, nil\n}\n\n// Sync waits for all informers to be synced.\nfunc (api *MetadataAPI) Sync(stopCh <-chan struct{}) {\n\tapi.sharedInformers.Start(stopCh)\n\n\twaitForCacheSync(api.syncChecks)\n}\n\n// UnregisterGauges unregisters all the prometheus cache gauges associated to this API\nfunc (api *MetadataAPI) UnregisterGauges() {\n\tapi.promGauges.unregister()\n}\n\nfunc (api *MetadataAPI) getLister(res APIResource) (cache.GenericLister, error) {\n\tinf, ok := api.inf[res]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"metadata informer (%v) not configured\", res)\n\t}\n\n\treturn inf.Lister(), nil\n}\n\n// Get returns the metadata for the supplied object type and name. This uses a\n// shared informer and the results may be out of date if the informer is\n// lagging behind.\nfunc (api *MetadataAPI) Get(res APIResource, name string) (*metav1.PartialObjectMetadata, error) {\n\tls, err := api.getLister(res)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobj, err := ls.Get(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// ls' concrete type is metadatalister.metadataListerShim, whose\n\t// Get method always returns *metav1.PartialObjectMetadata\n\tnsMeta, ok := obj.(*metav1.PartialObjectMetadata)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"couldn't convert obj %v to PartialObjectMetadata\", obj)\n\t}\n\n\treturn nsMeta, nil\n}\n\nfunc (api *MetadataAPI) getByNamespace(res APIResource, ns, name string) (*metav1.PartialObjectMetadata, error) {\n\tls, err := api.getLister(res)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobj, err := ls.ByNamespace(ns).Get(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnsMeta, ok := obj.(*metav1.PartialObjectMetadata)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"couldn't convert obj %v to PartialObjectMetadata\", obj)\n\t}\n\n\treturn nsMeta, nil\n}\n\n// GetByNamespaceFiltered returns a list of Kubernetes object references, given\n// a type, namespace, name and label selector. This uses a shared informer and\n// the results may be out of date if the informer is lagging behind.\nfunc (api *MetadataAPI) GetByNamespaceFiltered(\n\trestype APIResource,\n\tns string,\n\tname string,\n\tlabelSelector labels.Selector,\n) ([]*metav1.PartialObjectMetadata, error) {\n\tls, err := api.getLister(restype)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar objs []runtime.Object\n\tif ns == \"\" {\n\t\tobjs, err = ls.List(labelSelector)\n\t} else if name == \"\" {\n\t\tobjs, err = ls.ByNamespace(ns).List(labelSelector)\n\t} else {\n\t\tvar obj runtime.Object\n\t\tobj, err = ls.ByNamespace(ns).Get(name)\n\t\tobjs = []runtime.Object{obj}\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobjMetas := []*metav1.PartialObjectMetadata{}\n\tfor _, obj := range objs {\n\t\t// ls' concrete type is metadatalister.metadataListerShim, which\n\t\t// guarantees this cast won't fail\n\t\tobjMeta, ok := obj.(*metav1.PartialObjectMetadata)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"couldn't convert obj %v to PartialObjectMetadata\", obj)\n\t\t}\n\t\tgvk, err := restype.GVK()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// objMeta's TypeMeta fields aren't getting populated, so we do it\n\t\t// manually here\n\t\tobjMeta.SetGroupVersionKind(gvk)\n\t\tobjMetas = append(objMetas, objMeta)\n\t}\n\n\treturn objMetas, nil\n}\n\n// GetOwnerKindAndName returns the pod owner's kind and name, using owner\n// references from the Kubernetes API. The kind is represented as the\n// Kubernetes singular resource type (e.g. deployment, daemonset, job, etc.).\n// If retry is true, when the shared informer cache doesn't return anything we\n// try again with a direct Kubernetes API call.\nfunc (api *MetadataAPI) GetOwnerKindAndName(ctx context.Context, pod *corev1.Pod, retry bool) (string, string, error) {\n\townerRefs := pod.GetOwnerReferences()\n\tif len(ownerRefs) == 0 {\n\t\t// pod without a parent\n\t\treturn \"pod\", pod.Name, nil\n\t} else if len(ownerRefs) > 1 {\n\t\tlog.Debugf(\"unexpected owner reference count (%d): %+v\", len(ownerRefs), ownerRefs)\n\t\treturn \"pod\", pod.Name, nil\n\t}\n\n\tparent := ownerRefs[0]\n\tvar parentObj metav1.Object\n\tvar err error\n\tswitch parent.Kind {\n\tcase \"Job\":\n\t\tparentObj, err = api.getByNamespace(Job, pod.Namespace, parent.Name)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"failed to retrieve job from indexer %s/%s: %s\", pod.Namespace, parent.Name, err)\n\t\t\tif retry {\n\t\t\t\tparentObj, err = api.client.\n\t\t\t\t\tResource(batchv1.SchemeGroupVersion.WithResource(\"jobs\")).\n\t\t\t\t\tNamespace(pod.Namespace).\n\t\t\t\t\tGet(ctx, parent.Name, metav1.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Warnf(\"failed to retrieve job from direct API call %s/%s: %s\", pod.Namespace, parent.Name, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tcase \"ReplicaSet\":\n\t\tvar rsObj *metav1.PartialObjectMetadata\n\t\trsObj, err = api.getByNamespace(RS, pod.Namespace, parent.Name)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"failed to retrieve replicaset from indexer %s/%s: %s\", pod.Namespace, parent.Name, err)\n\t\t\tif retry {\n\t\t\t\trsObj, err = api.client.\n\t\t\t\t\tResource(appsv1.SchemeGroupVersion.WithResource(\"replicasets\")).\n\t\t\t\t\tNamespace(pod.Namespace).\n\t\t\t\t\tGet(ctx, parent.Name, metav1.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Warnf(\"failed to retrieve replicaset from direct API call %s/%s: %s\", pod.Namespace, parent.Name, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif rsObj == nil || !isValidRSParent(rsObj) {\n\t\t\treturn strings.ToLower(parent.Kind), parent.Name, nil\n\t\t}\n\t\tparentObj = rsObj\n\tdefault:\n\t\treturn strings.ToLower(parent.Kind), parent.Name, nil\n\t}\n\n\tif err == nil && len(parentObj.GetOwnerReferences()) == 1 {\n\t\tgrandParent := parentObj.GetOwnerReferences()[0]\n\t\treturn strings.ToLower(grandParent.Kind), grandParent.Name, nil\n\t}\n\treturn strings.ToLower(parent.Kind), parent.Name, nil\n}\n\nfunc (api *MetadataAPI) addInformer(res APIResource, informerLabels prometheus.Labels) error {\n\tgvk, err := res.GVK()\n\tif err != nil {\n\t\treturn err\n\t}\n\tgvr, _ := meta.UnsafeGuessKindToResource(gvk)\n\tinf := api.sharedInformers.ForResource(gvr)\n\tapi.syncChecks = append(api.syncChecks, inf.Informer().HasSynced)\n\tapi.promGauges.addInformerSize(strings.ToLower(gvk.Kind), informerLabels, inf.Informer())\n\tapi.inf[res] = inf\n\n\treturn nil\n}\n"
  },
  {
    "path": "controller/k8s/prometheus.go",
    "content": "package k8s\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\ntype promGauges struct {\n\tgauges []prometheus.GaugeFunc\n}\n\nfunc (p *promGauges) addInformerSize(kind string, labels prometheus.Labels, inf cache.SharedIndexInformer) {\n\tp.gauges = append(p.gauges, prometheus.NewGaugeFunc(prometheus.GaugeOpts{\n\t\tName:        fmt.Sprintf(\"%s_cache_size\", kind),\n\t\tHelp:        fmt.Sprintf(\"Number of items in the client-go %s cache\", kind),\n\t\tConstLabels: labels,\n\t}, func() float64 {\n\t\treturn float64(len(inf.GetStore().ListKeys()))\n\t}))\n}\n\nfunc (p *promGauges) unregister() {\n\tfor _, gauge := range p.gauges {\n\t\tprometheus.Unregister(gauge)\n\t}\n}\n"
  },
  {
    "path": "controller/k8s/test_helper.go",
    "content": "package k8s\n\nimport (\n\tl5dcrdclient \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/dynamic\"\n\t\"k8s.io/client-go/kubernetes\"\n\tclientsetscheme \"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/metadata/fake\"\n\t\"k8s.io/client-go/testing\"\n)\n\n// NewFakeAPI provides a mock Kubernetes API for testing.\nfunc NewFakeAPI(configs ...string) (*API, error) {\n\tclientSet, _, _, spClientSet, dynamicClient, err := k8s.NewFakeClientSets(configs...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn NewFakeClusterScopedAPI(clientSet, spClientSet, dynamicClient), nil\n}\n\n// NewFakeAPI provides a mock Kubernetes API for testing.\nfunc NewFakeAPIWithActions(configs ...string) (*API, func() []testing.Action, error) {\n\tclientSet, _, _, spClientSet, dynamicClient, err := k8s.NewFakeClientSets(configs...)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn NewFakeClusterScopedAPI(clientSet, spClientSet, dynamicClient), clientSet.Actions, nil\n}\n\n// NewFakeAPIWithL5dClient provides a mock Kubernetes API for testing like\n// NewFakeAPI, but it also returns the mock client for linkerd CRDs\nfunc NewFakeAPIWithL5dClient(configs ...string) (*API, error) {\n\tclientSet, _, _, l5dClientSet, dynamicClient, err := k8s.NewFakeClientSets(configs...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn NewFakeClusterScopedAPI(clientSet, l5dClientSet, dynamicClient), nil\n}\n\n// NewFakeClusterScopedAPI provides a mock Kubernetes API for testing.\nfunc NewFakeClusterScopedAPI(clientSet kubernetes.Interface, l5dClientSet l5dcrdclient.Interface, dynamicClient dynamic.Interface) *API {\n\treturn NewClusterScopedAPI(\n\t\tclientSet,\n\t\tdynamicClient,\n\t\tl5dClientSet,\n\t\t\"fake\",\n\t\tCJ,\n\t\tCM,\n\t\tDeploy,\n\t\tDS,\n\t\tEndpoint,\n\t\tJob,\n\t\tMWC,\n\t\tNS,\n\t\tPod,\n\t\tExtWorkload,\n\t\tRC,\n\t\tRS,\n\t\tSP,\n\t\tSS,\n\t\tSvc,\n\t\tNode,\n\t\tES,\n\t\tSrv,\n\t\tSecret,\n\t\tExtWorkload,\n\t)\n}\n\n// NewFakeMetadataAPI provides a mock Kubernetes API for testing.\nfunc NewFakeMetadataAPI(configs []string) (*MetadataAPI, error) {\n\tsch := runtime.NewScheme()\n\tmetav1.AddMetaToScheme(sch)\n\n\tvar objs []runtime.Object\n\tfor _, config := range configs {\n\t\tobj, err := k8s.ToRuntimeObject(config)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tobjMeta, err := toPartialObjectMetadata(obj)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tobjs = append(objs, objMeta)\n\t}\n\n\tmetadataClient := fake.NewSimpleMetadataClient(sch, objs...)\n\n\treturn newClusterScopedMetadataAPI(\n\t\tmetadataClient,\n\t\t\"fake\",\n\t\tCJ,\n\t\tCM,\n\t\tDeploy,\n\t\tDS,\n\t\tEndpoint,\n\t\tJob,\n\t\tMWC,\n\t\tNS,\n\t\tPod,\n\t\tRC,\n\t\tRS,\n\t\tSP,\n\t\tSS,\n\t\tSvc,\n\t\tNode,\n\t\tES,\n\t\tSvc,\n\t)\n}\n\nfunc toPartialObjectMetadata(obj runtime.Object) (*metav1.PartialObjectMetadata, error) {\n\tu := &unstructured.Unstructured{}\n\terr := clientsetscheme.Scheme.Convert(obj, u, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &metav1.PartialObjectMetadata{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tAPIVersion: u.GetAPIVersion(),\n\t\t\tKind:       u.GetKind(),\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace:       u.GetNamespace(),\n\t\t\tName:            u.GetName(),\n\t\t\tAnnotations:     u.GetAnnotations(),\n\t\t\tLabels:          u.GetLabels(),\n\t\t\tOwnerReferences: u.GetOwnerReferences(),\n\t\t},\n\t}, nil\n}\n"
  },
  {
    "path": "controller/proxy-injector/fake/client.go",
    "content": "package fake\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\n// NewClient returns a fake Kubernetes clientset.\nfunc NewClient(kubeconfig string, objs ...runtime.Object) kubernetes.Interface {\n\treturn fake.NewSimpleClientset(objs...)\n}\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/annotation.patch.json",
    "content": "[\n    {\n        \"op\": \"add\",\n        \"path\": \"/metadata/annotations\",\n        \"value\": {}\n    },\n    {\n        \"op\": \"add\",\n        \"path\": \"/metadata/annotations/config.linkerd.io~1opaque-ports\",\n        \"value\": \"3306\"\n    }\n]\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/deployment-inject-disabled.yaml",
    "content": "kind: Deployment\napiVersion: apps/v1\nmetadata:\n  name: nginx\n  namespace: kube-public\n  labels:\n    app: nginx\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n      annotations:\n        linkerd.io/inject: disabled\n        created-by: isim\n    spec:\n      containers:\n      - name: nginx\n        image: nginx\n        ports:\n        - name: http\n          containerPort: 80\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/deployment-with-injected-proxy.yaml",
    "content": "kind: Deployment\napiVersion: apps/v1\nmetadata:\n  name: nginx\n  namespace: kube-public\n  labels:\n    app: nginx\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n      annotations:\n        created-by: isim\n    spec:\n      initContainers:\n      - name: linkerd-init\n        image: cr.l5d.io/linkerd/proxy-init\n      containers:\n      - name: nginx\n        image: nginx\n        ports:\n        - name: http\n          containerPort: 80\n      - name: linkerd-proxy\n        image: gc.io/linkerd-io/proxy\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/filter-pod-opaque-ports.yaml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  name: foo\n  labels:\n    app: test\nspec:\n  initContainers:\n  - name: memcached\n    image: memcached\n    restartPolicy: Always\n    ports:\n    - containerPort: 11211\n  containers:\n  - name: foo\n    image: mysql\n    ports:\n    - containerPort: 3306\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/filter-service-opaque-ports.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: svc\n  namespace: kube-public\nspec:\n  selector:\n    app: test\n  ports:\n    # 9090 should be set since it targets an opaque port.\n  - name: a\n    port: 9090\n    targetPort: 3306\n    # 5432 should not be set since it does not target an opaque port.\n  - name: b\n    port: 5432\n    targetPort: 9091\n    # 3306 should be set since it does not have a target port and is opaque.\n  - name: c\n    port: 3306\n    # 9093 should not be set since it targets a named port and we don't know\n    # what port number it is exposed on.\n  - name: d\n    port: 9093\n    targetPort: not-an-int\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/filtered-pod-opaque-ports.json",
    "content": "[\n    {\n        \"op\": \"add\",\n        \"path\": \"/metadata/annotations\",\n        \"value\": {}\n    },\n    {\n        \"op\": \"add\",\n        \"path\": \"/metadata/annotations/config.linkerd.io~1opaque-ports\",\n        \"value\": \"11211,3306\"\n    }\n]\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/filtered-service-opaque-ports.json",
    "content": "[\n    {\n        \"op\": \"add\",\n        \"path\": \"/metadata/annotations\",\n        \"value\": {}\n    },\n    {\n        \"op\": \"add\",\n        \"path\": \"/metadata/annotations/config.linkerd.io~1opaque-ports\",\n        \"value\": \"9090,3306\"\n    }\n]\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/inject-init-container-spec.yaml",
    "content": "args:\n- --incoming-proxy-port\n- 4143\n- --outgoing-proxy-port\n- 4140\n- --proxy-uid\n- 2102\n- --proxy-gid\n- 2102\n- --inbound-ports-to-ignore\n- 4190,4191\nimage: cr.l5d.io/linkerd/proxy-init:v18.8.4\nimagePullPolicy: IfNotPresent\nname: linkerd-init\nresources: {}\nsecurityContext:\n  capabilities:\n    add:\n    - NET_ADMIN\n  privileged: false\n  runAsNonRoot: true\nterminationMessagePolicy: FallbackToLogsOnError\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/inject-linkerd-secrets-volume-spec.yaml",
    "content": "name: linkerd-secrets\nsecret:\n  secretName: nginx-deployment-tls-linkerd-io\n  optional: true"
  },
  {
    "path": "controller/proxy-injector/fake/data/namespace-inject-disabled.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: linkerd\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/namespace-inject-enabled.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: kube-public\n  annotations:\n    linkerd.io/inject: enabled\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/namespace-with-opaque-ports.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: kube-public\n  annotations:\n    linkerd.io/inject: enabled\n    config.linkerd.io/opaque-ports: \"3306\"\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod-cpu-ratio.json",
    "content": "[\n    {\n        \"op\": \"add\",\n        \"path\": \"/metadata/annotations/linkerd.io~1proxy-version\",\n        \"value\": \"dev-undefined\"\n    },\n    {\n        \"op\": \"add\",\n        \"path\": \"/metadata/annotations/linkerd.io~1trust-root-sha256\",\n        \"value\": \"5090806bcf2daff5d54739ba02a8e7b919f7e62b2a46757e11089c916ec97fc2\"\n    },\n    {\n        \"op\": \"add\",\n        \"path\": \"/metadata/labels/linkerd.io~1control-plane-ns\",\n        \"value\": \"linkerd\"\n    },\n    {\n        \"op\": \"add\",\n        \"path\": \"/metadata/labels/linkerd.io~1proxy-deployment\",\n        \"value\": \"owner-deployment\"\n    },\n    {\n        \"op\": \"add\",\n        \"path\": \"/metadata/labels/linkerd.io~1workload-ns\",\n        \"value\": \"kube-public\"\n    },\n    {\n        \"op\": \"add\",\n        \"path\": \"/spec/volumes\",\n        \"value\": []\n    },\n    {\n        \"op\": \"add\",\n        \"path\": \"/spec/initContainers\",\n        \"value\": []\n    },\n    {\n        \"op\": \"add\",\n        \"path\": \"/spec/volumes/-\",\n        \"value\": {\n            \"emptyDir\": {},\n            \"name\": \"linkerd-proxy-init-xtables-lock\"\n        }\n    },\n    {\n        \"op\": \"add\",\n        \"path\": \"/spec/initContainers/-\",\n        \"value\": {\n            \"args\": [\n                \"--firewall-bin-path\",\n                \"iptables-nft\",\n                \"--firewall-save-bin-path\",\n                \"iptables-nft-save\",\n                \"--ipv6=false\",\n                \"--incoming-proxy-port\",\n                \"4143\",\n                \"--outgoing-proxy-port\",\n                \"4140\",\n                \"--proxy-uid\",\n                \"2102\",\n                \"--inbound-ports-to-ignore\",\n                \"4190,4191,4567,4568\",\n                \"--outbound-ports-to-ignore\",\n                \"4567,4568\"\n            ],\n            \"image\": \"cr.l5d.io/linkerd/proxy:dev-undefined\",\n            \"imagePullPolicy\": \"IfNotPresent\",\n            \"command\": [\n                \"/usr/lib/linkerd/linkerd2-proxy-init\"\n            ],\n            \"name\": \"linkerd-init\",\n            \"resources\": {\n                \"requests\": {\n                    \"cpu\": \"3\"\n                }\n            },\n            \"securityContext\": {\n                \"allowPrivilegeEscalation\": false,\n                \"capabilities\": {\n                    \"add\": [\n                        \"NET_ADMIN\",\n                        \"NET_RAW\"\n                    ]\n                },\n                \"privileged\": false,\n                \"runAsNonRoot\": true,\n                \"runAsUser\": 65534,\n                \"runAsGroup\": 65534,\n                \"readOnlyRootFilesystem\": true,\n                \"seccompProfile\": {\n                    \"type\": \"RuntimeDefault\"\n                }\n            },\n            \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n            \"volumeMounts\": [\n                {\n                    \"mountPath\": \"/run\",\n                    \"name\": \"linkerd-proxy-init-xtables-lock\"\n                }\n            ]\n        }\n    },\n    {\n        \"op\": \"add\",\n        \"path\": \"/spec/volumes/-\",\n        \"value\": {\n            \"name\": \"linkerd-identity-end-entity\",\n            \"emptyDir\": {\n                \"medium\": \"Memory\"\n            }\n        }\n    },\n    {\n        \"op\": \"add\",\n        \"path\": \"/spec/volumes/-\",\n        \"value\": {\n            \"name\": \"linkerd-identity-token\",\n            \"projected\": {\n                \"sources\": [\n                    {\n                        \"serviceAccountToken\": {\n                            \"audience\": \"identity.l5d.io\",\n                            \"expirationSeconds\": 86400,\n                            \"path\": \"linkerd-identity-token\"\n                        }\n                    }\n                ]\n            }\n        }\n    },\n    {\n        \"op\": \"add\",\n        \"path\": \"/spec/containers/0\",\n        \"value\": {\n            \"env\": [\n                {\n                    \"name\": \"_pod_name\",\n                    \"valueFrom\": {\n                        \"fieldRef\": {\n                            \"fieldPath\": \"metadata.name\"\n                        }\n                    }\n                },\n                {\n                    \"name\": \"_pod_ns\",\n                    \"valueFrom\": {\n                        \"fieldRef\": {\n                            \"fieldPath\": \"metadata.namespace\"\n                        }\n                    }\n                },\n                {\n                    \"name\": \"_pod_uid\",\n                    \"valueFrom\": {\n                        \"fieldRef\": {\n                            \"fieldPath\": \"metadata.uid\"\n                        }\n                    }\n                },\n                {\n                    \"name\": \"_pod_ip\",\n                    \"valueFrom\": {\n                        \"fieldRef\": {\n                            \"fieldPath\": \"status.podIP\"\n                        }\n                    }\n                },\n                {\n                    \"name\": \"_pod_nodeName\",\n                    \"valueFrom\": {\n                        \"fieldRef\": {\n                            \"fieldPath\": \"spec.nodeName\"\n                        }\n                    }\n                },\n                {\n                    \"name\": \"_pod_containerName\",\n                    \"value\": \"linkerd-proxy\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_CORES\",\n                    \"value\": \"3\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_CORES_MIN\",\n                    \"value\": \"3\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_CORES_MAX_RATIO\",\n                    \"value\": \"0.1\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\",\n                    \"value\": \"false\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_LOG\",\n                    \"value\": \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_LOG_FORMAT\",\n                    \"value\": \"plain\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_DESTINATION_SVC_ADDR\",\n                    \"value\": \"linkerd-dst-headless.linkerd.svc.cluster.local.:8086\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\",\n                    \"value\": \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_POLICY_SVC_ADDR\",\n                    \"value\": \"linkerd-policy.linkerd.svc.cluster.local.:8090\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_POLICY_WORKLOAD\",\n                    \"value\": \"{\\\"ns\\\":\\\"$(_pod_ns)\\\", \\\"pod\\\":\\\"$(_pod_name)\\\"}\\n\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\",\n                    \"value\": \"all-unauthenticated\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\",\n                    \"value\": \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\",\n                    \"value\": \"3s\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\",\n                    \"value\": \"5m\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\",\n                    \"value\": \"1h\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\",\n                    \"value\": \"100ms\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\",\n                    \"value\": \"1000ms\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\",\n                    \"value\": \"5s\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\",\n                    \"value\": \"90s\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_CONTROL_LISTEN_ADDR\",\n                    \"value\": \"0.0.0.0:4190\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_ADMIN_LISTEN_ADDR\",\n                    \"value\": \"0.0.0.0:4191\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\",\n                    \"value\": \"127.0.0.1:4140\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\",\n                    \"value\": \"127.0.0.1:4140\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_INBOUND_LISTEN_ADDR\",\n                    \"value\": \"0.0.0.0:4143\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_INBOUND_IPS\",\n                    \"valueFrom\": {\n                        \"fieldRef\": {\n                            \"fieldPath\": \"status.podIPs\"\n                        }\n                    }\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_INBOUND_PORTS\",\n                    \"value\": \"80\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\",\n                    \"value\": \"svc.cluster.local.\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\",\n                    \"value\": \"10000ms\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\",\n                    \"value\": \"10000ms\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\",\n                    \"value\": \"30s\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\",\n                    \"value\": \"30s\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\",\n                    \"value\": \"false\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\",\n                    \"value\": \"10s\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\",\n                    \"value\": \"3s\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\",\n                    \"value\": \"10s\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\",\n                    \"value\": \"3s\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\",\n                    \"value\": \"25,587,3306,4444,5432,6379,9300,11211\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_DESTINATION_CONTEXT\",\n                    \"value\": \"{\\\"ns\\\":\\\"$(_pod_ns)\\\", \\\"nodeName\\\":\\\"$(_pod_nodeName)\\\", \\\"pod\\\":\\\"$(_pod_name)\\\"}\\n\"\n                },\n                {\n                    \"name\": \"_pod_sa\",\n                    \"valueFrom\": {\n                        \"fieldRef\": {\n                            \"fieldPath\": \"spec.serviceAccountName\"\n                        }\n                    }\n                },\n                {\n                    \"name\": \"_l5d_ns\",\n                    \"value\": \"linkerd\"\n                },\n                {\n                    \"name\": \"_l5d_trustdomain\",\n                    \"value\": \"cluster.local\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_IDENTITY_DIR\",\n                    \"value\": \"/var/run/linkerd/identity/end-entity\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\",\n                    \"value\": \"IdentityTrustAnchorsPEM\\n\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_IDENTITY_TOKEN_FILE\",\n                    \"value\": \"/var/run/secrets/tokens/linkerd-identity-token\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_IDENTITY_SVC_ADDR\",\n                    \"value\": \"linkerd-identity-headless.linkerd.svc.cluster.local.:8080\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_IDENTITY_LOCAL_NAME\",\n                    \"value\": \"$(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_IDENTITY_SVC_NAME\",\n                    \"value\": \"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_DESTINATION_SVC_NAME\",\n                    \"value\": \"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n                },\n                {\n                    \"name\": \"LINKERD2_PROXY_POLICY_SVC_NAME\",\n                    \"value\": \"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n                }\n            ],\n            \"image\": \"cr.l5d.io/linkerd/proxy:dev-undefined\",\n            \"imagePullPolicy\": \"IfNotPresent\",\n            \"lifecycle\": {\n                \"postStart\": {\n                    \"exec\": {\n                        \"command\": [\n                            \"/usr/lib/linkerd/linkerd-await\",\n                            \"--timeout=2m\",\n                            \"--port=4191\"\n                        ]\n                    }\n                }\n            },\n            \"livenessProbe\": {\n                \"httpGet\": {\n                    \"path\": \"/live\",\n                    \"port\": 4191\n                },\n                \"initialDelaySeconds\": 10,\n                \"timeoutSeconds\": 1\n            },\n            \"name\": \"linkerd-proxy\",\n            \"ports\": [\n                {\n                    \"containerPort\": 4143,\n                    \"name\": \"linkerd-proxy\"\n                },\n                {\n                    \"containerPort\": 4191,\n                    \"name\": \"linkerd-admin\"\n                }\n            ],\n            \"readinessProbe\": {\n                \"httpGet\": {\n                    \"path\": \"/ready\",\n                    \"port\": 4191\n                },\n                \"initialDelaySeconds\": 2,\n                \"timeoutSeconds\": 1\n            },\n            \"resources\": {\n                \"requests\": {\n                    \"cpu\": \"3\"\n                }\n            },\n            \"securityContext\": {\n                \"allowPrivilegeEscalation\": false,\n                \"readOnlyRootFilesystem\": true,\n                \"runAsNonRoot\": true,\n                \"runAsUser\": 2102,\n                \"seccompProfile\": {\n                    \"type\": \"RuntimeDefault\"\n                }\n            },\n            \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n            \"volumeMounts\": [\n                {\n                    \"mountPath\": \"/var/run/linkerd/identity/end-entity\",\n                    \"name\": \"linkerd-identity-end-entity\"\n                },\n                {\n                    \"mountPath\": \"/var/run/secrets/tokens\",\n                    \"name\": \"linkerd-identity-token\"\n                }\n            ]\n        }\n    }\n]\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod-inject-empty.yaml",
    "content": "kind: Pod\napiVersion: apps/v1\nmetadata:\n  name: nginx\n  namespace: kube-public\n  annotations:\n    created-by: isim\n  labels:\n    app: nginx\nspec:\n  containers:\n  - name: nginx\n    image: nginx\n    ports:\n    - name: http\n      containerPort: 80\n    livenessProbe:\n      httpGet:\n        path: /metrics\n        port: 9090\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod-inject-enabled-cpu-ratio.yaml",
    "content": "kind: Pod\napiVersion: apps/v1\nmetadata:\n  name: nginx\n  namespace: kube-public\n  annotations:\n    config.linkerd.io/proxy-cpu-request: \"3\"\n    config.linkerd.io/proxy-cpu-ratio-limit: \"0.10\"\n    linkerd.io/inject: enabled\n  labels:\n    app: nginx\nspec:\n  containers:\n  - name: nginx\n    image: nginx\n    ports:\n    - name: http\n      containerPort: 80\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod-inject-enabled-log-level.yaml",
    "content": "kind: Pod\napiVersion: apps/v1\nmetadata:\n  name: nginx\n  namespace: kube-public\n  annotations:\n    config.linkerd.io/proxy-log-level: linkerd[name=\"inbound\"]=trace,linkerd[name=\"outbound\"]=debug,info\n    linkerd.io/inject: enabled\n  labels:\n    app: nginx\nspec:\n  containers:\n  - name: nginx\n    image: nginx\n    ports:\n    - name: http\n      containerPort: 80\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod-inject-enabled.yaml",
    "content": "kind: Pod\napiVersion: apps/v1\nmetadata:\n  name: nginx\n  namespace: kube-public\n  annotations:\n    linkerd.io/inject: enabled\n  labels:\n    app: nginx\nspec:\n  containers:\n  - name: nginx\n    image: nginx\n    ports:\n    - name: http\n      containerPort: 80\n    readinessProbe:\n      httpGet:\n        path: /metrics\n        port: 9090\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod-log-level.json",
    "content": "[\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/linkerd.io~1proxy-version\",\n    \"value\": \"dev-undefined\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/linkerd.io~1trust-root-sha256\",\n    \"value\": \"5090806bcf2daff5d54739ba02a8e7b919f7e62b2a46757e11089c916ec97fc2\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1control-plane-ns\",\n    \"value\": \"linkerd\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1proxy-deployment\",\n    \"value\": \"owner-deployment\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1workload-ns\",\n    \"value\": \"kube-public\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes\",\n    \"value\": []\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/initContainers\",\n    \"value\": []\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"emptyDir\": {},\n      \"name\": \"linkerd-proxy-init-xtables-lock\"\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/initContainers/-\",\n    \"value\": {\n      \"args\": [\n        \"--firewall-bin-path\",\n        \"iptables-nft\",\n        \"--firewall-save-bin-path\",\n        \"iptables-nft-save\",\n        \"--ipv6=false\",\n        \"--incoming-proxy-port\",\n        \"4143\",\n        \"--outgoing-proxy-port\",\n        \"4140\",\n        \"--proxy-uid\",\n        \"2102\",\n        \"--inbound-ports-to-ignore\",\n        \"4190,4191,4567,4568\",\n        \"--outbound-ports-to-ignore\",\n        \"4567,4568\"\n      ],\n      \"image\": \"cr.l5d.io/linkerd/proxy:dev-undefined\",\n      \"imagePullPolicy\": \"IfNotPresent\",\n      \"command\": [\n        \"/usr/lib/linkerd/linkerd2-proxy-init\"\n      ],\n      \"name\": \"linkerd-init\",\n      \"resources\": null,\n      \"securityContext\": {\n        \"allowPrivilegeEscalation\": false,\n        \"capabilities\": {\n          \"add\": [\n            \"NET_ADMIN\",\n            \"NET_RAW\"\n          ]\n        },\n        \"privileged\": false,\n        \"runAsNonRoot\": true,\n        \"runAsUser\": 65534,\n        \"runAsGroup\": 65534,\n        \"readOnlyRootFilesystem\": true,\n        \"seccompProfile\": {\n          \"type\": \"RuntimeDefault\"\n        }\n      },\n      \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n      \"volumeMounts\": [\n        {\n          \"mountPath\": \"/run\",\n          \"name\": \"linkerd-proxy-init-xtables-lock\"\n        }\n      ]\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"name\": \"linkerd-identity-end-entity\",\n      \"emptyDir\": {\n        \"medium\": \"Memory\"\n      }\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"name\": \"linkerd-identity-token\",\n      \"projected\": {\n        \"sources\": [\n          {\n            \"serviceAccountToken\": {\n              \"audience\": \"identity.l5d.io\",\n              \"expirationSeconds\": 86400,\n              \"path\": \"linkerd-identity-token\"\n            }\n          }\n        ]\n      }\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/containers/0\",\n    \"value\": {\n      \"env\": [\n        {\n          \"name\": \"_pod_name\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"metadata.name\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_ns\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"metadata.namespace\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_uid\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"metadata.uid\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_ip\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"status.podIP\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_nodeName\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"spec.nodeName\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_containerName\",\n          \"value\": \"linkerd-proxy\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CORES\",\n          \"value\": \"1\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CORES_MIN\",\n          \"value\": \"1\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\",\n          \"value\": \"false\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_LOG\",\n          \"value\": \"linkerd[name=\\\"inbound\\\"]=trace,linkerd[name=\\\"outbound\\\"]=debug,info,[{headers}]=off,[{request}]=off\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_LOG_FORMAT\",\n          \"value\": \"plain\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_SVC_ADDR\",\n          \"value\": \"linkerd-dst-headless.linkerd.svc.cluster.local.:8086\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\",\n          \"value\": \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_SVC_ADDR\",\n          \"value\": \"linkerd-policy.linkerd.svc.cluster.local.:8090\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_WORKLOAD\",\n          \"value\": \"{\\\"ns\\\":\\\"$(_pod_ns)\\\", \\\"pod\\\":\\\"$(_pod_name)\\\"}\\n\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\",\n          \"value\": \"all-unauthenticated\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\",\n          \"value\": \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\",\n          \"value\": \"3s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\",\n          \"value\": \"5m\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\",\n          \"value\": \"1h\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\",\n          \"value\": \"100ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\",\n          \"value\": \"1000ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\",\n          \"value\": \"5s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\",\n          \"value\": \"90s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_LISTEN_ADDR\",\n          \"value\": \"0.0.0.0:4190\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_ADMIN_LISTEN_ADDR\",\n          \"value\": \"0.0.0.0:4191\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\",\n          \"value\": \"127.0.0.1:4140\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\",\n          \"value\": \"127.0.0.1:4140\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_LISTEN_ADDR\",\n          \"value\": \"0.0.0.0:4143\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_IPS\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"status.podIPs\"\n            }\n          }\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_PORTS\",\n          \"value\": \"80\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\",\n          \"value\": \"svc.cluster.local.\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\",\n          \"value\": \"10000ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\",\n          \"value\": \"10000ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\",\n          \"value\": \"30s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\",\n          \"value\": \"30s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\",\n          \"value\": \"false\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\",\n          \"value\": \"10s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\",\n          \"value\": \"3s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\",\n          \"value\": \"10s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\",\n          \"value\": \"3s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\",\n          \"value\": \"25,587,3306,4444,5432,6379,9300,11211\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_CONTEXT\",\n          \"value\": \"{\\\"ns\\\":\\\"$(_pod_ns)\\\", \\\"nodeName\\\":\\\"$(_pod_nodeName)\\\", \\\"pod\\\":\\\"$(_pod_name)\\\"}\\n\"\n        },\n        {\n          \"name\": \"_pod_sa\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"spec.serviceAccountName\"\n            }\n          }\n        },\n        {\n          \"name\": \"_l5d_ns\",\n          \"value\": \"linkerd\"\n        },\n        {\n          \"name\": \"_l5d_trustdomain\",\n          \"value\": \"cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_DIR\",\n          \"value\": \"/var/run/linkerd/identity/end-entity\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\",\n          \"value\": \"IdentityTrustAnchorsPEM\\n\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_TOKEN_FILE\",\n          \"value\": \"/var/run/secrets/tokens/linkerd-identity-token\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_SVC_ADDR\",\n          \"value\": \"linkerd-identity-headless.linkerd.svc.cluster.local.:8080\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_LOCAL_NAME\",\n          \"value\": \"$(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_SVC_NAME\",\n          \"value\": \"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_SVC_NAME\",\n          \"value\": \"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_SVC_NAME\",\n          \"value\": \"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n        }\n      ],\n      \"image\": \"cr.l5d.io/linkerd/proxy:dev-undefined\",\n      \"imagePullPolicy\": \"IfNotPresent\",\n      \"lifecycle\": {\n        \"postStart\": {\n          \"exec\": {\n            \"command\": [\n              \"/usr/lib/linkerd/linkerd-await\",\n              \"--timeout=2m\",\n              \"--port=4191\"\n            ]\n          }\n        }\n      },\n      \"livenessProbe\": {\n        \"httpGet\": {\n          \"path\": \"/live\",\n          \"port\": 4191\n        },\n        \"initialDelaySeconds\": 10,\n        \"timeoutSeconds\": 1\n      },\n      \"name\": \"linkerd-proxy\",\n      \"ports\": [\n        {\n          \"containerPort\": 4143,\n          \"name\": \"linkerd-proxy\"\n        },\n        {\n          \"containerPort\": 4191,\n          \"name\": \"linkerd-admin\"\n        }\n      ],\n      \"readinessProbe\": {\n        \"httpGet\": {\n          \"path\": \"/ready\",\n          \"port\": 4191\n        },\n        \"initialDelaySeconds\": 2,\n        \"timeoutSeconds\": 1\n      },\n      \"resources\": null,\n      \"securityContext\": {\n        \"allowPrivilegeEscalation\": false,\n        \"readOnlyRootFilesystem\": true,\n        \"runAsNonRoot\": true,\n        \"runAsUser\": 2102,\n        \"seccompProfile\": {\n          \"type\": \"RuntimeDefault\"\n        }\n      },\n      \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n      \"volumeMounts\": [\n        {\n          \"mountPath\": \"/var/run/linkerd/identity/end-entity\",\n          \"name\": \"linkerd-identity-end-entity\"\n        },\n        {\n          \"mountPath\": \"/var/run/secrets/tokens\",\n          \"name\": \"linkerd-identity-token\"\n        }\n      ]\n    }\n  }\n]\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod-nativesidecar-inject-enabled.yaml",
    "content": "kind: Pod\napiVersion: apps/v1\nmetadata:\n  name: nginx\n  namespace: kube-public\n  annotations:\n    linkerd.io/inject: enabled\n    config.linkerd.io/opaque-ports: memcached\n  labels:\n    app: nginx\nspec:\n  initContainers:\n  - name: memcached\n    image: memcached\n    restartPolicy: Always\n    ports:\n    - containerPort: 11211\n      name: memcached\n  containers:\n  - name: nginx\n    image: nginx\n    ports:\n    - name: http\n      containerPort: 80\n    readinessProbe:\n      httpGet:\n        path: /metrics\n        port: 9090\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod-with-custom-debug-tag.yaml",
    "content": "kind: Pod\napiVersion: apps/v1\nmetadata:\n  name: nginx\n  namespace: kube-public\n  annotations:\n    config.linkerd.io/debug-image: cr.l5d.io/linkerd/debug\n    config.linkerd.io/debug-image-version: edge-24.2.4\n    config.linkerd.io/enable-debug-sidecar: true\n    linkerd.io/inject: enabled\n  labels:\n    app: nginx\nspec:\n  containers:\n  - name: nginx\n    image: nginx\n    ports:\n    - name: http\n      containerPort: 80\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod-with-custom-debug.patch.json",
    "content": "[\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/linkerd.io~1proxy-version\",\n    \"value\": \"dev-undefined\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/linkerd.io~1trust-root-sha256\",\n    \"value\": \"5090806bcf2daff5d54739ba02a8e7b919f7e62b2a46757e11089c916ec97fc2\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1control-plane-ns\",\n    \"value\": \"linkerd\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1proxy-deployment\",\n    \"value\": \"owner-deployment\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1workload-ns\",\n    \"value\": \"kube-public\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes\",\n    \"value\": []\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/initContainers\",\n    \"value\": []\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"emptyDir\": {},\n      \"name\": \"linkerd-proxy-init-xtables-lock\"\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/initContainers/-\",\n    \"value\": {\n      \"args\": [\n        \"--firewall-bin-path\",\n        \"iptables-nft\",\n        \"--firewall-save-bin-path\",\n        \"iptables-nft-save\",\n        \"--ipv6=false\",\n        \"--incoming-proxy-port\",\n        \"4143\",\n        \"--outgoing-proxy-port\",\n        \"4140\",\n        \"--proxy-uid\",\n        \"2102\",\n        \"--inbound-ports-to-ignore\",\n        \"4190,4191,4567,4568\",\n        \"--outbound-ports-to-ignore\",\n        \"4567,4568\"\n      ],\n      \"image\": \"cr.l5d.io/linkerd/proxy:dev-undefined\",\n      \"imagePullPolicy\": \"IfNotPresent\",\n      \"command\": [\n        \"/usr/lib/linkerd/linkerd2-proxy-init\"\n      ],\n      \"name\": \"linkerd-init\",\n      \"resources\": null,\n      \"securityContext\": {\n        \"allowPrivilegeEscalation\": false,\n        \"capabilities\": {\n          \"add\": [\n            \"NET_ADMIN\",\n            \"NET_RAW\"\n          ]\n        },\n        \"privileged\": false,\n        \"readOnlyRootFilesystem\": true,\n        \"runAsGroup\": 65534,\n        \"runAsNonRoot\": true,\n        \"runAsUser\": 65534,\n        \"seccompProfile\": {\n          \"type\": \"RuntimeDefault\"\n        }\n      },\n      \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n      \"volumeMounts\": [\n        {\n          \"mountPath\": \"/run\",\n          \"name\": \"linkerd-proxy-init-xtables-lock\"\n        }\n      ]\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/containers/-\",\n    \"value\": {\n      \"image\": \"cr.l5d.io/linkerd/debug:edge-24.2.4\",\n      \"imagePullPolicy\": \"IfNotPresent\",\n      \"name\": \"linkerd-debug\",\n      \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n      \"livenessProbe\": {\n        \"exec\": {\n          \"command\": [\n            \"true\"\n          ]\n        }\n      },\n      \"readinessProbe\": {\n        \"exec\": {\n          \"command\": [\n            \"true\"\n          ]\n        }\n      }\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"name\": \"linkerd-identity-end-entity\",\n      \"emptyDir\": {\n        \"medium\": \"Memory\"\n      }\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"name\": \"linkerd-identity-token\",\n      \"projected\": {\n        \"sources\": [\n          {\n            \"serviceAccountToken\": {\n              \"audience\": \"identity.l5d.io\",\n              \"expirationSeconds\": 86400,\n              \"path\": \"linkerd-identity-token\"\n            }\n          }\n        ]\n      }\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/containers/0\",\n    \"value\": {\n      \"env\": [\n        {\n          \"name\": \"_pod_name\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"metadata.name\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_ns\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"metadata.namespace\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_uid\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"metadata.uid\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_ip\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"status.podIP\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_nodeName\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"spec.nodeName\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_containerName\",\n          \"value\": \"linkerd-proxy\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CORES\",\n          \"value\": \"1\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CORES_MIN\",\n          \"value\": \"1\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\",\n          \"value\": \"false\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_LOG\",\n          \"value\": \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_LOG_FORMAT\",\n          \"value\": \"plain\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_SVC_ADDR\",\n          \"value\": \"linkerd-dst-headless.linkerd.svc.cluster.local.:8086\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\",\n          \"value\": \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_SVC_ADDR\",\n          \"value\": \"linkerd-policy.linkerd.svc.cluster.local.:8090\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_WORKLOAD\",\n          \"value\": \"{\\\"ns\\\":\\\"$(_pod_ns)\\\", \\\"pod\\\":\\\"$(_pod_name)\\\"}\\n\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\",\n          \"value\": \"all-unauthenticated\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\",\n          \"value\": \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\",\n          \"value\": \"3s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\",\n          \"value\": \"5m\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\",\n          \"value\": \"1h\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\",\n          \"value\": \"100ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\",\n          \"value\": \"1000ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\",\n          \"value\": \"5s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\",\n          \"value\": \"90s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_LISTEN_ADDR\",\n          \"value\": \"0.0.0.0:4190\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_ADMIN_LISTEN_ADDR\",\n          \"value\": \"0.0.0.0:4191\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\",\n          \"value\": \"127.0.0.1:4140\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\",\n          \"value\": \"127.0.0.1:4140\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_LISTEN_ADDR\",\n          \"value\": \"0.0.0.0:4143\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_IPS\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"status.podIPs\"\n            }\n          }\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_PORTS\",\n          \"value\": \"80\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\",\n          \"value\": \"svc.cluster.local.\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\",\n          \"value\": \"10000ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\",\n          \"value\": \"10000ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\",\n          \"value\": \"30s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\",\n          \"value\": \"30s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\",\n          \"value\": \"false\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\",\n          \"value\": \"10s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\",\n          \"value\": \"3s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\",\n          \"value\": \"10s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\",\n          \"value\": \"3s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\",\n          \"value\": \"25,587,3306,4444,5432,6379,9300,11211\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_CONTEXT\",\n          \"value\": \"{\\\"ns\\\":\\\"$(_pod_ns)\\\", \\\"nodeName\\\":\\\"$(_pod_nodeName)\\\", \\\"pod\\\":\\\"$(_pod_name)\\\"}\\n\"\n        },\n        {\n          \"name\": \"_pod_sa\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"spec.serviceAccountName\"\n            }\n          }\n        },\n        {\n          \"name\": \"_l5d_ns\",\n          \"value\": \"linkerd\"\n        },\n        {\n          \"name\": \"_l5d_trustdomain\",\n          \"value\": \"cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_DIR\",\n          \"value\": \"/var/run/linkerd/identity/end-entity\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\",\n          \"value\": \"IdentityTrustAnchorsPEM\\n\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_TOKEN_FILE\",\n          \"value\": \"/var/run/secrets/tokens/linkerd-identity-token\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_SVC_ADDR\",\n          \"value\": \"linkerd-identity-headless.linkerd.svc.cluster.local.:8080\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_LOCAL_NAME\",\n          \"value\": \"$(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_SVC_NAME\",\n          \"value\": \"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_SVC_NAME\",\n          \"value\": \"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_SVC_NAME\",\n          \"value\": \"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n        }\n      ],\n      \"image\": \"cr.l5d.io/linkerd/proxy:dev-undefined\",\n      \"imagePullPolicy\": \"IfNotPresent\",\n      \"lifecycle\": {\n        \"postStart\": {\n          \"exec\": {\n            \"command\": [\n              \"/usr/lib/linkerd/linkerd-await\",\n              \"--timeout=2m\",\n              \"--port=4191\"\n            ]\n          }\n        }\n      },\n      \"livenessProbe\": {\n        \"httpGet\": {\n          \"path\": \"/live\",\n          \"port\": 4191\n        },\n        \"initialDelaySeconds\": 10,\n        \"timeoutSeconds\": 1\n      },\n      \"name\": \"linkerd-proxy\",\n      \"ports\": [\n        {\n          \"containerPort\": 4143,\n          \"name\": \"linkerd-proxy\"\n        },\n        {\n          \"containerPort\": 4191,\n          \"name\": \"linkerd-admin\"\n        }\n      ],\n      \"readinessProbe\": {\n        \"httpGet\": {\n          \"path\": \"/ready\",\n          \"port\": 4191\n        },\n        \"initialDelaySeconds\": 2,\n        \"timeoutSeconds\": 1\n      },\n      \"resources\": null,\n      \"securityContext\": {\n        \"allowPrivilegeEscalation\": false,\n        \"readOnlyRootFilesystem\": true,\n        \"runAsNonRoot\": true,\n        \"runAsUser\": 2102,\n        \"seccompProfile\": {\n          \"type\": \"RuntimeDefault\"\n        }\n      },\n      \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n      \"volumeMounts\": [\n        {\n          \"mountPath\": \"/var/run/linkerd/identity/end-entity\",\n          \"name\": \"linkerd-identity-end-entity\"\n        },\n        {\n          \"mountPath\": \"/var/run/secrets/tokens\",\n          \"name\": \"linkerd-identity-token\"\n        }\n      ]\n    }\n  }\n]\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod-with-debug-disabled.yaml",
    "content": "kind: Pod\napiVersion: apps/v1\nmetadata:\n  name: nginx\n  namespace: kube-public\n  annotations:\n    config.linkerd.io/enable-debug-sidecar: false\n    linkerd.io/inject: enabled\n  labels:\n    app: nginx\nspec:\n  containers:\n  - name: nginx\n    image: nginx\n    ports:\n    - name: http\n      containerPort: 80\n    - name: metrics\n      containerPort: 9090\n    livenessProbe:\n      httpGet:\n        path: /metrics\n        port: metrics\n    readinessProbe:\n      httpGet:\n        path: /metrics\n        port: metrics\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod-with-debug-enabled.yaml",
    "content": "kind: Pod\napiVersion: apps/v1\nmetadata:\n  name: nginx\n  namespace: kube-public\n  annotations:\n    config.linkerd.io/enable-debug-sidecar: true\n    linkerd.io/inject: enabled\n  labels:\n    app: nginx\nspec:\n  containers:\n  - name: nginx\n    image: nginx\n    ports:\n    - name: http\n      containerPort: 80\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod-with-debug.patch.json",
    "content": "[\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/linkerd.io~1proxy-version\",\n    \"value\": \"dev-undefined\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/linkerd.io~1trust-root-sha256\",\n    \"value\": \"5090806bcf2daff5d54739ba02a8e7b919f7e62b2a46757e11089c916ec97fc2\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1control-plane-ns\",\n    \"value\": \"linkerd\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1proxy-deployment\",\n    \"value\": \"owner-deployment\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1workload-ns\",\n    \"value\": \"kube-public\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes\",\n    \"value\": []\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/initContainers\",\n    \"value\": []\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"emptyDir\": {},\n      \"name\": \"linkerd-proxy-init-xtables-lock\"\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/initContainers/-\",\n    \"value\": {\n      \"args\": [\n        \"--firewall-bin-path\",\n        \"iptables-nft\",\n        \"--firewall-save-bin-path\",\n        \"iptables-nft-save\",\n        \"--ipv6=false\",\n        \"--incoming-proxy-port\",\n        \"4143\",\n        \"--outgoing-proxy-port\",\n        \"4140\",\n        \"--proxy-uid\",\n        \"2102\",\n        \"--inbound-ports-to-ignore\",\n        \"4190,4191,4567,4568\",\n        \"--outbound-ports-to-ignore\",\n        \"4567,4568\"\n      ],\n      \"image\": \"cr.l5d.io/linkerd/proxy:dev-undefined\",\n      \"imagePullPolicy\": \"IfNotPresent\",\n      \"command\": [\n        \"/usr/lib/linkerd/linkerd2-proxy-init\"\n      ],\n      \"name\": \"linkerd-init\",\n      \"resources\": null,\n      \"securityContext\": {\n        \"allowPrivilegeEscalation\": false,\n        \"capabilities\": {\n          \"add\": [\n            \"NET_ADMIN\",\n            \"NET_RAW\"\n          ]\n        },\n        \"privileged\": false,\n        \"readOnlyRootFilesystem\": true,\n        \"runAsGroup\": 65534,\n        \"runAsNonRoot\": true,\n        \"runAsUser\": 65534,\n        \"seccompProfile\": {\n          \"type\": \"RuntimeDefault\"\n        }\n      },\n      \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n      \"volumeMounts\": [\n        {\n          \"mountPath\": \"/run\",\n          \"name\": \"linkerd-proxy-init-xtables-lock\"\n        }\n      ]\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/containers/-\",\n    \"value\": {\n      \"image\": \"cr.l5d.io/linkerd/debug:dev-undefined\",\n      \"imagePullPolicy\": \"IfNotPresent\",\n      \"name\": \"linkerd-debug\",\n      \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n      \"livenessProbe\": {\n        \"exec\": {\n          \"command\": [\n            \"true\"\n          ]\n        }\n      },\n      \"readinessProbe\": {\n        \"exec\": {\n          \"command\": [\n            \"true\"\n          ]\n        }\n      }\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"name\": \"linkerd-identity-end-entity\",\n      \"emptyDir\": {\n        \"medium\": \"Memory\"\n      }\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"name\": \"linkerd-identity-token\",\n      \"projected\": {\n        \"sources\": [\n          {\n            \"serviceAccountToken\": {\n              \"audience\": \"identity.l5d.io\",\n              \"expirationSeconds\": 86400,\n              \"path\": \"linkerd-identity-token\"\n            }\n          }\n        ]\n      }\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/containers/0\",\n    \"value\": {\n      \"env\": [\n        {\n          \"name\": \"_pod_name\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"metadata.name\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_ns\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"metadata.namespace\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_uid\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"metadata.uid\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_ip\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"status.podIP\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_nodeName\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"spec.nodeName\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_containerName\",\n          \"value\": \"linkerd-proxy\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CORES\",\n          \"value\": \"1\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CORES_MIN\",\n          \"value\": \"1\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\",\n          \"value\": \"false\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_LOG\",\n          \"value\": \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_LOG_FORMAT\",\n          \"value\": \"plain\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_SVC_ADDR\",\n          \"value\": \"linkerd-dst-headless.linkerd.svc.cluster.local.:8086\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\",\n          \"value\": \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_SVC_ADDR\",\n          \"value\": \"linkerd-policy.linkerd.svc.cluster.local.:8090\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_WORKLOAD\",\n          \"value\": \"{\\\"ns\\\":\\\"$(_pod_ns)\\\", \\\"pod\\\":\\\"$(_pod_name)\\\"}\\n\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\",\n          \"value\": \"all-unauthenticated\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\",\n          \"value\": \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\",\n          \"value\": \"3s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\",\n          \"value\": \"5m\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\",\n          \"value\": \"1h\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\",\n          \"value\": \"100ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\",\n          \"value\": \"1000ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\",\n          \"value\": \"5s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\",\n          \"value\": \"90s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_LISTEN_ADDR\",\n          \"value\": \"0.0.0.0:4190\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_ADMIN_LISTEN_ADDR\",\n          \"value\": \"0.0.0.0:4191\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\",\n          \"value\": \"127.0.0.1:4140\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\",\n          \"value\": \"127.0.0.1:4140\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_LISTEN_ADDR\",\n          \"value\": \"0.0.0.0:4143\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_IPS\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"status.podIPs\"\n            }\n          }\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_PORTS\",\n          \"value\": \"80\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\",\n          \"value\": \"svc.cluster.local.\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\",\n          \"value\": \"10000ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\",\n          \"value\": \"10000ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\",\n          \"value\": \"30s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\",\n          \"value\": \"30s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\",\n          \"value\": \"false\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\",\n          \"value\": \"10s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\",\n          \"value\": \"3s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\",\n          \"value\": \"10s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\",\n          \"value\": \"3s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\",\n          \"value\": \"25,587,3306,4444,5432,6379,9300,11211\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_CONTEXT\",\n          \"value\": \"{\\\"ns\\\":\\\"$(_pod_ns)\\\", \\\"nodeName\\\":\\\"$(_pod_nodeName)\\\", \\\"pod\\\":\\\"$(_pod_name)\\\"}\\n\"\n        },\n        {\n          \"name\": \"_pod_sa\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"spec.serviceAccountName\"\n            }\n          }\n        },\n        {\n          \"name\": \"_l5d_ns\",\n          \"value\": \"linkerd\"\n        },\n        {\n          \"name\": \"_l5d_trustdomain\",\n          \"value\": \"cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_DIR\",\n          \"value\": \"/var/run/linkerd/identity/end-entity\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\",\n          \"value\": \"IdentityTrustAnchorsPEM\\n\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_TOKEN_FILE\",\n          \"value\": \"/var/run/secrets/tokens/linkerd-identity-token\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_SVC_ADDR\",\n          \"value\": \"linkerd-identity-headless.linkerd.svc.cluster.local.:8080\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_LOCAL_NAME\",\n          \"value\": \"$(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_SVC_NAME\",\n          \"value\": \"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_SVC_NAME\",\n          \"value\": \"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_SVC_NAME\",\n          \"value\": \"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n        }\n      ],\n      \"image\": \"cr.l5d.io/linkerd/proxy:dev-undefined\",\n      \"imagePullPolicy\": \"IfNotPresent\",\n      \"lifecycle\": {\n        \"postStart\": {\n          \"exec\": {\n            \"command\": [\n              \"/usr/lib/linkerd/linkerd-await\",\n              \"--timeout=2m\",\n              \"--port=4191\"\n            ]\n          }\n        }\n      },\n      \"livenessProbe\": {\n        \"httpGet\": {\n          \"path\": \"/live\",\n          \"port\": 4191\n        },\n        \"initialDelaySeconds\": 10,\n        \"timeoutSeconds\": 1\n      },\n      \"name\": \"linkerd-proxy\",\n      \"ports\": [\n        {\n          \"containerPort\": 4143,\n          \"name\": \"linkerd-proxy\"\n        },\n        {\n          \"containerPort\": 4191,\n          \"name\": \"linkerd-admin\"\n        }\n      ],\n      \"readinessProbe\": {\n        \"httpGet\": {\n          \"path\": \"/ready\",\n          \"port\": 4191\n        },\n        \"initialDelaySeconds\": 2,\n        \"timeoutSeconds\": 1\n      },\n      \"resources\": null,\n      \"securityContext\": {\n        \"allowPrivilegeEscalation\": false,\n        \"readOnlyRootFilesystem\": true,\n        \"runAsNonRoot\": true,\n        \"runAsUser\": 2102,\n        \"seccompProfile\": {\n          \"type\": \"RuntimeDefault\"\n        }\n      },\n      \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n      \"volumeMounts\": [\n        {\n          \"mountPath\": \"/var/run/linkerd/identity/end-entity\",\n          \"name\": \"linkerd-identity-end-entity\"\n        },\n        {\n          \"mountPath\": \"/var/run/secrets/tokens\",\n          \"name\": \"linkerd-identity-token\"\n        }\n      ]\n    }\n  }\n]\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod-with-ns-annotations.patch.json",
    "content": "[\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/config.alpha.linkerd.io~1proxy-wait-before-exit-seconds\",\n    \"value\": \"300\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/config.linkerd.io~1skip-outbound-ports\",\n    \"value\": \"34567\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/linkerd.io~1proxy-version\",\n    \"value\": \"dev-undefined\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/linkerd.io~1trust-root-sha256\",\n    \"value\": \"5090806bcf2daff5d54739ba02a8e7b919f7e62b2a46757e11089c916ec97fc2\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1control-plane-ns\",\n    \"value\": \"linkerd\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1proxy-deployment\",\n    \"value\": \"owner-deployment\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1workload-ns\",\n    \"value\": \"kube-public\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes\",\n    \"value\": []\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/initContainers\",\n    \"value\": []\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"emptyDir\": {},\n      \"name\": \"linkerd-proxy-init-xtables-lock\"\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/initContainers/-\",\n    \"value\": {\n      \"args\": [\n        \"--firewall-bin-path\",\n        \"iptables-nft\",\n        \"--firewall-save-bin-path\",\n        \"iptables-nft-save\",\n        \"--ipv6=false\",\n        \"--incoming-proxy-port\",\n        \"4143\",\n        \"--outgoing-proxy-port\",\n        \"4140\",\n        \"--proxy-uid\",\n        \"2102\",\n        \"--inbound-ports-to-ignore\",\n        \"4190,4191,4567,4568\",\n        \"--outbound-ports-to-ignore\",\n        \"34567\"\n      ],\n      \"image\": \"cr.l5d.io/linkerd/proxy:dev-undefined\",\n      \"imagePullPolicy\": \"IfNotPresent\",\n      \"command\": [\n        \"/usr/lib/linkerd/linkerd2-proxy-init\"\n      ],\n      \"name\": \"linkerd-init\",\n      \"resources\": null,\n      \"securityContext\": {\n        \"allowPrivilegeEscalation\": false,\n        \"capabilities\": {\n          \"add\": [\n            \"NET_ADMIN\",\n            \"NET_RAW\"\n          ]\n        },\n        \"privileged\": false,\n        \"runAsNonRoot\": true,\n        \"runAsUser\": 65534,\n        \"runAsGroup\": 65534,\n        \"readOnlyRootFilesystem\": true,\n        \"seccompProfile\": {\n          \"type\": \"RuntimeDefault\"\n        }\n      },\n      \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n      \"volumeMounts\": [\n        {\n          \"mountPath\": \"/run\",\n          \"name\": \"linkerd-proxy-init-xtables-lock\"\n        }\n      ]\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"name\": \"linkerd-identity-end-entity\",\n      \"emptyDir\": {\n        \"medium\": \"Memory\"\n      }\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"name\": \"linkerd-identity-token\",\n      \"projected\": {\n        \"sources\": [\n          {\n            \"serviceAccountToken\": {\n              \"audience\": \"identity.l5d.io\",\n              \"expirationSeconds\": 86400,\n              \"path\": \"linkerd-identity-token\"\n            }\n          }\n        ]\n      }\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/containers/0\",\n    \"value\": {\n      \"env\": [\n        {\n          \"name\": \"_pod_name\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"metadata.name\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_ns\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"metadata.namespace\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_uid\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"metadata.uid\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_ip\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"status.podIP\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_nodeName\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"spec.nodeName\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_containerName\",\n          \"value\": \"linkerd-proxy\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CORES\",\n          \"value\": \"1\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CORES_MIN\",\n          \"value\": \"1\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\",\n          \"value\": \"false\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_LOG\",\n          \"value\": \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_LOG_FORMAT\",\n          \"value\": \"plain\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_SVC_ADDR\",\n          \"value\": \"linkerd-dst-headless.linkerd.svc.cluster.local.:8086\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\",\n          \"value\": \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_SVC_ADDR\",\n          \"value\": \"linkerd-policy.linkerd.svc.cluster.local.:8090\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_WORKLOAD\",\n          \"value\": \"{\\\"ns\\\":\\\"$(_pod_ns)\\\", \\\"pod\\\":\\\"$(_pod_name)\\\"}\\n\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\",\n          \"value\": \"all-unauthenticated\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\",\n          \"value\": \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\",\n          \"value\": \"3s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\",\n          \"value\": \"5m\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\",\n          \"value\": \"1h\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\",\n          \"value\": \"100ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\",\n          \"value\": \"1000ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\",\n          \"value\": \"5s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\",\n          \"value\": \"90s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_LISTEN_ADDR\",\n          \"value\": \"0.0.0.0:4190\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_ADMIN_LISTEN_ADDR\",\n          \"value\": \"0.0.0.0:4191\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\",\n          \"value\": \"127.0.0.1:4140\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\",\n          \"value\": \"127.0.0.1:4140\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_LISTEN_ADDR\",\n          \"value\": \"0.0.0.0:4143\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_IPS\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"status.podIPs\"\n            }\n          }\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_PORTS\",\n          \"value\": \"80,9090\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\",\n          \"value\": \"svc.cluster.local.\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\",\n          \"value\": \"10000ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\",\n          \"value\": \"10000ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\",\n          \"value\": \"30s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\",\n          \"value\": \"30s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\",\n          \"value\": \"false\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\",\n          \"value\": \"10s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\",\n          \"value\": \"3s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\",\n          \"value\": \"10s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\",\n          \"value\": \"3s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\",\n          \"value\": \"25,587,3306,4444,5432,6379,9300,11211\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_CONTEXT\",\n          \"value\": \"{\\\"ns\\\":\\\"$(_pod_ns)\\\", \\\"nodeName\\\":\\\"$(_pod_nodeName)\\\", \\\"pod\\\":\\\"$(_pod_name)\\\"}\\n\"\n        },\n        {\n          \"name\": \"_pod_sa\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"spec.serviceAccountName\"\n            }\n          }\n        },\n        {\n          \"name\": \"_l5d_ns\",\n          \"value\": \"linkerd\"\n        },\n        {\n          \"name\": \"_l5d_trustdomain\",\n          \"value\": \"cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_DIR\",\n          \"value\": \"/var/run/linkerd/identity/end-entity\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\",\n          \"value\": \"IdentityTrustAnchorsPEM\\n\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_TOKEN_FILE\",\n          \"value\": \"/var/run/secrets/tokens/linkerd-identity-token\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_SVC_ADDR\",\n          \"value\": \"linkerd-identity-headless.linkerd.svc.cluster.local.:8080\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_LOCAL_NAME\",\n          \"value\": \"$(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_SVC_NAME\",\n          \"value\": \"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_SVC_NAME\",\n          \"value\": \"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_SVC_NAME\",\n          \"value\": \"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n        }\n      ],\n      \"image\": \"cr.l5d.io/linkerd/proxy:dev-undefined\",\n      \"imagePullPolicy\": \"IfNotPresent\",\n      \"lifecycle\": {\n        \"postStart\": {\n          \"exec\": {\n            \"command\": [\n              \"/usr/lib/linkerd/linkerd-await\",\n              \"--timeout=2m\",\n              \"--port=4191\"\n            ]\n          }\n        },\n        \"preStop\": {\n          \"exec\": {\n            \"command\": [\n              \"/bin/sleep\",\n              \"300\"\n            ]\n          }\n        }\n      },\n      \"livenessProbe\": {\n        \"httpGet\": {\n          \"path\": \"/live\",\n          \"port\": 4191\n        },\n        \"initialDelaySeconds\": 10,\n        \"timeoutSeconds\": 1\n      },\n      \"name\": \"linkerd-proxy\",\n      \"ports\": [\n        {\n          \"containerPort\": 4143,\n          \"name\": \"linkerd-proxy\"\n        },\n        {\n          \"containerPort\": 4191,\n          \"name\": \"linkerd-admin\"\n        }\n      ],\n      \"readinessProbe\": {\n        \"httpGet\": {\n          \"path\": \"/ready\",\n          \"port\": 4191\n        },\n        \"initialDelaySeconds\": 2,\n        \"timeoutSeconds\": 1\n      },\n      \"resources\": null,\n      \"securityContext\": {\n        \"allowPrivilegeEscalation\": false,\n        \"readOnlyRootFilesystem\": true,\n        \"runAsNonRoot\": true,\n        \"runAsUser\": 2102,\n        \"seccompProfile\": {\n          \"type\": \"RuntimeDefault\"\n        }\n      },\n      \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n      \"volumeMounts\": [\n        {\n          \"mountPath\": \"/var/run/linkerd/identity/end-entity\",\n          \"name\": \"linkerd-identity-end-entity\"\n        },\n        {\n          \"mountPath\": \"/var/run/secrets/tokens\",\n          \"name\": \"linkerd-identity-token\"\n        }\n      ]\n    }\n  }\n]\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod-with-opaque-ports.yaml",
    "content": "---\napiVersion: v1\nkind: Pod\nmetadata:\n  name: pod\n  namespace: kube-public\n  annotations:\n    config.linkerd.io/opaque-ports: \"8080\"\nspec:\n  containers:\n  - name: nginx\n    image: nginx\n    ports:\n    - name: http\n      containerPort: 80\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod-without-opaque-ports.yaml",
    "content": "---\napiVersion: v1\nkind: Pod\nmetadata:\n  name: pod\n  namespace: kube-public\nspec:\n  containers:\n  - name: nginx\n    image: nginx\n    ports:\n    - name: http\n      containerPort: 80\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod.nativesidecar.patch.json",
    "content": "[\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/linkerd.io~1proxy-version\",\n    \"value\": \"dev-undefined\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/linkerd.io~1trust-root-sha256\",\n    \"value\": \"5090806bcf2daff5d54739ba02a8e7b919f7e62b2a46757e11089c916ec97fc2\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1control-plane-ns\",\n    \"value\": \"linkerd\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1proxy-deployment\",\n    \"value\": \"owner-deployment\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1workload-ns\",\n    \"value\": \"kube-public\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes\",\n    \"value\": []\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"emptyDir\": {},\n      \"name\": \"linkerd-proxy-init-xtables-lock\"\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/initContainers/-\",\n    \"value\":\n      {\n        \"args\": [\n          \"--firewall-bin-path\",\n          \"iptables-nft\",\n          \"--firewall-save-bin-path\",\n          \"iptables-nft-save\",\n          \"--ipv6=false\",\n          \"--incoming-proxy-port\",\n          \"4143\",\n          \"--outgoing-proxy-port\",\n          \"4140\",\n          \"--proxy-uid\",\n          \"2102\",\n          \"--inbound-ports-to-ignore\",\n          \"4190,4191,4567,4568\",\n          \"--outbound-ports-to-ignore\",\n          \"4567,4568\"\n        ],\n        \"command\": [\n          \"/usr/lib/linkerd/linkerd2-proxy-init\"\n        ],\n        \"image\": \"cr.l5d.io/linkerd/proxy:dev-undefined\",\n        \"imagePullPolicy\": \"IfNotPresent\",\n        \"name\": \"linkerd-init\",\n        \"resources\": null,\n        \"securityContext\": {\n          \"allowPrivilegeEscalation\": false,\n          \"capabilities\": {\n            \"add\": [\n              \"NET_ADMIN\",\n              \"NET_RAW\"\n            ]\n          },\n          \"privileged\": false,\n          \"readOnlyRootFilesystem\": true,\n          \"runAsGroup\": 65534,\n          \"runAsNonRoot\": true,\n          \"runAsUser\": 65534,\n          \"seccompProfile\": {\n            \"type\": \"RuntimeDefault\"\n          }\n        },\n        \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n        \"volumeMounts\": [\n          {\n            \"mountPath\": \"/run\",\n            \"name\": \"linkerd-proxy-init-xtables-lock\"\n          }\n        ]\n      }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"name\": \"linkerd-identity-end-entity\",\n      \"emptyDir\": {\n        \"medium\": \"Memory\"\n      }\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\":\n      {\n        \"name\": \"linkerd-identity-token\",\n        \"projected\": {\n          \"sources\": [\n            {\n              \"serviceAccountToken\": {\n                \"audience\": \"identity.l5d.io\",\n                \"expirationSeconds\": 86400,\n                \"path\": \"linkerd-identity-token\"\n              }\n            }\n          ]\n        }\n      }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/containers/0\",\n    \"value\":\n      {\n        \"env\": [\n          {\n            \"name\": \"_pod_name\",\n            \"valueFrom\": {\n              \"fieldRef\": {\n                \"fieldPath\": \"metadata.name\"\n              }\n            }\n          },\n          {\n            \"name\": \"_pod_ns\",\n            \"valueFrom\": {\n              \"fieldRef\": {\n                \"fieldPath\": \"metadata.namespace\"\n              }\n            }\n          },\n          {\n            \"name\": \"_pod_uid\",\n            \"valueFrom\": {\n              \"fieldRef\": {\n                \"fieldPath\": \"metadata.uid\"\n              }\n            }\n          },\n          {\n            \"name\": \"_pod_ip\",\n            \"valueFrom\": {\n              \"fieldRef\": {\n                \"fieldPath\": \"status.podIP\"\n              }\n            }\n          },\n          {\n            \"name\": \"_pod_nodeName\",\n            \"valueFrom\": {\n              \"fieldRef\": {\n                \"fieldPath\": \"spec.nodeName\"\n              }\n            }\n          },\n          {\n            \"name\": \"_pod_containerName\",\n            \"value\": \"linkerd-proxy\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_CORES\",\n            \"value\": \"1\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_CORES_MIN\",\n            \"value\": \"1\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\",\n            \"value\": \"false\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_LOG\",\n            \"value\": \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_LOG_FORMAT\",\n            \"value\": \"plain\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_DESTINATION_SVC_ADDR\",\n            \"value\": \"linkerd-dst-headless.linkerd.svc.cluster.local.:8086\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\",\n            \"value\": \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_POLICY_SVC_ADDR\",\n            \"value\": \"linkerd-policy.linkerd.svc.cluster.local.:8090\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_POLICY_WORKLOAD\",\n            \"value\": \"{\\\"ns\\\":\\\"$(_pod_ns)\\\", \\\"pod\\\":\\\"$(_pod_name)\\\"}\\n\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\",\n            \"value\": \"all-unauthenticated\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\",\n            \"value\": \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\",\n            \"value\": \"3s\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\",\n            \"value\": \"5m\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\",\n            \"value\": \"1h\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\",\n            \"value\": \"100ms\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\",\n            \"value\": \"1000ms\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\",\n            \"value\": \"5s\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\",\n            \"value\": \"90s\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_CONTROL_LISTEN_ADDR\",\n            \"value\": \"0.0.0.0:4190\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_ADMIN_LISTEN_ADDR\",\n            \"value\": \"0.0.0.0:4191\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\",\n            \"value\": \"127.0.0.1:4140\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\",\n            \"value\": \"127.0.0.1:4140\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_INBOUND_LISTEN_ADDR\",\n            \"value\": \"0.0.0.0:4143\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_INBOUND_IPS\",\n            \"valueFrom\": {\n              \"fieldRef\": {\n                \"fieldPath\": \"status.podIPs\"\n              }\n            }\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_INBOUND_PORTS\",\n            \"value\": \"11211,80,9090\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\",\n            \"value\": \"svc.cluster.local.\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\",\n            \"value\": \"10000ms\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\",\n            \"value\": \"10000ms\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\",\n            \"value\": \"30s\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\",\n            \"value\": \"30s\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\",\n            \"value\": \"false\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\",\n            \"value\": \"10s\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\",\n            \"value\": \"3s\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\",\n            \"value\": \"10s\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\",\n            \"value\": \"3s\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\",\n            \"value\": \"11211\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_DESTINATION_CONTEXT\",\n            \"value\": \"{\\\"ns\\\":\\\"$(_pod_ns)\\\", \\\"nodeName\\\":\\\"$(_pod_nodeName)\\\", \\\"pod\\\":\\\"$(_pod_name)\\\"}\\n\"\n          },\n          {\n            \"name\": \"_pod_sa\",\n            \"valueFrom\": {\n              \"fieldRef\": {\n                \"fieldPath\": \"spec.serviceAccountName\"\n              }\n            }\n          },\n          {\n            \"name\": \"_l5d_ns\",\n            \"value\": \"linkerd\"\n          },\n          {\n            \"name\": \"_l5d_trustdomain\",\n            \"value\": \"cluster.local\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_IDENTITY_DIR\",\n            \"value\": \"/var/run/linkerd/identity/end-entity\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\",\n            \"value\": \"IdentityTrustAnchorsPEM\\n\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_IDENTITY_TOKEN_FILE\",\n            \"value\": \"/var/run/secrets/tokens/linkerd-identity-token\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_IDENTITY_SVC_ADDR\",\n            \"value\": \"linkerd-identity-headless.linkerd.svc.cluster.local.:8080\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_IDENTITY_LOCAL_NAME\",\n            \"value\": \"$(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_IDENTITY_SVC_NAME\",\n            \"value\": \"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_DESTINATION_SVC_NAME\",\n            \"value\": \"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n          },\n          {\n            \"name\": \"LINKERD2_PROXY_POLICY_SVC_NAME\",\n            \"value\": \"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n          }\n        ],\n        \"image\": \"cr.l5d.io/linkerd/proxy:dev-undefined\",\n        \"imagePullPolicy\": \"IfNotPresent\",\n        \"lifecycle\": {\n          \"postStart\": {\n            \"exec\": {\n              \"command\": [\n                \"/usr/lib/linkerd/linkerd-await\",\n                \"--timeout=2m\",\n                \"--port=4191\"\n              ]\n            }\n          }\n        },\n        \"livenessProbe\": {\n          \"httpGet\": {\n            \"path\": \"/live\",\n            \"port\": 4191\n          },\n          \"initialDelaySeconds\": 10,\n          \"timeoutSeconds\": 1\n        },\n        \"name\": \"linkerd-proxy\",\n        \"ports\": [\n          {\n            \"containerPort\": 4143,\n            \"name\": \"linkerd-proxy\"\n          },\n          {\n            \"containerPort\": 4191,\n            \"name\": \"linkerd-admin\"\n          }\n        ],\n        \"readinessProbe\": {\n          \"httpGet\": {\n            \"path\": \"/ready\",\n            \"port\": 4191\n          },\n          \"initialDelaySeconds\": 2,\n          \"timeoutSeconds\": 1\n        },\n        \"resources\": null,\n        \"securityContext\": {\n          \"allowPrivilegeEscalation\": false,\n          \"readOnlyRootFilesystem\": true,\n          \"runAsNonRoot\": true,\n          \"runAsUser\": 2102,\n          \"seccompProfile\": {\n            \"type\": \"RuntimeDefault\"\n          }\n        },\n        \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n        \"volumeMounts\": [\n          {\n            \"mountPath\": \"/var/run/linkerd/identity/end-entity\",\n            \"name\": \"linkerd-identity-end-entity\"\n          },\n          {\n            \"mountPath\": \"/var/run/secrets/tokens\",\n            \"name\": \"linkerd-identity-token\"\n          }\n        ]\n      }\n  }\n]\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/pod.patch.json",
    "content": "[\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/linkerd.io~1proxy-version\",\n    \"value\": \"dev-undefined\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/linkerd.io~1trust-root-sha256\",\n    \"value\": \"5090806bcf2daff5d54739ba02a8e7b919f7e62b2a46757e11089c916ec97fc2\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1control-plane-ns\",\n    \"value\": \"linkerd\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1proxy-deployment\",\n    \"value\": \"owner-deployment\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/labels/linkerd.io~1workload-ns\",\n    \"value\": \"kube-public\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes\",\n    \"value\": []\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/initContainers\",\n    \"value\": []\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"emptyDir\": {},\n      \"name\": \"linkerd-proxy-init-xtables-lock\"\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/initContainers/-\",\n    \"value\": {\n      \"args\": [\n        \"--firewall-bin-path\",\n        \"iptables-nft\",\n        \"--firewall-save-bin-path\",\n        \"iptables-nft-save\",\n        \"--ipv6=false\",\n        \"--incoming-proxy-port\",\n        \"4143\",\n        \"--outgoing-proxy-port\",\n        \"4140\",\n        \"--proxy-uid\",\n        \"2102\",\n        \"--inbound-ports-to-ignore\",\n        \"4190,4191,4567,4568\",\n        \"--outbound-ports-to-ignore\",\n        \"4567,4568\"\n      ],\n      \"image\": \"cr.l5d.io/linkerd/proxy:dev-undefined\",\n      \"imagePullPolicy\": \"IfNotPresent\",\n      \"command\": [\n        \"/usr/lib/linkerd/linkerd2-proxy-init\"\n      ],\n      \"name\": \"linkerd-init\",\n      \"resources\": null,\n      \"securityContext\": {\n        \"allowPrivilegeEscalation\": false,\n        \"capabilities\": {\n          \"add\": [\n            \"NET_ADMIN\",\n            \"NET_RAW\"\n          ]\n        },\n        \"privileged\": false,\n        \"runAsNonRoot\": true,\n        \"runAsUser\": 65534,\n        \"runAsGroup\": 65534,\n        \"readOnlyRootFilesystem\": true,\n        \"seccompProfile\": {\n          \"type\": \"RuntimeDefault\"\n        }\n      },\n      \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n      \"volumeMounts\": [\n        {\n          \"mountPath\": \"/run\",\n          \"name\": \"linkerd-proxy-init-xtables-lock\"\n        }\n      ]\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"name\": \"linkerd-identity-end-entity\",\n      \"emptyDir\": {\n        \"medium\": \"Memory\"\n      }\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/volumes/-\",\n    \"value\": {\n      \"name\": \"linkerd-identity-token\",\n      \"projected\": {\n        \"sources\": [\n          {\n            \"serviceAccountToken\": {\n              \"audience\": \"identity.l5d.io\",\n              \"expirationSeconds\": 86400,\n              \"path\": \"linkerd-identity-token\"\n            }\n          }\n        ]\n      }\n    }\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/containers/0\",\n    \"value\": {\n      \"env\": [\n        {\n          \"name\": \"_pod_name\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"metadata.name\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_ns\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"metadata.namespace\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_uid\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"metadata.uid\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_ip\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"status.podIP\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_nodeName\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"spec.nodeName\"\n            }\n          }\n        },\n        {\n          \"name\": \"_pod_containerName\",\n          \"value\": \"linkerd-proxy\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CORES\",\n          \"value\": \"1\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CORES_MIN\",\n          \"value\": \"1\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED\",\n          \"value\": \"false\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_LOG\",\n          \"value\": \"warn,linkerd=info,hickory=error,[{headers}]=off,[{request}]=off\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_LOG_FORMAT\",\n          \"value\": \"plain\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_SVC_ADDR\",\n          \"value\": \"linkerd-dst-headless.linkerd.svc.cluster.local.:8086\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_PROFILE_NETWORKS\",\n          \"value\": \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_SVC_ADDR\",\n          \"value\": \"linkerd-policy.linkerd.svc.cluster.local.:8090\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_WORKLOAD\",\n          \"value\": \"{\\\"ns\\\":\\\"$(_pod_ns)\\\", \\\"pod\\\":\\\"$(_pod_name)\\\"}\\n\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\",\n          \"value\": \"all-unauthenticated\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_CLUSTER_NETWORKS\",\n          \"value\": \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_INITIAL_TIMEOUT\",\n          \"value\": \"3s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_IDLE_TIMEOUT\",\n          \"value\": \"5m\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_STREAM_LIFETIME\",\n          \"value\": \"1h\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\",\n          \"value\": \"100ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\",\n          \"value\": \"1000ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_DISCOVERY_IDLE_TIMEOUT\",\n          \"value\": \"5s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_DISCOVERY_IDLE_TIMEOUT\",\n          \"value\": \"90s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_CONTROL_LISTEN_ADDR\",\n          \"value\": \"0.0.0.0:4190\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_ADMIN_LISTEN_ADDR\",\n          \"value\": \"0.0.0.0:4191\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR\",\n          \"value\": \"127.0.0.1:4140\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\",\n          \"value\": \"127.0.0.1:4140\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_LISTEN_ADDR\",\n          \"value\": \"0.0.0.0:4143\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_IPS\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"status.podIPs\"\n            }\n          }\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_PORTS\",\n          \"value\": \"80,9090\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\",\n          \"value\": \"svc.cluster.local.\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_ACCEPT_KEEPALIVE\",\n          \"value\": \"10000ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE\",\n          \"value\": \"10000ms\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_ACCEPT_USER_TIMEOUT\",\n          \"value\": \"30s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_CONNECT_USER_TIMEOUT\",\n          \"value\": \"30s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_METRICS_HOSTNAME_LABELS\",\n          \"value\": \"false\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\",\n          \"value\": \"10s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\",\n          \"value\": \"3s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_INTERVAL\",\n          \"value\": \"10s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_OUTBOUND_SERVER_HTTP2_KEEP_ALIVE_TIMEOUT\",\n          \"value\": \"3s\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\",\n          \"value\": \"25,587,3306,4444,5432,6379,9300,11211\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_CONTEXT\",\n          \"value\": \"{\\\"ns\\\":\\\"$(_pod_ns)\\\", \\\"nodeName\\\":\\\"$(_pod_nodeName)\\\", \\\"pod\\\":\\\"$(_pod_name)\\\"}\\n\"\n        },\n        {\n          \"name\": \"_pod_sa\",\n          \"valueFrom\": {\n            \"fieldRef\": {\n              \"fieldPath\": \"spec.serviceAccountName\"\n            }\n          }\n        },\n        {\n          \"name\": \"_l5d_ns\",\n          \"value\": \"linkerd\"\n        },\n        {\n          \"name\": \"_l5d_trustdomain\",\n          \"value\": \"cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_DIR\",\n          \"value\": \"/var/run/linkerd/identity/end-entity\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\",\n          \"value\": \"IdentityTrustAnchorsPEM\\n\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_TOKEN_FILE\",\n          \"value\": \"/var/run/secrets/tokens/linkerd-identity-token\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_SVC_ADDR\",\n          \"value\": \"linkerd-identity-headless.linkerd.svc.cluster.local.:8080\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_LOCAL_NAME\",\n          \"value\": \"$(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_IDENTITY_SVC_NAME\",\n          \"value\": \"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_DESTINATION_SVC_NAME\",\n          \"value\": \"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n        },\n        {\n          \"name\": \"LINKERD2_PROXY_POLICY_SVC_NAME\",\n          \"value\": \"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"\n        }\n      ],\n      \"image\": \"cr.l5d.io/linkerd/proxy:dev-undefined\",\n      \"imagePullPolicy\": \"IfNotPresent\",\n      \"lifecycle\": {\n        \"postStart\": {\n          \"exec\": {\n            \"command\": [\n              \"/usr/lib/linkerd/linkerd-await\",\n              \"--timeout=2m\",\n              \"--port=4191\"\n            ]\n          }\n        }\n      },\n      \"livenessProbe\": {\n        \"httpGet\": {\n          \"path\": \"/live\",\n          \"port\": 4191\n        },\n        \"initialDelaySeconds\": 10,\n        \"timeoutSeconds\": 1\n      },\n      \"name\": \"linkerd-proxy\",\n      \"ports\": [\n        {\n          \"containerPort\": 4143,\n          \"name\": \"linkerd-proxy\"\n        },\n        {\n          \"containerPort\": 4191,\n          \"name\": \"linkerd-admin\"\n        }\n      ],\n      \"readinessProbe\": {\n        \"httpGet\": {\n          \"path\": \"/ready\",\n          \"port\": 4191\n        },\n        \"initialDelaySeconds\": 2,\n        \"timeoutSeconds\": 1\n      },\n      \"resources\": null,\n      \"securityContext\": {\n        \"allowPrivilegeEscalation\": false,\n        \"readOnlyRootFilesystem\": true,\n        \"runAsNonRoot\": true,\n        \"runAsUser\": 2102,\n        \"seccompProfile\": {\n          \"type\": \"RuntimeDefault\"\n        }\n      },\n      \"terminationMessagePolicy\": \"FallbackToLogsOnError\",\n      \"volumeMounts\": [\n        {\n          \"mountPath\": \"/var/run/linkerd/identity/end-entity\",\n          \"name\": \"linkerd-identity-end-entity\"\n        },\n        {\n          \"mountPath\": \"/var/run/secrets/tokens\",\n          \"name\": \"linkerd-identity-token\"\n        }\n      ]\n    }\n  }\n]\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/service-with-opaque-ports.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc\n  namespace: kube-public\n  annotations:\n    config.linkerd.io/opaque-ports: \"8080\"\nspec:\n  selector:\n    app: svc\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n"
  },
  {
    "path": "controller/proxy-injector/fake/data/service-without-opaque-ports.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc\n  namespace: kube-public\nspec:\n  selector:\n    app: svc\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n"
  },
  {
    "path": "controller/proxy-injector/fake/factory.go",
    "content": "package fake\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\tadmissionv1beta1 \"k8s.io/api/admission/v1beta1\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"sigs.k8s.io/yaml\"\n)\n\n// These constants provide default, fake strings for testing proxy-injector.\nconst (\n\tDefaultControllerNamespace = \"linkerd\"\n\tDefaultNamespace           = \"default\"\n)\n\n// Factory is a factory that can convert in-file YAML content into Kubernetes\n// API objects.\ntype Factory struct {\n\trootDir string\n}\n\n// NewFactory returns a new instance of Fixture.\nfunc NewFactory(rootDir string) *Factory {\n\treturn &Factory{rootDir: rootDir}\n}\n\n// FileContents returns the content of the specified file as a slice of\n// bytes. If the file doesn't exist in the 'fake/data' folder, an error will be\n// returned.\nfunc (f *Factory) FileContents(filename string) ([]byte, error) {\n\treturn os.ReadFile(filepath.Join(f.rootDir, filename))\n}\n\n// AdmissionReview returns the content of the specified file as an\n// AdmissionReview type. An error will be returned if:\n// i. the file doesn't exist in the 'fake/data' folder or,\n// ii. the file content isn't a valid YAML structure that can be unmarshalled\n// into AdmissionReview type\nfunc (f *Factory) AdmissionReview(filename string) (*admissionv1beta1.AdmissionReview, error) {\n\tb, err := os.ReadFile(filepath.Join(f.rootDir, filename))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar admissionReview admissionv1beta1.AdmissionReview\n\tif err := yaml.Unmarshal(b, &admissionReview); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &admissionReview, nil\n}\n\n// Deployment returns the content of the specified file as a Deployment type. An\n// error will be returned if:\n// i. the file doesn't exist in the 'fake/data' folder or\n// ii. the file content isn't a valid YAML structure that can be unmarshalled\n// into Deployment type\nfunc (f *Factory) Deployment(filename string) (*appsv1.Deployment, error) {\n\tb, err := os.ReadFile(filepath.Join(f.rootDir, filename))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar deployment appsv1.Deployment\n\tif err := yaml.Unmarshal(b, &deployment); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &deployment, nil\n}\n\n// Container returns the content of the specified file as a Container type. An\n// error will be returned if:\n// i. the file doesn't exist in the 'fake/data' folder or\n// ii. the file content isn't a valid YAML structure that can be unmarshalled\n// into Container type\nfunc (f *Factory) Container(filename string) (*corev1.Container, error) {\n\tb, err := os.ReadFile(filepath.Join(f.rootDir, filename))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar container corev1.Container\n\tif err := yaml.Unmarshal(b, &container); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &container, nil\n}\n\n// ConfigMap returns the content of the specified file as a ConfigMap type. An\n// error will be returned if:\n// i. the file doesn't exist in the 'fake/data' folder or\n// ii. the file content isn't a valid YAML structure that can be unmarshalled\n// into ConfigMap type\nfunc (f *Factory) ConfigMap(filename string) (*corev1.ConfigMap, error) {\n\tb, err := os.ReadFile(filepath.Join(f.rootDir, filename))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar configMap corev1.ConfigMap\n\tif err := yaml.Unmarshal(b, &configMap); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &configMap, nil\n}\n\n// Namespace returns the content of the specified file as a Namespace type. An\n// error will be returned if:\n// i. the file doesn't exist in the 'fake/data' folder or\n// ii. the file content isn't a valid YAML structure that can be unmarshalled\n// into Namespace type\nfunc (f *Factory) Namespace(filename string) (*corev1.Namespace, error) {\n\tb, err := os.ReadFile(filepath.Join(f.rootDir, filename))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar namespace corev1.Namespace\n\tif err := yaml.Unmarshal(b, &namespace); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &namespace, nil\n}\n\n// Volume returns the content of the specified file as a Volume type. An error\n// will be returned if:\n// i. the file doesn't exist in the 'fake/data' folder or\n// ii. the file content isn't a valid YAML structure that can be unmarshalled\n// into Volume type\nfunc (f *Factory) Volume(filename string) (*corev1.Volume, error) {\n\tb, err := os.ReadFile(filepath.Join(f.rootDir, filename))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar volume corev1.Volume\n\tif err := yaml.Unmarshal(b, &volume); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &volume, nil\n}\n"
  },
  {
    "path": "controller/proxy-injector/metrics.go",
    "content": "package injector\n\nimport (\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/pkg/inject\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n)\n\nconst (\n\tlabelOwnerKind    = \"owner_kind\"\n\tlabelNamespace    = \"namespace\"\n\tlabelSkip         = \"skip\"\n\tlabelAnnotationAt = \"annotation_at\"\n\tlabelReason       = \"skip_reason\"\n)\n\nvar (\n\trequestLabels  = []string{labelOwnerKind, labelNamespace, labelAnnotationAt}\n\tresponseLabels = []string{labelOwnerKind, labelNamespace, labelSkip, labelReason, labelAnnotationAt}\n\n\tproxyInjectionAdmissionRequests = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tName: \"proxy_inject_admission_requests_total\",\n\t\tHelp: \"A counter for number of admission requests to proxy injector.\",\n\t}, append(requestLabels, validLabelNames(inject.ProxyAnnotations)...))\n\n\tproxyInjectionAdmissionResponses = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tName: \"proxy_inject_admission_responses_total\",\n\t\tHelp: \"A counter for number of admission responses from proxy injector.\",\n\t}, append(responseLabels, validLabelNames(inject.ProxyAnnotations)...))\n)\n\nfunc admissionRequestLabels(ownerKind, namespace, annotationAt string, configLabels prometheus.Labels) prometheus.Labels {\n\tconfigLabels[labelOwnerKind] = ownerKind\n\tconfigLabels[labelNamespace] = namespace\n\tconfigLabels[labelAnnotationAt] = annotationAt\n\treturn configLabels\n}\n\nfunc admissionResponseLabels(owner, namespace, skip, reason, annotationAt string, configLabels prometheus.Labels) prometheus.Labels {\n\tconfigLabels[labelOwnerKind] = owner\n\tconfigLabels[labelNamespace] = namespace\n\tconfigLabels[labelSkip] = skip\n\tconfigLabels[labelReason] = reason\n\tconfigLabels[labelAnnotationAt] = annotationAt\n\treturn configLabels\n}\n\nfunc configToPrometheusLabels(conf *inject.ResourceConfig) prometheus.Labels {\n\tlabels := conf.GetOverriddenConfiguration()\n\tpromLabels := map[string]string{}\n\n\tfor label, value := range labels {\n\t\tpromLabels[validProxyConfigurationLabel(label)] = value\n\n\t}\n\treturn promLabels\n}\n\nfunc validLabelNames(labels []string) []string {\n\tvar validLabels []string\n\n\tfor _, label := range labels {\n\t\tvalidLabels = append(validLabels, validProxyConfigurationLabel(label))\n\t}\n\treturn validLabels\n}\n\nfunc validProxyConfigurationLabel(label string) string {\n\treturn strings.ReplaceAll(label[len(k8s.ProxyConfigAnnotationsPrefix)+1:], \"-\", \"_\")\n}\n"
  },
  {
    "path": "controller/proxy-injector/webhook.go",
    "content": "package injector\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/controller/webhook\"\n\t\"github.com/linkerd/linkerd2/pkg/config\"\n\t\"github.com/linkerd/linkerd2/pkg/inject\"\n\tpkgK8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\tlog \"github.com/sirupsen/logrus\"\n\tadmissionv1beta1 \"k8s.io/api/admission/v1beta1\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/client-go/tools/record\"\n)\n\nconst (\n\teventTypeSkipped  = \"InjectionSkipped\"\n\teventTypeInjected = \"Injected\"\n)\n\n// Inject returns the function that produces an AdmissionResponse containing\n// the patch, if any, to apply to the pod (proxy sidecar and eventually the\n// init container to set it up)\nfunc Inject(linkerdNamespace string, overrider inject.ValueOverrider) webhook.Handler {\n\treturn func(\n\t\tctx context.Context,\n\t\tapi *k8s.MetadataAPI,\n\t\trequest *admissionv1beta1.AdmissionRequest,\n\t\trecorder record.EventRecorder,\n\t) (*admissionv1beta1.AdmissionResponse, error) {\n\t\tlog.Debugf(\"request object bytes: %s\", request.Object.Raw)\n\n\t\t// Build the resource config based off the request metadata and kind of\n\t\t// object. This is later used to build the injection report and generated\n\t\t// patch.\n\t\tvaluesConfig, err := config.Values(pkgK8s.MountPathValuesConfig)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcaPEM, err := os.ReadFile(pkgK8s.MountPathTrustRootsPEM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvaluesConfig.IdentityTrustAnchorsPEM = string(caPEM)\n\n\t\tns, err := api.Get(k8s.NS, request.Namespace)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresourceConfig := inject.NewResourceConfig(valuesConfig, inject.OriginWebhook, linkerdNamespace).\n\t\t\tWithOwnerRetriever(ownerRetriever(ctx, api, request.Namespace)).\n\t\t\tWithNsAnnotations(ns.GetAnnotations()).\n\t\t\tWithKind(request.Kind.Kind)\n\n\t\t// Build the injection report.\n\t\treport, err := resourceConfig.ParseMetaAndYAML(request.Object.Raw)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tlog.Infof(\"received %s\", report.ResName())\n\n\t\t// If the resource has an owner, then it should be retrieved for recording\n\t\t// events.\n\t\tvar parent *metav1.PartialObjectMetadata\n\t\tvar ownerKind string\n\t\tif ownerRef := resourceConfig.GetOwnerRef(); ownerRef != nil {\n\t\t\tres, err := k8s.GetAPIResource(ownerRef.Kind)\n\t\t\tif err != nil {\n\t\t\t\tlog.Tracef(\"skipping event for parent %s: %s\", ownerRef.Kind, err)\n\t\t\t} else {\n\t\t\t\tobjs, err := api.GetByNamespaceFiltered(res, request.Namespace, ownerRef.Name, labels.Everything())\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Warnf(\"couldn't retrieve parent object %s-%s-%s; error: %s\", request.Namespace, ownerRef.Kind, ownerRef.Name, err)\n\t\t\t\t} else if len(objs) == 0 {\n\t\t\t\t\tlog.Warnf(\"couldn't retrieve parent object %s-%s-%s\", request.Namespace, ownerRef.Kind, ownerRef.Name)\n\t\t\t\t} else {\n\t\t\t\t\tparent = objs[0]\n\t\t\t\t}\n\t\t\t\townerKind = strings.ToLower(ownerRef.Kind)\n\t\t\t}\n\t\t}\n\n\t\tconfigLabels := configToPrometheusLabels(resourceConfig)\n\n\t\tcounter, err := proxyInjectionAdmissionRequests.GetMetricWith(admissionRequestLabels(ownerKind, request.Namespace, report.InjectAnnotationAt, configLabels))\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"failed to get proxy_inject_admission_requests metric: %q\", err)\n\t\t} else {\n\t\t\tcounter.Inc()\n\t\t}\n\n\t\t// If the resource is injectable then admit it after creating a patch that\n\t\t// adds the proxy-init and proxy containers.\n\t\tinjectable, reasons := report.Injectable()\n\t\tif injectable {\n\t\t\tresourceConfig.AppendPodAnnotation(pkgK8s.CreatedByAnnotation, fmt.Sprintf(\"linkerd/proxy-injector %s\", version.Version))\n\n\t\t\t// If namespace has annotations that do not exist on pod then copy them\n\t\t\t// over to pod's template.\n\t\t\tinject.AppendNamespaceAnnotations(resourceConfig.GetOverrideAnnotations(), resourceConfig.GetNsAnnotations(), resourceConfig.GetWorkloadAnnotations())\n\n\t\t\t// If the pod did not inherit the opaque ports annotation from the\n\t\t\t// namespace, then add the default value from the config values. This\n\t\t\t// ensures that the generated patch always sets the opaque ports\n\t\t\t// annotation.\n\t\t\tif !resourceConfig.HasWorkloadAnnotation(pkgK8s.ProxyOpaquePortsAnnotation) {\n\t\t\t\tdefaultPorts := strings.Split(resourceConfig.GetValues().Proxy.OpaquePorts, \",\")\n\t\t\t\tfilteredPorts := resourceConfig.FilterPodOpaquePorts(defaultPorts)\n\t\t\t\t// Only add the annotation if there are ports that the pod exposes\n\t\t\t\t// that are in the default opaque ports list.\n\t\t\t\tif len(filteredPorts) != 0 {\n\t\t\t\t\tports := strings.Join(filteredPorts, \",\")\n\t\t\t\t\tresourceConfig.AppendPodAnnotation(pkgK8s.ProxyOpaquePortsAnnotation, ports)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpatchJSON, err := resourceConfig.GetPodPatch(true, overrider)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif parent != nil {\n\t\t\t\trecorder.Event(parent, v1.EventTypeNormal, eventTypeInjected, \"Linkerd sidecar proxy injected\")\n\t\t\t}\n\t\t\tlog.Infof(\"injection patch generated for: %s\", report.ResName())\n\t\t\tlog.Debugf(\"injection patch: %s\", patchJSON)\n\n\t\t\tcounter, err := proxyInjectionAdmissionResponses.GetMetricWith(admissionResponseLabels(ownerKind, request.Namespace, \"false\", \"\", report.InjectAnnotationAt, configLabels))\n\t\t\tif err != nil {\n\t\t\t\tlog.Errorf(\"failed to get proxy_inject_admission_responses metric: %q\", err)\n\t\t\t} else {\n\t\t\t\tcounter.Inc()\n\t\t\t}\n\n\t\t\tpatchType := admissionv1beta1.PatchTypeJSONPatch\n\t\t\treturn &admissionv1beta1.AdmissionResponse{\n\t\t\t\tUID:       request.UID,\n\t\t\t\tAllowed:   true,\n\t\t\t\tPatchType: &patchType,\n\t\t\t\tPatch:     patchJSON,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Resource could not be injected with the sidecar, format the reason\n\t\t// for injection being skipped to emit an event\n\t\treadableReasons := make([]string, 0, len(reasons))\n\t\tfor _, reason := range reasons {\n\t\t\treadableReasons = append(readableReasons, inject.Reasons[reason])\n\t\t}\n\t\treadableMsg := strings.Join(readableReasons, \", \")\n\n\t\tif parent != nil {\n\t\t\trecorder.Eventf(parent, v1.EventTypeNormal, eventTypeSkipped, \"Linkerd sidecar proxy injection skipped: %s\", readableMsg)\n\t\t}\n\n\t\t// Create a patch which adds the opaque ports annotation if the workload\n\t\t// doesn't already have it set.\n\t\tpatchJSON, err := resourceConfig.CreateOpaquePortsPatch()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// If resource needs to be patched with annotations (e.g opaque\n\t\t// ports), then admit the request with the relevant patch\n\t\tif len(patchJSON) != 0 {\n\t\t\tlog.Infof(\"annotation patch generated for: %s\", report.ResName())\n\t\t\tlog.Debugf(\"annotation patch: %s\", patchJSON)\n\n\t\t\tcounter, err := proxyInjectionAdmissionResponses.GetMetricWith(admissionResponseLabels(ownerKind, request.Namespace, \"false\", \"\", report.InjectAnnotationAt, configLabels))\n\t\t\tif err != nil {\n\t\t\t\tlog.Errorf(\"failed to get proxy_inject_admission_responses metric: %q\", err)\n\t\t\t} else {\n\t\t\t\tcounter.Inc()\n\t\t\t}\n\n\t\t\tpatchType := admissionv1beta1.PatchTypeJSONPatch\n\t\t\treturn &admissionv1beta1.AdmissionResponse{\n\t\t\t\tUID:       request.UID,\n\t\t\t\tAllowed:   true,\n\t\t\t\tPatchType: &patchType,\n\t\t\t\tPatch:     patchJSON,\n\t\t\t}, nil\n\t\t}\n\n\t\t// If the resource is a pod, and no annotation patch has\n\t\t// been generated, record in the metrics (and log) that it has been\n\t\t// entirely skipped and admit without any mutations\n\t\tif resourceConfig.IsPod() {\n\t\t\tlog.Infof(\"skipped %s: %s\", report.ResName(), readableMsg)\n\n\t\t\tcounter, err := proxyInjectionAdmissionResponses.GetMetricWith(admissionResponseLabels(ownerKind, request.Namespace, \"true\", strings.Join(reasons, \",\"), report.InjectAnnotationAt, configLabels))\n\t\t\tif err != nil {\n\t\t\t\tlog.Errorf(\"failed to get proxy_inject_admission_responses metric: %q\", err)\n\t\t\t} else {\n\t\t\t\tcounter.Inc()\n\t\t\t}\n\n\t\t\treturn &admissionv1beta1.AdmissionResponse{\n\t\t\t\tUID:     request.UID,\n\t\t\t\tAllowed: true,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &admissionv1beta1.AdmissionResponse{\n\t\t\tUID:     request.UID,\n\t\t\tAllowed: true,\n\t\t}, nil\n\t}\n}\n\nfunc ownerRetriever(ctx context.Context, api *k8s.MetadataAPI, ns string) inject.OwnerRetrieverFunc {\n\treturn func(p *v1.Pod) (string, string, error) {\n\t\tp.SetNamespace(ns)\n\t\treturn api.GetOwnerKindAndName(ctx, p, true)\n\t}\n}\n"
  },
  {
    "path": "controller/proxy-injector/webhook_test.go",
    "content": "package injector\n\nimport (\n\t\"encoding/json\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/controller/proxy-injector/fake\"\n\t\"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/linkerd/linkerd2/pkg/inject\"\n\tpkgK8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\tadmissionv1beta1 \"k8s.io/api/admission/v1beta1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n)\n\ntype unmarshalledPatch []map[string]interface{}\n\nvar (\n\tvalues, _ = linkerd2.NewValues()\n)\n\nfunc confNsEnabled() *inject.ResourceConfig {\n\treturn inject.\n\t\tNewResourceConfig(values, inject.OriginWebhook, \"linkerd\").\n\t\tWithNsAnnotations(map[string]string{\n\t\t\tpkgK8s.ProxyInjectAnnotation: pkgK8s.ProxyInjectEnabled,\n\t\t})\n}\n\nfunc confNsDisabled() *inject.ResourceConfig {\n\treturn inject.NewResourceConfig(values, inject.OriginWebhook, \"linkerd\").\n\t\tWithNsAnnotations(map[string]string{})\n}\n\nfunc confNsWithOpaquePorts() *inject.ResourceConfig {\n\treturn inject.\n\t\tNewResourceConfig(values, inject.OriginWebhook, \"linkerd\").\n\t\tWithNsAnnotations(map[string]string{\n\t\t\tpkgK8s.ProxyInjectAnnotation:      pkgK8s.ProxyInjectEnabled,\n\t\t\tpkgK8s.ProxyOpaquePortsAnnotation: \"3306\",\n\t\t})\n}\n\nfunc confNsWithoutOpaquePorts() *inject.ResourceConfig {\n\treturn inject.\n\t\tNewResourceConfig(values, inject.OriginWebhook, \"linkerd\").\n\t\tWithNsAnnotations(map[string]string{\n\t\t\tpkgK8s.ProxyInjectAnnotation: pkgK8s.ProxyInjectEnabled,\n\t\t})\n}\n\nfunc confNsWithConfigAnnotations() *inject.ResourceConfig {\n\treturn inject.\n\t\tNewResourceConfig(values, inject.OriginWebhook, \"linkerd\").\n\t\tWithNsAnnotations(map[string]string{\n\t\t\tpkgK8s.ProxyInjectAnnotation:                pkgK8s.ProxyInjectEnabled,\n\t\t\tpkgK8s.ProxyIgnoreOutboundPortsAnnotation:   \"34567\",\n\t\t\tpkgK8s.ProxyWaitBeforeExitSecondsAnnotation: \"300\",\n\t\t\t\"config.linkerd.io/invalid-key\":             \"invalid-value\",\n\t\t})\n}\nfunc TestGetPodPatch(t *testing.T) {\n\n\tvalues.IdentityTrustAnchorsPEM = \"IdentityTrustAnchorsPEM\"\n\n\tfactory := fake.NewFactory(filepath.Join(\"fake\", \"data\"))\n\tnsEnabled, err := factory.Namespace(\"namespace-inject-enabled.yaml\")\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\n\tnsDisabled, err := factory.Namespace(\"namespace-inject-disabled.yaml\")\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\n\tt.Run(\"by checking annotations\", func(t *testing.T) {\n\t\tvar testCases = []struct {\n\t\t\tfilename string\n\t\t\tns       *corev1.Namespace\n\t\t\tconf     *inject.ResourceConfig\n\t\t}{\n\t\t\t{\n\t\t\t\tfilename: \"pod-inject-empty.yaml\",\n\t\t\t\tns:       nsEnabled,\n\t\t\t\tconf:     confNsEnabled(),\n\t\t\t},\n\t\t\t{\n\t\t\t\tfilename: \"pod-inject-enabled.yaml\",\n\t\t\t\tns:       nsEnabled,\n\t\t\t\tconf:     confNsEnabled(),\n\t\t\t},\n\t\t\t{\n\t\t\t\tfilename: \"pod-inject-enabled.yaml\",\n\t\t\t\tns:       nsDisabled,\n\t\t\t\tconf:     confNsDisabled(),\n\t\t\t},\n\t\t\t{\n\t\t\t\tfilename: \"pod-with-debug-disabled.yaml\",\n\t\t\t\tns:       nsDisabled,\n\t\t\t\tconf:     confNsDisabled(),\n\t\t\t},\n\t\t}\n\n\t\t_, expectedPatch := loadPatch(factory, t, \"pod.patch.json\")\n\t\tfor _, testCase := range testCases {\n\t\t\ttestCase := testCase // pin\n\t\t\tt.Run(testCase.filename, func(t *testing.T) {\n\t\t\t\tpod := fileContents(factory, t, testCase.filename)\n\t\t\t\tfakeReq := getFakePodReq(pod)\n\t\t\t\tfullConf := testCase.conf.\n\t\t\t\t\tWithKind(fakeReq.Kind.Kind).\n\t\t\t\t\tWithOwnerRetriever(ownerRetrieverFake)\n\t\t\t\t_, err = fullConf.ParseMetaAndYAML(fakeReq.Object.Raw)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tpatchJSON, err := fullConf.GetPodPatch(true, inject.GetOverriddenValues)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Unexpected PatchForAdmissionRequest error: %s\", err)\n\t\t\t\t}\n\t\t\t\tactualPatch := unmarshalPatch(t, patchJSON)\n\t\t\t\tif diff := deep.Equal(expectedPatch, actualPatch); diff != nil {\n\t\t\t\t\tt.Fatalf(\"The actual patch didn't match what was expected.\\n%+v\", diff)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"by checking annotations with debug\", func(t *testing.T) {\n\t\t_, expectedPatch := loadPatch(factory, t, \"pod-with-debug.patch.json\")\n\n\t\tpod := fileContents(factory, t, \"pod-with-debug-enabled.yaml\")\n\t\tfakeReq := getFakePodReq(pod)\n\t\tconf := confNsEnabled().WithKind(fakeReq.Kind.Kind).WithOwnerRetriever(ownerRetrieverFake)\n\t\t_, err = conf.ParseMetaAndYAML(fakeReq.Object.Raw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tpatchJSON, err := conf.GetPodPatch(true, inject.GetOverriddenValues)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected PatchForAdmissionRequest error: %s\", err)\n\t\t}\n\t\tactualPatch := unmarshalPatch(t, patchJSON)\n\t\tif diff := deep.Equal(expectedPatch, actualPatch); diff != nil {\n\t\t\tt.Fatalf(\"The actual patch didn't match what was expected.\\n%+v\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"by checking annotations with custom debug image version\", func(t *testing.T) {\n\t\t_, expectedPatch := loadPatch(factory, t, \"pod-with-custom-debug.patch.json\")\n\n\t\tpod := fileContents(factory, t, \"pod-with-custom-debug-tag.yaml\")\n\t\tfakeReq := getFakePodReq(pod)\n\t\tconf := confNsEnabled().WithKind(fakeReq.Kind.Kind).WithOwnerRetriever(ownerRetrieverFake)\n\t\t_, err = conf.ParseMetaAndYAML(fakeReq.Object.Raw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tpatchJSON, err := conf.GetPodPatch(true, inject.GetOverriddenValues)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected PatchForAdmissionRequest error: %s\", err)\n\t\t}\n\t\tactualPatch := unmarshalPatch(t, patchJSON)\n\t\tif diff := deep.Equal(expectedPatch, actualPatch); diff != nil {\n\t\t\tt.Fatalf(\"The actual patch didn't match what was expected.\\n%+v\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"by configuring log level\", func(t *testing.T) {\n\t\t_, expectedPatch := loadPatch(factory, t, \"pod-log-level.json\")\n\n\t\tpod := fileContents(factory, t, \"pod-inject-enabled-log-level.yaml\")\n\t\tfakeReq := getFakePodReq(pod)\n\t\tconf := confNsWithoutOpaquePorts().\n\t\t\tWithKind(fakeReq.Kind.Kind).\n\t\t\tWithOwnerRetriever(ownerRetrieverFake)\n\t\t_, err = conf.ParseMetaAndYAML(fakeReq.Object.Raw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tpatchJSON, err := conf.GetPodPatch(true, inject.GetOverriddenValues)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected PatchForAdmissionRequest error: %s\", err)\n\t\t}\n\n\t\tactualPatch := unmarshalPatch(t, patchJSON)\n\t\tif diff := deep.Equal(expectedPatch, actualPatch); diff != nil {\n\t\t\tt.Fatalf(\"The actual patch didn't match what was expected.\\n%+v\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"by configuring cpu limit by ratio\", func(t *testing.T) {\n\t\t_, expectedPatch := loadPatch(factory, t, \"pod-cpu-ratio.json\")\n\n\t\tpod := fileContents(factory, t, \"pod-inject-enabled-cpu-ratio.yaml\")\n\t\tfakeReq := getFakePodReq(pod)\n\t\tconf := confNsWithoutOpaquePorts().\n\t\t\tWithKind(fakeReq.Kind.Kind).\n\t\t\tWithOwnerRetriever(ownerRetrieverFake)\n\t\t_, err = conf.ParseMetaAndYAML(fakeReq.Object.Raw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tpatchJSON, err := conf.GetPodPatch(true, inject.GetOverriddenValues)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected PatchForAdmissionRequest error: %s\", err)\n\t\t}\n\n\t\tactualPatch := unmarshalPatch(t, patchJSON)\n\t\tif diff := deep.Equal(expectedPatch, actualPatch); diff != nil {\n\t\t\texpectedJSON, _ := json.MarshalIndent(expectedPatch, \"\", \"  \")\n\t\t\tactualJSON, _ := json.MarshalIndent(actualPatch, \"\", \"  \")\n\t\t\tt.Fatalf(\"Expected:\\n%s\\n\\nActual:\\n%s\\n\\nDiff:%+v\",\n\t\t\t\tstring(expectedJSON), string(actualJSON), diff)\n\t\t}\n\t})\n\n\tt.Run(\"by checking pod inherits config annotations from namespace\", func(t *testing.T) {\n\t\t_, expectedPatch := loadPatch(factory, t, \"pod-with-ns-annotations.patch.json\")\n\n\t\tpod := fileContents(factory, t, \"pod-inject-enabled.yaml\")\n\t\tfakeReq := getFakePodReq(pod)\n\t\tconf := confNsWithConfigAnnotations().\n\t\t\tWithKind(fakeReq.Kind.Kind).\n\t\t\tWithOwnerRetriever(ownerRetrieverFake)\n\t\t_, err = conf.ParseMetaAndYAML(fakeReq.Object.Raw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// The namespace has two config annotations: one valid and one invalid\n\t\t// the pod patch should only contain the valid annotation.\n\t\tinject.AppendNamespaceAnnotations(conf.GetOverrideAnnotations(), conf.GetNsAnnotations(), conf.GetWorkloadAnnotations())\n\t\tpatchJSON, err := conf.GetPodPatch(true, inject.GetOverriddenValues)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected PatchForAdmissionRequest error: %s\", err)\n\t\t}\n\t\tactualPatch := unmarshalPatch(t, patchJSON)\n\t\tif diff := deep.Equal(expectedPatch, actualPatch); diff != nil {\n\t\t\tt.Fatalf(\"The actual patch didn't match what was expected.\\n+%v\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"by checking container spec\", func(t *testing.T) {\n\t\tdeployment := fileContents(factory, t, \"deployment-with-injected-proxy.yaml\")\n\t\tfakeReq := getFakePodReq(deployment)\n\t\tconf := confNsDisabled().WithKind(fakeReq.Kind.Kind)\n\t\tpatchJSON, err := conf.GetPodPatch(true, inject.GetOverriddenValues)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected PatchForAdmissionRequest error: %s\", err)\n\t\t}\n\n\t\tif len(patchJSON) == 0 {\n\t\t\tt.Errorf(\"Expected empty patch\")\n\t\t}\n\t})\n\n\tt.Run(\"by injecting pod already having a native sidecar container\", func(t *testing.T) {\n\t\tpod := fileContents(factory, t, \"pod-nativesidecar-inject-enabled.yaml\")\n\t\tfakeReq := getFakePodReq(pod)\n\t\tfullConf := confNsDisabled().\n\t\t\tWithKind(fakeReq.Kind.Kind).\n\t\t\tWithOwnerRetriever(ownerRetrieverFake)\n\t\t_, err = fullConf.ParseMetaAndYAML(fakeReq.Object.Raw)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tpatchJSON, err := fullConf.GetPodPatch(true, inject.GetOverriddenValues)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected PatchForAdmissionRequest error: %s\", err)\n\t\t}\n\t\tactualPatch := unmarshalPatch(t, patchJSON)\n\t\t_, expectedPatch := loadPatch(factory, t, \"pod.nativesidecar.patch.json\")\n\t\tif diff := deep.Equal(expectedPatch, actualPatch); diff != nil {\n\t\t\tt.Fatalf(\"The actual patch didn't match what was expected.\\n%+v\", diff)\n\t\t}\n\t})\n}\n\nfunc TestGetAnnotationPatch(t *testing.T) {\n\tfactory := fake.NewFactory(filepath.Join(\"fake\", \"data\"))\n\tnsWithOpaquePorts, err := factory.Namespace(\"namespace-with-opaque-ports.yaml\")\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\tnsWithoutOpaquePorts, err := factory.Namespace(\"namespace-inject-enabled.yaml\")\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\tt.Run(\"by checking patch annotations\", func(t *testing.T) {\n\t\tservicePatchBytes, servicePatch := loadPatch(factory, t, \"annotation.patch.json\")\n\t\tpodPatchBytes, podPatch := loadPatch(factory, t, \"annotation.patch.json\")\n\t\tfilteredServiceBytes, filteredServicePatch := loadPatch(factory, t, \"filtered-service-opaque-ports.json\")\n\t\tfilteredPodBytes, filteredPodPatch := loadPatch(factory, t, \"filtered-pod-opaque-ports.json\")\n\t\tvar testCases = []struct {\n\t\t\tname               string\n\t\t\tfilename           string\n\t\t\tns                 *corev1.Namespace\n\t\t\tconf               *inject.ResourceConfig\n\t\t\texpectedPatchBytes []byte\n\t\t\texpectedPatch      unmarshalledPatch\n\t\t}{\n\t\t\t{\n\t\t\t\tname:               \"service without opaque ports and namespace with\",\n\t\t\t\tfilename:           \"service-without-opaque-ports.yaml\",\n\t\t\t\tns:                 nsWithOpaquePorts,\n\t\t\t\tconf:               confNsWithOpaquePorts(),\n\t\t\t\texpectedPatchBytes: servicePatchBytes,\n\t\t\t\texpectedPatch:      servicePatch,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"service with opaque ports and namespace with\",\n\t\t\t\tfilename: \"service-with-opaque-ports.yaml\",\n\t\t\t\tns:       nsWithOpaquePorts,\n\t\t\t\tconf:     confNsWithOpaquePorts(),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"service with opaque ports and namespace without\",\n\t\t\t\tfilename: \"service-with-opaque-ports.yaml\",\n\t\t\t\tns:       nsWithoutOpaquePorts,\n\t\t\t\tconf:     confNsWithoutOpaquePorts(),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"service without opaque ports and namespace without\",\n\t\t\t\tfilename: \"service-without-opaque-ports.yaml\",\n\t\t\t\tns:       nsWithoutOpaquePorts,\n\t\t\t\tconf:     confNsWithoutOpaquePorts(),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:               \"pod without opaque ports and namespace with\",\n\t\t\t\tfilename:           \"pod-without-opaque-ports.yaml\",\n\t\t\t\tns:                 nsWithOpaquePorts,\n\t\t\t\tconf:               confNsWithOpaquePorts(),\n\t\t\t\texpectedPatchBytes: podPatchBytes,\n\t\t\t\texpectedPatch:      podPatch,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"pod with opaque ports and namespace with\",\n\t\t\t\tfilename: \"pod-with-opaque-ports.yaml\",\n\t\t\t\tns:       nsWithOpaquePorts,\n\t\t\t\tconf:     confNsWithOpaquePorts(),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"pod with opaque ports and namespace without\",\n\t\t\t\tfilename: \"pod-with-opaque-ports.yaml\",\n\t\t\t\tns:       nsWithoutOpaquePorts,\n\t\t\t\tconf:     confNsWithoutOpaquePorts(),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:     \"pod without opaque ports and namespace without\",\n\t\t\t\tfilename: \"pod-without-opaque-ports.yaml\",\n\t\t\t\tns:       nsWithoutOpaquePorts,\n\t\t\t\tconf:     confNsWithoutOpaquePorts(),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:               \"service opaque ports are filtered\",\n\t\t\t\tfilename:           \"filter-service-opaque-ports.yaml\",\n\t\t\t\tns:                 nsWithoutOpaquePorts,\n\t\t\t\tconf:               confNsWithoutOpaquePorts(),\n\t\t\t\texpectedPatchBytes: filteredServiceBytes,\n\t\t\t\texpectedPatch:      filteredServicePatch,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:               \"pod opaque ports are filtered\",\n\t\t\t\tfilename:           \"filter-pod-opaque-ports.yaml\",\n\t\t\t\tns:                 nsWithoutOpaquePorts,\n\t\t\t\tconf:               confNsWithoutOpaquePorts(),\n\t\t\t\texpectedPatchBytes: filteredPodBytes,\n\t\t\t\texpectedPatch:      filteredPodPatch,\n\t\t\t},\n\t\t}\n\t\tfor _, testCase := range testCases {\n\t\t\ttestCase := testCase // pin\n\t\t\tt.Run(testCase.name, func(t *testing.T) {\n\t\t\t\tservice := fileContents(factory, t, testCase.filename)\n\t\t\t\tfakeReq := getFakeServiceReq(service)\n\t\t\t\tfullConf := testCase.conf.\n\t\t\t\t\tWithKind(fakeReq.Kind.Kind).\n\t\t\t\t\tWithOwnerRetriever(ownerRetrieverFake)\n\t\t\t\t_, err = fullConf.ParseMetaAndYAML(fakeReq.Object.Raw)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\tpatchJSON, err := fullConf.CreateOpaquePortsPatch()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Unexpected error creating default opaque ports patch: %s\", err)\n\t\t\t\t}\n\t\t\t\tif len(testCase.expectedPatchBytes) != 0 && len(patchJSON) == 0 {\n\t\t\t\t\tt.Fatalf(\"There was no patch, but one was expected: %s\", testCase.expectedPatchBytes)\n\t\t\t\t} else if len(testCase.expectedPatchBytes) == 0 && len(patchJSON) != 0 {\n\t\t\t\t\tt.Fatalf(\"No patch was expected, but one was returned: %s\", patchJSON)\n\t\t\t\t}\n\t\t\t\tif len(testCase.expectedPatchBytes) == 0 {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tactualPatch := unmarshalPatch(t, patchJSON)\n\t\t\t\tif diff := deep.Equal(testCase.expectedPatch, actualPatch); diff != nil {\n\t\t\t\t\tt.Fatalf(\"The actual patch didn't match what was expected.\\n%+v\", diff)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc getFakePodReq(b []byte) *admissionv1beta1.AdmissionRequest {\n\treturn &admissionv1beta1.AdmissionRequest{\n\t\tKind:      metav1.GroupVersionKind{Kind: \"Pod\"},\n\t\tName:      \"foobar\",\n\t\tNamespace: \"linkerd\",\n\t\tObject:    runtime.RawExtension{Raw: b},\n\t}\n}\n\nfunc getFakeServiceReq(b []byte) *admissionv1beta1.AdmissionRequest {\n\treturn &admissionv1beta1.AdmissionRequest{\n\t\tKind:      metav1.GroupVersionKind{Kind: \"Service\"},\n\t\tName:      \"foobar\",\n\t\tNamespace: \"linkerd\",\n\t\tObject:    runtime.RawExtension{Raw: b},\n\t}\n}\n\nfunc ownerRetrieverFake(p *corev1.Pod) (string, string, error) {\n\treturn pkgK8s.Deployment, \"owner-deployment\", nil\n}\n\nfunc loadPatch(factory *fake.Factory, t *testing.T, name string) ([]byte, unmarshalledPatch) {\n\tt.Helper()\n\tbytes := fileContents(factory, t, name)\n\tpatch := unmarshalPatch(t, bytes)\n\treturn bytes, patch\n}\n\nfunc fileContents(factory *fake.Factory, t *testing.T, name string) []byte {\n\tt.Helper()\n\tb, err := factory.FileContents(name)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\treturn b\n}\n\nfunc unmarshalPatch(t *testing.T, patchJSON []byte) unmarshalledPatch {\n\tt.Helper()\n\tvar actualPatch unmarshalledPatch\n\tif err := json.Unmarshal(patchJSON, &actualPatch); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\treturn actualPatch\n}\n"
  },
  {
    "path": "controller/script/destination-client/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"flag\"\n\t\"io\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/destination\"\n\t\"github.com/linkerd/linkerd2/controller/api/destination\"\n\taddrUtil \"github.com/linkerd/linkerd2/pkg/addr\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\n// This is a throwaway script for testing the destination service\n\nfunc main() {\n\taddr := flag.String(\"addr\", \":8086\", \"address of destination service\")\n\tpath := flag.String(\"path\", \"strest-server.default.svc.cluster.local:8888\", \"destination path\")\n\tmethod := flag.String(\"method\", \"get\", \"which gRPC method to invoke\")\n\ttoken := flag.String(\"token\", \"\", \"context token\")\n\tflag.Parse()\n\n\tclient, conn, err := destination.NewClient(*addr)\n\tif err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n\tdefer conn.Close()\n\n\treq := &pb.GetDestination{\n\t\tScheme:       \"k8s\",\n\t\tPath:         *path,\n\t\tContextToken: *token,\n\t}\n\n\tswitch *method {\n\tcase \"get\":\n\t\tget(client, req)\n\tcase \"getProfile\":\n\t\tgetProfile(client, req)\n\tdefault:\n\t\t//nolint:gocritic\n\t\tlog.Fatalf(\"Unknown method: %s; supported methods: get, getProfile\", *method)\n\t}\n}\n\nfunc get(client pb.DestinationClient, req *pb.GetDestination) {\n\trsp, err := client.Get(context.Background(), req)\n\tif err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n\n\tfor {\n\t\tupdate, err := rsp.Recv()\n\t\tif err != nil {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tlog.Fatal(err.Error())\n\t\t}\n\n\t\tswitch updateType := update.Update.(type) {\n\t\tcase *pb.Update_Add:\n\t\t\tlog.Println(\"Add:\")\n\t\t\tlog.Printf(\"labels: %v\", updateType.Add.MetricLabels)\n\t\t\tfor _, addr := range updateType.Add.Addrs {\n\t\t\t\tlog.Printf(\"- %s:%d\", addrUtil.ProxyAddressToString(addr.Addr), addr.Addr.Port)\n\t\t\t\tlog.Printf(\"  - labels: %v\", addr.MetricLabels)\n\t\t\t\tswitch addr.GetProtocolHint().GetProtocol().(type) {\n\t\t\t\tcase *pb.ProtocolHint_H2_:\n\t\t\t\t\tlog.Printf(\"  - protocol hint: H2\")\n\t\t\t\tdefault:\n\t\t\t\t\tlog.Printf(\"  - protocol hint: UNKNOWN\")\n\t\t\t\t}\n\t\t\t\tlog.Printf(\"  - identity: %s\", addr.GetTlsIdentity())\n\t\t\t\tif ot := addr.GetProtocolHint().GetOpaqueTransport(); ot != nil {\n\t\t\t\t\tlog.Printf(\"  - opaque transport port: %d\", ot.GetInboundPort())\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlog.Println()\n\t\tcase *pb.Update_Remove:\n\t\t\tlog.Println(\"Remove:\")\n\t\t\tfor _, addr := range updateType.Remove.Addrs {\n\t\t\t\tlog.Printf(\"- %s:%d\", addrUtil.ProxyAddressToString(addr), addr.Port)\n\t\t\t}\n\t\t\tlog.Println()\n\t\tcase *pb.Update_NoEndpoints:\n\t\t\tlog.Println(\"NoEndpoints:\")\n\t\t\tlog.Printf(\"- exists:%t\", updateType.NoEndpoints.Exists)\n\t\t\tlog.Println()\n\t\t}\n\t}\n}\n\nfunc getProfile(client pb.DestinationClient, req *pb.GetDestination) {\n\trsp, err := client.GetProfile(context.Background(), req)\n\tif err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n\n\tfor {\n\t\tupdate, err := rsp.Recv()\n\t\tif err != nil {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tlog.Fatal(err.Error())\n\t\t}\n\t\tlog.Printf(\"%+v\", update)\n\t\tlog.Println()\n\t}\n}\n"
  },
  {
    "path": "controller/script/policy-client/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc main() {\n\tnamespace := flag.String(\"namespace\", \"\", \"namespace of resource to get\")\n\tflag.Parse()\n\n\tconfig, err := k8s.GetConfig(\"\", \"\")\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error configuring Kubernetes API client: %v\", err)\n\t\tos.Exit(1)\n\t}\n\tclient := versioned.NewForConfigOrDie(config)\n\n\tsrvWatch, err := client.ServerV1beta3().Servers(*namespace).Watch(context.Background(), metav1.ListOptions{})\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"failed to watch Servers: %s\", err)\n\t\tos.Exit(1)\n\t}\n\tsazWatch, err := client.ServerauthorizationV1beta1().ServerAuthorizations(*namespace).Watch(context.Background(), metav1.ListOptions{})\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"failed to watch ServerAuthorizations: %s\", err)\n\t\tos.Exit(1)\n\t}\n\tsrvUpdates := srvWatch.ResultChan()\n\tsazUpdates := sazWatch.ResultChan()\n\n\tfor {\n\t\tvar update interface{}\n\t\tselect {\n\t\tcase u := <-srvUpdates:\n\t\t\tupdate = u\n\t\tcase u := <-sazUpdates:\n\t\t\tupdate = u\n\t\t}\n\t\tb, _ := yaml.Marshal(update)\n\t\tfmt.Println(string(b))\n\t}\n}\n"
  },
  {
    "path": "controller/sp-validator/webhook.go",
    "content": "package validator\n\nimport (\n\t\"context\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/profiles\"\n\tadmissionv1beta1 \"k8s.io/api/admission/v1beta1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/tools/record\"\n)\n\n// AdmitSP verifies that the received Admission Request contains a valid\n// Service Profile definition\nfunc AdmitSP(\n\t_ context.Context, _ *k8s.MetadataAPI, request *admissionv1beta1.AdmissionRequest, _ record.EventRecorder,\n) (*admissionv1beta1.AdmissionResponse, error) {\n\tadmissionResponse := &admissionv1beta1.AdmissionResponse{\n\t\tUID:     request.UID,\n\t\tAllowed: true,\n\t}\n\tif err := profiles.Validate(request.Object.Raw); err != nil {\n\t\tadmissionResponse.Allowed = false\n\t\tadmissionResponse.Result = &metav1.Status{Message: err.Error(), Code: 400}\n\t}\n\treturn admissionResponse, nil\n}\n"
  },
  {
    "path": "controller/webhook/launcher.go",
    "content": "package webhook\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/admin\"\n\tpkgk8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\n// Launch sets up and starts the webhook and metrics servers\nfunc Launch(\n\tctx context.Context,\n\tapiresources []k8s.APIResource,\n\thandler Handler,\n\tcomponent,\n\tmetricsAddr string,\n\taddr string,\n\tkubeconfig string,\n\tenablePprof bool,\n) {\n\tready := false\n\tadminServer := admin.NewServer(metricsAddr, enablePprof, &ready)\n\n\tgo func() {\n\t\tlog.Infof(\"starting admin server on %s\", metricsAddr)\n\t\tif err := adminServer.ListenAndServe(); err != nil {\n\t\t\tlog.Errorf(\"failed to start webhook admin server: %s\", err)\n\t\t}\n\t}()\n\n\tstop := make(chan os.Signal, 1)\n\tdefer close(stop)\n\tsignal.Notify(stop, os.Interrupt, syscall.SIGTERM)\n\n\tconfig, err := pkgk8s.GetConfig(kubeconfig, \"\")\n\tif err != nil {\n\t\t//nolint:gocritic\n\t\tlog.Fatalf(\"error building Kubernetes API config: %s\", err)\n\t}\n\n\tk8sAPI, err := pkgk8s.NewAPIForConfig(config, \"\", []string{}, 0, 0, 0)\n\tif err != nil {\n\t\t//nolint:gocritic\n\t\tlog.Fatalf(\"error configuring Kubernetes API client: %s\", err)\n\t}\n\n\tmetadataAPI, err := k8s.InitializeMetadataAPI(kubeconfig, \"local\", apiresources...)\n\tif err != nil {\n\t\t//nolint:gocritic\n\t\tlog.Fatalf(\"failed to initialize Kubernetes API: %s\", err)\n\t}\n\n\ts, err := NewServer(ctx, k8sAPI, metadataAPI, addr, pkgk8s.MountPathTLSBase, handler, component)\n\tif err != nil {\n\t\t//nolint:gocritic\n\t\tlog.Fatalf(\"failed to initialize the webhook server: %s\", err)\n\t}\n\n\tgo s.Start()\n\n\tmetadataAPI.Sync(nil)\n\n\tready = true\n\n\t<-stop\n\tlog.Info(\"shutting down webhook server\")\n\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\tdefer cancel()\n\tif err := s.Shutdown(ctx); err != nil {\n\t\tlog.Error(err)\n\t}\n\n\tadminServer.Shutdown(ctx)\n}\n"
  },
  {
    "path": "controller/webhook/server.go",
    "content": "package webhook\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tpkgk8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpkgTls \"github.com/linkerd/linkerd2/pkg/tls\"\n\t\"github.com/linkerd/linkerd2/pkg/util\"\n\tlog \"github.com/sirupsen/logrus\"\n\tadmissionv1beta1 \"k8s.io/api/admission/v1beta1\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\ttypedcorev1 \"k8s.io/client-go/kubernetes/typed/core/v1\"\n\t\"k8s.io/client-go/tools/record\"\n\t\"sigs.k8s.io/yaml\"\n)\n\n// Handler is the signature for the functions that ultimately deal with\n// the admission request\ntype Handler func(\n\tcontext.Context,\n\t*k8s.MetadataAPI,\n\t*admissionv1beta1.AdmissionRequest,\n\trecord.EventRecorder,\n) (*admissionv1beta1.AdmissionResponse, error)\n\n// Server describes the https server implementing the webhook\ntype Server struct {\n\t*http.Server\n\tmetadataAPI *k8s.MetadataAPI\n\thandler     Handler\n\tcertValue   *atomic.Value\n\trecorder    record.EventRecorder\n}\n\n// NewServer returns a new instance of Server\nfunc NewServer(\n\tctx context.Context,\n\tapi *pkgk8s.KubernetesAPI,\n\tmetadataAPI *k8s.MetadataAPI,\n\taddr, certPath string,\n\thandler Handler,\n\tcomponent string,\n) (*Server, error) {\n\tupdateEvent := make(chan struct{})\n\terrEvent := make(chan error)\n\twatcher := pkgTls.NewFsCredsWatcher(certPath, updateEvent, errEvent).\n\t\tWithFilePaths(pkgk8s.MountPathTLSCrtPEM, pkgk8s.MountPathTLSKeyPEM)\n\tgo func() {\n\t\tif err := watcher.StartWatching(ctx); err != nil {\n\t\t\tlog.Fatalf(\"Failed to start creds watcher: %s\", err)\n\t\t}\n\t}()\n\n\tserver := &http.Server{\n\t\tAddr:              addr,\n\t\tReadHeaderTimeout: 15 * time.Second,\n\t\tTLSConfig: &tls.Config{\n\t\t\tMinVersion: tls.VersionTLS13,\n\t\t},\n\t}\n\n\teventBroadcaster := record.NewBroadcaster()\n\teventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{\n\t\t// In order to send events to all namespaces, we need to use an empty string here\n\t\t// re: client-go's event_expansion.go CreateWithEventNamespace()\n\t\tInterface: api.CoreV1().Events(\"\"),\n\t})\n\trecorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: component})\n\n\ts := getConfiguredServer(server, metadataAPI, handler, recorder)\n\tif err := watcher.UpdateCert(s.certValue); err != nil {\n\t\tlog.Fatalf(\"Failed to initialized certificate: %s\", err)\n\t}\n\n\tlog := log.WithFields(log.Fields{\n\t\t\"component\": \"proxy-injector\",\n\t\t\"addr\":      addr,\n\t})\n\n\tgo watcher.ProcessEvents(log, s.certValue, updateEvent, errEvent)\n\n\treturn s, nil\n}\n\nfunc getConfiguredServer(\n\thttpServer *http.Server,\n\tmetadataAPI *k8s.MetadataAPI,\n\thandler Handler,\n\trecorder record.EventRecorder,\n) *Server {\n\tvar emptyCert atomic.Value\n\ts := &Server{httpServer, metadataAPI, handler, &emptyCert, recorder}\n\ts.Handler = http.HandlerFunc(s.serve)\n\thttpServer.TLSConfig.GetCertificate = s.getCertificate\n\treturn s\n}\n\n// Start starts the https server\nfunc (s *Server) Start() {\n\tlog.Infof(\"listening at %s\", s.Server.Addr)\n\tif err := s.ListenAndServeTLS(\"\", \"\"); err != nil {\n\t\tif errors.Is(err, http.ErrServerClosed) {\n\t\t\treturn\n\t\t}\n\t\tlog.Fatal(err)\n\t}\n}\n\n// getCertificate provides the TLS server with the current cert\nfunc (s *Server) getCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {\n\treturn s.certValue.Load().(*tls.Certificate), nil\n}\n\nfunc (s *Server) serve(res http.ResponseWriter, req *http.Request) {\n\tvar (\n\t\tdata []byte\n\t\terr  error\n\t)\n\tif req.Body != nil {\n\t\tdata, err = util.ReadAllLimit(req.Body, 10*util.MB)\n\t\tif err != nil {\n\t\t\thttp.Error(res, err.Error(), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif len(data) == 0 {\n\t\tlog.Warn(\"received empty payload\")\n\t\treturn\n\t}\n\n\tresponse, err := s.processReq(req.Context(), data)\n\tif err != nil {\n\t\thttp.Error(res, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tresponseJSON, err := json.Marshal(response)\n\tif err != nil {\n\t\thttp.Error(res, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tif _, err := res.Write(responseJSON); err != nil {\n\t\thttp.Error(res, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n}\n\nfunc (s *Server) processReq(ctx context.Context, data []byte) (*admissionv1beta1.AdmissionReview, error) {\n\tadmissionReview, err := decode(data)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode admission review request: %w\", err)\n\t}\n\tif admissionReview.Request == nil || admissionReview.Request.UID == \"\" {\n\t\treturn nil, fmt.Errorf(\"invalid admission review request\")\n\t}\n\tlog.Infof(\"received admission review request %q\", admissionReview.Request.UID)\n\tlog.Debugf(\"admission request: %+v\", admissionReview.Request)\n\n\tadmissionResponse, err := s.handler(ctx, s.metadataAPI, admissionReview.Request, s.recorder)\n\tif err != nil {\n\t\tlog.Error(\"failed to run webhook handler. Reason: \", err)\n\t\tadmissionReview.Response = &admissionv1beta1.AdmissionResponse{\n\t\t\tUID:     admissionReview.Request.UID,\n\t\t\tAllowed: false,\n\t\t\tResult: &metav1.Status{\n\t\t\t\tMessage: err.Error(),\n\t\t\t},\n\t\t}\n\t\treturn admissionReview, nil\n\t}\n\tadmissionReview.Response = admissionResponse\n\n\treturn admissionReview, nil\n}\n\n// Shutdown initiates a graceful shutdown of the underlying HTTP server.\nfunc (s *Server) Shutdown(ctx context.Context) error {\n\treturn s.Server.Shutdown(ctx)\n}\n\nfunc decode(data []byte) (*admissionv1beta1.AdmissionReview, error) {\n\tvar admissionReview admissionv1beta1.AdmissionReview\n\terr := yaml.Unmarshal(data, &admissionReview)\n\treturn &admissionReview, err\n}\n"
  },
  {
    "path": "controller/webhook/server_test.go",
    "content": "package webhook\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n)\n\nvar mockHTTPServer = &http.Server{\n\tAddr:              \":0\",\n\tReadHeaderTimeout: 15 * time.Second,\n\tTLSConfig: &tls.Config{\n\t\tMinVersion: tls.VersionTLS13,\n\t},\n}\n\nfunc TestServe(t *testing.T) {\n\tt.Run(\"with empty http request body\", func(t *testing.T) {\n\t\tk8sAPI, err := k8s.NewFakeMetadataAPI(nil)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\ttestServer := getConfiguredServer(mockHTTPServer, k8sAPI, nil, nil)\n\t\tin := bytes.NewReader(nil)\n\t\trequest := httptest.NewRequest(http.MethodGet, \"/\", in)\n\n\t\trecorder := httptest.NewRecorder()\n\t\ttestServer.serve(recorder, request)\n\n\t\tif recorder.Code != http.StatusOK {\n\t\t\tt.Errorf(\"HTTP response status mismatch. Expected: %d. Actual: %d\", http.StatusOK, recorder.Code)\n\t\t}\n\n\t\tif reflect.DeepEqual(recorder.Body.Bytes(), []byte(\"\")) {\n\t\t\tt.Errorf(\"Content mismatch. Expected HTTP response body to be empty %v\", recorder.Body.Bytes())\n\t\t}\n\t})\n}\n\nfunc TestShutdown(t *testing.T) {\n\ttestServer := getConfiguredServer(mockHTTPServer, nil, nil, nil)\n\n\tgo func() {\n\t\tif err := testServer.ListenAndServe(); err != nil {\n\t\t\tif !errors.Is(err, http.ErrServerClosed) {\n\t\t\t\tt.Errorf(\"Unexpected error: %s\", err)\n\t\t\t}\n\t\t}\n\t}()\n\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\tif err := testServer.Shutdown(ctx); err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n}\n"
  },
  {
    "path": "controller/webhook/util.go",
    "content": "package webhook\n\nimport (\n\t\"fmt\"\n\n\tlabels \"github.com/linkerd/linkerd2/pkg/k8s\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// GetProxyContainerPath gets the proxy container jsonpath of a pod relative to spec;\n// this path is required in webhooks because of how patches are created.\nfunc GetProxyContainerPath(spec corev1.PodSpec) string {\n\tfor i, c := range spec.Containers {\n\t\tif c.Name == labels.ProxyContainerName {\n\t\t\treturn fmt.Sprintf(\"containers/%d\", i)\n\t\t}\n\t}\n\tfor i, c := range spec.InitContainers {\n\t\tif c.Name == labels.ProxyContainerName {\n\t\t\treturn fmt.Sprintf(\"initContainers/%d\", i)\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "deny.toml",
    "content": "[graph]\ntargets = [\n    { triple = \"x86_64-unknown-linux-gnu\" },\n    { triple = \"aarch64-unknown-linux-gnu\" },\n]\n\n[advisories]\ndb-path = \"~/.cargo/advisory-db\"\ndb-urls = [\"https://github.com/rustsec/advisory-db\"]\nignore = [\n]\n\n[licenses]\nallow = [\n    \"Apache-2.0\",\n    \"BSD-2-Clause\",\n    \"BSD-3-Clause\",\n    \"ISC\",\n    \"MIT\",\n    \"Unicode-3.0\",\n    \"Zlib\",\n]\nconfidence-threshold = 0.8\nexceptions = [\n    { allow = [\n        \"ISC\",\n        \"MIT\",\n        \"OpenSSL\",\n    ], name = \"ring\", version = \"*\" },\n    { allow = [\n        \"ISC\",\n        \"MIT\",\n        \"OpenSSL\",\n    ], name = \"aws-lc-sys\", version = \"*\" },\n    { allow = [\n        \"ISC\",\n        \"MIT\",\n        \"OpenSSL\",\n    ], name = \"aws-lc-fips-sys\", version = \"*\" },\n]\n\n[[licenses.clarify]]\nname = \"ring\"\nversion = \"*\"\nexpression = \"MIT AND ISC AND OpenSSL\"\nlicense-files = [{ path = \"LICENSE\", hash = 0xbd0eed23 }]\n\n[bans]\nmultiple-versions = \"deny\"\n# Wildcard dependencies are used for all workspace-local crates.\nwildcards = \"allow\"\nhighlight = \"all\"\nskip = []\nskip-tree = [\n    # `serde_json` and `h2` depend on diverged versions of `indexmap` (2.0.x and\n    # 1.9.x, respectively)\n    { name = \"indexmap\" },\n    # thiserror v2 is still making its way through the ecosystem\n    { name = \"thiserror\", version = \"1\" },\n    # getrandom v0.3 is still making its way through the ecosystem\n    { name = \"getrandom\", version = \"0.2\" },\n    # rand v0.10 is still making its way through the ecosystem. it uses a\n    # newer version of `cpufeatures` as well.\n    { name = \"rand\", version = \"0.9\" },\n    { name = \"cpufeatures\", version = \"0.2\" },\n]\n\n[sources]\nunknown-registry = \"deny\"\nunknown-git = \"deny\"\nallow-registry = [\"https://github.com/rust-lang/crates.io-index\"]\nallow-git = []\n\n[sources.allow-org]\ngithub = [\n    \"linkerd\",\n]\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/linkerd/linkerd2\n\ngo 1.25.8\n\nrequire (\n\tcontrib.go.opencensus.io/exporter/ocagent v0.7.0\n\tdario.cat/mergo v1.0.2\n\tgithub.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24\n\tgithub.com/bombsimon/logrusr/v4 v4.1.0\n\tgithub.com/briandowns/spinner v1.23.2\n\tgithub.com/clarketm/json v1.15.7\n\tgithub.com/emicklei/proto v1.14.3\n\tgithub.com/evanphx/json-patch v5.9.11+incompatible\n\tgithub.com/fatih/color v1.18.0\n\tgithub.com/fsnotify/fsnotify v1.9.0\n\tgithub.com/go-openapi/spec v0.22.4\n\tgithub.com/go-test/deep v1.1.1\n\tgithub.com/golang/protobuf v1.5.4\n\tgithub.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674\n\tgithub.com/grantae/certinfo v0.0.0-20170412194111-59d56a35515b\n\tgithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0\n\tgithub.com/julienschmidt/httprouter v1.3.0\n\tgithub.com/linkerd/linkerd2-proxy-api v0.18.0\n\tgithub.com/mattn/go-isatty v0.0.20\n\tgithub.com/mattn/go-runewidth v0.0.21\n\tgithub.com/nsf/termbox-go v1.1.1\n\tgithub.com/patrickmn/go-cache v2.1.0+incompatible\n\tgithub.com/pkg/browser v0.0.0-20170505125900-c90ca0c84f15\n\tgithub.com/prometheus/client_golang v1.23.2\n\tgithub.com/prometheus/client_model v0.6.2\n\tgithub.com/prometheus/common v0.67.5\n\tgithub.com/sergi/go-diff v1.4.0\n\tgithub.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92\n\tgithub.com/sirupsen/logrus v1.9.4\n\tgithub.com/spf13/cobra v1.10.2\n\tgithub.com/spf13/pflag v1.0.10\n\tgo.opencensus.io v0.24.0\n\tgolang.org/x/tools v0.43.0\n\tgoogle.golang.org/grpc v1.79.3\n\tgoogle.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1\n\tgoogle.golang.org/protobuf v1.36.11\n\tgopkg.in/yaml.v2 v2.4.0\n\thelm.sh/helm/v3 v3.20.1\n\tk8s.io/api v0.35.3\n\tk8s.io/apiextensions-apiserver v0.35.3\n\tk8s.io/apimachinery v0.35.3\n\tk8s.io/client-go v0.35.3\n\tk8s.io/code-generator v0.35.3\n\tk8s.io/endpointslice v0.35.3\n\tk8s.io/klog/v2 v2.140.0\n\tk8s.io/kube-aggregator v0.35.3\n\tk8s.io/utils v0.0.0-20251002143259-bc988d571ff4\n\tsigs.k8s.io/gateway-api v0.8.1\n\tsigs.k8s.io/yaml v1.6.0\n)\n\nrequire (\n\tgithub.com/golang-jwt/jwt/v5 v5.3.0 // indirect\n\tgolang.org/x/net v0.52.0 // indirect\n)\n\nrequire (\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/sprig/v3 v3.3.0 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/blang/semver/v4 v4.0.0 // indirect\n\tgithub.com/census-instrumentation/opencensus-proto v0.4.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/chai2010/gettext-go v1.0.2 // indirect\n\tgithub.com/clipperhouse/uax29/v2 v2.2.0 // indirect\n\tgithub.com/containerd/containerd v1.7.30 // indirect\n\tgithub.com/containerd/errdefs 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/cpuguy83/go-md2man/v2 v2.0.6 // 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/emicklei/go-restful/v3 v3.12.2 // indirect\n\tgithub.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // indirect\n\tgithub.com/go-errors/errors v1.4.2 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.22.5 // indirect\n\tgithub.com/go-openapi/jsonreference v0.21.5 // indirect\n\tgithub.com/go-openapi/swag v0.23.0 // indirect\n\tgithub.com/go-openapi/swag/conv v0.25.5 // indirect\n\tgithub.com/go-openapi/swag/jsonname v0.25.5 // indirect\n\tgithub.com/go-openapi/swag/jsonutils v0.25.5 // indirect\n\tgithub.com/go-openapi/swag/loading v0.25.5 // indirect\n\tgithub.com/go-openapi/swag/stringutils v0.25.5 // indirect\n\tgithub.com/go-openapi/swag/typeutils v0.25.5 // indirect\n\tgithub.com/go-openapi/swag/yamlutils v0.25.5 // indirect\n\tgithub.com/gobwas/glob v0.2.3 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect\n\tgithub.com/google/btree v1.1.3 // indirect\n\tgithub.com/google/gnostic-models v0.7.0 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // 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/josharian/intern v1.0.0 // indirect\n\tgithub.com/jpillora/backoff v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/klauspost/compress v1.18.0 // indirect\n\tgithub.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mitchellh/copystructure v1.2.0 // indirect\n\tgithub.com/mitchellh/go-wordwrap v1.0.1 // indirect\n\tgithub.com/mitchellh/reflectwalk v1.0.2 // indirect\n\tgithub.com/moby/spdystream v0.5.0 // indirect\n\tgithub.com/moby/term v0.5.2 // 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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // 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/procfs v0.16.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/shopspring/decimal v1.4.0 // indirect\n\tgithub.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect\n\tgithub.com/spf13/cast v1.7.0 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgithub.com/xlab/treeprint v1.2.0 // indirect\n\tgo.opentelemetry.io/otel v1.39.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.39.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/crypto v0.49.0 // indirect\n\tgolang.org/x/mod v0.34.0 // indirect\n\tgolang.org/x/oauth2 v0.34.0 // indirect\n\tgolang.org/x/sync v0.20.0 // indirect\n\tgolang.org/x/sys v0.42.0 // indirect\n\tgolang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c // indirect\n\tgolang.org/x/term v0.41.0 // indirect\n\tgolang.org/x/text v0.35.0 // indirect\n\tgolang.org/x/time v0.12.0 // indirect\n\tgolang.org/x/tools/godoc v0.1.0-deprecated // indirect\n\tgoogle.golang.org/api v0.143.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // 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/cli-runtime v0.35.1 // indirect\n\tk8s.io/component-base v0.35.3 // indirect\n\tk8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect\n\tk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect\n\tk8s.io/kubectl v0.35.1 // 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.20.1 // indirect\n\tsigs.k8s.io/kustomize/kyaml v0.20.1 // indirect; indirecxt\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncontrib.go.opencensus.io/exporter/ocagent v0.7.0 h1:BEfdCTXfMV30tLZD8c9n64V/tIZX5+9sXiuFLnrr1k8=\ncontrib.go.opencensus.io/exporter/ocagent v0.7.0/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY=\ndario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=\ndario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\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 v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=\ngithub.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\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/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/bombsimon/logrusr/v4 v4.1.0 h1:uZNPbwusB0eUXlO8hIUwStE6Lr5bLN6IgYgG+75kuh4=\ngithub.com/bombsimon/logrusr/v4 v4.1.0/go.mod h1:pjfHC5e59CvjTBIU3V3sGhFWFAnsnhOR03TRc6im0l8=\ngithub.com/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2FW8w=\ngithub.com/briandowns/spinner v1.23.2/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM=\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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=\ngithub.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=\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.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=\ngithub.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/clarketm/json v1.15.7 h1:zWsOtfj736/nP76KiS0HpcyO6W50ojEodx7T4LW4NMc=\ngithub.com/clarketm/json v1.15.7/go.mod h1:ynr2LRfb0fQU34l07csRNBTcivjySLLiY1YzQqKVfdo=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=\ngithub.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\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 v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4=\ngithub.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=\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-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=\ngithub.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=\ngithub.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=\ngithub.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=\ngithub.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/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-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-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/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=\ngithub.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/emicklei/proto v1.14.3 h1:zEhlzNkpP8kN6utonKMzlPfIvy82t5Kb9mufaJxSe1Q=\ngithub.com/emicklei/proto v1.14.3/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/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/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=\ngithub.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=\ngithub.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-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.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=\ngithub.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=\ngithub.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=\ngithub.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=\ngithub.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ=\ngithub.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ=\ngithub.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=\ngithub.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=\ngithub.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=\ngithub.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=\ngithub.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=\ngithub.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=\ngithub.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=\ngithub.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=\ngithub.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U=\ngithub.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo=\ngithub.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=\ngithub.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=\ngithub.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=\ngithub.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=\ngithub.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=\ngithub.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=\ngithub.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=\ngithub.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=\ngithub.com/go-openapi/testify/enable/yaml/v2 v2.4.0 h1:7SgOMTvJkM8yWrQlU8Jm18VeDPuAvB/xWrdxFJkoFag=\ngithub.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM=\ngithub.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=\ngithub.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=\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-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=\ngithub.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=\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/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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/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.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=\ngithub.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=\ngithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/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/grantae/certinfo v0.0.0-20170412194111-59d56a35515b h1:NGgE5ELokSf2tZ/bydyDUKrvd/jP8lrAoPNeBuMOTOk=\ngithub.com/grantae/certinfo v0.0.0-20170412194111-59d56a35515b/go.mod h1:zT/uzhdQGTqlwTq7Lpbj3JoJQWfPfIJ1tE0OidAmih8=\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-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=\ngithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=\ngithub.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw=\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 v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\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/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=\ngithub.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=\ngithub.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=\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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=\ngithub.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\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/linkerd/linkerd2-proxy-api v0.18.0 h1:6hAI0DjDObjI716GqsR4VrddnEJkMY7+H3HwNM5Tzvo=\ngithub.com/linkerd/linkerd2-proxy-api v0.18.0/go.mod h1:n7Sb/skuU8VDP/8TVZHeiAmB1QPFP4kygQkcsPzx3rI=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=\ngithub.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=\ngithub.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=\ngithub.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=\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/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/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=\ngithub.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=\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/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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=\ngithub.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\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/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=\ngithub.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=\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/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=\ngithub.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=\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-20170505125900-c90ca0c84f15 h1:mrI+6Ae64Wjt+uahGe5we/sPS1sXjvfT3YjtawAVgps=\ngithub.com/pkg/browser v0.0.0-20170505125900-c90ca0c84f15/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v1.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.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\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.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=\ngithub.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=\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/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\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/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/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs=\ngithub.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M=\ngithub.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 h1:OfRzdxCzDhp+rsKWXuOO2I/quKMJ/+TQwVbIP/gltZg=\ngithub.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92/go.mod h1:7/OT02F6S6I7v6WXb+IjhMuZEYfH/RJ5RwEWnEo5BMg=\ngithub.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=\ngithub.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=\ngithub.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=\ngithub.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=\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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngithub.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=\ngithub.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=\ngo.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/contrib/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.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=\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/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.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/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.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=\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=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=\ngolang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=\ngolang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=\ngolang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=\ngolang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=\ngolang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=\ngolang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c h1:6a8FdnNk6bTXBjR4AGKFgUKuo+7GnR3FX5L7CbveeZc=\ngolang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw=\ngolang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=\ngolang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=\ngolang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=\ngolang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=\ngolang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=\ngolang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=\ngolang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=\ngolang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=\ngolang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=\ngolang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk=\ngolang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg=\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=\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/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.143.0 h1:o8cekTkqhywkbZT6p1UHJPZ9+9uuCAJs/KYomxZB8fA=\ngoogle.golang.org/api v0.143.0/go.mod h1:FoX9DO9hT7DLNn97OuoZAGSDuNAXdJRuGK98rSUgurk=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\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-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=\ngoogle.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1 h1:/WILD1UcXj/ujCxgoL/DvRgt2CP3txG8+FwkUbb9110=\ngoogle.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1/go.mod h1:YNKnb2OAApgYn2oYY47Rn7alMr1zWjb2U8Q0aoGWiNc=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/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.2.3/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=\nhelm.sh/helm/v3 v3.20.1 h1:T8PodUaH1UwNvE+imUA2mIKjJItY8g7CVvLVP5g4NzI=\nhelm.sh/helm/v3 v3.20.1/go.mod h1:Fl1kBaWCpkUrM6IYXPjQ3bdZQfFrogKArqptvueZ6Ww=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nk8s.io/api v0.35.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ=\nk8s.io/api v0.35.3/go.mod h1:9Y9tkBcFwKNq2sxwZTQh1Njh9qHl81D0As56tu42GA4=\nk8s.io/apiextensions-apiserver v0.35.3 h1:2fQUhEO7P17sijylbdwt0nBdXP0TvHrHj0KeqHD8FiU=\nk8s.io/apiextensions-apiserver v0.35.3/go.mod h1:tK4Kz58ykRpwAEkXUb634HD1ZAegEElktz/B3jgETd8=\nk8s.io/apimachinery v0.35.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8=\nk8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=\nk8s.io/cli-runtime v0.35.1 h1:uKcXFe8J7AMAM4Gm2JDK4mp198dBEq2nyeYtO+JfGJE=\nk8s.io/cli-runtime v0.35.1/go.mod h1:55/hiXIq1C8qIJ3WBrWxEwDLdHQYhBNRdZOz9f7yvTw=\nk8s.io/client-go v0.35.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg=\nk8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c=\nk8s.io/code-generator v0.35.3 h1:NDGCLkEm6Ho65wTdSe2EgErmmtsrezOPwwOchlNc6FQ=\nk8s.io/code-generator v0.35.3/go.mod h1:LAVriRGXQusHQ0Ns64SE1ublSswm1KrK7cXn0GuQETg=\nk8s.io/component-base v0.35.3 h1:mbKbzoIMy7JDWS/wqZobYW1JDVRn/RKRaoMQHP9c4P0=\nk8s.io/component-base v0.35.3/go.mod h1:IZ8LEG30kPN4Et5NeC7vjNv5aU73ku5MS15iZyvyMYk=\nk8s.io/endpointslice v0.35.3 h1:O5oBs7AotMNqWVOegTuND2SUQpNwAGa/mZ5RlxqL9Eg=\nk8s.io/endpointslice v0.35.3/go.mod h1:wFJUsyjtmcJgd3+M4HZBw/SmOLxJl7HK2kN4bK81TP8=\nk8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ=\nk8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM=\nk8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=\nk8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=\nk8s.io/kube-aggregator v0.35.3 h1:erIo8Dfapd0Fg44XAbgCNioJMtr3Z5mI/G1PSpj9B7Q=\nk8s.io/kube-aggregator v0.35.3/go.mod h1:lOLyWTEuiKT2kS/Wkj0foq+P+Xt4gs/xkrhz2r33lAQ=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=\nk8s.io/kubectl v0.35.1 h1:zP3Er8C5i1dcAFUMh9Eva0kVvZHptXIn/+8NtRWMxwg=\nk8s.io/kubectl v0.35.1/go.mod h1:cQ2uAPs5IO/kx8R5s5J3Ihv3VCYwrx0obCXum0CvnXo=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\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=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\nsigs.k8s.io/gateway-api v0.8.1 h1:Bo4NMAQFYkQZnHXOfufbYwbPW7b3Ic5NjpbeW6EJxuU=\nsigs.k8s.io/gateway-api v0.8.1/go.mod h1:0PteDrsrgkRmr13nDqFWnev8tOysAVrwnvfFM55tSVg=\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.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=\nsigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM=\nsigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78=\nsigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po=\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.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.0/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": "grafana/README.md",
    "content": "# Using Grafana with Linkerd\n\nYou can install Grafana in various ways, like using the [Grafana official Helm\nchart](https://github.com/grafana/helm-charts/tree/main/charts/grafana), or the\n[Grafana Operator](https://github.com/grafana-operator/grafana-operator). Hosted\nsolutions are also available, like [Grafana\nCloud](https://grafana.com/products/cloud/).\n\nThe file `grafana/values.yaml` provides a default Helm config for the [Grafana\nofficial Helm\nchart](https://github.com/grafana/helm-charts/tree/main/charts/grafana), which\npulls the Linkerd dashboards published at\n<https://grafana.com/orgs/linkerd/dashboards>.\n\nYou can install the chart like this:\n\n```shell\nhelm repo add grafana https://grafana.github.io/helm-charts\nhelm install grafana -n grafana --create-namespace grafana/grafana \\\n  -f https://raw.githubusercontent.com/linkerd/linkerd2/main/grafana/values.yaml\n```\n\nPlease make sure to update the entries in `grafana/values.yaml` before using the\nfile; in particular:\n\n- auth and log settings under `grafana.ini`\n- `datasources.datasources.yaml.datasources[0].url` should point to your\n  Prometheus service\n\nThe other installation methods can easily import those same dashboards using\ntheir IDs, as listed in `grafana/values.yaml`.\n\nIn order to have the Linkerd Viz Dashboard show the Grafana icon there where\nrelevant, and have it link to the appropriate Grafana dashboard, make sure you\nhave a proper location set up in the `grafana.url` setting in Linkerd Viz's\n`values.yaml`.\n\n## Granting access\n\nAs of linkerd `stable-2.13.0` (or `edge-23.1.1`), the access to Linkerd Viz'\nPrometheus instance has been restricted through the `prometheus-admin`\nAuthorizationPolicy, granting access only to the `metrics-api` ServiceAccount.\nIn order to grant access to Grafana, you need to add an AuthorizationPolicy\npointing to its ServiceAccount. You can apply\n[authzpolicy-grafana.yaml](grafana/authzpolicy-grafana.yaml) which grants\npermission for the `grafana` ServiceAccount.\n\n## Note to developers\n\nThe `grafana/dashboards` directory contains the same dashboard definitions\npublished under <https://grafana.com/orgs/linkerd>. Please keep them in sync when\nmaking any changes. After logging into grafana.com (using the linkerd account),\nthe dashboards can be managed under Org Settings -> My Dashboards.\n"
  },
  {
    "path": "grafana/authzpolicy-grafana.yaml",
    "content": "apiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: grafana\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: prometheus-admin\n  requiredAuthenticationRefs:\n    - kind: ServiceAccount\n      name: grafana\n      namespace: grafana\n\n"
  },
  {
    "path": "grafana/dashboards/authority.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\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  \"gnetId\": null,\n  \"graphTooltip\": 1,\n  \"id\": null,\n  \"iteration\": 1539806914987,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"height:32px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 32px\\\">au/$authority</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 2,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#d44a3a\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#299c46\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"percentunit\",\n      \"gauge\": {\n        \"maxValue\": 1,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 2\n      },\n      \"id\": 4,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"inbound\\\"}[$__rate_interval])) / sum(irate(response_total{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"0.9,.99\",\n      \"title\": \"SUCCESS RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 2\n      },\n      \"id\": 6,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \" RPS\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"REQUEST RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 2\n      },\n      \"id\": 8,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \" ms\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, authority))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"P95 LATENCY\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>TOP-LINE TRAFFIC</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 6\n      },\n      \"id\": 10,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 12,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (authority) / sum(irate(response_total{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (authority)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"au/{{authority}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 8\n      },\n      \"id\": 14,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"inbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (authority)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒au/{{authority}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"inbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (authority)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"au/{{authority}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 8\n      },\n      \"id\": 16,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, authority))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p50 au/{{authority}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, authority))\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p95 au/{{authority}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, authority))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p99 au/{{authority}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"ms\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND TRAFFIC BY DEPLOYMENT</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 15\n      },\n      \"id\": 18,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 17\n      },\n      \"id\": 20,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (deployment) / sum(irate(response_total{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 17\n      },\n      \"id\": 22,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"deploy/{{deployment}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 17\n      },\n      \"id\": 24,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, deployment))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P95 deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"P95 LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND TRAFFIC BY POD</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 24\n      },\n      \"id\": 26,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 26\n      },\n      \"id\": 28,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (pod) / sum(irate(response_total{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"po/{{pod}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 26\n      },\n      \"id\": 30,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒po/{{pod}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"po/{{pod}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 26\n      },\n      \"id\": 32,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", authority=\\\"$authority\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, pod))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P95 po/{{pod}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"P95 LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 33\n      },\n      \"height\": \"1px\",\n      \"id\": 34,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    }\n  ],\n  \"refresh\": \"1m\",\n  \"schemaVersion\": 18,\n  \"style\": \"dark\",\n  \"tags\": [\n    \"linkerd\"\n  ],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"text\": \"default\",\n          \"value\": \"default\"\n        },\n        \"hide\": 0,\n        \"label\": \"Data Source\",\n        \"name\": \"datasource\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Namespace\",\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": \"label_values(request_total, namespace)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Authority\",\n        \"multi\": false,\n        \"name\": \"authority\",\n        \"options\": [],\n        \"query\": \"label_values(request_total{namespace=\\\"$namespace\\\"}, authority)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-5m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"\",\n  \"title\": \"Linkerd Authority\",\n  \"uid\": \"linkerd-authority\",\n  \"version\": 1\n}\n"
  },
  {
    "path": "grafana/dashboards/cronjob.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n    \"annotations\": {\n      \"list\": [\n        {\n          \"builtIn\": 1,\n          \"datasource\": \"-- Grafana --\",\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    \"gnetId\": null,\n    \"graphTooltip\": 1,\n    \"id\": null,\n    \"iteration\": 1531763681685,\n    \"links\": [],\n    \"panels\": [\n      {\n        \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"height:32px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 32px\\\">cj/$cronjob</span>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 0\n        },\n        \"id\": 20,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"colorBackground\": false,\n        \"colorValue\": false,\n        \"colors\": [\n          \"#d44a3a\",\n          \"rgba(237, 129, 40, 0.89)\",\n          \"#299c46\"\n        ],\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"decimals\": null,\n        \"format\": \"percentunit\",\n        \"gauge\": {\n          \"maxValue\": 1,\n          \"minValue\": 0,\n          \"show\": true,\n          \"thresholdLabels\": false,\n          \"thresholdMarkers\": true\n        },\n        \"gridPos\": {\n          \"h\": 4,\n          \"w\": 8,\n          \"x\": 0,\n          \"y\": 2\n        },\n        \"id\": 5,\n        \"interval\": null,\n        \"links\": [],\n        \"mappingType\": 1,\n        \"mappingTypes\": [\n          {\n            \"name\": \"value to text\",\n            \"value\": 1\n          },\n          {\n            \"name\": \"range to text\",\n            \"value\": 2\n          }\n        ],\n        \"maxDataPoints\": 100,\n        \"nullPointMode\": \"connected\",\n        \"nullText\": null,\n        \"options\": {},\n        \"postfix\": \"\",\n        \"postfixFontSize\": \"50%\",\n        \"prefix\": \"\",\n        \"prefixFontSize\": \"50%\",\n        \"rangeMaps\": [\n          {\n            \"from\": \"null\",\n            \"text\": \"N/A\",\n            \"to\": \"null\"\n          }\n        ],\n        \"sparkline\": {\n          \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n          \"full\": true,\n          \"lineColor\": \"rgb(31, 120, 193)\",\n          \"show\": true\n        },\n        \"tableColumn\": \"\",\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"inbound\\\"}[$__rate_interval])) / sum(irate(response_total{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n            \"format\": \"time_series\",\n            \"instant\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": \"0.9,.99\",\n        \"title\": \"SUCCESS RATE\",\n        \"transparent\": true,\n        \"type\": \"singlestat\",\n        \"valueFontSize\": \"80%\",\n        \"valueMaps\": [\n          {\n            \"op\": \"=\",\n            \"text\": \"N/A\",\n            \"value\": \"null\"\n          }\n        ],\n        \"valueName\": \"current\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"colorBackground\": false,\n        \"colorValue\": false,\n        \"colors\": [\n          \"#299c46\",\n          \"rgba(237, 129, 40, 0.89)\",\n          \"#d44a3a\"\n        ],\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"decimals\": null,\n        \"format\": \"none\",\n        \"gauge\": {\n          \"maxValue\": 100,\n          \"minValue\": 0,\n          \"show\": false,\n          \"thresholdLabels\": false,\n          \"thresholdMarkers\": true\n        },\n        \"gridPos\": {\n          \"h\": 4,\n          \"w\": 8,\n          \"x\": 8,\n          \"y\": 2\n        },\n        \"id\": 4,\n        \"interval\": null,\n        \"links\": [],\n        \"mappingType\": 1,\n        \"mappingTypes\": [\n          {\n            \"name\": \"value to text\",\n            \"value\": 1\n          },\n          {\n            \"name\": \"range to text\",\n            \"value\": 2\n          }\n        ],\n        \"maxDataPoints\": 100,\n        \"nullPointMode\": \"connected\",\n        \"nullText\": null,\n        \"options\": {},\n        \"postfix\": \" RPS\",\n        \"postfixFontSize\": \"100%\",\n        \"prefix\": \"\",\n        \"prefixFontSize\": \"50%\",\n        \"rangeMaps\": [\n          {\n            \"from\": \"null\",\n            \"text\": \"N/A\",\n            \"to\": \"null\"\n          }\n        ],\n        \"sparkline\": {\n          \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n          \"full\": true,\n          \"lineColor\": \"rgb(31, 120, 193)\",\n          \"show\": true\n        },\n        \"tableColumn\": \"\",\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n            \"format\": \"time_series\",\n            \"instant\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": \"\",\n        \"title\": \"REQUEST RATE\",\n        \"transparent\": true,\n        \"type\": \"singlestat\",\n        \"valueFontSize\": \"100%\",\n        \"valueMaps\": [\n          {\n            \"op\": \"=\",\n            \"text\": \"N/A\",\n            \"value\": \"null\"\n          }\n        ],\n        \"valueName\": \"current\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"colorBackground\": false,\n        \"colorValue\": false,\n        \"colors\": [\n          \"#299c46\",\n          \"rgba(237, 129, 40, 0.89)\",\n          \"#d44a3a\"\n        ],\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"decimals\": null,\n        \"format\": \"none\",\n        \"gauge\": {\n          \"maxValue\": 100,\n          \"minValue\": 0,\n          \"show\": false,\n          \"thresholdLabels\": false,\n          \"thresholdMarkers\": true\n        },\n        \"gridPos\": {\n          \"h\": 4,\n          \"w\": 4,\n          \"x\": 16,\n          \"y\": 2\n        },\n        \"id\": 11,\n        \"interval\": null,\n        \"links\": [],\n        \"mappingType\": 1,\n        \"mappingTypes\": [\n          {\n            \"name\": \"value to text\",\n            \"value\": 1\n          },\n          {\n            \"name\": \"range to text\",\n            \"value\": 2\n          }\n        ],\n        \"maxDataPoints\": 100,\n        \"nullPointMode\": \"connected\",\n        \"nullText\": null,\n        \"options\": {},\n        \"postfix\": \"\",\n        \"postfixFontSize\": \"100%\",\n        \"prefix\": \"\",\n        \"prefixFontSize\": \"100%\",\n        \"rangeMaps\": [\n          {\n            \"from\": \"null\",\n            \"text\": \"N/A\",\n            \"to\": \"null\"\n          }\n        ],\n        \"sparkline\": {\n          \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n          \"full\": false,\n          \"lineColor\": \"rgb(31, 120, 193)\",\n          \"show\": false\n        },\n        \"tableColumn\": \"\",\n        \"targets\": [\n          {\n            \"expr\": \"count(count(request_total{dst_namespace=\\\"$namespace\\\", cronjob!=\\\"\\\", dst_cronjob!=\\\"\\\", dst_cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\"}) by (namespace, cronjob))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": \"\",\n        \"title\": \"INBOUND CRONJOBS\",\n        \"transparent\": true,\n        \"type\": \"singlestat\",\n        \"valueFontSize\": \"100%\",\n        \"valueMaps\": [\n          {\n            \"op\": \"=\",\n            \"text\": \"N/A\",\n            \"value\": \"null\"\n          }\n        ],\n        \"valueName\": \"current\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"colorBackground\": false,\n        \"colorValue\": false,\n        \"colors\": [\n          \"#299c46\",\n          \"rgba(237, 129, 40, 0.89)\",\n          \"#d44a3a\"\n        ],\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"format\": \"none\",\n        \"gauge\": {\n          \"maxValue\": 100,\n          \"minValue\": 0,\n          \"show\": false,\n          \"thresholdLabels\": false,\n          \"thresholdMarkers\": true\n        },\n        \"gridPos\": {\n          \"h\": 4,\n          \"w\": 4,\n          \"x\": 20,\n          \"y\": 2\n        },\n        \"id\": 15,\n        \"interval\": null,\n        \"links\": [],\n        \"mappingType\": 1,\n        \"mappingTypes\": [\n          {\n            \"name\": \"value to text\",\n            \"value\": 1\n          },\n          {\n            \"name\": \"range to text\",\n            \"value\": 2\n          }\n        ],\n        \"maxDataPoints\": 100,\n        \"nullPointMode\": \"connected\",\n        \"nullText\": null,\n        \"options\": {},\n        \"postfix\": \"\",\n        \"postfixFontSize\": \"100%\",\n        \"prefix\": \"\",\n        \"prefixFontSize\": \"50%\",\n        \"rangeMaps\": [\n          {\n            \"from\": \"null\",\n            \"text\": \"N/A\",\n            \"to\": \"null\"\n          }\n        ],\n        \"sparkline\": {\n          \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n          \"full\": false,\n          \"lineColor\": \"rgb(31, 120, 193)\",\n          \"show\": false\n        },\n        \"tableColumn\": \"\",\n        \"targets\": [\n          {\n            \"expr\": \"count(count(request_total{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\"}) by (namespace, dst_cronjob))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": \"\",\n        \"title\": \"OUTBOUND CRONJOBS\",\n        \"transparent\": true,\n        \"type\": \"singlestat\",\n        \"valueFontSize\": \"100%\",\n        \"valueMaps\": [\n          {\n            \"op\": \"=\",\n            \"text\": \"N/A\",\n            \"value\": \"null\"\n          }\n        ],\n        \"valueName\": \"current\"\n      },\n      {\n        \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND TRAFFIC</span>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 6\n        },\n        \"id\": 17,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 0,\n          \"y\": 8\n        },\n        \"id\": 67,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (cronjob) / sum(irate(response_total{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (cronjob)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"cj/{{cronjob}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"SUCCESS RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": null,\n            \"format\": \"percentunit\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": \"1\",\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 0,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 8,\n          \"y\": 8\n        },\n        \"id\": 2,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"inbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (cronjob)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"🔒cj/{{cronjob}}\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"inbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (cronjob)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"cj/{{cronjob}}\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"REQUEST RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": null,\n            \"format\": \"rps\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 16,\n          \"y\": 8\n        },\n        \"id\": 68,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, cronjob))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"p50 cj/{{cronjob}}\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, cronjob))\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"p95 cj/{{cronjob}}\",\n            \"refId\": \"B\"\n          },\n          {\n            \"expr\": \"histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, cronjob))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"p99 cj/{{cronjob}}\",\n            \"refId\": \"C\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"LATENCY\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": null,\n            \"format\": \"ms\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"collapsed\": true,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 15\n        },\n        \"id\": 148,\n        \"panels\": [\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 1,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 0,\n              \"y\": 16\n            },\n            \"id\": 167,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"inbound\\\",errno!=\\\"\\\"}\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"{{peer}} {{errno}}\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"TCP CONNECTION FAILURES\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"decimals\": null,\n                \"format\": \"none\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": \"0\",\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 0,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 8,\n              \"y\": 16\n            },\n            \"id\": 168,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"inbound\\\"}\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"{{peer}}\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"TCP CONNECTIONS OPEN\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"short\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": \"0\",\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"cards\": {\n              \"cardPadding\": null,\n              \"cardRound\": null\n            },\n            \"color\": {\n              \"cardColor\": \"#b4ff00\",\n              \"colorScale\": \"sqrt\",\n              \"colorScheme\": \"interpolateOranges\",\n              \"exponent\": 0.5,\n              \"mode\": \"spectrum\"\n            },\n            \"dataFormat\": \"timeseries\",\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 16,\n              \"y\": 16\n            },\n            \"heatmap\": {},\n            \"hideZeroBuckets\": false,\n            \"highlightCards\": true,\n            \"id\": 169,\n            \"legend\": {\n              \"show\": false\n            },\n            \"links\": [],\n            \"options\": {},\n            \"reverseYBuckets\": false,\n            \"targets\": [\n              {\n                \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"inbound\\\"}\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"refId\": \"A\"\n              }\n            ],\n            \"title\": \"TCP CONNECTION DURATION\",\n            \"tooltip\": {\n              \"show\": true,\n              \"showHistogram\": true\n            },\n            \"type\": \"heatmap\",\n            \"xAxis\": {\n              \"show\": true\n            },\n            \"xBucketNumber\": null,\n            \"xBucketSize\": null,\n            \"yAxis\": {\n              \"decimals\": null,\n              \"format\": \"dtdurationms\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true,\n              \"splitFactor\": null\n            },\n            \"yBucketBound\": \"auto\",\n            \"yBucketNumber\": null,\n            \"yBucketSize\": null\n          }\n        ],\n        \"title\": \"Inbound TCP Metrics\",\n        \"type\": \"row\"\n      },\n      {\n        \"collapsed\": false,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 16\n        },\n        \"id\": 152,\n        \"panels\": [],\n        \"title\": \"\",\n        \"type\": \"row\"\n      },\n      {\n        \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND CRONJOBS</span>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 17\n        },\n        \"id\": 76,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      },\n      {\n        \"collapsed\": true,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 19\n        },\n        \"id\": 59,\n        \"panels\": [\n          {\n            \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">cj/$inbound</span>\\n</div>\",\n            \"gridPos\": {\n              \"h\": 2,\n              \"w\": 24,\n              \"x\": 0,\n              \"y\": 22.2\n            },\n            \"id\": 39,\n            \"links\": [],\n            \"mode\": \"html\",\n            \"options\": {},\n            \"title\": \"\",\n            \"transparent\": true,\n            \"type\": \"text\"\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 1,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 0,\n              \"y\": 24.2\n            },\n            \"id\": 36,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", cronjob!=\\\"\\\", cronjob=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (cronjob, pod) / sum(irate(response_total{cronjob!=\\\"\\\", cronjob=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (cronjob, pod)\",\n                \"format\": \"time_series\",\n                \"instant\": false,\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"po/{{pod}}\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"SUCCESS RATE\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 2,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"decimals\": null,\n                \"format\": \"percentunit\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": \"1\",\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 0,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 8,\n              \"y\": 24.2\n            },\n            \"id\": 22,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"sum(irate(request_total{cronjob!=\\\"\\\", cronjob=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (cronjob, pod)\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"🔒po/{{pod}}\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"sum(irate(request_total{cronjob!=\\\"\\\", cronjob=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (cronjob, pod)\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"po/{{pod}}\",\n                \"refId\": \"B\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"REQUEST RATE\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 2,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"decimals\": null,\n                \"format\": \"rps\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": \"0\",\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 1,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 16,\n              \"y\": 24.2\n            },\n            \"id\": 29,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{cronjob!=\\\"\\\", cronjob=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, cronjob))\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"P50 cj/{{cronjob}}\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{cronjob!=\\\"\\\", cronjob=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, cronjob))\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"P95 cj/{{cronjob}}\",\n                \"refId\": \"B\"\n              },\n              {\n                \"expr\": \"histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{cronjob!=\\\"\\\", cronjob=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, cronjob))\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"P99 cj/{{cronjob}}\",\n                \"refId\": \"C\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"LATENCY\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 2,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"ms\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          }\n        ],\n        \"repeat\": \"inbound\",\n        \"title\": \"cj/$inbound\",\n        \"type\": \"row\"\n      },\n      {\n        \"collapsed\": false,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 20\n        },\n        \"id\": 34,\n        \"panels\": [],\n        \"repeat\": null,\n        \"title\": \"\",\n        \"type\": \"row\"\n      },\n      {\n        \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND TRAFFIC</span>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 21\n        },\n        \"id\": 32,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 0,\n          \"y\": 23\n        },\n        \"id\": 77,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_cronjob) / sum(irate(response_total{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_cronjob)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"cj/{{dst_cronjob}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"SUCCESS RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": null,\n            \"format\": \"percentunit\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": \"1\",\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 0,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 8,\n          \"y\": 23\n        },\n        \"id\": 78,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_cronjob)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"🔒cj/{{dst_cronjob}}\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_cronjob)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"cj/{{dst_cronjob}}\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"REQUEST RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"rps\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 16,\n          \"y\": 23\n        },\n        \"id\": 79,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_cronjob))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"P95 cj/{{dst_cronjob}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"P95 LATENCY\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"ms\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"collapsed\": true,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 30\n        },\n        \"id\": 154,\n        \"panels\": [\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 1,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 0,\n              \"y\": 29\n            },\n            \"id\": 157,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\",errno!=\\\"\\\"}\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"{{peer}} {{errno}}\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"TCP CONNECTION FAILURES\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"decimals\": null,\n                \"format\": \"none\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": \"0\",\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 0,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 8,\n              \"y\": 29\n            },\n            \"id\": 166,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\"}\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"{{peer}}\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"TCP CONNECTIONS OPEN\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"short\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": \"0\",\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"cards\": {\n              \"cardPadding\": null,\n              \"cardRound\": null\n            },\n            \"color\": {\n              \"cardColor\": \"#b4ff00\",\n              \"colorScale\": \"sqrt\",\n              \"colorScheme\": \"interpolateOranges\",\n              \"exponent\": 0.5,\n              \"mode\": \"spectrum\"\n            },\n            \"dataFormat\": \"timeseries\",\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 16,\n              \"y\": 29\n            },\n            \"heatmap\": {},\n            \"hideZeroBuckets\": false,\n            \"highlightCards\": true,\n            \"id\": 160,\n            \"legend\": {\n              \"show\": false\n            },\n            \"links\": [],\n            \"options\": {},\n            \"reverseYBuckets\": false,\n            \"targets\": [\n              {\n                \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", direction=\\\"outbound\\\"}\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"refId\": \"A\"\n              }\n            ],\n            \"title\": \"TCP CONNECTION DURATION\",\n            \"tooltip\": {\n              \"show\": true,\n              \"showHistogram\": true\n            },\n            \"type\": \"heatmap\",\n            \"xAxis\": {\n              \"show\": true\n            },\n            \"xBucketNumber\": null,\n            \"xBucketSize\": null,\n            \"yAxis\": {\n              \"decimals\": null,\n              \"format\": \"dtdurationms\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true,\n              \"splitFactor\": null\n            },\n            \"yBucketBound\": \"auto\",\n            \"yBucketNumber\": null,\n            \"yBucketSize\": null\n          }\n        ],\n        \"title\": \"Outbound TCP Metrics\",\n        \"type\": \"row\"\n      },\n      {\n        \"collapsed\": false,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 31\n        },\n        \"id\": 156,\n        \"panels\": [],\n        \"title\": \"\",\n        \"type\": \"row\"\n      },\n      {\n        \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND CRONJOBS</span>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 32\n        },\n        \"id\": 80,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      },\n      {\n        \"collapsed\": true,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 34\n        },\n        \"id\": 27,\n        \"panels\": [\n          {\n            \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">cj/$outbound</span>\\n</div>\",\n            \"gridPos\": {\n              \"h\": 2,\n              \"w\": 24,\n              \"x\": 0,\n              \"y\": 36\n            },\n            \"id\": 40,\n            \"links\": [],\n            \"mode\": \"html\",\n            \"options\": {},\n            \"title\": \"\",\n            \"transparent\": true,\n            \"type\": \"text\"\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 1,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 0,\n              \"y\": 38\n            },\n            \"id\": 28,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", dst_cronjob=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_cronjob) / sum(irate(response_total{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", dst_cronjob=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_cronjob)\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"cj/{{dst_cronjob}}\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"SUCCESS RATE\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 2,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"decimals\": null,\n                \"format\": \"percentunit\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": \"1\",\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 0,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 8,\n              \"y\": 38\n            },\n            \"id\": 35,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", dst_cronjob=\\\"$outbound\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_cronjob)\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"🔒cj/{{dst_cronjob}}\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", dst_cronjob=\\\"$outbound\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_cronjob)\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"cj/{{dst_cronjob}}\",\n                \"refId\": \"B\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"REQUEST RATE\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 2,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"rps\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": \"0\",\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 1,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 16,\n              \"y\": 38\n            },\n            \"id\": 41,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", dst_cronjob=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_cronjob))\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"P50 cj/{{dst_cronjob}}\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", dst_cronjob=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_cronjob))\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"P95 cj/{{dst_cronjob}}\",\n                \"refId\": \"B\"\n              },\n              {\n                \"expr\": \"histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\", dst_cronjob=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_cronjob))\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"P99 cj/{{dst_cronjob}}\",\n                \"refId\": \"C\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"LATENCY\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 2,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"ms\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          }\n        ],\n        \"repeat\": \"outbound\",\n        \"title\": \"cj/$outbound\",\n        \"type\": \"row\"\n      },\n      {\n      \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 3,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 35\n        },\n        \"height\": \"1px\",\n        \"id\": 171,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      }\n    ],\n    \"refresh\": \"1m\",\n    \"schemaVersion\": 18,\n    \"style\": \"dark\",\n    \"tags\": [\n      \"linkerd\"\n    ],\n    \"templating\": {\n      \"list\": [\n        {\n          \"current\": {\n            \"text\": \"default\",\n            \"value\": \"default\"\n          },\n          \"hide\": 0,\n          \"label\": \"Data Source\",\n          \"name\": \"datasource\",\n          \"options\": [],\n          \"query\": \"prometheus\",\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"type\": \"datasource\"\n        },\n        {\n          \"allValue\": \".*\",\n          \"current\": {},\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"definition\": \"\",\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"label\": \"Namespace\",\n          \"multi\": false,\n          \"name\": \"namespace\",\n          \"options\": [],\n          \"query\": \"label_values(process_start_time_seconds{cronjob!=\\\"\\\"}, namespace)\",\n          \"refresh\": 2,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 1,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"allValue\": \".*\",\n          \"current\": {},\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"definition\": \"\",\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"label\": \"Deployment\",\n          \"multi\": false,\n          \"name\": \"cronjob\",\n          \"options\": [],\n          \"query\": \"label_values(process_start_time_seconds{namespace=\\\"$namespace\\\"}, cronjob)\",\n          \"refresh\": 2,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 1,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"allValue\": \".*\",\n          \"current\": {},\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"definition\": \"\",\n          \"hide\": 2,\n          \"includeAll\": true,\n          \"label\": null,\n          \"multi\": false,\n          \"name\": \"inbound\",\n          \"options\": [],\n          \"query\": \"label_values(request_total{dst_namespace=\\\"$namespace\\\", dst_cronjob=\\\"$cronjob\\\"}, cronjob)\",\n          \"refresh\": 2,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 1,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"allValue\": \".*\",\n          \"current\": {},\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"definition\": \"\",\n          \"hide\": 2,\n          \"includeAll\": true,\n          \"label\": null,\n          \"multi\": false,\n          \"name\": \"outbound\",\n          \"options\": [],\n          \"query\": \"label_values(request_total{namespace=\\\"$namespace\\\", cronjob=\\\"$cronjob\\\"}, dst_cronjob)\",\n          \"refresh\": 2,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 1,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        }\n      ]\n    },\n    \"time\": {\n      \"from\": \"now-5m\",\n      \"to\": \"now\"\n    },\n    \"timepicker\": {\n      \"refresh_intervals\": [\n        \"5s\",\n        \"10s\",\n        \"30s\",\n        \"1m\",\n        \"5m\",\n        \"15m\",\n        \"30m\",\n        \"1h\",\n        \"2h\",\n        \"1d\"\n      ],\n      \"time_options\": [\n        \"5m\",\n        \"15m\",\n        \"1h\",\n        \"6h\",\n        \"12h\",\n        \"24h\",\n        \"2d\",\n        \"7d\",\n        \"30d\"\n      ]\n    },\n    \"timezone\": \"\",\n    \"title\": \"Linkerd CronJob\",\n    \"uid\": \"linkerd-cronjob\",\n    \"version\": 1\n  }\n  \n"
  },
  {
    "path": "grafana/dashboards/daemonset.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\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  \"gnetId\": null,\n  \"graphTooltip\": 1,\n  \"id\": null,\n  \"iteration\": 1547542312069,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"height:32px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 32px\\\">ds/$daemonset</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 20,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#d44a3a\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#299c46\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"percentunit\",\n      \"gauge\": {\n        \"maxValue\": 1,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 2\n      },\n      \"id\": 5,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) / sum(irate(response_total{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"0.9,.99\",\n      \"title\": \"SUCCESS RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 2\n      },\n      \"id\": 4,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \" RPS\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"REQUEST RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 4,\n        \"x\": 16,\n        \"y\": 2\n      },\n      \"id\": 11,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"100%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(count(request_total{dst_namespace=\\\"$namespace\\\", daemonset!=\\\"\\\", dst_daemonset!=\\\"\\\", dst_daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\"}) by (namespace, daemonset))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"INBOUND DAEMONSETS\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 4,\n        \"x\": 20,\n        \"y\": 2\n      },\n      \"id\": 15,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(count(request_total{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\"}) by (namespace, dst_daemonset))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"OUTBOUND DAEMONSETS\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND TRAFFIC</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 6\n      },\n      \"id\": 17,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 67,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (daemonset) / sum(irate(response_total{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (daemonset)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"ds/{{daemonset}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 8\n      },\n      \"id\": 2,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"inbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (daemonset)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒ds/{{daemonset}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"inbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (daemonset)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"ds/{{daemonset}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 8\n      },\n      \"id\": 68,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, daemonset))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p50 ds/{{daemonset}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, daemonset))\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p95 ds/{{daemonset}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, daemonset))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p99 ds/{{daemonset}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"ms\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 15\n      },\n      \"id\": 148,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 16\n          },\n          \"id\": 167,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"inbound\\\",errno!=\\\"\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}} {{errno}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTION FAILURES\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"none\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 16\n          },\n          \"id\": 168,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"inbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTIONS OPEN\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"short\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"cards\": {\n            \"cardPadding\": null,\n            \"cardRound\": null\n          },\n          \"color\": {\n            \"cardColor\": \"#b4ff00\",\n            \"colorScale\": \"sqrt\",\n            \"colorScheme\": \"interpolateOranges\",\n            \"exponent\": 0.5,\n            \"mode\": \"spectrum\"\n          },\n          \"dataFormat\": \"timeseries\",\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 16\n          },\n          \"heatmap\": {},\n          \"hideZeroBuckets\": false,\n          \"highlightCards\": true,\n          \"id\": 169,\n          \"legend\": {\n            \"show\": false\n          },\n          \"links\": [],\n          \"options\": {},\n          \"reverseYBuckets\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"inbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"refId\": \"A\"\n            }\n          ],\n          \"title\": \"TCP CONNECTION DURATION\",\n          \"tooltip\": {\n            \"show\": true,\n            \"showHistogram\": true\n          },\n          \"type\": \"heatmap\",\n          \"xAxis\": {\n            \"show\": true\n          },\n          \"xBucketNumber\": null,\n          \"xBucketSize\": null,\n          \"yAxis\": {\n            \"decimals\": null,\n            \"format\": \"dtdurationms\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true,\n            \"splitFactor\": null\n          },\n          \"yBucketBound\": \"auto\",\n          \"yBucketNumber\": null,\n          \"yBucketSize\": null\n        }\n      ],\n      \"title\": \"Inbound TCP Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"id\": 152,\n      \"panels\": [],\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND DAEMONSETS</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 17\n      },\n      \"id\": 76,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 19\n      },\n      \"id\": 59,\n      \"panels\": [\n        {\n          \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">ds/$inbound</span>\\n</div>\",\n          \"gridPos\": {\n            \"h\": 2,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 22.2\n          },\n          \"id\": 39,\n          \"links\": [],\n          \"mode\": \"html\",\n          \"options\": {},\n          \"title\": \"\",\n          \"transparent\": true,\n          \"type\": \"text\"\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 24.2\n          },\n          \"id\": 36,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", daemonset!=\\\"\\\", daemonset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (daemonset, pod) / sum(irate(response_total{daemonset!=\\\"\\\", daemonset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (daemonset, pod)\",\n              \"format\": \"time_series\",\n              \"instant\": false,\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"po/{{pod}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"SUCCESS RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"percentunit\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": \"1\",\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 24.2\n          },\n          \"id\": 22,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(request_total{daemonset!=\\\"\\\", daemonset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (daemonset, pod)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"🔒po/{{pod}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"sum(irate(request_total{daemonset!=\\\"\\\", daemonset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (daemonset, pod)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"po/{{pod}}\",\n              \"refId\": \"B\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"REQUEST RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"rps\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 24.2\n          },\n          \"id\": 29,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{daemonset!=\\\"\\\", daemonset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, daemonset))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P50 ds/{{daemonset}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{daemonset!=\\\"\\\", daemonset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, daemonset))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P95 ds/{{daemonset}}\",\n              \"refId\": \"B\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{daemonset!=\\\"\\\", daemonset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, daemonset))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P99 ds/{{daemonset}}\",\n              \"refId\": \"C\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"LATENCY\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"ms\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        }\n      ],\n      \"repeat\": \"inbound\",\n      \"title\": \"ds/$inbound\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 20\n      },\n      \"id\": 34,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND TRAFFIC</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"id\": 32,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 23\n      },\n      \"id\": 77,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_daemonset) / sum(irate(response_total{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_daemonset)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"ds/{{dst_daemonset}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 23\n      },\n      \"id\": 78,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_daemonset)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒ds/{{dst_daemonset}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_daemonset)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"ds/{{dst_daemonset}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 23\n      },\n      \"id\": 79,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_daemonset))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P95 ds/{{dst_daemonset}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"P95 LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 30\n      },\n      \"id\": 154,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 29\n          },\n          \"id\": 157,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\",errno!=\\\"\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}} {{errno}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTION FAILURES\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"none\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 29\n          },\n          \"id\": 166,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTIONS OPEN\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"short\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"cards\": {\n            \"cardPadding\": null,\n            \"cardRound\": null\n          },\n          \"color\": {\n            \"cardColor\": \"#b4ff00\",\n            \"colorScale\": \"sqrt\",\n            \"colorScheme\": \"interpolateOranges\",\n            \"exponent\": 0.5,\n            \"mode\": \"spectrum\"\n          },\n          \"dataFormat\": \"timeseries\",\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 29\n          },\n          \"heatmap\": {},\n          \"hideZeroBuckets\": false,\n          \"highlightCards\": true,\n          \"id\": 160,\n          \"legend\": {\n            \"show\": false\n          },\n          \"links\": [],\n          \"options\": {},\n          \"reverseYBuckets\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", direction=\\\"outbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"refId\": \"A\"\n            }\n          ],\n          \"title\": \"TCP CONNECTION DURATION\",\n          \"tooltip\": {\n            \"show\": true,\n            \"showHistogram\": true\n          },\n          \"type\": \"heatmap\",\n          \"xAxis\": {\n            \"show\": true\n          },\n          \"xBucketNumber\": null,\n          \"xBucketSize\": null,\n          \"yAxis\": {\n            \"decimals\": null,\n            \"format\": \"dtdurationms\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true,\n            \"splitFactor\": null\n          },\n          \"yBucketBound\": \"auto\",\n          \"yBucketNumber\": null,\n          \"yBucketSize\": null\n        }\n      ],\n      \"title\": \"Outbound TCP Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 31\n      },\n      \"id\": 156,\n      \"panels\": [],\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND DAEMONSETS</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 32\n      },\n      \"id\": 80,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 34\n      },\n      \"id\": 27,\n      \"panels\": [\n        {\n          \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">ds/$outbound</span>\\n</div>\",\n          \"gridPos\": {\n            \"h\": 2,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 36\n          },\n          \"id\": 40,\n          \"links\": [],\n          \"mode\": \"html\",\n          \"options\": {},\n          \"title\": \"\",\n          \"transparent\": true,\n          \"type\": \"text\"\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 38\n          },\n          \"id\": 28,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", dst_daemonset=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_daemonset) / sum(irate(response_total{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", dst_daemonset=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_daemonset)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"ds/{{dst_daemonset}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"SUCCESS RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"percentunit\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": \"1\",\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 38\n          },\n          \"id\": 35,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", dst_daemonset=\\\"$outbound\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_daemonset)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"🔒ds/{{dst_daemonset}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", dst_daemonset=\\\"$outbound\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_daemonset)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"ds/{{dst_daemonset}}\",\n              \"refId\": \"B\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"REQUEST RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"rps\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 38\n          },\n          \"id\": 41,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", dst_daemonset=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_daemonset))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P50 ds/{{dst_daemonset}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", dst_daemonset=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_daemonset))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P95 ds/{{dst_daemonset}}\",\n              \"refId\": \"B\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\", dst_daemonset=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_daemonset))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P99 ds/{{dst_daemonset}}\",\n              \"refId\": \"C\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"LATENCY\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"ms\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        }\n      ],\n      \"repeat\": \"outbound\",\n      \"title\": \"ds/$outbound\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 35\n      },\n      \"height\": \"1px\",\n      \"id\": 171,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    }\n  ],\n  \"refresh\": \"1m\",\n  \"schemaVersion\": 18,\n  \"style\": \"dark\",\n  \"tags\": [\n    \"linkerd\"\n  ],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"text\": \"default\",\n          \"value\": \"default\"\n        },\n        \"hide\": 0,\n        \"label\": \"Data Source\",\n        \"name\": \"datasource\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Namespace\",\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{daemonset!=\\\"\\\"}, namespace)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"DaemonSet\",\n        \"multi\": false,\n        \"name\": \"daemonset\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{namespace=\\\"$namespace\\\"}, daemonset)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"inbound\",\n        \"options\": [],\n        \"query\": \"label_values(request_total{dst_namespace=\\\"$namespace\\\", dst_daemonset=\\\"$daemonset\\\"}, daemonset)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"outbound\",\n        \"options\": [],\n        \"query\": \"label_values(request_total{namespace=\\\"$namespace\\\", daemonset=\\\"$daemonset\\\"}, dst_daemonset)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-5m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"\",\n  \"title\": \"Linkerd DaemonSet\",\n  \"uid\": \"linkerd-daemonset\",\n  \"version\": 1\n}\n"
  },
  {
    "path": "grafana/dashboards/deployment.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\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  \"gnetId\": null,\n  \"graphTooltip\": 1,\n  \"id\": null,\n  \"iteration\": 1531763681685,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"height:32px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 32px\\\">deploy/$deployment</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 20,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#d44a3a\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#299c46\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"percentunit\",\n      \"gauge\": {\n        \"maxValue\": 1,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 2\n      },\n      \"id\": 5,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\"}[$__rate_interval])) / sum(irate(response_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"0.9,.99\",\n      \"title\": \"SUCCESS RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 2\n      },\n      \"id\": 4,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \" RPS\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"REQUEST RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 4,\n        \"x\": 16,\n        \"y\": 2\n      },\n      \"id\": 11,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"100%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(count(request_total{dst_namespace=\\\"$namespace\\\", deployment!=\\\"\\\", dst_deployment!=\\\"\\\", dst_deployment=\\\"$deployment\\\", direction=\\\"outbound\\\"}) by (namespace, deployment))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"INBOUND DEPLOYMENTS\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 4,\n        \"x\": 20,\n        \"y\": 2\n      },\n      \"id\": 15,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(count(request_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"outbound\\\"}) by (namespace, dst_deployment))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"OUTBOUND DEPLOYMENTS\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND TRAFFIC</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 6\n      },\n      \"id\": 17,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 67,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (deployment) / sum(irate(response_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 8\n      },\n      \"id\": 2,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"deploy/{{deployment}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 8\n      },\n      \"id\": 68,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, deployment))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p50 deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, deployment))\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p95 deploy/{{deployment}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, deployment))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p99 deploy/{{deployment}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"ms\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 15\n      },\n      \"id\": 148,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 16\n          },\n          \"id\": 167,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\",errno!=\\\"\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}} {{errno}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTION FAILURES\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"none\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 16\n          },\n          \"id\": 168,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTIONS OPEN\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"short\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"cards\": {\n            \"cardPadding\": null,\n            \"cardRound\": null\n          },\n          \"color\": {\n            \"cardColor\": \"#b4ff00\",\n            \"colorScale\": \"sqrt\",\n            \"colorScheme\": \"interpolateOranges\",\n            \"exponent\": 0.5,\n            \"mode\": \"spectrum\"\n          },\n          \"dataFormat\": \"timeseries\",\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 16\n          },\n          \"heatmap\": {},\n          \"hideZeroBuckets\": false,\n          \"highlightCards\": true,\n          \"id\": 169,\n          \"legend\": {\n            \"show\": false\n          },\n          \"links\": [],\n          \"options\": {},\n          \"reverseYBuckets\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"refId\": \"A\"\n            }\n          ],\n          \"title\": \"TCP CONNECTION DURATION\",\n          \"tooltip\": {\n            \"show\": true,\n            \"showHistogram\": true\n          },\n          \"type\": \"heatmap\",\n          \"xAxis\": {\n            \"show\": true\n          },\n          \"xBucketNumber\": null,\n          \"xBucketSize\": null,\n          \"yAxis\": {\n            \"decimals\": null,\n            \"format\": \"dtdurationms\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true,\n            \"splitFactor\": null\n          },\n          \"yBucketBound\": \"auto\",\n          \"yBucketNumber\": null,\n          \"yBucketSize\": null\n        }\n      ],\n      \"title\": \"Inbound TCP Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"id\": 152,\n      \"panels\": [],\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND DEPLOYMENTS</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 17\n      },\n      \"id\": 76,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 19\n      },\n      \"id\": 59,\n      \"panels\": [\n        {\n          \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">deploy/$inbound</span>\\n</div>\",\n          \"gridPos\": {\n            \"h\": 2,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 22.2\n          },\n          \"id\": 39,\n          \"links\": [],\n          \"mode\": \"html\",\n          \"options\": {},\n          \"title\": \"\",\n          \"transparent\": true,\n          \"type\": \"text\"\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 24.2\n          },\n          \"id\": 36,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", deployment!=\\\"\\\", deployment=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_deployment=\\\"$deployment\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (deployment, pod) / sum(irate(response_total{deployment!=\\\"\\\", deployment=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_deployment=\\\"$deployment\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (deployment, pod)\",\n              \"format\": \"time_series\",\n              \"instant\": false,\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"po/{{pod}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"SUCCESS RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"percentunit\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": \"1\",\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 24.2\n          },\n          \"id\": 22,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(request_total{deployment!=\\\"\\\", deployment=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_deployment=\\\"$deployment\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (deployment, pod)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"🔒po/{{pod}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"sum(irate(request_total{deployment!=\\\"\\\", deployment=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_deployment=\\\"$deployment\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (deployment, pod)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"po/{{pod}}\",\n              \"refId\": \"B\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"REQUEST RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"rps\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 24.2\n          },\n          \"id\": 29,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{deployment!=\\\"\\\", deployment=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_deployment=\\\"$deployment\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, deployment))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P50 deploy/{{deployment}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{deployment!=\\\"\\\", deployment=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_deployment=\\\"$deployment\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, deployment))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P95 deploy/{{deployment}}\",\n              \"refId\": \"B\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{deployment!=\\\"\\\", deployment=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_deployment=\\\"$deployment\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, deployment))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P99 deploy/{{deployment}}\",\n              \"refId\": \"C\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"LATENCY\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"ms\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        }\n      ],\n      \"repeat\": \"inbound\",\n      \"title\": \"deploy/$inbound\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 20\n      },\n      \"id\": 34,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND TRAFFIC</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"id\": 32,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 23\n      },\n      \"id\": 77,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_deployment) / sum(irate(response_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"deploy/{{dst_deployment}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 23\n      },\n      \"id\": 78,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒deploy/{{dst_deployment}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"deploy/{{dst_deployment}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 23\n      },\n      \"id\": 79,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_deployment))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P95 deploy/{{dst_deployment}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"P95 LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 30\n      },\n      \"id\": 154,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 29\n          },\n          \"id\": 157,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"outbound\\\",errno!=\\\"\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}} {{errno}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTION FAILURES\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"none\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 29\n          },\n          \"id\": 166,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"outbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTIONS OPEN\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"short\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"cards\": {\n            \"cardPadding\": null,\n            \"cardRound\": null\n          },\n          \"color\": {\n            \"cardColor\": \"#b4ff00\",\n            \"colorScale\": \"sqrt\",\n            \"colorScheme\": \"interpolateOranges\",\n            \"exponent\": 0.5,\n            \"mode\": \"spectrum\"\n          },\n          \"dataFormat\": \"timeseries\",\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 29\n          },\n          \"heatmap\": {},\n          \"hideZeroBuckets\": false,\n          \"highlightCards\": true,\n          \"id\": 160,\n          \"legend\": {\n            \"show\": false\n          },\n          \"links\": [],\n          \"options\": {},\n          \"reverseYBuckets\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"outbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"refId\": \"A\"\n            }\n          ],\n          \"title\": \"TCP CONNECTION DURATION\",\n          \"tooltip\": {\n            \"show\": true,\n            \"showHistogram\": true\n          },\n          \"type\": \"heatmap\",\n          \"xAxis\": {\n            \"show\": true\n          },\n          \"xBucketNumber\": null,\n          \"xBucketSize\": null,\n          \"yAxis\": {\n            \"decimals\": null,\n            \"format\": \"dtdurationms\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true,\n            \"splitFactor\": null\n          },\n          \"yBucketBound\": \"auto\",\n          \"yBucketNumber\": null,\n          \"yBucketSize\": null\n        }\n      ],\n      \"title\": \"Outbound TCP Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 31\n      },\n      \"id\": 156,\n      \"panels\": [],\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND DEPLOYMENTS</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 32\n      },\n      \"id\": 80,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 34\n      },\n      \"id\": 27,\n      \"panels\": [\n        {\n          \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">deploy/$outbound</span>\\n</div>\",\n          \"gridPos\": {\n            \"h\": 2,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 36\n          },\n          \"id\": 40,\n          \"links\": [],\n          \"mode\": \"html\",\n          \"options\": {},\n          \"title\": \"\",\n          \"transparent\": true,\n          \"type\": \"text\"\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 38\n          },\n          \"id\": 28,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", dst_deployment=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_deployment) / sum(irate(response_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", dst_deployment=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_deployment)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"deploy/{{dst_deployment}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"SUCCESS RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"percentunit\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": \"1\",\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 38\n          },\n          \"id\": 35,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", dst_deployment=\\\"$outbound\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_deployment)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"🔒deploy/{{dst_deployment}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", dst_deployment=\\\"$outbound\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_deployment)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"deploy/{{dst_deployment}}\",\n              \"refId\": \"B\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"REQUEST RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"rps\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 38\n          },\n          \"id\": 41,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", dst_deployment=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_deployment))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P50 deploy/{{dst_deployment}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", dst_deployment=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_deployment))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P95 deploy/{{dst_deployment}}\",\n              \"refId\": \"B\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", dst_deployment=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_deployment))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P99 deploy/{{dst_deployment}}\",\n              \"refId\": \"C\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"LATENCY\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"ms\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        }\n      ],\n      \"repeat\": \"outbound\",\n      \"title\": \"deploy/$outbound\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 35\n      },\n      \"height\": \"1px\",\n      \"id\": 171,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    }\n  ],\n  \"refresh\": \"1m\",\n  \"schemaVersion\": 18,\n  \"style\": \"dark\",\n  \"tags\": [\n    \"linkerd\"\n  ],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"text\": \"default\",\n          \"value\": \"default\"\n        },\n        \"hide\": 0,\n        \"label\": \"Data Source\",\n        \"name\": \"datasource\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Namespace\",\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{deployment!=\\\"\\\"}, namespace)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Deployment\",\n        \"multi\": false,\n        \"name\": \"deployment\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{namespace=\\\"$namespace\\\"}, deployment)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"inbound\",\n        \"options\": [],\n        \"query\": \"label_values(request_total{dst_namespace=\\\"$namespace\\\", dst_deployment=\\\"$deployment\\\"}, deployment)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"outbound\",\n        \"options\": [],\n        \"query\": \"label_values(request_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\"}, dst_deployment)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-5m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"\",\n  \"title\": \"Linkerd Deployment\",\n  \"uid\": \"linkerd-deployment\",\n  \"version\": 1\n}\n"
  },
  {
    "path": "grafana/dashboards/health.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\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  \"gnetId\": null,\n  \"graphTooltip\": 1,\n  \"id\": null,\n  \"iteration\": 1531764407999,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>Data-Plane Telemetry</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 400,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 2\n      },\n      \"id\": 397,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"process_virtual_memory_bytes{job=\\\"linkerd-proxy\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{namespace}}/{{pod}}/virtual\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"process_resident_memory_bytes{job=\\\"linkerd-proxy\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{namespace}}/{{pod}}/resident\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"MEMORY USAGE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"decbytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 2\n      },\n      \"id\": 399,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(process_cpu_seconds_total{job=\\\"linkerd-proxy\\\"}[$__rate_interval])) by (namespace, pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{namespace}}/{{pod}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"CPU USAGE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 2\n      },\n      \"id\": 398,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"process_open_fds{job=\\\"linkerd-proxy\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{namespace}}/{{pod}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"OPEN FILE DESCRIPTORS\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>Control-Plane Traffic</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"id\": 401,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 11\n      },\n      \"id\": 23,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"scopedVars\": {\n        \"deployment\": {\n          \"selected\": false\n        }\n      },\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", deployment!=\\\"\\\", namespace=~\\\"$namespace|$namespace-viz\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (pod) / sum(irate(response_total{deployment!=\\\"\\\", namespace=~\\\"$namespace|$namespace-viz\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"po/{{pod}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"percentunit\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 11\n      },\n      \"id\": 24,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"scopedVars\": {\n        \"deployment\": {\n          \"selected\": false\n        }\n      },\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{deployment!=\\\"\\\", namespace=~\\\"$namespace|$namespace-viz\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"po/{{pod}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"description\": \"\",\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 11\n      },\n      \"id\": 25,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"scopedVars\": {\n        \"deployment\": {\n          \"selected\": false\n        }\n      },\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{deployment!=\\\"\\\", namespace=~\\\"$namespace|$namespace-viz\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, pod))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"po/{{pod}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"P95 LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>Control-Plane TCP Metrics</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 18\n      },\n      \"id\": 731,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 20\n      },\n      \"id\": 179,\n      \"panels\": [\n        {\n          \"content\": \"<div>\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:40px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">deploy/$deployment</span>\\n</div>\",\n          \"gridPos\": {\n            \"h\": 2,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 21\n          },\n          \"id\": 282,\n          \"links\": [],\n          \"mode\": \"html\",\n          \"options\": {},\n          \"repeatedByRow\": true,\n          \"scopedVars\": {\n            \"deployment\": {\n              \"selected\": false\n            }\n          },\n          \"title\": \"\",\n          \"transparent\": true,\n          \"type\": \"text\"\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 23\n          },\n          \"id\": 227,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 1,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"scopedVars\": {\n            \"deployment\": {\n              \"selected\": false\n            }\n          },\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_close_total{deployment=\\\"$deployment\\\", namespace=~\\\"$namespace|$namespace-viz\\\", direction=\\\"inbound\\\",errno!=\\\"\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}} {{errno}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTION FAILURES\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 23\n          },\n          \"id\": 132,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 1,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"scopedVars\": {\n            \"deployment\": {\n              \"selected\": false\n            }\n          },\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_open_connections{deployment=\\\"$deployment\\\", namespace=~\\\"$namespace|$namespace-viz\\\", direction=\\\"inbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"OPEN TCP CONNECTIONS\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"cards\": {\n            \"cardPadding\": null,\n            \"cardRound\": null\n          },\n          \"color\": {\n            \"cardColor\": \"#b4ff00\",\n            \"colorScale\": \"sqrt\",\n            \"colorScheme\": \"interpolateOranges\",\n            \"exponent\": 0.5,\n            \"mode\": \"spectrum\"\n          },\n          \"dataFormat\": \"timeseries\",\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 23\n          },\n          \"heatmap\": {},\n          \"hideZeroBuckets\": false,\n          \"highlightCards\": true,\n          \"id\": 229,\n          \"legend\": {\n            \"show\": false\n          },\n          \"links\": [],\n          \"options\": {},\n          \"reverseYBuckets\": false,\n          \"scopedVars\": {\n            \"deployment\": {\n              \"selected\": false\n            }\n          },\n          \"targets\": [\n            {\n              \"expr\": \"tcp_connection_duration_ms_bucket{deployment=\\\"$deployment\\\", namespace=~\\\"$namespace|$namespace-viz\\\", direction=\\\"inbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"refId\": \"A\"\n            }\n          ],\n          \"title\": \"TCP CONNECTION DURATION\",\n          \"tooltip\": {\n            \"show\": true,\n            \"showHistogram\": true\n          },\n          \"type\": \"heatmap\",\n          \"xAxis\": {\n            \"show\": true\n          },\n          \"xBucketNumber\": null,\n          \"xBucketSize\": null,\n          \"yAxis\": {\n            \"decimals\": null,\n            \"format\": \"short\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true,\n            \"splitFactor\": null\n          },\n          \"yBucketBound\": \"auto\",\n          \"yBucketNumber\": null,\n          \"yBucketSize\": null\n        }\n      ],\n      \"repeat\": \"deployment\",\n      \"scopedVars\": {\n        \"deployment\": {\n          \"selected\": false\n        }\n      },\n      \"title\": \"$deployment\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>Control-Plane Telemetry</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 28\n      },\n      \"id\": 27,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 30\n      },\n      \"id\": 2,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"go_goroutines{job=\\\"linkerd-controller\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{component}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"goroutines\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 30\n      },\n      \"id\": 5,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"process_resident_memory_bytes{job=\\\"linkerd-controller\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{component}}/resident\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"process_virtual_memory_bytes{job=\\\"linkerd-controller\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{component}}/virtual\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Process Memory\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"decbytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 30\n      },\n      \"id\": 7,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"process_open_fds{job=\\\"linkerd-controller\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{component}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Open FDs\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 37\n      },\n      \"id\": 9,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"go_threads{job=\\\"linkerd-controller\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{component}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Threads\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>Control-Plane Clients/Servers</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 44\n      },\n      \"id\": 625,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 46\n      },\n      \"id\": 622,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(http_server_requests_total{job=\\\"linkerd-controller\\\"}[$__rate_interval])) by (component, method, code)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{component}}/{{method}}/{{code}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Server Request Rate\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 46\n      },\n      \"id\": 12,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.5, sum(rate(http_server_request_latency_seconds_bucket{job=\\\"linkerd-controller\\\"}[$__rate_interval])) by (le, component, method, code))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P50 {{component}}/{{method}}/{{code}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(http_server_request_latency_seconds_bucket{job=\\\"linkerd-controller\\\"}[$__rate_interval])) by (le, component, method, code))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P95 {{component}}/{{method}}/{{code}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.99, sum(rate(http_server_request_latency_seconds_bucket{job=\\\"linkerd-controller\\\"}[$__rate_interval])) by (le, component, method, code))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P99 {{component}}/{{method}}/{{code}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"HTTP Server Latency\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 46\n      },\n      \"id\": 624,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.5, sum(rate(http_server_response_size_bytes_bucket{job=\\\"linkerd-controller\\\"}[$__rate_interval])) by (le, component, method, code))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P50 {{component}}/{{method}}/{{code}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(http_server_response_size_bytes_bucket{job=\\\"linkerd-controller\\\"}[$__rate_interval])) by (le, component, method, code))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P95 {{component}}/{{method}}/{{code}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.99, sum(rate(http_server_response_size_bytes_bucket{job=\\\"linkerd-controller\\\"}[$__rate_interval])) by (le, component, method, code))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P99 {{component}}/{{method}}/{{code}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"HTTP Server Response Size\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"decbytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 53\n      },\n      \"id\": 623,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(http_client_requests_total{job=\\\"linkerd-controller\\\"}[$__rate_interval])) by (component, client, method, code)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{component}}/{{client}}/{{method}}/{{code}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Client Request Rate\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 53\n      },\n      \"id\": 570,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.5, sum(rate(http_client_request_latency_seconds_bucket{job=\\\"linkerd-controller\\\"}[$__rate_interval])) by (le, component, client, method, code))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P50 {{component}}/{{client}}/{{method}}/{{code}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(http_client_request_latency_seconds_bucket{job=\\\"linkerd-controller\\\"}[$__rate_interval])) by (le, component, client, method, code))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P95 {{component}}/{{client}}/{{method}}/{{code}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.99, sum(rate(http_client_request_latency_seconds_bucket{job=\\\"linkerd-controller\\\"}[$__rate_interval])) by (le, component, client, method, code))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P99 {{component}}/{{client}}/{{method}}/{{code}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"HTTP Client Latency\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 53\n      },\n      \"id\": 621,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(http_client_in_flight_requests{job=\\\"linkerd-controller\\\"}) by (component, client)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{component}}/{{client}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Client In-Flight Requests\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 60\n      },\n      \"id\": 458,\n      \"panels\": [],\n      \"repeat\": \"component\",\n      \"scopedVars\": {\n        \"component\": {\n          \"selected\": false\n        }\n      },\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div>\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:40px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">$component</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 61\n      },\n      \"id\": 30,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"scopedVars\": {\n        \"component\": {\n          \"selected\": false\n        }\n      },\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 63\n      },\n      \"id\": 6,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"scopedVars\": {\n        \"component\": {\n          \"selected\": false\n        }\n      },\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"go_memstats_alloc_bytes{job=\\\"linkerd-controller\\\", component=\\\"$component\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"alloc/{{component}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"irate(go_memstats_alloc_bytes_total{job=\\\"linkerd-controller\\\", component=\\\"$component\\\"}[$__rate_interval])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"alloc rate/{{component}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"go_memstats_stack_inuse_bytes{job=\\\"linkerd-controller\\\", component=\\\"$component\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"stack/{{component}}\",\n          \"refId\": \"C\"\n        },\n        {\n          \"expr\": \"go_memstats_heap_inuse_bytes{job=\\\"linkerd-controller\\\", component=\\\"$component\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"heap/{{component}}\",\n          \"refId\": \"D\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Go Memstats\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"decbytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 63\n      },\n      \"id\": 8,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"scopedVars\": {\n        \"component\": {\n          \"selected\": false\n        }\n      },\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"go_gc_duration_seconds{job=\\\"linkerd-controller\\\", quantile=\\\"0.5\\\", component=\\\"$component\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P50 {{component}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"go_gc_duration_seconds{job=\\\"linkerd-controller\\\", quantile=\\\"0.75\\\", component=\\\"$component\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P75 {{component}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"go_gc_duration_seconds{job=\\\"linkerd-controller\\\", quantile=\\\"1\\\", component=\\\"$component\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Max {{component}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"GC Duration\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 63\n      },\n      \"id\": 14,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"scopedVars\": {\n        \"component\": {\n          \"selected\": false\n        }\n      },\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"irate(grpc_server_msg_sent_total{job=\\\"linkerd-controller\\\", component=\\\"$component\\\"}[$__rate_interval])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"sent/{{component}}/{{grpc_method}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"irate(grpc_server_msg_received_total{job=\\\"linkerd-controller\\\", component=\\\"$component\\\"}[$__rate_interval])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"received/{{component}}/{{grpc_method}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"gRPC Message Volume\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 141.4\n      },\n      \"id\": 515,\n      \"panels\": [],\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 142.4\n      },\n      \"height\": \"1px\",\n      \"id\": 519,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    }\n  ],\n  \"refresh\": \"1m\",\n  \"schemaVersion\": 18,\n  \"style\": \"dark\",\n  \"tags\": [\n    \"linkerd\"\n  ],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"text\": \"default\",\n          \"value\": \"default\"\n        },\n        \"hide\": 0,\n        \"label\": \"Data Source\",\n        \"name\": \"datasource\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {\n          \"text\": \"All\",\n          \"value\": \"$__all\"\n        },\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": \"Deployment\",\n        \"multi\": false,\n        \"name\": \"deployment\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{namespace=~\\\"$namespace|$namespace-viz\\\"}, deployment)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {\n          \"text\": \"All\",\n          \"value\": \"$__all\"\n        },\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": \"Component\",\n        \"multi\": false,\n        \"name\": \"component\",\n        \"options\": [],\n        \"query\": \"label_values(component)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Linkerd Namespace\",\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{control_plane_ns!=\\\"\\\"},  control_plane_ns)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Linkerd Viz Namespace\",\n        \"multi\": false,\n        \"name\": \"namespace-viz\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{control_plane_ns!=\\\"\\\",extension!=\\\"\\\"},  namespace)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-5m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"\",\n  \"title\": \"Linkerd Health\",\n  \"uid\": \"linkerd-health\",\n  \"version\": 1\n}"
  },
  {
    "path": "grafana/dashboards/job.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\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  \"gnetId\": null,\n  \"graphTooltip\": 1,\n  \"id\": null,\n  \"iteration\": 1547542312069,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"height:32px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 32px\\\">job/$job</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 20,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#d44a3a\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#299c46\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"percentunit\",\n      \"gauge\": {\n        \"maxValue\": 1,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 2\n      },\n      \"id\": 5,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"inbound\\\"}[$__rate_interval])) / sum(irate(response_total{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"0.9,.99\",\n      \"title\": \"SUCCESS RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 2\n      },\n      \"id\": 4,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \" RPS\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"REQUEST RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 4,\n        \"x\": 16,\n        \"y\": 2\n      },\n      \"id\": 11,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"100%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(count(request_total{dst_namespace=\\\"$namespace\\\", k8s_job!=\\\"\\\", dst_k8s_job!=\\\"\\\", dst_k8s_job=\\\"$job\\\", direction=\\\"outbound\\\"}) by (namespace, k8s_job))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"INBOUND JOBS\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 4,\n        \"x\": 20,\n        \"y\": 2\n      },\n      \"id\": 15,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(count(request_total{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"outbound\\\"}) by (namespace, dst_k8s_job))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"OUTBOUND JOBS\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND TRAFFIC</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 6\n      },\n      \"id\": 17,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 67,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (k8s_job) / sum(irate(response_total{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (k8s_job)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"job/{{k8s_job}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 8\n      },\n      \"id\": 2,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"inbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (k8s_job)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒job/{{k8s_job}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"inbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (k8s_job)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"job/{{k8s_job}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 8\n      },\n      \"id\": 68,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, k8s_job))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p50 job/{{k8s_job}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, k8s_job))\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p95 job/{{k8s_job}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, k8s_job))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p99 job/{{k8s_job}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"ms\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 15\n      },\n      \"id\": 148,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 16\n          },\n          \"id\": 167,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"inbound\\\",errno!=\\\"\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}} {{errno}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTION FAILURES\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"none\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 16\n          },\n          \"id\": 168,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"inbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTIONS OPEN\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"short\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"cards\": {\n            \"cardPadding\": null,\n            \"cardRound\": null\n          },\n          \"color\": {\n            \"cardColor\": \"#b4ff00\",\n            \"colorScale\": \"sqrt\",\n            \"colorScheme\": \"interpolateOranges\",\n            \"exponent\": 0.5,\n            \"mode\": \"spectrum\"\n          },\n          \"dataFormat\": \"timeseries\",\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 16\n          },\n          \"heatmap\": {},\n          \"hideZeroBuckets\": false,\n          \"highlightCards\": true,\n          \"id\": 169,\n          \"legend\": {\n            \"show\": false\n          },\n          \"links\": [],\n          \"options\": {},\n          \"reverseYBuckets\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"inbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"refId\": \"A\"\n            }\n          ],\n          \"title\": \"TCP CONNECTION DURATION\",\n          \"tooltip\": {\n            \"show\": true,\n            \"showHistogram\": true\n          },\n          \"type\": \"heatmap\",\n          \"xAxis\": {\n            \"show\": true\n          },\n          \"xBucketNumber\": null,\n          \"xBucketSize\": null,\n          \"yAxis\": {\n            \"decimals\": null,\n            \"format\": \"dtdurationms\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true,\n            \"splitFactor\": null\n          },\n          \"yBucketBound\": \"auto\",\n          \"yBucketNumber\": null,\n          \"yBucketSize\": null\n        }\n      ],\n      \"title\": \"Inbound TCP Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"id\": 152,\n      \"panels\": [],\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND JOBS</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 17\n      },\n      \"id\": 76,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 19\n      },\n      \"id\": 59,\n      \"panels\": [\n        {\n          \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">job/$inbound</span>\\n</div>\",\n          \"gridPos\": {\n            \"h\": 2,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 22.2\n          },\n          \"id\": 39,\n          \"links\": [],\n          \"mode\": \"html\",\n          \"options\": {},\n          \"title\": \"\",\n          \"transparent\": true,\n          \"type\": \"text\"\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 24.2\n          },\n          \"id\": 36,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", k8s_job!=\\\"\\\", k8s_job=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_k8s_job=\\\"$job\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (k8s_job, pod) / sum(irate(response_total{k8s_job!=\\\"\\\", k8s_job=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_k8s_job=\\\"$job\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (k8s_job, pod)\",\n              \"format\": \"time_series\",\n              \"instant\": false,\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"po/{{pod}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"SUCCESS RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"percentunit\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": \"1\",\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 24.2\n          },\n          \"id\": 22,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(request_total{k8s_job!=\\\"\\\", k8s_job=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_k8s_job=\\\"$job\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (k8s_job, pod)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"🔒po/{{pod}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"sum(irate(request_total{k8s_job!=\\\"\\\", k8s_job=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_k8s_job=\\\"$job\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (k8s_job, pod)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"po/{{pod}}\",\n              \"refId\": \"B\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"REQUEST RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"rps\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 24.2\n          },\n          \"id\": 29,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{k8s_job!=\\\"\\\", k8s_job=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_k8s_job=\\\"$job\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, k8s_job))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P50 job/{{k8s_job}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{k8s_job!=\\\"\\\", k8s_job=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_k8s_job=\\\"$job\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, k8s_job))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P95 job/{{k8s_job}}\",\n              \"refId\": \"B\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{k8s_job!=\\\"\\\", k8s_job=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_k8s_job=\\\"$job\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, k8s_job))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P99 job/{{k8s_job}}\",\n              \"refId\": \"C\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"LATENCY\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"ms\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        }\n      ],\n      \"repeat\": \"inbound\",\n      \"title\": \"job/$inbound\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 20\n      },\n      \"id\": 34,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND TRAFFIC</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"id\": 32,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 23\n      },\n      \"id\": 77,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_k8s_job) / sum(irate(response_total{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_k8s_job)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"job/{{dst_k8s_job}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 23\n      },\n      \"id\": 78,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_k8s_job)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒job/{{dst_k8s_job}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_k8s_job)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"job/{{dst_k8s_job}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 23\n      },\n      \"id\": 79,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_k8s_job))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P95 job/{{dst_k8s_job}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"P95 LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 30\n      },\n      \"id\": 154,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 29\n          },\n          \"id\": 157,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"outbound\\\",errno!=\\\"\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}} {{errno}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTION FAILURES\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"none\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 29\n          },\n          \"id\": 166,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"outbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTIONS OPEN\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"short\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"cards\": {\n            \"cardPadding\": null,\n            \"cardRound\": null\n          },\n          \"color\": {\n            \"cardColor\": \"#b4ff00\",\n            \"colorScale\": \"sqrt\",\n            \"colorScheme\": \"interpolateOranges\",\n            \"exponent\": 0.5,\n            \"mode\": \"spectrum\"\n          },\n          \"dataFormat\": \"timeseries\",\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 29\n          },\n          \"heatmap\": {},\n          \"hideZeroBuckets\": false,\n          \"highlightCards\": true,\n          \"id\": 160,\n          \"legend\": {\n            \"show\": false\n          },\n          \"links\": [],\n          \"options\": {},\n          \"reverseYBuckets\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", direction=\\\"outbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"refId\": \"A\"\n            }\n          ],\n          \"title\": \"TCP CONNECTION DURATION\",\n          \"tooltip\": {\n            \"show\": true,\n            \"showHistogram\": true\n          },\n          \"type\": \"heatmap\",\n          \"xAxis\": {\n            \"show\": true\n          },\n          \"xBucketNumber\": null,\n          \"xBucketSize\": null,\n          \"yAxis\": {\n            \"decimals\": null,\n            \"format\": \"dtdurationms\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true,\n            \"splitFactor\": null\n          },\n          \"yBucketBound\": \"auto\",\n          \"yBucketNumber\": null,\n          \"yBucketSize\": null\n        }\n      ],\n      \"title\": \"Outbound TCP Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 31\n      },\n      \"id\": 156,\n      \"panels\": [],\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND JOBS</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 32\n      },\n      \"id\": 80,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 34\n      },\n      \"id\": 27,\n      \"panels\": [\n        {\n          \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">job/$outbound</span>\\n</div>\",\n          \"gridPos\": {\n            \"h\": 2,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 36\n          },\n          \"id\": 40,\n          \"links\": [],\n          \"mode\": \"html\",\n          \"options\": {},\n          \"title\": \"\",\n          \"transparent\": true,\n          \"type\": \"text\"\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 38\n          },\n          \"id\": 28,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", dst_k8s_job=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_k8s_job) / sum(irate(response_total{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", dst_k8s_job=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_k8s_job)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"job/{{dst_k8s_job}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"SUCCESS RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"percentunit\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": \"1\",\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 38\n          },\n          \"id\": 35,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", dst_k8s_job=\\\"$outbound\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_k8s_job)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"🔒job/{{dst_k8s_job}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", dst_k8s_job=\\\"$outbound\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_k8s_job)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"job/{{dst_k8s_job}}\",\n              \"refId\": \"B\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"REQUEST RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"rps\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 38\n          },\n          \"id\": 41,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", dst_k8s_job=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_k8s_job))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P50 job/{{dst_k8s_job}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", dst_k8s_job=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_k8s_job))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P95 job/{{dst_k8s_job}}\",\n              \"refId\": \"B\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\", dst_k8s_job=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_k8s_job))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P99 job/{{dst_k8s_job}}\",\n              \"refId\": \"C\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"LATENCY\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"ms\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        }\n      ],\n      \"repeat\": \"outbound\",\n      \"title\": \"job/$outbound\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 35\n      },\n      \"height\": \"1px\",\n      \"id\": 171,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    }\n  ],\n  \"refresh\": \"1m\",\n  \"schemaVersion\": 18,\n  \"style\": \"dark\",\n  \"tags\": [\n    \"linkerd\"\n  ],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"text\": \"default\",\n          \"value\": \"default\"\n        },\n        \"hide\": 0,\n        \"label\": \"Data Source\",\n        \"name\": \"datasource\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Namespace\",\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{k8s_job!=\\\"\\\"}, namespace)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Job\",\n        \"multi\": false,\n        \"name\": \"job\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{namespace=\\\"$namespace\\\"}, k8s_job)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"inbound\",\n        \"options\": [],\n        \"query\": \"label_values(request_total{dst_namespace=\\\"$namespace\\\", dst_k8s_job=\\\"$job\\\"}, k8s_job)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"outbound\",\n        \"options\": [],\n        \"query\": \"label_values(request_total{namespace=\\\"$namespace\\\", k8s_job=\\\"$job\\\"}, dst_k8s_job)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-5m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"\",\n  \"title\": \"Linkerd Job\",\n  \"uid\": \"linkerd-job\",\n  \"version\": 1\n}\n"
  },
  {
    "path": "grafana/dashboards/kubernetes.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"description\": \"Monitors Kubernetes cluster using Prometheus. Shows overall cluster CPU / Memory / Filesystem usage as well as individual pod, containers, systemd services statistics. Uses cAdvisor metrics only.\",\n  \"editable\": true,\n  \"gnetId\": 315,\n  \"graphTooltip\": 0,\n  \"id\": null,\n  \"iteration\": 1565301085323,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 33,\n      \"panels\": [],\n      \"title\": \"Network I/O pressure\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"height\": \"200px\",\n      \"id\": 32,\n      \"isNew\": true,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": false,\n        \"sideWidth\": 200,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum (rate (container_network_receive_bytes_total{kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m]))\",\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Received\",\n          \"metric\": \"network\",\n          \"refId\": \"A\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"- sum (rate (container_network_transmit_bytes_total{kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m]))\",\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Sent\",\n          \"metric\": \"network\",\n          \"refId\": \"B\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Network I/O pressure\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"Bps\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"Bps\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 6\n      },\n      \"id\": 34,\n      \"panels\": [],\n      \"title\": \"Total usage\",\n      \"type\": \"row\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": true,\n      \"colors\": [\n        \"rgba(50, 172, 45, 0.97)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(245, 54, 54, 0.9)\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"format\": \"percent\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 7\n      },\n      \"height\": \"180px\",\n      \"id\": 4,\n      \"interval\": null,\n      \"isNew\": true,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum (container_memory_working_set_bytes{id=\\\"/\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}) / sum (machine_memory_bytes{kubernetes_io_hostname=~\\\"^$Node$\\\"}) * 100\",\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": \"65, 90\",\n      \"title\": \"Cluster memory usage\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": true,\n      \"colors\": [\n        \"rgba(50, 172, 45, 0.97)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(245, 54, 54, 0.9)\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"format\": \"percent\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 7\n      },\n      \"height\": \"180px\",\n      \"id\": 6,\n      \"interval\": null,\n      \"isNew\": true,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum (rate (container_cpu_usage_seconds_total{id=\\\"/\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) / sum (machine_cpu_cores{kubernetes_io_hostname=~\\\"^$Node$\\\"}) * 100\",\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": \"65, 90\",\n      \"title\": \"Cluster CPU usage (1m avg)\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": true,\n      \"colors\": [\n        \"rgba(50, 172, 45, 0.97)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(245, 54, 54, 0.9)\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"format\": \"percent\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 7\n      },\n      \"height\": \"180px\",\n      \"id\": 7,\n      \"interval\": null,\n      \"isNew\": true,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum (container_fs_usage_bytes{device=~\\\"^/dev/[sv]d[a-z][1-9]?$\\\",id=\\\"/\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}) / sum (container_fs_limit_bytes{device=~\\\"^/dev/[sv]d[a-z][1-9]$\\\",id=\\\"/\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}) * 100\",\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": \"65, 90\",\n      \"title\": \"Cluster filesystem usage\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"rgba(50, 172, 45, 0.97)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(245, 54, 54, 0.9)\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"format\": \"bytes\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 4,\n        \"x\": 0,\n        \"y\": 12\n      },\n      \"height\": \"1px\",\n      \"id\": 9,\n      \"interval\": null,\n      \"isNew\": true,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"20%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"20%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum (container_memory_working_set_bytes{id=\\\"/\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"})\",\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Used\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"50%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"rgba(50, 172, 45, 0.97)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(245, 54, 54, 0.9)\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"format\": \"bytes\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 4,\n        \"x\": 4,\n        \"y\": 12\n      },\n      \"height\": \"1px\",\n      \"id\": 10,\n      \"interval\": null,\n      \"isNew\": true,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum (machine_memory_bytes{kubernetes_io_hostname=~\\\"^$Node$\\\"})\",\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Total\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"50%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"rgba(50, 172, 45, 0.97)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(245, 54, 54, 0.9)\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 4,\n        \"x\": 8,\n        \"y\": 12\n      },\n      \"height\": \"1px\",\n      \"id\": 11,\n      \"interval\": null,\n      \"isNew\": true,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \" cores\",\n      \"postfixFontSize\": \"30%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum (rate (container_cpu_usage_seconds_total{id=\\\"/\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m]))\",\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Used\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"50%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"rgba(50, 172, 45, 0.97)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(245, 54, 54, 0.9)\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 4,\n        \"x\": 12,\n        \"y\": 12\n      },\n      \"height\": \"1px\",\n      \"id\": 12,\n      \"interval\": null,\n      \"isNew\": true,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \" cores\",\n      \"postfixFontSize\": \"30%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum (machine_cpu_cores{kubernetes_io_hostname=~\\\"^$Node$\\\"})\",\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Total\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"50%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"rgba(50, 172, 45, 0.97)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(245, 54, 54, 0.9)\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"format\": \"bytes\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 4,\n        \"x\": 16,\n        \"y\": 12\n      },\n      \"height\": \"1px\",\n      \"id\": 13,\n      \"interval\": null,\n      \"isNew\": true,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum (container_fs_usage_bytes{device=~\\\"^/dev/[sv]d[a-z][1-9]$\\\",id=\\\"/\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"})\",\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Used\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"50%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"rgba(50, 172, 45, 0.97)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(245, 54, 54, 0.9)\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"format\": \"bytes\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 4,\n        \"x\": 20,\n        \"y\": 12\n      },\n      \"height\": \"1px\",\n      \"id\": 14,\n      \"interval\": null,\n      \"isNew\": true,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum (container_fs_limit_bytes{device=~\\\"^/dev/[sv]d[a-z][1-9]$\\\",id=\\\"/\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"})\",\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"Total\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"50%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 15\n      },\n      \"id\": 35,\n      \"panels\": [],\n      \"title\": \"Pods CPU usage\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": 3,\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"height\": \"\",\n      \"id\": 17,\n      \"isNew\": true,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": true,\n      \"targets\": [\n        {\n          \"expr\": \"sum (rate (container_cpu_usage_seconds_total{image!=\\\"\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (pod_name, pod)\",\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ pod_name }}{{ pod }}\",\n          \"metric\": \"container_cpu\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Pods CPU usage (1m avg)\",\n      \"tooltip\": {\n        \"msResolution\": true,\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"none\",\n          \"label\": \"cores\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 23\n      },\n      \"id\": 36,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"decimals\": 3,\n          \"editable\": true,\n          \"error\": false,\n          \"fill\": 0,\n          \"grid\": {},\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 23\n          },\n          \"height\": \"\",\n          \"id\": 23,\n          \"isNew\": true,\n          \"legend\": {\n            \"alignAsTable\": true,\n            \"avg\": true,\n            \"current\": true,\n            \"max\": false,\n            \"min\": false,\n            \"rightSide\": true,\n            \"show\": true,\n            \"sort\": \"current\",\n            \"sortDesc\": true,\n            \"total\": false,\n            \"values\": true\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"connected\",\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"stack\": false,\n          \"steppedLine\": true,\n          \"targets\": [\n            {\n              \"expr\": \"sum (rate (container_cpu_usage_seconds_total{systemd_service_name!=\\\"\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (systemd_service_name)\",\n              \"hide\": false,\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{ systemd_service_name }}\",\n              \"metric\": \"container_cpu\",\n              \"refId\": \"A\",\n              \"step\": 10\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeShift\": null,\n          \"title\": \"System services CPU usage (1m avg)\",\n          \"tooltip\": {\n            \"msResolution\": true,\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"cumulative\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"show\": true\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"none\",\n              \"label\": \"cores\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": false\n            }\n          ]\n        }\n      ],\n      \"title\": \"System services CPU usage\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 24\n      },\n      \"id\": 37,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"decimals\": 3,\n          \"editable\": true,\n          \"error\": false,\n          \"fill\": 0,\n          \"grid\": {},\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 24\n          },\n          \"height\": \"\",\n          \"id\": 24,\n          \"isNew\": true,\n          \"legend\": {\n            \"alignAsTable\": true,\n            \"avg\": true,\n            \"current\": true,\n            \"hideEmpty\": false,\n            \"hideZero\": false,\n            \"max\": false,\n            \"min\": false,\n            \"rightSide\": true,\n            \"show\": true,\n            \"sideWidth\": null,\n            \"sort\": \"current\",\n            \"sortDesc\": true,\n            \"total\": false,\n            \"values\": true\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"connected\",\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"stack\": false,\n          \"steppedLine\": true,\n          \"targets\": [\n            {\n              \"expr\": \"sum (rate (container_cpu_usage_seconds_total{image!=\\\"\\\",container_name!=\\\"POD\\\",container!=\\\"POD\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (container_name, container, pod_name, pod)\",\n              \"hide\": false,\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"pod: {{ pod_name }}{{ pod }} | {{ container_name }}{{ container }}\",\n              \"metric\": \"container_cpu\",\n              \"refId\": \"A\",\n              \"step\": 10\n            },\n            {\n              \"expr\": \"sum (rate (container_cpu_usage_seconds_total{image!=\\\"\\\",name!~\\\"^k8s_.*\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (kubernetes_io_hostname, name, image)\",\n              \"hide\": false,\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"docker: {{ kubernetes_io_hostname }} | {{ image }} ({{ name }})\",\n              \"metric\": \"container_cpu\",\n              \"refId\": \"B\",\n              \"step\": 10\n            },\n            {\n              \"expr\": \"sum (rate (container_cpu_usage_seconds_total{rkt_container_name!=\\\"\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (kubernetes_io_hostname, rkt_container_name)\",\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"rkt: {{ kubernetes_io_hostname }} | {{ rkt_container_name }}\",\n              \"metric\": \"container_cpu\",\n              \"refId\": \"C\",\n              \"step\": 10\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeShift\": null,\n          \"title\": \"Containers CPU usage (1m avg)\",\n          \"tooltip\": {\n            \"msResolution\": true,\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"cumulative\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"show\": true\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"none\",\n              \"label\": \"cores\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": false\n            }\n          ]\n        }\n      ],\n      \"title\": \"Containers CPU usage\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 25\n      },\n      \"id\": 38,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"decimals\": 3,\n          \"editable\": true,\n          \"error\": false,\n          \"fill\": 0,\n          \"grid\": {},\n          \"gridPos\": {\n            \"h\": 13,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 25\n          },\n          \"id\": 20,\n          \"isNew\": true,\n          \"legend\": {\n            \"alignAsTable\": true,\n            \"avg\": true,\n            \"current\": true,\n            \"max\": false,\n            \"min\": false,\n            \"rightSide\": false,\n            \"show\": true,\n            \"sort\": \"current\",\n            \"sortDesc\": true,\n            \"total\": false,\n            \"values\": true\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"connected\",\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"stack\": false,\n          \"steppedLine\": true,\n          \"targets\": [\n            {\n              \"expr\": \"sum (rate (container_cpu_usage_seconds_total{id!=\\\"/\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (id)\",\n              \"hide\": false,\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{ id }}\",\n              \"metric\": \"container_cpu\",\n              \"refId\": \"A\",\n              \"step\": 10\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeShift\": null,\n          \"title\": \"All processes CPU usage (1m avg)\",\n          \"tooltip\": {\n            \"msResolution\": true,\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"cumulative\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"show\": true\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"none\",\n              \"label\": \"cores\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": false\n            }\n          ]\n        }\n      ],\n      \"repeat\": null,\n      \"title\": \"All processes CPU usage\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 26\n      },\n      \"id\": 39,\n      \"panels\": [],\n      \"title\": \"Pods memory usage\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 27\n      },\n      \"id\": 25,\n      \"isNew\": true,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sideWidth\": 200,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": true,\n      \"targets\": [\n        {\n          \"expr\": \"sum (container_memory_working_set_bytes{image!=\\\"\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}) by (pod_name, pod)\",\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ pod_name }}{{ pod }}\",\n          \"metric\": \"container_memory_usage:sort_desc\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Pods memory usage\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 34\n      },\n      \"id\": 40,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"decimals\": 2,\n          \"editable\": true,\n          \"error\": false,\n          \"fill\": 0,\n          \"grid\": {},\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 34\n          },\n          \"id\": 26,\n          \"isNew\": true,\n          \"legend\": {\n            \"alignAsTable\": true,\n            \"avg\": true,\n            \"current\": true,\n            \"max\": false,\n            \"min\": false,\n            \"rightSide\": true,\n            \"show\": true,\n            \"sideWidth\": 200,\n            \"sort\": \"current\",\n            \"sortDesc\": true,\n            \"total\": false,\n            \"values\": true\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"connected\",\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"stack\": false,\n          \"steppedLine\": true,\n          \"targets\": [\n            {\n              \"expr\": \"sum (container_memory_working_set_bytes{systemd_service_name!=\\\"\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}) by (systemd_service_name)\",\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{ systemd_service_name }}\",\n              \"metric\": \"container_memory_usage:sort_desc\",\n              \"refId\": \"A\",\n              \"step\": 10\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeShift\": null,\n          \"title\": \"System services memory usage\",\n          \"tooltip\": {\n            \"msResolution\": false,\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"cumulative\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"show\": true\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"bytes\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": false\n            }\n          ]\n        }\n      ],\n      \"title\": \"System services memory usage\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 35\n      },\n      \"id\": 41,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"decimals\": 2,\n          \"editable\": true,\n          \"error\": false,\n          \"fill\": 0,\n          \"grid\": {},\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 35\n          },\n          \"id\": 27,\n          \"isNew\": true,\n          \"legend\": {\n            \"alignAsTable\": true,\n            \"avg\": true,\n            \"current\": true,\n            \"max\": false,\n            \"min\": false,\n            \"rightSide\": true,\n            \"show\": true,\n            \"sideWidth\": 200,\n            \"sort\": \"current\",\n            \"sortDesc\": true,\n            \"total\": false,\n            \"values\": true\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"connected\",\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"stack\": false,\n          \"steppedLine\": true,\n          \"targets\": [\n            {\n              \"expr\": \"sum (container_memory_working_set_bytes{image!=\\\"\\\",container_name!=\\\"POD\\\",container!=\\\"POD\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}) by (container_name, container, pod_name, pod)\",\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"pod: {{ pod_name }}{{ pod }} | {{ container_name }}{{ container }}\",\n              \"metric\": \"container_memory_usage:sort_desc\",\n              \"refId\": \"A\",\n              \"step\": 10\n            },\n            {\n              \"expr\": \"sum (container_memory_working_set_bytes{image!=\\\"\\\",name!~\\\"^k8s_.*\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}) by (kubernetes_io_hostname, name, image)\",\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"docker: {{ kubernetes_io_hostname }} | {{ image }} ({{ name }})\",\n              \"metric\": \"container_memory_usage:sort_desc\",\n              \"refId\": \"B\",\n              \"step\": 10\n            },\n            {\n              \"expr\": \"sum (container_memory_working_set_bytes{rkt_container_name!=\\\"\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}) by (kubernetes_io_hostname, rkt_container_name)\",\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"rkt: {{ kubernetes_io_hostname }} | {{ rkt_container_name }}\",\n              \"metric\": \"container_memory_usage:sort_desc\",\n              \"refId\": \"C\",\n              \"step\": 10\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeShift\": null,\n          \"title\": \"Containers memory usage\",\n          \"tooltip\": {\n            \"msResolution\": false,\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"cumulative\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"show\": true\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"bytes\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": false\n            }\n          ]\n        }\n      ],\n      \"title\": \"Containers memory usage\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 36\n      },\n      \"id\": 42,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"decimals\": 2,\n          \"editable\": true,\n          \"error\": false,\n          \"fill\": 0,\n          \"grid\": {},\n          \"gridPos\": {\n            \"h\": 13,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 36\n          },\n          \"id\": 28,\n          \"isNew\": true,\n          \"legend\": {\n            \"alignAsTable\": true,\n            \"avg\": true,\n            \"current\": true,\n            \"max\": false,\n            \"min\": false,\n            \"rightSide\": false,\n            \"show\": true,\n            \"sideWidth\": 200,\n            \"sort\": \"current\",\n            \"sortDesc\": true,\n            \"total\": false,\n            \"values\": true\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"connected\",\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"stack\": false,\n          \"steppedLine\": true,\n          \"targets\": [\n            {\n              \"expr\": \"sum (container_memory_working_set_bytes{id!=\\\"/\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}) by (id)\",\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{ id }}\",\n              \"metric\": \"container_memory_usage:sort_desc\",\n              \"refId\": \"A\",\n              \"step\": 10\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeShift\": null,\n          \"title\": \"All processes memory usage\",\n          \"tooltip\": {\n            \"msResolution\": false,\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"cumulative\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"show\": true\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"bytes\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": false\n            }\n          ]\n        }\n      ],\n      \"title\": \"All processes memory usage\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 37\n      },\n      \"id\": 43,\n      \"panels\": [],\n      \"title\": \"Pods network I/O\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 38\n      },\n      \"id\": 16,\n      \"isNew\": true,\n      \"legend\": {\n        \"alignAsTable\": true,\n        \"avg\": true,\n        \"current\": true,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": true,\n        \"show\": true,\n        \"sideWidth\": 200,\n        \"sort\": \"current\",\n        \"sortDesc\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum (rate (container_network_receive_bytes_total{image!=\\\"\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (pod_name, pod)\",\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"-> {{ pod_name }}{{ pod }}\",\n          \"metric\": \"network\",\n          \"refId\": \"A\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"- sum (rate (container_network_transmit_bytes_total{image!=\\\"\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (pod_name, pod)\",\n          \"interval\": \"10s\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"<- {{ pod_name }}{{ pod }}\",\n          \"metric\": \"network\",\n          \"refId\": \"B\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeShift\": null,\n      \"title\": \"Pods network I/O (1m avg)\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"show\": true\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"Bps\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": false\n        }\n      ]\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 45\n      },\n      \"id\": 44,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"decimals\": 2,\n          \"editable\": true,\n          \"error\": false,\n          \"fill\": 1,\n          \"grid\": {},\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 45\n          },\n          \"id\": 30,\n          \"isNew\": true,\n          \"legend\": {\n            \"alignAsTable\": true,\n            \"avg\": true,\n            \"current\": true,\n            \"max\": false,\n            \"min\": false,\n            \"rightSide\": true,\n            \"show\": true,\n            \"sideWidth\": 200,\n            \"sort\": \"current\",\n            \"sortDesc\": true,\n            \"total\": false,\n            \"values\": true\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"connected\",\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum (rate (container_network_receive_bytes_total{image!=\\\"\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (container_name, container, pod_name, pod)\",\n              \"hide\": false,\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"-> pod: {{ pod_name }}{{ pod }} | {{ container_name }}{{ container }}\",\n              \"metric\": \"network\",\n              \"refId\": \"B\",\n              \"step\": 10\n            },\n            {\n              \"expr\": \"- sum (rate (container_network_transmit_bytes_total{image!=\\\"\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (container_name, container, pod_name, pod)\",\n              \"hide\": false,\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"<- pod: {{ pod_name }}{{ pod }} | {{ container_name }}{{ container }}\",\n              \"metric\": \"network\",\n              \"refId\": \"D\",\n              \"step\": 10\n            },\n            {\n              \"expr\": \"sum (rate (container_network_receive_bytes_total{image!=\\\"\\\",name!~\\\"^k8s_.*\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (kubernetes_io_hostname, name, image)\",\n              \"hide\": false,\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"-> docker: {{ kubernetes_io_hostname }} | {{ image }} ({{ name }})\",\n              \"metric\": \"network\",\n              \"refId\": \"A\",\n              \"step\": 10\n            },\n            {\n              \"expr\": \"- sum (rate (container_network_transmit_bytes_total{image!=\\\"\\\",name!~\\\"^k8s_.*\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (kubernetes_io_hostname, name, image)\",\n              \"hide\": false,\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"<- docker: {{ kubernetes_io_hostname }} | {{ image }} ({{ name }})\",\n              \"metric\": \"network\",\n              \"refId\": \"C\",\n              \"step\": 10\n            },\n            {\n              \"expr\": \"sum (rate (container_network_transmit_bytes_total{rkt_container_name!=\\\"\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (kubernetes_io_hostname, rkt_container_name)\",\n              \"hide\": false,\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"-> rkt: {{ kubernetes_io_hostname }} | {{ rkt_container_name }}\",\n              \"metric\": \"network\",\n              \"refId\": \"E\",\n              \"step\": 10\n            },\n            {\n              \"expr\": \"- sum (rate (container_network_transmit_bytes_total{rkt_container_name!=\\\"\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (kubernetes_io_hostname, rkt_container_name)\",\n              \"hide\": false,\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"<- rkt: {{ kubernetes_io_hostname }} | {{ rkt_container_name }}\",\n              \"metric\": \"network\",\n              \"refId\": \"F\",\n              \"step\": 10\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeShift\": null,\n          \"title\": \"Containers network I/O (1m avg)\",\n          \"tooltip\": {\n            \"msResolution\": false,\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"cumulative\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"show\": true\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"Bps\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": false\n            }\n          ]\n        }\n      ],\n      \"title\": \"Containers network I/O\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 46\n      },\n      \"id\": 45,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"decimals\": 2,\n          \"editable\": true,\n          \"error\": false,\n          \"fill\": 1,\n          \"grid\": {},\n          \"gridPos\": {\n            \"h\": 13,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 46\n          },\n          \"id\": 29,\n          \"isNew\": true,\n          \"legend\": {\n            \"alignAsTable\": true,\n            \"avg\": true,\n            \"current\": true,\n            \"max\": false,\n            \"min\": false,\n            \"rightSide\": false,\n            \"show\": true,\n            \"sideWidth\": 200,\n            \"sort\": \"current\",\n            \"sortDesc\": true,\n            \"total\": false,\n            \"values\": true\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"connected\",\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum (rate (container_network_receive_bytes_total{id!=\\\"/\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (id)\",\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"-> {{ id }}\",\n              \"metric\": \"network\",\n              \"refId\": \"A\",\n              \"step\": 10\n            },\n            {\n              \"expr\": \"- sum (rate (container_network_transmit_bytes_total{id!=\\\"/\\\",kubernetes_io_hostname=~\\\"^$Node$\\\"}[1m])) by (id)\",\n              \"interval\": \"10s\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"<- {{ id }}\",\n              \"metric\": \"network\",\n              \"refId\": \"B\",\n              \"step\": 10\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeShift\": null,\n          \"title\": \"All processes network I/O (1m avg)\",\n          \"tooltip\": {\n            \"msResolution\": false,\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"cumulative\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"show\": true\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"Bps\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": false\n            }\n          ]\n        }\n      ],\n      \"title\": \"All processes network I/O\",\n      \"type\": \"row\"\n    }\n  ],\n  \"refresh\": \"1m\",\n  \"schemaVersion\": 18,\n  \"style\": \"dark\",\n  \"tags\": [\n    \"kubernetes\"\n  ],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"default\",\n          \"value\": \"default\"\n        },\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Data Source\",\n        \"multi\": false,\n        \"name\": \"datasource\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"Node\",\n        \"options\": [],\n        \"query\": \"label_values(kubernetes_io_hostname)\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-5m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"browser\",\n  \"title\": \"Kubernetes cluster monitoring (via Prometheus)\",\n  \"uid\": \"k8s\",\n  \"version\": 1\n}\n"
  },
  {
    "path": "grafana/dashboards/multicluster.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n    \"annotations\": {\n        \"list\": [\n            {\n                \"builtIn\": 1,\n                \"datasource\": \"-- Grafana --\",\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    \"gnetId\": null,\n    \"graphTooltip\": 1,\n    \"id\": null,\n    \"iteration\": 1531434867463,\n    \"links\": [],\n    \"panels\": [\n        {\n            \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"height:32px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 32px\\\">Cluster: $cluster</span>\\n</div>\",\n            \"gridPos\": {\n                \"h\": 2,\n                \"w\": 24,\n                \"x\": 0,\n                \"y\": 0\n            },\n            \"id\": 20,\n            \"links\": [],\n            \"mode\": \"html\",\n            \"options\": {},\n            \"title\": \"\",\n            \"transparent\": true,\n            \"type\": \"text\"\n        },\n        {\n            \"cacheTimeout\": null,\n            \"colorBackground\": false,\n            \"colorValue\": false,\n            \"colors\": [\n                \"#d44a3a\",\n                \"rgba(237, 129, 40, 0.89)\",\n                \"#299c46\"\n            ],\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"decimals\": null,\n            \"format\": \"percentunit\",\n            \"gauge\": {\n                \"maxValue\": 1,\n                \"minValue\": 0,\n                \"show\": true,\n                \"thresholdLabels\": false,\n                \"thresholdMarkers\": true\n            },\n            \"gridPos\": {\n                \"h\": 4,\n                \"w\": 8,\n                \"x\": 0,\n                \"y\": 2\n            },\n            \"id\": 5,\n            \"interval\": null,\n            \"links\": [],\n            \"mappingType\": 1,\n            \"mappingTypes\": [\n                {\n                    \"name\": \"value to text\",\n                    \"value\": 1\n                },\n                {\n                    \"name\": \"range to text\",\n                    \"value\": 2\n                }\n            ],\n            \"maxDataPoints\": 100,\n            \"nullPointMode\": \"connected\",\n            \"nullText\": null,\n            \"options\": {},\n            \"postfix\": \"\",\n            \"postfixFontSize\": \"50%\",\n            \"prefix\": \"\",\n            \"prefixFontSize\": \"50%\",\n            \"rangeMaps\": [\n                {\n                    \"from\": \"null\",\n                    \"text\": \"N/A\",\n                    \"to\": \"null\"\n                }\n            ],\n            \"sparkline\": {\n                \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n                \"full\": true,\n                \"lineColor\": \"rgb(31, 120, 193)\",\n                \"show\": true\n            },\n            \"tableColumn\": \"\",\n            \"targets\": [\n                {\n                    \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", direction=\\\"outbound\\\"}[$__rate_interval])) / sum(irate(response_total{dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", direction=\\\"outbound\\\"}[$__rate_interval]))\",\n                    \"format\": \"time_series\",\n                    \"instant\": false,\n                    \"intervalFactor\": 1,\n                    \"legendFormat\": \"\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"thresholds\": \"0.9,.99\",\n            \"title\": \"SUCCESS RATE\",\n            \"transparent\": true,\n            \"type\": \"singlestat\",\n            \"valueFontSize\": \"80%\",\n            \"valueMaps\": [\n                {\n                    \"op\": \"=\",\n                    \"text\": \"N/A\",\n                    \"value\": \"null\"\n                }\n            ],\n            \"valueName\": \"current\"\n        },\n        {\n            \"cacheTimeout\": null,\n            \"colorBackground\": false,\n            \"colorValue\": false,\n            \"colors\": [\n                \"#299c46\",\n                \"rgba(237, 129, 40, 0.89)\",\n                \"#d44a3a\"\n            ],\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"decimals\": null,\n            \"format\": \"none\",\n            \"gauge\": {\n                \"maxValue\": 100,\n                \"minValue\": 0,\n                \"show\": false,\n                \"thresholdLabels\": false,\n                \"thresholdMarkers\": true\n            },\n            \"gridPos\": {\n                \"h\": 4,\n                \"w\": 8,\n                \"x\": 8,\n                \"y\": 2\n            },\n            \"id\": 4,\n            \"interval\": null,\n            \"links\": [],\n            \"mappingType\": 1,\n            \"mappingTypes\": [\n                {\n                    \"name\": \"value to text\",\n                    \"value\": 1\n                },\n                {\n                    \"name\": \"range to text\",\n                    \"value\": 2\n                }\n            ],\n            \"maxDataPoints\": 100,\n            \"nullPointMode\": \"connected\",\n            \"nullText\": null,\n            \"options\": {},\n            \"postfix\": \" RPS\",\n            \"postfixFontSize\": \"100%\",\n            \"prefix\": \"\",\n            \"prefixFontSize\": \"50%\",\n            \"rangeMaps\": [\n                {\n                    \"from\": \"null\",\n                    \"text\": \"N/A\",\n                    \"to\": \"null\"\n                }\n            ],\n            \"sparkline\": {\n                \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n                \"full\": true,\n                \"lineColor\": \"rgb(31, 120, 193)\",\n                \"show\": true\n            },\n            \"tableColumn\": \"\",\n            \"targets\": [\n                {\n                    \"expr\": \"sum(irate(request_total{dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", direction=\\\"outbound\\\"}[$__rate_interval]))\",\n                    \"format\": \"time_series\",\n                    \"instant\": false,\n                    \"intervalFactor\": 1,\n                    \"legendFormat\": \"\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"thresholds\": \"\",\n            \"title\": \"REQUEST RATE\",\n            \"transparent\": true,\n            \"type\": \"singlestat\",\n            \"valueFontSize\": \"100%\",\n            \"valueMaps\": [\n                {\n                    \"op\": \"=\",\n                    \"text\": \"N/A\",\n                    \"value\": \"null\"\n                }\n            ],\n            \"valueName\": \"current\"\n        },\n        {\n            \"cacheTimeout\": null,\n            \"colorBackground\": false,\n            \"colorValue\": false,\n            \"colors\": [\n                \"#299c46\",\n                \"rgba(237, 129, 40, 0.89)\",\n                \"#d44a3a\"\n            ],\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"decimals\": null,\n            \"format\": \"none\",\n            \"gauge\": {\n                \"maxValue\": 100,\n                \"minValue\": 0,\n                \"show\": false,\n                \"thresholdLabels\": false,\n                \"thresholdMarkers\": true\n            },\n            \"gridPos\": {\n                \"h\": 4,\n                \"w\": 8,\n                \"x\": 16,\n                \"y\": 2\n            },\n            \"id\": 81,\n            \"interval\": null,\n            \"links\": [],\n            \"mappingType\": 1,\n            \"mappingTypes\": [\n                {\n                    \"name\": \"value to text\",\n                    \"value\": 1\n                },\n                {\n                    \"name\": \"range to text\",\n                    \"value\": 2\n                }\n            ],\n            \"maxDataPoints\": 100,\n            \"nullPointMode\": \"connected\",\n            \"nullText\": null,\n            \"options\": {},\n            \"postfix\": \" ms\",\n            \"postfixFontSize\": \"100%\",\n            \"prefix\": \"\",\n            \"prefixFontSize\": \"50%\",\n            \"rangeMaps\": [\n                {\n                    \"from\": \"null\",\n                    \"text\": \"N/A\",\n                    \"to\": \"null\"\n                }\n            ],\n            \"sparkline\": {\n                \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n                \"full\": true,\n                \"lineColor\": \"rgb(31, 120, 193)\",\n                \"show\": true\n            },\n            \"tableColumn\": \"\",\n            \"targets\": [\n                {\n                    \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le))\",\n                    \"format\": \"time_series\",\n                    \"instant\": false,\n                    \"intervalFactor\": 1,\n                    \"legendFormat\": \"\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"thresholds\": \"\",\n            \"title\": \"P95 LATENCY\",\n            \"transparent\": true,\n            \"type\": \"singlestat\",\n            \"valueFontSize\": \"100%\",\n            \"valueMaps\": [\n                {\n                    \"op\": \"=\",\n                    \"text\": \"N/A\",\n                    \"value\": \"null\"\n                }\n            ],\n            \"valueName\": \"current\"\n        },\n        {\n            \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>TOP-LINE TRAFFIC</span>\\n</div>\",\n            \"gridPos\": {\n                \"h\": 2,\n                \"w\": 24,\n                \"x\": 0,\n                \"y\": 6\n            },\n            \"id\": 17,\n            \"links\": [],\n            \"mode\": \"html\",\n            \"options\": {},\n            \"title\": \"\",\n            \"transparent\": true,\n            \"type\": \"text\"\n        },\n        {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 1,\n            \"gridPos\": {\n                \"h\": 7,\n                \"w\": 8,\n                \"x\": 0,\n                \"y\": 8\n            },\n            \"id\": 67,\n            \"legend\": {\n                \"avg\": false,\n                \"current\": false,\n                \"max\": false,\n                \"min\": false,\n                \"show\": false,\n                \"total\": false,\n                \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n                {\n                    \"expr\": \"sum(irate(response_total{classification=\\\"success\\\",dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", direction=\\\"outbound\\\"}[$__rate_interval])) / sum(irate(response_total{dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", direction=\\\"outbound\\\"}[$__rate_interval]))\",\n                    \"format\": \"time_series\",\n                    \"intervalFactor\": 1,\n                    \"legendFormat\": \"\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"SUCCESS RATE\",\n            \"tooltip\": {\n                \"shared\": true,\n                \"sort\": 2,\n                \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n                \"buckets\": null,\n                \"mode\": \"time\",\n                \"name\": null,\n                \"show\": true,\n                \"values\": []\n            },\n            \"yaxes\": [\n                {\n                    \"decimals\": null,\n                    \"format\": \"percentunit\",\n                    \"label\": \"\",\n                    \"logBase\": 1,\n                    \"max\": \"1\",\n                    \"min\": null,\n                    \"show\": true\n                },\n                {\n                    \"format\": \"short\",\n                    \"label\": null,\n                    \"logBase\": 1,\n                    \"max\": null,\n                    \"min\": null,\n                    \"show\": true\n                }\n            ],\n            \"yaxis\": {\n                \"align\": false,\n                \"alignLevel\": null\n            }\n        },\n        {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 0,\n            \"gridPos\": {\n                \"h\": 7,\n                \"w\": 8,\n                \"x\": 8,\n                \"y\": 8\n            },\n            \"id\": 2,\n            \"legend\": {\n                \"avg\": false,\n                \"current\": false,\n                \"max\": false,\n                \"min\": false,\n                \"show\": false,\n                \"total\": false,\n                \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n                {\n                    \"expr\": \"sum(irate(request_total{dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval]))\",\n                    \"format\": \"time_series\",\n                    \"intervalFactor\": 1,\n                    \"legendFormat\": \"\",\n                    \"refId\": \"A\"\n                },\n                {\n                    \"expr\": \"sum(irate(request_total{dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", tls!=\\\"true\\\"}[$__rate_interval]))\",\n                    \"format\": \"time_series\",\n                    \"intervalFactor\": 1,\n                    \"legendFormat\": \"\",\n                    \"refId\": \"B\"\n                }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"REQUEST RATE\",\n            \"tooltip\": {\n                \"shared\": true,\n                \"sort\": 2,\n                \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n                \"buckets\": null,\n                \"mode\": \"time\",\n                \"name\": null,\n                \"show\": true,\n                \"values\": []\n            },\n            \"yaxes\": [\n                {\n                    \"decimals\": null,\n                    \"format\": \"rps\",\n                    \"label\": \"\",\n                    \"logBase\": 1,\n                    \"max\": null,\n                    \"min\": \"0\",\n                    \"show\": true\n                },\n                {\n                    \"format\": \"short\",\n                    \"label\": null,\n                    \"logBase\": 1,\n                    \"max\": null,\n                    \"min\": null,\n                    \"show\": true\n                }\n            ],\n            \"yaxis\": {\n                \"align\": false,\n                \"alignLevel\": null\n            }\n        },\n        {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 1,\n            \"gridPos\": {\n                \"h\": 7,\n                \"w\": 8,\n                \"x\": 16,\n                \"y\": 8\n            },\n            \"id\": 68,\n            \"legend\": {\n                \"avg\": false,\n                \"current\": false,\n                \"max\": false,\n                \"min\": false,\n                \"show\": false,\n                \"total\": false,\n                \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n                {\n                    \"expr\": \"histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le))\",\n                    \"format\": \"time_series\",\n                    \"intervalFactor\": 1,\n                    \"legendFormat\": \"p50 gateway\",\n                    \"refId\": \"A\"\n                },\n                {\n                    \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le))\",\n                    \"format\": \"time_series\",\n                    \"hide\": false,\n                    \"intervalFactor\": 1,\n                    \"legendFormat\": \"p95 gateway\",\n                    \"refId\": \"B\"\n                },\n                {\n                    \"expr\": \"histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le))\",\n                    \"format\": \"time_series\",\n                    \"intervalFactor\": 1,\n                    \"legendFormat\": \"p99 gateway\",\n                    \"refId\": \"C\"\n                }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"LATENCY\",\n            \"tooltip\": {\n                \"shared\": true,\n                \"sort\": 2,\n                \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n                \"buckets\": null,\n                \"mode\": \"time\",\n                \"name\": null,\n                \"show\": true,\n                \"values\": []\n            },\n            \"yaxes\": [\n                {\n                    \"decimals\": null,\n                    \"format\": \"ms\",\n                    \"label\": \"\",\n                    \"logBase\": 1,\n                    \"max\": null,\n                    \"min\": null,\n                    \"show\": true\n                },\n                {\n                    \"format\": \"short\",\n                    \"label\": null,\n                    \"logBase\": 1,\n                    \"max\": null,\n                    \"min\": null,\n                    \"show\": true\n                }\n            ],\n            \"yaxis\": {\n                \"align\": false,\n                \"alignLevel\": null\n            }\n        },\n        {\n            \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>TRAFFIC BY TARGET SERVICE</span>\\n</div>\",\n            \"gridPos\": {\n                \"h\": 2,\n                \"w\": 24,\n                \"x\": 0,\n                \"y\": 15\n            },\n            \"id\": 32,\n            \"links\": [],\n            \"mode\": \"html\",\n            \"options\": {},\n            \"title\": \"\",\n            \"transparent\": true,\n            \"type\": \"text\"\n        },\n        {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 1,\n            \"gridPos\": {\n                \"h\": 7,\n                \"w\": 8,\n                \"x\": 0,\n                \"y\": 17\n            },\n            \"id\": 77,\n            \"legend\": {\n                \"avg\": false,\n                \"current\": false,\n                \"max\": false,\n                \"min\": false,\n                \"show\": false,\n                \"total\": false,\n                \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n                {\n                    \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_target_service) / sum(irate(response_total{dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_target_service)\",\n                    \"format\": \"time_series\",\n                    \"intervalFactor\": 1,\n                    \"legendFormat\": \"target-svc/{{dst_target_service}}\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"SUCCESS RATE\",\n            \"tooltip\": {\n                \"shared\": true,\n                \"sort\": 2,\n                \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n                \"buckets\": null,\n                \"mode\": \"time\",\n                \"name\": null,\n                \"show\": true,\n                \"values\": []\n            },\n            \"yaxes\": [\n                {\n                    \"decimals\": null,\n                    \"format\": \"percentunit\",\n                    \"label\": \"\",\n                    \"logBase\": 1,\n                    \"max\": \"1\",\n                    \"min\": null,\n                    \"show\": true\n                },\n                {\n                    \"format\": \"short\",\n                    \"label\": null,\n                    \"logBase\": 1,\n                    \"max\": null,\n                    \"min\": null,\n                    \"show\": true\n                }\n            ],\n            \"yaxis\": {\n                \"align\": false,\n                \"alignLevel\": null\n            }\n        },\n        {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 0,\n            \"gridPos\": {\n                \"h\": 7,\n                \"w\": 8,\n                \"x\": 8,\n                \"y\": 17\n            },\n            \"id\": 78,\n            \"legend\": {\n                \"avg\": false,\n                \"current\": false,\n                \"max\": false,\n                \"min\": false,\n                \"show\": false,\n                \"total\": false,\n                \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n                {\n                    \"expr\": \"sum(irate(request_total{dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_target_service)\",\n                    \"format\": \"time_series\",\n                    \"intervalFactor\": 1,\n                    \"legendFormat\": \"🔒target-svc/{{dst_target_service}}\",\n                    \"refId\": \"A\"\n                },\n                {\n                    \"expr\": \"sum(irate(request_total{dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_target_service)\",\n                    \"format\": \"time_series\",\n                    \"intervalFactor\": 1,\n                    \"legendFormat\": \"target-svc/{{dst_target_service}}\",\n                    \"refId\": \"B\"\n                }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"REQUEST RATE\",\n            \"tooltip\": {\n                \"shared\": true,\n                \"sort\": 2,\n                \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n                \"buckets\": null,\n                \"mode\": \"time\",\n                \"name\": null,\n                \"show\": true,\n                \"values\": []\n            },\n            \"yaxes\": [\n                {\n                    \"format\": \"rps\",\n                    \"label\": \"\",\n                    \"logBase\": 1,\n                    \"max\": null,\n                    \"min\": \"0\",\n                    \"show\": true\n                },\n                {\n                    \"format\": \"short\",\n                    \"label\": null,\n                    \"logBase\": 1,\n                    \"max\": null,\n                    \"min\": null,\n                    \"show\": true\n                }\n            ],\n            \"yaxis\": {\n                \"align\": false,\n                \"alignLevel\": null\n            }\n        },\n        {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 1,\n            \"gridPos\": {\n                \"h\": 7,\n                \"w\": 8,\n                \"x\": 16,\n                \"y\": 17\n            },\n            \"id\": 79,\n            \"legend\": {\n                \"avg\": false,\n                \"current\": false,\n                \"max\": false,\n                \"min\": false,\n                \"show\": false,\n                \"total\": false,\n                \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n                {\n                    \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{dst_target_cluster=\\\"$cluster\\\", dst_target_cluster!=\\\"\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_target_service))\",\n                    \"format\": \"time_series\",\n                    \"intervalFactor\": 1,\n                    \"legendFormat\": \"P95 target-svc/{{dst_target_service}}\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"P95 LATENCY\",\n            \"tooltip\": {\n                \"shared\": true,\n                \"sort\": 2,\n                \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n                \"buckets\": null,\n                \"mode\": \"time\",\n                \"name\": null,\n                \"show\": true,\n                \"values\": []\n            },\n            \"yaxes\": [\n                {\n                    \"format\": \"ms\",\n                    \"label\": null,\n                    \"logBase\": 1,\n                    \"max\": null,\n                    \"min\": null,\n                    \"show\": true\n                },\n                {\n                    \"format\": \"short\",\n                    \"label\": null,\n                    \"logBase\": 1,\n                    \"max\": null,\n                    \"min\": null,\n                    \"show\": true\n                }\n            ],\n            \"yaxis\": {\n                \"align\": false,\n                \"alignLevel\": null\n            }\n        }\n    ],\n    \"refresh\": \"1m\",\n    \"schemaVersion\": 18,\n    \"style\": \"dark\",\n    \"tags\": [\n        \"linkerd\"\n    ],\n    \"templating\": {\n        \"list\": [\n            {\n              \"current\": {\n                \"text\": \"default\",\n                \"value\": \"default\"\n              },\n              \"hide\": 0,\n              \"label\": \"Data Source\",\n              \"name\": \"datasource\",\n              \"options\": [],\n              \"query\": \"prometheus\",\n              \"refresh\": 1,\n              \"regex\": \"\",\n              \"type\": \"datasource\"\n            },\n            {\n              \"allValue\": \".*\",\n                \"current\": {\n                    \"text\": \"All\",\n                    \"value\": \"$__all\"\n                },\n                \"datasource\": {\n                    \"type\": \"prometheus\",\n                    \"uid\": \"${datasource}\"\n                },\n                \"definition\": \"\",\n                \"hide\": 0,\n                \"includeAll\": false,\n                \"label\": \"Cluster\",\n                \"multi\": false,\n                \"name\": \"cluster\",\n                \"options\": [],\n                \"query\": \"label_values(request_total, dst_target_cluster)\",\n                \"refresh\": 2,\n                \"regex\": \"\",\n                \"skipUrlSync\": false,\n                \"sort\": 1,\n                \"tagValuesQuery\": \"\",\n                \"tags\": [],\n                \"tagsQuery\": \"\",\n                \"type\": \"query\",\n                \"useTags\": false\n            }\n        ]\n    },\n    \"time\": {\n        \"from\": \"now-5m\",\n        \"to\": \"now\"\n    },\n    \"timepicker\": {\n        \"refresh_intervals\": [\n            \"5s\",\n            \"10s\",\n            \"30s\",\n            \"1m\",\n            \"5m\",\n            \"15m\",\n            \"30m\",\n            \"1h\",\n            \"2h\",\n            \"1d\"\n        ],\n        \"time_options\": [\n            \"5m\",\n            \"15m\",\n            \"1h\",\n            \"6h\",\n            \"12h\",\n            \"24h\",\n            \"2d\",\n            \"7d\",\n            \"30d\"\n        ]\n    },\n    \"timezone\": \"\",\n    \"title\": \"Linkerd Multicluster\",\n    \"uid\": \"linkerd-multicluster\",\n    \"version\": 1\n}\n"
  },
  {
    "path": "grafana/dashboards/namespace.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\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  \"gnetId\": null,\n  \"graphTooltip\": 1,\n  \"id\": null,\n  \"iteration\": 1566417010901,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"height\": \"1px\",\n      \"id\": 14,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": true,\n      \"colors\": [\n        \"#d44a3a\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#299c46\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"format\": \"percentunit\",\n      \"gauge\": {\n        \"maxValue\": 1,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 3\n      },\n      \"height\": \"\",\n      \"id\": 28,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=~\\\"$namespace\\\", deployment=~\\\"$deployment\\\"}[$__rate_interval])) / sum(irate(response_total{namespace=~\\\"$namespace\\\", deployment=~\\\"$deployment\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \".9,.99\",\n      \"title\": \"GLOBAL SUCCESS RATE\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#d44a3a\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#299c46\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"format\": \"rps\",\n      \"gauge\": {\n        \"maxValue\": 1,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 3\n      },\n      \"height\": \"\",\n      \"id\": 29,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=~\\\"$namespace\\\", deployment=~\\\"$deployment\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"GLOBAL REQUEST VOLUME\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#d44a3a\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#299c46\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 1,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 3\n      },\n      \"height\": \"\",\n      \"id\": 86,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(count(request_total{namespace=~\\\"$namespace\\\", deployment=~\\\"$deployment\\\"}) by (namespace, deployment))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"DEPLOYMENTS MONITORED\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"200%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>TOP LINE</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 7\n      },\n      \"height\": \"1px\",\n      \"id\": 20,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"id\": 21,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=~\\\"$namespace\\\", deployment=~\\\"$deployment\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (deployment) / sum(irate(response_total{namespace=~\\\"$namespace\\\", deployment=~\\\"$deployment\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"percentunit\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": 1,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 9\n      },\n      \"id\": 22,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=~\\\"$namespace\\\", deployment=~\\\"$deployment\\\", direction=\\\"inbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=~\\\"$namespace\\\", deployment=~\\\"$deployment\\\", direction=\\\"inbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"deploy/{{deployment}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST VOLUME\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 9\n      },\n      \"id\": 23,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=~\\\"$namespace\\\", deployment=~\\\"$deployment\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, deployment))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p95 deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"P95 LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>DEPLOYMENTS</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"height\": \"1px\",\n      \"id\": 19,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 18\n      },\n      \"id\": 40,\n      \"panels\": [],\n      \"repeat\": \"deployment\",\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div>\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <a href=\\\"./dashboard/db/linkerd-deployment?var-namespace=$namespace&var-deployment=$deployment\\\">\\n    <span style=\\\"font-size: 15px; border-image:none\\\">deploy/$deployment</span>\\n  </a>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 19\n      },\n      \"height\": \"1px\",\n      \"id\": 13,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"id\": 6,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (deployment) / sum(irate(response_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"percentunit\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": 1,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 21\n      },\n      \"id\": 8,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (deployment)\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (deployment)\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"deploy/{{deployment}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST VOLUME\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 21\n      },\n      \"id\": 9,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, deployment))\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p95 deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"P95 LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n  \"refresh\": \"1m\",\n  \"schemaVersion\": 18,\n  \"style\": \"dark\",\n  \"tags\": [\n    \"linkerd\"\n  ],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"text\": \"default\",\n          \"value\": \"default\"\n        },\n        \"hide\": 0,\n        \"label\": \"Data Source\",\n        \"name\": \"datasource\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"label_values(process_start_time_seconds{deployment!=\\\"\\\"}, namespace)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Namespace\",\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{deployment!=\\\"\\\"}, namespace)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"label_values(process_start_time_seconds{namespace=\\\"$namespace\\\"}, deployment)\",\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"label\": \"Deployments\",\n        \"multi\": true,\n        \"name\": \"deployment\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{namespace=\\\"$namespace\\\"}, deployment)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-5m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"\",\n  \"title\": \"Linkerd Namespace\",\n  \"uid\": \"linkerd-namespace\",\n  \"version\": 15\n}\n"
  },
  {
    "path": "grafana/dashboards/pod.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\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  \"gnetId\": null,\n  \"graphTooltip\": 1,\n  \"id\": null,\n  \"iteration\": 1531434418636,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"height:32px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 32px\\\">po/$pod</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 20,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#d44a3a\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#299c46\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"percentunit\",\n      \"gauge\": {\n        \"maxValue\": 1,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 2\n      },\n      \"id\": 5,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"inbound\\\"}[$__rate_interval])) / sum(irate(response_total{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"0.9,.99\",\n      \"title\": \"SUCCESS RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 2\n      },\n      \"id\": 4,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \" RPS\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"REQUEST RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 4,\n        \"x\": 16,\n        \"y\": 2\n      },\n      \"id\": 11,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"100%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(count(request_total{dst_namespace=\\\"$namespace\\\", dst_pod!=\\\"\\\", dst_pod=\\\"$pod\\\", direction=\\\"outbound\\\"}) by (namespace, pod))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"INBOUND PODS\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 4,\n        \"x\": 20,\n        \"y\": 2\n      },\n      \"id\": 15,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(count(request_total{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"outbound\\\"}) by (namespace, dst_pod))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"OUTBOUND PODS\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND TRAFFIC</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 6\n      },\n      \"id\": 17,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 67,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (pod) / sum(irate(response_total{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"po/{{pod}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 8\n      },\n      \"id\": 2,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"inbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒po/{{pod}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"inbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"po/{{pod}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 8\n      },\n      \"id\": 68,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, pod))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p50 po/{{pod}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, pod))\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p95 po/{{pod}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, pod))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p99 po/{{pod}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"ms\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 15\n      },\n      \"id\": 201,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 8,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 16\n          },\n          \"id\": 173,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\" ,deployment=\\\"$deployment\\\", direction=\\\"inbound\\\",errno!=\\\"\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}} {{errno}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTIONS FAILURES\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 8,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 16\n          },\n          \"id\": 175,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\" ,deployment=\\\"$deployment\\\", direction=\\\"inbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTIONS OPEN\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"cards\": {\n            \"cardPadding\": null,\n            \"cardRound\": null\n          },\n          \"color\": {\n            \"cardColor\": \"#b4ff00\",\n            \"colorScale\": \"sqrt\",\n            \"colorScheme\": \"interpolateOranges\",\n            \"exponent\": 0.5,\n            \"mode\": \"spectrum\"\n          },\n          \"dataFormat\": \"timeseries\",\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"gridPos\": {\n            \"h\": 8,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 16\n          },\n          \"heatmap\": {},\n          \"hideZeroBuckets\": false,\n          \"highlightCards\": true,\n          \"id\": 177,\n          \"legend\": {\n            \"show\": false\n          },\n          \"links\": [],\n          \"options\": {},\n          \"reverseYBuckets\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\" ,deployment=\\\"$deployment\\\", direction=\\\"inbound\\\"}\",\n              \"format\": \"heatmap\",\n              \"intervalFactor\": 1,\n              \"refId\": \"A\"\n            }\n          ],\n          \"title\": \"TCP CONNECTION DURATION\",\n          \"tooltip\": {\n            \"show\": true,\n            \"showHistogram\": true\n          },\n          \"type\": \"heatmap\",\n          \"xAxis\": {\n            \"show\": true\n          },\n          \"xBucketNumber\": null,\n          \"xBucketSize\": null,\n          \"yAxis\": {\n            \"decimals\": null,\n            \"format\": \"ms\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true,\n            \"splitFactor\": null\n          },\n          \"yBucketBound\": \"auto\",\n          \"yBucketNumber\": null,\n          \"yBucketSize\": null\n        }\n      ],\n      \"title\": \"Inbound TCP Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"id\": 199,\n      \"panels\": [],\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND PODS</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 17\n      },\n      \"id\": 80,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 19\n      },\n      \"id\": 81,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", dst_namespace=\\\"$namespace\\\", dst_pod!=\\\"\\\", dst_pod=\\\"$pod\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (pod) / sum(irate(response_total{dst_namespace=\\\"$namespace\\\", dst_pod!=\\\"\\\", dst_pod=\\\"$pod\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (pod)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"po/{{pod}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 19\n      },\n      \"id\": 82,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{dst_namespace=\\\"$namespace\\\", dst_pod!=\\\"\\\", dst_pod=\\\"$pod\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒po/{{pod}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{dst_namespace=\\\"$namespace\\\", dst_pod!=\\\"\\\", dst_pod=\\\"$pod\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"po/{{pod}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 19\n      },\n      \"id\": 83,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{dst_namespace=\\\"$namespace\\\", dst_pod!=\\\"\\\", dst_pod=\\\"$pod\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, pod))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p50 po/{{pod}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{dst_namespace=\\\"$namespace\\\", dst_pod!=\\\"\\\", dst_pod=\\\"$pod\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, pod))\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p95 po/{{pod}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{dst_namespace=\\\"$namespace\\\", dst_pod!=\\\"\\\", dst_pod=\\\"$pod\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, pod))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p99 po/{{pod}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"ms\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND TRAFFIC</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 26\n      },\n      \"id\": 88,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 28\n      },\n      \"id\": 89,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (pod) / sum(irate(response_total{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (pod)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"po/{{pod}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 28\n      },\n      \"id\": 90,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒po/{{pod}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"po/{{pod}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 28\n      },\n      \"id\": 91,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, pod))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p50 po/{{pod}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, pod))\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p95 po/{{pod}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, pod))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p99 po/{{pod}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"ms\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 35\n      },\n      \"id\": 197,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 8,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 36\n          },\n          \"id\": 191,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", deployment=\\\"$deployment\\\", direction=\\\"outbound\\\",errno!=\\\"\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}} {{errno}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTION FAILURES\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"none\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 8,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 36\n          },\n          \"id\": 183,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\" ,deployment=\\\"$deployment\\\", direction=\\\"outbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTIONS OPEN\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"cards\": {\n            \"cardPadding\": null,\n            \"cardRound\": null\n          },\n          \"color\": {\n            \"cardColor\": \"#b4ff00\",\n            \"colorScale\": \"sqrt\",\n            \"colorScheme\": \"interpolateOranges\",\n            \"exponent\": 0.5,\n            \"mode\": \"spectrum\"\n          },\n          \"dataFormat\": \"timeseries\",\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"gridPos\": {\n            \"h\": 8,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 36\n          },\n          \"heatmap\": {},\n          \"hideZeroBuckets\": false,\n          \"highlightCards\": true,\n          \"id\": 189,\n          \"legend\": {\n            \"show\": false\n          },\n          \"links\": [],\n          \"options\": {},\n          \"reverseYBuckets\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", deployment=\\\"$deployment\\\", direction=\\\"outbound\\\"}\",\n              \"format\": \"heatmap\",\n              \"intervalFactor\": 1,\n              \"refId\": \"A\"\n            }\n          ],\n          \"title\": \"TCP CONNECTION DURATION\",\n          \"tooltip\": {\n            \"show\": true,\n            \"showHistogram\": true\n          },\n          \"type\": \"heatmap\",\n          \"xAxis\": {\n            \"show\": true\n          },\n          \"xBucketNumber\": null,\n          \"xBucketSize\": null,\n          \"yAxis\": {\n            \"decimals\": null,\n            \"format\": \"ms\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true,\n            \"splitFactor\": null\n          },\n          \"yBucketBound\": \"auto\",\n          \"yBucketNumber\": null,\n          \"yBucketSize\": null\n        }\n      ],\n      \"title\": \"Outbound TCP Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 36\n      },\n      \"id\": 187,\n      \"panels\": [],\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND PODS</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 37\n      },\n      \"id\": 84,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 39\n      },\n      \"id\": 85,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_pod) / sum(irate(response_total{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_pod)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"po/{{dst_pod}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 39\n      },\n      \"id\": 86,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒po/{{dst_pod}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"po/{{dst_pod}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 39\n      },\n      \"id\": 87,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_pod))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p50 po/{{dst_pod}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_pod))\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p95 po/{{dst_pod}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", pod=\\\"$pod\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_pod))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p99 po/{{dst_pod}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"ms\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 46\n      },\n      \"height\": \"1px\",\n      \"id\": 171,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    }\n  ],\n  \"refresh\": \"1m\",\n  \"schemaVersion\": 18,\n  \"style\": \"dark\",\n  \"tags\": [\n    \"linkerd\"\n  ],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"text\": \"default\",\n          \"value\": \"default\"\n        },\n        \"hide\": 0,\n        \"label\": \"Data Source\",\n        \"name\": \"datasource\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Namespace\",\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds, namespace)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Pod\",\n        \"multi\": false,\n        \"name\": \"pod\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{namespace=\\\"$namespace\\\"}, pod)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"label_values(process_start_time_seconds{namespace=\\\"$namespace\\\"}, deployment)\",\n        \"hide\": 2,\n        \"includeAll\": false,\n        \"label\": \"deployment\",\n        \"multi\": false,\n        \"name\": \"deployment\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{namespace=\\\"$namespace\\\"}, deployment)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {\n          \"text\": \"All\",\n          \"value\": \"$__all\"\n        },\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"label_values(request_total{dst_namespace=\\\"$namespace\\\", dst_deployment=\\\"$deployment\\\"}, deployment)\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"inbound\",\n        \"options\": [],\n        \"query\": \"\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {\n          \"text\": \"All\",\n          \"value\": \"$__all\"\n        },\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"label_values(request_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\"}, dst_deployment)\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"outbound\",\n        \"options\": [],\n        \"query\": \"label_values(request_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\"}, dst_deployment)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-5m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"\",\n  \"title\": \"Linkerd Pod\",\n  \"uid\": \"linkerd-pod\",\n  \"version\": 1\n}\n"
  },
  {
    "path": "grafana/dashboards/prometheus-2-stats.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\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  \"gnetId\": null,\n  \"graphTooltip\": 1,\n  \"id\": null,\n  \"links\": [\n    {\n      \"icon\": \"info\",\n      \"tags\": [],\n      \"targetBlank\": true,\n      \"title\": \"Grafana Docs\",\n      \"tooltip\": \"\",\n      \"type\": \"link\",\n      \"url\": \"http://docs.grafana.org/\"\n    },\n    {\n      \"icon\": \"info\",\n      \"tags\": [],\n      \"targetBlank\": true,\n      \"title\": \"Prometheus Docs\",\n      \"type\": \"link\",\n      \"url\": \"http://prometheus.io/docs/introduction/overview/\"\n    }\n  ],\n  \"panels\": [\n    {\n      \"aliasColors\": {\n        \"prometheus\": \"#C15C17\",\n        \"{instance=\\\"localhost:9090\\\",job=\\\"prometheus\\\"}\": \"#CCA300\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 6,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 3,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(prometheus_tsdb_head_samples_appended_total{job=\\\"prometheus\\\"}[5m]))\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"samples\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Samples Appended\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 6,\n        \"x\": 6,\n        \"y\": 0\n      },\n      \"id\": 14,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"topk(5, max(scrape_duration_seconds) by (job))\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{job}}\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Scrape Duration\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"description\": \"\",\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 6,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"id\": 16,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(process_resident_memory_bytes{job=\\\"prometheus\\\"})\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"p8s process resident memory\",\n          \"refId\": \"D\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"process_virtual_memory_bytes{job=\\\"prometheus\\\"}\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"virtual memory\",\n          \"refId\": \"C\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Memory Profile\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": true,\n      \"colors\": [\n        \"rgba(50, 172, 45, 0.97)\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"rgba(245, 54, 54, 0.9)\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 6,\n        \"x\": 18,\n        \"y\": 0\n      },\n      \"id\": 37,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"prometheus_tsdb_wal_corruptions_total{job=\\\"prometheus\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\",\n          \"step\": 60\n        }\n      ],\n      \"thresholds\": \"0.1,1\",\n      \"title\": \"WAL Corruptions\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"200%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"None\",\n          \"value\": \"0\"\n        }\n      ],\n      \"valueName\": \"max\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 6,\n        \"x\": 0,\n        \"y\": 5\n      },\n      \"id\": 29,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(prometheus_tsdb_head_active_appenders{job=\\\"prometheus\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"active_appenders\",\n          \"metric\": \"\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"sum(process_open_fds{job=\\\"prometheus\\\"})\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"open_fds\",\n          \"refId\": \"B\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Active Appenders\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"prometheus\": \"#F9BA8F\",\n        \"{instance=\\\"localhost:9090\\\",interval=\\\"5s\\\",job=\\\"prometheus\\\"}\": \"#F9BA8F\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 6,\n        \"x\": 6,\n        \"y\": 5\n      },\n      \"id\": 2,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"prometheus_tsdb_blocks_loaded{job=\\\"prometheus\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"blocks\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Blocks Loaded\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"description\": \"\",\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 6,\n        \"x\": 12,\n        \"y\": 5\n      },\n      \"id\": 33,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"prometheus_tsdb_head_chunks{job=\\\"prometheus\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"chunks\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Head Chunks\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"bytes\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": false\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 6,\n        \"x\": 18,\n        \"y\": 5\n      },\n      \"id\": 36,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"duration-p99\",\n          \"yaxis\": 2\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"prometheus_tsdb_head_gc_duration_seconds{job=\\\"prometheus\\\",quantile=\\\"0.99\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"duration-p99\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"irate(prometheus_tsdb_head_gc_duration_seconds_count{job=\\\"prometheus\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"collections\",\n          \"refId\": \"B\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Head Block GC Activity\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"description\": \"\",\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 10\n      },\n      \"id\": 20,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"duration-p99\",\n          \"yaxis\": 2\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.99, sum(rate(prometheus_tsdb_compaction_duration_bucket{job=\\\"prometheus\\\"}[5m])) by (le))\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"duration-{{p99}}\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"irate(prometheus_tsdb_compactions_total{job=\\\"prometheus\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"compactions\",\n          \"refId\": \"B\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"irate(prometheus_tsdb_compactions_failed_total{job=\\\"prometheus\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"failed\",\n          \"refId\": \"C\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"irate(prometheus_tsdb_compactions_triggered_total{job=\\\"prometheus\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"triggered\",\n          \"refId\": \"D\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Compaction Activity\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"s\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 10\n      },\n      \"id\": 32,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_tsdb_reloads_total{job=\\\"prometheus\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"reloads\",\n          \"refId\": \"A\",\n          \"step\": 20\n        },\n        {\n          \"expr\": \"rate(prometheus_tsdb_reloads_failures_total{job=\\\"prometheus\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"failures\",\n          \"refId\": \"B\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Reload Count\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 5,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 10\n      },\n      \"id\": 38,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"prometheus_engine_query_duration_seconds{job=\\\"prometheus\\\", quantile=\\\"0.99\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{slice}}_p99\",\n          \"refId\": \"A\",\n          \"step\": 20\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Query Durations\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 0,\n      \"grid\": {},\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 15\n      },\n      \"id\": 35,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": false,\n        \"current\": false,\n        \"hideEmpty\": true,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"connected\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"max(prometheus_rule_group_duration_seconds{job=\\\"prometheus\\\"}) by (quantile)\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{quantile}}\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Rule Group Eval Duration\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"cumulative\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 15\n      },\n      \"id\": 39,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_rule_group_iterations_missed_total{job=\\\"prometheus\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"missed\",\n          \"refId\": \"B\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"rate(prometheus_rule_group_iterations_total{job=\\\"prometheus\\\"}[5m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"iterations\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Rule Group Eval Activity\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 22\n      },\n      \"height\": \"1px\",\n      \"id\": 171,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    }\n  ],\n  \"refresh\": \"1m\",\n  \"revision\": \"1.0\",\n  \"schemaVersion\": 18,\n  \"style\": \"dark\",\n  \"tags\": [\n    \"prometheus\"\n  ],\n  \"templating\": {\n    \"list\": []\n  },\n  \"time\": {\n    \"from\": \"now-1h\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"now\": true,\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"browser\",\n  \"title\": \"Prometheus 2 Stats\",\n  \"uid\": \"prometheus\",\n  \"version\": 1\n}\n"
  },
  {
    "path": "grafana/dashboards/prometheus-benchmark.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"description\": \"Metrics useful for benchmarking and loadtesting Prometheus itself. Designed primarily for Prometheus 2.7.x.\",\n  \"editable\": true,\n  \"gnetId\": 9761,\n  \"graphTooltip\": 1,\n  \"id\": null,\n  \"iteration\": 1549379947837,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0      \n      },\n      \"id\": 49,\n      \"panels\": [],\n      \"title\": \"Basics\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"id\": 40,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"prometheus_build_info{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{version}} - {{revision}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Prometheus Version\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 1\n      },\n      \"id\": 42,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"time() - process_start_time_seconds{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Uptime\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Uptime\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"dtdurations\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 1\n      },\n      \"id\": 72,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"time() - prometheus_config_last_reload_success_timestamp_seconds{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Age\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Last Successful Config Reload\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"transparent\": false,\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"dtdurations\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 46,\n      \"panels\": [],\n      \"title\": \"Ingestion\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\",\n        \"Time series\": \"#70dbed\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"id\": 3,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"prometheus_tsdb_head_series{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Time series\",\n          \"metric\": \"prometheus_local_storage_memory_series\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Head Time series\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 9\n      },\n      \"id\": 26,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"prometheus_tsdb_head_active_appenders{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Head Appenders\",\n          \"metric\": \"prometheus_local_storage_memory_series\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Head Active Appenders\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"samples/s\": \"#e5a8e2\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 9\n      },\n      \"id\": 1,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_tsdb_head_samples_appended_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"samples/s\",\n          \"metric\": \"prometheus_local_storage_ingested_samples_total\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Samples Appended/s\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\",\n        \"To persist\": \"#9AC48A\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"id\": 2,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"/Max.*/\",\n          \"fill\": 0\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"prometheus_tsdb_head_chunks{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Chunks\",\n          \"metric\": \"prometheus_local_storage_memory_chunks\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Head Chunks\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 16\n      },\n      \"id\": 4,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_tsdb_head_chunks_created_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Created\",\n          \"metric\": \"prometheus_local_storage_chunk_ops_total\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Head Chunks Created\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ops\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\",\n        \"Removed\": \"#e5ac0e\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 16\n      },\n      \"id\": 25,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_tsdb_head_chunks_removed_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Removed\",\n          \"metric\": \"prometheus_local_storage_chunk_ops_total\",\n          \"refId\": \"B\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Head Chunks Removed\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ops\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 23\n      },\n      \"id\": 52,\n      \"panels\": [],\n      \"title\": \"Compaction\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max\": \"#447ebc\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\",\n        \"Min\": \"#447ebc\",\n        \"Now\": \"#7eb26d\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 24\n      },\n      \"id\": 28,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Max\",\n          \"fillBelowTo\": \"Min\",\n          \"lines\": false\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"prometheus_tsdb_head_min_time{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Min\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"A\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"time() * 1000\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Now\",\n          \"refId\": \"C\"\n        },\n        {\n          \"expr\": \"prometheus_tsdb_head_max_time{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Max\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"B\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Head Time Range\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"dateTimeAsIso\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 24\n      },\n      \"id\": 29,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_tsdb_head_gc_duration_seconds_sum{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"GC Time/s\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Head GC Time/s\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 24\n      },\n      \"id\": 14,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"Queue length\",\n          \"yaxis\": 2\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"prometheus_tsdb_blocks_loaded{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Blocks Loaded\",\n          \"metric\": \"prometheus_local_storage_indexing_batch_sizes_sum\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Blocks Loaded\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Failed Compactions\": \"#bf1b00\",\n        \"Failed Reloads\": \"#bf1b00\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 31\n      },\n      \"id\": 30,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_tsdb_reloads_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Reloads\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"TSDB Reloads/s\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"none\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Failed Compactions\": \"#bf1b00\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 31\n      },\n      \"id\": 31,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_tsdb_wal_fsync_duration_seconds_sum{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m]) / rate(prometheus_tsdb_wal_fsync_duration_seconds_count{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Fsync Latency\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"A\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"rate(prometheus_tsdb_wal_truncate_duration_seconds_sum{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m]) / rate(prometheus_tsdb_wal_truncate_duration_seconds_count{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Truncate Latency\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"B\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"WAL Fsync&Truncate Latency\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Failed Compactions\": \"#bf1b00\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\",\n        \"{instance=\\\"demo.robustperception.io:9090\\\",job=\\\"prometheus\\\"}\": \"#bf1b00\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 31\n      },\n      \"id\": 32,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_tsdb_wal_corruptions_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"WAL Corruptions\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"A\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"rate(prometheus_tsdb_reloads_failures_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Reload Failures\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"B\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"rate(prometheus_tsdb_head_series_not_found{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Head Series Not Found\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"C\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"rate(prometheus_tsdb_compactions_failed_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Compaction Failures\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"D\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"rate(prometheus_tsdb_retention_cutoffs_failures_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Retention Cutoff Failures\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"E\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"rate(prometheus_tsdb_checkpoint_creations_failed_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"WAL Checkpoint Creation Failures\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"F\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"rate(prometheus_tsdb_checkpoint_deletions_failed_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"WAL Checkpoint Deletion Failures\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"G\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"TSDB Problems/s\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"none\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Failed Compactions\": \"#bf1b00\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 38\n      },\n      \"id\": 19,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_tsdb_compactions_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m])\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Compactions\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Compactions/s\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"none\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 38\n      },\n      \"id\": 33,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_tsdb_compaction_duration_seconds_sum{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Compaction Time/s\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Allocated bytes\": \"#F9BA8F\",\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\",\n        \"RSS\": \"#890F02\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 38\n      },\n      \"id\": 8,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_tsdb_time_retentions_total[10m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Time Cutoffs\",\n          \"metric\": \"last\",\n          \"refId\": \"A\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"rate(prometheus_tsdb_size_retentions_total[10m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Size Cutoffs\",\n          \"metric\": \"last\",\n          \"refId\": \"B\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Retention Cutoffs/s\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"none\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 45\n      },\n      \"id\": 27,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_tsdb_compaction_chunk_range_seconds_sum{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m]) / rate(prometheus_tsdb_compaction_chunk_range_seconds_count{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Chunk Time Range\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"First Compaction, Avg Chunk Time Range\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"dtdurationms\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 45\n      },\n      \"id\": 35,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_tsdb_compaction_chunk_size_bytes_sum{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m]) / rate(prometheus_tsdb_compaction_chunk_samples_sum{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Bytes/Sample\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"First Compaction, Avg Bytes/Sample\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 45\n      },\n      \"id\": 34,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_tsdb_compaction_chunk_samples_sum{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m]) / rate(prometheus_tsdb_compaction_chunk_samples_count{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[10m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Chunk Samples\",\n          \"metric\": \"prometheus_local_storage_series_chunks_persisted_count\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"First Compaction, Avg Chunk Samples\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"none\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 52\n      },\n      \"id\": 55,\n      \"panels\": [],\n      \"title\": \"Resource Usage\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {\n        \"Allocated bytes\": \"#7EB26D\",\n        \"Allocated bytes - 1m max\": \"#BF1B00\",\n        \"Allocated bytes - 1m min\": \"#BF1B00\",\n        \"Allocated bytes - 5m max\": \"#BF1B00\",\n        \"Allocated bytes - 5m min\": \"#BF1B00\",\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\",\n        \"RSS\": \"#447EBC\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 53\n      },\n      \"id\": 6,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [\n        {\n          \"alias\": \"/-/\",\n          \"fill\": 0\n        }\n      ],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"process_resident_memory_bytes{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"RSS\",\n          \"metric\": \"process_resident_memory_bytes\",\n          \"refId\": \"B\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"prometheus_local_storage_target_heap_size_bytes{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Target heap size\",\n          \"metric\": \"go_memstats_alloc_bytes\",\n          \"refId\": \"D\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"go_memstats_next_gc_bytes{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Next GC\",\n          \"metric\": \"go_memstats_next_gc_bytes\",\n          \"refId\": \"C\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"go_memstats_alloc_bytes{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Allocated\",\n          \"metric\": \"go_memstats_alloc_bytes\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Memory\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Allocated bytes\": \"#F9BA8F\",\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\",\n        \"RSS\": \"#890F02\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 53\n      },\n      \"id\": 7,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(go_memstats_alloc_bytes_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m])\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Allocated Bytes/s\",\n          \"metric\": \"go_memstats_alloc_bytes\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Allocations\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 53\n      },\n      \"id\": 9,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": false,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"irate(process_cpu_seconds_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m])\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Irate\",\n          \"metric\": \"prometheus_local_storage_ingested_samples_total\",\n          \"refId\": \"A\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"rate(process_cpu_seconds_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[5m])\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"5m rate\",\n          \"metric\": \"prometheus_local_storage_ingested_samples_total\",\n          \"refId\": \"B\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"CPU\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": [\n          \"avg\"\n        ]\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"none\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 60\n      },\n      \"id\": 70,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": false,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"prometheus_tsdb_symbol_table_size_bytes{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"RAM Used\",\n          \"metric\": \"prometheus_local_storage_ingested_samples_total\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Symbol Tables Size\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": [\n          \"avg\"\n        ]\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": 2,\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 60\n      },\n      \"id\": 71,\n      \"legend\": {\n        \"alignAsTable\": false,\n        \"avg\": false,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"max\": false,\n        \"min\": false,\n        \"rightSide\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"prometheus_tsdb_storage_blocks_bytes_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"} or prometheus_tsdb_storage_blocks_bytes{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Disk Used\",\n          \"metric\": \"prometheus_local_storage_ingested_samples_total\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Block Size\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": [\n          \"avg\"\n        ]\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"bytes\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Max\": \"#e24d42\",\n        \"Open\": \"#508642\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 60\n      },\n      \"id\": 41,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"process_max_fds{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Max\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"process_open_fds{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"Open\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"File Descriptors\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 67\n      },\n      \"id\": 58,\n      \"panels\": [],\n      \"title\": \"HTTP Server\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"description\": \"\",\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 68\n      },\n      \"id\": 38,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_http_request_duration_seconds_count{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{handler}}\",\n          \"metric\": \"prometheus_local_storage_memory_chunkdescs\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"HTTP requests/s\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"description\": \"\",\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 68\n      },\n      \"id\": 37,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_http_request_duration_seconds_sum{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m]) / rate(prometheus_http_request_duration_seconds_count{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{handler}}\",\n          \"metric\": \"prometheus_local_storage_memory_chunkdescs\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"HTTP request latency\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"description\": \"\",\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 68\n      },\n      \"id\": 36,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_http_request_duration_seconds_sum{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{handler}}\",\n          \"metric\": \"prometheus_local_storage_memory_chunkdescs\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Time spent in HTTP requests/s\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 75\n      },\n      \"id\": 63,\n      \"panels\": [],\n      \"title\": \"Query Engine\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"description\": \"Time spent in each mode, per second\",\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 76\n      },\n      \"id\": 24,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": true,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_engine_query_duration_seconds_sum{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"{{slice}}\",\n          \"metric\": \"prometheus_local_storage_memory_chunkdescs\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Query engine timings/s\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 76\n      },\n      \"id\": 22,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_rule_group_iterations_missed_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m])  \",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Rule group missed\",\n          \"metric\": \"prometheus_local_storage_memory_chunkdescs\",\n          \"refId\": \"B\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"rate(prometheus_rule_evaluation_failures_total{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Rule evals failed\",\n          \"metric\": \"prometheus_local_storage_memory_chunkdescs\",\n          \"refId\": \"C\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Rule group evaluation problems/s\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 76\n      },\n      \"id\": 23,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"rate(prometheus_rule_group_duration_seconds_sum{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}[1m])\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Rule evaluation duration\",\n          \"metric\": \"prometheus_local_storage_memory_chunkdescs\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Evaluation time of rule groups/s\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 83\n      },\n      \"id\": 61,\n      \"panels\": [],\n      \"repeat\": \"RuleGroup\",\n      \"title\": \"Rule Group: $RuleGroup\",\n      \"type\": \"row\"\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Interval\": \"#890f02\",\n        \"Last Duration\": \"#f9934e\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 84\n      },\n      \"id\": 43,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"repeat\": null,\n      \"repeatDirection\": \"h\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"prometheus_rule_group_interval_seconds{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\",rule_group=~\\\"$RuleGroup\\\"}\\n\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Interval\",\n          \"metric\": \"prometheus_local_storage_memory_chunkdescs\",\n          \"refId\": \"A\",\n          \"step\": 10\n        },\n        {\n          \"expr\": \"prometheus_rule_group_last_duration_seconds{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\",rule_group=~\\\"$RuleGroup\\\"}\\n\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Last Duration\",\n          \"metric\": \"prometheus_local_storage_memory_chunkdescs\",\n          \"refId\": \"B\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"$RuleGroup: Duration\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"s\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {\n        \"Chunks\": \"#1F78C1\",\n        \"Chunks to persist\": \"#508642\",\n        \"Interval\": \"#890f02\",\n        \"Last Duration\": \"#f9934e\",\n        \"Max chunks\": \"#052B51\",\n        \"Max to persist\": \"#3F6833\"\n      },\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"editable\": true,\n      \"error\": false,\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 84\n      },\n      \"id\": 66,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"repeatDirection\": \"h\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"prometheus_rule_group_rules{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\",rule_group=~\\\"$RuleGroup\\\"}\\n\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Rules\",\n          \"metric\": \"prometheus_local_storage_memory_chunkdescs\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"$RuleGroup: Rules\",\n      \"tooltip\": {\n        \"msResolution\": false,\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": 0,\n          \"format\": \"none\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 91\n      },\n      \"height\": \"1px\",\n      \"id\": 171,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    }\n  ],\n  \"refresh\": \"1m\",\n  \"schemaVersion\": 16,\n  \"style\": \"dark\",\n  \"tags\": [\n    \"prometheus\"\n  ],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"text\": \"default\",\n          \"value\": \"default\"\n        },\n        \"hide\": 0,\n        \"label\": \"Data Source\",\n        \"name\": \"datasource\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"Prometheus\",\n        \"options\": [],\n        \"query\": \"query_result(up{job=\\\"prometheus\\\"} == 1)\",\n        \"refresh\": 2,\n        \"regex\": \".*instance=\\\"([^\\\"]+):9090\\\".*\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": null,\n        \"tags\": [],\n        \"tagsQuery\": null,\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"RuleGroup\",\n        \"options\": [],\n        \"query\": \"prometheus_rule_group_last_duration_seconds{job=\\\"prometheus\\\",instance=\\\"$Prometheus:9090\\\"}\",\n        \"refresh\": 2,\n        \"regex\": \".*rule_group=\\\"(.*?)\\\".*\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-6h\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"browser\",\n  \"title\": \"Prometheus Benchmark - 2.7.x\",\n  \"uid\": \"prometheus-benchmark\",\n  \"version\": 10\n}\n"
  },
  {
    "path": "grafana/dashboards/replicaset.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n    \"annotations\": {\n      \"list\": [\n        {\n          \"builtIn\": 1,\n          \"datasource\": \"-- Grafana --\",\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    \"gnetId\": null,\n    \"graphTooltip\": 1,\n    \"id\": 16,\n    \"iteration\": 1573121539385,\n    \"links\": [],\n    \"panels\": [\n      {        \n        \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"height:32px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 32px\\\">replicaset/$replicaset</span>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 0\n        },\n        \"id\": 20,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"colorBackground\": false,\n        \"colorValue\": false,\n        \"colors\": [\n          \"#d44a3a\",\n          \"rgba(237, 129, 40, 0.89)\",\n          \"#299c46\"\n        ],\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"decimals\": null,\n        \"format\": \"percentunit\",\n        \"gauge\": {\n          \"maxValue\": 1,\n          \"minValue\": 0,\n          \"show\": true,\n          \"thresholdLabels\": false,\n          \"thresholdMarkers\": true\n        },\n        \"gridPos\": {\n          \"h\": 4,\n          \"w\": 8,\n          \"x\": 0,\n          \"y\": 2\n        },\n        \"id\": 5,\n        \"interval\": null,\n        \"links\": [],\n        \"mappingType\": 1,\n        \"mappingTypes\": [\n          {\n            \"name\": \"value to text\",\n            \"value\": 1\n          },\n          {\n            \"name\": \"range to text\",\n            \"value\": 2\n          }\n        ],\n        \"maxDataPoints\": 100,\n        \"nullPointMode\": \"connected\",\n        \"nullText\": null,\n        \"options\": {},\n        \"postfix\": \"\",\n        \"postfixFontSize\": \"50%\",\n        \"prefix\": \"\",\n        \"prefixFontSize\": \"50%\",\n        \"rangeMaps\": [\n          {\n            \"from\": \"null\",\n            \"text\": \"N/A\",\n            \"to\": \"null\"\n          }\n        ],\n        \"sparkline\": {\n          \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n          \"full\": true,\n          \"lineColor\": \"rgb(31, 120, 193)\",\n          \"show\": true\n        },\n        \"tableColumn\": \"\",\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) / sum(irate(response_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n            \"format\": \"time_series\",\n            \"instant\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": \"0.9,.99\",\n        \"title\": \"SUCCESS RATE\",\n        \"transparent\": true,\n        \"type\": \"singlestat\",\n        \"valueFontSize\": \"80%\",\n        \"valueMaps\": [\n          {\n            \"op\": \"=\",\n            \"text\": \"N/A\",\n            \"value\": \"null\"\n          }\n        ],\n        \"valueName\": \"current\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"colorBackground\": false,\n        \"colorValue\": false,\n        \"colors\": [\n          \"#299c46\",\n          \"rgba(237, 129, 40, 0.89)\",\n          \"#d44a3a\"\n        ],\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"decimals\": null,\n        \"format\": \"none\",\n        \"gauge\": {\n          \"maxValue\": 100,\n          \"minValue\": 0,\n          \"show\": false,\n          \"thresholdLabels\": false,\n          \"thresholdMarkers\": true\n        },\n        \"gridPos\": {\n          \"h\": 4,\n          \"w\": 8,\n          \"x\": 8,\n          \"y\": 2\n        },\n        \"id\": 4,\n        \"interval\": null,\n        \"links\": [],\n        \"mappingType\": 1,\n        \"mappingTypes\": [\n          {\n            \"name\": \"value to text\",\n            \"value\": 1\n          },\n          {\n            \"name\": \"range to text\",\n            \"value\": 2\n          }\n        ],\n        \"maxDataPoints\": 100,\n        \"nullPointMode\": \"connected\",\n        \"nullText\": null,\n        \"options\": {},\n        \"postfix\": \" RPS\",\n        \"postfixFontSize\": \"100%\",\n        \"prefix\": \"\",\n        \"prefixFontSize\": \"50%\",\n        \"rangeMaps\": [\n          {\n            \"from\": \"null\",\n            \"text\": \"N/A\",\n            \"to\": \"null\"\n          }\n        ],\n        \"sparkline\": {\n          \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n          \"full\": true,\n          \"lineColor\": \"rgb(31, 120, 193)\",\n          \"show\": true\n        },\n        \"tableColumn\": \"\",\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n            \"format\": \"time_series\",\n            \"instant\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": \"\",\n        \"title\": \"REQUEST RATE\",\n        \"transparent\": true,\n        \"type\": \"singlestat\",\n        \"valueFontSize\": \"100%\",\n        \"valueMaps\": [\n          {\n            \"op\": \"=\",\n            \"text\": \"N/A\",\n            \"value\": \"null\"\n          }\n        ],\n        \"valueName\": \"current\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"colorBackground\": false,\n        \"colorValue\": false,\n        \"colors\": [\n          \"#299c46\",\n          \"rgba(237, 129, 40, 0.89)\",\n          \"#d44a3a\"\n        ],\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"decimals\": null,\n        \"format\": \"none\",\n        \"gauge\": {\n          \"maxValue\": 100,\n          \"minValue\": 0,\n          \"show\": false,\n          \"thresholdLabels\": false,\n          \"thresholdMarkers\": true\n        },\n        \"gridPos\": {\n          \"h\": 4,\n          \"w\": 4,\n          \"x\": 16,\n          \"y\": 2\n        },\n        \"id\": 11,\n        \"interval\": null,\n        \"links\": [],\n        \"mappingType\": 1,\n        \"mappingTypes\": [\n          {\n            \"name\": \"value to text\",\n            \"value\": 1\n          },\n          {\n            \"name\": \"range to text\",\n            \"value\": 2\n          }\n        ],\n        \"maxDataPoints\": 100,\n        \"nullPointMode\": \"connected\",\n        \"nullText\": null,\n        \"options\": {},\n        \"postfix\": \"\",\n        \"postfixFontSize\": \"100%\",\n        \"prefix\": \"\",\n        \"prefixFontSize\": \"100%\",\n        \"rangeMaps\": [\n          {\n            \"from\": \"null\",\n            \"text\": \"N/A\",\n            \"to\": \"null\"\n          }\n        ],\n        \"sparkline\": {\n          \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n          \"full\": false,\n          \"lineColor\": \"rgb(31, 120, 193)\",\n          \"show\": false\n        },\n        \"tableColumn\": \"\",\n        \"targets\": [\n          {\n            \"expr\": \"count(count(request_total{dst_namespace=\\\"$namespace\\\", replicaset!=\\\"\\\", dst_replicaset!=\\\"\\\", dst_replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\"}) by (namespace, replicaset))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": \"\",\n        \"title\": \"INBOUND REPLICASETS\",\n        \"transparent\": true,\n        \"type\": \"singlestat\",\n        \"valueFontSize\": \"100%\",\n        \"valueMaps\": [\n          {\n            \"op\": \"=\",\n            \"text\": \"N/A\",\n            \"value\": \"null\"\n          }\n        ],\n        \"valueName\": \"current\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"colorBackground\": false,\n        \"colorValue\": false,\n        \"colors\": [\n          \"#299c46\",\n          \"rgba(237, 129, 40, 0.89)\",\n          \"#d44a3a\"\n        ],\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"format\": \"none\",\n        \"gauge\": {\n          \"maxValue\": 100,\n          \"minValue\": 0,\n          \"show\": false,\n          \"thresholdLabels\": false,\n          \"thresholdMarkers\": true\n        },\n        \"gridPos\": {\n          \"h\": 4,\n          \"w\": 4,\n          \"x\": 20,\n          \"y\": 2\n        },\n        \"id\": 15,\n        \"interval\": null,\n        \"links\": [],\n        \"mappingType\": 1,\n        \"mappingTypes\": [\n          {\n            \"name\": \"value to text\",\n            \"value\": 1\n          },\n          {\n            \"name\": \"range to text\",\n            \"value\": 2\n          }\n        ],\n        \"maxDataPoints\": 100,\n        \"nullPointMode\": \"connected\",\n        \"nullText\": null,\n        \"options\": {},\n        \"postfix\": \"\",\n        \"postfixFontSize\": \"100%\",\n        \"prefix\": \"\",\n        \"prefixFontSize\": \"50%\",\n        \"rangeMaps\": [\n          {\n            \"from\": \"null\",\n            \"text\": \"N/A\",\n            \"to\": \"null\"\n          }\n        ],\n        \"sparkline\": {\n          \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n          \"full\": false,\n          \"lineColor\": \"rgb(31, 120, 193)\",\n          \"show\": false\n        },\n        \"tableColumn\": \"\",\n        \"targets\": [\n          {\n            \"expr\": \"count(count(request_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\"}) by (namespace, dst_replicaset))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": \"\",\n        \"title\": \"OUTBOUND REPLICASETS\",\n        \"transparent\": true,\n        \"type\": \"singlestat\",\n        \"valueFontSize\": \"100%\",\n        \"valueMaps\": [\n          {\n            \"op\": \"=\",\n            \"text\": \"N/A\",\n            \"value\": \"null\"\n          }\n        ],\n        \"valueName\": \"current\"\n      },\n      {\n        \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND TRAFFIC</span>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 6\n        },\n        \"id\": 17,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 0,\n          \"y\": 8\n        },\n        \"id\": 67,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (replicaset) / sum(irate(response_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (replicaset)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"rs/{{replicaset}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"SUCCESS RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": null,\n            \"format\": \"percentunit\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": \"1\",\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 0,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 8,\n          \"y\": 8\n        },\n        \"id\": 2,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"inbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (replicaset)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"🔒rs/{{replicaset}}\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"inbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (replicaset)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"rs/{{replicaset}}\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"REQUEST RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": null,\n            \"format\": \"rps\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 16,\n          \"y\": 8\n        },\n        \"id\": 68,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, replicaset))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"p50 rs/{{replicaset}}\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, replicaset))\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"p95 rs/{{replicaset}}\",\n            \"refId\": \"B\"\n          },\n          {\n            \"expr\": \"histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, replicaset))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"p99 rs/{{replicaset}}\",\n            \"refId\": \"C\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"LATENCY\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": null,\n            \"format\": \"ms\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"collapsed\": true,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 15\n        },\n        \"id\": 148,\n        \"panels\": [\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 1,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 0,\n              \"y\": 16\n            },\n            \"id\": 167,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"inbound\\\",errno!=\\\"\\\"}\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"{{peer}} {{errno}}\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"TCP CONNECTION FAILURES\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"decimals\": null,\n                \"format\": \"none\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": \"0\",\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 0,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 8,\n              \"y\": 16\n            },\n            \"id\": 168,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"inbound\\\"}\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"{{peer}}\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"TCP CONNECTIONS OPEN\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"short\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": \"0\",\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"cards\": {\n              \"cardPadding\": null,\n              \"cardRound\": null\n            },\n            \"color\": {\n              \"cardColor\": \"#b4ff00\",\n              \"colorScale\": \"sqrt\",\n              \"colorScheme\": \"interpolateOranges\",\n              \"exponent\": 0.5,\n              \"mode\": \"spectrum\"\n            },\n            \"dataFormat\": \"timeseries\",\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 16,\n              \"y\": 16\n            },\n            \"heatmap\": {},\n            \"hideZeroBuckets\": false,\n            \"highlightCards\": true,\n            \"id\": 169,\n            \"legend\": {\n              \"show\": false\n            },\n            \"links\": [],\n            \"options\": {},\n            \"reverseYBuckets\": false,\n            \"targets\": [\n              {\n                \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"inbound\\\"}\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"refId\": \"A\"\n              }\n            ],\n            \"title\": \"TCP CONNECTION DURATION\",\n            \"tooltip\": {\n              \"show\": true,\n              \"showHistogram\": true\n            },\n            \"type\": \"heatmap\",\n            \"xAxis\": {\n              \"show\": true\n            },\n            \"xBucketNumber\": null,\n            \"xBucketSize\": null,\n            \"yAxis\": {\n              \"decimals\": null,\n              \"format\": \"dtdurationms\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true,\n              \"splitFactor\": null\n            },\n            \"yBucketBound\": \"auto\",\n            \"yBucketNumber\": null,\n            \"yBucketSize\": null\n          }\n        ],\n        \"title\": \"Inbound TCP Metrics\",\n        \"type\": \"row\"\n      },\n      {\n        \"collapsed\": false,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 16\n        },\n        \"id\": 152,\n        \"panels\": [],\n        \"title\": \"\",\n        \"type\": \"row\"\n      },\n      {\n        \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND REPLICASETS</span>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 17\n        },\n        \"id\": 76,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      },\n      {\n        \"collapsed\": false,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 19\n        },\n        \"id\": 59,\n        \"panels\": [],\n        \"repeat\": \"inbound\",\n        \"scopedVars\": {\n          \"inbound\": {\n            \"selected\": false,\n            \"text\": \"web\",\n            \"value\": \"web\"\n          }\n        },\n        \"title\": \"rs/$inbound\",\n        \"type\": \"row\"\n      },\n      {\n        \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">rs/$inbound</span>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 20\n        },\n        \"id\": 39,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"scopedVars\": {\n          \"inbound\": {\n            \"selected\": false,\n            \"text\": \"web\",\n            \"value\": \"web\"\n          }\n        },\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 0,\n          \"y\": 22\n        },\n        \"id\": 36,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"scopedVars\": {\n          \"inbound\": {\n            \"selected\": false,\n            \"text\": \"web\",\n            \"value\": \"web\"\n          }\n        },\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", replicaset!=\\\"\\\", replicaset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (replicaset, pod) / sum(irate(response_total{replicaset!=\\\"\\\", replicaset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (replicaset, pod)\",\n            \"format\": \"time_series\",\n            \"instant\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"po/{{pod}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"SUCCESS RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": null,\n            \"format\": \"percentunit\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": \"1\",\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 0,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 8,\n          \"y\": 22\n        },\n        \"id\": 22,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"scopedVars\": {\n          \"inbound\": {\n            \"selected\": false,\n            \"text\": \"web\",\n            \"value\": \"web\"\n          }\n        },\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(request_total{replicaset!=\\\"\\\", replicaset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (replicaset, pod)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"🔒po/{{pod}}\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"sum(irate(request_total{replicaset!=\\\"\\\", replicaset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (replicaset, pod)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"po/{{pod}}\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"REQUEST RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": null,\n            \"format\": \"rps\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 16,\n          \"y\": 22\n        },\n        \"id\": 29,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"scopedVars\": {\n          \"inbound\": {\n            \"selected\": false,\n            \"text\": \"web\",\n            \"value\": \"web\"\n          }\n        },\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{replicaset!=\\\"\\\", replicaset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, replicaset))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"P50 rs/{{replicaset}}\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{replicaset!=\\\"\\\", replicaset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, replicaset))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"P95 rs/{{replicaset}}\",\n            \"refId\": \"B\"\n          },\n          {\n            \"expr\": \"histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{replicaset!=\\\"\\\", replicaset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, replicaset))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"P99 rs/{{replicaset}}\",\n            \"refId\": \"C\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"LATENCY\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"ms\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"collapsed\": false,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 29\n        },\n        \"id\": 34,\n        \"panels\": [],\n        \"repeat\": null,\n        \"title\": \"\",\n        \"type\": \"row\"\n      },\n      {\n        \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND TRAFFIC</span>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 30\n        },\n        \"id\": 32,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 0,\n          \"y\": 32\n        },\n        \"id\": 77,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_replicaset) / sum(irate(response_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_replicaset)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"rs/{{dst_replicaset}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"SUCCESS RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": null,\n            \"format\": \"percentunit\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": \"1\",\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 0,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 8,\n          \"y\": 32\n        },\n        \"id\": 78,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_replicaset)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"🔒rs/{{dst_replicaset}}\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_replicaset)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"rs/{{dst_replicaset}}\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"REQUEST RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"rps\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 16,\n          \"y\": 32\n        },\n        \"id\": 79,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_replicaset))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"P95 rs/{{dst_replicaset}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"P95 LATENCY\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"ms\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"collapsed\": true,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 39\n        },\n        \"id\": 154,\n        \"panels\": [\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 1,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 0,\n              \"y\": 29\n            },\n            \"id\": 157,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\",errno!=\\\"\\\"}\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"{{peer}} {{errno}}\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"TCP CONNECTION FAILURES\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"decimals\": null,\n                \"format\": \"none\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": \"0\",\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 0,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 8,\n              \"y\": 29\n            },\n            \"id\": 166,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\"}\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"{{peer}}\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"TCP CONNECTIONS OPEN\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"short\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": \"0\",\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"cards\": {\n              \"cardPadding\": null,\n              \"cardRound\": null\n            },\n            \"color\": {\n              \"cardColor\": \"#b4ff00\",\n              \"colorScale\": \"sqrt\",\n              \"colorScheme\": \"interpolateOranges\",\n              \"exponent\": 0.5,\n              \"mode\": \"spectrum\"\n            },\n            \"dataFormat\": \"timeseries\",\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 16,\n              \"y\": 29\n            },\n            \"heatmap\": {},\n            \"hideZeroBuckets\": false,\n            \"highlightCards\": true,\n            \"id\": 160,\n            \"legend\": {\n              \"show\": false\n            },\n            \"links\": [],\n            \"options\": {},\n            \"reverseYBuckets\": false,\n            \"targets\": [\n              {\n                \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", direction=\\\"outbound\\\"}\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"refId\": \"A\"\n              }\n            ],\n            \"title\": \"TCP CONNECTION DURATION\",\n            \"tooltip\": {\n              \"show\": true,\n              \"showHistogram\": true\n            },\n            \"type\": \"heatmap\",\n            \"xAxis\": {\n              \"show\": true\n            },\n            \"xBucketNumber\": null,\n            \"xBucketSize\": null,\n            \"yAxis\": {\n              \"decimals\": null,\n              \"format\": \"dtdurationms\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true,\n              \"splitFactor\": null\n            },\n            \"yBucketBound\": \"auto\",\n            \"yBucketNumber\": null,\n            \"yBucketSize\": null\n          }\n        ],\n        \"title\": \"Outbound TCP Metrics\",\n        \"type\": \"row\"\n      },\n      {\n        \"collapsed\": false,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 40\n        },\n        \"id\": 156,\n        \"panels\": [],\n        \"title\": \"\",\n        \"type\": \"row\"\n      },\n      {\n        \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND REPLICASETS</span>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 41\n        },\n        \"id\": 80,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      },\n      {\n        \"collapsed\": true,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 43\n        },\n        \"id\": 27,\n        \"panels\": [\n          {\n            \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">rs/$outbound</span>\\n</div>\",\n            \"gridPos\": {\n              \"h\": 2,\n              \"w\": 24,\n              \"x\": 0,\n              \"y\": 36\n            },\n            \"id\": 40,\n            \"links\": [],\n            \"mode\": \"html\",\n            \"options\": {},\n            \"title\": \"\",\n            \"transparent\": true,\n            \"type\": \"text\"\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 1,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 0,\n              \"y\": 38\n            },\n            \"id\": 28,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", dst_replicaset=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_replicaset) / sum(irate(response_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", dst_replicaset=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_replicaset)\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"rs/{{dst_replicaset}}\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"SUCCESS RATE\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 2,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"decimals\": null,\n                \"format\": \"percentunit\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": \"1\",\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 0,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 8,\n              \"y\": 38\n            },\n            \"id\": 35,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", dst_replicaset=\\\"$outbound\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_replicaset)\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"🔒rs/{{dst_replicaset}}\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", dst_replicaset=\\\"$outbound\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_replicaset)\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"rs/{{dst_replicaset}}\",\n                \"refId\": \"B\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"REQUEST RATE\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 2,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"rps\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": \"0\",\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"fill\": 1,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 8,\n              \"x\": 16,\n              \"y\": 38\n            },\n            \"id\": 41,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": false,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 2,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {},\n            \"percentage\": false,\n            \"pointradius\": 5,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", dst_replicaset=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_replicaset))\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"P50 rs/{{dst_replicaset}}\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", dst_replicaset=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_replicaset))\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"P95 rs/{{dst_replicaset}}\",\n                \"refId\": \"B\"\n              },\n              {\n                \"expr\": \"histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\", dst_replicaset=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_replicaset))\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"P99 rs/{{dst_replicaset}}\",\n                \"refId\": \"C\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"LATENCY\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 2,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"ms\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          }\n        ],\n        \"repeat\": \"outbound\",\n        \"title\": \"rs/$outbound\",\n        \"type\": \"row\"\n      },\n      {\n        \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 3,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 44\n        },\n        \"height\": \"1px\",\n        \"id\": 171,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      }\n    ],\n    \"refresh\": \"1m\",\n    \"schemaVersion\": 18,\n    \"style\": \"dark\",\n    \"tags\": [\n      \"linkerd\"\n    ],\n    \"templating\": {\n      \"list\": [\n        {\n          \"current\": {\n            \"text\": \"default\",\n            \"value\": \"default\"\n          },\n          \"hide\": 0,\n          \"label\": \"Data Source\",\n          \"name\": \"datasource\",\n          \"options\": [],\n          \"query\": \"prometheus\",\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"type\": \"datasource\"\n        },\n        {\n          \"allValue\": \".*\",\n          \"current\": {\n            \"text\": \"default\",\n            \"value\": \"default\"\n          },\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"definition\": \"label_values(process_start_time_seconds{replicaset!=\\\"\\\"}, namespace)\",\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"label\": \"Namespace\",\n          \"multi\": false,\n          \"name\": \"namespace\",\n          \"options\": [],\n          \"query\": \"label_values(process_start_time_seconds{replicaset!=\\\"\\\"}, namespace)\",\n          \"refresh\": 2,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 1,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"allValue\": \".*\",\n          \"current\": {\n            \"text\": \"rs1\",\n            \"value\": \"rs1\"\n          },\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"definition\": \"label_values(process_start_time_seconds{namespace=\\\"$namespace\\\"}, replicaset)\",\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"label\": \"ReplicaSet\",\n          \"multi\": false,\n          \"name\": \"replicaset\",\n          \"options\": [],\n          \"query\": \"label_values(process_start_time_seconds{namespace=\\\"$namespace\\\"}, replicaset)\",\n          \"refresh\": 2,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 1,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"allValue\": \".*\",\n          \"current\": {\n            \"text\": \"All\",\n            \"value\": \"$__all\"\n          },\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"definition\": \"label_values(request_total{dst_namespace=\\\"$namespace\\\", dst_replicaset=\\\"$replicaset\\\"}, replicaset)\",\n          \"hide\": 2,\n          \"includeAll\": true,\n          \"label\": null,\n          \"multi\": false,\n          \"name\": \"inbound\",\n          \"options\": [],\n          \"query\": \"label_values(request_total{dst_namespace=\\\"$namespace\\\", dst_replicaset=\\\"$replicaset\\\"}, replicaset)\",\n          \"refresh\": 2,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 1,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"allValue\": \".*\",\n          \"current\": {\n            \"text\": \"All\",\n            \"value\": \"$__all\"\n          },\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"definition\": \"label_values(request_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\"}, dst_replicaset)\",\n          \"hide\": 2,\n          \"includeAll\": true,\n          \"label\": null,\n          \"multi\": false,\n          \"name\": \"outbound\",\n          \"options\": [],\n          \"query\": \"label_values(request_total{namespace=\\\"$namespace\\\", replicaset=\\\"$replicaset\\\"}, dst_replicaset)\",\n          \"refresh\": 2,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 1,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        }\n      ]\n    },\n    \"time\": {\n      \"from\": \"now-5m\",\n      \"to\": \"now\"\n    },\n    \"timepicker\": {\n      \"refresh_intervals\": [\n        \"5s\",\n        \"10s\",\n        \"30s\",\n        \"1m\",\n        \"5m\",\n        \"15m\",\n        \"30m\",\n        \"1h\",\n        \"2h\",\n        \"1d\"\n      ],\n      \"time_options\": [\n        \"5m\",\n        \"15m\",\n        \"1h\",\n        \"6h\",\n        \"12h\",\n        \"24h\",\n        \"2d\",\n        \"7d\",\n        \"30d\"\n      ]\n    },\n    \"timezone\": \"\",\n    \"title\": \"Linkerd ReplicaSet\",\n    \"uid\": \"linkerd-replicaset\",\n    \"version\": 1\n  }\n"
  },
  {
    "path": "grafana/dashboards/replicationcontroller.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\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  \"gnetId\": null,\n  \"graphTooltip\": 1,\n  \"id\": null,\n  \"iteration\": 1531764114061,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"height:32px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 32px\\\">rc/$replicationcontroller</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 20,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#d44a3a\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#299c46\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"percentunit\",\n      \"gauge\": {\n        \"maxValue\": 1,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 2\n      },\n      \"id\": 5,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"inbound\\\"}[$__rate_interval])) / sum(irate(response_total{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"0.9,.99\",\n      \"title\": \"SUCCESS RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 2\n      },\n      \"id\": 4,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \" RPS\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"REQUEST RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 4,\n        \"x\": 16,\n        \"y\": 2\n      },\n      \"id\": 11,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"100%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(count(request_total{dst_namespace=\\\"$namespace\\\", replicationcontroller!=\\\"\\\", dst_replicationcontroller!=\\\"\\\", dst_replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\"}) by (namespace, replicationcontroller))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"INBOUND REPLICATIONCONTROLLERS\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 4,\n        \"x\": 20,\n        \"y\": 2\n      },\n      \"id\": 15,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(count(request_total{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\"}) by (namespace, dst_replicationcontroller))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"OUTBOUND REPLICATIONCONTROLLERS\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND TRAFFIC</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 6\n      },\n      \"id\": 17,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 67,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (replicationcontroller) / sum(irate(response_total{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (replicationcontroller)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"rc/{{replicationcontroller}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 8\n      },\n      \"id\": 2,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"inbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (replicationcontroller)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒rc/{{replicationcontroller}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"inbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (replicationcontroller)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"rc/{{replicationcontroller}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 8\n      },\n      \"id\": 68,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, replicationcontroller))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p50 rc/{{replicationcontroller}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, replicationcontroller))\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p95 rc/{{replicationcontroller}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, replicationcontroller))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p99 rc/{{replicationcontroller}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"ms\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 15\n      },\n      \"id\": 148,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 16\n          },\n          \"id\": 167,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"inbound\\\",errno!=\\\"\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}} {{errno}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTION FAILURES\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"none\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 16\n          },\n          \"id\": 168,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"inbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTIONS OPEN\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"short\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"cards\": {\n            \"cardPadding\": null,\n            \"cardRound\": null\n          },\n          \"color\": {\n            \"cardColor\": \"#b4ff00\",\n            \"colorScale\": \"sqrt\",\n            \"colorScheme\": \"interpolateOranges\",\n            \"exponent\": 0.5,\n            \"mode\": \"spectrum\"\n          },\n          \"dataFormat\": \"timeseries\",\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 16\n          },\n          \"heatmap\": {},\n          \"hideZeroBuckets\": false,\n          \"highlightCards\": true,\n          \"id\": 169,\n          \"legend\": {\n            \"show\": false\n          },\n          \"links\": [],\n          \"options\": {},\n          \"reverseYBuckets\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"inbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"refId\": \"A\"\n            }\n          ],\n          \"title\": \"TCP CONNECTION DURATION\",\n          \"tooltip\": {\n            \"show\": true,\n            \"showHistogram\": true\n          },\n          \"type\": \"heatmap\",\n          \"xAxis\": {\n            \"show\": true\n          },\n          \"xBucketNumber\": null,\n          \"xBucketSize\": null,\n          \"yAxis\": {\n            \"decimals\": null,\n            \"format\": \"dtdurationms\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true,\n            \"splitFactor\": null\n          },\n          \"yBucketBound\": \"auto\",\n          \"yBucketNumber\": null,\n          \"yBucketSize\": null\n        }\n      ],\n      \"title\": \"Inbound TCP Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"id\": 152,\n      \"panels\": [],\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND REPLICATIONCONTROLLERS</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 17\n      },\n      \"id\": 76,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 19\n      },\n      \"id\": 59,\n      \"panels\": [\n        {\n          \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">rc/$inbound</span>\\n</div>\",\n          \"gridPos\": {\n            \"h\": 2,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 22.2\n          },\n          \"id\": 39,\n          \"links\": [],\n          \"mode\": \"html\",\n          \"options\": {},\n          \"title\": \"\",\n          \"transparent\": true,\n          \"type\": \"text\"\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 24.2\n          },\n          \"id\": 36,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", replicationcontroller!=\\\"\\\", replicationcontroller=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (replicationcontroller, pod) / sum(irate(response_total{replicationcontroller!=\\\"\\\", replicationcontroller=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (replicationcontroller, pod)\",\n              \"format\": \"time_series\",\n              \"instant\": false,\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"po/{{pod}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"SUCCESS RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"percentunit\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": \"1\",\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 24.2\n          },\n          \"id\": 22,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(request_total{replicationcontroller!=\\\"\\\", replicationcontroller=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (replicationcontroller, pod)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"🔒po/{{pod}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"sum(irate(request_total{replicationcontroller!=\\\"\\\", replicationcontroller=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (replicationcontroller, pod)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"po/{{pod}}\",\n              \"refId\": \"B\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"REQUEST RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"rps\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 24.2\n          },\n          \"id\": 29,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{replicationcontroller!=\\\"\\\", replicationcontroller=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, replicationcontroller))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P50 rc/{{replicationcontroller}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{replicationcontroller!=\\\"\\\", replicationcontroller=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, replicationcontroller))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P95 rc/{{replicationcontroller}}\",\n              \"refId\": \"B\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{replicationcontroller!=\\\"\\\", replicationcontroller=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, replicationcontroller))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P99 rc/{{replicationcontroller}}\",\n              \"refId\": \"C\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"LATENCY\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"ms\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        }\n      ],\n      \"repeat\": \"inbound\",\n      \"title\": \"rc/$inbound\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 20\n      },\n      \"id\": 34,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND TRAFFIC</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"id\": 32,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 23\n      },\n      \"id\": 77,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_replicationcontroller) / sum(irate(response_total{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_replicationcontroller)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"rc/{{dst_replicationcontroller}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 23\n      },\n      \"id\": 78,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_replicationcontroller)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒rc/{{dst_replicationcontroller}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_replicationcontroller)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"rc/{{dst_replicationcontroller}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 23\n      },\n      \"id\": 79,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_replicationcontroller))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P95 rc/{{dst_replicationcontroller}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"P95 LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 30\n      },\n      \"id\": 154,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 29\n          },\n          \"id\": 157,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\",errno!=\\\"\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}} {{errno}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTION FAILURES\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"none\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 29\n          },\n          \"id\": 166,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTIONS OPEN\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"short\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"cards\": {\n            \"cardPadding\": null,\n            \"cardRound\": null\n          },\n          \"color\": {\n            \"cardColor\": \"#b4ff00\",\n            \"colorScale\": \"sqrt\",\n            \"colorScheme\": \"interpolateOranges\",\n            \"exponent\": 0.5,\n            \"mode\": \"spectrum\"\n          },\n          \"dataFormat\": \"timeseries\",\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 29\n          },\n          \"heatmap\": {},\n          \"hideZeroBuckets\": false,\n          \"highlightCards\": true,\n          \"id\": 160,\n          \"legend\": {\n            \"show\": false\n          },\n          \"links\": [],\n          \"options\": {},\n          \"reverseYBuckets\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", direction=\\\"outbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"refId\": \"A\"\n            }\n          ],\n          \"title\": \"TCP CONNECTION DURATION\",\n          \"tooltip\": {\n            \"show\": true,\n            \"showHistogram\": true\n          },\n          \"type\": \"heatmap\",\n          \"xAxis\": {\n            \"show\": true\n          },\n          \"xBucketNumber\": null,\n          \"xBucketSize\": null,\n          \"yAxis\": {\n            \"decimals\": null,\n            \"format\": \"dtdurationms\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true,\n            \"splitFactor\": null\n          },\n          \"yBucketBound\": \"auto\",\n          \"yBucketNumber\": null,\n          \"yBucketSize\": null\n        }\n      ],\n      \"title\": \"Outbound TCP Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 31\n      },\n      \"id\": 156,\n      \"panels\": [],\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND REPLICATIONCONTROLLERS</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 32\n      },\n      \"id\": 80,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 34\n      },\n      \"id\": 27,\n      \"panels\": [\n        {\n          \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">rc/$outbound</span>\\n</div>\",\n          \"gridPos\": {\n            \"h\": 2,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 36\n          },\n          \"id\": 40,\n          \"links\": [],\n          \"mode\": \"html\",\n          \"options\": {},\n          \"title\": \"\",\n          \"transparent\": true,\n          \"type\": \"text\"\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 38\n          },\n          \"id\": 28,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", dst_replicationcontroller=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_replicationcontroller) / sum(irate(response_total{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", dst_replicationcontroller=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_replicationcontroller)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"rc/{{dst_replicationcontroller}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"SUCCESS RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"percentunit\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": \"1\",\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 38\n          },\n          \"id\": 35,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", dst_replicationcontroller=\\\"$outbound\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_replicationcontroller)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"🔒rc/{{dst_replicationcontroller}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", dst_replicationcontroller=\\\"$outbound\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_replicationcontroller)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"rc/{{dst_replicationcontroller}}\",\n              \"refId\": \"B\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"REQUEST RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"rps\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 38\n          },\n          \"id\": 41,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", dst_replicationcontroller=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_replicationcontroller))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P50 rc/{{dst_replicationcontroller}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", dst_replicationcontroller=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_replicationcontroller))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P95 rc/{{dst_replicationcontroller}}\",\n              \"refId\": \"B\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\", dst_replicationcontroller=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_replicationcontroller))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P99 rc/{{dst_replicationcontroller}}\",\n              \"refId\": \"C\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"LATENCY\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"ms\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        }\n      ],\n      \"repeat\": \"outbound\",\n      \"title\": \"rc/$outbound\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 35\n      },\n      \"height\": \"1px\",\n      \"id\": 171,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    }\n  ],\n  \"refresh\": \"1m\",\n  \"schemaVersion\": 18,\n  \"style\": \"dark\",\n  \"tags\": [\n    \"linkerd\"\n  ],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"text\": \"default\",\n          \"value\": \"default\"\n        },\n        \"hide\": 0,\n        \"label\": \"Data Source\",\n        \"name\": \"datasource\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Namespace\",\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{replicationcontroller!=\\\"\\\"}, namespace)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"ReplicationController\",\n        \"multi\": false,\n        \"name\": \"replicationcontroller\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{namespace=\\\"$namespace\\\"}, replicationcontroller)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"inbound\",\n        \"options\": [],\n        \"query\": \"label_values(request_total{dst_namespace=\\\"$namespace\\\", dst_replicationcontroller=\\\"$replicationcontroller\\\"}, replicationcontroller)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"outbound\",\n        \"options\": [],\n        \"query\": \"label_values(request_total{namespace=\\\"$namespace\\\", replicationcontroller=\\\"$replicationcontroller\\\"}, dst_replicationcontroller)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-5m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"\",\n  \"title\": \"Linkerd ReplicationController\",\n  \"uid\": \"linkerd-replicationcontroller\",\n  \"version\": 1\n}\n"
  },
  {
    "path": "grafana/dashboards/route.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n    \"annotations\": {\n      \"list\": [\n        {\n          \"builtIn\": 1,\n          \"datasource\": \"-- Grafana --\",\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    \"gnetId\": null,\n    \"graphTooltip\": 1,\n    \"id\": null,\n    \"iteration\": 1539806914987,\n    \"links\": [],\n    \"panels\": [\n      {\n        \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"height:32px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 32px\\\">route/$rt_route</span>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 0\n        },\n        \"id\": 2,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"colorBackground\": false,\n        \"colorValue\": false,\n        \"colors\": [\n          \"#d44a3a\",\n          \"rgba(237, 129, 40, 0.89)\",\n          \"#299c46\"\n        ],\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"decimals\": null,\n        \"format\": \"percentunit\",\n        \"gauge\": {\n          \"maxValue\": 1,\n          \"minValue\": 0,\n          \"show\": true,\n          \"thresholdLabels\": false,\n          \"thresholdMarkers\": true\n        },\n        \"gridPos\": {\n          \"h\": 4,\n          \"w\": 8,\n          \"x\": 0,\n          \"y\": 2\n        },\n        \"id\": 4,\n        \"interval\": null,\n        \"links\": [],\n        \"mappingType\": 1,\n        \"mappingTypes\": [\n          {\n            \"name\": \"value to text\",\n            \"value\": 1\n          },\n          {\n            \"name\": \"range to text\",\n            \"value\": 2\n          }\n        ],\n        \"maxDataPoints\": 100,\n        \"nullPointMode\": \"connected\",\n        \"nullText\": null,\n        \"options\": {},\n        \"postfix\": \"\",\n        \"postfixFontSize\": \"50%\",\n        \"prefix\": \"\",\n        \"prefixFontSize\": \"50%\",\n        \"rangeMaps\": [\n          {\n            \"from\": \"null\",\n            \"text\": \"N/A\",\n            \"to\": \"null\"\n          }\n        ],\n        \"sparkline\": {\n          \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n          \"full\": true,\n          \"lineColor\": \"rgb(31, 120, 193)\",\n          \"show\": true\n        },\n        \"tableColumn\": \"\",\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(route_response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", direction=\\\"inbound\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) / sum(irate(route_response_total{namespace=\\\"$namespace\\\", direction=\\\"inbound\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval]))\",\n            \"format\": \"time_series\",\n            \"instant\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": \"0.9,.99\",\n        \"title\": \"SUCCESS RATE\",\n        \"transparent\": true,\n        \"type\": \"singlestat\",\n        \"valueFontSize\": \"80%\",\n        \"valueMaps\": [\n          {\n            \"op\": \"=\",\n            \"text\": \"N/A\",\n            \"value\": \"null\"\n          }\n        ],\n        \"valueName\": \"current\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"colorBackground\": false,\n        \"colorValue\": false,\n        \"colors\": [\n          \"#299c46\",\n          \"rgba(237, 129, 40, 0.89)\",\n          \"#d44a3a\"\n        ],\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"decimals\": null,\n        \"format\": \"none\",\n        \"gauge\": {\n          \"maxValue\": 100,\n          \"minValue\": 0,\n          \"show\": false,\n          \"thresholdLabels\": false,\n          \"thresholdMarkers\": true\n        },\n        \"gridPos\": {\n          \"h\": 4,\n          \"w\": 8,\n          \"x\": 8,\n          \"y\": 2\n        },\n        \"id\": 6,\n        \"interval\": null,\n        \"links\": [],\n        \"mappingType\": 1,\n        \"mappingTypes\": [\n          {\n            \"name\": \"value to text\",\n            \"value\": 1\n          },\n          {\n            \"name\": \"range to text\",\n            \"value\": 2\n          }\n        ],\n        \"maxDataPoints\": 100,\n        \"nullPointMode\": \"connected\",\n        \"nullText\": null,\n        \"options\": {},\n        \"postfix\": \" RPS\",\n        \"postfixFontSize\": \"100%\",\n        \"prefix\": \"\",\n        \"prefixFontSize\": \"50%\",\n        \"rangeMaps\": [\n          {\n            \"from\": \"null\",\n            \"text\": \"N/A\",\n            \"to\": \"null\"\n          }\n        ],\n        \"sparkline\": {\n          \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n          \"full\": true,\n          \"lineColor\": \"rgb(31, 120, 193)\",\n          \"show\": true\n        },\n        \"tableColumn\": \"\",\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(route_request_total{namespace=\\\"$namespace\\\", direction=\\\"inbound\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval]))\",\n            \"format\": \"time_series\",\n            \"instant\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": \"\",\n        \"title\": \"REQUEST RATE\",\n        \"transparent\": true,\n        \"type\": \"singlestat\",\n        \"valueFontSize\": \"100%\",\n        \"valueMaps\": [\n          {\n            \"op\": \"=\",\n            \"text\": \"N/A\",\n            \"value\": \"null\"\n          }\n        ],\n        \"valueName\": \"current\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"colorBackground\": false,\n        \"colorValue\": false,\n        \"colors\": [\n          \"#299c46\",\n          \"rgba(237, 129, 40, 0.89)\",\n          \"#d44a3a\"\n        ],\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"decimals\": null,\n        \"format\": \"none\",\n        \"gauge\": {\n          \"maxValue\": 100,\n          \"minValue\": 0,\n          \"show\": false,\n          \"thresholdLabels\": false,\n          \"thresholdMarkers\": true\n        },\n        \"gridPos\": {\n          \"h\": 4,\n          \"w\": 8,\n          \"x\": 16,\n          \"y\": 2\n        },\n        \"id\": 8,\n        \"interval\": null,\n        \"links\": [],\n        \"mappingType\": 1,\n        \"mappingTypes\": [\n          {\n            \"name\": \"value to text\",\n            \"value\": 1\n          },\n          {\n            \"name\": \"range to text\",\n            \"value\": 2\n          }\n        ],\n        \"maxDataPoints\": 100,\n        \"nullPointMode\": \"connected\",\n        \"nullText\": null,\n        \"options\": {},\n        \"postfix\": \" ms\",\n        \"postfixFontSize\": \"100%\",\n        \"prefix\": \"\",\n        \"prefixFontSize\": \"50%\",\n        \"rangeMaps\": [\n          {\n            \"from\": \"null\",\n            \"text\": \"N/A\",\n            \"to\": \"null\"\n          }\n        ],\n        \"sparkline\": {\n          \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n          \"full\": true,\n          \"lineColor\": \"rgb(31, 120, 193)\",\n          \"show\": true\n        },\n        \"tableColumn\": \"\",\n        \"targets\": [\n          {\n            \"expr\": \"histogram_quantile(0.95, sum(irate(route_response_latency_ms_bucket{namespace=\\\"$namespace\\\", direction=\\\"inbound\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (le, rt_route))\",\n            \"format\": \"time_series\",\n            \"instant\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": \"\",\n        \"title\": \"P95 LATENCY\",\n        \"transparent\": true,\n        \"type\": \"singlestat\",\n        \"valueFontSize\": \"100%\",\n        \"valueMaps\": [\n          {\n            \"op\": \"=\",\n            \"text\": \"N/A\",\n            \"value\": \"null\"\n          }\n        ],\n        \"valueName\": \"current\"\n      },\n      {\n        \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>TOP-LINE TRAFFIC</span>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 6\n        },\n        \"id\": 10,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 0,\n          \"y\": 8\n        },\n        \"id\": 12,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(route_response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", direction=\\\"inbound\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (rt_route) / sum(irate(route_response_total{namespace=\\\"$namespace\\\", direction=\\\"inbound\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (rt_route)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"route/{{rt_route}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"SUCCESS RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": null,\n            \"format\": \"percentunit\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": \"1\",\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 0,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 8,\n          \"y\": 8\n        },\n        \"id\": 14,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(route_request_total{namespace=\\\"$namespace\\\", direction=\\\"inbound\\\", rt_route=\\\"$rt_route\\\", tls=\\\"true\\\"}[$__rate_interval])) by (rt_route)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"🔒route/{{rt_route}}\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"sum(irate(route_request_total{namespace=\\\"$namespace\\\", direction=\\\"inbound\\\", rt_route=\\\"$rt_route\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (rt_route)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"route/{{rt_route}}\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"REQUEST RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": null,\n            \"format\": \"rps\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 16,\n          \"y\": 8\n        },\n        \"id\": 16,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"histogram_quantile(0.5, sum(irate(route_response_latency_ms_bucket{namespace=\\\"$namespace\\\", direction=\\\"inbound\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (le, rt_route))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"p50 route/{{rt_route}}\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"histogram_quantile(0.95, sum(irate(route_response_latency_ms_bucket{namespace=\\\"$namespace\\\", direction=\\\"inbound\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (le, rt_route))\",\n            \"format\": \"time_series\",\n            \"hide\": false,\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"p95 route/{{rt_route}}\",\n            \"refId\": \"B\"\n          },\n          {\n            \"expr\": \"histogram_quantile(0.99, sum(irate(route_response_latency_ms_bucket{namespace=\\\"$namespace\\\", direction=\\\"inbound\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (le, rt_route))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"p99 route/{{rt_route}}\",\n            \"refId\": \"C\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"LATENCY\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": null,\n            \"format\": \"ms\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND TRAFFIC BY DEPLOYMENT</span>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 15\n        },\n        \"id\": 18,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 0,\n          \"y\": 17\n        },\n        \"id\": 20,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(route_response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", direction=\\\"outbound\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (deployment, rt_route) / sum(irate(route_response_total{namespace=\\\"$namespace\\\", direction=\\\"outbound\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (deployment, rt_route)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"deploy/{{deployment}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"SUCCESS RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": null,\n            \"format\": \"percentunit\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": \"1\",\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 0,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 8,\n          \"y\": 17\n        },\n        \"id\": 22,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(route_request_total{namespace=\\\"$namespace\\\", direction=\\\"outbound\\\", tls=\\\"true\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (deployment, rt_route)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"🔒deploy/{{deployment}}\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"sum(irate(route_request_total{namespace=\\\"$namespace\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (deployment, rt_route)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"deploy/{{deployment}}\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"REQUEST RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"rps\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 16,\n          \"y\": 17\n        },\n        \"id\": 24,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"histogram_quantile(0.95, sum(rate(route_response_latency_ms_bucket{namespace=\\\"$namespace\\\", direction=\\\"outbound\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (le, deployment, rt_route))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"P95 deploy/{{deployment}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"P95 LATENCY\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"ms\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND TRAFFIC BY POD</span>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 2,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 24\n        },\n        \"id\": 26,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 0,\n          \"y\": 26\n        },\n        \"id\": 28,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(route_response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", direction=\\\"outbound\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (pod, rt_route) / sum(irate(route_response_total{namespace=\\\"$namespace\\\", direction=\\\"outbound\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (pod, rt_route)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"po/{{pod}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"SUCCESS RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": null,\n            \"format\": \"percentunit\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": \"1\",\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 0,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 8,\n          \"y\": 26\n        },\n        \"id\": 30,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(irate(route_request_total{namespace=\\\"$namespace\\\", direction=\\\"outbound\\\", tls=\\\"true\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (pod, rt_route)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"🔒po/{{pod}}\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"sum(irate(route_request_total{namespace=\\\"$namespace\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (pod, rt_route)\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"po/{{pod}}\",\n            \"refId\": \"B\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"REQUEST RATE\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"rps\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"fill\": 1,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 8,\n          \"x\": 16,\n          \"y\": 26\n        },\n        \"id\": 32,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {},\n        \"percentage\": false,\n        \"pointradius\": 5,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"histogram_quantile(0.95, sum(rate(route_response_latency_ms_bucket{namespace=\\\"$namespace\\\", direction=\\\"outbound\\\", rt_route=\\\"$rt_route\\\"}[$__rate_interval])) by (le, pod))\",\n            \"format\": \"time_series\",\n            \"intervalFactor\": 1,\n            \"legendFormat\": \"P95 po/{{pod, rt_route}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"P95 LATENCY\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 2,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"ms\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n        \"gridPos\": {\n          \"h\": 3,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 33\n        },\n        \"height\": \"1px\",\n        \"id\": 34,\n        \"links\": [],\n        \"mode\": \"html\",\n        \"options\": {},\n        \"title\": \"\",\n        \"transparent\": true,\n        \"type\": \"text\"\n      }\n    ],\n    \"refresh\": \"1m\",\n    \"schemaVersion\": 18,\n    \"style\": \"dark\",\n    \"tags\": [\n      \"linkerd\"\n    ],\n    \"templating\": {\n      \"list\": [\n        {\n          \"current\": {\n            \"text\": \"default\",\n            \"value\": \"default\"\n          },\n          \"hide\": 0,\n          \"label\": \"Data Source\",\n          \"name\": \"datasource\",\n          \"options\": [],\n          \"query\": \"prometheus\",\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"type\": \"datasource\"\n        },\n        {\n          \"allValue\": null,\n          \"current\": {},\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"definition\": \"\",\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"label\": \"Namespace\",\n          \"multi\": false,\n          \"name\": \"namespace\",\n          \"options\": [],\n          \"query\": \"label_values(route_request_total, namespace)\",\n          \"refresh\": 2,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 1,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n            \"allValue\": null,\n            \"current\": {},\n            \"datasource\": {\n              \"type\": \"prometheus\",\n              \"uid\": \"${datasource}\"\n            },\n            \"definition\": \"\",\n            \"hide\": 0,\n            \"includeAll\": false,\n            \"label\": \"Route\",\n            \"multi\": false,\n            \"name\": \"rt_route\",\n            \"options\": [],\n            \"query\": \"label_values(route_request_total{namespace=\\\"$namespace\\\"}, rt_route)\",\n            \"refresh\": 2,\n            \"regex\": \"\",\n            \"skipUrlSync\": false,\n            \"sort\": 1,\n            \"tagValuesQuery\": \"\",\n            \"tags\": [],\n            \"tagsQuery\": \"\",\n            \"type\": \"query\",\n            \"useTags\": false\n          }\n      ]\n    },\n    \"time\": {\n      \"from\": \"now-5m\",\n      \"to\": \"now\"\n    },\n    \"timepicker\": {\n      \"refresh_intervals\": [\n        \"5s\",\n        \"10s\",\n        \"30s\",\n        \"1m\",\n        \"5m\",\n        \"15m\",\n        \"30m\",\n        \"1h\",\n        \"2h\",\n        \"1d\"\n      ],\n      \"time_options\": [\n        \"5m\",\n        \"15m\",\n        \"1h\",\n        \"6h\",\n        \"12h\",\n        \"24h\",\n        \"2d\",\n        \"7d\",\n        \"30d\"\n      ]\n    },\n    \"timezone\": \"\",\n    \"title\": \"Linkerd Route\",\n    \"uid\": \"route\",\n    \"version\": 1\n  }\n  \n"
  },
  {
    "path": "grafana/dashboards/service.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\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  \"gnetId\": null,\n  \"graphTooltip\": 1,\n  \"id\": null,\n  \"iteration\": 1531434867463,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"height:32px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 32px\\\">svc/$service</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 20,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#d44a3a\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#299c46\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"percentunit\",\n      \"gauge\": {\n        \"maxValue\": 1,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 2\n      },\n      \"id\": 5,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\"}[$__rate_interval])) / sum(irate(response_total{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"0.9,.99\",\n      \"title\": \"SUCCESS RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 2\n      },\n      \"id\": 4,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \" RPS\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"REQUEST RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 2\n      },\n      \"id\": 81,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \" ms\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_service))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"P95 LATENCY\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>TOP-LINE TRAFFIC</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 6\n      },\n      \"id\": 17,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 67,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_service) / sum(irate(response_total{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_service)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"svc/{{dst_service}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 8\n      },\n      \"id\": 2,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_service)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒svc/{{dst_service}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_service)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"svc/{{dst_service}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 8\n      },\n      \"id\": 68,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_service))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p50 svc/{{dst_service}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_service))\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p95 svc/{{dst_service}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_service))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p99 svc/{{dst_service}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"ms\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND TRAFFIC BY DEPLOYMENT</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 15\n      },\n      \"id\": 32,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 17\n      },\n      \"id\": 77,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_service, deployment) / sum(irate(response_total{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_service, deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 17\n      },\n      \"id\": 78,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_service, deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_service, deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"deploy/{{deployment}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 17\n      },\n      \"id\": 79,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_service, deployment))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P95 deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"P95 LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND TRAFFIC BY POD</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 24\n      },\n      \"id\": 82,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 26\n      },\n      \"id\": 83,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_service, pod) / sum(irate(response_total{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_service, pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"po/{{pod}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 26\n      },\n      \"id\": 84,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_service, pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒po/{{pod}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_service, pod)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"po/{{pod}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 26\n      },\n      \"id\": 85,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_service, pod))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P95 po/{{pod}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"P95 LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 33\n      },\n      \"height\": \"1px\",\n      \"id\": 171,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    }\n  ],\n  \"refresh\": \"1m\",\n  \"schemaVersion\": 18,\n  \"style\": \"dark\",\n  \"tags\": [\n    \"linkerd\"\n  ],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"text\": \"default\",\n          \"value\": \"default\"\n        },\n        \"hide\": 0,\n        \"label\": \"Data Source\",\n        \"name\": \"datasource\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Namespace\",\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": \"label_values(request_total, namespace)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Service\",\n        \"multi\": false,\n        \"name\": \"service\",\n        \"options\": [],\n        \"query\": \"label_values(request_total{namespace=\\\"$namespace\\\"}, dst_service)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {\n          \"text\": \"All\",\n          \"value\": \"$__all\"\n        },\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"inbound\",\n        \"options\": [],\n        \"query\": \"label_values(request_total{dst_namespace=\\\"$namespace\\\", dst_service=\\\"$service\\\"}, deployment)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {\n          \"text\": \"All\",\n          \"value\": \"$__all\"\n        },\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"outbound\",\n        \"options\": [],\n        \"query\": \"label_values(request_total{namespace=\\\"$namespace\\\", deployment=\\\"$deployment\\\"}, dst_deployment)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-5m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"\",\n  \"title\": \"Linkerd Service\",\n  \"uid\": \"linkerd-service\",\n  \"version\": 1\n}\n"
  },
  {
    "path": "grafana/dashboards/statefulset.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\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  \"gnetId\": null,\n  \"graphTooltip\": 1,\n  \"id\": null,\n  \"iteration\": 1531763681685,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"height:32px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 32px\\\">sts/$statefulset</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 20,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#d44a3a\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#299c46\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"percentunit\",\n      \"gauge\": {\n        \"maxValue\": 1,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 2\n      },\n      \"id\": 5,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) / sum(irate(response_total{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"0.9,.99\",\n      \"title\": \"SUCCESS RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 2\n      },\n      \"id\": 4,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \" RPS\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"inbound\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"REQUEST RATE\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"decimals\": null,\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 4,\n        \"x\": 16,\n        \"y\": 2\n      },\n      \"id\": 11,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"100%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(count(request_total{dst_namespace=\\\"$namespace\\\", statefulset!=\\\"\\\", dst_statefulset!=\\\"\\\", dst_statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\"}) by (namespace, statefulset))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"INBOUND STATEFULSETS\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#299c46\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#d44a3a\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 100,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 4,\n        \"x\": 20,\n        \"y\": 2\n      },\n      \"id\": 15,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"100%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": false,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(count(request_total{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\"}) by (namespace, dst_statefulset))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"OUTBOUND STATEFULSETS\",\n      \"transparent\": true,\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"100%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND TRAFFIC</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 6\n      },\n      \"id\": 17,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 8\n      },\n      \"id\": 67,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (statefulset) / sum(irate(response_total{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (statefulset)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"sts/{{statefulset}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 8\n      },\n      \"id\": 2,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"inbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (statefulset)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒sts/{{statefulset}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"inbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (statefulset)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"sts/{{statefulset}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 8\n      },\n      \"id\": 68,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, statefulset))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p50 sts/{{statefulset}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, statefulset))\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p95 sts/{{statefulset}}\",\n          \"refId\": \"B\"\n        },\n        {\n          \"expr\": \"histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, statefulset))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p99 sts/{{statefulset}}\",\n          \"refId\": \"C\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"ms\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 15\n      },\n      \"id\": 148,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 16\n          },\n          \"id\": 167,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"inbound\\\",errno!=\\\"\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}} {{errno}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTION FAILURES\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"none\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 16\n          },\n          \"id\": 168,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"inbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTIONS OPEN\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"short\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"cards\": {\n            \"cardPadding\": null,\n            \"cardRound\": null\n          },\n          \"color\": {\n            \"cardColor\": \"#b4ff00\",\n            \"colorScale\": \"sqrt\",\n            \"colorScheme\": \"interpolateOranges\",\n            \"exponent\": 0.5,\n            \"mode\": \"spectrum\"\n          },\n          \"dataFormat\": \"timeseries\",\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 16\n          },\n          \"heatmap\": {},\n          \"hideZeroBuckets\": false,\n          \"highlightCards\": true,\n          \"id\": 169,\n          \"legend\": {\n            \"show\": false\n          },\n          \"links\": [],\n          \"options\": {},\n          \"reverseYBuckets\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"inbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"refId\": \"A\"\n            }\n          ],\n          \"title\": \"TCP CONNECTION DURATION\",\n          \"tooltip\": {\n            \"show\": true,\n            \"showHistogram\": true\n          },\n          \"type\": \"heatmap\",\n          \"xAxis\": {\n            \"show\": true\n          },\n          \"xBucketNumber\": null,\n          \"xBucketSize\": null,\n          \"yAxis\": {\n            \"decimals\": null,\n            \"format\": \"dtdurationms\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true,\n            \"splitFactor\": null\n          },\n          \"yBucketBound\": \"auto\",\n          \"yBucketNumber\": null,\n          \"yBucketSize\": null\n        }\n      ],\n      \"title\": \"Inbound TCP Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"id\": 152,\n      \"panels\": [],\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>INBOUND STATEFULSETS</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 17\n      },\n      \"id\": 76,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 19\n      },\n      \"id\": 59,\n      \"panels\": [\n        {\n          \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">sts/$inbound</span>\\n</div>\",\n          \"gridPos\": {\n            \"h\": 2,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 22.2\n          },\n          \"id\": 39,\n          \"links\": [],\n          \"mode\": \"html\",\n          \"options\": {},\n          \"title\": \"\",\n          \"transparent\": true,\n          \"type\": \"text\"\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 24.2\n          },\n          \"id\": 36,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", statefulset!=\\\"\\\", statefulset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (statefulset, pod) / sum(irate(response_total{statefulset!=\\\"\\\", statefulset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (statefulset, pod)\",\n              \"format\": \"time_series\",\n              \"instant\": false,\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"po/{{pod}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"SUCCESS RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"percentunit\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": \"1\",\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 24.2\n          },\n          \"id\": 22,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(request_total{statefulset!=\\\"\\\", statefulset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (statefulset, pod)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"🔒po/{{pod}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"sum(irate(request_total{statefulset!=\\\"\\\", statefulset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (statefulset, pod)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"po/{{pod}}\",\n              \"refId\": \"B\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"REQUEST RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"rps\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 24.2\n          },\n          \"id\": 29,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{statefulset!=\\\"\\\", statefulset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, statefulset))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P50 sts/{{statefulset}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{statefulset!=\\\"\\\", statefulset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, statefulset))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P95 sts/{{statefulset}}\",\n              \"refId\": \"B\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{statefulset!=\\\"\\\", statefulset=\\\"$inbound\\\", dst_namespace=\\\"$namespace\\\", dst_statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, statefulset))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P99 sts/{{statefulset}}\",\n              \"refId\": \"C\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"LATENCY\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"ms\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        }\n      ],\n      \"repeat\": \"inbound\",\n      \"title\": \"sts/$inbound\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 20\n      },\n      \"id\": 34,\n      \"panels\": [],\n      \"repeat\": null,\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND TRAFFIC</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"id\": 32,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 23\n      },\n      \"id\": 77,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_statefulset) / sum(irate(response_total{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_statefulset)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"sts/{{dst_statefulset}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"decimals\": null,\n          \"format\": \"percentunit\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": \"1\",\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 23\n      },\n      \"id\": 78,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_statefulset)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒sts/{{dst_statefulset}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_statefulset)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"sts/{{dst_statefulset}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": \"\",\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": \"0\",\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 23\n      },\n      \"id\": 79,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_statefulset))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"P95 sts/{{dst_statefulset}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"P95 LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 30\n      },\n      \"id\": 154,\n      \"panels\": [\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 29\n          },\n          \"id\": 157,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_close_total{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\",errno!=\\\"\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}} {{errno}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTION FAILURES\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"none\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 29\n          },\n          \"id\": 166,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_open_connections{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"{{peer}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"TCP CONNECTIONS OPEN\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 0,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"short\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"cards\": {\n            \"cardPadding\": null,\n            \"cardRound\": null\n          },\n          \"color\": {\n            \"cardColor\": \"#b4ff00\",\n            \"colorScale\": \"sqrt\",\n            \"colorScheme\": \"interpolateOranges\",\n            \"exponent\": 0.5,\n            \"mode\": \"spectrum\"\n          },\n          \"dataFormat\": \"timeseries\",\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 29\n          },\n          \"heatmap\": {},\n          \"hideZeroBuckets\": false,\n          \"highlightCards\": true,\n          \"id\": 160,\n          \"legend\": {\n            \"show\": false\n          },\n          \"links\": [],\n          \"options\": {},\n          \"reverseYBuckets\": false,\n          \"targets\": [\n            {\n              \"expr\": \"tcp_connection_duration_ms_bucket{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", direction=\\\"outbound\\\"}\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"refId\": \"A\"\n            }\n          ],\n          \"title\": \"TCP CONNECTION DURATION\",\n          \"tooltip\": {\n            \"show\": true,\n            \"showHistogram\": true\n          },\n          \"type\": \"heatmap\",\n          \"xAxis\": {\n            \"show\": true\n          },\n          \"xBucketNumber\": null,\n          \"xBucketSize\": null,\n          \"yAxis\": {\n            \"decimals\": null,\n            \"format\": \"dtdurationms\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": \"0\",\n            \"show\": true,\n            \"splitFactor\": null\n          },\n          \"yBucketBound\": \"auto\",\n          \"yBucketNumber\": null,\n          \"yBucketSize\": null\n        }\n      ],\n      \"title\": \"Outbound TCP Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 31\n      },\n      \"id\": 156,\n      \"panels\": [],\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>OUTBOUND STATEFULSETS</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 32\n      },\n      \"id\": 80,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"collapsed\": true,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 34\n      },\n      \"id\": 27,\n      \"panels\": [\n        {\n          \"content\": \"<div style=\\\"display: flex; align-items: center\\\">\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <span style=\\\"font-size: 15px; border-image:none\\\">sts/$outbound</span>\\n</div>\",\n          \"gridPos\": {\n            \"h\": 2,\n            \"w\": 24,\n            \"x\": 0,\n            \"y\": 36\n          },\n          \"id\": 40,\n          \"links\": [],\n          \"mode\": \"html\",\n          \"options\": {},\n          \"title\": \"\",\n          \"transparent\": true,\n          \"type\": \"text\"\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 0,\n            \"y\": 38\n          },\n          \"id\": 28,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", dst_statefulset=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_statefulset) / sum(irate(response_total{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", dst_statefulset=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (dst_statefulset)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"sts/{{dst_statefulset}}\",\n              \"refId\": \"A\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"SUCCESS RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"decimals\": null,\n              \"format\": \"percentunit\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": \"1\",\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 0,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 8,\n            \"y\": 38\n          },\n          \"id\": 35,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", dst_statefulset=\\\"$outbound\\\", direction=\\\"outbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (dst_statefulset)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"🔒sts/{{dst_statefulset}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", dst_statefulset=\\\"$outbound\\\", direction=\\\"outbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (dst_statefulset)\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"sts/{{dst_statefulset}}\",\n              \"refId\": \"B\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"REQUEST RATE\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"rps\",\n              \"label\": \"\",\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": \"0\",\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        },\n        {\n          \"aliasColors\": {},\n          \"bars\": false,\n          \"dashLength\": 10,\n          \"dashes\": false,\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${datasource}\"\n          },\n          \"fill\": 1,\n          \"gridPos\": {\n            \"h\": 7,\n            \"w\": 8,\n            \"x\": 16,\n            \"y\": 38\n          },\n          \"id\": 41,\n          \"legend\": {\n            \"avg\": false,\n            \"current\": false,\n            \"max\": false,\n            \"min\": false,\n            \"show\": false,\n            \"total\": false,\n            \"values\": false\n          },\n          \"lines\": true,\n          \"linewidth\": 2,\n          \"links\": [],\n          \"nullPointMode\": \"null\",\n          \"options\": {},\n          \"percentage\": false,\n          \"pointradius\": 5,\n          \"points\": false,\n          \"renderer\": \"flot\",\n          \"seriesOverrides\": [],\n          \"spaceLength\": 10,\n          \"stack\": false,\n          \"steppedLine\": false,\n          \"targets\": [\n            {\n              \"expr\": \"histogram_quantile(0.5, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", dst_statefulset=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_statefulset))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P50 sts/{{dst_statefulset}}\",\n              \"refId\": \"A\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.95, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", dst_statefulset=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_statefulset))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P95 sts/{{dst_statefulset}}\",\n              \"refId\": \"B\"\n            },\n            {\n              \"expr\": \"histogram_quantile(0.99, sum(rate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\", dst_statefulset=\\\"$outbound\\\", direction=\\\"outbound\\\"}[$__rate_interval])) by (le, dst_statefulset))\",\n              \"format\": \"time_series\",\n              \"intervalFactor\": 1,\n              \"legendFormat\": \"P99 sts/{{dst_statefulset}}\",\n              \"refId\": \"C\"\n            }\n          ],\n          \"thresholds\": [],\n          \"timeFrom\": null,\n          \"timeRegions\": [],\n          \"timeShift\": null,\n          \"title\": \"LATENCY\",\n          \"tooltip\": {\n            \"shared\": true,\n            \"sort\": 2,\n            \"value_type\": \"individual\"\n          },\n          \"type\": \"graph\",\n          \"xaxis\": {\n            \"buckets\": null,\n            \"mode\": \"time\",\n            \"name\": null,\n            \"show\": true,\n            \"values\": []\n          },\n          \"yaxes\": [\n            {\n              \"format\": \"ms\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            },\n            {\n              \"format\": \"short\",\n              \"label\": null,\n              \"logBase\": 1,\n              \"max\": null,\n              \"min\": null,\n              \"show\": true\n            }\n          ],\n          \"yaxis\": {\n            \"align\": false,\n            \"alignLevel\": null\n          }\n        }\n      ],\n      \"repeat\": \"outbound\",\n      \"title\": \"sts/$outbound\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 35\n      },\n      \"height\": \"1px\",\n      \"id\": 171,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    }\n  ],\n  \"refresh\": \"1m\",\n  \"schemaVersion\": 18,\n  \"style\": \"dark\",\n  \"tags\": [\n    \"linkerd\"\n  ],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"text\": \"default\",\n          \"value\": \"default\"\n        },\n        \"hide\": 0,\n        \"label\": \"Data Source\",\n        \"name\": \"datasource\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"Namespace\",\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{statefulset!=\\\"\\\"}, namespace)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": \"StatefulSet\",\n        \"multi\": false,\n        \"name\": \"statefulset\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{namespace=\\\"$namespace\\\"}, statefulset)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"inbound\",\n        \"options\": [],\n        \"query\": \"label_values(request_total{dst_namespace=\\\"$namespace\\\", dst_statefulset=\\\"$statefulset\\\"}, statefulset)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {},\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"outbound\",\n        \"options\": [],\n        \"query\": \"label_values(request_total{namespace=\\\"$namespace\\\", statefulset=\\\"$statefulset\\\"}, dst_statefulset)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-5m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"\",\n  \"title\": \"Linkerd StatefulSet\",\n  \"uid\": \"linkerd-statefulset\",\n  \"version\": 1\n}\n"
  },
  {
    "path": "grafana/dashboards/top-line.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"datasource\",\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\": \"gauge\",\n      \"name\": \"Gauge\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"8.3.3\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"graph\",\n      \"name\": \"Graph (old)\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"heatmap\",\n      \"name\": \"Heatmap\",\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      \"type\": \"panel\",\n      \"id\": \"text\",\n      \"name\": \"Text\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\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  \"gnetId\": null,\n  \"graphTooltip\": 1,\n  \"id\": null,\n  \"iteration\": 1563896358132,\n  \"links\": [],\n  \"panels\": [\n    {\n      \"content\": \"<div>\\n  <div style=\\\"position: absolute; top: 0, left: 0\\\">\\n    <a href=\\\"https://linkerd.io\\\" target=\\\"_blank\\\"><img src=\\\"https://linkerd.io/images/identity/svg/linkerd_primary_color_white.svg\\\" style=\\\"height: 30px;\\\"></a>\\n  </div>\\n  <div id=\\\"version\\\" style=\\\"position: absolute; top: 0; right: 0; font-size: 15px\\\">\\n  </div>\\n</div>\\n<div style=\\\"display:none\\\">\\n<script type=\\\"text/javascript\\\">\\nvar localReqURL =\\n  window.location.href.substring(\\n    0,\\n    window.location.href.indexOf(\\n    \\\"/grafana/\\\"\\n    )\\n  )+'/overview';\\n\\nfetch(localReqURL, {\\n  credentials: 'include',\\n  headers: {\\n    \\\"Content-Type\\\": \\\"text/html; charset=utf-8\\\",\\n  },\\n})\\n.then(response => response.text())\\n.then(text => (new window.DOMParser()).parseFromString(text, \\\"text/html\\\"))\\n.then(html => {\\n  var main = html.getElementById('main');\\n  var localVersion = main.getAttribute(\\\"data-release-version\\\");\\n  var versionElem = document.getElementById('version');\\n\\n  var channel;\\n  var parts = localVersion.split(\\\"-\\\", 2);\\n  if (parts.length === 2) {\\n    channel = parts[0];\\n    versionElem.innerHTML += 'Running Linkerd ' + parts[1] + ' (' + parts[0] + ')' + '.<br>';\\n  } else {\\n    versionElem.innerHTML += 'Running Linkerd ' + localVersion + '.<br>';\\n  }\\n  var uuid = main.getAttribute(\\\"data-uuid\\\");\\n\\n  fetch('https://versioncheck.linkerd.io/version.json?version='+localVersion+'&uuid='+uuid+'&source=grafana', {\\n    credentials: 'include',\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json; charset=utf-8\\\",\\n    },\\n  })\\n  .then(response => response.json())\\n  .then(json => {\\n    if (!channel || !json[channel]) {\\n      versionElem.innerHTML += 'Version check failed.'\\n    } else if (json[channel] === localVersion) {\\n      versionElem.innerHTML += 'Linkerd is up to date.';\\n    } else {\\n      parts = json[channel].split(\\\"-\\\", 2);\\n      if (parts.length === 2) {\\n        versionElem.innerHTML += \\\"A new \\\"+parts[0]+\\\" version (\\\"+parts[1]+\\\") is available.\\\"\\n      } else {\\n        versionElem.innerHTML += \\\"A new version (\\\"+json[channel]+\\\") is available.\\\"\\n      }\\n      versionElem.innerHTML += \\\" <a href='https://versioncheck.linkerd.io/update' target='_blank'>Update now</a>.\\\";\\n    }\\n  });\\n});\\n</script>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 3,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"height\": \"1px\",\n      \"id\": 14,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": true,\n      \"colors\": [\n        \"#d44a3a\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#299c46\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"format\": \"percentunit\",\n      \"gauge\": {\n        \"maxValue\": 1,\n        \"minValue\": 0,\n        \"show\": true,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 3\n      },\n      \"height\": \"\",\n      \"id\": 28,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", deployment=~\\\"$deployment\\\"}[$__rate_interval])) / sum(irate(response_total{deployment=~\\\"$deployment\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \".9,.99\",\n      \"title\": \"GLOBAL SUCCESS RATE\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#d44a3a\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#299c46\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"format\": \"rps\",\n      \"gauge\": {\n        \"maxValue\": 1,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 3\n      },\n      \"height\": \"\",\n      \"id\": 29,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": true\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{deployment=~\\\"$deployment\\\"}[$__rate_interval]))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"GLOBAL REQUEST VOLUME\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"80%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#d44a3a\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#299c46\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 1,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 4,\n        \"x\": 16,\n        \"y\": 3\n      },\n      \"height\": \"\",\n      \"id\": 30,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(count(request_total{namespace=~\\\"$namespace\\\"}) by (namespace))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"NAMESPACES MONITORED\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"200%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"cacheTimeout\": null,\n      \"colorBackground\": false,\n      \"colorValue\": false,\n      \"colors\": [\n        \"#d44a3a\",\n        \"rgba(237, 129, 40, 0.89)\",\n        \"#299c46\"\n      ],\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"format\": \"none\",\n      \"gauge\": {\n        \"maxValue\": 1,\n        \"minValue\": 0,\n        \"show\": false,\n        \"thresholdLabels\": false,\n        \"thresholdMarkers\": true\n      },\n      \"gridPos\": {\n        \"h\": 4,\n        \"w\": 4,\n        \"x\": 20,\n        \"y\": 3\n      },\n      \"height\": \"\",\n      \"id\": 86,\n      \"interval\": null,\n      \"links\": [],\n      \"mappingType\": 1,\n      \"mappingTypes\": [\n        {\n          \"name\": \"value to text\",\n          \"value\": 1\n        },\n        {\n          \"name\": \"range to text\",\n          \"value\": 2\n        }\n      ],\n      \"maxDataPoints\": 100,\n      \"nullPointMode\": \"connected\",\n      \"nullText\": null,\n      \"options\": {},\n      \"postfix\": \"\",\n      \"postfixFontSize\": \"50%\",\n      \"prefix\": \"\",\n      \"prefixFontSize\": \"50%\",\n      \"rangeMaps\": [\n        {\n          \"from\": \"null\",\n          \"text\": \"N/A\",\n          \"to\": \"null\"\n        }\n      ],\n      \"sparkline\": {\n        \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n        \"full\": true,\n        \"lineColor\": \"rgb(31, 120, 193)\",\n        \"show\": false\n      },\n      \"tableColumn\": \"\",\n      \"targets\": [\n        {\n          \"expr\": \"count(count(request_total{namespace=~\\\"$namespace\\\"}) by (namespace, deployment))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": \"\",\n      \"title\": \"DEPLOYMENTS MONITORED\",\n      \"type\": \"singlestat\",\n      \"valueFontSize\": \"200%\",\n      \"valueMaps\": [\n        {\n          \"op\": \"=\",\n          \"text\": \"N/A\",\n          \"value\": \"null\"\n        }\n      ],\n      \"valueName\": \"current\"\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>TOP LINE</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 7\n      },\n      \"height\": \"1px\",\n      \"id\": 20,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"id\": 21,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=~\\\"$namespace\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (namespace) / sum(irate(response_total{namespace=~\\\"$namespace\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (namespace)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"ns/{{namespace}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"percentunit\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": 1,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 9\n      },\n      \"id\": 22,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=~\\\"$namespace\\\", direction=\\\"inbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (namespace)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒ns/{{namespace}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=~\\\"$namespace\\\", direction=\\\"inbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (namespace)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"ns/{{namespace}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST VOLUME\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 9\n      },\n      \"id\": 23,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=~\\\"$namespace\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, namespace))\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p95 ns/{{namespace}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"P95 LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"content\": \"<div class=\\\"text-center dashboard-header\\\">\\n  <span>NAMESPACES</span>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 16\n      },\n      \"height\": \"1px\",\n      \"id\": 19,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 18\n      },\n      \"id\": 40,\n      \"panels\": [],\n      \"repeat\": \"namespace\",\n      \"scopedVars\": {\n        \"namespace\": {\n          \"selected\": false\n        }\n      },\n      \"title\": \"\",\n      \"type\": \"row\"\n    },\n    {\n      \"content\": \"<div>\\n  <img src=\\\"https://linkerd.io/images/identity/favicon/linkerd-favicon.png\\\" style=\\\"baseline; height:30px;\\\"/>&nbsp;\\n  <a href=\\\"./dashboard/db/linkerd-deployment?var-namespace=$namespace\\\">\\n    <span style=\\\"font-size: 15px; border-image:none\\\">ns/$namespace</span>\\n  </a>\\n</div>\",\n      \"gridPos\": {\n        \"h\": 2,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 19\n      },\n      \"height\": \"1px\",\n      \"id\": 13,\n      \"links\": [],\n      \"mode\": \"html\",\n      \"options\": {},\n      \"scopedVars\": {\n        \"namespace\": {\n          \"selected\": false\n        }\n      },\n      \"title\": \"\",\n      \"transparent\": true,\n      \"type\": \"text\"\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 0,\n        \"y\": 21\n      },\n      \"id\": 6,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"scopedVars\": {\n        \"namespace\": {\n          \"selected\": false\n        }\n      },\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(response_total{classification=\\\"success\\\", namespace=\\\"$namespace\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (deployment) / sum(irate(response_total{namespace=\\\"$namespace\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"SUCCESS RATE\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"percentunit\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": 1,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 0,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 8,\n        \"y\": 21\n      },\n      \"id\": 8,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"scopedVars\": {\n        \"namespace\": {\n          \"selected\": false\n        }\n      },\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", direction=\\\"inbound\\\", tls=\\\"true\\\"}[$__rate_interval])) by (deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"🔒deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        },\n        {\n          \"expr\": \"sum(irate(request_total{namespace=\\\"$namespace\\\", direction=\\\"inbound\\\", tls!=\\\"true\\\"}[$__rate_interval])) by (deployment)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"deploy/{{deployment}}\",\n          \"refId\": \"B\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"REQUEST VOLUME\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"rps\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": 0,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${datasource}\"\n      },\n      \"fill\": 1,\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 8,\n        \"x\": 16,\n        \"y\": 21\n      },\n      \"id\": 9,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": false,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 2,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {},\n      \"percentage\": false,\n      \"pointradius\": 5,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"scopedVars\": {\n        \"namespace\": {\n          \"selected\": false\n        }\n      },\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{namespace=\\\"$namespace\\\", direction=\\\"inbound\\\"}[$__rate_interval])) by (le, deployment))\",\n          \"format\": \"time_series\",\n          \"hide\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"p95 deploy/{{deployment}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"P95 LATENCY\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 2,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"ms\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n  \"refresh\": \"1m\",\n  \"schemaVersion\": 18,\n  \"style\": \"dark\",\n  \"tags\": [\n    \"linkerd\"\n  ],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"text\": \"default\",\n          \"value\": \"default\"\n        },\n        \"hide\": 0,\n        \"label\": \"Data Source\",\n        \"name\": \"datasource\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {\n          \"text\": \"All\",\n          \"value\": \"$__all\"\n        },\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds, namespace)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      },\n      {\n        \"allValue\": \".*\",\n        \"current\": {\n          \"text\": \"All\",\n          \"value\": \"$__all\"\n        },\n        \"datasource\": {\n          \"type\": \"prometheus\",\n          \"uid\": \"${datasource}\"\n        },\n        \"definition\": \"\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"deployment\",\n        \"options\": [],\n        \"query\": \"label_values(process_start_time_seconds{deployment!=\\\"\\\"}, deployment)\",\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 1,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-5m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"\",\n  \"title\": \"Linkerd Top Line\",\n  \"uid\": \"linkerd-top-line\",\n  \"version\": 1\n}\n"
  },
  {
    "path": "grafana/values.yaml",
    "content": "podAnnotations:\n  linkerd.io/inject: enabled\n\ngrafana.ini:\n  server:\n    root_url: '%(protocol)s://%(domain)s:/grafana/'\n  auth:\n    disable_login_form: true\n  auth.anonymous:\n    enabled: true\n    org_role: Editor\n  auth.basic:\n    enabled: false\n  analytics:\n    check_for_updates: false\n  panels:\n    disable_sanitize_html: true\n  log:\n    mode: console\n  log.console:\n    format: text\n    level: info\n\ndatasources:\n  datasources.yaml:\n    apiVersion: 1\n    datasources:\n    - name: prometheus\n      type: prometheus\n      access: proxy\n      orgId: 1\n      url: http://prometheus.linkerd-viz.svc.cluster.local:9090\n      isDefault: true\n      jsonData:\n        timeInterval: \"5s\"\n      editable: true\n\ndashboardProviders:\n  dashboardproviders.yaml:\n    apiVersion: 1\n    providers:\n    - name: 'default'\n      orgId: 1\n      folder: ''\n      type: file\n      disableDeletion: false\n      editable: true\n      options:\n        path: /var/lib/grafana/dashboards/default\n\ndashboards:\n  default:\n    # all these charts are hosted at https://grafana.com/grafana/dashboards/{id}\n    top-line:\n      gnetId: 15474\n      revision: 4\n      datasource: prometheus\n    health:\n      gnetId: 15486\n      revision: 3\n      datasource: prometheus\n    kubernetes:\n      gnetId: 15479\n      revision: 2\n      datasource: prometheus\n    namespace:\n      gnetId: 15478\n      revision: 3\n      datasource: prometheus\n    deployment:\n      gnetId: 15475\n      revision: 6\n      datasource: prometheus\n    pod:\n      gnetId: 15477\n      revision: 3\n      datasource: prometheus\n    service:\n      gnetId: 15480\n      revision: 3\n      datasource: prometheus\n    route:\n      gnetId: 15481\n      revision: 3\n      datasource: prometheus\n    authority:\n      gnetId: 15482\n      revision: 3\n      datasource: prometheus\n    cronjob:\n      gnetId: 15483\n      revision: 3\n      datasource: prometheus\n    job:\n      gnetId: 15487\n      revision: 3\n      datasource: prometheus\n    daemonset:\n      gnetId: 15484\n      revision: 3\n      datasource: prometheus\n    replicaset:\n      gnetId: 15491\n      revision: 3\n      datasource: prometheus\n    statefulset:\n      gnetId: 15493\n      revision: 3\n      datasource: prometheus\n    replicationcontroller:\n      gnetId: 15492\n      revision: 4\n      datasource: prometheus\n    prometheus:\n      gnetId: 15489\n      revision: 2\n      datasource: prometheus\n    prometheus-benchmark:\n      gnetId: 15490\n      revision: 2\n      datasource: prometheus\n    multicluster:\n      gnetId: 15488\n      revision: 3\n      datasource: prometheus\n"
  },
  {
    "path": "justfile",
    "content": "# See https://just.systems/man/en\n\nlint: action-lint action-dev-check md-lint sh-lint rs-fetch rs-clippy rs-check-fmt go-lint\n\nexport GWAPI_VERSION := \"v1.2.0\"\n\n##\n## Go\n##\n\nexport GO111MODULE := \"on\"\n\ngo-fetch:\n    go mod download\n\ngo-fmt *flags:\n    bin/fmt {{ flags }}\n\ngo-lint *flags:\n    golangci-lint run {{ flags }}\n\ngo-test:\n    LINKERD_TEST_PRETTY_DIFF=1 gotestsum -- -race -v -mod=readonly --timeout 10m ./...\n\n##\n## Rust\n##\n\n# By default we compile in development mode mode because it's faster.\nrs-build-type := if env_var_or_default(\"RELEASE\", \"\") == \"\" { \"debug\" } else { \"release\" }\n\n# Overriddes the default Rust toolchain version.\nrs-toolchain := \"\"\n\nrs-features := 'all'\n\n_cargo := \"cargo\" + if rs-toolchain != \"\" { \" +\" + rs-toolchain } else { \"\" }\n\n# Fetch Rust dependencies.\nrs-fetch:\n    {{ _cargo }} fetch --locked\n\n# Format Rust code.\nrs-fmt:\n    {{ _cargo }} fmt --all\n\n# Check that the Rust code is formatted correctly.\nrs-check-fmt:\n    {{ _cargo }} fmt --all -- --check\n\n# Lint Rust code.\nrs-clippy:\n    {{ _cargo }} clippy --frozen --workspace --all-targets --no-deps {{ _features }} {{ _fmt }}\n\nalias clippy := rs-clippy\n\n# Audit Rust dependencies.\nrs-audit-deps:\n    {{ _cargo }} deny {{ _features }} check\n\n# Generate Rust documentation.\nrs-doc *flags:\n    {{ _cargo }} doc --frozen \\\n        {{ if rs-build-type == \"release\" { \"--release\" } else { \"\" } }} \\\n        {{ _features }} \\\n        {{ flags }}\n\nrs-test-build:\n    {{ _cargo-test }} --no-run --frozen \\\n        --workspace --exclude=linkerd-policy-test \\\n        {{ _features }}\n\n# Run Rust unit tests\nrs-test *flags:\n    {{ _cargo-test }} --frozen \\\n        --workspace --exclude=linkerd-policy-test \\\n        {{ if rs-build-type == \"release\" { \"--release\" } else { \"\" } }} \\\n        {{ _features }} \\\n        {{ flags }}\n\n# Check each crate independently to ensure its Cargo.toml is sufficient.\nrs-check-dirs:\n    #!/usr/bin/env bash\n    set -euo pipefail\n    while IFS= read -r toml ; do\n        {{ just_executable() }} \\\n            rs-build-type='{{ rs-build-type }}' \\\n            rs-features='{{ rs-features }}' \\\n            rs-toolchain='{{ rs-toolchain }}' \\\n            _rs-check-dir \"${toml%/*}\"\n        {{ just_executable() }} \\\n            rs-build-type='{{ rs-build-type }}' \\\n            rs-features='{{ rs-features }}' \\\n            rs-toolchain='{{ rs-toolchain }}' \\\n            _rs-check-dir \"${toml%/*}\" --tests\n    done < <(find . -mindepth 2 -name Cargo.toml | sort -r)\n\n_rs-check-dir dir *flags:\n    cd {{ dir }} \\\n        && {{ _cargo }} check --frozen \\\n                {{ if rs-build-type == \"release\" { \"--release\" } else { \"\" } }} \\\n                {{ _features }} \\\n                {{ flags }} \\\n                {{ _fmt }}\n\n# If we're running in github actions and cargo-action-fmt is installed, then add\n# a command suffix that formats errors.\n_fmt := if env_var_or_default(\"GITHUB_ACTIONS\", \"\") != \"true\" { \"\" } else {\n    ```\n    if command -v cargo-action-fmt >/dev/null 2>&1; then\n        echo \"--message-format=json | cargo-action-fmt\"\n    fi\n    ```\n}\n\n# Configures which features to enable when invoking cargo commands.\n_features := if rs-features == \"all\" {\n        \"--all-features\"\n    } else if rs-features != \"\" {\n        \"--no-default-features --features=\" + rs-features\n    } else { \"\" }\n\n# Use cargo-nextest if it is available. It may not be available when running\n# outside of the devcontainer.\n_cargo-test := _cargo + ```\n    if command -v cargo-nextest >/dev/null 2>&1 ; then\n        echo \" nextest run\"\n    else\n        echo \" test\"\n    fi\n    ```\n\n##\n## Policy integration tests\n##\n\nexport POLICY_TEST_CONTEXT := env_var_or_default(\"POLICY_TEST_CONTEXT\", \"k3d-\" + k3d-name)\n\n# Install linkerd in the test cluster and run the policy tests.\npolicy-test: linkerd-install policy-test-deps-load policy-test-run && policy-test-cleanup linkerd-uninstall\n\n_policy-test-flags := \"--no-default-features\" + if GATEWAY_API_CHANNEL == \"experimental\" { \" --features=gateway-api-experimental\" } else { \"\" }\n\n# Run the policy tests without installing linkerd.\npolicy-test-run *flags:\n    cd policy-test && {{ _cargo-test }} {{ _policy-test-flags }} {{ flags }}\n\n# Build the policy tests without running them.\npolicy-test-build:\n    cd policy-test && {{ _cargo-test }} --no-run {{ _policy-test-flags }}\n\n# Delete all test namespaces and remove linkerd from the cluster.\npolicy-test-cleanup:\n    {{ _kubectl }} delete ns --selector='linkerd-policy-test'\n    @while [ $({{ _kubectl }} get ns --selector='linkerd-policy-test' -o json |jq '.items | length') != \"0\" ]; do sleep 1 ; done\n\npolicy-test-deps-pull:\n    docker pull -q docker.io/chainguard/kubectl:latest-dev\n    docker pull -q docker.io/curlimages/curl:latest\n    docker pull -q ghcr.io/olix0r/hokay:latest\n\n# Load all images into the test cluster.\npolicy-test-deps-load: _k3d-init policy-test-deps-pull\n    for i in {1..3} ; do {{ _k3d-load }} \\\n        chainguard/kubectl:latest-dev \\\n        curlimages/curl:latest \\\n        fortio/fortio:latest \\\n        ghcr.io/olix0r/hokay:latest && exit || sleep 1 ; done\n\n##\n## Test cluster\n##\n\n# The name of the k3d cluster to use.\nk3d-name := \"l5d-test\"\n\n# The name of the docker network to use (i.e., for multicluster testing).\nk3d-network := \"k3d-name\"\n\n# The kubernetes version to use for the test cluster. e.g. 'v1.24', 'latest', etc\nk3d-k8s := \"latest\"\n\nk3d-agents := \"0\"\nk3d-servers := \"1\"\n\n_k3d-flags := \"--no-lb --k3s-arg --disable='local-storage,traefik,servicelb,metrics-server@server:*'\"\n\n_context := \"--context=k3d-\" + k3d-name\n_kubectl := \"kubectl \" + _context\n\n_k3d-load := \"k3d image import --mode=direct --cluster=\" + k3d-name\n\n# Run kubectl with the test cluster context.\nk *flags:\n    {{ _kubectl }} {{ flags }}\n\n# Creates a k3d cluster that can be used for testing.\nk3d-create: && _k3d-ready\n    k3d cluster create {{ k3d-name }} \\\n        --image='+{{ k3d-k8s }}' \\\n        --agents='{{ k3d-agents }}' \\\n        --servers='{{ k3d-servers }}' \\\n        --network='{{ k3d-network }}' \\\n        {{ _k3d-flags }} \\\n        --kubeconfig-update-default \\\n        --kubeconfig-switch-context=false\n\n# Deletes the test cluster.\nk3d-delete:\n    k3d cluster delete {{ k3d-name }}\n\n# Print information the test cluster's detailed status.\nk3d-info:\n    k3d cluster list {{ k3d-name }} -o json | jq .\n\n# Set the default kubectl context to the test cluster.\nk3d-use:\n    k3d kubeconfig merge {{ k3d-name }} \\\n        --kubeconfig-merge-default \\\n        --kubeconfig-switch-context=true \\\n        >/dev/null\n\n# Ensures the test cluster has been initialized.\n_k3d-init: && _k3d-ready\n    #!/usr/bin/env bash\n    set -euo pipefail\n    if ! k3d cluster list {{ k3d-name }} >/dev/null 2>/dev/null; then\n        {{ just_executable() }} \\\n            k3d-k8s='{{ k3d-k8s }}' \\\n            k3d-name='{{ k3d-name }}' \\\n            k3d-network='{{ k3d-network }}' \\\n            _k3d-flags='{{ _k3d-flags }}' \\\n            k3d-create\n    fi\n    k3d kubeconfig merge {{ k3d-name }} \\\n        --kubeconfig-merge-default \\\n        --kubeconfig-switch-context=false \\\n        >/dev/null\n\n_k3d-ready: _k3d-api-ready _k3d-dns-ready\n\n# Wait for the cluster's API server to be accessible\n_k3d-api-ready:\n    #!/usr/bin/env bash\n    set -euo pipefail\n    for i in {1..6} ; do\n        if {{ _kubectl }} cluster-info >/dev/null ; then exit 0 ; fi\n        sleep 10\n    done\n    exit 1\n\n# Wait for the cluster's DNS pods to be ready.\n_k3d-dns-ready:\n    while [ $({{ _kubectl }} get po -n kube-system -l k8s-app=kube-dns -o json |jq '.items | length') = \"0\" ]; do sleep 1 ; done\n    {{ _kubectl }} wait pod --for=condition=ready \\\n        --namespace=kube-system --selector=k8s-app=kube-dns \\\n        --timeout=1m\n\n##\n## Docker images\n##\n\n# If DOCKER_REGISTRY is not already set, use a bogus registry with a unique\n# name so that it's virtually impossible to accidentally use an incorrect image.\nexport DOCKER_REGISTRY := env_var_or_default(\"DOCKER_REGISTRY\", \"test.l5d.io/\" + _test-id )\n_test-id := `tr -dc 'a-z0-9' </dev/urandom | fold -w 5 | head -n 1`\n\n# The docker image tag.\nlinkerd-tag := `bin/root-tag`\n\ndocker-arch := ''\n\n_pause-load: _k3d-init\n    #!/usr/bin/env bash\n    set -euo pipefail\n    img=\"$(yq .gateway.pauseImage multicluster/charts/linkerd-multicluster/values.yaml)\"\n    if [ -z \"$(docker image ls -q \"$img\")\" ]; then\n       docker pull -q \"$img\"\n    fi\n    for i in {1..3} ; do {{ _k3d-load }} \"$img\" && exit || sleep 1 ; done\n\n##\n## Linkerd CLI\n##\n\n# The Linkerd CLI binary.\nlinkerd-exec := \"bin/linkerd\"\n_linkerd := linkerd-exec + \" \" + _context\n\ncontroller-image := DOCKER_REGISTRY + \"/controller\"\nproxy-image := DOCKER_REGISTRY + \"/proxy\"\n\n# When GATEWAY_API_VERSION is 'linkerd' we use the CLI's vendored gateway API\n# CRDs. Otherwise, we install the CRDs from the upstream release.\n# This should be kept up-to-date with the latest stable release of the gateway API.\nexport GATEWAY_API_VERSION := env_var_or_default(\"GATEWAY_API_VERSION\", \"v1.2.1\")\n\n# When GATEWAY_API_CHANNEL is 'experimental', we enable testing of experimental\n# resource types (TCPRoute, TLSRoute). Alternatively, the 'standard' channel may\n# be used.\nexport GATEWAY_API_CHANNEL := env_var_or_default(\"GATEWAY_API_CHANNEL\", \"experimental\")\n\n_gateway-url := if GATEWAY_API_VERSION != \"linkerd\" { \"https://github.com/kubernetes-sigs/gateway-api/releases/download/\" + GATEWAY_API_VERSION + \"/\" + GATEWAY_API_CHANNEL + \"-install.yaml\" } else { \"\" }\n\n# External dependencies\n#\n# We execute these commands lazily in case `yq` isn't present (so that other\n# just recipes can succeed).\n_cni-plugin-image-cmd := \"yq '.image | \\\"ghcr.io/linkerd/cni-plugin:\\\" + .version' charts/linkerd2-cni/values.yaml\"\n_prometheus-image-cmd := \"yq '.prometheus.image | .registry + \\\"/\\\" + .name + \\\":\\\" + .tag'  viz/charts/linkerd-viz/values.yaml\"\n\nlinkerd *flags:\n    {{ _linkerd }} {{ flags }}\n\n# Install crds on the test cluster.\nlinkerd-crds-install: _k3d-init\n    if [ -n \"{{ _gateway-url }}\" ]; then {{ _kubectl }} apply --server-side -f \"{{ _gateway-url }}\" ; fi\n    {{ _linkerd }} install --crds --set installGatewayAPI={{ if _gateway-url == \"\" { \"true \"} else { \"false \"} }} \\\n        | {{ _kubectl }} apply --server-side -f -\n    {{ _kubectl }} wait crd --for condition=established \\\n        --selector='linkerd.io/control-plane-ns' \\\n        --timeout=1m\n    {{ _kubectl }} get crds -o json | jq -r '.items[] | select(.spec.group==\"gateway.networking.k8s.io\") | .metadata.name' \\\n        | xargs {{ _kubectl }} wait crd --for condition=established  --timeout=1m\n\n# Install linkerd on the test cluster using test images.\nlinkerd-install *args='': linkerd-load linkerd-crds-install && _linkerd-ready\n    {{ _linkerd }} install \\\n            --set='imagePullPolicy=Never' \\\n            --set='controllerImage={{ controller-image }}' \\\n            --set='linkerdVersion={{ linkerd-tag }}' \\\n            --set='proxy.image.name={{ proxy-image }}' \\\n            --set='proxy.image.version={{ linkerd-tag }}' \\\n            {{ args }} \\\n        | {{ _kubectl }} apply -f -\n\n# Wait for all test namespaces to be removed before uninstalling linkerd from the cluster.\nlinkerd-uninstall:\n    {{ _linkerd }} uninstall \\\n        | {{ _kubectl }} delete -f -\n\nlinkerd-load: _linkerd-images _k3d-init\n    for i in {1..3} ; do {{ _k3d-load }} \\\n        '{{ controller-image }}:{{ linkerd-tag }}' \\\n        '{{ proxy-image }}:{{ linkerd-tag }}' \\\n        && exit || sleep 1 ; done\n\nlinkerd-load-cni:\n    docker pull -q $({{ _cni-plugin-image-cmd }})\n    {{ _k3d-load }} $({{ _cni-plugin-image-cmd }})\n\nlinkerd-build: _controller-build\n    TAG={{ linkerd-tag }} bin/docker-build-proxy\n\n_linkerd-images:\n    #!/usr/bin/env bash\n    set -xeuo pipefail\n    for img in \\\n        '{{ controller-image }}:{{ linkerd-tag }}' \\\n        '{{ proxy-image }}:{{ linkerd-tag }}'\n    do\n        if [ -z $(docker image ls -q \"$img\") ]; then\n            # Build images if any one of the images is missing.\n            exec {{ just_executable() }} \\\n                controller-image='{{ controller-image }}' \\\n                linkerd-tag='{{ linkerd-tag }}' \\\n                linkerd-build\n        fi\n    done\n\n# Build the policy controller docker image for testing (on amd64).\n_controller-build:\n    docker buildx build . \\\n        --file='Dockerfile.controller' \\\n        --platform={{ if docker-arch == '' { \"amd64\" } else { docker-arch} }} \\\n        --build-arg='build_type={{ rs-build-type }}' \\\n        --tag='{{ controller-image }}:{{ linkerd-tag }}' \\\n        --progress=plain \\\n        --load\n\n_linkerd-ready:\n    if ! {{ _kubectl }} wait pod --for=condition=ready \\\n            --namespace=linkerd --selector='linkerd.io/control-plane-component' \\\n            --timeout=1m ; then \\\n        {{ _kubectl }} describe pods --namespace=linkerd ; \\\n    fi\n\n# Ensure that a linkerd control plane is installed\n_linkerd-init: && _linkerd-ready\n    #!/usr/bin/env bash\n    set -euo pipefail\n    if ! {{ _kubectl }} get ns linkerd >/dev/null 2>&1 ; then\n        {{ just_executable() }} \\\n            k3d-name='{{ k3d-name }}' \\\n            k3d-k8s='{{ k3d-k8s }}' \\\n            k3d-agents='{{ k3d-agents }}' \\\n            k3d-servers='{{ k3d-servers }}' \\\n            k3d-network='{{ k3d-network }}' \\\n            _k3d-flags='{{ _k3d-flags }}' \\\n            linkerd-tag='{{ linkerd-tag }}' \\\n            controller-image='{{ controller-image }}' \\\n            proxy-image='{{ proxy-image }}' \\\n            linkerd-exec='{{ linkerd-exec }}' \\\n            linkerd-install\n    fi\n\n##\n## linkerd viz\n##\n\nlinkerd-viz *flags: _k3d-init\n    {{ _linkerd }} viz {{ flags }}\n\nlinkerd-viz-install: _linkerd-init linkerd-viz-load && _linkerd-viz-ready\n    {{ _linkerd }} viz install \\\n            --set='defaultRegistry={{ DOCKER_REGISTRY }}' \\\n            --set='linkerdVersion={{ linkerd-tag }}' \\\n            --set='defaultImagePullPolicy=Never' \\\n        | {{ _kubectl }} apply -f -\n\n# Wait for all test namespaces to be removed before uninstalling linkerd from the cluster.\nlinkerd-viz-uninstall:\n    {{ _linkerd }} viz uninstall \\\n        | {{ _kubectl }} delete -f -\n\n_linkerd-viz-images:\n    #!/usr/bin/env bash\n    set -euo pipefail\n    docker pull -q $({{ _prometheus-image-cmd }})\n    for img in \\\n        '{{ DOCKER_REGISTRY }}/metrics-api:{{ linkerd-tag }}' \\\n        '{{ DOCKER_REGISTRY }}/tap:{{ linkerd-tag }}' \\\n        '{{ DOCKER_REGISTRY }}/web:{{ linkerd-tag }}'\n    do\n        if [ -z $(docker image ls -q \"$img\") ]; then\n            echo \"Missing image: $img\" >&2\n            exec {{ just_executable() }} \\\n                linkerd-tag='{{ linkerd-tag }}' \\\n                linkerd-viz-build\n        fi\n    done\n\nlinkerd-viz-load: _linkerd-viz-images _k3d-init\n    for i in {1..3} ; do {{ _k3d-load }} \\\n        {{ DOCKER_REGISTRY }}/metrics-api:{{ linkerd-tag }} \\\n        {{ DOCKER_REGISTRY }}/tap:{{ linkerd-tag }} \\\n        {{ DOCKER_REGISTRY }}/web:{{ linkerd-tag }} \\\n        $({{ _prometheus-image-cmd }}) && exit || sleep 1 ; done\n\nlinkerd-viz-build:\n    TAG={{ linkerd-tag }} bin/docker-build-metrics-api\n    TAG={{ linkerd-tag }} bin/docker-build-tap\n    TAG={{ linkerd-tag }} bin/docker-build-web\n\n_linkerd-viz-ready:\n    {{ _kubectl }} wait pod --for=condition=ready \\\n        --namespace=linkerd-viz --selector='linkerd.io/extension=viz' \\\n        --timeout=1m\n\n# Ensure that a linkerd control plane is installed\n_linkerd-viz-uninit:\n    #!/usr/bin/env bash\n    set -euo pipefail\n    if {{ _kubectl }} get ns linkerd-viz >/dev/null 2>&1 ; then\n        {{ just_executable() }} \\\n            k3d-name='{{ k3d-name }}' \\\n            linkerd-exec='{{ linkerd-exec }}' \\\n            linkerd-viz-uninstall\n    fi\n\n##\n## linkerd multicluster\n##\n\n_mc-target-k3d-flags := \"--k3s-arg --disable='local-storage,metrics-server@server:*' --k3s-arg '--cluster-cidr=10.23.0.0/24@server:*'\"\n\nlinkerd-mc-install: _linkerd-init\n    {{ _linkerd }} mc install --set='linkerdVersion={{ linkerd-tag }}' \\\n        | {{ _kubectl }} apply -f -\n\n# Wait for all test namespaces to be removed before uninstalling linkerd-multicluster.\nlinkerd-mc-uninstall:\n    {{ _linkerd }} mc uninstall \\\n        | {{ _kubectl }} delete -f -\n\nmc-target-k3d-delete:\n    #!/usr/bin/env bash\n    set -euo pipefail\n    if k3d cluster list '{{ k3d-name }}-target' >/dev/null 2>/dev/null; then\n        {{ just_executable() }} \\\n            k3d-name='{{ k3d-name }}-target' \\\n            k3d-delete\n    fi\n\nmc-load: _k3d-init linkerd-load\n\nmc-target-load:\n    @{{ just_executable() }} \\\n        k3d-name='{{ k3d-name }}-target' \\\n        k3d-k8s='{{ k3d-k8s }}' \\\n        k3d-agents='{{ k3d-agents }}' \\\n        k3d-servers='{{ k3d-servers }}' \\\n        k3d-network='{{ k3d-network }}' \\\n        _k3d-flags='{{ _mc-target-k3d-flags }}' \\\n        controller-image='{{ controller-image }}' \\\n        proxy-image='{{ proxy-image }}' \\\n        linkerd-exec='{{ linkerd-exec }}' \\\n        linkerd-tag='{{ linkerd-tag }}' \\\n        _pause-load \\\n        mc-load\n\nmc-install-gwapi:\n\t{{ _kubectl }} apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/${GWAPI_VERSION}/experimental-install.yaml\n\nmc-test-build:\n    go build --mod=readonly \\\n        ./test/integration/multicluster/...\n\nk3d-source-server := \"k3d-\" + k3d-name + \"-server-0\"\nk3d-target-server := \"k3d-\" + k3d-name + \"-target-server-0\"\n\n_mc-route-output-fmt := \"-o jsonpath='ip route add {.spec.podCIDR} via {.status.addresses[?(.type==\\\"InternalIP\\\")].address}'\"\n_mc-print-source-route := _kubectl + \" \" + \"get node \" + k3d-source-server + \" \" + _mc-route-output-fmt\n_mc-print-target-route := \"kubectl --context=k3d-\" + k3d-name + \"-target \"+ \"get node \" + k3d-target-server + \" \" + _mc-route-output-fmt\n\n# Allow two k3d server nodes to participate in a flat network\nmc-flat-network-init:\n\t@docker exec k3d-{{k3d-name}}-server-0 `{{_mc-print-target-route}}`\n\t@docker exec k3d-{{k3d-name}}-target-server-0 `{{_mc-print-source-route}}`\n\n\n# Run the multicluster tests without any setup\nmc-test-run *flags:\n    LINKERD_DOCKER_REGISTRY='{{ DOCKER_REGISTRY }}' \\\n        go test -v -test.timeout=20m --failfast --mod=readonly \\\n            ./test/integration/multicluster/... \\\n                -integration-tests \\\n                -linkerd='{{ justfile_directory() }}/bin/linkerd' \\\n                -multicluster-source-context='k3d-{{ k3d-name }}' \\\n                -multicluster-target-context='k3d-{{ k3d-name }}-target' \\\n                {{ flags }}\n\n##\n## GitHub Actions\n##\n\n# Format actionlint output for Github Actions if running in CI.\n_actionlint-fmt := if env_var_or_default(\"GITHUB_ACTIONS\", \"\") != \"true\" { \"\" } else {\n  '{{range $err := .}}::error file={{$err.Filepath}},line={{$err.Line}},col={{$err.Column}}::{{$err.Message}}%0A```%0A{{replace $err.Snippet \"\\\\n\" \"%0A\"}}%0A```\\n{{end}}'\n}\n\n# Lints all GitHub Actions workflows\naction-lint:\n    actionlint {{ if _actionlint-fmt != '' { \"-format '\" + _actionlint-fmt + \"'\" } else { \"\" } }} .github/workflows/*\n\n# Ensure all devcontainer versions are in sync\naction-dev-check:\n    just-dev check-action-images\n\n##\n## Other tools...\n##\n\nmd-lint:\n    markdownlint-cli2 '**/*.md' '!**/node_modules' '!target'\n\nsh-lint:\n    bin/shellcheck-all\n\n##\n## Git\n##\n\n# Display the git history minus Dependabot updates\nhistory *paths='.':\n    @git log --oneline --graph --invert-grep --author=\"dependabot\" -- {{ paths }}\n\n# Display the history of Dependabot changes\nhistory-dependabot *paths='.':\n    @git log --oneline --graph --author=\"dependabot\" -- {{ paths }}\n\n# vim: set ft=make :\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/.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\nOWNERS\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*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/Chart.yaml",
    "content": "apiVersion: \"v1\"\n# this version will be updated by the CI before publishing the Helm tarball\nappVersion: edge-XX.X.X\ndescription: |\n  The Linkerd-Multicluster extension contains resources to support multicluster\n  linking to remote clusters\nhome: https://linkerd.io\nkeywords:\n  - service-mesh\nkubeVersion: \">=1.23.0-0\"\nname: \"linkerd-multicluster\"\nsources:\n  - https://github.com/linkerd/linkerd2/\n# this version will be updated by the CI before publishing the Helm tarball\nversion: 0.0.0-undefined\nicon: https://linkerd.io/images/logo-only-200h.png\nmaintainers:\n  - name: Linkerd authors\n    email: cncf-linkerd-dev@lists.cncf.io\n    url: https://linkerd.io/\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/NOTES.txt",
    "content": "The Linkerd Multicluster extension was successfully installed 🎉\n\nTo make sure everything works as expected, run the following:\n\n  linkerd multicluster check\n\nLooking for more? Visit https://linkerd.io/2/features/multicluster/\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/README.md.gotmpl",
    "content": "{{ template \"chart.header\" . }}\n{{ template \"chart.description\" . }}\n\n{{ template \"chart.versionBadge\" . }}\n{{ template \"chart.typeBadge\" . }}\n{{ template \"chart.appVersionBadge\" . }}\n\n{{ template \"chart.homepageLine\" . }}\n\n## Quickstart and documentation\n\nYou can run Linkerd on any Kubernetes cluster in a matter of seconds. See the\n[Linkerd Getting Started Guide][getting-started] for how.\n\nFor more comprehensive documentation, start with the [Linkerd\ndocs][linkerd-docs].\n\n## Prerequisite: Linkerd Core Control-Plane\n\nBefore installing the Linkerd Multicluster extension, The core control-plane has\nto be installed first by following the [Linkerd Install\nGuide](https://linkerd.io/2/tasks/install/).\n\n## Adding Linkerd's Helm repository\n\n```bash\n# To add the repo for Linkerd edge releases:\nhelm repo add linkerd https://helm.linkerd.io/edge\n```\n\n## Installing the Multicluster Extension Chart\n\n```bash\nhelm install linkerd-multicluster -n linkerd-multicluster --create-namespace linkerd/linkerd-multicluster\n```\n\n## Get involved\n\n* Check out Linkerd's source code at [GitHub][linkerd2].\n* Join Linkerd's [user mailing list][linkerd-users], [developer mailing\n  list][linkerd-dev], and [announcements mailing list][linkerd-announce].\n* Follow [@linkerd][twitter] on Twitter.\n* Join the [Linkerd Slack][slack].\n\n[getting-started]: https://linkerd.io/2/getting-started/\n[linkerd2]: https://github.com/linkerd/linkerd2\n[linkerd-announce]: https://lists.cncf.io/g/cncf-linkerd-announce\n[linkerd-dev]: https://lists.cncf.io/g/cncf-linkerd-dev\n[linkerd-docs]: https://linkerd.io/2/overview/\n[linkerd-users]: https://lists.cncf.io/g/cncf-linkerd-users\n[slack]: http://slack.linkerd.io\n[twitter]: https://twitter.com/linkerd\n\n{{ template \"chart.requirementsSection\" . }}\n\n{{ template \"chart.valuesSection\" . }}\n\n{{ template \"helm-docs.versionFooter\" . }}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/requirements.yaml",
    "content": "dependencies:\n  - name: partials\n    version: 0.1.0\n    repository: file://../../../charts/partials\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/templates/controller/deployment.yaml",
    "content": "{{- range .Values.controllers }}\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    linkerd.io/extension: multicluster\n    component: controller\n    mirror.linkerd.io/cluster-name: {{.link.ref.name}}\n    {{- with $.Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  name: controller-{{.link.ref.name}}\n  namespace: {{ $.Release.Namespace }}\nspec:\n  replicas: {{ dig \"replicas\" $.Values.controllerDefaults.replicas . }}\n  revisionHistoryLimit: {{$.Values.revisionHistoryLimit}}\n  selector:\n    matchLabels:\n      component: controller\n      mirror.linkerd.io/cluster-name: {{.link.ref.name}}\n  {{- if dig \"enablePodAntiAffinity\" $.Values.controllerDefaults.enablePodAntiAffinity . }}\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 1\n  {{- end }}\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        {{- with $.Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n      labels:\n        linkerd.io/extension: multicluster\n        component: controller\n        mirror.linkerd.io/cluster-name: {{.link.ref.name}}\n        {{- with $.Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n    spec:\n    {{- $tree := deepCopy $ }}\n    {{- $_ := set $tree.Values \"enablePodAntiAffinity\" (dig \"enablePodAntiAffinity\" $.Values.controllerDefaults.enablePodAntiAffinity .) -}}\n    {{- $_ := set $tree.Values \"nodeAffinity\" (dig \"nodeAffinity\" $.Values.controllerDefaults.nodeAffinity .) -}}\n    {{- $_ := set $tree \"component\" .link.ref.name -}}\n    {{- $_ := set $tree \"label\" \"mirror.linkerd.io/cluster-name\" -}}\n    {{- with include \"linkerd.affinity\" $tree }}\n    {{- . | nindent 6 }}\n    {{- end }}\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - service-mirror\n        - -log-level={{ dig \"logLevel\" $.Values.controllerDefaults.logLevel . }}\n        - -log-format={{ dig \"logFormat\" $.Values.controllerDefaults.logFormat . }}\n        - -event-requeue-limit={{ dig \"retryLimit\" $.Values.controllerDefaults.retryLimit . }}\n        - -namespace={{$.Release.Namespace}}\n        {{- if dig \"enableHeadlessServices\" $.Values.controllerDefaults.enableHeadlessServices . }}\n        - -enable-headless-services\n        {{- end }}\n        {{- if $.Values.enableNamespaceCreation }}\n        - -enable-namespace-creation\n        {{- end }}\n        - -enable-pprof={{ dig \"enablePprof\" $.Values.controllerDefaults.enablePprof . }}\n        - -probe-service=probe-{{.link.ref.name}}\n        - {{.link.ref.name}}\n        {{- if or $.Values.serviceMirrorAdditionalEnv $.Values.serviceMirrorExperimentalEnv }}\n        env:\n        {{- with $.Values.serviceMirrorAdditionalEnv }}\n        {{- toYaml . | nindent 8 -}}\n        {{- end }}\n        {{- with $.Values.serviceMirrorExperimentalEnv }}\n        {{- toYaml . | nindent 8 -}}\n        {{- end }}\n        {{- end }}\n        image: {{ dig \"image\" \"name\" $.Values.controllerDefaults.image.name . }}:{{ dig \"image\" \"version\" $.Values.controllerDefaults.image.version . }}\n        name: controller\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: {{ dig \"UID\" $.Values.controllerDefaults.UID . }}\n          runAsGroup: {{ dig \"GID\" $.Values.controllerDefaults.GID . }}\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n        ports:\n        - containerPort: 9999\n          name: ctrl-admin\n        {{- with dig \"resources\" $.Values.controllerDefaults.resources . }}\n        resources: {{ toYaml . | nindent 10 }}\n        {{- end }}\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: controller-{{.link.ref.name}}\n      volumes:\n      - {{- include \"partials.volumes.manual-mount-service-account-token\" $ | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{- with dig \"nodeSelector\" $.Values.controllerDefaults.nodeSelector . }}\n      nodeSelector: {{ toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with dig \"tolerations\" $.Values.controllerDefaults.tolerations . }}\n      tolerations: {{ toYaml . | nindent 6 }}\n      {{- end }}\n{{- end}}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/templates/controller/pdb.yaml",
    "content": "{{- range .Values.controllers }}\n{{- if dig \"enablePodAntiAffinity\" $.Values.enablePodAntiAffinity . }}\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: controller-{{.link.ref.name}}\n  namespace: {{ $.Release.Namespace }}\n  labels:\n    component: controller\n  annotations:\n    {{ include \"partials.annotations.created-by\" $ }}\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      component: controller\n      mirror.linkerd.io/cluster-name: {{.link.ref.name}}\n{{- end}}\n{{- end}}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/templates/controller/probe-svc.yaml",
    "content": "{{- range .Values.controllers }}\n{{- if dig \"gateway\" \"enabled\" $.Values.controllerDefaults.gateway.enabled . }}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: probe-{{.link.ref.name}}\n  namespace: {{ $.Release.Namespace }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" $ }}\n  labels:\n    linkerd.io/extension: multicluster\n    mirror.linkerd.io/mirrored-gateway: \"true\"\n    mirror.linkerd.io/cluster-name: {{.link.ref.name}}\n    {{- with $.Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nspec:\n  ports:\n  - name: mc-probe\n    port: {{ dig \"gateway\" \"probe\" \"port\" $.Values.controllerDefaults.gateway.probe.port . }}\n    protocol: TCP\n{{ end -}}\n{{ end -}}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/templates/controller/rbac.yaml",
    "content": "{{- range .Values.controllers }}\n{{- if empty ((.link).ref).name -}}\n{{- fail \"the value link.ref.name is required\" -}}\n{{ end -}}\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: controller-{{.link.ref.name}}\n  namespace: {{ $.Release.Namespace }}\n  labels:\n    linkerd.io/extension: multicluster\n    component: controller\n    mirror.linkerd.io/cluster-name: {{.link.ref.name}}\n    {{- with $.Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n{{- include \"partials.image-pull-secrets\" $.Values.imagePullSecrets }}\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-multicluster-controller-access-local-resources-{{.link.ref.name}}\n  labels:\n    linkerd.io/extension: multicluster\n    component: controller\n    mirror.linkerd.io/cluster-name: {{.link.ref.name}}\n    {{- with $.Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-multicluster-controller-access-local-resources\nsubjects:\n- kind: ServiceAccount\n  name: controller-{{.link.ref.name}}\n  namespace: {{$.Release.Namespace}}\n---\nkind: Role\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: controller-read-remote-creds-{{.link.ref.name}}\n  namespace: {{ $.Release.Namespace }}\n  labels:\n    linkerd.io/extension: multicluster\n    component: controller\n    mirror.linkerd.io/cluster-name: {{.link.ref.name}}\n    {{- with $.Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"secrets\"]\n    resourceNames: [\"cluster-credentials-{{.link.ref.name}}\"]\n    verbs: [\"list\", \"get\", \"watch\"]\n  - apiGroups: [\"multicluster.linkerd.io\"]\n    resources: [\"links\"]\n    verbs: [\"list\", \"get\", \"watch\"]\n  - apiGroups: [\"multicluster.linkerd.io\"]\n    resources: [\"links/status\"]\n    verbs: [\"update\", \"patch\"]\n  - apiGroups: [\"coordination.k8s.io\"]\n    resources: [\"leases\"]\n    verbs: [\"create\", \"get\", \"update\", \"patch\"]\n---\nkind: RoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: controller-read-remote-creds-{{.link.ref.name}}\n  namespace: {{ $.Release.Namespace }}\n  labels:\n    linkerd.io/extension: multicluster\n    component: controller\n    mirror.linkerd.io/cluster-name: {{.link.ref.name}}\n    {{- with $.Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: controller-read-remote-creds-{{.link.ref.name}}\nsubjects:\n  - kind: ServiceAccount\n    name: controller-{{.link.ref.name}}\n    namespace: {{$.Release.Namespace}}\n{{ end -}}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/templates/controller-clusterrole.yaml",
    "content": "---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-multicluster-controller-access-local-resources\n  labels:\n    linkerd.io/extension: multicluster\n    component: controller\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"delete\", \"update\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n{{- if .Values.enableNamespaceCreation }}\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"create\"]\n{{- end}}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/templates/gateway-policy.yaml",
    "content": "{{if .Values.gateway.enabled -}}\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: linkerd-gateway\n  labels:\n    linkerd.io/extension: multicluster\n    app: {{.Values.gateway.name}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  podSelector:\n    matchLabels:\n      app: {{.Values.gateway.name}}\n  port: linkerd-proxy\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: linkerd-gateway\n  labels:\n    linkerd.io/extension: multicluster\n    app: {{.Values.gateway.name}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: linkerd-gateway\n  requiredAuthenticationRefs:\n    - group: policy.linkerd.io\n      kind: MeshTLSAuthentication\n      name: any-meshed\n      namespace: {{ .Release.Namespace }}\n    - group: policy.linkerd.io\n      kind: NetworkAuthentication\n      name: source-cluster\n      namespace: {{ .Release.Namespace }}\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: MeshTLSAuthentication\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: any-meshed\n  labels:\n    linkerd.io/extension: multicluster\n    app: {{.Values.gateway.name}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  identities:\n  - '*'\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: source-cluster\n  labels:\n    linkerd.io/extension: multicluster\n    app: {{.Values.gateway.name}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  networks:\n    # Change this to the source cluster cidrs pointing to this gateway.\n    # Note that the source IP in some providers (e.g. GKE) will be the local\n    # node's IP and not the source cluster's\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n{{end -}}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/templates/gateway.yaml",
    "content": "{{if .Values.gateway.enabled -}}\n---\n{{- $tree := deepCopy . }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  labels:\n    app.kubernetes.io/name: gateway\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: {{.Values.linkerdVersion}}\n    component: gateway\n    app: {{.Values.gateway.name}}\n    linkerd.io/extension: multicluster\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  name: {{.Values.gateway.name}}\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: {{.Values.gateway.replicas}}\n  revisionHistoryLimit: {{.Values.revisionHistoryLimit}}\n  selector:\n    matchLabels:\n      app: {{.Values.gateway.name}}\n  {{- if .Values.enablePodAntiAffinity }}\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 1\n  {{- end }}\n  template:\n    metadata:\n      annotations:\n        {{ include \"partials.annotations.created-by\" . }}\n        linkerd.io/inject: enabled\n        config.linkerd.io/proxy-require-identity-inbound-ports: \"{{.Values.gateway.port}}\"\n        config.linkerd.io/enable-gateway: \"true\"\n        config.linkerd.io/default-inbound-policy: all-authenticated\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        {{- with .Values.gateway.deploymentAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n      labels:\n        app: {{.Values.gateway.name}}\n        linkerd.io/extension: multicluster\n        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n    spec:\n      {{- $_ := set $tree \"component\" .Values.gateway.name -}}\n      {{- $_ := set $tree \"label\" \"app\" -}}\n      {{- with include \"linkerd.affinity\" $tree }}\n      {{- . | nindent 6 }}\n      {{- end }}\n      {{- if .Values.gateway.terminationGracePeriodSeconds }}\n      terminationGracePeriodSeconds: {{.Values.gateway.terminationGracePeriodSeconds}}\n      {{- end }}\n      automountServiceAccountToken: false\n      containers:\n      - name: pause\n        image: {{ .Values.gateway.pauseImage }}\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: {{.Values.gateway.UID}}\n          runAsGroup: {{.Values.gateway.GID}}\n          seccompProfile:\n            type: RuntimeDefault\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: {{.Values.gateway.name}}\n      {{- with .Values.gateway.nodeSelector }}\n      nodeSelector: {{ toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.gateway.tolerations }}\n      tolerations: {{ toYaml . | nindent 6 }}\n      {{- end }}\n{{- if .Values.enablePodAntiAffinity }}\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: {{.Values.gateway.name}}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: {{.Values.gateway.name}}\n    linkerd.io/extension: multicluster\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      app: {{.Values.gateway.name}}\n{{- end }}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{.Values.gateway.name}}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: multicluster\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    mirror.linkerd.io/gateway-identity: {{.Values.gateway.name}}.{{.Release.Namespace}}.serviceaccount.identity.{{.Values.linkerdNamespace}}.{{.Values.identityTrustDomain}}\n    mirror.linkerd.io/probe-period: \"{{.Values.gateway.probe.seconds}}\"\n    mirror.linkerd.io/probe-path: {{.Values.gateway.probe.path}}\n    mirror.linkerd.io/multicluster-gateway: \"true\"\n    component: gateway\n    {{ include \"partials.annotations.created-by\" . }}\n    {{- with .Values.gateway.serviceAnnotations }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nspec:\n  ports:\n  {{- $setNodePorts := (or (eq .Values.gateway.serviceType \"NodePort\") (eq .Values.gateway.serviceType \"LoadBalancer\")) }}\n  - name: mc-gateway\n    port: {{.Values.gateway.port}}\n    protocol: TCP\n  {{- if (and $setNodePorts .Values.gateway.nodePort) }}\n    nodePort: {{ .Values.gateway.nodePort }}\n  {{- end }}\n  - name: mc-probe\n    port: {{.Values.gateway.probe.port}}\n    protocol: TCP\n  {{- if (and $setNodePorts .Values.gateway.probe.nodePort) }}\n    nodePort: {{ .Values.gateway.probe.nodePort }}\n  {{- end }}\n  selector:\n    app: {{.Values.gateway.name}}\n  type: {{ .Values.gateway.serviceType }}\n{{- with .Values.gateway.serviceExternalTrafficPolicy }}\n  externalTrafficPolicy: {{ . }}\n{{- end }}\n{{- if .Values.gateway.loadBalancerClass }}\n  loadBalancerClass: {{ .Values.gateway.loadBalancerClass }}\n{{- end }}\n{{- if .Values.gateway.loadBalancerIP }}\n  loadBalancerIP: {{ .Values.gateway.loadBalancerIP }}\n{{- end }}\n{{- if .Values.gateway.loadBalancerSourceRanges }}\n  loadBalancerSourceRanges:\n  {{- range .Values.gateway.loadBalancerSourceRanges }}\n  - {{ . }}\n  {{- end }}\n{{- end }}\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: {{.Values.gateway.name}}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: multicluster\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n{{- include \"partials.image-pull-secrets\" .Values.imagePullSecrets }}\n{{end -}}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/templates/link-crd.yaml",
    "content": "---\n###\n### Link CRD\n###\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: links.multicluster.linkerd.io\n  labels:\n    linkerd.io/extension: multicluster\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  group: multicluster.linkerd.io\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              clusterCredentialsSecret:\n                description: Kubernetes secret of target cluster\n                type: string\n              gatewayAddress:\n                description: Gateway address of target cluster\n                type: string\n              gatewayIdentity:\n                description: Gateway Identity FQDN\n                type: string\n              gatewayPort:\n                description: Gateway Port\n                type: string\n              probeSpec:\n                description: Spec for gateway health probe\n                type: object\n                properties:\n                  failureThreshold:\n                    default: \"3\"\n                    description: Minimum consecutive failures for the probe to be considered failed\n                    type: string\n                  path:\n                    description: Path of remote gateway health endpoint\n                    type: string\n                  period:\n                    description: Interval in between probe requests\n                    type: string\n                  port:\n                    description: Port of remote gateway health endpoint\n                    type: string\n                  timeout:\n                    default: 30s\n                    description: Probe request timeout\n                    format: duration\n                    type: string\n              selector:\n                description: Kubernetes Label Selector\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              remoteDiscoverySelector:\n                description: Selector for Services to mirror in remote discovery mode\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              targetClusterName:\n                description: Name of target cluster to link to\n                type: string\n              targetClusterDomain:\n                description: Domain name of target cluster to link to\n                type: string\n              targetClusterLinkerdNamespace:\n                description: Name of namespace Linkerd control plane is installed in on target cluster\n                type: string\n  - name: v1alpha2\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              clusterCredentialsSecret:\n                description: Kubernetes secret of target cluster\n                type: string\n              gatewayAddress:\n                description: Gateway address of target cluster\n                type: string\n              gatewayIdentity:\n                description: Gateway Identity FQDN\n                type: string\n              gatewayPort:\n                description: Gateway Port\n                type: string\n              probeSpec:\n                description: Spec for gateway health probe\n                type: object\n                properties:\n                  failureThreshold:\n                    default: \"3\"\n                    description: Minimum consecutive failures for the probe to be considered failed\n                    type: string\n                  path:\n                    description: Path of remote gateway health endpoint\n                    type: string\n                  period:\n                    description: Interval in between probe requests\n                    format: duration\n                    type: string\n                  port:\n                    description: Port of remote gateway health endpoint\n                    type: string\n                  timeout:\n                    default: 30s\n                    description: Probe request timeout\n                    format: duration\n                    type: string\n              selector:\n                description: Kubernetes Label Selector\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              remoteDiscoverySelector:\n                description: Selector for Services to mirror in remote discovery mode\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              federatedServiceSelector:\n                description: Selector for federated service memebers\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              targetClusterName:\n                description: Name of target cluster to link to\n                type: string\n              targetClusterDomain:\n                description: Domain name of target cluster to link to\n                type: string\n              targetClusterLinkerdNamespace:\n                description: Name of namespace Linkerd control plane is installed in on target cluster\n                type: string\n          status:\n            description: Status defines the state of resources managed by this Link\n            properties:\n              mirrorServices:\n                description: List of services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a mirrored service\n                  properties:\n                    conditions:\n                      description: Conditions of the mirrored service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n              federatedServices:\n                description: List of federated services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a federated service\n                  properties:\n                    conditions:\n                      description: Conditions of the federated service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n            type: object\n    subresources:\n      status: {}\n  - name: v1alpha3\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              clusterCredentialsSecret:\n                description: Kubernetes secret of target cluster\n                type: string\n              gatewayAddress:\n                description: Gateway address of target cluster\n                type: string\n              gatewayIdentity:\n                description: Gateway Identity FQDN\n                type: string\n              gatewayPort:\n                description: Gateway Port\n                type: string\n              probeSpec:\n                description: Spec for gateway health probe\n                type: object\n                properties:\n                  failureThreshold:\n                    default: \"3\"\n                    description: Minimum consecutive failures for the probe to be considered failed\n                    type: string\n                  path:\n                    description: Path of remote gateway health endpoint\n                    type: string\n                  period:\n                    description: Interval in between probe requests\n                    format: duration\n                    type: string\n                  port:\n                    description: Port of remote gateway health endpoint\n                    type: string\n                  timeout:\n                    default: 30s\n                    description: Probe request timeout\n                    format: duration\n                    type: string\n              selector:\n                description: Kubernetes Label Selector\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              remoteDiscoverySelector:\n                description: Selector for Services to mirror in remote discovery mode\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              federatedServiceSelector:\n                description: Selector for federated service memebers\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              targetClusterName:\n                description: Name of target cluster to link to\n                type: string\n              targetClusterDomain:\n                description: Domain name of target cluster to link to\n                type: string\n              targetClusterLinkerdNamespace:\n                description: Name of namespace Linkerd control plane is installed in on target cluster\n                type: string\n              excludedAnnotations:\n                description: List of annotations which should not be copied to the federated service\n                type: array\n                items:\n                  type: string\n              excludedLabels:\n                description: List of labels which should not be copied to the federated service\n                type: array\n                items:\n                  type: string\n          status:\n            description: Status defines the state of resources managed by this Link\n            properties:\n              mirrorServices:\n                description: List of services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a mirrored service\n                  properties:\n                    conditions:\n                      description: Conditions of the mirrored service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n              federatedServices:\n                description: List of federated services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a federated service\n                  properties:\n                    conditions:\n                      description: Conditions of the federated service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n            type: object\n    subresources:\n      status: {}\n  scope: Namespaced\n  names:\n    plural: links\n    singular: link\n    kind: Link\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/templates/local-service-mirror.yaml",
    "content": "---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-local-service-mirror-access-local-resources\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"delete\", \"update\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"multicluster.linkerd.io\"]\n  resources: [\"links\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"multicluster.linkerd.io\"]\n  resources: [\"links/status\"]\n  verbs: [\"update\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-local-service-mirror-access-local-resources\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-local-service-mirror-access-local-resources\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-local-service-mirror\n  namespace: {{.Release.Namespace}}\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-local-service-mirror\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n{{- include \"partials.image-pull-secrets\" .Values.imagePullSecrets }}\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  name: linkerd-local-service-mirror\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: {{ .Values.localServiceMirror.replicas }}\n  revisionHistoryLimit: {{.Values.revisionHistoryLimit}}\n  selector:\n    matchLabels:\n      component: local-service-mirror\n  {{- if .Values.enablePodAntiAffinity }}\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 1\n  {{- end }}\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n      labels:\n        linkerd.io/extension: multicluster\n        component: local-service-mirror\n        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n    spec:\n    {{- if .Values.enablePodAntiAffinity}}\n    {{- with $tree := deepCopy . }}\n    {{- $_ := set $tree \"component\" \"local-service-mirror\" -}}\n    {{- $_ := set $tree \"label\" \"component\" -}}\n    {{- include \"linkerd.affinity\" $tree | nindent 6 }}\n    {{- end }}\n    {{- end }}\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - service-mirror\n        - -log-level={{.Values.localServiceMirror.logLevel}}\n        - -log-format={{.Values.localServiceMirror.logFormat}}\n        - -event-requeue-limit={{.Values.localServiceMirror.serviceMirrorRetryLimit}}\n        - -namespace={{.Release.Namespace}}\n        - -enable-pprof={{.Values.localServiceMirror.enablePprof | default false}}\n        - -local-mirror\n        - -federated-service-selector={{.Values.localServiceMirror.federatedServiceSelector}}\n        - -excluded-labels={{.Values.localServiceMirror.excludedLabels}}\n        - -excluded-annotations={{.Values.localServiceMirror.excludedAnnotations}}\n        {{- if or .Values.localServiceMirror.additionalEnv .Values.localServiceMirror.experimentalEnv }}\n        env:\n        {{- with .Values.localServiceMirror.additionalEnv }}\n        {{- toYaml . | nindent 8 -}}\n        {{- end }}\n        {{- with .Values.localServiceMirror.experimentalEnv }}\n        {{- toYaml . | nindent 8 -}}\n        {{- end }}\n        {{- end }}\n        image: {{.Values.localServiceMirror.image.name}}:{{.Values.localServiceMirror.image.version}}\n        name: service-mirror\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: {{.Values.localServiceMirror.UID}}\n          runAsGroup: {{.Values.localServiceMirror.GID}}\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n        ports:\n        - containerPort: 9999\n          name: locsm-admin\n        {{- with .Values.localServiceMirror.resources }}\n        resources: {{ toYaml . | nindent 10 }}\n        {{- end }}\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-local-service-mirror\n      volumes:\n      - {{- include \"partials.volumes.manual-mount-service-account-token\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{- with .Values.nodeSelector }}\n      nodeSelector: {{ toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.tolerations }}\n      tolerations: {{ toYaml . | nindent 6 }}\n      {{- end }}\n{{- if .Values.enablePodAntiAffinity }}\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-local-service-mirror\n  namespace: {{ .Release.Namespace }}\n  labels:\n    component: local-service-mirror\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      component: local-service-mirror\n{{- end}}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/templates/namespace-metadata-rbac.yaml",
    "content": "{{- if .Values.createNamespaceMetadataJob}}\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  labels:\n    linkerd.io/extension: multicluster\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    \"helm.sh/hook\": post-install\n    \"helm.sh/hook-weight\": \"0\"\n    \"helm.sh/hook-delete-policy\": before-hook-creation,hook-succeeded\n  name: namespace-metadata\n  namespace: {{.Release.Namespace}}\n{{- include \"partials.image-pull-secrets\" .Values.imagePullSecrets }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/extension: multicluster\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    \"helm.sh/hook\": post-install\n    \"helm.sh/hook-weight\": \"0\"\n    \"helm.sh/hook-delete-policy\": before-hook-creation,hook-succeeded\n  name: namespace-metadata\n  namespace: {{.Release.Namespace}}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"get\", \"patch\"]\n  resourceNames: [\"{{.Release.Namespace}}\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    linkerd.io/extension: multicluster\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    \"helm.sh/hook\": post-install\n    \"helm.sh/hook-weight\": \"0\"\n    \"helm.sh/hook-delete-policy\": before-hook-creation,hook-succeeded\n  name: namespace-metadata\n  namespace: {{.Release.Namespace}}\nroleRef:\n  kind: Role\n  name: namespace-metadata\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: namespace-metadata\n  namespace: {{.Release.Namespace}}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  namespace: {{ .Values.linkerdNamespace }}\n  labels:\n    linkerd.io/extension: multicluster\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    \"helm.sh/hook\": post-install\n    \"helm.sh/hook-weight\": \"0\"\n    \"helm.sh/hook-delete-policy\": before-hook-creation,hook-succeeded\n  name: mc-namespace-metadata-linkerd-config\nroleRef:\n  kind: Role\n  name: ext-namespace-metadata-linkerd-config\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: namespace-metadata\n  namespace: {{.Release.Namespace}}\n{{- end }}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/templates/namespace-metadata.yaml",
    "content": "{{- if .Values.createNamespaceMetadataJob}}\napiVersion: batch/v1\nkind: Job\nmetadata:\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    \"helm.sh/hook\": post-install\n    \"helm.sh/hook-weight\": \"1\"\n    \"helm.sh/hook-delete-policy\": before-hook-creation,hook-succeeded\n  labels:\n    linkerd.io/extension: multicluster\n    app.kubernetes.io/name: namespace-metadata\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: {{.Values.linkerdVersion}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  name: namespace-metadata\n  namespace: {{.Release.Namespace}}\nspec:\n  template:\n    metadata:\n      annotations:\n        {{ include \"partials.annotations.created-by\" . }}\n        linkerd.io/inject: disabled\n      labels:\n        linkerd.io/extension: multicluster\n        app.kubernetes.io/name: namespace-metadata\n        app.kubernetes.io/part-of: Linkerd\n        app.kubernetes.io/version: {{.Values.linkerdVersion}}\n        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n    spec:\n      {{- with .Values.namespaceMetadata.nodeSelector }}\n      nodeSelector: {{ toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.namespaceMetadata.tolerations }}\n      tolerations: {{ toYaml . | nindent 6 }}\n      {{- end }}\n      restartPolicy: Never\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: namespace-metadata\n      automountServiceAccountToken: false\n      containers:\n      - name: namespace-metadata\n        image: {{.Values.namespaceMetadata.image.registry}}/{{.Values.namespaceMetadata.image.name}}:{{.Values.namespaceMetadata.image.tag}}\n        imagePullPolicy: {{.Values.namespaceMetadata.image.pullPolicy | default .Values.imagePullPolicy}}\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: {{.Values.gateway.UID}}\n          runAsGroup: {{.Values.gateway.GID}}\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n        args:\n        - --extension\n        - multicluster\n        - --namespace\n        - {{.Release.Namespace}}\n        - --linkerd-namespace\n        - {{.Values.linkerdNamespace}}\n      volumes:\n      - {{- include \"partials.volumes.manual-mount-service-account-token\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n{{- end }}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/templates/namespace.yaml",
    "content": "{{- if eq .Release.Service \"CLI\" -}}\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: multicluster\n    {{- /* linkerd-init requires extended capabilities and so requires priviledged mode */}}\n    pod-security.kubernetes.io/enforce: {{ if .Values.cniEnabled }}restricted{{ else }}privileged{{ end }}\n{{end -}}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/templates/psp.yaml",
    "content": "{{ if .Values.enablePSP -}}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: psp\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: multicluster\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: ['policy', 'extensions']\n  resources: ['podsecuritypolicies']\n  verbs: ['use']\n  resourceNames:\n  - linkerd-{{.Values.linkerdNamespace}}-control-plane\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-multicluster-psp\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: multicluster\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  kind: Role\n  name: psp\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: {{.Values.gateway.name}}\n  namespace: {{.Release.Namespace}}\n- kind: ServiceAccount\n  name: namespace-metadata\n  namespace: {{.Release.Namespace}}\n{{ end -}}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/templates/remote-access-service-mirror-rbac.yaml",
    "content": "{{if .Values.remoteMirrorServiceAccount -}}\n{{- $names := .Values.remoteMirrorServiceAccountName -}}\n{{- if not (kindIs \"slice\" .Values.remoteMirrorServiceAccountName) -}}\n  {{- $names = splitList \",\" .Values.remoteMirrorServiceAccountName -}}\n{{- end -}}\n{{- range $names -}}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{.}}\n  labels:\n    linkerd.io/extension: multicluster\n    {{- with $.Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" $ }}\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"policy.linkerd.io\"]\n  resources: [\"servers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{.}}\n  namespace: {{$.Release.Namespace}}\n  labels:\n    linkerd.io/extension: multicluster\n    {{- with $.Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" $ }}\n{{- include \"partials.image-pull-secrets\" $.Values.imagePullSecrets }}\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: {{.}}-token\n  namespace: {{$.Release.Namespace}}\n  labels:\n    linkerd.io/extension: multicluster\n    {{- with $.Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    kubernetes.io/service-account.name: {{.}}\n    {{ include \"partials.annotations.created-by\" $ }}\ntype: kubernetes.io/service-account-token\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: {{.}}\n  labels:\n    linkerd.io/extension: multicluster\n    {{- with $.Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" $ }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{.}}\nsubjects:\n- kind: ServiceAccount\n  name: {{.}}\n  namespace: {{$.Release.Namespace}}\n{{end -}}\n{{end -}}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/templates/service-mirror-policy.yaml",
    "content": "---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: service-mirror\n  labels:\n    linkerd.io/extension: multicluster\n    component: linkerd-service-mirror\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nspec:\n  podSelector:\n    matchExpressions:\n    - key: component\n      operator: In\n      values:\n      - linkerd-service-mirror\n      - controller\n  port: svcmi-admin\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: service-mirror\n  labels:\n    linkerd.io/extension: multicluster\n    component: linkerd-service-mirror\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: service-mirror\n  requiredAuthenticationRefs:\n    # In order to use `linkerd mc gateways` you need viz' Prometheus instance\n    # to be able to reach the service-mirror. In order to also have a separate\n    # Prometheus scrape the service-mirror an additional AuthorizationPolicy\n    # resource should be created.\n    - kind: ServiceAccount\n      name: prometheus\n      namespace: linkerd-viz\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/values-ha.yaml",
    "content": "gateway:\n  replicas: 3\n\nenablePodAntiAffinity: true\n\n# nodeAffinity:\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster/values.yaml",
    "content": "gateway:\n  # -- If the gateway component should be installed\n  enabled: true\n  # -- Number of replicas for the gateway pod\n  replicas: 1\n  # -- The name of the gateway that will be installed\n  name: linkerd-gateway\n  # -- The port on which all the gateway will accept incoming traffic\n  port: 4143\n  # -- Service Type of gateway Service\n  serviceType: LoadBalancer\n  # nodePort -- Set the gateway nodePort (for LoadBalancer or NodePort) to a specific value\n  # nodePort:\n  probe:\n    # -- The path that will be used by remote clusters for determining whether the\n    # gateway is alive\n    path: /ready\n    # -- The port used for liveliness probing\n    port: 4191\n    # nodePort -- Set the probe nodePort (for LoadBalancer or NodePort) to a specific value\n    # nodePort:\n    # -- The interval (in seconds) between liveness probes\n    seconds: 3\n  # -- Annotations to add to the gateway service\n  serviceAnnotations: {}\n  # -- Set externalTrafficPolicy on gateway service\n  serviceExternalTrafficPolicy: \"\"\n  # -- Annotations to add to the gateway deployment\n  deploymentAnnotations: {}\n  # -- Set loadBalancerClass on gateway service\n  loadBalancerClass: \"\"\n  # -- Set loadBalancerIP on gateway service\n  loadBalancerIP: \"\"\n  # -- Set loadBalancerSourceRanges on gateway service\n  loadBalancerSourceRanges: []\n  # -- Set terminationGracePeriodSeconds on gateway deployment\n  terminationGracePeriodSeconds: \"\"\n  # -- Node selectors for the gateway pod\n  nodeSelector: {}\n  # -- Tolerations for the gateway pod\n  tolerations: []\n\n  # -- The pause container to use\n  pauseImage: \"registry.k8s.io/pause:3.2\"\n\n  # -- User id under which the gateway shall be ran\n  UID: 2103\n\n  # -- Group id under which the gateway shall be ran\n  GID: 2103\n\n# -- Control plane version\nlinkerdVersion: linkerdVersionValue\n# -- Additional annotations to add to all pods\npodAnnotations: {}\n# -- Additional labels to add to all pods\npodLabels: {}\n# -- Labels to apply to all resources\ncommonLabels: {}\n# -- Docker imagePullPolicy for all multicluster components\nimagePullPolicy: IfNotPresent\n# -- For Private docker registries, authentication is needed.\n#  Registry secrets are applied to the respective service accounts\nimagePullSecrets: []\n# - name: my-private-docker-registry-login-secret\n# -- The port on which the proxy accepts outbound traffic\nproxyOutboundPort: 4140\n# -- If the remote mirror service account should be installed\nremoteMirrorServiceAccount: true\n# -- The name of the service account used to allow remote clusters to mirror\n# local services\nremoteMirrorServiceAccountName: linkerd-service-mirror-remote-access-default\n# -- Namespace of linkerd installation\nlinkerdNamespace: linkerd\n# -- Identity Trust Domain of the certificate authority\nidentityTrustDomain: cluster.local\n\nnamespaceMetadata:\n  image:\n    # -- Docker registry for the namespace-metadata instance\n    registry: cr.l5d.io/linkerd\n    # -- Docker image name for the namespace-metadata instance\n    name: extension-init\n    # -- Docker image tag for the namespace-metadata instance\n    tag: v0.1.9\n    # -- Pull policy for the namespace-metadata instance\n    # @default -- imagePullPolicy\n    pullPolicy: \"\"\n\n  # -- Node selectors for the namespace-metadata instance\n  nodeSelector: {}\n  # -- Tolerations for the namespace-metadata instance\n  tolerations: []\n\n# -- Create Roles and RoleBindings to associate this extension's\n# ServiceAccounts to the control plane PSP resource. This requires that\n# `enabledPSP` is set to true on the control plane install. Note PSP has been\n# deprecated since k8s v1.21\nenablePSP: false\n\n# -- Toggle support for creating namespaces for mirror services when necessary\nenableNamespaceCreation: false\n\n# -- Enables Pod Anti Affinity logic to balance the placement of replicas\n# across hosts and zones for High Availability.\n# Enable this only when you have multiple replicas of components.\nenablePodAntiAffinity: false\n\n# -- NodeAffinity section, See the\n# [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity)\n# for more information\n# nodeAffinity:\n\n# -- Creates a Job that adds necessary metadata to the extension's namespace\n# during install; disable if lack of privileges require doing this manually\ncreateNamespaceMetadataJob: true\n\n# -- Specifies the number of old ReplicaSets to retain to allow rollback.\nrevisionHistoryLimit: 10\n\nlocalServiceMirror:\n  # -- Number of times local service mirror updates are allowed to be requeued\n  # (retried)\n  serviceMirrorRetryLimit: 3\n\n  # -- Label selector for federated service members in the local cluster.\n  federatedServiceSelector: \"mirror.linkerd.io/federated=member\"\n\n  # -- Labels that should not be copied from the local service to the mirror\n  # service.\n  excludedLabels: \"\"\n\n  # -- Annotations that should not be copied from the local service to the\n  # mirror service.\n  excludedAnnotations: \"\"\n\n  # -- Number of local service mirror replicas to run\n  replicas: 1\n\n  image:\n    # -- Docker image for the Service mirror component (uses the Linkerd controller\n    # image)\n    name: cr.l5d.io/linkerd/controller\n    # -- Pull policy for the Service mirror container image\n    # @default -- imagePullPolicy\n    pullPolicy: \"\"\n    # -- Tag for the Service mirror container image\n    # @default -- linkerdVersion\n    version: linkerdVersionValue\n\n  # -- Log level for the Multicluster components\n  logLevel: info\n\n  # -- Log format (`plain` or `json`)\n  logFormat: plain\n\n  # -- enables the use of pprof endpoints on control plane component's admin\n  # servers\n  enablePprof: false\n\n  # -- User id under which the Service Mirror shall be ran\n  UID: 2103\n  # -- Group id under which the Service Mirror shall be ran\n  GID: 2103\n\n  # -- Resources for the Service mirror container\n  resources: {}\n\n# -- List of service mirror controllers.\n# References to the Links deployed in the cluster, each of which will have a\n# corresponding service mirror controller deployed.\n# Only `link.ref.name` is required for each entry.\n# Example (all the missing values take their values from controllerDefaults):\n# controllers:\n# - link:\n#     ref:\n#       name: target1\n#   logLevel: debug\n# - link:\n#     ref:\n#       name: target2\n#   gateway:\n#     enabled: false\n#   replicas: 2\ncontrollers: []\n\ncontrollerDefaults:\n  # -- Number of service mirror replicas for a given Link\n  replicas: 1\n  image:\n    name: cr.l5d.io/linkerd/controller\n    # @default -- imagePullPolicy\n    pullPolicy: \"\"\n    # @default -- linkerdVersion\n    version: linkerdVersionValue\n  gateway:\n    # -- Enables a probe service for the gateway\n    enabled: true\n    probe:\n      # -- Port used for liveliness probing\n      port: 4191\n  # -- Log level (`error`, `warn`, `info`, `debug` or `trace`)\n  logLevel: info\n  # -- Log format (`plain` or `json`)\n  logFormat: plain\n  # -- Toggle support for mirroring headless services\n  enableHeadlessServices: false\n  # -- Enables the use of pprof endpoints for the controller\n  enablePprof: false\n  UID: 2103\n  GID: 2103\n  # -- Number of times service mirror updates are allowed to be requeued (retried)\n  retryLimit: 3\n  # -- Resources to assign to the controller.\n  # See `policyController.resources` in the linkerd-control-plane chart for the expected format\n  resources: {}\n  nodeSelector: {}\n  tolerations: {}\n  enablePodAntiAffinity: false\n  nodeAffinity: {}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster-link/.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\nOWNERS\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*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster-link/Chart.yaml",
    "content": "apiVersion: v1\nappVersion: edge-XX.X.X\ndescription: |\n  A helm chart containing the resources to enable mirroring\n  of services from a remote cluster.\n\n  Warning: The purpose of this chart is just to support the `linkerd\n  multicluster link` CLI command, which also produces the\n  `cluster-credentials` secret and the Link CR, which are not found in this\n  chart. Therefore this chart is not a replacement for that command, and\n  shouldn't be used as-is unless you really know what you're doing ;-)\nkubeVersion: \">=1.23.0-0\"\nicon: https://linkerd.io/images/logo-only-200h.png\nname: \"linkerd-multicluster-link\"\nversion: 0.2.0\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster-link/README.md.gotmpl",
    "content": "{{ template \"chart.header\" . }}\n{{ template \"chart.description\" . }}\n\n{{ template \"chart.versionBadge\" . }}\n{{ template \"chart.typeBadge\" . }}\n{{ template \"chart.appVersionBadge\" . }}\n\n{{ template \"chart.homepageLine\" . }}\n\n{{ template \"chart.requirementsSection\" . }}\n\n{{ template \"chart.valuesSection\" . }}\n\n{{ template \"helm-docs.versionFooter\" . }}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster-link/requirements.yaml",
    "content": "dependencies:\n  - name: partials\n    version: 0.1.0\n    repository: file://../../../charts/partials\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster-link/templates/gateway-mirror.yaml",
    "content": "{{ if .Values.gateway.enabled -}}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: probe-gateway-{{.Values.targetClusterName}}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: multicluster\n    mirror.linkerd.io/mirrored-gateway: \"true\"\n    mirror.linkerd.io/cluster-name: {{.Values.targetClusterName}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nspec:\n  ports:\n  - name: mc-probe\n    port: {{.Values.gateway.probe.port}}\n    protocol: TCP\n{{ end -}}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster-link/templates/psp.yaml",
    "content": "{{if .Values.enablePSP -}}\n---\nkind: RoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-multicluster-link-psp-{{.Values.targetClusterName}}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: multicluster\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: psp\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-service-mirror-{{.Values.targetClusterName}}\n  namespace: {{.Release.Namespace}}\n{{ end -}}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster-link/templates/service-mirror.yaml",
    "content": "kind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-service-mirror-access-local-resources-{{.Values.targetClusterName}}\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: {{.Values.targetClusterName}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"delete\", \"update\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n{{- if .Values.enableNamespaceCreation }}\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"create\"]\n{{- end}}\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-service-mirror-access-local-resources-{{.Values.targetClusterName}}\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: {{.Values.targetClusterName}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-service-mirror-access-local-resources-{{.Values.targetClusterName}}\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-service-mirror-{{.Values.targetClusterName}}\n  namespace: {{.Release.Namespace}}\n---\nkind: Role\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-service-mirror-read-remote-creds-{{.Values.targetClusterName}}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: {{.Values.targetClusterName}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"secrets\"]\n    resourceNames: [\"cluster-credentials-{{.Values.targetClusterName}}\"]\n    verbs: [\"list\", \"get\", \"watch\"]\n  - apiGroups: [\"multicluster.linkerd.io\"]\n    resources: [\"links\"]\n    verbs: [\"list\", \"get\", \"watch\"]\n  - apiGroups: [\"multicluster.linkerd.io\"]\n    resources: [\"links/status\"]\n    verbs: [\"update\", \"patch\"]\n  - apiGroups: [\"coordination.k8s.io\"]\n    resources: [\"leases\"]\n    verbs: [\"create\", \"get\", \"update\", \"patch\"]\n---\nkind: RoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-service-mirror-read-remote-creds-{{.Values.targetClusterName}}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: {{.Values.targetClusterName}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: linkerd-service-mirror-read-remote-creds-{{.Values.targetClusterName}}\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-service-mirror-{{.Values.targetClusterName}}\n    namespace: {{.Release.Namespace}}\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-service-mirror-{{.Values.targetClusterName}}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: {{.Values.targetClusterName}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n{{- include \"partials.image-pull-secrets\" .Values.imagePullSecrets }}\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: {{.Values.targetClusterName}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  name: linkerd-service-mirror-{{.Values.targetClusterName}}\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: {{ .Values.replicas }}\n  revisionHistoryLimit: {{.Values.revisionHistoryLimit}}\n  selector:\n    matchLabels:\n      component: linkerd-service-mirror\n      mirror.linkerd.io/cluster-name: {{.Values.targetClusterName}}\n  {{- if .Values.enablePodAntiAffinity }}\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 1\n  {{- end }}\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n      labels:\n        linkerd.io/extension: multicluster\n        component: linkerd-service-mirror\n        mirror.linkerd.io/cluster-name: {{.Values.targetClusterName}}\n        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n    spec:\n    {{- if .Values.enablePodAntiAffinity}}\n    {{- with $tree := deepCopy . }}\n    {{- $_ := set $tree \"component\" .Values.targetClusterName -}}\n    {{- $_ := set $tree \"label\" \"mirror.linkerd.io/cluster-name\" -}}\n    {{- include \"linkerd.affinity\" $tree | nindent 6 }}\n    {{- end }}\n    {{- end }}\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - service-mirror\n        - -log-level={{.Values.logLevel}}\n        - -log-format={{.Values.logFormat}}\n        - -event-requeue-limit={{.Values.serviceMirrorRetryLimit}}\n        - -namespace={{.Release.Namespace}}\n        {{- if .Values.enableHeadlessServices }}\n        - -enable-headless-services\n        {{- end }}\n        {{- if .Values.enableNamespaceCreation }}\n        - -enable-namespace-creation\n        {{- end }}\n        - -enable-pprof={{.Values.enablePprof | default false}}\n        - -probe-service=probe-gateway-{{.Values.targetClusterName}}\n        - {{.Values.targetClusterName}}\n        {{- if or .Values.serviceMirrorAdditionalEnv .Values.serviceMirrorExperimentalEnv }}\n        env:\n        {{- with .Values.serviceMirrorAdditionalEnv }}\n        {{- toYaml . | nindent 8 -}}\n        {{- end }}\n        {{- with .Values.serviceMirrorExperimentalEnv }}\n        {{- toYaml . | nindent 8 -}}\n        {{- end }}\n        {{- end }}\n        image: {{.Values.controllerImage}}:{{.Values.controllerImageVersion}}\n        name: service-mirror\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: {{.Values.serviceMirrorUID}}\n          runAsGroup: {{.Values.serviceMirrorGID}}\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n        ports:\n        - containerPort: 9999\n          name: svcmi-admin\n        {{- with .Values.resources }}\n        resources: {{ toYaml . | nindent 10 }}\n        {{- end }}\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-service-mirror-{{.Values.targetClusterName}}\n      volumes:\n      - {{- include \"partials.volumes.manual-mount-service-account-token\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{- with .Values.nodeSelector }}\n      nodeSelector: {{ toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.tolerations }}\n      tolerations: {{ toYaml . | nindent 6 }}\n      {{- end }}\n{{- if .Values.enablePodAntiAffinity }}\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-service-mirror-{{.Values.targetClusterName}}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    component: linkerd-service-mirror\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      component: linkerd-service-mirror\n      mirror.linkerd.io/cluster-name: {{.Values.targetClusterName}}\n{{- end}}\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster-link/values-ha.yaml",
    "content": "enablePodAntiAffinity: true\nreplicas: 3\n"
  },
  {
    "path": "multicluster/charts/linkerd-multicluster-link/values.yaml",
    "content": "# -- Docker image for the Service mirror component (uses the Linkerd controller\n# image)\ncontrollerImage: cr.l5d.io/linkerd/controller\n# -- Tag for the Service Mirror container Docker image\ncontrollerImageVersion: linkerdVersionValue\n# -- For Private docker registries, authentication is needed.\n#  Registry secrets are applied to the respective service accounts\nimagePullSecrets: []\n# -- Additional annotations to add to all pods\npodAnnotations: {}\n# -- Additional labels to add to all pods\npodLabels: {}\n# -- Labels to apply to all resources\ncommonLabels: {}\n# -- Toggle support for mirroring headless services\nenableHeadlessServices: false\n# -- Toggle support for creating namespaces for mirror services when necessary\nenableNamespaceCreation: false\n# -- Enables Pod Anti Affinity logic to balance the placement of replicas\n# across hosts and zones for High Availability.\n# Enable this only when you have multiple replicas of components.\nenablePodAntiAffinity: false\ngateway:\n  # -- Controls whether link will create a probe service for the gateway\n  enabled: true\n  probe:\n    # -- The port used for liveliness probing\n    port: 4191\n# -- Log level for the Multicluster components\nlogLevel: info\n# -- Log format (`plain` or `json`)\nlogFormat: plain\n# -- Node selectors for the Service mirror pod\nnodeSelector: {}\n# -- Number of service mirror replicas to run\nreplicas: 1\n# -- Resources for the Service mirror container\nresources: {}\n# -- Number of times update from the remote cluster is allowed to be requeued\n# (retried)\nserviceMirrorRetryLimit: 3\n# -- User id under which the Service Mirror shall be ran\nserviceMirrorUID: 2103\n# -- Group id under which the Service Mirror shall be ran\nserviceMirrorGID: 2103\n# -- Name of the target cluster that's going to be linked\ntargetClusterName: \"\"\n# -- Tolerations for the Service mirror pod\ntolerations: {}\n\n# -- Create RoleBindings to associate ServiceAccount of target cluster Service\n# Mirror to the control plane PSP resource. This requires that `enabledPSP` is\n# set to true on the extension and control plane install. Note PSP has been\n# deprecated since k8s v1.21\nenablePSP: false\n\n# -- Specifies the number of old ReplicaSets to retain to allow rollback.\nrevisionHistoryLimit: 10\n"
  },
  {
    "path": "multicluster/charts/templates.go",
    "content": "package charts\n\nimport (\n\t\"embed\"\n)\n\n//go:embed linkerd-multicluster linkerd-multicluster-link\nvar Templates embed.FS\n"
  },
  {
    "path": "multicluster/cmd/allow.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/linkerd/linkerd2/multicluster/charts\"\n\tmccharts \"github.com/linkerd/linkerd2/multicluster/values\"\n\tchartspkg \"github.com/linkerd/linkerd2/pkg/charts\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\t\"github.com/spf13/cobra\"\n\tchartloader \"helm.sh/helm/v3/pkg/chart/loader\"\n\t\"helm.sh/helm/v3/pkg/chartutil\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"sigs.k8s.io/yaml\"\n)\n\ntype (\n\tallowOptions struct {\n\t\tnamespace          string\n\t\tserviceAccountName string\n\t\tignoreCluster      bool\n\t\toutput             string\n\t}\n)\n\nfunc newAllowCommand() *cobra.Command {\n\topts := allowOptions{\n\t\tnamespace:     defaultMulticlusterNamespace,\n\t\tignoreCluster: false,\n\t\toutput:        \"yaml\",\n\t}\n\n\tcmd := &cobra.Command{\n\t\tHidden: false,\n\t\tUse:    \"allow\",\n\t\tShort:  \"Outputs credential resources that allow service-mirror controllers to connect to this cluster\",\n\t\tArgs:   cobra.NoArgs,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\n\t\t\tvalues, err := buildMulticlusterAllowValues(cmd.Context(), &opts)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// Render raw values and create chart config\n\t\t\trawValues, err := yaml.Marshal(values)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfiles := []*chartloader.BufferedFile{\n\t\t\t\t{Name: chartutil.ChartfileName},\n\t\t\t\t{Name: \"templates/namespace.yaml\"},\n\t\t\t\t{Name: \"templates/remote-access-service-mirror-rbac.yaml\"},\n\t\t\t}\n\n\t\t\tchart := &chartspkg.Chart{\n\t\t\t\tName:      mccharts.HelmDefaultChartDir,\n\t\t\t\tDir:       mccharts.HelmDefaultChartDir,\n\t\t\t\tNamespace: opts.namespace,\n\t\t\t\tRawValues: rawValues,\n\t\t\t\tFiles:     files,\n\t\t\t\tFs:        charts.Templates,\n\t\t\t}\n\t\t\tbuf, err := chart.Render()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\treturn pkgcmd.RenderYAMLAs(&buf, stdout, opts.output)\n\t\t},\n\t}\n\n\tcmd.Flags().StringVar(&opts.namespace, \"namespace\", defaultMulticlusterNamespace, \"The destination namespace for the service account.\")\n\tcmd.Flags().BoolVar(&opts.ignoreCluster, \"ignore-cluster\", false, \"Ignore cluster configuration\")\n\tcmd.Flags().StringVar(&opts.serviceAccountName, \"service-account-name\", \"\", \"The name of the multicluster access service account\")\n\tcmd.PersistentFlags().StringVarP(&opts.output, \"output\", \"o\", \"yaml\", \"Output format. One of: json|yaml\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tcmd, []string{\"namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\n\treturn cmd\n}\n\nfunc buildMulticlusterAllowValues(ctx context.Context, opts *allowOptions) (*mccharts.Values, error) {\n\n\tkubeAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif opts.namespace == \"\" {\n\t\treturn nil, errors.New(\"you need to specify a namespace\")\n\t}\n\n\tif opts.serviceAccountName == \"\" {\n\t\treturn nil, errors.New(\"you need to specify a service account name\")\n\t}\n\n\tdefaults, err := mccharts.NewInstallValues()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdefaults.LinkerdVersion = version.Version\n\tdefaults.Gateway.Enabled = false\n\tdefaults.ServiceMirror = false\n\tdefaults.RemoteMirrorServiceAccount = true\n\tdefaults.RemoteMirrorServiceAccountName = opts.serviceAccountName\n\n\tif !opts.ignoreCluster {\n\t\tacc, err := kubeAPI.CoreV1().ServiceAccounts(opts.namespace).Get(ctx, defaults.RemoteMirrorServiceAccountName, metav1.GetOptions{})\n\t\tif err == nil && acc != nil {\n\t\t\treturn nil, fmt.Errorf(\"Service account with name %s already exists, use --ignore-cluster for force operation\", defaults.RemoteMirrorServiceAccountName)\n\t\t}\n\t\tif !kerrors.IsNotFound(err) {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn defaults, nil\n}\n"
  },
  {
    "path": "multicluster/cmd/check.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/x509\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/servicemirror\"\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\t\"github.com/prometheus/common/expfmt\"\n\t\"github.com/prometheus/common/model\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n)\n\nconst (\n\t// MulticlusterExtensionName is the name of the multicluster extension\n\tMulticlusterExtensionName = \"multicluster\"\n\n\t// MulticlusterLegacyExtension is the name of the multicluster extension\n\t// prior to stable-2.10.0 when the linkerd prefix was removed.\n\tMulticlusterLegacyExtension = \"linkerd-multicluster\"\n\n\t// LinkerdMulticlusterExtensionCheck adds checks related to the multicluster extension\n\tLinkerdMulticlusterExtensionCheck healthcheck.CategoryID = \"linkerd-multicluster\"\n\n\tlegacyServiceMirrorComponentName = \"service-mirror\"\n\tserviceMirrorComponentName       = \"controller\"\n)\n\n// For these vars, the second name is for service mirror controllers\n// managed by the linkerd-multicluster chart\nvar (\n\tlinkerdServiceMirrorServiceAccountNames = []string{\"linkerd-service-mirror-%s\", \"controller-%s\"}\n\tlinkerdServiceMirrorComponentNames      = []string{legacyServiceMirrorComponentName, serviceMirrorComponentName}\n\n\tlinkerdServiceMirrorClusterRoleNames = []string{\n\t\t\"linkerd-service-mirror-access-local-resources-%s\",\n\t\t\"linkerd-multicluster-controller-access-local-resources\",\n\t}\n\tlinkerdServiceMirrorRoleNames = []string{\n\t\t\"linkerd-service-mirror-read-remote-creds-%s\",\n\t\t\"controller-read-remote-creds-%s\",\n\t}\n)\n\ntype checkOptions struct {\n\twait    time.Duration\n\toutput  string\n\ttimeout time.Duration\n}\n\nfunc newCheckOptions() *checkOptions {\n\treturn &checkOptions{\n\t\twait:    300 * time.Second,\n\t\toutput:  healthcheck.TableOutput,\n\t\ttimeout: 10 * time.Second,\n\t}\n}\n\nfunc (options *checkOptions) validate() error {\n\tif options.output != healthcheck.TableOutput && options.output != healthcheck.JSONOutput && options.output != healthcheck.ShortOutput {\n\t\treturn fmt.Errorf(\"Invalid output type '%s'. Supported output types are: %s, %s, %s\", options.output, healthcheck.JSONOutput, healthcheck.TableOutput, healthcheck.ShortOutput)\n\t}\n\treturn nil\n}\n\ntype healthChecker struct {\n\t*healthcheck.HealthChecker\n\tlinks []v1alpha3.Link\n}\n\nfunc newHealthChecker(linkerdHC *healthcheck.HealthChecker) *healthChecker {\n\treturn &healthChecker{\n\t\tlinkerdHC,\n\t\t[]v1alpha3.Link{},\n\t}\n}\n\n// NewCmdCheck generates a new cobra command for the multicluster extension.\nfunc NewCmdCheck() *cobra.Command {\n\toptions := newCheckOptions()\n\tcmd := &cobra.Command{\n\t\tUse:   \"check [flags]\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Check the multicluster extension for potential problems\",\n\t\tLong: `Check the multicluster extension for potential problems.\n\nThe check command will perform a series of checks to validate that the\nmulticluster extension is configured correctly. If the command encounters a\nfailure it will print additional information about the failure and exit with a\nnon-zero exit code.`,\n\t\tExample: `  # Check that the multicluster extension is configured correctly\n  linkerd multicluster check`,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\t// Get the multicluster extension namespace\n\t\t\tkubeAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"failed to run multicluster check: %s\\n\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\t_, err = kubeAPI.GetNamespaceWithExtensionLabel(context.Background(), MulticlusterExtensionName)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%s; install by running `linkerd multicluster install | kubectl apply -f -`\\n\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\treturn configureAndRunChecks(stdout, stderr, options)\n\t\t},\n\t}\n\tcmd.Flags().StringVarP(&options.output, \"output\", \"o\", options.output, \"Output format. One of: table, json, short\")\n\tcmd.Flags().DurationVar(&options.wait, \"wait\", options.wait, \"Maximum allowed time for all tests to pass\")\n\tcmd.Flags().DurationVar(&options.timeout, \"timeout\", options.timeout, \"Timeout for calls to the Kubernetes API\")\n\tcmd.Flags().Bool(\"proxy\", false, \"\")\n\tcmd.Flags().MarkHidden(\"proxy\")\n\tcmd.Flags().StringP(\"namespace\", \"n\", \"\", \"\")\n\tcmd.Flags().MarkHidden(\"namespace\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tcmd, []string{\"namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\tpkgcmd.ConfigureOutputFlagCompletion(cmd)\n\n\treturn cmd\n}\n\nfunc configureAndRunChecks(wout io.Writer, werr io.Writer, options *checkOptions) error {\n\terr := options.validate()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Validation error when executing check command: %w\", err)\n\t}\n\tchecks := []healthcheck.CategoryID{\n\t\tLinkerdMulticlusterExtensionCheck,\n\t}\n\tlinkerdHC := healthcheck.NewHealthChecker(checks, &healthcheck.Options{\n\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\tKubeConfig:            kubeconfigPath,\n\t\tKubeContext:           kubeContext,\n\t\tImpersonate:           impersonate,\n\t\tImpersonateGroup:      impersonateGroup,\n\t\tAPIAddr:               apiAddr,\n\t\tRetryDeadline:         time.Now().Add(options.wait),\n\t})\n\n\terr = linkerdHC.InitializeKubeAPIClient()\n\tif err != nil {\n\t\tfmt.Fprintf(werr, \"Error initializing k8s API client: %s\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\terr = linkerdHC.InitializeLinkerdGlobalConfig(context.Background())\n\tif err != nil {\n\t\tfmt.Fprintf(werr, \"Failed to fetch linkerd config: %s\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\thc := newHealthChecker(linkerdHC)\n\tcategory := multiclusterCategory(hc, options.timeout)\n\thc.AppendCategories(category)\n\tsuccess, warning := healthcheck.RunChecks(wout, werr, hc, options.output)\n\thealthcheck.PrintChecksResult(wout, options.output, success, warning)\n\tif !success {\n\t\tos.Exit(1)\n\t}\n\treturn nil\n}\n\nfunc multiclusterCategory(hc *healthChecker, wait time.Duration) *healthcheck.Category {\n\tcheckers := []healthcheck.Checker{}\n\tcheckers = append(checkers,\n\t\t*healthcheck.NewChecker(\"Link CRD exists\").\n\t\t\tWithHintAnchor(\"l5d-multicluster-link-crd-exists\").\n\t\t\tFatal().\n\t\t\tWithCheck(func(ctx context.Context) error { return hc.checkLinkCRD(ctx) }))\n\tcheckers = append(checkers,\n\t\t*healthcheck.NewChecker(\"Link resources are valid\").\n\t\t\tWithHintAnchor(\"l5d-multicluster-links-are-valid\").\n\t\t\tFatal().\n\t\t\tWithCheck(func(ctx context.Context) error { return hc.checkLinks(ctx) }))\n\tcheckers = append(checkers,\n\t\t*healthcheck.NewChecker(\"Link and CLI versions match\").\n\t\t\tWithHintAnchor(\"l5d-multicluster-links-version\").\n\t\t\tWarning().\n\t\t\tWithCheck(func(ctx context.Context) error { return hc.checkLinkVersions() }))\n\tcheckers = append(checkers,\n\t\t*healthcheck.NewChecker(\"remote cluster access credentials are valid\").\n\t\t\tWithHintAnchor(\"l5d-smc-target-clusters-access\").\n\t\t\tWithCheck(func(ctx context.Context) error { return hc.checkRemoteClusterConnectivity(ctx) }))\n\tcheckers = append(checkers,\n\t\t*healthcheck.NewChecker(\"clusters share trust anchors\").\n\t\t\tWithHintAnchor(\"l5d-multicluster-clusters-share-anchors\").\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\tlocalAnchors, err := tls.DecodePEMCertificates(hc.LinkerdConfig().IdentityTrustAnchorsPEM)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"Cannot parse source trust anchors: %w\", err)\n\t\t\t\t}\n\t\t\t\treturn hc.checkRemoteClusterAnchors(ctx, localAnchors)\n\t\t\t}))\n\tcheckers = append(checkers,\n\t\t*healthcheck.NewChecker(\"service mirror controller has required permissions\").\n\t\t\tWithHintAnchor(\"l5d-multicluster-source-rbac-correct\").\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\treturn hc.checkServiceMirrorLocalRBAC(ctx)\n\t\t\t}))\n\tcheckers = append(checkers,\n\t\t*healthcheck.NewChecker(\"service mirror controllers are running\").\n\t\t\tWithHintAnchor(\"l5d-multicluster-service-mirror-running\").\n\t\t\tWithRetryDeadline(hc.RetryDeadline).\n\t\t\tSurfaceErrorOnRetry().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\treturn hc.checkServiceMirrorController(ctx)\n\t\t\t}))\n\tcheckers = append(checkers,\n\t\t*healthcheck.NewChecker(\"extension is managing controllers\").\n\t\t\tWithHintAnchor(\"l5d-multicluster-managed-controllers\").\n\t\t\tWarning().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\treturn hc.checkLegacyController(ctx)\n\t\t\t}))\n\tcheckers = append(checkers,\n\t\t*healthcheck.NewChecker(\"probe services able to communicate with all gateway mirrors\").\n\t\t\tWithHintAnchor(\"l5d-multicluster-gateways-endpoints\").\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\treturn hc.checkIfGatewayMirrorsHaveEndpoints(ctx, wait)\n\t\t\t}))\n\tcheckers = append(checkers,\n\t\t*healthcheck.NewChecker(\"all mirror services have endpoints\").\n\t\t\tWithHintAnchor(\"l5d-multicluster-services-endpoints\").\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\treturn hc.checkIfMirrorServicesHaveEndpoints(ctx)\n\t\t\t}))\n\tcheckers = append(checkers,\n\t\t*healthcheck.NewChecker(\"all mirror services are part of a Link\").\n\t\t\tWithHintAnchor(\"l5d-multicluster-orphaned-services\").\n\t\t\tWarning().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\treturn hc.checkForOrphanedServices(ctx)\n\t\t\t}))\n\n\tcheckers = append(checkers,\n\t\t*healthcheck.NewChecker(\"multicluster extension proxies are healthy\").\n\t\t\tWithHintAnchor(\"l5d-multicluster-proxy-healthy\").\n\t\t\tWarning().\n\t\t\tWithRetryDeadline(hc.RetryDeadline).\n\t\t\tSurfaceErrorOnRetry().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\tfor _, link := range hc.links {\n\t\t\t\t\terr := hc.CheckProxyHealth(ctx, hc.ControlPlaneNamespace, link.Namespace)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}))\n\n\tcheckers = append(checkers,\n\t\t*healthcheck.NewChecker(\"multicluster extension proxies are up-to-date\").\n\t\t\tWithHintAnchor(\"l5d-multicluster-proxy-cp-version\").\n\t\t\tWarning().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\tvar err error\n\t\t\t\tif hc.VersionOverride != \"\" {\n\t\t\t\t\thc.LatestVersions, err = version.NewChannels(hc.VersionOverride)\n\t\t\t\t} else {\n\t\t\t\t\tuuid := \"unknown\"\n\t\t\t\t\tif hc.UUID() != \"\" {\n\t\t\t\t\t\tuuid = hc.UUID()\n\t\t\t\t\t}\n\t\t\t\t\thc.LatestVersions, err = version.GetLatestVersions(ctx, uuid, \"cli\")\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tvar pods []corev1.Pod\n\t\t\t\tfor _, link := range hc.links {\n\t\t\t\t\tnsPods, err := hc.KubeAPIClient().GetPodsByNamespace(ctx, link.Namespace)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\tpods = append(pods, nsPods...)\n\t\t\t\t}\n\n\t\t\t\treturn hc.CheckProxyVersionsUpToDate(pods)\n\t\t\t}))\n\n\tcheckers = append(checkers,\n\t\t*healthcheck.NewChecker(\"multicluster extension proxies and cli versions match\").\n\t\t\tWithHintAnchor(\"l5d-multicluster-proxy-cli-version\").\n\t\t\tWarning().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\tvar pods []corev1.Pod\n\t\t\t\tfor _, link := range hc.links {\n\t\t\t\t\tnsPods, err := hc.KubeAPIClient().GetPodsByNamespace(ctx, link.Namespace)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\tpods = append(pods, nsPods...)\n\t\t\t\t}\n\n\t\t\t\treturn healthcheck.CheckIfProxyVersionsMatchWithCLI(pods)\n\t\t\t}))\n\n\treturn healthcheck.NewCategory(LinkerdMulticlusterExtensionCheck, checkers, true)\n}\n\nfunc (hc *healthChecker) checkLinkCRD(ctx context.Context) error {\n\terr := hc.linkAccess(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"multicluster.linkerd.io/Link CRD is missing: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc (hc *healthChecker) linkAccess(ctx context.Context) error {\n\tres, err := hc.KubeAPIClient().Discovery().ServerResourcesForGroupVersion(k8s.LinkAPIGroupVersion)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif res.GroupVersion == k8s.LinkAPIGroupVersion {\n\t\tfor _, apiRes := range res.APIResources {\n\t\t\tif apiRes.Kind == k8s.LinkKind {\n\t\t\t\treturn k8s.ResourceAuthz(ctx, hc.KubeAPIClient(), \"\", \"list\", k8s.LinkAPIGroup, k8s.LinkAPIVersion, \"links\", \"\")\n\t\t\t}\n\t\t}\n\t}\n\treturn errors.New(\"Link CRD not found\")\n}\n\nfunc (hc *healthChecker) checkLinks(ctx context.Context) error {\n\tlinks, err := hc.KubeAPIClient().L5dCrdClient.LinkV1alpha3().Links(\"\").List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(links.Items) == 0 {\n\t\treturn healthcheck.SkipError{Reason: \"no links detected\"}\n\t}\n\tlinkNames := []string{}\n\tfor _, l := range links.Items {\n\t\tlinkNames = append(linkNames, fmt.Sprintf(\"\\t* %s\", l.Spec.TargetClusterName))\n\t}\n\thc.links = links.Items\n\treturn healthcheck.VerboseSuccess{Message: strings.Join(linkNames, \"\\n\")}\n}\n\nfunc (hc *healthChecker) checkLinkVersions() error {\n\terrors := []error{}\n\tlinks := []string{}\n\tfor _, link := range hc.links {\n\t\tparts := strings.Split(link.Annotations[k8s.CreatedByAnnotation], \" \")\n\t\tif len(parts) == 2 && parts[0] == \"linkerd/cli\" {\n\t\t\tif parts[1] == version.Version {\n\t\t\t\tlinks = append(links, fmt.Sprintf(\"\\t* %s\", link.Spec.TargetClusterName))\n\t\t\t} else {\n\t\t\t\terrors = append(errors, fmt.Errorf(\"* %s: CLI version is %s but Link version is %s\", link.Spec.TargetClusterName, version.Version, parts[1]))\n\t\t\t}\n\t\t} else {\n\t\t\terrors = append(errors, fmt.Errorf(\"* %s: unable to determine version\", link.Spec.TargetClusterName))\n\t\t}\n\t}\n\tif len(errors) > 0 {\n\t\treturn joinErrors(errors, 2)\n\t}\n\tif len(links) == 0 {\n\t\treturn healthcheck.SkipError{Reason: \"no links\"}\n\t}\n\treturn healthcheck.VerboseSuccess{Message: strings.Join(links, \"\\n\")}\n}\n\nfunc (hc *healthChecker) checkRemoteClusterConnectivity(ctx context.Context) error {\n\terrors := []error{}\n\tlinks := []string{}\n\tfor _, link := range hc.links {\n\t\t// Load the credentials secret\n\t\tsecret, err := hc.KubeAPIClient().Interface.CoreV1().Secrets(link.Namespace).Get(ctx, link.Spec.ClusterCredentialsSecret, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\terrors = append(errors, fmt.Errorf(\"* secret: [%s/%s]: %w\", link.Namespace, link.Spec.ClusterCredentialsSecret, err))\n\t\t\tcontinue\n\t\t}\n\t\tconfig, err := servicemirror.ParseRemoteClusterSecret(secret)\n\t\tif err != nil {\n\t\t\terrors = append(errors, fmt.Errorf(\"* secret: [%s/%s]: could not parse config secret: %w\", secret.Namespace, secret.Name, err))\n\t\t\tcontinue\n\t\t}\n\t\tclientConfig, err := clientcmd.RESTConfigFromKubeConfig(config)\n\t\tif err != nil {\n\t\t\terrors = append(errors, fmt.Errorf(\"* secret: [%s/%s] cluster: [%s]: unable to parse api config: %w\", secret.Namespace, secret.Name, link.Spec.TargetClusterName, err))\n\t\t\tcontinue\n\t\t}\n\t\tremoteAPI, err := k8s.NewAPIForConfig(clientConfig, \"\", []string{}, healthcheck.RequestTimeout, 0, 0)\n\t\tif err != nil {\n\t\t\terrors = append(errors, fmt.Errorf(\"* secret: [%s/%s] cluster: [%s]: could not instantiate api for target cluster: %w\", secret.Namespace, secret.Name, link.Spec.TargetClusterName, err))\n\t\t\tcontinue\n\t\t}\n\t\t// We use this call just to check connectivity.\n\t\t_, err = remoteAPI.Discovery().ServerVersion()\n\t\tif err != nil {\n\t\t\terrors = append(errors, fmt.Errorf(\"* failed to connect to API for cluster: [%s]: %w\", link.Spec.TargetClusterName, err))\n\t\t\tcontinue\n\t\t}\n\t\tverbs := []string{\"get\", \"list\", \"watch\"}\n\t\tfor _, verb := range verbs {\n\t\t\tif err := healthcheck.CheckCanPerformAction(ctx, remoteAPI, verb, corev1.NamespaceAll, \"\", \"v1\", \"services\"); err != nil {\n\t\t\t\terrors = append(errors, fmt.Errorf(\"* missing service permission [%s] for cluster [%s]: %w\", verb, link.Spec.TargetClusterName, err))\n\t\t\t}\n\t\t}\n\t\tlinks = append(links, fmt.Sprintf(\"\\t* %s\", link.Spec.TargetClusterName))\n\t}\n\tif len(errors) > 0 {\n\t\treturn joinErrors(errors, 2)\n\t}\n\tif len(links) == 0 {\n\t\treturn healthcheck.SkipError{Reason: \"no links\"}\n\t}\n\treturn healthcheck.VerboseSuccess{Message: strings.Join(links, \"\\n\")}\n}\n\nfunc (hc *healthChecker) checkRemoteClusterAnchors(ctx context.Context, localAnchors []*x509.Certificate) error {\n\terrors := []string{}\n\tlinks := []string{}\n\tfor _, link := range hc.links {\n\t\t// Load the credentials secret\n\t\tsecret, err := hc.KubeAPIClient().Interface.CoreV1().Secrets(link.Namespace).Get(ctx, link.Spec.ClusterCredentialsSecret, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\terrors = append(errors, fmt.Sprintf(\"* secret: [%s/%s]: %s\", link.Namespace, link.Spec.ClusterCredentialsSecret, err))\n\t\t\tcontinue\n\t\t}\n\t\tconfig, err := servicemirror.ParseRemoteClusterSecret(secret)\n\t\tif err != nil {\n\t\t\terrors = append(errors, fmt.Sprintf(\"* secret: [%s/%s]: could not parse config secret: %s\", secret.Namespace, secret.Name, err))\n\t\t\tcontinue\n\t\t}\n\t\tclientConfig, err := clientcmd.RESTConfigFromKubeConfig(config)\n\t\tif err != nil {\n\t\t\terrors = append(errors, fmt.Sprintf(\"* secret: [%s/%s] cluster: [%s]: unable to parse api config: %s\", secret.Namespace, secret.Name, link.Spec.TargetClusterName, err))\n\t\t\tcontinue\n\t\t}\n\t\tremoteAPI, err := k8s.NewAPIForConfig(clientConfig, \"\", []string{}, healthcheck.RequestTimeout, 0, 0)\n\t\tif err != nil {\n\t\t\terrors = append(errors, fmt.Sprintf(\"* secret: [%s/%s] cluster: [%s]: could not instantiate api for target cluster: %s\", secret.Namespace, secret.Name, link.Spec.TargetClusterName, err))\n\t\t\tcontinue\n\t\t}\n\t\t_, values, err := healthcheck.FetchCurrentConfiguration(ctx, remoteAPI, link.Spec.TargetClusterLinkerdNamespace)\n\t\tif err != nil {\n\t\t\terrors = append(errors, fmt.Sprintf(\"* %s: unable to fetch anchors: %s\", link.Spec.TargetClusterName, err))\n\t\t\tcontinue\n\t\t}\n\t\tremoteAnchors, err := tls.DecodePEMCertificates(values.IdentityTrustAnchorsPEM)\n\t\tif err != nil {\n\t\t\terrors = append(errors, fmt.Sprintf(\"* %s: cannot parse trust anchors\", link.Spec.TargetClusterName))\n\t\t\tcontinue\n\t\t}\n\t\t// we fail early if the lens are not the same. If they are the\n\t\t// same, we can only compare certs one way and be sure we have\n\t\t// identical anchors\n\t\tif len(remoteAnchors) != len(localAnchors) {\n\t\t\terrors = append(errors, fmt.Sprintf(\"* %s\", link.Spec.TargetClusterName))\n\t\t\tcontinue\n\t\t}\n\t\tlocalAnchorsMap := make(map[string]*x509.Certificate)\n\t\tfor _, c := range localAnchors {\n\t\t\tlocalAnchorsMap[string(c.Signature)] = c\n\t\t}\n\t\tfor _, remote := range remoteAnchors {\n\t\t\tlocal, ok := localAnchorsMap[string(remote.Signature)]\n\t\t\tif !ok || !local.Equal(remote) {\n\t\t\t\terrors = append(errors, fmt.Sprintf(\"* %s\", link.Spec.TargetClusterName))\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tlinks = append(links, fmt.Sprintf(\"\\t* %s\", link.Spec.TargetClusterName))\n\t}\n\tif len(errors) > 0 {\n\t\treturn fmt.Errorf(\"Problematic clusters:\\n    %s\", strings.Join(errors, \"\\n    \"))\n\t}\n\tif len(links) == 0 {\n\t\treturn healthcheck.SkipError{Reason: \"no links\"}\n\t}\n\treturn healthcheck.VerboseSuccess{Message: strings.Join(links, \"\\n\")}\n}\n\nfunc (hc *healthChecker) checkServiceMirrorLocalRBAC(ctx context.Context) error {\n\tlinks := []string{}\n\tmessages := []string{}\n\tfor _, link := range hc.links {\n\t\terr := healthcheck.CheckServiceAccounts(\n\t\t\tctx,\n\t\t\thc.KubeAPIClient(),\n\t\t\t[]string{fmt.Sprintf(linkerdServiceMirrorServiceAccountNames[0], link.Spec.TargetClusterName)},\n\t\t\tlink.Namespace,\n\t\t\tserviceMirrorComponentsSelector(link.Spec.TargetClusterName),\n\t\t)\n\t\tif err != nil {\n\t\t\terr2 := healthcheck.CheckServiceAccounts(\n\t\t\t\tctx,\n\t\t\t\thc.KubeAPIClient(),\n\t\t\t\t[]string{fmt.Sprintf(linkerdServiceMirrorServiceAccountNames[1], link.Spec.TargetClusterName)},\n\t\t\t\tlink.Namespace,\n\t\t\t\tserviceMirrorComponentsSelector(link.Spec.TargetClusterName),\n\t\t\t)\n\t\t\tif err2 != nil {\n\t\t\t\tmessages = append(messages, err.Error(), err2.Error())\n\t\t\t}\n\t\t}\n\t\terr = healthcheck.CheckClusterRoles(\n\t\t\tctx,\n\t\t\thc.KubeAPIClient(),\n\t\t\ttrue,\n\t\t\t[]string{fmt.Sprintf(linkerdServiceMirrorClusterRoleNames[0], link.Spec.TargetClusterName)},\n\t\t\tserviceMirrorComponentsSelector(link.Spec.TargetClusterName),\n\t\t)\n\t\tif err != nil {\n\t\t\terr2 := healthcheck.CheckClusterRoles(\n\t\t\t\tctx,\n\t\t\t\thc.KubeAPIClient(),\n\t\t\t\ttrue,\n\t\t\t\t[]string{linkerdServiceMirrorClusterRoleNames[1]},\n\t\t\t\t\"component=controller\",\n\t\t\t)\n\t\t\tif err2 != nil {\n\t\t\t\tmessages = append(messages, err.Error(), err2.Error())\n\t\t\t}\n\t\t}\n\t\terr = healthcheck.CheckClusterRoleBindings(\n\t\t\tctx,\n\t\t\thc.KubeAPIClient(),\n\t\t\ttrue,\n\t\t\t[]string{fmt.Sprintf(linkerdServiceMirrorClusterRoleNames[0], link.Spec.TargetClusterName)},\n\t\t\tserviceMirrorComponentsSelector(link.Spec.TargetClusterName),\n\t\t)\n\t\tif err != nil {\n\t\t\terr2 := healthcheck.CheckClusterRoleBindings(\n\t\t\t\tctx,\n\t\t\t\thc.KubeAPIClient(),\n\t\t\t\ttrue,\n\t\t\t\t[]string{fmt.Sprintf(\"%s-%s\", linkerdServiceMirrorClusterRoleNames[1], link.Spec.TargetClusterName)},\n\t\t\t\tserviceMirrorComponentsSelector(link.Spec.TargetClusterName),\n\t\t\t)\n\t\t\tif err2 != nil {\n\t\t\t\tmessages = append(messages, err.Error(), err2.Error())\n\t\t\t}\n\t\t}\n\t\terr = healthcheck.CheckRoles(\n\t\t\tctx,\n\t\t\thc.KubeAPIClient(),\n\t\t\ttrue,\n\t\t\tlink.Namespace,\n\t\t\t[]string{fmt.Sprintf(linkerdServiceMirrorRoleNames[0], link.Spec.TargetClusterName)},\n\t\t\tserviceMirrorComponentsSelector(link.Spec.TargetClusterName),\n\t\t)\n\t\tif err != nil {\n\t\t\terr2 := healthcheck.CheckRoles(\n\t\t\t\tctx,\n\t\t\t\thc.KubeAPIClient(),\n\t\t\t\ttrue,\n\t\t\t\tlink.Namespace,\n\t\t\t\t[]string{fmt.Sprintf(linkerdServiceMirrorRoleNames[1], link.Spec.TargetClusterName)},\n\t\t\t\tserviceMirrorComponentsSelector(link.Spec.TargetClusterName),\n\t\t\t)\n\t\t\tif err2 != nil {\n\t\t\t\tmessages = append(messages, err.Error(), err2.Error())\n\t\t\t}\n\t\t}\n\t\terr = healthcheck.CheckRoleBindings(\n\t\t\tctx,\n\t\t\thc.KubeAPIClient(),\n\t\t\ttrue,\n\t\t\tlink.Namespace,\n\t\t\t[]string{fmt.Sprintf(linkerdServiceMirrorRoleNames[0], link.Spec.TargetClusterName)},\n\t\t\tserviceMirrorComponentsSelector(link.Spec.TargetClusterName),\n\t\t)\n\t\tif err != nil {\n\t\t\terr2 := healthcheck.CheckRoleBindings(\n\t\t\t\tctx,\n\t\t\t\thc.KubeAPIClient(),\n\t\t\t\ttrue,\n\t\t\t\tlink.Namespace,\n\t\t\t\t[]string{fmt.Sprintf(linkerdServiceMirrorRoleNames[1], link.Spec.TargetClusterName)},\n\t\t\t\tserviceMirrorComponentsSelector(link.Spec.TargetClusterName),\n\t\t\t)\n\t\t\tif err2 != nil {\n\t\t\t\tmessages = append(messages, err.Error(), err2.Error())\n\t\t\t}\n\t\t}\n\t\tlinks = append(links, fmt.Sprintf(\"\\t* %s\", link.Spec.TargetClusterName))\n\t}\n\tif len(messages) > 0 {\n\t\treturn errors.New(strings.Join(messages, \"\\n\"))\n\t}\n\tif len(links) == 0 {\n\t\treturn healthcheck.SkipError{Reason: \"no links\"}\n\t}\n\treturn healthcheck.VerboseSuccess{Message: strings.Join(links, \"\\n\")}\n}\n\nfunc (hc *healthChecker) checkServiceMirrorController(ctx context.Context) error {\n\terrors := []error{}\n\tclusterNames := []string{}\n\tfor _, link := range hc.links {\n\t\toptions := metav1.ListOptions{\n\t\t\tLabelSelector: serviceMirrorComponentsSelector(link.Spec.TargetClusterName),\n\t\t}\n\t\tresult, err := hc.KubeAPIClient().AppsV1().Deployments(corev1.NamespaceAll).List(ctx, options)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(result.Items) > 1 {\n\t\t\terrors = append(errors, fmt.Errorf(\"* too many service mirror controller deployments for Link %s\", link.Spec.TargetClusterName))\n\t\t\tcontinue\n\t\t}\n\t\tif len(result.Items) == 0 {\n\t\t\terrors = append(errors, fmt.Errorf(\"* no service mirror controller deployment for Link %s\", link.Spec.TargetClusterName))\n\t\t\tcontinue\n\t\t}\n\t\tcontroller := result.Items[0]\n\t\tif controller.Status.AvailableReplicas < 1 {\n\t\t\terrors = append(errors, fmt.Errorf(\"* service mirror controller is not available: %s/%s\", controller.Namespace, controller.Name))\n\t\t\tcontinue\n\t\t}\n\t\tclusterNames = append(clusterNames, fmt.Sprintf(\"\\t* %s\", link.Spec.TargetClusterName))\n\t}\n\tif len(errors) > 0 {\n\t\treturn joinErrors(errors, 2)\n\t}\n\tif len(clusterNames) == 0 {\n\t\treturn healthcheck.SkipError{Reason: \"no links\"}\n\t}\n\treturn healthcheck.VerboseSuccess{Message: strings.Join(clusterNames, \"\\n\")}\n}\n\nfunc (hc *healthChecker) checkLegacyController(ctx context.Context) error {\n\terrors := []error{}\n\tclusterNames := []string{}\n\tfor _, link := range hc.links {\n\t\toptions := metav1.ListOptions{\n\t\t\tLabelSelector: serviceMirrorComponentsSelector(link.Spec.TargetClusterName),\n\t\t}\n\t\tresult, err := hc.KubeAPIClient().AppsV1().Deployments(corev1.NamespaceAll).List(ctx, options)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(result.Items) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tcontroller := result.Items[0]\n\t\tif controller.GetLabels()[\"component\"] == legacyServiceMirrorComponentName {\n\t\t\terrors = append(errors, fmt.Errorf(\"* using legacy service mirror controller for Link: %s\", link.Spec.TargetClusterName))\n\t\t\tcontinue\n\t\t}\n\t\tclusterNames = append(clusterNames, fmt.Sprintf(\"\\t* %s\", link.Spec.TargetClusterName))\n\t}\n\tif len(errors) > 0 {\n\t\treturn joinErrors(errors, 2)\n\t}\n\tif len(clusterNames) == 0 {\n\t\treturn healthcheck.SkipError{Reason: \"no links\"}\n\t}\n\treturn healthcheck.VerboseSuccess{Message: strings.Join(clusterNames, \"\\n\")}\n}\n\nfunc (hc *healthChecker) checkIfGatewayMirrorsHaveEndpoints(ctx context.Context, wait time.Duration) error {\n\tmulticlusterNs, err := hc.KubeAPIClient().GetNamespaceWithExtensionLabel(ctx, MulticlusterExtensionName)\n\tif err != nil {\n\t\treturn healthcheck.SkipError{Reason: fmt.Sprintf(\"failed to find the linkerd-multicluster namespace: %s\", err)}\n\t}\n\n\tlinks := []string{}\n\terrors := []error{}\n\tfor _, link := range hc.links {\n\t\t// When linked against a cluster without a gateway, there will be no\n\t\t// gateway address and no probe spec initialised. In such cases, skip\n\t\t// the check\n\t\tif link.Spec.GatewayAddress == \"\" || link.Spec.ProbeSpec.Path == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Check that each gateway probe service has endpoints.\n\t\tselector := metav1.ListOptions{LabelSelector: fmt.Sprintf(\"%s,%s=%s\", k8s.MirroredGatewayLabel, k8s.RemoteClusterNameLabel, link.Spec.TargetClusterName)}\n\t\tgatewayMirrors, err := hc.KubeAPIClient().CoreV1().Services(metav1.NamespaceAll).List(ctx, selector)\n\t\tif err != nil {\n\t\t\terrors = append(errors, err)\n\t\t\tcontinue\n\t\t}\n\t\tif len(gatewayMirrors.Items) != 1 {\n\t\t\terrors = append(errors, fmt.Errorf(\"wrong number (%d) of probe gateways for target cluster %s\", len(gatewayMirrors.Items), link.Spec.TargetClusterName))\n\t\t\tcontinue\n\t\t}\n\t\tsvc := gatewayMirrors.Items[0]\n\t\tendpoints, err := hc.KubeAPIClient().CoreV1().Endpoints(svc.Namespace).Get(ctx, svc.Name, metav1.GetOptions{})\n\t\tif err != nil || len(endpoints.Subsets) == 0 {\n\t\t\terrors = append(errors, fmt.Errorf(\"%s.%s mirrored from cluster [%s] has no endpoints\", svc.Name, svc.Namespace, svc.Labels[k8s.RemoteClusterNameLabel]))\n\t\t\tcontinue\n\t\t}\n\n\t\t// Get the service mirror component in the linkerd-multicluster\n\t\t// namespace which corresponds to the current link.\n\t\tselector = metav1.ListOptions{LabelSelector: fmt.Sprintf(\"component in(linkerd-service-mirror, controller),mirror.linkerd.io/cluster-name=%s\", link.Spec.TargetClusterName)}\n\t\tpods, err := hc.KubeAPIClient().CoreV1().Pods(multiclusterNs.Name).List(ctx, selector)\n\t\tif err != nil {\n\t\t\terrors = append(errors, fmt.Errorf(\"failed to get the service-mirror component for target cluster %s: %w\", link.Spec.TargetClusterName, err))\n\t\t\tcontinue\n\t\t}\n\n\t\tlease, err := hc.KubeAPIClient().CoordinationV1().Leases(multiclusterNs.Name).Get(ctx, fmt.Sprintf(\"service-mirror-write-%s\", link.Spec.TargetClusterName), metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\terrors = append(errors, fmt.Errorf(\"failed to get the service-mirror component Lease for target cluster %s: %w\", link.Spec.TargetClusterName, err))\n\t\t\tcontinue\n\t\t}\n\n\t\t// Build a simple lookup table to retrieve Lease object claimant.\n\t\t// Metrics should only be pulled from claimants as they are the ones\n\t\t// running probes.\n\t\tleaders := make(map[string]struct{})\n\t\tleaders[*lease.Spec.HolderIdentity] = struct{}{}\n\n\t\t// Get and parse the gateway metrics so that we can extract liveness\n\t\t// information.\n\t\tgatewayMetrics, err := getGatewayMetrics(hc.KubeAPIClient(), pods.Items, leaders, wait)\n\t\tif err != nil {\n\t\t\terrors = append(errors, fmt.Errorf(\"failed to get gateway metrics for target cluster %s: %w\", link.Spec.TargetClusterName, err))\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(gatewayMetrics) != 1 {\n\t\t\terrors = append(errors, fmt.Errorf(\"expected exactly one gateway metric for target cluster %s; got %d\", link.Spec.TargetClusterName, len(gatewayMetrics)))\n\t\t\tcontinue\n\t\t}\n\n\t\tgatewayMetric := gatewayMetrics[0]\n\t\tif gatewayMetric.err != nil {\n\t\t\terrors = append(errors, fmt.Errorf(\"Failed to get gateway status for %s: %w\\n\", gatewayMetric.clusterName, gatewayMetric.err))\n\t\t\tcontinue\n\t\t}\n\n\t\tmetricsParser := expfmt.NewTextParser(model.LegacyValidation)\n\t\tparsedMetrics, err := metricsParser.TextToMetricFamilies(bytes.NewReader(gatewayMetric.metrics))\n\t\tif err != nil {\n\t\t\terrors = append(errors, fmt.Errorf(\"failed to parse gateway metrics for target cluster %s: %w\", link.Spec.TargetClusterName, err))\n\t\t\tcontinue\n\t\t}\n\n\t\t// Ensure the gateway for the current link is alive.\n\t\tfor _, metrics := range parsedMetrics[\"gateway_alive\"].GetMetric() {\n\t\t\tif !isTargetClusterMetric(metrics, link.Spec.TargetClusterName) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif metrics.GetGauge().GetValue() != 1 {\n\t\t\t\terr = fmt.Errorf(\"liveness checks failed for %s\", link.Spec.TargetClusterName)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\terrors = append(errors, err)\n\t\t\tcontinue\n\t\t}\n\t\tlinks = append(links, fmt.Sprintf(\"\\t* %s\", link.Spec.TargetClusterName))\n\t}\n\tif len(errors) > 0 {\n\t\treturn joinErrors(errors, 1)\n\t}\n\tif len(links) == 0 {\n\t\treturn healthcheck.SkipError{Reason: \"no links\"}\n\t}\n\treturn healthcheck.VerboseSuccess{Message: strings.Join(links, \"\\n\")}\n}\n\nfunc (hc *healthChecker) checkIfMirrorServicesHaveEndpoints(ctx context.Context) error {\n\tvar servicesWithNoEndpoints []string\n\tselector := fmt.Sprintf(\"%s, !%s, !%s\", k8s.MirroredResourceLabel, k8s.MirroredGatewayLabel, k8s.RemoteDiscoveryLabel)\n\tmirrorServices, err := hc.KubeAPIClient().CoreV1().Services(metav1.NamespaceAll).List(ctx, metav1.ListOptions{LabelSelector: selector})\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, svc := range mirrorServices.Items {\n\t\tif svc.Annotations[k8s.RemoteDiscoveryAnnotation] != \"\" || svc.Annotations[k8s.LocalDiscoveryAnnotation] != \"\" {\n\t\t\t// This is a federated service and does not need to have endpoints.\n\t\t\tcontinue\n\t\t}\n\t\t// have to use a new ctx for each call, otherwise we risk reaching the original context deadline\n\t\tctx, cancel := context.WithTimeout(context.Background(), healthcheck.RequestTimeout)\n\t\tdefer cancel()\n\t\tendpoint, err := hc.KubeAPIClient().CoreV1().Endpoints(svc.Namespace).Get(ctx, svc.Name, metav1.GetOptions{})\n\t\tif err != nil || len(endpoint.Subsets) == 0 {\n\t\t\tlog.Debugf(\"error retrieving Endpoints: %s\", err)\n\t\t\tservicesWithNoEndpoints = append(servicesWithNoEndpoints, fmt.Sprintf(\"%s.%s mirrored from cluster [%s]\", svc.Name, svc.Namespace, svc.Labels[k8s.RemoteClusterNameLabel]))\n\t\t}\n\t}\n\tif len(servicesWithNoEndpoints) > 0 {\n\t\treturn fmt.Errorf(\"Some mirror services do not have endpoints:\\n    %s\", strings.Join(servicesWithNoEndpoints, \"\\n    \"))\n\t}\n\tif len(mirrorServices.Items) == 0 {\n\t\treturn healthcheck.SkipError{Reason: \"no mirror services\"}\n\t}\n\treturn nil\n}\n\nfunc (hc *healthChecker) checkForOrphanedServices(ctx context.Context) error {\n\terrors := []error{}\n\tselector := fmt.Sprintf(\"%s, !%s, %s\", k8s.MirroredResourceLabel, k8s.MirroredGatewayLabel, k8s.RemoteClusterNameLabel)\n\tmirrorServices, err := hc.KubeAPIClient().CoreV1().Services(metav1.NamespaceAll).List(ctx, metav1.ListOptions{LabelSelector: selector})\n\tif err != nil {\n\t\treturn err\n\t}\n\tlinks, err := hc.KubeAPIClient().L5dCrdClient.LinkV1alpha3().Links(\"\").List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, svc := range mirrorServices.Items {\n\t\ttargetCluster := svc.Labels[k8s.RemoteClusterNameLabel]\n\t\thasLink := false\n\t\tfor _, link := range links.Items {\n\t\t\tif link.Spec.TargetClusterName == targetCluster {\n\t\t\t\thasLink = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !hasLink {\n\t\t\terrors = append(errors, fmt.Errorf(\"mirror service %s.%s is not part of any Link\", svc.Name, svc.Namespace))\n\t\t}\n\t}\n\tif len(mirrorServices.Items) == 0 {\n\t\treturn healthcheck.SkipError{Reason: \"no mirror services\"}\n\t}\n\tif len(errors) > 0 {\n\t\treturn joinErrors(errors, 1)\n\t}\n\treturn nil\n}\n\nfunc joinErrors(errs []error, tabDepth int) error {\n\tindent := strings.Repeat(\"    \", tabDepth)\n\terrStrings := []string{}\n\tfor _, err := range errs {\n\t\terrStrings = append(errStrings, indent+err.Error())\n\t}\n\treturn errors.New(strings.Join(errStrings, \"\\n\"))\n}\n\nfunc serviceMirrorComponentsSelector(targetCluster string) string {\n\treturn fmt.Sprintf(\"component in (%s),%s=%s\",\n\t\tstrings.Join(linkerdServiceMirrorComponentNames, \", \"),\n\t\tk8s.RemoteClusterNameLabel, targetCluster)\n}\n"
  },
  {
    "path": "multicluster/cmd/gateways.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/cli/table\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tio_prometheus_client \"github.com/prometheus/client_model/go\"\n\t\"github.com/prometheus/common/expfmt\"\n\t\"github.com/prometheus/common/model\"\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\ntype (\n\tgatewaysOptions struct {\n\t\tclusterName string\n\t\toutput      string\n\t\twait        time.Duration\n\t}\n\n\tgatewayMetrics struct {\n\t\tclusterName string\n\t\tmetrics     []byte\n\t\terr         error\n\t}\n\n\tgatewayStatus struct {\n\t\tClusterName      string `json:\"clusterName\"`\n\t\tAlive            bool   `json:\"alive\"`\n\t\tNumberOfServices int    `json:\"numberOfServices\"`\n\t\tLatency          uint64 `json:\"latency\"`\n\t}\n)\n\nfunc newGatewaysOptions() *gatewaysOptions {\n\treturn &gatewaysOptions{\n\t\twait: 30 * time.Second,\n\t}\n}\n\nfunc newGatewaysCommand() *cobra.Command {\n\n\topts := newGatewaysOptions()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"gateways\",\n\t\tShort: \"Display stats information about the gateways in target clusters\",\n\t\tArgs:  cobra.NoArgs,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// Get all the service mirror components in the\n\t\t\t// linkerd-multicluster namespace which we'll collect gateway\n\t\t\t// metrics from.\n\t\t\tmulticlusterNs, err := k8sAPI.GetNamespaceWithExtensionLabel(cmd.Context(), MulticlusterExtensionName)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"make sure the linkerd-multicluster extension is installed, using 'linkerd multicluster install' (%w)\", err)\n\t\t\t}\n\t\t\tselector := \"component in (linkerd-service-mirror, controller)\"\n\t\t\tif opts.clusterName != \"\" {\n\t\t\t\tselector = fmt.Sprintf(\"%s,mirror.linkerd.io/cluster-name=%s\", selector, opts.clusterName)\n\t\t\t}\n\t\t\tpods, err := k8sAPI.CoreV1().Pods(multiclusterNs.Name).List(cmd.Context(), metav1.ListOptions{LabelSelector: selector})\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"failed to list pods in namespace %s: %s\", multiclusterNs.Name, err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\tleases, err := k8sAPI.CoordinationV1().Leases(multiclusterNs.Name).List(cmd.Context(), metav1.ListOptions{})\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"failed to list pods in namespace %s: %s\", multiclusterNs.Name, err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\t// Build a simple lookup table to retrieve Lease object claimants.\n\t\t\t// Metrics should only be pulled from claimants as they are the ones\n\t\t\t// running probes.\n\t\t\tleaders := make(map[string]struct{})\n\t\t\tfor _, lease := range leases.Items {\n\t\t\t\t// If the Lease is not used by the service-mirror, or if it does\n\t\t\t\t// not have a claimant, then ignore it\n\t\t\t\tif !strings.Contains(lease.Name, \"service-mirror-write\") || lease.Spec.HolderIdentity == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tleaders[*lease.Spec.HolderIdentity] = struct{}{}\n\t\t\t}\n\n\t\t\tvar statuses []gatewayStatus\n\t\t\tgatewayMetrics, err := getGatewayMetrics(k8sAPI, pods.Items, leaders, opts.wait)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"Failed to get gateway metrics for cluster %s: %s\\n\", opts.clusterName, err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\tfor _, gateway := range gatewayMetrics {\n\t\t\t\tif gateway.err != nil {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"Failed to get gateway status for %s: %s\\n\", gateway.clusterName, gateway.err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tgatewayStatus := gatewayStatus{\n\t\t\t\t\tClusterName: gateway.clusterName,\n\t\t\t\t}\n\n\t\t\t\t// Parse the gateway metrics so that we can extract liveness\n\t\t\t\t// and latency information.\n\t\t\t\tmetricsParser := expfmt.NewTextParser(model.LegacyValidation)\n\t\t\t\tparsedMetrics, err := metricsParser.TextToMetricFamilies(bytes.NewReader(gateway.metrics))\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"Failed to parse metrics for %s: %s\\n\", gateway.clusterName, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tskipGatewayMetrics := false\n\t\t\t\tfor _, metrics := range parsedMetrics[\"gateway_enabled\"].GetMetric() {\n\t\t\t\t\tif !isTargetClusterMetric(metrics, gateway.clusterName) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tif metrics.GetGauge().GetValue() != 1 {\n\t\t\t\t\t\tskipGatewayMetrics = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif skipGatewayMetrics {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// Check if the gateway is alive by using the gateway_alive\n\t\t\t\t// metric and ensuring the label matches the target cluster.\n\t\t\t\tfor _, metrics := range parsedMetrics[\"gateway_alive\"].GetMetric() {\n\t\t\t\t\tif !isTargetClusterMetric(metrics, gateway.clusterName) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif metrics.GetGauge().GetValue() == 1 {\n\t\t\t\t\t\tgatewayStatus.Alive = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Search the local cluster for mirror services that are\n\t\t\t\t// mirrored from the target cluster.\n\t\t\t\tselector := fmt.Sprintf(\"%s=%s,%s=%s\",\n\t\t\t\t\tk8s.MirroredResourceLabel, \"true\",\n\t\t\t\t\tk8s.RemoteClusterNameLabel, gateway.clusterName,\n\t\t\t\t)\n\t\t\t\tservices, err := k8sAPI.CoreV1().Services(corev1.NamespaceAll).List(context.Background(), metav1.ListOptions{LabelSelector: selector})\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"Failed to list services for %s: %s\\n\", gateway.clusterName, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tgatewayStatus.NumberOfServices = len(services.Items)\n\n\t\t\t\t// Check the last observed latency by using the\n\t\t\t\t// gateway_latency metric and ensuring the label the target\n\t\t\t\t// cluster.\n\t\t\t\tfor _, metrics := range parsedMetrics[\"gateway_latency\"].GetMetric() {\n\t\t\t\t\tif !isTargetClusterMetric(metrics, gateway.clusterName) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tgatewayStatus.Latency = uint64(metrics.GetGauge().GetValue())\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tstatuses = append(statuses, gatewayStatus)\n\t\t\t}\n\n\t\t\tswitch opts.output {\n\t\t\tcase \"json\":\n\t\t\t\tout, err := json.MarshalIndent(statuses, \"\", \"  \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Fprint(os.Stderr, err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t\tfmt.Printf(\"%s\\n\", out)\n\t\t\tdefault:\n\t\t\t\trenderGateways(statuses, stdout)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcmd.Flags().StringVar(&opts.clusterName, \"cluster-name\", \"\", \"the name of the target cluster\")\n\tcmd.Flags().DurationVarP(&opts.wait, \"wait\", \"w\", opts.wait, \"time allowed to fetch diagnostics\")\n\tcmd.Flags().StringVarP(&opts.output, \"output\", \"o\", \"\", \"used to print output in different format\")\n\n\treturn cmd\n}\n\nfunc getGatewayMetrics(k8sAPI *k8s.KubernetesAPI, pods []corev1.Pod, leaders map[string]struct{}, wait time.Duration) ([]gatewayMetrics, error) {\n\tvar metrics []gatewayMetrics\n\tmetricsChan := make(chan gatewayMetrics)\n\tvar wg sync.WaitGroup\n\tfor _, pod := range pods {\n\t\tif _, found := leaders[pod.Name]; !found {\n\t\t\tcontinue\n\t\t}\n\n\t\twg.Add(1)\n\t\tgo func(p corev1.Pod) {\n\t\t\tdefer wg.Done()\n\t\t\tname := p.Labels[k8s.RemoteClusterNameLabel]\n\t\t\tcontainer, err := getServiceMirrorContainer(p)\n\t\t\tif err != nil {\n\t\t\t\tmetricsChan <- gatewayMetrics{\n\t\t\t\t\tclusterName: name,\n\t\t\t\t\terr:         err,\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tportName := k8s.AdminHTTPPortNameSuffix\n\t\t\tfor _, cp := range container.Ports {\n\t\t\t\tif strings.HasSuffix(cp.Name, k8s.AdminHTTPPortNameSuffix) {\n\t\t\t\t\tportName = cp.Name\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tmetrics, err := k8s.GetContainerMetrics(k8sAPI, p, container, false, portName)\n\t\t\tmetricsChan <- gatewayMetrics{\n\t\t\t\tclusterName: name,\n\t\t\t\tmetrics:     metrics,\n\t\t\t\terr:         err,\n\t\t\t}\n\t\t}(pod)\n\t}\n\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(metricsChan)\n\t}()\n\n\ttimeout := time.NewTimer(wait)\n\tdefer timeout.Stop()\n\nwait:\n\tfor {\n\t\tselect {\n\t\tcase metric := <-metricsChan:\n\t\t\tif metric.clusterName == \"\" {\n\t\t\t\t// channel closed\n\t\t\t\tbreak wait\n\t\t\t}\n\t\t\tmetrics = append(metrics, metric)\n\t\tcase <-timeout.C:\n\t\t\treturn nil, fmt.Errorf(\"timed out waiting for metrics\")\n\t\t}\n\t}\n\n\treturn metrics, nil\n}\n\nfunc getServiceMirrorContainer(pod corev1.Pod) (corev1.Container, error) {\n\tif pod.Status.Phase != corev1.PodRunning {\n\t\treturn corev1.Container{}, fmt.Errorf(\"pod not running: %s\", pod.GetName())\n\t}\n\tfor _, c := range pod.Spec.Containers {\n\t\t// \"controller\" is for the service mirror controllers managed by the\n\t\t// linkerd-multicluster chart\n\t\tif c.Name == \"service-mirror\" || c.Name == \"controller\" {\n\t\t\treturn c, nil\n\t\t}\n\t}\n\treturn corev1.Container{}, fmt.Errorf(\"pod %s did not have a 'service-mirror' nor a 'controller' container\", pod.GetName())\n}\n\nfunc isTargetClusterMetric(metric *io_prometheus_client.Metric, targetClusterName string) bool {\n\tfor _, label := range metric.GetLabel() {\n\t\tif label.GetName() == \"target_cluster_name\" {\n\t\t\treturn label.GetValue() == targetClusterName\n\t\t}\n\t}\n\treturn false\n}\n\nfunc renderGateways(statuses []gatewayStatus, w io.Writer) {\n\tt := buildGatewaysTable()\n\tt.Data = []table.Row{}\n\tfor _, status := range statuses {\n\t\tstatus := status\n\t\tt.Data = append(t.Data, gatewayStatusToTableRow(status))\n\t}\n\tt.Render(w)\n}\n\nvar (\n\tclusterNameHeader    = \"CLUSTER\"\n\taliveHeader          = \"ALIVE\"\n\tpairedServicesHeader = \"NUM_SVC\"\n\tlatencyHeader        = \"LATENCY\"\n)\n\nfunc buildGatewaysTable() table.Table {\n\tcolumns := []table.Column{\n\t\t{\n\t\t\tHeader:    clusterNameHeader,\n\t\t\tWidth:     7,\n\t\t\tFlexible:  true,\n\t\t\tLeftAlign: true,\n\t\t},\n\t\t{\n\t\t\tHeader:    aliveHeader,\n\t\t\tWidth:     5,\n\t\t\tFlexible:  true,\n\t\t\tLeftAlign: true,\n\t\t},\n\t\t{\n\t\t\tHeader: pairedServicesHeader,\n\t\t\tWidth:  9,\n\t\t},\n\t\t{\n\t\t\tHeader: latencyHeader,\n\t\t\tWidth:  11,\n\t\t},\n\t}\n\tt := table.NewTable(columns, []table.Row{})\n\tt.Sort = []int{0} // sort by cluster name\n\treturn t\n}\n\nfunc gatewayStatusToTableRow(status gatewayStatus) []string {\n\tvalueOrPlaceholder := func(value string) string {\n\t\tif status.Alive {\n\t\t\treturn value\n\t\t}\n\t\treturn \"-\"\n\t}\n\talive := \"False\"\n\tif status.Alive {\n\t\talive = \"True\"\n\t}\n\treturn []string{\n\t\tstatus.ClusterName,\n\t\talive,\n\t\tfmt.Sprint(status.NumberOfServices),\n\t\tvalueOrPlaceholder(fmt.Sprintf(\"%dms\", status.Latency)),\n\t}\n\n}\n"
  },
  {
    "path": "multicluster/cmd/install.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/multicluster/charts\"\n\tmulticluster \"github.com/linkerd/linkerd2/multicluster/values\"\n\tchartspkg \"github.com/linkerd/linkerd2/pkg/charts\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\t\"helm.sh/helm/v3/pkg/chart/loader\"\n\t\"helm.sh/helm/v3/pkg/chartutil\"\n\tvaluespkg \"helm.sh/helm/v3/pkg/cli/values\"\n\t\"helm.sh/helm/v3/pkg/engine\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"sigs.k8s.io/yaml\"\n)\n\ntype (\n\tmulticlusterInstallOptions struct {\n\t\tgateway                 multicluster.Gateway\n\t\tremoteMirrorCredentials bool\n\t}\n)\n\nvar TemplatesMulticluster = []string{\n\tchartutil.ChartfileName,\n\tchartutil.ValuesfileName,\n\t\"templates/namespace.yaml\",\n\t\"templates/gateway.yaml\",\n\t\"templates/gateway-policy.yaml\",\n\t\"templates/psp.yaml\",\n\t\"templates/remote-access-service-mirror-rbac.yaml\",\n\t\"templates/link-crd.yaml\",\n\t\"templates/service-mirror-policy.yaml\",\n\t\"templates/local-service-mirror.yaml\",\n\t\"templates/controller-clusterrole.yaml\",\n\t\"templates/controller/deployment.yaml\",\n\t\"templates/controller/pdb.yaml\",\n\t\"templates/controller/probe-svc.yaml\",\n\t\"templates/controller/rbac.yaml\",\n}\n\nfunc newMulticlusterInstallCommand() *cobra.Command {\n\toptions, err := newMulticlusterInstallOptionsWithDefault()\n\tvar ha bool\n\tvar wait time.Duration\n\tvar valuesOptions valuespkg.Options\n\tvar ignoreCluster bool\n\tvar cniEnabled bool\n\tvar output string\n\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"install\",\n\t\tShort: \"Output Kubernetes configs to install the Linkerd multicluster add-on\",\n\t\tArgs:  cobra.NoArgs,\n\t\tExample: `  # Default install.\n  linkerd multicluster install | kubectl apply -f -\n\nThe installation can be configured by using the --set, --values, --set-string and --set-file flags.\nA full list of configurable values can be found at https://github.com/linkerd/linkerd2/blob/main/multicluster/charts/linkerd-multicluster/values.yaml\n  `,\n\t\tRunE: func(cmd *cobra.Command, _ []string) error {\n\t\t\tif !ignoreCluster {\n\t\t\t\t// Wait for the core control-plane to be up and running\n\t\t\t\thc := healthcheck.NewWithCoreChecks(&healthcheck.Options{\n\t\t\t\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\t\t\t\tKubeConfig:            kubeconfigPath,\n\t\t\t\t\tKubeContext:           kubeContext,\n\t\t\t\t\tImpersonate:           impersonate,\n\t\t\t\t\tImpersonateGroup:      impersonateGroup,\n\t\t\t\t\tAPIAddr:               apiAddr,\n\t\t\t\t\tRetryDeadline:         time.Now().Add(wait),\n\t\t\t\t})\n\t\t\t\thc.RunWithExitOnError()\n\t\t\t\tcniEnabled = hc.CNIEnabled\n\t\t\t}\n\t\t\treturn install(cmd.Context(), stdout, options, valuesOptions, ha, ignoreCluster, cniEnabled, output)\n\t\t},\n\t}\n\n\tflags.AddValueOptionsFlags(cmd.Flags(), &valuesOptions)\n\tcmd.Flags().BoolVar(&options.gateway.Enabled, \"gateway\", options.gateway.Enabled, \"If the gateway component should be installed\")\n\tcmd.Flags().Uint32Var(&options.gateway.Port, \"gateway-port\", options.gateway.Port, \"The port on the gateway used for all incoming traffic\")\n\tcmd.Flags().Uint32Var(&options.gateway.Probe.Seconds, \"gateway-probe-seconds\", options.gateway.Probe.Seconds, \"The interval at which the gateway will be checked for being alive in seconds\")\n\tcmd.Flags().Uint32Var(&options.gateway.Probe.Port, \"gateway-probe-port\", options.gateway.Probe.Port, \"The liveness check port of the gateway\")\n\tcmd.Flags().BoolVar(&options.remoteMirrorCredentials, \"service-mirror-credentials\", options.remoteMirrorCredentials, \"Whether to install the service account which can be used by service mirror components in source clusters to discover exported services\")\n\tcmd.Flags().StringVar(&options.gateway.ServiceType, \"gateway-service-type\", options.gateway.ServiceType, \"Overwrite Service type for gateway service\")\n\tcmd.Flags().BoolVar(&ha, \"ha\", false, `Install multicluster extension in High Availability mode.`)\n\tcmd.Flags().DurationVar(&wait, \"wait\", 300*time.Second, \"Wait for core control-plane components to be available\")\n\tcmd.Flags().BoolVar(&ignoreCluster, \"ignore-cluster\", false,\n\t\t\"Ignore the current Kubernetes cluster when checking for existing cluster configuration (default false)\")\n\tcmd.PersistentFlags().StringVarP(&output, \"output\", \"o\", \"yaml\", \"Output format. One of: json|yaml\")\n\n\t// Hide developer focused flags in release builds.\n\trelease, err := version.IsReleaseChannel(version.Version)\n\tif err != nil {\n\t\tlog.Errorf(\"Unable to parse version: %s\", version.Version)\n\t}\n\tif release {\n\t\tcmd.Flags().MarkHidden(\"control-plane-version\")\n\t\tcmd.Flags().MarkHidden(\"gateway-nginx-image\")\n\t\tcmd.Flags().MarkHidden(\"gateway-nginx-image-version\")\n\t}\n\n\treturn cmd\n}\n\nfunc install(ctx context.Context, w io.Writer, options *multiclusterInstallOptions, valuesOptions valuespkg.Options, ha, ignoreCluster, cniEnabled bool, format string) error {\n\tvalues, err := buildMulticlusterInstallValues(ctx, options, ignoreCluster)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Create values override\n\tvaluesOverrides, err := valuesOptions.MergeValues(nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ha {\n\t\tvaluesOverrides, err = chartspkg.OverrideFromFile(valuesOverrides, charts.Templates, multicluster.HelmDefaultChartDir, \"values-ha.yaml\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif cniEnabled {\n\t\tvaluesOverrides[\"cniEnabled\"] = true\n\t}\n\n\treturn render(w, values, valuesOverrides, format)\n}\n\nfunc render(w io.Writer, values *multicluster.Values, valuesOverrides map[string]interface{}, format string) error {\n\tvar files []*loader.BufferedFile\n\tfor _, template := range TemplatesMulticluster {\n\t\tfiles = append(files, &loader.BufferedFile{Name: template})\n\t}\n\n\t// Load all multicluster install chart files into buffer\n\tif err := chartspkg.FilesReader(charts.Templates, multicluster.HelmDefaultChartDir+\"/\", files); err != nil {\n\t\treturn err\n\t}\n\n\tpartialFiles, err := chartspkg.LoadPartials()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Create a Chart obj from the files\n\tchart, err := loader.LoadFiles(append(files, partialFiles...))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Render raw values and create chart config\n\trawValues, err := yaml.Marshal(values)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// Store final Values generated from values.yaml and CLI flags\n\terr = yaml.Unmarshal(rawValues, &chart.Values)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif reg := os.Getenv(flags.EnvOverrideDockerRegistry); reg != \"\" {\n\t\toverrideRegistry(chart.Values, \"localServiceMirror.image.name\", reg, \"cr.l5d.io/linkerd/controller\")\n\n\t\tif controllers, ok := valuesOverrides[\"controllers\"].([]any); ok {\n\t\t\tfor _, c := range controllers {\n\t\t\t\tif controller, ok := c.(map[string]any); ok {\n\t\t\t\t\toverrideRegistry(controller, \"image.name\", reg, \"cr.l5d.io/linkerd/controller\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tvals, err := chartutil.CoalesceValues(chart, valuesOverrides)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfullValues := map[string]interface{}{\n\t\t\"Values\": vals,\n\t\t\"Release\": map[string]interface{}{\n\t\t\t\"Namespace\": defaultMulticlusterNamespace,\n\t\t\t\"Service\":   \"CLI\",\n\t\t},\n\t}\n\n\t// Attach the final values into the `Values` field for rendering to work\n\trenderedTemplates, err := engine.Render(chart, fullValues)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Merge templates and inject\n\tvar buf bytes.Buffer\n\tfor _, tmpl := range chart.Templates {\n\t\tt := path.Join(chart.Metadata.Name, tmpl.Name)\n\t\tif _, err := buf.WriteString(renderedTemplates[t]); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn pkgcmd.RenderYAMLAs(&buf, w, format)\n}\n\nfunc newMulticlusterInstallOptionsWithDefault() (*multiclusterInstallOptions, error) {\n\tdefaults, err := multicluster.NewInstallValues()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &multiclusterInstallOptions{\n\t\tgateway:                 *defaults.Gateway,\n\t\tremoteMirrorCredentials: true,\n\t}, nil\n}\n\nfunc buildMulticlusterInstallValues(ctx context.Context, opts *multiclusterInstallOptions, ignoreCluster bool) (*multicluster.Values, error) {\n\tdefaults, err := multicluster.NewInstallValues()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdefaults.LocalServiceMirror.Image.Version = version.Version\n\tdefaults.ControllerDefaults.Image.Version = version.Version\n\tdefaults.Gateway.Enabled = opts.gateway.Enabled\n\tdefaults.Gateway.Port = opts.gateway.Port\n\tdefaults.Gateway.Probe.Seconds = opts.gateway.Probe.Seconds\n\tdefaults.Gateway.Probe.Port = opts.gateway.Probe.Port\n\tdefaults.LinkerdNamespace = controlPlaneNamespace\n\tdefaults.LinkerdVersion = version.Version\n\tdefaults.RemoteMirrorServiceAccount = opts.remoteMirrorCredentials\n\tdefaults.Gateway.ServiceType = opts.gateway.ServiceType\n\n\tif ignoreCluster {\n\t\treturn defaults, nil\n\t}\n\n\tvalues, err := getLinkerdConfigMap(ctx)\n\tif err != nil {\n\t\tif kerrors.IsNotFound(err) {\n\t\t\treturn nil, errors.New(\"you need Linkerd to be installed in order to install multicluster addons\")\n\t\t}\n\t\treturn nil, err\n\t}\n\tdefaults.ProxyOutboundPort = uint32(values.Proxy.Ports.Outbound)\n\tdefaults.IdentityTrustDomain = values.IdentityTrustDomain\n\n\treturn defaults, nil\n}\n\n// overrideRegistry overrides the image name in the values map at the given\n// keyPath with the given registry. If the keyPath does not exist, it is\n// created.\nfunc overrideRegistry(values map[string]any, keyPath, reg, defaultImage string) {\n\tkeys := strings.Split(keyPath, \".\")\n\tm := values\n\n\t// Traverse map using keys, except for the last key\n\tfor _, key := range keys[:len(keys)-1] {\n\t\tif next, ok := m[key].(map[string]any); ok {\n\t\t\tm = next\n\t\t} else {\n\t\t\tnewMap := make(map[string]any)\n\t\t\tm[key] = newMap\n\t\t\tm = newMap\n\t\t}\n\t}\n\n\t// Override the last key if it exists and is a string\n\tlastKey := keys[len(keys)-1]\n\tif val, ok := m[lastKey].(string); ok {\n\t\tm[lastKey] = pkgcmd.RegistryOverride(val, reg)\n\t} else {\n\t\t// If the key is missing, initialize it with an empty string before overriding\n\t\tm[lastKey] = pkgcmd.RegistryOverride(defaultImage, reg)\n\t}\n}\n"
  },
  {
    "path": "multicluster/cmd/install_test.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"testing\"\n\n\tmulticluster \"github.com/linkerd/linkerd2/multicluster/values\"\n\t\"github.com/linkerd/linkerd2/pkg/charts\"\n)\n\nfunc TestRender(t *testing.T) {\n\t// pin values that are changed by render functions on each test run\n\tdefaultValues := map[string]interface{}{}\n\n\ttestCases := []struct {\n\t\tvalues             map[string]interface{}\n\t\tmulticlusterValues *multicluster.Values\n\t\tgoldenFileName     string\n\t}{\n\t\t{\n\t\t\tnil,\n\t\t\tnil,\n\t\t\t\"install_default.golden\",\n\t\t},\n\t\t{\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"enablePSP\": \"true\",\n\t\t\t},\n\t\t\tnil,\n\t\t\t\"install_psp.golden\",\n\t\t},\n\t\t{\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"enablePSP\": \"true\",\n\t\t\t\t\"gateway\": map[string]interface{}{\n\t\t\t\t\t\"replicas\": 3,\n\t\t\t\t},\n\t\t\t\t\"enablePodAntiAffinity\": true,\n\t\t\t},\n\t\t\tnil,\n\t\t\t\"install_ha.golden\",\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d: %s\", i, tc.goldenFileName), func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\t// Merge overrides with default\n\t\t\tif err := render(&buf, tc.multiclusterValues, charts.MergeMaps(defaultValues, tc.values), \"yaml\"); err != nil {\n\t\t\t\tt.Fatalf(\"Failed to render templates: %v\", err)\n\t\t\t}\n\t\t\tif err := testDataDiffer.DiffTestYAML(tc.goldenFileName, buf.String()); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "multicluster/cmd/link-gen.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\t\"k8s.io/client-go/tools/clientcmd/api\"\n\t\"sigs.k8s.io/yaml\"\n)\n\ntype (\n\tlinkGenOptions struct {\n\t\tnamespace                string\n\t\tclusterName              string\n\t\tapiServerAddress         string\n\t\tserviceAccountName       string\n\t\tgatewayName              string\n\t\tgatewayNamespace         string\n\t\tselector                 string\n\t\tremoteDiscoverySelector  string\n\t\tfederatedServiceSelector string\n\t\tgatewayAddresses         string\n\t\tgatewayPort              uint32\n\t\texcludedAnnotations      []string\n\t\texcludedLabels           []string\n\t\tenableGateway            bool\n\t\toutput                   string\n\t}\n)\n\nfunc newGenCommand() *cobra.Command {\n\topts := newLinkGenOptionsWithDefault()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"link-gen\",\n\t\tShort: \"Outputs a Link manifest and credentials for another cluster to mirror services from this one\",\n\t\tLong: `Outputs a Link manifest and credentials for another cluster to mirror services from this one.\n\nNote that the Link resource applies only in one direction. In order for two\nclusters to mirror each other, a Link resource will have to be generated for\neach cluster and applied to the other.`,\n\t\tArgs: cobra.NoArgs,\n\t\tExample: `  # To link the west cluster to east\n  linkerd --context=east multicluster link-gen --cluster-name east | kubectl --context=west apply -f -\n  `,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\n\t\t\tif opts.clusterName == \"\" {\n\t\t\t\treturn errors.New(\"you need to specify cluster name\")\n\t\t\t}\n\n\t\t\tconfigMap, err := getLinkerdConfigMap(cmd.Context())\n\t\t\tif err != nil {\n\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\treturn errors.New(\"you need Linkerd to be installed on a cluster in order to get its credentials\")\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tk, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tkubeconfig, err := getKubeconfig(cmd.Context(), k, opts)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tcreds, err := getCreds(kubeconfig, opts.clusterName, opts.namespace, nil, nil, opts.output)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tdestinationLabels := map[string]string{\n\t\t\t\tclusterNameLabel: opts.clusterName,\n\t\t\t}\n\t\t\tdestinationAnnotations := map[string]string{\n\t\t\t\ttrustDomainAnnotation:   configMap.IdentityTrustDomain,\n\t\t\t\tclusterDomainAnnotation: configMap.ClusterDomain,\n\t\t\t}\n\t\t\tdestinationCreds, err := getCreds(kubeconfig, opts.clusterName, controlPlaneNamespace, destinationLabels, destinationAnnotations, opts.output)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tlink, err := getLink(cmd.Context(), k, configMap.ClusterDomain, opts)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tseparator := []byte(\"---\\n\")\n\t\t\tif opts.output == \"json\" {\n\t\t\t\tseparator = []byte(\"\\n\")\n\t\t\t}\n\t\t\tstdout.Write(creds)\n\t\t\tstdout.Write(separator)\n\t\t\tstdout.Write(destinationCreds)\n\t\t\tstdout.Write(separator)\n\t\t\tstdout.Write(link)\n\t\t\tstdout.Write(separator)\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcmd.Flags().StringVar(&opts.namespace, \"namespace\", defaultMulticlusterNamespace, \"The namespace for the service account\")\n\tcmd.Flags().StringVar(&opts.clusterName, \"cluster-name\", \"\", \"Cluster name\")\n\tcmd.Flags().StringVar(&opts.apiServerAddress, \"api-server-address\", \"\", \"The api server address of the target cluster\")\n\tcmd.Flags().StringVar(&opts.serviceAccountName, \"service-account-name\", defaultServiceAccountName, \"The name of the service account associated with the credentials\")\n\tcmd.Flags().StringVar(&opts.gatewayName, \"gateway-name\", defaultGatewayName, \"The name of the gateway service\")\n\tcmd.Flags().StringVar(&opts.gatewayNamespace, \"gateway-namespace\", defaultMulticlusterNamespace, \"The namespace of the gateway service\")\n\tcmd.Flags().StringVarP(&opts.selector, \"selector\", \"l\", opts.selector, \"Selector (label query) to filter which services in the target cluster to mirror\")\n\tcmd.Flags().StringVar(&opts.remoteDiscoverySelector, \"remote-discovery-selector\", opts.remoteDiscoverySelector, \"Selector (label query) to filter which services in the target cluster to mirror in remote discovery mode\")\n\tcmd.Flags().StringVar(&opts.federatedServiceSelector, \"federated-service-selector\", opts.federatedServiceSelector, \"Selector (label query) for federated service members in the target cluster\")\n\tcmd.Flags().StringVar(&opts.gatewayAddresses, \"gateway-addresses\", opts.gatewayAddresses, \"If specified, overwrites gateway addresses when gateway service is not type LoadBalancer (comma separated list)\")\n\tcmd.Flags().Uint32Var(&opts.gatewayPort, \"gateway-port\", opts.gatewayPort, \"If specified, overwrites gateway port when gateway service is not type LoadBalancer\")\n\tcmd.Flags().StringSliceVar(&opts.excludedAnnotations, \"excluded-annotations\", opts.excludedAnnotations, \"Annotations to exclude when mirroring services\")\n\tcmd.Flags().StringSliceVar(&opts.excludedLabels, \"excluded-labels\", opts.excludedLabels, \"Labels to exclude when mirroring services\")\n\tcmd.Flags().BoolVar(&opts.enableGateway, \"gateway\", opts.enableGateway, \"If false, allows a link to be created against a cluster that does not have a gateway service\")\n\tcmd.Flags().StringVarP(&opts.output, \"output\", \"o\", \"yaml\", \"Output format. One of: json|yaml\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tcmd, []string{\"namespace\", \"gateway-namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\treturn cmd\n}\n\nfunc getKubeconfig(ctx context.Context, k *k8s.KubernetesAPI, opts *linkGenOptions) ([]byte, error) {\n\trules := clientcmd.NewDefaultClientConfigLoadingRules()\n\trules.ExplicitPath = kubeconfigPath\n\tloader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{})\n\tconfig, err := loader.RawConfig()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif kubeContext != \"\" {\n\t\tconfig.CurrentContext = kubeContext\n\t}\n\n\tsa, err := k.CoreV1().ServiceAccounts(opts.namespace).Get(ctx, opts.serviceAccountName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlistOpts := metav1.ListOptions{\n\t\tFieldSelector: fmt.Sprintf(\"type=%s\", corev1.SecretTypeServiceAccountToken),\n\t}\n\tsecrets, err := k.CoreV1().Secrets(opts.namespace).List(ctx, listOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttoken, err := extractSAToken(secrets.Items, sa.Name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcontext, ok := config.Contexts[config.CurrentContext]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"could not extract current context from config\")\n\t}\n\n\tcontext.AuthInfo = opts.serviceAccountName\n\tconfig.Contexts = map[string]*api.Context{\n\t\tconfig.CurrentContext: context,\n\t}\n\tconfig.AuthInfos = map[string]*api.AuthInfo{\n\t\topts.serviceAccountName: {\n\t\t\tToken: token,\n\t\t},\n\t}\n\n\tcluster := config.Clusters[context.Cluster]\n\n\tif opts.apiServerAddress != \"\" {\n\t\tcluster.Server = opts.apiServerAddress\n\t}\n\n\tconfig.Clusters = map[string]*api.Cluster{\n\t\tcontext.Cluster: cluster,\n\t}\n\n\treturn clientcmd.Write(config)\n}\n\nfunc getCreds(kubeconfig []byte, clusterName, namespace string, labels, annotations map[string]string, output string) ([]byte, error) {\n\tcreds := corev1.Secret{\n\t\tType:     k8s.MirrorSecretType,\n\t\tTypeMeta: metav1.TypeMeta{Kind: \"Secret\", APIVersion: \"v1\"},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        fmt.Sprintf(\"cluster-credentials-%s\", clusterName),\n\t\t\tNamespace:   namespace,\n\t\t\tLabels:      labels,\n\t\t\tAnnotations: annotations,\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\tk8s.ConfigKeyName: kubeconfig,\n\t\t},\n\t}\n\n\tvar credsOut []byte\n\tvar err error\n\n\tswitch output {\n\tcase \"yaml\":\n\t\tcredsOut, err = yaml.Marshal(creds)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase \"json\":\n\t\tcredsOut, err = json.Marshal(creds)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"output format %s not supported\", output)\n\t}\n\n\treturn credsOut, nil\n}\n\nfunc getLink(ctx context.Context, k *k8s.KubernetesAPI, clusterDomain string, opts *linkGenOptions) ([]byte, error) {\n\tremoteDiscoverySelector, err := metav1.ParseToLabelSelector(opts.remoteDiscoverySelector)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfederatedServiceSelector, err := metav1.ParseToLabelSelector(opts.federatedServiceSelector)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlink := v1alpha3.Link{\n\t\tTypeMeta: metav1.TypeMeta{Kind: \"Link\", APIVersion: \"multicluster.linkerd.io/v1alpha3\"},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      opts.clusterName,\n\t\t\tNamespace: opts.namespace,\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tk8s.CreatedByAnnotation: k8s.CreatedByAnnotationValue(),\n\t\t\t},\n\t\t},\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName:             opts.clusterName,\n\t\t\tTargetClusterDomain:           clusterDomain,\n\t\t\tTargetClusterLinkerdNamespace: controlPlaneNamespace,\n\t\t\tClusterCredentialsSecret:      fmt.Sprintf(\"cluster-credentials-%s\", opts.clusterName),\n\t\t\tRemoteDiscoverySelector:       remoteDiscoverySelector,\n\t\t\tFederatedServiceSelector:      federatedServiceSelector,\n\t\t\tExcludedAnnotations:           opts.excludedAnnotations,\n\t\t\tExcludedLabels:                opts.excludedLabels,\n\t\t},\n\t}\n\n\t// If there is a gateway in the exporting cluster, populate Link\n\t// resource with gateway information\n\tif opts.enableGateway {\n\t\tgateway, err := k.CoreV1().Services(opts.gatewayNamespace).Get(ctx, opts.gatewayName, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tgwAddresses := []string{}\n\t\tfor _, ingress := range gateway.Status.LoadBalancer.Ingress {\n\t\t\taddr := ingress.IP\n\t\t\tif addr == \"\" {\n\t\t\t\taddr = ingress.Hostname\n\t\t\t}\n\t\t\tif addr == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tgwAddresses = append(gwAddresses, addr)\n\t\t}\n\n\t\tif opts.gatewayAddresses != \"\" {\n\t\t\tlink.Spec.GatewayAddress = opts.gatewayAddresses\n\t\t} else if len(gwAddresses) > 0 {\n\t\t\tlink.Spec.GatewayAddress = strings.Join(gwAddresses, \",\")\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"gateway %s.%s has no ingress addresses\", gateway.Name, gateway.Namespace)\n\t\t}\n\n\t\tgatewayIdentity, ok := gateway.Annotations[k8s.GatewayIdentity]\n\t\tif !ok || gatewayIdentity == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"gateway %s.%s has no %s annotation\", gateway.Name, gateway.Namespace, k8s.GatewayIdentity)\n\t\t}\n\t\tlink.Spec.GatewayIdentity = gatewayIdentity\n\n\t\tprobeSpec, err := extractProbeSpec(gateway)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tlink.Spec.ProbeSpec = probeSpec\n\n\t\tgatewayPort, err := extractGatewayPort(gateway)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// Override with user provided gateway port if present\n\t\tif opts.gatewayPort != 0 {\n\t\t\tgatewayPort = opts.gatewayPort\n\t\t}\n\t\tlink.Spec.GatewayPort = fmt.Sprintf(\"%d\", gatewayPort)\n\n\t\tlink.Spec.Selector, err = metav1.ParseToLabelSelector(opts.selector)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvar linkOut []byte\n\tswitch opts.output {\n\tcase \"yaml\":\n\t\tlinkOut, err = yaml.Marshal(link)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase \"json\":\n\t\tlinkOut, err = json.Marshal(link)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"output format %s not supported\", opts.output)\n\t}\n\n\treturn linkOut, nil\n}\n\nfunc newLinkGenOptionsWithDefault() *linkGenOptions {\n\treturn &linkGenOptions{\n\t\tnamespace:                defaultMulticlusterNamespace,\n\t\tselector:                 fmt.Sprintf(\"%s=%s\", k8s.DefaultExportedServiceSelector, \"true\"),\n\t\tremoteDiscoverySelector:  fmt.Sprintf(\"%s=%s\", k8s.DefaultExportedServiceSelector, \"remote-discovery\"),\n\t\tfederatedServiceSelector: fmt.Sprintf(\"%s=%s\", k8s.DefaultFederatedServiceSelector, \"member\"),\n\t\tgatewayAddresses:         \"\",\n\t\tgatewayPort:              0,\n\t\texcludedAnnotations:      []string{},\n\t\texcludedLabels:           []string{},\n\t\tenableGateway:            true,\n\t}\n}\n\nfunc extractGatewayPort(gateway *corev1.Service) (uint32, error) {\n\tfor _, port := range gateway.Spec.Ports {\n\t\tif port.Name == k8s.GatewayPortName {\n\t\t\tif gateway.Spec.Type == \"NodePort\" {\n\t\t\t\treturn uint32(port.NodePort), nil\n\t\t\t}\n\t\t\treturn uint32(port.Port), nil\n\t\t}\n\t}\n\treturn 0, fmt.Errorf(\"gateway service %s has no gateway port named %s\", gateway.Name, k8s.GatewayPortName)\n}\n\nfunc extractSAToken(secrets []corev1.Secret, saName string) (string, error) {\n\tfor _, secret := range secrets {\n\t\tboundSA := secret.Annotations[saNameAnnotationKey]\n\t\tif saName == boundSA {\n\t\t\ttoken, ok := secret.Data[tokenKey]\n\t\t\tif !ok {\n\t\t\t\treturn \"\", fmt.Errorf(\"could not find the token data in service account secret %s\", secret.Name)\n\t\t\t}\n\n\t\t\treturn string(token), nil\n\t\t}\n\t}\n\n\treturn \"\", fmt.Errorf(\"could not find service account token secret for %s\", saName)\n}\n\n// ExtractProbeSpec parses the ProbSpec from a gateway service's annotations.\n// For now we're not including the failureThreshold and timeout fields which\n// are new since edge-24.9.3, to avoid errors when attempting to apply them in\n// clusters with an older Link CRD.\nfunc extractProbeSpec(gateway *corev1.Service) (v1alpha3.ProbeSpec, error) {\n\tpath := gateway.Annotations[k8s.GatewayProbePath]\n\tif path == \"\" {\n\t\treturn v1alpha3.ProbeSpec{}, errors.New(\"probe path is empty\")\n\t}\n\n\tport, err := extractPort(gateway.Spec, k8s.ProbePortName)\n\tif err != nil {\n\t\treturn v1alpha3.ProbeSpec{}, err\n\t}\n\n\t// the `mirror.linkerd.io/probe-period` annotation is initialized with a\n\t// default value of \"3\", but we require a duration-formatted string. So we\n\t// perform the conversion, if required.\n\tperiod := gateway.Annotations[k8s.GatewayProbePeriod]\n\tif secs, err := strconv.ParseInt(period, 10, 64); err == nil {\n\t\tdur := time.Duration(secs) * time.Second\n\t\tperiod = dur.String()\n\t} else if _, err := time.ParseDuration(period); err != nil {\n\t\treturn v1alpha3.ProbeSpec{}, fmt.Errorf(\"could not parse probe period: %w\", err)\n\t}\n\n\treturn v1alpha3.ProbeSpec{\n\t\tPath:   path,\n\t\tPort:   fmt.Sprintf(\"%d\", port),\n\t\tPeriod: period,\n\t}, nil\n}\n\nfunc extractPort(spec corev1.ServiceSpec, portName string) (uint32, error) {\n\tfor _, p := range spec.Ports {\n\t\tif p.Name == portName {\n\t\t\tif spec.Type == \"NodePort\" {\n\t\t\t\treturn uint32(p.NodePort), nil\n\t\t\t}\n\t\t\treturn uint32(p.Port), nil\n\t\t}\n\t}\n\treturn 0, fmt.Errorf(\"could not find port with name %s\", portName)\n}\n"
  },
  {
    "path": "multicluster/cmd/link.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\t\"github.com/linkerd/linkerd2/multicluster/charts\"\n\tmulticluster \"github.com/linkerd/linkerd2/multicluster/values\"\n\tchartspkg \"github.com/linkerd/linkerd2/pkg/charts\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\tchartloader \"helm.sh/helm/v3/pkg/chart/loader\"\n\t\"helm.sh/helm/v3/pkg/chartutil\"\n\tvaluespkg \"helm.sh/helm/v3/pkg/cli/values\"\n\t\"helm.sh/helm/v3/pkg/engine\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\t\"k8s.io/client-go/tools/clientcmd/api\"\n\t\"sigs.k8s.io/yaml\"\n)\n\ntype (\n\tlinkOptions struct {\n\t\tnamespace                string\n\t\tclusterName              string\n\t\tapiServerAddress         string\n\t\tserviceAccountName       string\n\t\tgatewayName              string\n\t\tgatewayNamespace         string\n\t\tserviceMirrorRetryLimit  uint32\n\t\tlogLevel                 string\n\t\tlogFormat                string\n\t\tcontrolPlaneVersion      string\n\t\tdockerRegistry           string\n\t\tselector                 string\n\t\tremoteDiscoverySelector  string\n\t\tfederatedServiceSelector string\n\t\tgatewayAddresses         string\n\t\tgatewayPort              uint32\n\t\texcludedAnnotations      []string\n\t\texcludedLabels           []string\n\t\tha                       bool\n\t\tenableGateway            bool\n\t\tonlyController           bool\n\t\toutput                   string\n\t}\n)\n\nfunc newLinkCommand() *cobra.Command {\n\topts, err := newLinkOptionsWithDefault()\n\n\t// Override the default value with env registry path.\n\t// If cli cmd contains --registry flag, it will override env variable.\n\tif registry := os.Getenv(flags.EnvOverrideDockerRegistry); registry != \"\" {\n\t\topts.dockerRegistry = registry\n\t}\n\n\tvar valuesOptions valuespkg.Options\n\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\n\tcmd := &cobra.Command{\n\t\tUse:        \"link\",\n\t\tDeprecated: \"please use `multicluster link-gen` instead.\",\n\t\tShort:      \"Outputs resources that allow another cluster to mirror services from this one\",\n\t\tLong: `Outputs resources that allow another cluster to mirror services from this one.\n\nNote that the Link resource applies only in one direction. In order for two\nclusters to mirror each other, a Link resource will have to be generated for\neach cluster and applied to the other.`,\n\t\tArgs: cobra.NoArgs,\n\t\tExample: `  # To link the west cluster to east\n  linkerd --context=east multicluster link --cluster-name east | kubectl --context=west apply -f -\n\nThe command can be configured by using the --set, --values, --set-string and --set-file flags.\nA full list of configurable values can be found at https://github.com/linkerd/linkerd2/blob/main/multicluster/charts/linkerd-multicluster-link/values.yaml\n  `,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\n\t\t\tif opts.clusterName == \"\" {\n\t\t\t\treturn errors.New(\"You need to specify cluster name\")\n\t\t\t}\n\n\t\t\tconfigMap, err := getLinkerdConfigMap(cmd.Context())\n\t\t\tif err != nil {\n\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\treturn errors.New(\"you need Linkerd to be installed on a cluster in order to get its credentials\")\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\trules := clientcmd.NewDefaultClientConfigLoadingRules()\n\t\t\trules.ExplicitPath = kubeconfigPath\n\t\t\tloader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{})\n\t\t\tconfig, err := loader.RawConfig()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif kubeContext != \"\" {\n\t\t\t\tconfig.CurrentContext = kubeContext\n\t\t\t}\n\n\t\t\tk, err := k8s.NewAPI(kubeconfigPath, config.CurrentContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tsa, err := k.CoreV1().ServiceAccounts(opts.namespace).Get(cmd.Context(), opts.serviceAccountName, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tlistOpts := metav1.ListOptions{\n\t\t\t\tFieldSelector: fmt.Sprintf(\"type=%s\", corev1.SecretTypeServiceAccountToken),\n\t\t\t}\n\t\t\tsecrets, err := k.CoreV1().Secrets(opts.namespace).List(cmd.Context(), listOpts)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\ttoken, err := extractSAToken(secrets.Items, sa.Name)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tcontext, ok := config.Contexts[config.CurrentContext]\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"could not extract current context from config\")\n\t\t\t}\n\n\t\t\tcontext.AuthInfo = opts.serviceAccountName\n\t\t\tconfig.Contexts = map[string]*api.Context{\n\t\t\t\tconfig.CurrentContext: context,\n\t\t\t}\n\t\t\tconfig.AuthInfos = map[string]*api.AuthInfo{\n\t\t\t\topts.serviceAccountName: {\n\t\t\t\t\tToken: token,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tcluster := config.Clusters[context.Cluster]\n\n\t\t\tif opts.apiServerAddress != \"\" {\n\t\t\t\tcluster.Server = opts.apiServerAddress\n\t\t\t}\n\n\t\t\tconfig.Clusters = map[string]*api.Cluster{\n\t\t\t\tcontext.Cluster: cluster,\n\t\t\t}\n\n\t\t\tkubeconfig, err := clientcmd.Write(config)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tcreds := corev1.Secret{\n\t\t\t\tType:     k8s.MirrorSecretType,\n\t\t\t\tTypeMeta: metav1.TypeMeta{Kind: \"Secret\", APIVersion: \"v1\"},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      fmt.Sprintf(\"cluster-credentials-%s\", opts.clusterName),\n\t\t\t\t\tNamespace: opts.namespace,\n\t\t\t\t},\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\tk8s.ConfigKeyName: kubeconfig,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tvar credsOut []byte\n\t\t\tif opts.output == \"yaml\" {\n\t\t\t\tcredsOut, err = yaml.Marshal(creds)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else if opts.output == \"json\" {\n\t\t\t\tcredsOut, err = json.Marshal(creds)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"output format %s not supported\", opts.output)\n\t\t\t}\n\n\t\t\tdestinationCreds := corev1.Secret{\n\t\t\t\tType:     k8s.MirrorSecretType,\n\t\t\t\tTypeMeta: metav1.TypeMeta{Kind: \"Secret\", APIVersion: \"v1\"},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      fmt.Sprintf(\"cluster-credentials-%s\", opts.clusterName),\n\t\t\t\t\tNamespace: controlPlaneNamespace,\n\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\tclusterNameLabel: opts.clusterName,\n\t\t\t\t\t},\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\ttrustDomainAnnotation:   configMap.IdentityTrustDomain,\n\t\t\t\t\t\tclusterDomainAnnotation: configMap.ClusterDomain,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\tk8s.ConfigKeyName: kubeconfig,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tvar destinationCredsOut []byte\n\t\t\tif opts.output == \"yaml\" {\n\t\t\t\tdestinationCredsOut, err = yaml.Marshal(destinationCreds)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else if opts.output == \"json\" {\n\t\t\t\tdestinationCredsOut, err = json.Marshal(destinationCreds)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"output format %s not supported\", opts.output)\n\t\t\t}\n\n\t\t\tremoteDiscoverySelector, err := metav1.ParseToLabelSelector(opts.remoteDiscoverySelector)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tfederatedServiceSelector, err := metav1.ParseToLabelSelector(opts.federatedServiceSelector)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tlink := v1alpha3.Link{\n\t\t\t\tTypeMeta: metav1.TypeMeta{Kind: \"Link\", APIVersion: \"multicluster.linkerd.io/v1alpha3\"},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      opts.clusterName,\n\t\t\t\t\tNamespace: opts.namespace,\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tk8s.CreatedByAnnotation: k8s.CreatedByAnnotationValue(),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSpec: v1alpha3.LinkSpec{\n\t\t\t\t\tTargetClusterName:             opts.clusterName,\n\t\t\t\t\tTargetClusterDomain:           configMap.ClusterDomain,\n\t\t\t\t\tTargetClusterLinkerdNamespace: controlPlaneNamespace,\n\t\t\t\t\tClusterCredentialsSecret:      fmt.Sprintf(\"cluster-credentials-%s\", opts.clusterName),\n\t\t\t\t\tRemoteDiscoverySelector:       remoteDiscoverySelector,\n\t\t\t\t\tFederatedServiceSelector:      federatedServiceSelector,\n\t\t\t\t\tExcludedAnnotations:           opts.excludedAnnotations,\n\t\t\t\t\tExcludedLabels:                opts.excludedLabels,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\t// If there is a gateway in the exporting cluster, populate Link\n\t\t\t// resource with gateway information\n\t\t\tif opts.enableGateway {\n\t\t\t\tgateway, err := k.CoreV1().Services(opts.gatewayNamespace).Get(cmd.Context(), opts.gatewayName, metav1.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tgwAddresses := []string{}\n\t\t\t\tfor _, ingress := range gateway.Status.LoadBalancer.Ingress {\n\t\t\t\t\taddr := ingress.IP\n\t\t\t\t\tif addr == \"\" {\n\t\t\t\t\t\taddr = ingress.Hostname\n\t\t\t\t\t}\n\t\t\t\t\tif addr == \"\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tgwAddresses = append(gwAddresses, addr)\n\t\t\t\t}\n\n\t\t\t\tif opts.gatewayAddresses != \"\" {\n\t\t\t\t\tlink.Spec.GatewayAddress = opts.gatewayAddresses\n\t\t\t\t} else if len(gwAddresses) > 0 {\n\t\t\t\t\tlink.Spec.GatewayAddress = strings.Join(gwAddresses, \",\")\n\t\t\t\t} else {\n\t\t\t\t\treturn fmt.Errorf(\"Gateway %s.%s has no ingress addresses\", gateway.Name, gateway.Namespace)\n\t\t\t\t}\n\n\t\t\t\tgatewayIdentity, ok := gateway.Annotations[k8s.GatewayIdentity]\n\t\t\t\tif !ok || gatewayIdentity == \"\" {\n\t\t\t\t\treturn fmt.Errorf(\"Gateway %s.%s has no %s annotation\", gateway.Name, gateway.Namespace, k8s.GatewayIdentity)\n\t\t\t\t}\n\t\t\t\tlink.Spec.GatewayIdentity = gatewayIdentity\n\n\t\t\t\tprobeSpec, err := extractProbeSpec(gateway)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tlink.Spec.ProbeSpec = probeSpec\n\n\t\t\t\tgatewayPort, err := extractGatewayPort(gateway)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\t// Override with user provided gateway port if present\n\t\t\t\tif opts.gatewayPort != 0 {\n\t\t\t\t\tgatewayPort = opts.gatewayPort\n\t\t\t\t}\n\t\t\t\tlink.Spec.GatewayPort = fmt.Sprintf(\"%d\", gatewayPort)\n\n\t\t\t\tlink.Spec.Selector, err = metav1.ParseToLabelSelector(opts.selector)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar linkOut []byte\n\t\t\tif opts.output == \"yaml\" {\n\t\t\t\tlinkOut, err = yaml.Marshal(link)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else if opts.output == \"json\" {\n\t\t\t\tlinkOut, err = json.Marshal(link)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"output format %s not supported\", opts.output)\n\t\t\t}\n\n\t\t\tvalues, err := buildServiceMirrorValues(opts)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// Create values override\n\t\t\tvaluesOverrides, err := valuesOptions.MergeValues(nil)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif opts.ha {\n\t\t\t\tif valuesOverrides, err = chartspkg.OverrideFromFile(\n\t\t\t\t\tvaluesOverrides,\n\t\t\t\t\tcharts.Templates,\n\t\t\t\t\tmulticluster.HelmDefaultLinkChartDir,\n\t\t\t\t\t\"values-ha.yaml\",\n\t\t\t\t); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\tserviceMirrorOut, err := renderServiceMirror(values, valuesOverrides, opts.namespace, opts.output)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tseparator := []byte(\"---\\n\")\n\t\t\tif opts.output == \"json\" {\n\t\t\t\tseparator = []byte(\"\\n\")\n\t\t\t}\n\t\t\tstdout.Write(credsOut)\n\t\t\tstdout.Write(separator)\n\t\t\tstdout.Write(destinationCredsOut)\n\t\t\tstdout.Write(separator)\n\t\t\tstdout.Write(linkOut)\n\t\t\tstdout.Write(separator)\n\t\t\tstdout.Write(serviceMirrorOut)\n\t\t\tstdout.Write(separator)\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tflags.AddValueOptionsFlags(cmd.Flags(), &valuesOptions)\n\tcmd.Flags().StringVar(&opts.namespace, \"namespace\", defaultMulticlusterNamespace, \"The namespace for the service account\")\n\tcmd.Flags().StringVar(&opts.clusterName, \"cluster-name\", \"\", \"Cluster name\")\n\tcmd.Flags().StringVar(&opts.apiServerAddress, \"api-server-address\", \"\", \"The api server address of the target cluster\")\n\tcmd.Flags().StringVar(&opts.serviceAccountName, \"service-account-name\", defaultServiceAccountName, \"The name of the service account associated with the credentials\")\n\tcmd.Flags().StringVar(&opts.controlPlaneVersion, \"control-plane-version\", opts.controlPlaneVersion, \"(Development) Tag to be used for the service mirror controller image\")\n\tcmd.Flags().StringVar(&opts.gatewayName, \"gateway-name\", defaultGatewayName, \"The name of the gateway service\")\n\tcmd.Flags().StringVar(&opts.gatewayNamespace, \"gateway-namespace\", defaultMulticlusterNamespace, \"The namespace of the gateway service\")\n\tcmd.Flags().Uint32Var(&opts.serviceMirrorRetryLimit, \"service-mirror-retry-limit\", opts.serviceMirrorRetryLimit, \"The number of times a failed update from the target cluster is allowed to be retried\")\n\tcmd.Flags().StringVar(&opts.logLevel, \"log-level\", opts.logLevel, \"Log level for the Multicluster components\")\n\tcmd.Flags().StringVar(&opts.logFormat, \"log-format\", opts.logFormat, \"Log format for the Multicluster components\")\n\tcmd.Flags().StringVar(&opts.dockerRegistry, \"registry\", opts.dockerRegistry,\n\t\tfmt.Sprintf(\"Docker registry to pull service mirror controller image from ($%s)\", flags.EnvOverrideDockerRegistry))\n\tcmd.Flags().StringVarP(&opts.selector, \"selector\", \"l\", opts.selector, \"Selector (label query) to filter which services in the target cluster to mirror\")\n\tcmd.Flags().StringVar(&opts.remoteDiscoverySelector, \"remote-discovery-selector\", opts.remoteDiscoverySelector, \"Selector (label query) to filter which services in the target cluster to mirror in remote discovery mode\")\n\tcmd.Flags().StringVar(&opts.federatedServiceSelector, \"federated-service-selector\", opts.federatedServiceSelector, \"Selector (label query) for federated service members in the target cluster\")\n\tcmd.Flags().StringVar(&opts.gatewayAddresses, \"gateway-addresses\", opts.gatewayAddresses, \"If specified, overwrites gateway addresses when gateway service is not type LoadBalancer (comma separated list)\")\n\tcmd.Flags().Uint32Var(&opts.gatewayPort, \"gateway-port\", opts.gatewayPort, \"If specified, overwrites gateway port when gateway service is not type LoadBalancer\")\n\tcmd.Flags().StringSliceVar(&opts.excludedAnnotations, \"excluded-annotations\", opts.excludedAnnotations, \"Annotations to exclude when mirroring services\")\n\tcmd.Flags().StringSliceVar(&opts.excludedLabels, \"excluded-labels\", opts.excludedLabels, \"Labels to exclude when mirroring services\")\n\tcmd.Flags().BoolVar(&opts.ha, \"ha\", opts.ha, \"Enable HA configuration for the service-mirror deployment (default false)\")\n\tcmd.Flags().BoolVar(&opts.enableGateway, \"gateway\", opts.enableGateway, \"If false, allows a link to be created against a cluster that does not have a gateway service\")\n\tcmd.Flags().StringVarP(&opts.output, \"output\", \"o\", \"yaml\", \"Output format. One of: json|yaml\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tcmd, []string{\"namespace\", \"gateway-namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\treturn cmd\n}\n\nfunc renderServiceMirror(values *multicluster.Values, valuesOverrides map[string]interface{}, namespace string, format string) ([]byte, error) {\n\tfiles := []*chartloader.BufferedFile{\n\t\t{Name: chartutil.ChartfileName},\n\t\t{Name: \"templates/service-mirror.yaml\"},\n\t\t{Name: \"templates/psp.yaml\"},\n\t\t{Name: \"templates/gateway-mirror.yaml\"},\n\t}\n\n\t// Load all multicluster link chart files into buffer\n\tif err := chartspkg.FilesReader(charts.Templates, multicluster.HelmDefaultLinkChartDir+\"/\", files); err != nil {\n\t\treturn nil, err\n\t}\n\n\tpartialFiles, err := chartspkg.LoadPartials()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Create a Chart obj from the files\n\tchart, err := chartloader.LoadFiles(append(files, partialFiles...))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Render raw values and create chart config\n\trawValues, err := yaml.Marshal(values)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Store final Values generated from values.yaml and CLI flags\n\terr = yaml.Unmarshal(rawValues, &chart.Values)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvals, err := chartutil.CoalesceValues(chart, valuesOverrides)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfullValues := map[string]interface{}{\n\t\t\"Values\": vals,\n\t\t\"Release\": map[string]interface{}{\n\t\t\t\"Namespace\": namespace,\n\t\t\t\"Service\":   \"CLI\",\n\t\t},\n\t}\n\n\t// Attach the final values into the `Values` field for rendering to work\n\trenderedTemplates, err := engine.Render(chart, fullValues)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Merge templates and inject\n\tvar yamlBytes bytes.Buffer\n\tfor _, tmpl := range chart.Templates {\n\t\tt := path.Join(chart.Metadata.Name, tmpl.Name)\n\t\tif _, err := yamlBytes.WriteString(renderedTemplates[t]); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvar out bytes.Buffer\n\terr = pkgcmd.RenderYAMLAs(&yamlBytes, &out, format)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out.Bytes(), nil\n}\n\nfunc newLinkOptionsWithDefault() (*linkOptions, error) {\n\tdefaults, err := multicluster.NewLinkValues()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &linkOptions{\n\t\tcontrolPlaneVersion:      version.Version,\n\t\tnamespace:                defaultMulticlusterNamespace,\n\t\tdockerRegistry:           pkgcmd.DefaultDockerRegistry,\n\t\tserviceMirrorRetryLimit:  defaults.ServiceMirrorRetryLimit,\n\t\tlogLevel:                 defaults.LogLevel,\n\t\tlogFormat:                defaults.LogFormat,\n\t\tselector:                 fmt.Sprintf(\"%s=%s\", k8s.DefaultExportedServiceSelector, \"true\"),\n\t\tremoteDiscoverySelector:  fmt.Sprintf(\"%s=%s\", k8s.DefaultExportedServiceSelector, \"remote-discovery\"),\n\t\tfederatedServiceSelector: fmt.Sprintf(\"%s=%s\", k8s.DefaultFederatedServiceSelector, \"member\"),\n\t\tgatewayAddresses:         \"\",\n\t\tgatewayPort:              0,\n\t\texcludedAnnotations:      []string{},\n\t\texcludedLabels:           []string{},\n\t\tha:                       false,\n\t\tenableGateway:            true,\n\t}, nil\n}\n\nfunc buildServiceMirrorValues(opts *linkOptions) (*multicluster.Values, error) {\n\n\tif !alphaNumDashDot.MatchString(opts.controlPlaneVersion) {\n\t\treturn nil, fmt.Errorf(\"%s is not a valid version\", opts.controlPlaneVersion)\n\t}\n\n\tif opts.namespace == \"\" {\n\t\treturn nil, errors.New(\"you need to specify a namespace\")\n\t}\n\n\tif _, err := log.ParseLevel(opts.logLevel); err != nil {\n\t\treturn nil, fmt.Errorf(\"--log-level must be one of: panic, fatal, error, warn, info, debug, trace\")\n\t}\n\n\tif opts.logFormat != \"plain\" && opts.logFormat != \"json\" {\n\t\treturn nil, fmt.Errorf(\"--log-format must be one of: plain, json\")\n\t}\n\n\tif opts.selector != \"\" && opts.selector != fmt.Sprintf(\"%s=%s\", k8s.DefaultExportedServiceSelector, \"true\") {\n\t\tif !opts.enableGateway {\n\t\t\treturn nil, fmt.Errorf(\"--selector and --gateway=false are mutually exclusive\")\n\t\t}\n\t}\n\n\tif opts.gatewayAddresses != \"\" && !opts.enableGateway {\n\t\treturn nil, fmt.Errorf(\"--gateway-addresses and --gateway=false are mutually exclusive\")\n\t}\n\n\tif opts.gatewayPort != 0 && !opts.enableGateway {\n\t\treturn nil, fmt.Errorf(\"--gateway-port and --gateway=false are mutually exclusive\")\n\t}\n\n\tdefaults, err := multicluster.NewLinkValues()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdefaults.Gateway.Enabled = opts.enableGateway\n\tdefaults.TargetClusterName = opts.clusterName\n\tdefaults.ServiceMirrorRetryLimit = opts.serviceMirrorRetryLimit\n\tdefaults.LogLevel = opts.logLevel\n\tdefaults.LogFormat = opts.logFormat\n\tdefaults.ControllerImageVersion = opts.controlPlaneVersion\n\tdefaults.ControllerImage = fmt.Sprintf(\"%s/controller\", opts.dockerRegistry)\n\n\treturn defaults, nil\n}\n"
  },
  {
    "path": "multicluster/cmd/link_test.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\tmulticluster \"github.com/linkerd/linkerd2/multicluster/values\"\n\t\"github.com/linkerd/linkerd2/pkg/charts\"\n)\n\nfunc TestServiceMirrorRender(t *testing.T) {\n\tdefaultValues := map[string]interface{}{}\n\tlinkValues, _ := multicluster.NewLinkValues()\n\tlinkValues.TargetClusterName = \"test-cluster\"\n\ttestCases := []struct {\n\t\tserviceMirrorValues *multicluster.Values\n\t\toverrides           map[string]interface{}\n\t\tgoldenFileName      string\n\t}{\n\t\t{\n\t\t\tlinkValues,\n\t\t\tnil,\n\t\t\t\"service_mirror_default.golden\",\n\t\t},\n\n\t\t{\n\t\t\tlinkValues,\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"enablePodAntiAffinity\": true,\n\t\t\t},\n\t\t\t\"service_mirror_ha.golden\",\n\t\t},\n\t}\n\tfor i, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(fmt.Sprintf(\"%d: %s\", i, tc.goldenFileName), func(t *testing.T) {\n\t\t\tout, err := renderServiceMirror(tc.serviceMirrorValues, charts.MergeMaps(defaultValues, tc.overrides), \"test\", \"yaml\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to render templates: %v\", err)\n\t\t\t}\n\t\t\tfmt.Println(string(out))\n\t\t\tif err = testDataDiffer.DiffTestYAML(tc.goldenFileName, string(out)); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "multicluster/cmd/main_test.go",
    "content": "package cmd\n\nimport (\n\t\"flag\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\nvar (\n\ttestDataDiffer testutil.TestDataDiffer\n)\n\n// TestMain parses flags before running tests\nfunc TestMain(m *testing.M) {\n\tflag.BoolVar(&testDataDiffer.UpdateFixtures, \"update\", false, \"update text fixtures in place\")\n\tprettyDiff := os.Getenv(\"LINKERD_TEST_PRETTY_DIFF\") != \"\"\n\tflag.BoolVar(&testDataDiffer.PrettyDiff, \"pretty-diff\", prettyDiff, \"display the full text when diffing\")\n\tflag.StringVar(&testDataDiffer.RejectPath, \"reject-path\", \"\", \"write results for failed tests to this path (path is relative to the test location)\")\n\tflag.Parse()\n\tos.Exit(m.Run())\n}\n"
  },
  {
    "path": "multicluster/cmd/root.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"regexp\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n)\n\nconst (\n\tdefaultLinkerdNamespace              = \"linkerd\"\n\tdefaultMulticlusterNamespace         = \"linkerd-multicluster\"\n\tdefaultGatewayName                   = \"linkerd-gateway\"\n\thelmMulticlusterLinkDefaultChartName = \"linkerd-multicluster-link\"\n\ttokenKey                             = \"token\"\n\n\tsaNameAnnotationKey       = \"kubernetes.io/service-account.name\"\n\tdefaultServiceAccountName = \"linkerd-service-mirror-remote-access-default\"\n\n\tclusterNameLabel        = \"multicluster.linkerd.io/cluster-name\"\n\ttrustDomainAnnotation   = \"multicluster.linkerd.io/trust-domain\"\n\tclusterDomainAnnotation = \"multicluster.linkerd.io/cluster-domain\"\n)\n\nvar (\n\tHelmMulticlusterDefaultChartName = \"linkerd-multicluster\"\n\n\tapiAddr               string // An empty value means \"use the Kubernetes configuration\"\n\tcontrolPlaneNamespace string\n\tkubeconfigPath        string\n\tkubeContext           string\n\timpersonate           string\n\timpersonateGroup      []string\n\tverbose               bool\n\n\t// special handling for Windows, on all other platforms these resolve to\n\t// os.Stdout and os.Stderr, thanks to https://github.com/mattn/go-colorable\n\tstdout = color.Output\n\tstderr = color.Error\n\n\t// These regexs are not as strict as they could be, but are a quick and dirty\n\t// sanity check against illegal characters.\n\talphaNumDashDot = regexp.MustCompile(`^[\\.a-zA-Z0-9-]+$`)\n)\n\n// NewCmdMulticluster returns a new multicluster command\nfunc NewCmdMulticluster() *cobra.Command {\n\n\tmulticlusterCmd := &cobra.Command{\n\t\tUse:     \"multicluster [flags]\",\n\t\tAliases: []string{\"mc\"},\n\t\tArgs:    cobra.NoArgs,\n\t\tShort:   \"Manages the multicluster setup for Linkerd\",\n\t\tLong: `Manages the multicluster setup for Linkerd.\n\nThis command provides subcommands to manage the multicluster support\nfunctionality of Linkerd. You can use it to install the service mirror\ncomponents on a cluster, manage credentials and link clusters together.`,\n\t\tExample: `  # Install multicluster addons.\n  linkerd --context=cluster-a multicluster install | kubectl --context=cluster-a apply -f -\n\n  # Extract mirroring cluster credentials from cluster A and install them on cluster B\n  linkerd --context=cluster-a multicluster link --cluster-name=target | kubectl apply --context=cluster-b -f -`,\n\t\tPersistentPreRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif verbose {\n\t\t\t\tlog.SetLevel(log.DebugLevel)\n\t\t\t} else {\n\t\t\t\tlog.SetLevel(log.PanicLevel)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tmulticlusterCmd.PersistentFlags().StringVarP(&controlPlaneNamespace, \"linkerd-namespace\", \"L\", defaultLinkerdNamespace, \"Namespace in which Linkerd is installed\")\n\tmulticlusterCmd.PersistentFlags().StringVar(&kubeconfigPath, \"kubeconfig\", \"\", \"Path to the kubeconfig file to use for CLI requests\")\n\tmulticlusterCmd.PersistentFlags().StringVar(&kubeContext, \"context\", \"\", \"Name of the kubeconfig context to use\")\n\tmulticlusterCmd.PersistentFlags().StringVar(&impersonate, \"as\", \"\", \"Username to impersonate for Kubernetes operations\")\n\tmulticlusterCmd.PersistentFlags().StringArrayVar(&impersonateGroup, \"as-group\", []string{}, \"Group to impersonate for Kubernetes operations\")\n\tmulticlusterCmd.PersistentFlags().StringVar(&apiAddr, \"api-addr\", \"\", \"Override kubeconfig and communicate directly with the control plane at host:port (mostly for testing)\")\n\tmulticlusterCmd.PersistentFlags().BoolVar(&verbose, \"verbose\", false, \"Turn on debug logging\")\n\tmulticlusterCmd.AddCommand(newLinkCommand())\n\tmulticlusterCmd.AddCommand(newUnlinkCommand())\n\tmulticlusterCmd.AddCommand(newGenCommand())\n\tmulticlusterCmd.AddCommand(newMulticlusterInstallCommand())\n\tmulticlusterCmd.AddCommand(NewCmdCheck())\n\tmulticlusterCmd.AddCommand(newMulticlusterUninstallCommand())\n\tmulticlusterCmd.AddCommand(newGatewaysCommand())\n\tmulticlusterCmd.AddCommand(newAllowCommand())\n\n\t// resource-aware completion flag configurations\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tmulticlusterCmd, []string{\"linkerd-namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\n\tpkgcmd.ConfigureKubeContextFlagCompletion(multiclusterCmd, kubeconfigPath)\n\treturn multiclusterCmd\n}\n\nfunc getLinkerdConfigMap(ctx context.Context) (*linkerd2.Values, error) {\n\tkubeAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, values, err := healthcheck.FetchCurrentConfiguration(ctx, kubeAPI, controlPlaneNamespace)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn values, nil\n}\n"
  },
  {
    "path": "multicluster/cmd/service-mirror/main.go",
    "content": "package servicemirror\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\tcontrollerK8s \"github.com/linkerd/linkerd2/controller/k8s\"\n\tservicemirror \"github.com/linkerd/linkerd2/multicluster/service-mirror\"\n\t\"github.com/linkerd/linkerd2/pkg/admin\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tsm \"github.com/linkerd/linkerd2/pkg/servicemirror\"\n\tlog \"github.com/sirupsen/logrus\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\t\"k8s.io/client-go/tools/leaderelection\"\n\t\"k8s.io/client-go/tools/leaderelection/resourcelock\"\n)\n\nconst (\n\tlinkWatchRestartAfter = 10 * time.Second\n\t// Duration of the lease\n\tLEASE_DURATION = 30 * time.Second\n\t// Deadline for the leader to refresh its lease. Defaults to the same value\n\t// used by core controllers\n\tLEASE_RENEW_DEADLINE = 10 * time.Second\n\t// Duration leader elector clients should wait between action re-tries.\n\t// Defaults to the same value used by core controllers\n\tLEASE_RETRY_PERIOD = 2 * time.Second\n)\n\nvar (\n\tclusterWatcher *servicemirror.RemoteClusterServiceWatcher\n\tprobeWorker    *servicemirror.ProbeWorker\n)\n\n// Main executes the service-mirror controller\nfunc Main(args []string) {\n\tcmd := flag.NewFlagSet(\"service-mirror\", flag.ExitOnError)\n\n\tkubeConfigPath := cmd.String(\"kubeconfig\", \"\", \"path to the local kube config\")\n\trequeueLimit := cmd.Int(\"event-requeue-limit\", 3, \"requeue limit for events\")\n\tmetricsAddr := cmd.String(\"metrics-addr\", \":9999\", \"address to serve scrapable metrics on\")\n\tnamespace := cmd.String(\"namespace\", \"\", \"namespace containing Link and credentials Secret\")\n\trepairPeriod := cmd.Duration(\"endpoint-refresh-period\", 1*time.Minute, \"frequency to refresh endpoint resolution\")\n\tenableHeadlessSvc := cmd.Bool(\"enable-headless-services\", false, \"toggle support for headless service mirroring\")\n\tenableNamespaceCreation := cmd.Bool(\"enable-namespace-creation\", false, \"toggle support for namespace creation\")\n\tenablePprof := cmd.Bool(\"enable-pprof\", false, \"Enable pprof endpoints on the admin server\")\n\tlocalMirror := cmd.Bool(\"local-mirror\", false, \"watch the local cluster for federated service members\")\n\tfederatedServiceSelector := cmd.String(\"federated-service-selector\", k8s.DefaultFederatedServiceSelector, \"Selector (label query) for federated service members in the local cluster\")\n\texludedAnnotations := cmd.String(\"excluded-annotations\", \"\", \"Annotations to exclude when mirroring services\")\n\texcludedLabels := cmd.String(\"excluded-labels\", \"\", \"Labels to exclude when mirroring services\")\n\tprobeSvc := cmd.String(\"probe-service\", \"\", \"Name of the target cluster probe service\")\n\n\tflags.ConfigureAndParse(cmd, args)\n\tlinkName := cmd.Arg(0)\n\n\tready := false\n\tadminServer := admin.NewServer(*metricsAddr, *enablePprof, &ready)\n\n\tgo func() {\n\t\tlog.Infof(\"starting admin server on %s\", *metricsAddr)\n\t\tif err := adminServer.ListenAndServe(); err != nil {\n\t\t\tlog.Errorf(\"failed to start service mirror admin server: %s\", err)\n\t\t}\n\t}()\n\n\trootCtx, cancel := context.WithCancel(context.Background())\n\n\tstop := make(chan os.Signal, 1)\n\tsignal.Notify(stop, os.Interrupt, syscall.SIGTERM)\n\tgo func() {\n\t\t<-stop\n\t\tlog.Info(\"Received shutdown signal\")\n\t\t// Cancel root context. Cancellation will be propagated to all other\n\t\t// contexts that are children of the root context.\n\t\tcancel()\n\t}()\n\n\t// We create two different kubernetes API clients for the local cluster:\n\t// l5dClient is used for watching Link resources and updating their\n\t// statuses.\n\t//\n\t// controllerK8sAPI is used by the cluster watcher to manage\n\t// mirror resources such as services, namespaces, and endpoints.\n\n\tcontrollerK8sAPI, err := controllerK8s.InitializeAPI(\n\t\trootCtx,\n\t\t*kubeConfigPath,\n\t\tfalse,\n\t\t\"local\",\n\t\tcontrollerK8s.NS,\n\t\tcontrollerK8s.Svc,\n\t\tcontrollerK8s.Endpoint,\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to initialize K8s API: %s\", err)\n\t}\n\tconfig, err := k8s.GetConfig(*kubeConfigPath, \"\")\n\tif err != nil {\n\t\tlog.Fatalf(\"error configuring Kubernetes API client: %s\", err)\n\t}\n\tl5dClient, err := controllerK8s.NewL5DCRDClient(config)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to initialize K8s API: %s\", err)\n\t}\n\n\tmetrics := servicemirror.NewProbeMetricVecs()\n\tcontrollerK8sAPI.Sync(nil)\n\tready = true\n\n\tlinksAPI := controllerK8s.NewL5dNamespacedAPI(l5dClient, *namespace, \"local\", controllerK8s.Link)\n\tlog.Infof(\"Starting Link informer\")\n\tlinksAPI.Sync(rootCtx.Done())\n\n\tvar run func(context.Context)\n\n\tif *localMirror {\n\t\trun = func(ctx context.Context) {\n\t\t\tfederatedLabelSelector, err := metav1.ParseToLabelSelector(*federatedServiceSelector)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalf(\"failed to parse federated service selector: %s\", err)\n\t\t\t}\n\n\t\t\texcludedLabelList := []string{}\n\t\t\tif *excludedLabels != \"\" {\n\t\t\t\texcludedLabelList = strings.Split(*excludedLabels, \",\")\n\t\t\t}\n\t\t\texcludedAnnotationList := []string{}\n\t\t\tif *exludedAnnotations != \"\" {\n\t\t\t\texcludedAnnotationList = strings.Split(*exludedAnnotations, \",\")\n\t\t\t}\n\n\t\t\tlink := v1alpha3.Link{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"local\",\n\t\t\t\t\tNamespace: *namespace,\n\t\t\t\t},\n\t\t\t\tSpec: v1alpha3.LinkSpec{\n\t\t\t\t\tTargetClusterName:        \"\",\n\t\t\t\t\tSelector:                 nil,\n\t\t\t\t\tRemoteDiscoverySelector:  nil,\n\t\t\t\t\tFederatedServiceSelector: federatedLabelSelector,\n\t\t\t\t\tExcludedAnnotations:      excludedAnnotationList,\n\t\t\t\t\tExcludedLabels:           excludedLabelList,\n\t\t\t\t},\n\t\t\t}\n\t\t\terr = startLocalClusterWatcher(ctx, *namespace, controllerK8sAPI, linksAPI, *requeueLimit, *repairPeriod, *enableHeadlessSvc, *enableNamespaceCreation, link)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalf(\"Failed to start local cluster watcher: %s\", err)\n\t\t\t}\n\n\t\t\t// ctx.Done() is a one-shot channel that will be closed once\n\t\t\t// the context has been cancelled. Receiving from a closed\n\t\t\t// channel yields the value immediately.\n\t\t\t<-ctx.Done()\n\t\t\t// The channel will be closed by the leader elector when a\n\t\t\t// lease is lost, or by a background task handling SIGTERM.\n\t\t\t// Before terminating the loop, stop the workers and set\n\t\t\t// them to nil to release memory.\n\t\t\tcleanupWorkers()\n\t\t}\n\t} else {\n\t\trun = func(ctx context.Context) {\n\t\t\t// Use a small buffered channel for Link updates to avoid dropping\n\t\t\t// updates if there is an update burst.\n\t\t\tresults := make(chan *v1alpha3.Link, 100)\n\n\t\t\t_, err := linksAPI.Link().Informer().AddEventHandler(servicemirror.GetLinkHandlers(results, linkName))\n\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalf(\"Failed to add event handler to Link informer: %s\", err)\n\t\t\t}\n\n\t\t\t// Each time the link resource is updated, reload the config and restart the\n\t\t\t// cluster watcher.\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\t// ctx.Done() is a one-shot channel that will be closed once\n\t\t\t\t// the context has been cancelled. Receiving from a closed\n\t\t\t\t// channel yields the value immediately.\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\t// The channel will be closed by the leader elector when a\n\t\t\t\t\t// lease is lost, or by a background task handling SIGTERM.\n\t\t\t\t\t// Before terminating the loop, stop the workers and set\n\t\t\t\t\t// them to nil to release memory.\n\t\t\t\t\tcleanupWorkers()\n\t\t\t\tcase link := <-results:\n\t\t\t\t\tif link != nil {\n\t\t\t\t\t\tlog.Infof(\"Got updated link %s: %+v\", linkName, link)\n\t\t\t\t\t\tcreds, err := loadCredentials(ctx, link, *namespace, controllerK8sAPI.Client)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlog.Errorf(\"Failed to load remote cluster credentials: %s\", err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\terr = restartClusterWatcher(ctx, link, *namespace, *probeSvc, creds, controllerK8sAPI, linksAPI, *requeueLimit, *repairPeriod, metrics, *enableHeadlessSvc, *enableNamespaceCreation)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t// failed to restart cluster watcher; give a bit of slack\n\t\t\t\t\t\t\t// and requeue the link to give it another try\n\t\t\t\t\t\t\tlog.Error(err)\n\t\t\t\t\t\t\ttime.Sleep(linkWatchRestartAfter)\n\t\t\t\t\t\t\tresults <- link\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlog.Infof(\"Link %s deleted\", linkName)\n\t\t\t\t\t\tcleanupWorkers()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\thostname, found := os.LookupEnv(\"HOSTNAME\")\n\tif !found {\n\t\tlog.Fatal(\"Failed to fetch 'HOSTNAME' environment variable\")\n\t}\n\n\tleaseName := fmt.Sprintf(\"service-mirror-write-%s\", linkName)\n\tif *localMirror {\n\t\tleaseName = \"local-service-mirror-write\"\n\t}\n\tlock := &resourcelock.LeaseLock{\n\t\tLeaseMeta: metav1.ObjectMeta{\n\t\t\tName:      leaseName,\n\t\t\tNamespace: *namespace,\n\t\t},\n\t\tClient: controllerK8sAPI.Client.CoordinationV1(),\n\t\tLockConfig: resourcelock.ResourceLockConfig{\n\t\t\tIdentity: hostname,\n\t\t},\n\t}\n\nelection:\n\tfor {\n\t\t// RunOrDie will block until the lease is lost.\n\t\t//\n\t\t// When a lease is acquired, the OnStartedLeading callback will be\n\t\t// triggered, and a main watcher loop will be established to watch Link\n\t\t// resources.\n\t\t//\n\t\t// When the lease is lost, all watchers will be cleaned-up and we will\n\t\t// loop then attempt to re-acquire the lease.\n\t\tleaderelection.RunOrDie(rootCtx, leaderelection.LeaderElectionConfig{\n\t\t\t// When runtime context is cancelled, lock will be released. Implies any\n\t\t\t// code guarded by the lease _must_ finish before cancelling.\n\t\t\tReleaseOnCancel: true,\n\t\t\tLock:            lock,\n\t\t\tLeaseDuration:   LEASE_DURATION,\n\t\t\tRenewDeadline:   LEASE_RENEW_DEADLINE,\n\t\t\tRetryPeriod:     LEASE_RETRY_PERIOD,\n\t\t\tCallbacks: leaderelection.LeaderCallbacks{\n\t\t\t\tOnStartedLeading: func(ctx context.Context) {\n\t\t\t\t\t// When a lease is lost, RunOrDie will cancel the context\n\t\t\t\t\t// passed into the OnStartedLeading callback. This will in\n\t\t\t\t\t// turn cause us to cancel the work in the run() function,\n\t\t\t\t\t// effectively terminating and cleaning-up the watches.\n\t\t\t\t\tlog.Info(\"Starting controller loop\")\n\t\t\t\t\trun(ctx)\n\t\t\t\t},\n\t\t\t\tOnStoppedLeading: func() {\n\t\t\t\t\tlog.Infof(\"%s released lease\", hostname)\n\t\t\t\t},\n\t\t\t\tOnNewLeader: func(identity string) {\n\t\t\t\t\tif identity == hostname {\n\t\t\t\t\t\tlog.Infof(\"%s acquired lease\", hostname)\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\n\t\tselect {\n\t\t// If the lease has been lost, and we have received a shutdown signal,\n\t\t// break the loop and gracefully exit. We can guarantee at this point\n\t\t// resources have been released.\n\t\tcase <-rootCtx.Done():\n\t\t\tbreak election\n\t\t// If the lease has been lost, loop and attempt to re-acquire it.\n\t\tdefault:\n\n\t\t}\n\t}\n\tlog.Info(\"Shutting down\")\n}\n\n// cleanupWorkers is a utility function that checks whether the worker pointers\n// (clusterWatcher and probeWorker) are instantiated, and if they are, stops\n// their execution and sets the pointers to a nil value so that memory may be\n// garbage collected.\nfunc cleanupWorkers() {\n\tif clusterWatcher != nil {\n\t\t// release, but do not clean-up services created\n\t\t// the `unlink` command will take care of that\n\t\tclusterWatcher.Stop(false)\n\t\tclusterWatcher = nil\n\t}\n\n\tif probeWorker != nil {\n\t\tprobeWorker.Stop()\n\t\tprobeWorker = nil\n\t}\n}\n\nfunc loadCredentials(ctx context.Context, link *v1alpha3.Link, namespace string, k8sAPI kubernetes.Interface) ([]byte, error) {\n\t// Load the credentials secret\n\tsecret, err := k8sAPI.CoreV1().Secrets(namespace).Get(ctx, link.Spec.ClusterCredentialsSecret, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to load credentials secret %s: %w\", link.Spec.ClusterCredentialsSecret, err)\n\t}\n\treturn sm.ParseRemoteClusterSecret(secret)\n}\n\nfunc restartClusterWatcher(\n\tctx context.Context,\n\tlink *v1alpha3.Link,\n\tnamespace,\n\tprobeSvc string,\n\tcreds []byte,\n\tcontrollerK8sAPI *controllerK8s.API,\n\tlinksAPI *controllerK8s.API,\n\trequeueLimit int,\n\trepairPeriod time.Duration,\n\tmetrics servicemirror.ProbeMetricVecs,\n\tenableHeadlessSvc bool,\n\tenableNamespaceCreation bool,\n) error {\n\n\tcleanupWorkers()\n\n\tworkerMetrics, err := metrics.NewWorkerMetrics(link.Spec.TargetClusterName)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create metrics for cluster watcher: %w\", err)\n\t}\n\n\t// If linked against a cluster that has a gateway, start a probe and\n\t// initialise the liveness channel\n\tvar ch chan bool\n\tif link.Spec.ProbeSpec.Path != \"\" {\n\t\tprobeWorker = servicemirror.NewProbeWorker(probeSvc, &link.Spec.ProbeSpec, workerMetrics, link.Spec.TargetClusterName)\n\t\tprobeWorker.Start()\n\t\tch = probeWorker.Liveness\n\t}\n\n\t// Start cluster watcher\n\tcfg, err := clientcmd.RESTConfigFromKubeConfig(creds)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to parse kube config: %w\", err)\n\t}\n\tremoteAPI, err := controllerK8s.InitializeAPIForConfig(ctx, cfg, false, link.Spec.TargetClusterName, controllerK8s.Svc, controllerK8s.Endpoint)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot initialize api for target cluster %s: %w\", link.Spec.TargetClusterName, err)\n\t}\n\tcw, err := servicemirror.NewRemoteClusterServiceWatcher(\n\t\tctx,\n\t\tnamespace,\n\t\tcontrollerK8sAPI,\n\t\tremoteAPI,\n\t\tlinksAPI,\n\t\tprobeSvc,\n\t\tlink,\n\t\trequeueLimit,\n\t\trepairPeriod,\n\t\tch,\n\t\tenableHeadlessSvc,\n\t\tenableNamespaceCreation,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to create cluster watcher: %w\", err)\n\t}\n\tclusterWatcher = cw\n\terr = clusterWatcher.Start(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to start cluster watcher: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc startLocalClusterWatcher(\n\tctx context.Context,\n\tnamespace string,\n\tcontrollerK8sAPI *controllerK8s.API,\n\tlinksAPI *controllerK8s.API,\n\trequeueLimit int,\n\trepairPeriod time.Duration,\n\tenableHeadlessSvc bool,\n\tenableNamespaceCreation bool,\n\tlink v1alpha3.Link,\n) error {\n\tcw, err := servicemirror.NewRemoteClusterServiceWatcher(\n\t\tctx,\n\t\tnamespace,\n\t\tcontrollerK8sAPI,\n\t\tcontrollerK8sAPI,\n\t\tlinksAPI,\n\t\t\"\",\n\t\t&link,\n\t\trequeueLimit,\n\t\trepairPeriod,\n\t\tmake(chan bool),\n\t\tenableHeadlessSvc,\n\t\tenableNamespaceCreation,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to create cluster watcher: %w\", err)\n\t}\n\tclusterWatcher = cw\n\terr = clusterWatcher.Start(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to start cluster watcher: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "multicluster/cmd/testdata/install_default.golden",
    "content": "kind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n    pod-security.kubernetes.io/enforce: privileged\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\n  labels:\n    app.kubernetes.io/name: gateway\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerdVersionValue\n    component: gateway\n    app: linkerd-gateway\n    linkerd.io/extension: multicluster\n  name: linkerd-gateway\n  namespace: linkerd-multicluster\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      app: linkerd-gateway\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/helm linkerdVersionValue\n        linkerd.io/inject: enabled\n        config.linkerd.io/proxy-require-identity-inbound-ports: \"4143\"\n        config.linkerd.io/enable-gateway: \"true\"\n        config.linkerd.io/default-inbound-policy: all-authenticated\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        app: linkerd-gateway\n        linkerd.io/extension: multicluster\n    spec:\n      automountServiceAccountToken: false\n      containers:\n      - name: pause\n        image: registry.k8s.io/pause:3.2\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-gateway\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: linkerd-gateway\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    mirror.linkerd.io/gateway-identity: linkerd-gateway.linkerd-multicluster.serviceaccount.identity.linkerd.cluster.local\n    mirror.linkerd.io/probe-period: \"3\"\n    mirror.linkerd.io/probe-path: /ready\n    mirror.linkerd.io/multicluster-gateway: \"true\"\n    component: gateway\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  ports:\n  - name: mc-gateway\n    port: 4143\n    protocol: TCP\n  - name: mc-probe\n    port: 4191\n    protocol: TCP\n  selector:\n    app: linkerd-gateway\n  type: LoadBalancer\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-gateway\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-multicluster\n  name: linkerd-gateway\n  labels:\n    linkerd.io/extension: multicluster\n    app: linkerd-gateway\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  podSelector:\n    matchLabels:\n      app: linkerd-gateway\n  port: linkerd-proxy\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-multicluster\n  name: linkerd-gateway\n  labels:\n    linkerd.io/extension: multicluster\n    app: linkerd-gateway\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: linkerd-gateway\n  requiredAuthenticationRefs:\n    - group: policy.linkerd.io\n      kind: MeshTLSAuthentication\n      name: any-meshed\n      namespace: linkerd-multicluster\n    - group: policy.linkerd.io\n      kind: NetworkAuthentication\n      name: source-cluster\n      namespace: linkerd-multicluster\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: MeshTLSAuthentication\nmetadata:\n  namespace: linkerd-multicluster\n  name: any-meshed\n  labels:\n    linkerd.io/extension: multicluster\n    app: linkerd-gateway\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  identities:\n  - '*'\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: linkerd-multicluster\n  name: source-cluster\n  labels:\n    linkerd.io/extension: multicluster\n    app: linkerd-gateway\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  networks:\n    # Change this to the source cluster cidrs pointing to this gateway.\n    # Note that the source IP in some providers (e.g. GKE) will be the local\n    # node's IP and not the source cluster's\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-service-mirror-remote-access-default\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"policy.linkerd.io\"]\n  resources: [\"servers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: linkerd-service-mirror-remote-access-default\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: linkerd-service-mirror-remote-access-default-token\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    kubernetes.io/service-account.name: linkerd-service-mirror-remote-access-default\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\ntype: kubernetes.io/service-account-token\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-service-mirror-remote-access-default\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-service-mirror-remote-access-default\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-service-mirror-remote-access-default\n  namespace: linkerd-multicluster\n---\n###\n### Link CRD\n###\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: links.multicluster.linkerd.io\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  group: multicluster.linkerd.io\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              clusterCredentialsSecret:\n                description: Kubernetes secret of target cluster\n                type: string\n              gatewayAddress:\n                description: Gateway address of target cluster\n                type: string\n              gatewayIdentity:\n                description: Gateway Identity FQDN\n                type: string\n              gatewayPort:\n                description: Gateway Port\n                type: string\n              probeSpec:\n                description: Spec for gateway health probe\n                type: object\n                properties:\n                  failureThreshold:\n                    default: \"3\"\n                    description: Minimum consecutive failures for the probe to be considered failed\n                    type: string\n                  path:\n                    description: Path of remote gateway health endpoint\n                    type: string\n                  period:\n                    description: Interval in between probe requests\n                    type: string\n                  port:\n                    description: Port of remote gateway health endpoint\n                    type: string\n                  timeout:\n                    default: 30s\n                    description: Probe request timeout\n                    format: duration\n                    type: string\n              selector:\n                description: Kubernetes Label Selector\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              remoteDiscoverySelector:\n                description: Selector for Services to mirror in remote discovery mode\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              targetClusterName:\n                description: Name of target cluster to link to\n                type: string\n              targetClusterDomain:\n                description: Domain name of target cluster to link to\n                type: string\n              targetClusterLinkerdNamespace:\n                description: Name of namespace Linkerd control plane is installed in on target cluster\n                type: string\n  - name: v1alpha2\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              clusterCredentialsSecret:\n                description: Kubernetes secret of target cluster\n                type: string\n              gatewayAddress:\n                description: Gateway address of target cluster\n                type: string\n              gatewayIdentity:\n                description: Gateway Identity FQDN\n                type: string\n              gatewayPort:\n                description: Gateway Port\n                type: string\n              probeSpec:\n                description: Spec for gateway health probe\n                type: object\n                properties:\n                  failureThreshold:\n                    default: \"3\"\n                    description: Minimum consecutive failures for the probe to be considered failed\n                    type: string\n                  path:\n                    description: Path of remote gateway health endpoint\n                    type: string\n                  period:\n                    description: Interval in between probe requests\n                    format: duration\n                    type: string\n                  port:\n                    description: Port of remote gateway health endpoint\n                    type: string\n                  timeout:\n                    default: 30s\n                    description: Probe request timeout\n                    format: duration\n                    type: string\n              selector:\n                description: Kubernetes Label Selector\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              remoteDiscoverySelector:\n                description: Selector for Services to mirror in remote discovery mode\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              federatedServiceSelector:\n                description: Selector for federated service memebers\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              targetClusterName:\n                description: Name of target cluster to link to\n                type: string\n              targetClusterDomain:\n                description: Domain name of target cluster to link to\n                type: string\n              targetClusterLinkerdNamespace:\n                description: Name of namespace Linkerd control plane is installed in on target cluster\n                type: string\n          status:\n            description: Status defines the state of resources managed by this Link\n            properties:\n              mirrorServices:\n                description: List of services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a mirrored service\n                  properties:\n                    conditions:\n                      description: Conditions of the mirrored service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n              federatedServices:\n                description: List of federated services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a federated service\n                  properties:\n                    conditions:\n                      description: Conditions of the federated service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n            type: object\n    subresources:\n      status: {}\n  - name: v1alpha3\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              clusterCredentialsSecret:\n                description: Kubernetes secret of target cluster\n                type: string\n              gatewayAddress:\n                description: Gateway address of target cluster\n                type: string\n              gatewayIdentity:\n                description: Gateway Identity FQDN\n                type: string\n              gatewayPort:\n                description: Gateway Port\n                type: string\n              probeSpec:\n                description: Spec for gateway health probe\n                type: object\n                properties:\n                  failureThreshold:\n                    default: \"3\"\n                    description: Minimum consecutive failures for the probe to be considered failed\n                    type: string\n                  path:\n                    description: Path of remote gateway health endpoint\n                    type: string\n                  period:\n                    description: Interval in between probe requests\n                    format: duration\n                    type: string\n                  port:\n                    description: Port of remote gateway health endpoint\n                    type: string\n                  timeout:\n                    default: 30s\n                    description: Probe request timeout\n                    format: duration\n                    type: string\n              selector:\n                description: Kubernetes Label Selector\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              remoteDiscoverySelector:\n                description: Selector for Services to mirror in remote discovery mode\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              federatedServiceSelector:\n                description: Selector for federated service memebers\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              targetClusterName:\n                description: Name of target cluster to link to\n                type: string\n              targetClusterDomain:\n                description: Domain name of target cluster to link to\n                type: string\n              targetClusterLinkerdNamespace:\n                description: Name of namespace Linkerd control plane is installed in on target cluster\n                type: string\n              excludedAnnotations:\n                description: List of annotations which should not be copied to the federated service\n                type: array\n                items:\n                  type: string\n              excludedLabels:\n                description: List of labels which should not be copied to the federated service\n                type: array\n                items:\n                  type: string\n          status:\n            description: Status defines the state of resources managed by this Link\n            properties:\n              mirrorServices:\n                description: List of services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a mirrored service\n                  properties:\n                    conditions:\n                      description: Conditions of the mirrored service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n              federatedServices:\n                description: List of federated services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a federated service\n                  properties:\n                    conditions:\n                      description: Conditions of the federated service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n            type: object\n    subresources:\n      status: {}\n  scope: Namespaced\n  names:\n    plural: links\n    singular: link\n    kind: Link\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-multicluster\n  name: service-mirror\n  labels:\n    linkerd.io/extension: multicluster\n    component: linkerd-service-mirror\nspec:\n  podSelector:\n    matchExpressions:\n    - key: component\n      operator: In\n      values:\n      - linkerd-service-mirror\n      - controller\n  port: svcmi-admin\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-multicluster\n  name: service-mirror\n  labels:\n    linkerd.io/extension: multicluster\n    component: linkerd-service-mirror\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: service-mirror\n  requiredAuthenticationRefs:\n    # In order to use `linkerd mc gateways` you need viz' Prometheus instance\n    # to be able to reach the service-mirror. In order to also have a separate\n    # Prometheus scrape the service-mirror an additional AuthorizationPolicy\n    # resource should be created.\n    - kind: ServiceAccount\n      name: prometheus\n      namespace: linkerd-viz\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-local-service-mirror-access-local-resources\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\nrules:\n- apiGroups: [\"\"]\n  resources: [\"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"delete\", \"update\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"multicluster.linkerd.io\"]\n  resources: [\"links\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"multicluster.linkerd.io\"]\n  resources: [\"links/status\"]\n  verbs: [\"update\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-local-service-mirror-access-local-resources\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-local-service-mirror-access-local-resources\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-local-service-mirror\n  namespace: linkerd-multicluster\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-local-service-mirror\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\n  name: linkerd-local-service-mirror\n  namespace: linkerd-multicluster\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      component: local-service-mirror\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n      labels:\n        linkerd.io/extension: multicluster\n        component: local-service-mirror\n    spec:\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - service-mirror\n        - -log-level=info\n        - -log-format=plain\n        - -event-requeue-limit=3\n        - -namespace=linkerd-multicluster\n        - -enable-pprof=false\n        - -local-mirror\n        - -federated-service-selector=mirror.linkerd.io/federated=member\n        - -excluded-labels=\n        - -excluded-annotations=\n        image: cr.l5d.io/linkerd/controller:linkerdVersionValue\n        name: service-mirror\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n        ports:\n        - containerPort: 9999\n          name: locsm-admin\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-local-service-mirror\n      volumes:\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-multicluster-controller-access-local-resources\n  labels:\n    linkerd.io/extension: multicluster\n    component: controller\nrules:\n- apiGroups: [\"\"]\n  resources: [\"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"delete\", \"update\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n\n\n"
  },
  {
    "path": "multicluster/cmd/testdata/install_ha.golden",
    "content": "kind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n    pod-security.kubernetes.io/enforce: privileged\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\n  labels:\n    app.kubernetes.io/name: gateway\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerdVersionValue\n    component: gateway\n    app: linkerd-gateway\n    linkerd.io/extension: multicluster\n  name: linkerd-gateway\n  namespace: linkerd-multicluster\nspec:\n  replicas: 3\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      app: linkerd-gateway\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/helm linkerdVersionValue\n        linkerd.io/inject: enabled\n        config.linkerd.io/proxy-require-identity-inbound-ports: \"4143\"\n        config.linkerd.io/enable-gateway: \"true\"\n        config.linkerd.io/default-inbound-policy: all-authenticated\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        app: linkerd-gateway\n        linkerd.io/extension: multicluster\n    spec:\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: app\n                  operator: In\n                  values:\n                  - linkerd-gateway\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: app\n                operator: In\n                values:\n                - linkerd-gateway\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - name: pause\n        image: registry.k8s.io/pause:3.2\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-gateway\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-gateway\n  namespace: linkerd-multicluster\n  labels:\n    app: linkerd-gateway\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      app: linkerd-gateway\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: linkerd-gateway\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    mirror.linkerd.io/gateway-identity: linkerd-gateway.linkerd-multicluster.serviceaccount.identity.linkerd.cluster.local\n    mirror.linkerd.io/probe-period: \"3\"\n    mirror.linkerd.io/probe-path: /ready\n    mirror.linkerd.io/multicluster-gateway: \"true\"\n    component: gateway\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  ports:\n  - name: mc-gateway\n    port: 4143\n    protocol: TCP\n  - name: mc-probe\n    port: 4191\n    protocol: TCP\n  selector:\n    app: linkerd-gateway\n  type: LoadBalancer\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-gateway\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-multicluster\n  name: linkerd-gateway\n  labels:\n    linkerd.io/extension: multicluster\n    app: linkerd-gateway\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  podSelector:\n    matchLabels:\n      app: linkerd-gateway\n  port: linkerd-proxy\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-multicluster\n  name: linkerd-gateway\n  labels:\n    linkerd.io/extension: multicluster\n    app: linkerd-gateway\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: linkerd-gateway\n  requiredAuthenticationRefs:\n    - group: policy.linkerd.io\n      kind: MeshTLSAuthentication\n      name: any-meshed\n      namespace: linkerd-multicluster\n    - group: policy.linkerd.io\n      kind: NetworkAuthentication\n      name: source-cluster\n      namespace: linkerd-multicluster\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: MeshTLSAuthentication\nmetadata:\n  namespace: linkerd-multicluster\n  name: any-meshed\n  labels:\n    linkerd.io/extension: multicluster\n    app: linkerd-gateway\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  identities:\n  - '*'\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: linkerd-multicluster\n  name: source-cluster\n  labels:\n    linkerd.io/extension: multicluster\n    app: linkerd-gateway\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  networks:\n    # Change this to the source cluster cidrs pointing to this gateway.\n    # Note that the source IP in some providers (e.g. GKE) will be the local\n    # node's IP and not the source cluster's\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: psp\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\nrules:\n- apiGroups: ['policy', 'extensions']\n  resources: ['podsecuritypolicies']\n  verbs: ['use']\n  resourceNames:\n  - linkerd-linkerd-control-plane\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-multicluster-psp\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n    namespace: linkerd-multicluster\nroleRef:\n  kind: Role\n  name: psp\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-gateway\n  namespace: linkerd-multicluster\n- kind: ServiceAccount\n  name: namespace-metadata\n  namespace: linkerd-multicluster\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-service-mirror-remote-access-default\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"policy.linkerd.io\"]\n  resources: [\"servers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: linkerd-service-mirror-remote-access-default\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: linkerd-service-mirror-remote-access-default-token\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    kubernetes.io/service-account.name: linkerd-service-mirror-remote-access-default\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\ntype: kubernetes.io/service-account-token\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-service-mirror-remote-access-default\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-service-mirror-remote-access-default\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-service-mirror-remote-access-default\n  namespace: linkerd-multicluster\n---\n###\n### Link CRD\n###\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: links.multicluster.linkerd.io\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  group: multicluster.linkerd.io\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              clusterCredentialsSecret:\n                description: Kubernetes secret of target cluster\n                type: string\n              gatewayAddress:\n                description: Gateway address of target cluster\n                type: string\n              gatewayIdentity:\n                description: Gateway Identity FQDN\n                type: string\n              gatewayPort:\n                description: Gateway Port\n                type: string\n              probeSpec:\n                description: Spec for gateway health probe\n                type: object\n                properties:\n                  failureThreshold:\n                    default: \"3\"\n                    description: Minimum consecutive failures for the probe to be considered failed\n                    type: string\n                  path:\n                    description: Path of remote gateway health endpoint\n                    type: string\n                  period:\n                    description: Interval in between probe requests\n                    type: string\n                  port:\n                    description: Port of remote gateway health endpoint\n                    type: string\n                  timeout:\n                    default: 30s\n                    description: Probe request timeout\n                    format: duration\n                    type: string\n              selector:\n                description: Kubernetes Label Selector\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              remoteDiscoverySelector:\n                description: Selector for Services to mirror in remote discovery mode\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              targetClusterName:\n                description: Name of target cluster to link to\n                type: string\n              targetClusterDomain:\n                description: Domain name of target cluster to link to\n                type: string\n              targetClusterLinkerdNamespace:\n                description: Name of namespace Linkerd control plane is installed in on target cluster\n                type: string\n  - name: v1alpha2\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              clusterCredentialsSecret:\n                description: Kubernetes secret of target cluster\n                type: string\n              gatewayAddress:\n                description: Gateway address of target cluster\n                type: string\n              gatewayIdentity:\n                description: Gateway Identity FQDN\n                type: string\n              gatewayPort:\n                description: Gateway Port\n                type: string\n              probeSpec:\n                description: Spec for gateway health probe\n                type: object\n                properties:\n                  failureThreshold:\n                    default: \"3\"\n                    description: Minimum consecutive failures for the probe to be considered failed\n                    type: string\n                  path:\n                    description: Path of remote gateway health endpoint\n                    type: string\n                  period:\n                    description: Interval in between probe requests\n                    format: duration\n                    type: string\n                  port:\n                    description: Port of remote gateway health endpoint\n                    type: string\n                  timeout:\n                    default: 30s\n                    description: Probe request timeout\n                    format: duration\n                    type: string\n              selector:\n                description: Kubernetes Label Selector\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              remoteDiscoverySelector:\n                description: Selector for Services to mirror in remote discovery mode\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              federatedServiceSelector:\n                description: Selector for federated service memebers\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              targetClusterName:\n                description: Name of target cluster to link to\n                type: string\n              targetClusterDomain:\n                description: Domain name of target cluster to link to\n                type: string\n              targetClusterLinkerdNamespace:\n                description: Name of namespace Linkerd control plane is installed in on target cluster\n                type: string\n          status:\n            description: Status defines the state of resources managed by this Link\n            properties:\n              mirrorServices:\n                description: List of services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a mirrored service\n                  properties:\n                    conditions:\n                      description: Conditions of the mirrored service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n              federatedServices:\n                description: List of federated services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a federated service\n                  properties:\n                    conditions:\n                      description: Conditions of the federated service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n            type: object\n    subresources:\n      status: {}\n  - name: v1alpha3\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              clusterCredentialsSecret:\n                description: Kubernetes secret of target cluster\n                type: string\n              gatewayAddress:\n                description: Gateway address of target cluster\n                type: string\n              gatewayIdentity:\n                description: Gateway Identity FQDN\n                type: string\n              gatewayPort:\n                description: Gateway Port\n                type: string\n              probeSpec:\n                description: Spec for gateway health probe\n                type: object\n                properties:\n                  failureThreshold:\n                    default: \"3\"\n                    description: Minimum consecutive failures for the probe to be considered failed\n                    type: string\n                  path:\n                    description: Path of remote gateway health endpoint\n                    type: string\n                  period:\n                    description: Interval in between probe requests\n                    format: duration\n                    type: string\n                  port:\n                    description: Port of remote gateway health endpoint\n                    type: string\n                  timeout:\n                    default: 30s\n                    description: Probe request timeout\n                    format: duration\n                    type: string\n              selector:\n                description: Kubernetes Label Selector\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              remoteDiscoverySelector:\n                description: Selector for Services to mirror in remote discovery mode\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              federatedServiceSelector:\n                description: Selector for federated service memebers\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              targetClusterName:\n                description: Name of target cluster to link to\n                type: string\n              targetClusterDomain:\n                description: Domain name of target cluster to link to\n                type: string\n              targetClusterLinkerdNamespace:\n                description: Name of namespace Linkerd control plane is installed in on target cluster\n                type: string\n              excludedAnnotations:\n                description: List of annotations which should not be copied to the federated service\n                type: array\n                items:\n                  type: string\n              excludedLabels:\n                description: List of labels which should not be copied to the federated service\n                type: array\n                items:\n                  type: string\n          status:\n            description: Status defines the state of resources managed by this Link\n            properties:\n              mirrorServices:\n                description: List of services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a mirrored service\n                  properties:\n                    conditions:\n                      description: Conditions of the mirrored service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n              federatedServices:\n                description: List of federated services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a federated service\n                  properties:\n                    conditions:\n                      description: Conditions of the federated service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n            type: object\n    subresources:\n      status: {}\n  scope: Namespaced\n  names:\n    plural: links\n    singular: link\n    kind: Link\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-multicluster\n  name: service-mirror\n  labels:\n    linkerd.io/extension: multicluster\n    component: linkerd-service-mirror\nspec:\n  podSelector:\n    matchExpressions:\n    - key: component\n      operator: In\n      values:\n      - linkerd-service-mirror\n      - controller\n  port: svcmi-admin\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-multicluster\n  name: service-mirror\n  labels:\n    linkerd.io/extension: multicluster\n    component: linkerd-service-mirror\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: service-mirror\n  requiredAuthenticationRefs:\n    # In order to use `linkerd mc gateways` you need viz' Prometheus instance\n    # to be able to reach the service-mirror. In order to also have a separate\n    # Prometheus scrape the service-mirror an additional AuthorizationPolicy\n    # resource should be created.\n    - kind: ServiceAccount\n      name: prometheus\n      namespace: linkerd-viz\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-local-service-mirror-access-local-resources\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\nrules:\n- apiGroups: [\"\"]\n  resources: [\"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"delete\", \"update\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"multicluster.linkerd.io\"]\n  resources: [\"links\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"multicluster.linkerd.io\"]\n  resources: [\"links/status\"]\n  verbs: [\"update\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-local-service-mirror-access-local-resources\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-local-service-mirror-access-local-resources\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-local-service-mirror\n  namespace: linkerd-multicluster\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-local-service-mirror\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\n  name: linkerd-local-service-mirror\n  namespace: linkerd-multicluster\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      component: local-service-mirror\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n      labels:\n        linkerd.io/extension: multicluster\n        component: local-service-mirror\n    spec:\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: component\n                  operator: In\n                  values:\n                  - local-service-mirror\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: component\n                operator: In\n                values:\n                - local-service-mirror\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - service-mirror\n        - -log-level=info\n        - -log-format=plain\n        - -event-requeue-limit=3\n        - -namespace=linkerd-multicluster\n        - -enable-pprof=false\n        - -local-mirror\n        - -federated-service-selector=mirror.linkerd.io/federated=member\n        - -excluded-labels=\n        - -excluded-annotations=\n        image: cr.l5d.io/linkerd/controller:linkerdVersionValue\n        name: service-mirror\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n        ports:\n        - containerPort: 9999\n          name: locsm-admin\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-local-service-mirror\n      volumes:\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-local-service-mirror\n  namespace: linkerd-multicluster\n  labels:\n    component: local-service-mirror\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      component: local-service-mirror\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-multicluster-controller-access-local-resources\n  labels:\n    linkerd.io/extension: multicluster\n    component: controller\nrules:\n- apiGroups: [\"\"]\n  resources: [\"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"delete\", \"update\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n\n\n"
  },
  {
    "path": "multicluster/cmd/testdata/install_psp.golden",
    "content": "kind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n    pod-security.kubernetes.io/enforce: privileged\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\n  labels:\n    app.kubernetes.io/name: gateway\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: linkerdVersionValue\n    component: gateway\n    app: linkerd-gateway\n    linkerd.io/extension: multicluster\n  name: linkerd-gateway\n  namespace: linkerd-multicluster\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      app: linkerd-gateway\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/helm linkerdVersionValue\n        linkerd.io/inject: enabled\n        config.linkerd.io/proxy-require-identity-inbound-ports: \"4143\"\n        config.linkerd.io/enable-gateway: \"true\"\n        config.linkerd.io/default-inbound-policy: all-authenticated\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        app: linkerd-gateway\n        linkerd.io/extension: multicluster\n    spec:\n      automountServiceAccountToken: false\n      containers:\n      - name: pause\n        image: registry.k8s.io/pause:3.2\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-gateway\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: linkerd-gateway\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    mirror.linkerd.io/gateway-identity: linkerd-gateway.linkerd-multicluster.serviceaccount.identity.linkerd.cluster.local\n    mirror.linkerd.io/probe-period: \"3\"\n    mirror.linkerd.io/probe-path: /ready\n    mirror.linkerd.io/multicluster-gateway: \"true\"\n    component: gateway\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  ports:\n  - name: mc-gateway\n    port: 4143\n    protocol: TCP\n  - name: mc-probe\n    port: 4191\n    protocol: TCP\n  selector:\n    app: linkerd-gateway\n  type: LoadBalancer\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-gateway\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-multicluster\n  name: linkerd-gateway\n  labels:\n    linkerd.io/extension: multicluster\n    app: linkerd-gateway\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  podSelector:\n    matchLabels:\n      app: linkerd-gateway\n  port: linkerd-proxy\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-multicluster\n  name: linkerd-gateway\n  labels:\n    linkerd.io/extension: multicluster\n    app: linkerd-gateway\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: linkerd-gateway\n  requiredAuthenticationRefs:\n    - group: policy.linkerd.io\n      kind: MeshTLSAuthentication\n      name: any-meshed\n      namespace: linkerd-multicluster\n    - group: policy.linkerd.io\n      kind: NetworkAuthentication\n      name: source-cluster\n      namespace: linkerd-multicluster\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: MeshTLSAuthentication\nmetadata:\n  namespace: linkerd-multicluster\n  name: any-meshed\n  labels:\n    linkerd.io/extension: multicluster\n    app: linkerd-gateway\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  identities:\n  - '*'\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: linkerd-multicluster\n  name: source-cluster\n  labels:\n    linkerd.io/extension: multicluster\n    app: linkerd-gateway\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  networks:\n    # Change this to the source cluster cidrs pointing to this gateway.\n    # Note that the source IP in some providers (e.g. GKE) will be the local\n    # node's IP and not the source cluster's\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: psp\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\nrules:\n- apiGroups: ['policy', 'extensions']\n  resources: ['podsecuritypolicies']\n  verbs: ['use']\n  resourceNames:\n  - linkerd-linkerd-control-plane\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-multicluster-psp\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n    namespace: linkerd-multicluster\nroleRef:\n  kind: Role\n  name: psp\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-gateway\n  namespace: linkerd-multicluster\n- kind: ServiceAccount\n  name: namespace-metadata\n  namespace: linkerd-multicluster\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-service-mirror-remote-access-default\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"policy.linkerd.io\"]\n  resources: [\"servers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: linkerd-service-mirror-remote-access-default\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: linkerd-service-mirror-remote-access-default-token\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    kubernetes.io/service-account.name: linkerd-service-mirror-remote-access-default\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\ntype: kubernetes.io/service-account-token\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-service-mirror-remote-access-default\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-service-mirror-remote-access-default\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-service-mirror-remote-access-default\n  namespace: linkerd-multicluster\n---\n###\n### Link CRD\n###\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: links.multicluster.linkerd.io\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/helm linkerdVersionValue\nspec:\n  group: multicluster.linkerd.io\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              clusterCredentialsSecret:\n                description: Kubernetes secret of target cluster\n                type: string\n              gatewayAddress:\n                description: Gateway address of target cluster\n                type: string\n              gatewayIdentity:\n                description: Gateway Identity FQDN\n                type: string\n              gatewayPort:\n                description: Gateway Port\n                type: string\n              probeSpec:\n                description: Spec for gateway health probe\n                type: object\n                properties:\n                  failureThreshold:\n                    default: \"3\"\n                    description: Minimum consecutive failures for the probe to be considered failed\n                    type: string\n                  path:\n                    description: Path of remote gateway health endpoint\n                    type: string\n                  period:\n                    description: Interval in between probe requests\n                    type: string\n                  port:\n                    description: Port of remote gateway health endpoint\n                    type: string\n                  timeout:\n                    default: 30s\n                    description: Probe request timeout\n                    format: duration\n                    type: string\n              selector:\n                description: Kubernetes Label Selector\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              remoteDiscoverySelector:\n                description: Selector for Services to mirror in remote discovery mode\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              targetClusterName:\n                description: Name of target cluster to link to\n                type: string\n              targetClusterDomain:\n                description: Domain name of target cluster to link to\n                type: string\n              targetClusterLinkerdNamespace:\n                description: Name of namespace Linkerd control plane is installed in on target cluster\n                type: string\n  - name: v1alpha2\n    served: true\n    storage: false\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              clusterCredentialsSecret:\n                description: Kubernetes secret of target cluster\n                type: string\n              gatewayAddress:\n                description: Gateway address of target cluster\n                type: string\n              gatewayIdentity:\n                description: Gateway Identity FQDN\n                type: string\n              gatewayPort:\n                description: Gateway Port\n                type: string\n              probeSpec:\n                description: Spec for gateway health probe\n                type: object\n                properties:\n                  failureThreshold:\n                    default: \"3\"\n                    description: Minimum consecutive failures for the probe to be considered failed\n                    type: string\n                  path:\n                    description: Path of remote gateway health endpoint\n                    type: string\n                  period:\n                    description: Interval in between probe requests\n                    format: duration\n                    type: string\n                  port:\n                    description: Port of remote gateway health endpoint\n                    type: string\n                  timeout:\n                    default: 30s\n                    description: Probe request timeout\n                    format: duration\n                    type: string\n              selector:\n                description: Kubernetes Label Selector\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              remoteDiscoverySelector:\n                description: Selector for Services to mirror in remote discovery mode\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              federatedServiceSelector:\n                description: Selector for federated service memebers\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              targetClusterName:\n                description: Name of target cluster to link to\n                type: string\n              targetClusterDomain:\n                description: Domain name of target cluster to link to\n                type: string\n              targetClusterLinkerdNamespace:\n                description: Name of namespace Linkerd control plane is installed in on target cluster\n                type: string\n          status:\n            description: Status defines the state of resources managed by this Link\n            properties:\n              mirrorServices:\n                description: List of services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a mirrored service\n                  properties:\n                    conditions:\n                      description: Conditions of the mirrored service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n              federatedServices:\n                description: List of federated services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a federated service\n                  properties:\n                    conditions:\n                      description: Conditions of the federated service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n            type: object\n    subresources:\n      status: {}\n  - name: v1alpha3\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              clusterCredentialsSecret:\n                description: Kubernetes secret of target cluster\n                type: string\n              gatewayAddress:\n                description: Gateway address of target cluster\n                type: string\n              gatewayIdentity:\n                description: Gateway Identity FQDN\n                type: string\n              gatewayPort:\n                description: Gateway Port\n                type: string\n              probeSpec:\n                description: Spec for gateway health probe\n                type: object\n                properties:\n                  failureThreshold:\n                    default: \"3\"\n                    description: Minimum consecutive failures for the probe to be considered failed\n                    type: string\n                  path:\n                    description: Path of remote gateway health endpoint\n                    type: string\n                  period:\n                    description: Interval in between probe requests\n                    format: duration\n                    type: string\n                  port:\n                    description: Port of remote gateway health endpoint\n                    type: string\n                  timeout:\n                    default: 30s\n                    description: Probe request timeout\n                    format: duration\n                    type: string\n              selector:\n                description: Kubernetes Label Selector\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              remoteDiscoverySelector:\n                description: Selector for Services to mirror in remote discovery mode\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              federatedServiceSelector:\n                description: Selector for federated service memebers\n                type: object\n                properties:\n                  matchLabels:\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  matchExpressions:\n                    description: List of selector requirements\n                    type: array\n                    items:\n                      description: A selector item requires a key and an operator\n                      type: object\n                      required:\n                      - key\n                      - operator\n                      properties:\n                        key:\n                          description: Label key that selector should apply to\n                          type: string\n                        operator:\n                          description: Evaluation of a label in relation to set\n                          type: string\n                          enum: [In, NotIn, Exists, DoesNotExist]\n                        values:\n                          type: array\n                          items:\n                            type: string\n              targetClusterName:\n                description: Name of target cluster to link to\n                type: string\n              targetClusterDomain:\n                description: Domain name of target cluster to link to\n                type: string\n              targetClusterLinkerdNamespace:\n                description: Name of namespace Linkerd control plane is installed in on target cluster\n                type: string\n              excludedAnnotations:\n                description: List of annotations which should not be copied to the federated service\n                type: array\n                items:\n                  type: string\n              excludedLabels:\n                description: List of labels which should not be copied to the federated service\n                type: array\n                items:\n                  type: string\n          status:\n            description: Status defines the state of resources managed by this Link\n            properties:\n              mirrorServices:\n                description: List of services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a mirrored service\n                  properties:\n                    conditions:\n                      description: Conditions of the mirrored service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n              federatedServices:\n                description: List of federated services mirrored by this Link\n                type: array\n                items:\n                  description: The status of a federated service\n                  properties:\n                    conditions:\n                      description: Conditions of the federated service\n                      type: array\n                      items:\n                        description: The status of a condition\n                        properties:\n                          lastTransitionTime:\n                            description: lastTransitionTime is the last time the condition\n                              transitioned from one status to another. This should\n                              be when the underlying condition changed.  If that is\n                              not known, then using the time when the API field changed\n                              is acceptable.\n                            format: date-time\n                            type: string\n                          message:\n                            description: message is a human readable message indicating\n                              details about the transition. This may be an empty string.\n                            type: string\n                          reason:\n                            description: reason contains a programmatic identifier\n                              indicating the reason for the condition's last transition.\n                              Producers of specific condition types may define expected\n                              values and meanings for this field, and whether the\n                              values are considered a guaranteed API. The value should\n                              be a CamelCase string. This field may not be empty.\n                            maxLength: 1024\n                            minLength: 1\n                            pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                            type: string\n                          status:\n                            description: status of the condition, one of True, False,\n                              Unknown.\n                            enum:\n                            - \"True\"\n                            - \"False\"\n                            - Unknown\n                            type: string\n                          type:\n                            description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                              --- Many .condition.type values are consistent across\n                              resources like Available, but because arbitrary conditions\n                              can be useful (see .node.status.conditions), the ability\n                              to deconflict is important. The regex it matches is\n                              (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                            maxLength: 316\n                            pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                            type: string\n                          localRef:\n                            description: LocalRef corresponds with the Service in the\n                              local cluster that this Link is managing.\n                            properties:\n                              group:\n                                default: core\n                                description: \"Group is the group of the referent.\"\n                                maxLength: 253\n                                pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                                type: string\n                              kind:\n                                default: Service\n                                description: \"Kind is kind of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                                type: string\n                              name:\n                                description: \"Name is the name of the referent.\"\n                                maxLength: 253\n                                minLength: 1\n                                type: string\n                              namespace:\n                                description: \"Namespace is the namespace of the referent.\"\n                                maxLength: 63\n                                minLength: 1\n                                pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                                type: string\n                            type: object\n                            required:\n                            - name\n                            - namespace\n                        required:\n                        - lastTransitionTime\n                        - message\n                        - reason\n                        - status\n                        - type\n                        type: object\n                    controllerName:\n                      description: \"ControllerName is a domain/path string that indicates\n                        the name of the controller that wrote this status. This corresponds\n                        with the controllerName field on GatewayClass. \\n Example:\n                        \\\"example.net/gateway-controller\\\". \\n The format of this\n                        field is DOMAIN \\\"/\\\" PATH, where DOMAIN and PATH are valid\n                        Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n                        \\n Controllers MUST populate this field when writing status.\n                        Controllers should ensure that entries to status populated\n                        with their ControllerName are cleaned up when they are no\n                        longer necessary.\"\n                      maxLength: 253\n                      minLength: 1\n                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/[A-Za-z0-9\\/\\-._~%!$&'()*+,;=:]+$\n                      type: string\n                    remoteRef:\n                      description: RemoteRef corresponds with the Service in the\n                        target cluster that this Link is mirroring.\n                      properties:\n                        group:\n                          default: core\n                          description: \"Group is the group of the referent.\"\n                          maxLength: 253\n                          pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\n                          type: string\n                        kind:\n                          default: Service\n                          description: \"Kind is kind of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$\n                          type: string\n                        name:\n                          description: \"Name is the name of the referent.\"\n                          maxLength: 253\n                          minLength: 1\n                          type: string\n                        namespace:\n                          description: \"Namespace is the namespace of the referent.\"\n                          maxLength: 63\n                          minLength: 1\n                          pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\n                          type: string\n                      required:\n                      - name\n                      - namespace\n                      type: object\n                  type: object\n                  required:\n                  - controllerName\n                  - remoteRef\n            type: object\n    subresources:\n      status: {}\n  scope: Namespaced\n  names:\n    plural: links\n    singular: link\n    kind: Link\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-multicluster\n  name: service-mirror\n  labels:\n    linkerd.io/extension: multicluster\n    component: linkerd-service-mirror\nspec:\n  podSelector:\n    matchExpressions:\n    - key: component\n      operator: In\n      values:\n      - linkerd-service-mirror\n      - controller\n  port: svcmi-admin\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-multicluster\n  name: service-mirror\n  labels:\n    linkerd.io/extension: multicluster\n    component: linkerd-service-mirror\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: service-mirror\n  requiredAuthenticationRefs:\n    # In order to use `linkerd mc gateways` you need viz' Prometheus instance\n    # to be able to reach the service-mirror. In order to also have a separate\n    # Prometheus scrape the service-mirror an additional AuthorizationPolicy\n    # resource should be created.\n    - kind: ServiceAccount\n      name: prometheus\n      namespace: linkerd-viz\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-local-service-mirror-access-local-resources\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\nrules:\n- apiGroups: [\"\"]\n  resources: [\"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"delete\", \"update\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"create\", \"get\", \"update\", \"patch\"]\n- apiGroups: [\"multicluster.linkerd.io\"]\n  resources: [\"links\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"multicluster.linkerd.io\"]\n  resources: [\"links/status\"]\n  verbs: [\"update\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-local-service-mirror-access-local-resources\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-local-service-mirror-access-local-resources\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-local-service-mirror\n  namespace: linkerd-multicluster\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-local-service-mirror\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    linkerd.io/extension: multicluster\n    component: local-service-mirror\n  name: linkerd-local-service-mirror\n  namespace: linkerd-multicluster\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      component: local-service-mirror\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n      labels:\n        linkerd.io/extension: multicluster\n        component: local-service-mirror\n    spec:\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - service-mirror\n        - -log-level=info\n        - -log-format=plain\n        - -event-requeue-limit=3\n        - -namespace=linkerd-multicluster\n        - -enable-pprof=false\n        - -local-mirror\n        - -federated-service-selector=mirror.linkerd.io/federated=member\n        - -excluded-labels=\n        - -excluded-annotations=\n        image: cr.l5d.io/linkerd/controller:linkerdVersionValue\n        name: service-mirror\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n        ports:\n        - containerPort: 9999\n          name: locsm-admin\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-local-service-mirror\n      volumes:\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-multicluster-controller-access-local-resources\n  labels:\n    linkerd.io/extension: multicluster\n    component: controller\nrules:\n- apiGroups: [\"\"]\n  resources: [\"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"delete\", \"update\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n\n\n"
  },
  {
    "path": "multicluster/cmd/testdata/service_mirror_default.golden",
    "content": "kind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-service-mirror-access-local-resources-test-cluster\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: test-cluster\nrules:\n- apiGroups: [\"\"]\n  resources: [\"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"delete\", \"update\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-service-mirror-access-local-resources-test-cluster\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: test-cluster\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-service-mirror-access-local-resources-test-cluster\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-service-mirror-test-cluster\n  namespace: test\n---\nkind: Role\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-service-mirror-read-remote-creds-test-cluster\n  namespace: test\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: test-cluster\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"secrets\"]\n    resourceNames: [\"cluster-credentials-test-cluster\"]\n    verbs: [\"list\", \"get\", \"watch\"]\n  - apiGroups: [\"multicluster.linkerd.io\"]\n    resources: [\"links\"]\n    verbs: [\"list\", \"get\", \"watch\"]\n  - apiGroups: [\"multicluster.linkerd.io\"]\n    resources: [\"links/status\"]\n    verbs: [\"update\", \"patch\"]\n  - apiGroups: [\"coordination.k8s.io\"]\n    resources: [\"leases\"]\n    verbs: [\"create\", \"get\", \"update\", \"patch\"]\n---\nkind: RoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-service-mirror-read-remote-creds-test-cluster\n  namespace: test\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: test-cluster\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: linkerd-service-mirror-read-remote-creds-test-cluster\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-service-mirror-test-cluster\n    namespace: test\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-service-mirror-test-cluster\n  namespace: test\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: test-cluster\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: test-cluster\n  name: linkerd-service-mirror-test-cluster\n  namespace: test\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      component: linkerd-service-mirror\n      mirror.linkerd.io/cluster-name: test-cluster\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n      labels:\n        linkerd.io/extension: multicluster\n        component: linkerd-service-mirror\n        mirror.linkerd.io/cluster-name: test-cluster\n    spec:\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - service-mirror\n        - -log-level=info\n        - -log-format=plain\n        - -event-requeue-limit=3\n        - -namespace=test\n        - -enable-pprof=false\n        - -probe-service=probe-gateway-test-cluster\n        - test-cluster\n        image: cr.l5d.io/linkerd/controller:dev-undefined\n        name: service-mirror\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n        ports:\n        - containerPort: 9999\n          name: svcmi-admin\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-service-mirror-test-cluster\n      volumes:\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: probe-gateway-test-cluster\n  namespace: test\n  labels:\n    linkerd.io/extension: multicluster\n    mirror.linkerd.io/mirrored-gateway: \"true\"\n    mirror.linkerd.io/cluster-name: test-cluster\nspec:\n  ports:\n  - name: mc-probe\n    port: 4191\n    protocol: TCP\n"
  },
  {
    "path": "multicluster/cmd/testdata/service_mirror_ha.golden",
    "content": "kind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-service-mirror-access-local-resources-test-cluster\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: test-cluster\nrules:\n- apiGroups: [\"\"]\n  resources: [\"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\", \"create\", \"delete\", \"update\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-service-mirror-access-local-resources-test-cluster\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: test-cluster\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-service-mirror-access-local-resources-test-cluster\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-service-mirror-test-cluster\n  namespace: test\n---\nkind: Role\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-service-mirror-read-remote-creds-test-cluster\n  namespace: test\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: test-cluster\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"secrets\"]\n    resourceNames: [\"cluster-credentials-test-cluster\"]\n    verbs: [\"list\", \"get\", \"watch\"]\n  - apiGroups: [\"multicluster.linkerd.io\"]\n    resources: [\"links\"]\n    verbs: [\"list\", \"get\", \"watch\"]\n  - apiGroups: [\"multicluster.linkerd.io\"]\n    resources: [\"links/status\"]\n    verbs: [\"update\", \"patch\"]\n  - apiGroups: [\"coordination.k8s.io\"]\n    resources: [\"leases\"]\n    verbs: [\"create\", \"get\", \"update\", \"patch\"]\n---\nkind: RoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-service-mirror-read-remote-creds-test-cluster\n  namespace: test\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: test-cluster\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: linkerd-service-mirror-read-remote-creds-test-cluster\nsubjects:\n  - kind: ServiceAccount\n    name: linkerd-service-mirror-test-cluster\n    namespace: test\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-service-mirror-test-cluster\n  namespace: test\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: test-cluster\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    linkerd.io/extension: multicluster\n    component: service-mirror\n    mirror.linkerd.io/cluster-name: test-cluster\n  name: linkerd-service-mirror-test-cluster\n  namespace: test\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      component: linkerd-service-mirror\n      mirror.linkerd.io/cluster-name: test-cluster\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 1\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n      labels:\n        linkerd.io/extension: multicluster\n        component: linkerd-service-mirror\n        mirror.linkerd.io/cluster-name: test-cluster\n    spec:\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: mirror.linkerd.io/cluster-name\n                  operator: In\n                  values:\n                  - test-cluster\n              topologyKey: topology.kubernetes.io/zone\n            weight: 100\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: mirror.linkerd.io/cluster-name\n                operator: In\n                values:\n                - test-cluster\n            topologyKey: kubernetes.io/hostname\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - service-mirror\n        - -log-level=info\n        - -log-format=plain\n        - -event-requeue-limit=3\n        - -namespace=test\n        - -enable-pprof=false\n        - -probe-service=probe-gateway-test-cluster\n        - test-cluster\n        image: cr.l5d.io/linkerd/controller:dev-undefined\n        name: service-mirror\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n        ports:\n        - containerPort: 9999\n          name: svcmi-admin\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: linkerd-service-mirror-test-cluster\n      volumes:\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: linkerd-service-mirror-test-cluster\n  namespace: test\n  labels:\n    component: linkerd-service-mirror\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      component: linkerd-service-mirror\n      mirror.linkerd.io/cluster-name: test-cluster\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: probe-gateway-test-cluster\n  namespace: test\n  labels:\n    linkerd.io/extension: multicluster\n    mirror.linkerd.io/mirrored-gateway: \"true\"\n    mirror.linkerd.io/cluster-name: test-cluster\nspec:\n  ports:\n  - name: mc-probe\n    port: 4191\n    protocol: TCP\n"
  },
  {
    "path": "multicluster/cmd/uninstall.go",
    "content": "package cmd\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\tpkgCmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/spf13/cobra\"\n\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n)\n\nfunc newMulticlusterUninstallCommand() *cobra.Command {\n\tvar output string\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"uninstall\",\n\t\tShort: \"Output Kubernetes configs to uninstall the Linkerd multicluster add-on\",\n\t\tArgs:  cobra.NoArgs,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\n\t\t\trules := clientcmd.NewDefaultClientConfigLoadingRules()\n\t\t\trules.ExplicitPath = kubeconfigPath\n\t\t\tloader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{})\n\t\t\tconfig, err := loader.RawConfig()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif kubeContext != \"\" {\n\t\t\t\tconfig.CurrentContext = kubeContext\n\t\t\t}\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, config.CurrentContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tlinks, err := k8sAPI.L5dCrdClient.LinkV1alpha3().Links(\"\").List(cmd.Context(), metav1.ListOptions{})\n\t\t\tif err != nil && !kerrors.IsNotFound(err) {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif len(links.Items) > 0 {\n\t\t\t\terr := []string{\"Please unlink the following clusters before uninstalling multicluster:\"}\n\t\t\t\tfor _, link := range links.Items {\n\t\t\t\t\terr = append(err, fmt.Sprintf(\"  * %s\", link.Spec.TargetClusterName))\n\t\t\t\t}\n\t\t\t\treturn errors.New(strings.Join(err, \"\\n\"))\n\t\t\t}\n\n\t\t\tselector, err := pkgCmd.GetLabelSelector(k8s.LinkerdExtensionLabel, MulticlusterExtensionName, MulticlusterLegacyExtension)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\terr = pkgCmd.Uninstall(cmd.Context(), k8sAPI, selector, output)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintln(os.Stderr, err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tcmd.PersistentFlags().StringVarP(&output, \"output\", \"o\", \"yaml\", \"Output format. One of: json|yaml\")\n\n\treturn cmd\n}\n"
  },
  {
    "path": "multicluster/cmd/unlink.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s/resource\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcoordinationv1 \"k8s.io/api/coordination/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\trbac \"k8s.io/api/rbac/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n)\n\nfunc newUnlinkCommand() *cobra.Command {\n\topts, err := newLinkOptionsWithDefault()\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\n\tcmd := &cobra.Command{\n\t\tUse:        \"unlink\",\n\t\tDeprecated: \"only use for removing service mirror resources created by the (also deprecated) 'linkerd multicluster link' command (Linkerd 2.17 and earlier), using the flag --only-controller\",\n\t\tShort:      \"Outputs link resources for deletion\",\n\t\tArgs:       cobra.NoArgs,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\n\t\t\tif opts.clusterName == \"\" {\n\t\t\t\treturn errors.New(\"you need to specify cluster name\")\n\t\t\t}\n\n\t\t\trules := clientcmd.NewDefaultClientConfigLoadingRules()\n\t\t\trules.ExplicitPath = kubeconfigPath\n\t\t\tloader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{})\n\t\t\tconfig, err := loader.RawConfig()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif kubeContext != \"\" {\n\t\t\t\tconfig.CurrentContext = kubeContext\n\t\t\t}\n\n\t\t\tk, err := k8s.NewAPI(kubeconfigPath, config.CurrentContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tl, err := k.L5dCrdClient.LinkV1alpha3().Links(opts.namespace).Get(cmd.Context(), opts.clusterName, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tsecret := resource.NewNamespaced(corev1.SchemeGroupVersion.String(), \"Secret\", fmt.Sprintf(\"cluster-credentials-%s\", opts.clusterName), opts.namespace)\n\t\t\tlink := resource.NewNamespaced(k8s.LinkAPIGroupVersion, \"Link\", opts.clusterName, opts.namespace)\n\t\t\tclusterRole := resource.New(rbac.SchemeGroupVersion.String(), \"ClusterRole\", fmt.Sprintf(\"linkerd-service-mirror-access-local-resources-%s\", opts.clusterName))\n\t\t\tclusterRoleBinding := resource.New(rbac.SchemeGroupVersion.String(), \"ClusterRoleBinding\", fmt.Sprintf(\"linkerd-service-mirror-access-local-resources-%s\", opts.clusterName))\n\t\t\trole := resource.NewNamespaced(rbac.SchemeGroupVersion.String(), \"Role\", fmt.Sprintf(\"linkerd-service-mirror-read-remote-creds-%s\", opts.clusterName), opts.namespace)\n\t\t\troleBinding := resource.NewNamespaced(rbac.SchemeGroupVersion.String(), \"RoleBinding\", fmt.Sprintf(\"linkerd-service-mirror-read-remote-creds-%s\", opts.clusterName), opts.namespace)\n\t\t\tserviceAccount := resource.NewNamespaced(corev1.SchemeGroupVersion.String(), \"ServiceAccount\", fmt.Sprintf(\"linkerd-service-mirror-%s\", opts.clusterName), opts.namespace)\n\t\t\tserviceMirror := resource.NewNamespaced(appsv1.SchemeGroupVersion.String(), \"Deployment\", fmt.Sprintf(\"linkerd-service-mirror-%s\", opts.clusterName), opts.namespace)\n\t\t\tlease := resource.NewNamespaced(coordinationv1.SchemeGroupVersion.String(), \"Lease\", fmt.Sprintf(\"service-mirror-write-%s\", opts.clusterName), opts.namespace)\n\n\t\t\tresources := []resource.Kubernetes{\n\t\t\t\tclusterRole, clusterRoleBinding,\n\t\t\t\trole, roleBinding, serviceAccount, serviceMirror,\n\t\t\t}\n\n\t\t\tif l.Spec.ProbeSpec.Path != \"\" {\n\t\t\t\tgatewayMirror := resource.NewNamespaced(corev1.SchemeGroupVersion.String(), \"Service\", fmt.Sprintf(\"probe-gateway-%s\", opts.clusterName), opts.namespace)\n\t\t\t\tresources = append(resources, gatewayMirror)\n\t\t\t}\n\n\t\t\tif !opts.onlyController {\n\t\t\t\tresources = append(resources, secret, link, lease)\n\n\t\t\t\tsvcs, err := getServiceMirrors(cmd.Context(), k, opts.clusterName)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tresources = append(resources, svcs...)\n\n\t\t\t\tcreds, err := getDestinationCredentials(cmd.Context(), k, opts.clusterName)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tresources = append(resources, creds...)\n\t\t\t}\n\n\t\t\tfor _, r := range resources {\n\t\t\t\tif opts.output == \"yaml\" {\n\t\t\t\t\tif err := r.RenderResource(stdout); err != nil {\n\t\t\t\t\t\tlog.Errorf(\"failed to render resource %s: %s\", r.Name, err)\n\t\t\t\t\t}\n\t\t\t\t} else if opts.output == \"json\" {\n\t\t\t\t\tif err := r.RenderResourceJSON(stdout); err != nil {\n\t\t\t\t\t\tlog.Errorf(\"failed to render resource %s: %s\", r.Name, err)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn fmt.Errorf(\"unsupported format: %s\", opts.output)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcmd.Flags().StringVar(&opts.namespace, \"namespace\", defaultMulticlusterNamespace, \"The namespace for the service account\")\n\tcmd.Flags().StringVar(&opts.clusterName, \"cluster-name\", \"\", \"Cluster name\")\n\tcmd.Flags().BoolVar(&opts.onlyController, \"only-controller\", false, \"Only output the service-mirror controller and related resources. Leave out Link CR, credentials, Lease and mirror services.\")\n\tcmd.Flags().StringVarP(&opts.output, \"output\", \"o\", \"yaml\", \"Output format. One of: json|yaml\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tcmd, []string{\"namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\n\tconfigureClusterNameFlagCompletion(cmd)\n\treturn cmd\n}\n\nfunc getServiceMirrors(ctx context.Context, k *k8s.KubernetesAPI, clusterName string) ([]resource.Kubernetes, error) {\n\tselector := fmt.Sprintf(\"%s=%s,%s=%s\",\n\t\tk8s.MirroredResourceLabel, \"true\",\n\t\tk8s.RemoteClusterNameLabel, clusterName,\n\t)\n\tsvcList, err := k.CoreV1().Services(metav1.NamespaceAll).List(ctx, metav1.ListOptions{LabelSelector: selector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresources := []resource.Kubernetes{}\n\tfor _, svc := range svcList.Items {\n\t\tresources = append(resources,\n\t\t\tresource.NewNamespaced(corev1.SchemeGroupVersion.String(), \"Service\", svc.Name, svc.Namespace),\n\t\t)\n\t}\n\n\treturn resources, nil\n}\n\nfunc getDestinationCredentials(ctx context.Context, k *k8s.KubernetesAPI, clusterName string) ([]resource.Kubernetes, error) {\n\tselector := fmt.Sprintf(\"%s=%s\", clusterNameLabel, clusterName)\n\tdestinationCredentials, err := k.CoreV1().Secrets(controlPlaneNamespace).List(ctx, metav1.ListOptions{LabelSelector: selector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresources := []resource.Kubernetes{}\n\tfor _, secret := range destinationCredentials.Items {\n\t\tresources = append(resources,\n\t\t\tresource.NewNamespaced(corev1.SchemeGroupVersion.String(), \"Secret\", secret.Name, secret.Namespace),\n\t\t)\n\t}\n\n\treturn resources, nil\n}\n\nfunc configureClusterNameFlagCompletion(cmd *cobra.Command) {\n\tcmd.RegisterFlagCompletionFunc(\"cluster-name\",\n\t\tfunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tcc := k8s.NewCommandCompletion(k8sAPI, corev1.NamespaceAll)\n\t\t\tresults, err := cc.Complete([]string{strings.ToLower(k8s.LinkKind)}, toComplete)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\treturn results, cobra.ShellCompDirectiveDefault\n\t\t})\n}\n"
  },
  {
    "path": "multicluster/service-mirror/cluster_watcher.go",
    "content": "package servicemirror\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"reflect\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tconsts \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\ttypedcorev1 \"k8s.io/client-go/kubernetes/typed/core/v1\"\n\t\"k8s.io/client-go/tools/cache\"\n\t\"k8s.io/client-go/tools/record\"\n\t\"k8s.io/client-go/util/workqueue\"\n)\n\nconst (\n\teventTypeSkipped = \"ServiceMirroringSkipped\"\n\n\treasonMirrored         = \"Mirrored\"\n\treasonInvalidService   = \"InvalidService\"\n\treasonError            = \"Error\"\n\treasonMissingNamespace = \"MissingNamespace\"\n)\n\ntype (\n\t// RemoteClusterServiceWatcher is a watcher instantiated for every cluster that is being watched\n\t// Its main job is to listen to events coming from the remote cluster and react accordingly, keeping\n\t// the state of the mirrored services in sync. This is achieved by maintaining a SharedInformer\n\t// on the remote cluster. The basic add/update/delete operations are mapped to a more domain specific\n\t// events, put onto a work queue and handled by the processing loop. In case processing an event fails\n\t// it can be requeued up to N times, to ensure that the failure is not due to some temporary network\n\t// problems or general glitch in the Matrix.\n\tRemoteClusterServiceWatcher struct {\n\t\tserviceMirrorNamespace   string\n\t\tlink                     *v1alpha3.Link\n\t\tremoteAPIClient          *k8s.API\n\t\tlocalAPIClient           *k8s.API\n\t\tlinksAPIClient           *k8s.API\n\t\tprobeSvc                 string\n\t\tstopper                  chan struct{}\n\t\teventBroadcaster         record.EventBroadcaster\n\t\trecorder                 record.EventRecorder\n\t\tlog                      *logging.Entry\n\t\teventsQueue              workqueue.TypedRateLimitingInterface[any]\n\t\trequeueLimit             int\n\t\trepairPeriod             time.Duration\n\t\tgatewayAlive             bool\n\t\tliveness                 chan bool\n\t\theadlessServicesEnabled  bool\n\t\tnamespaceCreationEnabled bool\n\n\t\tinformerHandlers\n\t}\n\n\tinformerHandlers struct {\n\t\tsvcHandler cache.ResourceEventHandlerRegistration\n\t\tepHandler  cache.ResourceEventHandlerRegistration\n\t\tnsHandler  cache.ResourceEventHandlerRegistration\n\t}\n\n\t// RemoteServiceExported is generated whenever a remote service is created Observing\n\t// this event means that the service in question is not mirrored atm\n\tRemoteServiceExported struct {\n\t\tservice *corev1.Service\n\t}\n\n\t// CreateFederatedService is generated whenever a remote service joins a\n\t// federated service and the local federated service does not exist yet.\n\tCreateFederatedService struct {\n\t\tservice *corev1.Service\n\t}\n\n\t// RemoteExportedServiceUpdated is generated when we see something about an already\n\t// mirrored service change on the remote cluster. In that case we need to\n\t// reconcile. Most importantly we need to keep track of exposed ports\n\t// and gateway association changes.\n\tRemoteExportedServiceUpdated struct {\n\t\tremoteUpdate *corev1.Service\n\t}\n\n\t// RemoteServiceJoinedFederatedService is generated when a remote server\n\t// joins a federated service and the local federated service already exists.\n\tRemoteServiceJoinsFederatedService struct {\n\t\tremoteUpdate *corev1.Service\n\t}\n\n\t// RemoteServiceUnexported when a remote service is going away or it is not\n\t// considered mirrored anymore\n\tRemoteServiceUnexported struct {\n\t\tName      string\n\t\tNamespace string\n\t}\n\n\t// RemoteServiceLeavesFederatedService when a remote service is going away or\n\t// it is no longer part of the federated service\n\tRemoteServiceLeavesFederatedService struct {\n\t\tName      string\n\t\tNamespace string\n\t}\n\n\t// ClusterUnregistered is issued when this ClusterWatcher is shut down.\n\tClusterUnregistered struct{}\n\n\t// OrphanedServicesGcTriggered is a self-triggered event which aims to delete any\n\t// orphaned services that are no longer on the remote cluster. It is emitted every\n\t// time a new remote cluster is registered for monitoring. The need for this arises\n\t// because the following might happen.\n\t//\n\t// 1. A cluster is registered for monitoring\n\t// 2. Services A,B,C are created and mirrored\n\t// 3. Then this component crashes, leaving the mirrors around\n\t// 4. In the meantime services B and C are deleted on the remote cluster\n\t// 5. When the controller starts up again it registers to listen for mirrored services\n\t// 6. It receives an ADD for A but not a DELETE for B and C\n\t//\n\t// This event indicates that we need to make a diff with all services on the remote\n\t// cluster, ensuring that we do not keep any mirrors that are not relevant anymore\n\tOrphanedServicesGcTriggered struct{}\n\n\t// OnAddCalled is issued when the onAdd function of the\n\t// shared informer is called\n\tOnAddCalled struct {\n\t\tsvc *corev1.Service\n\t}\n\n\t// OnAddEndpointsCalled is issued when the onAdd function of the Endpoints\n\t// shared informer is called\n\tOnAddEndpointsCalled struct {\n\t\tep *corev1.Endpoints\n\t}\n\n\t// OnUpdateCalled is issued when the onUpdate function of the\n\t// shared informer is called\n\tOnUpdateCalled struct {\n\t\tsvc *corev1.Service\n\t}\n\n\t// OnUpdateEndpointsCalled is issued when the onUpdate function of the\n\t// shared Endpoints informer is called\n\tOnUpdateEndpointsCalled struct {\n\t\tep *corev1.Endpoints\n\t}\n\t// OnDeleteCalled is issued when the onDelete function of the\n\t// shared informer is called\n\tOnDeleteCalled struct {\n\t\tsvc *corev1.Service\n\t}\n\n\t// RepairEndpoints is issued when the service mirror and mirror gateway\n\t// endpoints should be resolved based on the remote gateway and updated.\n\tRepairEndpoints struct{}\n\n\t// OnLocalNamespaceAdded is issued when when a new namespace is added to\n\t// the local cluster. This means that we should check the remote cluster\n\t// for exported service in that namespace.\n\tOnLocalNamespaceAdded struct {\n\t\tns *corev1.Namespace\n\t}\n\n\t// RetryableError is an error that should be retried through requeuing events\n\tRetryableError struct{ Inner []error }\n)\n\nfunc (re RetryableError) Error() string {\n\tvar errorStrings []string\n\tfor _, err := range re.Inner {\n\t\terrorStrings = append(errorStrings, err.Error())\n\t}\n\treturn fmt.Sprintf(\"Inner errors:\\n\\t%s\", strings.Join(errorStrings, \"\\n\\t\"))\n}\n\n// NewRemoteClusterServiceWatcher constructs a new cluster watcher\nfunc NewRemoteClusterServiceWatcher(\n\tctx context.Context,\n\tserviceMirrorNamespace string,\n\tlocalAPI *k8s.API,\n\tremoteAPI *k8s.API,\n\tlinksAPI *k8s.API,\n\tprobeSvc string,\n\tlink *v1alpha3.Link,\n\trequeueLimit int,\n\trepairPeriod time.Duration,\n\tliveness chan bool,\n\tenableHeadlessSvc bool,\n\tenableNamespaceCreation bool,\n) (*RemoteClusterServiceWatcher, error) {\n\t_, err := remoteAPI.Client.Discovery().ServerVersion()\n\tif err != nil {\n\t\tremoteAPI.UnregisterGauges()\n\t\treturn nil, fmt.Errorf(\"cannot connect to api for target cluster %s: %w\", link.Spec.TargetClusterName, err)\n\t}\n\n\t// Create k8s event recorder\n\teventBroadcaster := record.NewBroadcaster()\n\teventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{\n\t\tInterface: remoteAPI.Client.CoreV1().Events(\"\"),\n\t})\n\trecorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{\n\t\tComponent: fmt.Sprintf(\"linkerd-service-mirror-%s\", link.Spec.TargetClusterName),\n\t})\n\n\tstopper := make(chan struct{})\n\treturn &RemoteClusterServiceWatcher{\n\t\tserviceMirrorNamespace: serviceMirrorNamespace,\n\t\tlink:                   link,\n\t\tremoteAPIClient:        remoteAPI,\n\t\tlocalAPIClient:         localAPI,\n\t\tlinksAPIClient:         linksAPI,\n\t\tprobeSvc:               probeSvc,\n\t\tstopper:                stopper,\n\t\teventBroadcaster:       eventBroadcaster,\n\t\trecorder:               recorder,\n\t\tlog: logging.WithFields(logging.Fields{\n\t\t\t\"cluster\": link.Spec.TargetClusterName,\n\t\t}),\n\t\teventsQueue:              workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any]()),\n\t\trequeueLimit:             requeueLimit,\n\t\trepairPeriod:             repairPeriod,\n\t\tliveness:                 liveness,\n\t\theadlessServicesEnabled:  enableHeadlessSvc,\n\t\tnamespaceCreationEnabled: enableNamespaceCreation,\n\t\t// always instantiate the gatewayAlive=true to prevent unexpected service fail fast\n\t\tgatewayAlive: true,\n\t}, nil\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) mirrorServiceName(remoteName string) string {\n\treturn fmt.Sprintf(\"%s-%s\", remoteName, rcsw.link.Spec.TargetClusterName)\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) federatedServiceName(remoteName string) string {\n\treturn fmt.Sprintf(\"%s-federated\", remoteName)\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) targetResourceName(mirrorName string) string {\n\treturn strings.TrimSuffix(mirrorName, \"-\"+rcsw.link.Spec.TargetClusterName)\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) originalResourceName(mirroredName string) string {\n\treturn strings.TrimSuffix(mirroredName, fmt.Sprintf(\"-%s\", rcsw.link.Spec.TargetClusterName))\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) isLabelExlucded(label string) bool {\n\tif strings.HasPrefix(label, consts.SvcMirrorPrefix) {\n\t\treturn true\n\t}\n\tfor _, excludedLabel := range rcsw.link.Spec.ExcludedLabels {\n\t\tif strings.HasSuffix(excludedLabel, \"/*\") {\n\t\t\ttrimmed := strings.TrimSuffix(excludedLabel, \"/*\")\n\t\t\tif strings.HasPrefix(label, trimmed) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t} else if label == excludedLabel {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// Provides labels for mirrored or federatedservice.\n// Copies all labels from the remote service to local service (except labels\n// with the \"SvcMirrorPrefix\").\nfunc (rcsw *RemoteClusterServiceWatcher) getCommonServiceLabels(remoteService *corev1.Service) map[string]string {\n\tlabels := map[string]string{\n\t\tconsts.MirroredResourceLabel: \"true\",\n\t}\n\n\tfor key, value := range remoteService.ObjectMeta.Labels {\n\t\tif rcsw.isLabelExlucded(key) {\n\t\t\tcontinue\n\t\t}\n\t\tlabels[key] = value\n\t}\n\n\treturn labels\n}\n\n// Provides labels for mirror service.\n// Copies all labels from the remote service to mirrored service (except labels\n// with the \"SvcMirrorPrefix\").\nfunc (rcsw *RemoteClusterServiceWatcher) getMirrorServiceLabels(remoteService *corev1.Service) map[string]string {\n\tlabels := rcsw.getCommonServiceLabels(remoteService)\n\tlabels[consts.RemoteClusterNameLabel] = rcsw.link.Spec.TargetClusterName\n\n\tif rcsw.isRemoteDiscovery(remoteService.Labels) {\n\t\tlabels[consts.RemoteDiscoveryLabel] = rcsw.link.Spec.TargetClusterName\n\t\tlabels[consts.RemoteServiceLabel] = remoteService.GetName()\n\t}\n\n\treturn labels\n}\n\n// Provides labels for mirror endpoint. Copies all labels from the exported\n// service to the mirror endpoint (except labels with the \"SvcMirrorPrefix\").\nfunc (rcsw *RemoteClusterServiceWatcher) getMirrorEndpointLabels(exportedService *corev1.Service) map[string]string {\n\tlabels := rcsw.getCommonServiceLabels(exportedService)\n\n\tlabels[consts.RemoteClusterNameLabel] = rcsw.link.Spec.TargetClusterName\n\n\treturn labels\n}\n\n// Provides labels for federated services. Copies all labels from the remote\n// service to the federated service (except labels with the \"SvcMirrorPrefix\").\nfunc (rcsw *RemoteClusterServiceWatcher) getFederatedServiceLabels(remoteService *corev1.Service) map[string]string {\n\tlabels := rcsw.getCommonServiceLabels(remoteService)\n\n\treturn labels\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) isAnnotationExlucded(annotation string) bool {\n\t// Topology aware hints are not multicluster aware.\n\tif annotation == \"service.kubernetes.io/topology-aware-hints\" || annotation == \"service.kubernetes.io/topology-mode\" {\n\t\treturn true\n\t}\n\tfor _, excludedAnnotation := range rcsw.link.Spec.ExcludedAnnotations {\n\t\tif strings.HasSuffix(excludedAnnotation, \"/*\") {\n\t\t\ttrimmed := strings.TrimSuffix(excludedAnnotation, \"/*\")\n\t\t\tif strings.HasPrefix(annotation, trimmed) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t} else if annotation == excludedAnnotation {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// Provides annotations for mirror or federated services\nfunc (rcsw *RemoteClusterServiceWatcher) getCommonServiceAnnotations(remoteService *corev1.Service) map[string]string {\n\tannotations := map[string]string{}\n\n\tfor key, value := range remoteService.ObjectMeta.Annotations {\n\t\tif rcsw.isAnnotationExlucded(key) {\n\t\t\tcontinue\n\t\t}\n\t\tannotations[key] = value\n\t}\n\n\tvalue, ok := remoteService.GetAnnotations()[consts.ProxyOpaquePortsAnnotation]\n\tif ok {\n\t\tannotations[consts.ProxyOpaquePortsAnnotation] = value\n\t}\n\n\treturn annotations\n}\n\n// Provides annotations for mirror services\nfunc (rcsw *RemoteClusterServiceWatcher) getMirrorServiceAnnotations(remoteService *corev1.Service) map[string]string {\n\tannotations := rcsw.getCommonServiceAnnotations(remoteService)\n\n\tannotations[consts.RemoteServiceFqName] = fmt.Sprintf(\"%s.%s.svc.%s\", remoteService.Name, remoteService.Namespace, rcsw.link.Spec.TargetClusterDomain)\n\tannotations[consts.RemoteResourceVersionAnnotation] = remoteService.ResourceVersion // needed to detect real changes\n\n\treturn annotations\n}\n\n// Provides annotations for federated service\nfunc (rcsw *RemoteClusterServiceWatcher) getFederatedServiceAnnotations(remoteService *corev1.Service) map[string]string {\n\tannotations := rcsw.getCommonServiceAnnotations(remoteService)\n\n\tif rcsw.link.Spec.TargetClusterName == \"\" {\n\t\t// Local discovery\n\t\tannotations[consts.LocalDiscoveryAnnotation] = remoteService.Name\n\t} else {\n\t\t// Remote discovery\n\t\tannotations[consts.RemoteDiscoveryAnnotation] = fmt.Sprintf(\"%s@%s\", remoteService.Name, rcsw.link.Spec.TargetClusterName)\n\t}\n\n\treturn annotations\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) mirrorNamespaceIfNecessary(ctx context.Context, namespace string) error {\n\t// if the namespace is already present we do not need to change it.\n\t// if we are creating it we want to put a label indicating this is a\n\t// mirrored resource\n\tif _, err := rcsw.localAPIClient.NS().Lister().Get(namespace); err != nil {\n\t\tif kerrors.IsNotFound(err) {\n\t\t\t// if the namespace is not found, we can just create it\n\t\t\tns := &corev1.Namespace{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\tconsts.MirroredResourceLabel:  \"true\",\n\t\t\t\t\t\tconsts.RemoteClusterNameLabel: rcsw.link.Spec.TargetClusterName,\n\t\t\t\t\t},\n\t\t\t\t\tName: namespace,\n\t\t\t\t},\n\t\t\t}\n\t\t\t_, err := rcsw.localAPIClient.Client.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})\n\t\t\tif err != nil {\n\t\t\t\t// something went wrong with the create, we can just retry as well\n\t\t\t\treturn RetryableError{[]error{err}}\n\t\t\t}\n\t\t} else {\n\t\t\t// something else went wrong, so we can just retry\n\t\t\treturn RetryableError{[]error{err}}\n\t\t}\n\t}\n\treturn nil\n}\n\n// This method takes care of port remapping. What it does essentially is get the one gateway port\n// that we should send traffic to and create endpoint ports that bind to the mirrored service ports\n// (same name, etc) but send traffic to the gateway port. This way we do not need to do any remapping\n// on the service side of things. It all happens in the endpoints.\nfunc (rcsw *RemoteClusterServiceWatcher) getEndpointsPorts(service *corev1.Service) ([]corev1.EndpointPort, error) {\n\tgatewayPort, err := strconv.ParseInt(rcsw.link.Spec.GatewayPort, 10, 32)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar endpointsPorts []corev1.EndpointPort\n\tfor _, remotePort := range service.Spec.Ports {\n\t\tendpointsPorts = append(endpointsPorts, corev1.EndpointPort{\n\t\t\tName:     remotePort.Name,\n\t\t\tProtocol: remotePort.Protocol,\n\t\t\tPort:     int32(gatewayPort),\n\t\t})\n\t}\n\treturn endpointsPorts, nil\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) cleanupOrphanedServices(ctx context.Context) error {\n\tmatchLabels := map[string]string{\n\t\tconsts.MirroredResourceLabel:  \"true\",\n\t\tconsts.RemoteClusterNameLabel: rcsw.link.Spec.TargetClusterName,\n\t}\n\n\tservicesOnLocalCluster, err := rcsw.localAPIClient.Svc().Lister().List(labels.Set(matchLabels).AsSelector())\n\tif err != nil {\n\t\tinnerErr := fmt.Errorf(\"failed to list services while cleaning up mirror services: %w\", err)\n\t\tif kerrors.IsNotFound(err) {\n\t\t\treturn innerErr\n\t\t}\n\t\t// if it is something else, we can just retry\n\t\treturn RetryableError{[]error{innerErr}}\n\t}\n\n\tvar errors []error\n\tfor _, srv := range servicesOnLocalCluster {\n\t\tmirroredName := srv.Name\n\t\t// For headless services with cluster IPs representing the backing pods, the mirrored service name\n\t\t// is the root headless service in the source cluster\n\t\tif remoteHeadlessSvcName, headlessMirror := srv.Labels[consts.MirroredHeadlessSvcNameLabel]; headlessMirror {\n\t\t\tmirroredName = remoteHeadlessSvcName\n\t\t}\n\t\tremoteServiceName := rcsw.originalResourceName(mirroredName)\n\t\t_, err := rcsw.remoteAPIClient.Svc().Lister().Services(srv.Namespace).Get(remoteServiceName)\n\t\tif err != nil {\n\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t// service does not exist anymore. Need to delete\n\t\t\t\tif err := rcsw.localAPIClient.Client.CoreV1().Services(srv.Namespace).Delete(ctx, srv.Name, metav1.DeleteOptions{}); err != nil {\n\t\t\t\t\t// something went wrong with deletion, we need to retry\n\t\t\t\t\terrors = append(errors, err)\n\t\t\t\t} else {\n\t\t\t\t\trcsw.log.Infof(\"Deleted service %s/%s while cleaning up mirror services\", srv.Namespace, srv.Name)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// something went wrong getting the service, we can retry\n\t\t\t\terrors = append(errors, err)\n\t\t\t}\n\t\t}\n\t}\n\tif len(errors) > 0 {\n\t\treturn RetryableError{errors}\n\t}\n\n\treturn nil\n}\n\n// Whenever we stop watching a cluster, we need to cleanup everything that we have\n// created. This piece of code is responsible for doing just that. It takes care of\n// services, endpoints and namespaces (if needed)\nfunc (rcsw *RemoteClusterServiceWatcher) cleanupMirroredResources(ctx context.Context) error {\n\tmatchLabels := map[string]string{\n\t\tconsts.MirroredResourceLabel:  \"true\",\n\t\tconsts.RemoteClusterNameLabel: rcsw.link.Spec.TargetClusterName,\n\t}\n\n\tservices, err := rcsw.localAPIClient.Svc().Lister().List(labels.Set(matchLabels).AsSelector())\n\tif err != nil {\n\t\tinnerErr := fmt.Errorf(\"could not retrieve mirrored services that need cleaning up: %w\", err)\n\t\tif kerrors.IsNotFound(err) {\n\t\t\treturn innerErr\n\t\t}\n\t\t// if its not notFound then something else went wrong, so we can retry\n\t\treturn RetryableError{[]error{innerErr}}\n\t}\n\n\tvar errors []error\n\tfor _, svc := range services {\n\t\tif err := rcsw.localAPIClient.Client.CoreV1().Services(svc.Namespace).Delete(ctx, svc.Name, metav1.DeleteOptions{}); err != nil {\n\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\terrors = append(errors, fmt.Errorf(\"Could not delete service %s/%s: %w\", svc.Namespace, svc.Name, err))\n\t\t} else {\n\t\t\trcsw.log.Infof(\"Deleted service %s/%s\", svc.Namespace, svc.Name)\n\t\t}\n\t}\n\n\tendpoints, err := rcsw.localAPIClient.Endpoint().Lister().List(labels.Set(matchLabels).AsSelector())\n\tif err != nil {\n\t\tinnerErr := fmt.Errorf(\"could not retrieve endpoints that need cleaning up: %w\", err)\n\t\tif kerrors.IsNotFound(err) {\n\t\t\treturn innerErr\n\t\t}\n\t\treturn RetryableError{[]error{innerErr}}\n\t}\n\n\tfor _, endpoint := range endpoints {\n\t\tif err := rcsw.localAPIClient.Client.CoreV1().Endpoints(endpoint.Namespace).Delete(ctx, endpoint.Name, metav1.DeleteOptions{}); err != nil {\n\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\terrors = append(errors, fmt.Errorf(\"Could not delete endpoints %s/%s: %w\", endpoint.Namespace, endpoint.Name, err))\n\t\t} else {\n\t\t\trcsw.log.Infof(\"Deleted endpoints %s/%s\", endpoint.Namespace, endpoint.Name)\n\t\t}\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn RetryableError{errors}\n\t}\n\treturn nil\n}\n\n// Deletes a locally mirrored service as it is not present on the remote cluster anymore\nfunc (rcsw *RemoteClusterServiceWatcher) handleRemoteServiceUnexported(ctx context.Context, ev *RemoteServiceUnexported) error {\n\trcsw.deleteLinkMirrorStatus(\n\t\tev.Name, ev.Namespace,\n\t)\n\n\tlocalServiceName := rcsw.mirrorServiceName(ev.Name)\n\tlocalService, err := rcsw.localAPIClient.Svc().Lister().Services(ev.Namespace).Get(localServiceName)\n\tvar errors []error\n\tif err != nil {\n\t\tif kerrors.IsNotFound(err) {\n\t\t\trcsw.log.Debugf(\"Failed to delete mirror service %s/%s: %v\", ev.Namespace, ev.Name, err)\n\t\t\treturn nil\n\t\t}\n\t\terrors = append(errors, fmt.Errorf(\"could not fetch service %s/%s: %w\", ev.Namespace, localServiceName, err))\n\t}\n\n\t// If the mirror service is headless, also delete its endpoint mirror\n\t// services.\n\tif localService.Spec.ClusterIP == corev1.ClusterIPNone {\n\t\tmatchLabels := map[string]string{\n\t\t\tconsts.MirroredHeadlessSvcNameLabel: localServiceName,\n\t\t}\n\t\tendpointMirrorServices, err := rcsw.localAPIClient.Svc().Lister().List(labels.Set(matchLabels).AsSelector())\n\t\tif err != nil {\n\t\t\tif !kerrors.IsNotFound(err) {\n\t\t\t\terrors = append(errors, fmt.Errorf(\"could not fetch endpoint mirrors for mirror service %s/%s: %w\", ev.Namespace, localServiceName, err))\n\t\t\t}\n\t\t}\n\n\t\tfor _, endpointMirror := range endpointMirrorServices {\n\t\t\terr = rcsw.localAPIClient.Client.CoreV1().Services(endpointMirror.Namespace).Delete(ctx, endpointMirror.Name, metav1.DeleteOptions{})\n\t\t\tif err != nil {\n\t\t\t\tif !kerrors.IsNotFound(err) {\n\t\t\t\t\terrors = append(errors, fmt.Errorf(\"could not delete endpoint mirror %s/%s: %w\", endpointMirror.Namespace, endpointMirror.Name, err))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\trcsw.log.Infof(\"Deleting mirrored service %s/%s\", ev.Namespace, localServiceName)\n\tif err := rcsw.localAPIClient.Client.CoreV1().Services(ev.Namespace).Delete(ctx, localServiceName, metav1.DeleteOptions{}); err != nil {\n\t\tif !kerrors.IsNotFound(err) {\n\t\t\terrors = append(errors, fmt.Errorf(\"could not delete service: %s/%s: %w\", ev.Namespace, localServiceName, err))\n\t\t}\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn RetryableError{errors}\n\t}\n\n\trcsw.log.Infof(\"Successfully deleted service: %s/%s\", ev.Namespace, localServiceName)\n\treturn nil\n}\n\n// Removes a remote service from a local federated service.\nfunc (rcsw *RemoteClusterServiceWatcher) handleFederatedServiceLeave(ctx context.Context, ev *RemoteServiceLeavesFederatedService) error {\n\trcsw.deleteLinkFederatedStatus(\n\t\tev.Name, ev.Namespace,\n\t)\n\n\tlocalServiceName := rcsw.federatedServiceName(ev.Name)\n\tlocalService, err := rcsw.localAPIClient.Svc().Lister().Services(ev.Namespace).Get(localServiceName)\n\n\tif err != nil {\n\t\tif kerrors.IsNotFound(err) {\n\t\t\trcsw.log.Debugf(\"Failed to update federated service %s/%s: %v\", ev.Namespace, ev.Name, err)\n\t\t\treturn nil\n\t\t}\n\t\treturn RetryableError{[]error{fmt.Errorf(\"could not fetch service %s/%s: %w\", ev.Namespace, localServiceName, err)}}\n\t}\n\n\tif rcsw.link.Spec.TargetClusterName == \"\" {\n\t\t// Local discovery\n\t\tdelete(localService.Annotations, consts.LocalDiscoveryAnnotation)\n\t} else {\n\t\tremoteTarget := fmt.Sprintf(\"%s@%s\", ev.Name, rcsw.link.Spec.TargetClusterName)\n\t\tif !remoteDiscoveryContains(localService.Annotations[consts.RemoteDiscoveryAnnotation], remoteTarget) {\n\t\t\treturn nil\n\t\t}\n\n\t\tremoteDiscoveryList := strings.Split(localService.Annotations[consts.RemoteDiscoveryAnnotation], \",\")\n\t\tnewRemoteDiscoveryList := []string{}\n\t\tfor _, member := range remoteDiscoveryList {\n\t\t\tif member == remoteTarget {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tnewRemoteDiscoveryList = append(newRemoteDiscoveryList, member)\n\t\t}\n\t\tlocalService.Annotations[consts.RemoteDiscoveryAnnotation] = strings.Join(newRemoteDiscoveryList, \",\")\n\t}\n\n\tif len(localService.Annotations[consts.RemoteDiscoveryAnnotation]) == 0 && len(localService.Annotations[consts.LocalDiscoveryAnnotation]) == 0 {\n\t\trcsw.log.Infof(\"Deleting federated service %s/%s\", ev.Namespace, localServiceName)\n\t\tif err := rcsw.localAPIClient.Client.CoreV1().Services(ev.Namespace).Delete(ctx, localServiceName, metav1.DeleteOptions{}); err != nil {\n\t\t\tif !kerrors.IsNotFound(err) {\n\t\t\t\treturn RetryableError{[]error{fmt.Errorf(\"could not delete service: %s/%s: %w\", ev.Namespace, localServiceName, err)}}\n\t\t\t}\n\t\t}\n\t\trcsw.log.Infof(\"Successfully deleted service: %s/%s\", ev.Namespace, localServiceName)\n\t\trcsw.deleteLinkFederatedStatus(\n\t\t\tev.Name, ev.Namespace,\n\t\t)\n\t\treturn nil\n\t}\n\n\tif _, err := rcsw.localAPIClient.Client.CoreV1().Services(ev.Namespace).Update(ctx, localService, metav1.UpdateOptions{}); err != nil {\n\t\treturn RetryableError{[]error{err}}\n\t}\n\treturn nil\n}\n\n// Updates a locally mirrored service. There might have been some pretty fundamental changes such as\n// new gateway being assigned or additional ports exposed. This method takes care of that.\nfunc (rcsw *RemoteClusterServiceWatcher) handleRemoteExportedServiceUpdated(ctx context.Context, ev *RemoteExportedServiceUpdated) error {\n\trcsw.log.Infof(\"Updating mirror service for %s/%s\", ev.remoteUpdate.Namespace, ev.remoteUpdate.Name)\n\n\tmirrorName := rcsw.mirrorServiceName(ev.remoteUpdate.Name)\n\tlocalService, err := rcsw.localAPIClient.Svc().Lister().Services(ev.remoteUpdate.Namespace).Get(mirrorName)\n\tif err != nil {\n\t\tif kerrors.IsNotFound(err) {\n\t\t\trcsw.log.Infof(\"Mirror service %s/%s not found, re-creating\", ev.remoteUpdate.Namespace, mirrorName)\n\t\t\trcsw.eventsQueue.Add(&RemoteServiceExported{\n\t\t\t\tservice: ev.remoteUpdate,\n\t\t\t})\n\t\t\treturn nil\n\t\t}\n\t\treturn RetryableError{[]error{err}}\n\t}\n\n\tlocalEndpoints, err := rcsw.localAPIClient.Endpoint().Lister().Endpoints(localService.Namespace).Get(mirrorName)\n\tif err != nil {\n\t\tif kerrors.IsNotFound(err) {\n\t\t\tlocalEndpoints = nil\n\t\t} else {\n\t\t\treturn RetryableError{[]error{err}}\n\t\t}\n\t}\n\n\tif rcsw.isRemoteDiscovery(ev.remoteUpdate.Labels) {\n\t\t// The service is mirrored in remote discovery mode and any local\n\t\t// endpoints for it should be deleted if they exist.\n\t\tif localEndpoints != nil {\n\t\t\terr := rcsw.localAPIClient.Client.CoreV1().Endpoints(localService.Namespace).Delete(ctx, localService.Name, metav1.DeleteOptions{})\n\t\t\tif err != nil {\n\t\t\t\trcsw.updateLinkMirrorStatus(\n\t\t\t\t\tev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),\n\t\t\t\t\tmirrorStatusCondition(false, reasonError, fmt.Sprintf(\"Failed to delete mirror endpoints: %s\", err), nil),\n\t\t\t\t)\n\t\t\t\treturn RetryableError{[]error{\n\t\t\t\t\tfmt.Errorf(\"failed to delete mirror endpoints for %s/%s: %w\", localService.Namespace, localService.Name, err),\n\t\t\t\t}}\n\t\t\t}\n\t\t}\n\t} else if localEndpoints == nil {\n\t\t// The service is mirrored in gateway mode and gateway endpoints should\n\t\t// be created for it.\n\t\terr := rcsw.createGatewayEndpoints(ctx, ev.remoteUpdate)\n\t\tif err != nil {\n\t\t\trcsw.updateLinkMirrorStatus(\n\t\t\t\tev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),\n\t\t\t\tmirrorStatusCondition(false, reasonError, fmt.Sprintf(\"Failed to create mirror endpoints: %s\", err), nil),\n\t\t\t)\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\t// The service is mirrored in gateway mode and gateway endpoints already\n\t\t// exist for it but may need to be updated.\n\t\tgatewayAddresses, err := rcsw.resolveGatewayAddress()\n\t\tif err != nil {\n\t\t\trcsw.updateLinkMirrorStatus(\n\t\t\t\tev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),\n\t\t\t\tmirrorStatusCondition(false, reasonError, fmt.Sprintf(\"Failed to get gateway address: %s\", err), nil),\n\t\t\t)\n\t\t\treturn err\n\t\t}\n\n\t\tcopiedEndpoints := localEndpoints.DeepCopy()\n\t\tports, err := rcsw.getEndpointsPorts(ev.remoteUpdate)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tcopiedEndpoints.Subsets = []corev1.EndpointSubset{\n\t\t\t{\n\t\t\t\tAddresses: gatewayAddresses,\n\t\t\t\tPorts:     ports,\n\t\t\t},\n\t\t}\n\n\t\tif copiedEndpoints.Annotations == nil {\n\t\t\tcopiedEndpoints.Annotations = make(map[string]string)\n\t\t}\n\t\tcopiedEndpoints.Annotations[consts.RemoteGatewayIdentity] = rcsw.link.Spec.GatewayIdentity\n\n\t\terr = rcsw.updateMirrorEndpoints(ctx, copiedEndpoints)\n\t\tif err != nil {\n\t\t\trcsw.updateLinkMirrorStatus(\n\t\t\t\tev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),\n\t\t\t\tmirrorStatusCondition(false, reasonError, fmt.Sprintf(\"Failed to update mirror endpoints: %s\", err), nil),\n\t\t\t)\n\t\t\treturn RetryableError{[]error{err}}\n\t\t}\n\t}\n\n\tlocalService.Labels = rcsw.getMirrorServiceLabels(ev.remoteUpdate)\n\tlocalService.Annotations = rcsw.getMirrorServiceAnnotations(ev.remoteUpdate)\n\tlocalService.Spec.Ports = remapRemoteServicePorts(ev.remoteUpdate.Spec.Ports)\n\n\tif _, err := rcsw.localAPIClient.Client.CoreV1().Services(localService.Namespace).Update(ctx, localService, metav1.UpdateOptions{}); err != nil {\n\t\trcsw.updateLinkMirrorStatus(\n\t\t\tev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),\n\t\t\tmirrorStatusCondition(false, reasonError, fmt.Sprintf(\"Failed to update mirror service: %s\", err), nil),\n\t\t)\n\t\treturn RetryableError{[]error{err}}\n\t}\n\trcsw.updateLinkMirrorStatus(\n\t\tev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),\n\t\tmirrorStatusCondition(true, reasonMirrored, \"\", localService),\n\t)\n\treturn nil\n}\n\n// Updates a federated service to include the remote service as a member.\nfunc (rcsw *RemoteClusterServiceWatcher) handleFederatedServiceJoin(ctx context.Context, ev *RemoteServiceJoinsFederatedService) error {\n\tfederatedName := rcsw.federatedServiceName(ev.remoteUpdate.Name)\n\trcsw.log.Infof(\"Updating federated service %s/%s\", ev.remoteUpdate.Namespace, federatedName)\n\n\tlocalService, err := rcsw.localAPIClient.Svc().Lister().Services(ev.remoteUpdate.Namespace).Get(federatedName)\n\tif err != nil {\n\t\tif kerrors.IsNotFound(err) {\n\t\t\trcsw.eventsQueue.Add(&CreateFederatedService{\n\t\t\t\tservice: ev.remoteUpdate,\n\t\t\t})\n\t\t\treturn nil\n\t\t}\n\t\trcsw.updateLinkFederatedStatus(\n\t\t\tev.remoteUpdate.Name, ev.remoteUpdate.Namespace,\n\t\t\tmirrorStatusCondition(false, reasonError, fmt.Sprintf(\"Failed to get federated service: %s\", err), nil),\n\t\t)\n\t\treturn RetryableError{[]error{err}}\n\t}\n\n\tprevious := localService.DeepCopy()\n\n\tif ev.remoteUpdate.Spec.ClusterIP == corev1.ClusterIPNone {\n\t\trcsw.updateLinkFederatedStatus(\n\t\t\tev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),\n\t\t\tmirrorStatusCondition(false, reasonInvalidService, \"Headless service cannot join federated service\", nil),\n\t\t)\n\t\treturn fmt.Errorf(\"headless service %s/%s cannot join federated service\", ev.remoteUpdate.GetNamespace(), ev.remoteUpdate.GetName())\n\t}\n\n\t// We treat the member with the oldest Link as the source of truth for Service\n\t// metadata such as labels and annotations.\n\tvar isOldest bool\n\tif rcsw.link.Spec.TargetClusterName == \"\" {\n\t\t// Always treat the local cluster as the oldest.\n\t\tisOldest = true\n\t} else if _, localDiscoveryExists := localService.Annotations[consts.LocalDiscoveryAnnotation]; localDiscoveryExists {\n\t\t// The local cluster is older than us.\n\t\tisOldest = false\n\t} else {\n\t\t// The local cluster is not a member of the federated service. Therefore,\n\t\t// we check the remote discovery to see if we are the oldest.\n\t\tisOldest = true\n\t\tmembers := strings.Split(localService.Annotations[consts.RemoteDiscoveryAnnotation], \",\")\n\t\tfor _, member := range members {\n\t\t\ttarget := strings.Split(member, \"@\")\n\t\t\tif len(target) != 2 {\n\t\t\t\trcsw.log.Errorf(\"Invalid federated service member: %s\", member)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcluster := target[1]\n\t\t\tlink, err := rcsw.linksAPIClient.Link().Lister().Links(rcsw.serviceMirrorNamespace).Get(cluster)\n\t\t\tif err != nil {\n\t\t\t\trcsw.log.Errorf(\"Failed to get link %s: %s\", cluster, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif link.CreationTimestamp.Before(&rcsw.link.CreationTimestamp) {\n\t\t\t\trcsw.log.Debugf(\"ignoring metadata from %s because %s is older\", rcsw.link.Spec.TargetClusterName, cluster)\n\t\t\t\tisOldest = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif isOldest {\n\t\tpreservedAnnotations := map[string]string{}\n\t\tfor _, k := range []string{consts.RemoteDiscoveryAnnotation, consts.LocalDiscoveryAnnotation} {\n\t\t\tif v, ok := localService.Annotations[k]; ok {\n\t\t\t\tpreservedAnnotations[k] = v\n\t\t\t}\n\t\t}\n\t\tlocalService.Labels = rcsw.getFederatedServiceLabels(ev.remoteUpdate)\n\t\tlocalService.Annotations = rcsw.getFederatedServiceAnnotations(ev.remoteUpdate)\n\t\tfor k, v := range preservedAnnotations {\n\t\t\tlocalService.Annotations[k] = v\n\t\t}\n\n\t\tlocalService.Spec.Ports = remapRemoteServicePorts(ev.remoteUpdate.Spec.Ports)\n\t}\n\n\tif rcsw.link.Spec.TargetClusterName == \"\" {\n\t\t// Local discovery\n\t\tlocalService.Annotations[consts.LocalDiscoveryAnnotation] = ev.remoteUpdate.Name\n\t} else {\n\t\t// Remote discovery\n\t\tremoteTarget := fmt.Sprintf(\"%s@%s\", ev.remoteUpdate.Name, rcsw.link.Spec.TargetClusterName)\n\t\tif !remoteDiscoveryContains(localService.Annotations[consts.RemoteDiscoveryAnnotation], remoteTarget) {\n\t\t\tif localService.Annotations[consts.RemoteDiscoveryAnnotation] == \"\" {\n\t\t\t\tlocalService.Annotations[consts.RemoteDiscoveryAnnotation] = remoteTarget\n\t\t\t} else {\n\t\t\t\tlocalService.Annotations[consts.RemoteDiscoveryAnnotation] = fmt.Sprintf(\n\t\t\t\t\t\"%s,%s\",\n\t\t\t\t\tlocalService.Annotations[consts.RemoteDiscoveryAnnotation],\n\t\t\t\t\tremoteTarget,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Don't update the service if it has not changed.\n\tif reflect.DeepEqual(previous, localService) {\n\t\treturn nil\n\t}\n\n\tif _, err := rcsw.localAPIClient.Client.CoreV1().Services(localService.Namespace).Update(ctx, localService, metav1.UpdateOptions{}); err != nil {\n\t\trcsw.updateLinkFederatedStatus(\n\t\t\tev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),\n\t\t\tmirrorStatusCondition(false, reasonError, fmt.Sprintf(\"Failed to update federated service: %s\", err), nil),\n\t\t)\n\t\treturn RetryableError{[]error{err}}\n\t}\n\trcsw.updateLinkFederatedStatus(\n\t\tev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),\n\t\tmirrorStatusCondition(true, reasonMirrored, \"\", localService),\n\t)\n\treturn nil\n}\n\nfunc remoteDiscoveryContains(remoteDiscoveryList string, remoteTarget string) bool {\n\tfor _, member := range strings.Split(remoteDiscoveryList, \",\") {\n\t\tif member == remoteTarget {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc remapRemoteServicePorts(ports []corev1.ServicePort) []corev1.ServicePort {\n\t// We ignore the NodePort here as its not relevant\n\t// to the local cluster\n\tvar newPorts []corev1.ServicePort\n\tfor _, port := range ports {\n\t\tnewPorts = append(newPorts, corev1.ServicePort{\n\t\t\tName:       port.Name,\n\t\t\tProtocol:   port.Protocol,\n\t\t\tPort:       port.Port,\n\t\t\tTargetPort: port.TargetPort,\n\t\t})\n\t}\n\treturn newPorts\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) handleRemoteServiceExported(ctx context.Context, ev *RemoteServiceExported) error {\n\tremoteService := ev.service.DeepCopy()\n\tif rcsw.headlessServicesEnabled && remoteService.Spec.ClusterIP == corev1.ClusterIPNone {\n\t\trcsw.updateLinkMirrorStatus(\n\t\t\tev.service.GetName(), ev.service.GetNamespace(),\n\t\t\tmirrorStatusCondition(false, reasonInvalidService, \"Headless mirror services are disabled\", nil),\n\t\t)\n\t\treturn nil\n\t}\n\n\tserviceInfo := fmt.Sprintf(\"%s/%s\", remoteService.Namespace, remoteService.Name)\n\tlocalServiceName := rcsw.mirrorServiceName(remoteService.Name)\n\n\tif rcsw.namespaceCreationEnabled {\n\t\tif err := rcsw.mirrorNamespaceIfNecessary(ctx, remoteService.Namespace); err != nil {\n\t\t\trcsw.updateLinkMirrorStatus(\n\t\t\t\tev.service.GetName(), ev.service.GetNamespace(),\n\t\t\t\tmirrorStatusCondition(false, reasonError, fmt.Sprintf(\"Failed to create namespace: %s\", err), nil),\n\t\t\t)\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\t// Ensure the namespace exists, and skip mirroring if it doesn't\n\t\tif _, err := rcsw.localAPIClient.Client.CoreV1().Namespaces().Get(ctx, remoteService.Namespace, metav1.GetOptions{}); err != nil {\n\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\trcsw.recorder.Event(remoteService, corev1.EventTypeNormal, eventTypeSkipped, \"Skipped mirroring service: namespace does not exist\")\n\t\t\t\trcsw.log.Warnf(\"Skipping mirroring of service %s: namespace %s does not exist\", serviceInfo, remoteService.Namespace)\n\t\t\t\trcsw.updateLinkMirrorStatus(\n\t\t\t\t\tev.service.GetName(), ev.service.GetNamespace(),\n\t\t\t\t\tmirrorStatusCondition(false, reasonMissingNamespace, \"Namespace does not exist\", nil),\n\t\t\t\t)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trcsw.updateLinkMirrorStatus(\n\t\t\t\tev.service.GetName(), ev.service.GetNamespace(),\n\t\t\t\tmirrorStatusCondition(false, reasonError, fmt.Sprintf(\"Failed get namespace: %s\", err), nil),\n\t\t\t)\n\t\t\t// something else went wrong, so we can just retry\n\t\t\treturn RetryableError{[]error{err}}\n\t\t}\n\t}\n\n\tserviceToCreate := &corev1.Service{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        localServiceName,\n\t\t\tNamespace:   remoteService.Namespace,\n\t\t\tAnnotations: rcsw.getMirrorServiceAnnotations(remoteService),\n\t\t\tLabels:      rcsw.getMirrorServiceLabels(remoteService),\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tPorts: remapRemoteServicePorts(remoteService.Spec.Ports),\n\t\t},\n\t}\n\n\trcsw.log.Infof(\"Creating a new service mirror for %s\", serviceInfo)\n\tif _, err := rcsw.localAPIClient.Client.CoreV1().Services(remoteService.Namespace).Create(ctx, serviceToCreate, metav1.CreateOptions{}); err != nil {\n\t\tif !kerrors.IsAlreadyExists(err) {\n\t\t\trcsw.updateLinkMirrorStatus(\n\t\t\t\tev.service.GetName(), ev.service.GetNamespace(),\n\t\t\t\tmirrorStatusCondition(false, reasonError, fmt.Sprintf(\"Failed to create mirror service: %s\", err), nil),\n\t\t\t)\n\t\t\t// we might have created it during earlier attempt, if that is not the case, we retry\n\t\t\treturn RetryableError{[]error{err}}\n\t\t}\n\t}\n\n\tif rcsw.isRemoteDiscovery(remoteService.Labels) {\n\t\t// For remote discovery services, skip creating gateway endpoints.\n\t\trcsw.updateLinkMirrorStatus(\n\t\t\tev.service.GetName(), ev.service.GetNamespace(),\n\t\t\tmirrorStatusCondition(true, reasonMirrored, \"\", serviceToCreate),\n\t\t)\n\t\treturn nil\n\t}\n\n\terr := rcsw.createGatewayEndpoints(ctx, remoteService)\n\tif err != nil {\n\t\trcsw.updateLinkMirrorStatus(\n\t\t\tev.service.GetName(), ev.service.GetNamespace(),\n\t\t\tmirrorStatusCondition(false, reasonError, fmt.Sprintf(\"Failed to create mirror endpoints: %s\", err), nil),\n\t\t)\n\t\treturn err\n\t}\n\n\trcsw.updateLinkMirrorStatus(\n\t\tev.service.GetName(), ev.service.GetNamespace(),\n\t\tmirrorStatusCondition(true, reasonMirrored, \"\", serviceToCreate),\n\t)\n\treturn nil\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) handleCreateFederatedService(ctx context.Context, ev *CreateFederatedService) error {\n\tremoteService := ev.service.DeepCopy()\n\tserviceInfo := fmt.Sprintf(\"%s/%s\", remoteService.Namespace, remoteService.Name)\n\n\tif remoteService.Spec.ClusterIP == corev1.ClusterIPNone {\n\t\trcsw.updateLinkFederatedStatus(\n\t\t\tremoteService.GetName(), remoteService.GetNamespace(),\n\t\t\tmirrorStatusCondition(false, reasonInvalidService, \"Headless service cannot join federated service\", nil),\n\t\t)\n\t\treturn fmt.Errorf(\"headless service %s cannot join federated service\", serviceInfo)\n\t}\n\n\tlocalServiceName := rcsw.federatedServiceName(remoteService.Name)\n\n\tif rcsw.namespaceCreationEnabled {\n\t\tif err := rcsw.mirrorNamespaceIfNecessary(ctx, remoteService.Namespace); err != nil {\n\t\t\trcsw.updateLinkFederatedStatus(\n\t\t\t\tremoteService.GetName(), remoteService.GetNamespace(),\n\t\t\t\tmirrorStatusCondition(false, reasonError, fmt.Sprintf(\"Failed to create namespace: %s\", err), nil),\n\t\t\t)\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\t// Ensure the namespace exists, and skip mirroring if it doesn't\n\t\tif _, err := rcsw.localAPIClient.Client.CoreV1().Namespaces().Get(ctx, remoteService.Namespace, metav1.GetOptions{}); err != nil {\n\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\trcsw.recorder.Event(remoteService, corev1.EventTypeNormal, eventTypeSkipped, \"Skipped mirroring service: namespace does not exist\")\n\t\t\t\trcsw.log.Warnf(\"Skipping mirroring of service %s: namespace %s does not exist\", serviceInfo, remoteService.Namespace)\n\t\t\t\trcsw.updateLinkFederatedStatus(\n\t\t\t\t\tremoteService.GetName(), remoteService.GetNamespace(),\n\t\t\t\t\tmirrorStatusCondition(false, reasonMissingNamespace, \"Namespace does not exist\", nil),\n\t\t\t\t)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\t// something else went wrong, so we can just retry\n\t\t\trcsw.updateLinkFederatedStatus(\n\t\t\t\tremoteService.GetName(), remoteService.GetNamespace(),\n\t\t\t\tmirrorStatusCondition(false, reasonError, fmt.Sprintf(\"Failed get namespace: %s\", err), nil),\n\t\t\t)\n\t\t\treturn RetryableError{[]error{err}}\n\t\t}\n\t}\n\n\tserviceToCreate := &corev1.Service{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        localServiceName,\n\t\t\tNamespace:   remoteService.Namespace,\n\t\t\tAnnotations: rcsw.getFederatedServiceAnnotations(remoteService),\n\t\t\tLabels:      rcsw.getFederatedServiceLabels(remoteService),\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tPorts: remapRemoteServicePorts(remoteService.Spec.Ports),\n\t\t},\n\t}\n\n\trcsw.log.Infof(\"Creating a new federated service for %s\", serviceInfo)\n\tif _, err := rcsw.localAPIClient.Client.CoreV1().Services(remoteService.Namespace).Create(ctx, serviceToCreate, metav1.CreateOptions{}); err != nil {\n\t\tif !kerrors.IsAlreadyExists(err) {\n\t\t\t// we might have created it during earlier attempt, if that is not the case, we retry\n\t\t\trcsw.updateLinkFederatedStatus(\n\t\t\t\tremoteService.GetName(), remoteService.GetNamespace(),\n\t\t\t\tmirrorStatusCondition(false, reasonError, fmt.Sprintf(\"Failed to create federated service: %s\", err), nil),\n\t\t\t)\n\t\t\treturn RetryableError{[]error{err}}\n\t\t}\n\t}\n\n\trcsw.updateLinkFederatedStatus(\n\t\tremoteService.GetName(), remoteService.GetNamespace(),\n\t\tmirrorStatusCondition(true, reasonMirrored, \"\", serviceToCreate),\n\t)\n\treturn nil\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) handleLocalNamespaceAdded(ns *corev1.Namespace) error {\n\t// When a local namespace is added, we issue a create event for all the services in the corresponding namespace in\n\t// case any of them are exported and need to be mirrored.\n\tsvcs, err := rcsw.remoteAPIClient.Svc().Lister().Services(ns.Name).List(labels.Everything())\n\tif err != nil {\n\t\treturn RetryableError{[]error{err}}\n\t}\n\tfor _, svc := range svcs {\n\t\trcsw.eventsQueue.Add(&OnAddCalled{\n\t\t\tsvc: svc,\n\t\t})\n\t}\n\treturn nil\n}\n\n// isEmptyService returns true if any of these conditions are true:\n// - svc's Endpoint is not found\n// - svc's Endpoint has no Subsets (happens when there's no associated Pod)\n// - svc's Endpoint has Subsets, but none have addresses (only notReadyAddresses,\n// when the pod is not ready yet)\nfunc (rcsw *RemoteClusterServiceWatcher) isEmptyService(svc *corev1.Service) (bool, error) {\n\tep, err := rcsw.remoteAPIClient.Endpoint().Lister().Endpoints(svc.Namespace).Get(svc.Name)\n\tif err != nil {\n\t\tif kerrors.IsNotFound(err) {\n\t\t\trcsw.log.Debugf(\"target endpoint %s/%s not found\", svc.Namespace, svc.Name)\n\t\t\treturn true, nil\n\t\t}\n\n\t\treturn true, err\n\t}\n\treturn rcsw.isEmptyEndpoints(ep), nil\n}\n\n// isEmptyEndpoints returns true if any of these conditions are true:\n// - The Endpoint is not found\n// - The Endpoint has no Subsets (happens when there's no associated Pod)\n// - The Endpoint has Subsets, but none have addresses (only notReadyAddresses,\n// when the pod is not ready yet)\nfunc (rcsw *RemoteClusterServiceWatcher) isEmptyEndpoints(ep *corev1.Endpoints) bool {\n\tif len(ep.Subsets) == 0 {\n\t\trcsw.log.Debugf(\"endpoint %s/%s has no Subsets\", ep.Namespace, ep.Name)\n\t\treturn true\n\t}\n\tfor _, subset := range ep.Subsets {\n\t\tif len(subset.Addresses) > 0 {\n\t\t\treturn false\n\t\t}\n\t}\n\trcsw.log.Debugf(\"endpoint %s/%s has no ready addresses\", ep.Namespace, ep.Name)\n\treturn true\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) createGatewayEndpoints(ctx context.Context, exportedService *corev1.Service) error {\n\tempty, err := rcsw.isEmptyService(exportedService)\n\tif err != nil {\n\t\treturn RetryableError{[]error{err}}\n\t}\n\n\tgatewayAddresses, err := rcsw.resolveGatewayAddress()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlocalServiceName := rcsw.mirrorServiceName(exportedService.Name)\n\tserviceInfo := fmt.Sprintf(\"%s/%s\", exportedService.Namespace, exportedService.Name)\n\tendpointsToCreate := &corev1.Endpoints{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      localServiceName,\n\t\t\tNamespace: exportedService.Namespace,\n\t\t\tLabels:    rcsw.getMirrorEndpointLabels(exportedService),\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tconsts.RemoteServiceFqName: fmt.Sprintf(\"%s.%s.svc.%s\", exportedService.Name, exportedService.Namespace, rcsw.link.Spec.TargetClusterDomain),\n\t\t\t},\n\t\t},\n\t}\n\n\trcsw.log.Infof(\"Resolved gateway [%v:%s] for %s\", gatewayAddresses, rcsw.link.Spec.GatewayPort, serviceInfo)\n\n\tports, err := rcsw.getEndpointsPorts(exportedService)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !empty && len(gatewayAddresses) > 0 {\n\n\t\tendpointsToCreate.Subsets = []corev1.EndpointSubset{\n\t\t\t{\n\t\t\t\tAddresses: gatewayAddresses,\n\t\t\t\tPorts:     ports,\n\t\t\t},\n\t\t}\n\t} else if !empty {\n\t\tendpointsToCreate.Subsets = []corev1.EndpointSubset{\n\t\t\t{\n\t\t\t\tNotReadyAddresses: gatewayAddresses,\n\t\t\t\tPorts:             ports,\n\t\t\t},\n\t\t}\n\t\trcsw.log.Warnf(\"could not resolve gateway addresses for %s; setting endpoint subsets to not ready\", serviceInfo)\n\t} else {\n\t\trcsw.log.Warnf(\"exported service %s is empty\", serviceInfo)\n\t}\n\n\tif rcsw.link.Spec.GatewayIdentity != \"\" {\n\t\tendpointsToCreate.Annotations[consts.RemoteGatewayIdentity] = rcsw.link.Spec.GatewayIdentity\n\t}\n\n\trcsw.log.Infof(\"Creating a new endpoints for %s\", serviceInfo)\n\terr = rcsw.createMirrorEndpoints(ctx, endpointsToCreate)\n\tif err != nil {\n\t\tif svcErr := rcsw.localAPIClient.Client.CoreV1().Services(exportedService.Namespace).Delete(ctx, localServiceName, metav1.DeleteOptions{}); svcErr != nil {\n\t\t\trcsw.log.Errorf(\"Failed to delete service %s after endpoints creation failed: %s\", localServiceName, svcErr)\n\t\t}\n\t\treturn RetryableError{[]error{err}}\n\t}\n\treturn nil\n}\n\n// this method is common to both CREATE and UPDATE because if we have been\n// offline for some time due to a crash a CREATE for a service that we have\n// observed before is simply a case of UPDATE\nfunc (rcsw *RemoteClusterServiceWatcher) createOrUpdateService(service *corev1.Service) error {\n\tmirrorName := rcsw.mirrorServiceName(service.Name)\n\n\tif rcsw.isExported(service.Labels) || rcsw.isRemoteDiscovery(service.Labels) {\n\t\t// The desired state is that the local mirror service should exist.\n\t\tlocalService, err := rcsw.localAPIClient.Svc().Lister().Services(service.Namespace).Get(mirrorName)\n\t\tif err != nil {\n\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\trcsw.eventsQueue.Add(&RemoteServiceExported{\n\t\t\t\t\tservice: service,\n\t\t\t\t})\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn RetryableError{[]error{err}}\n\t\t}\n\t\t// if we have the local service present, we need to issue an update\n\t\tlastMirroredRemoteVersion, ok := localService.Annotations[consts.RemoteResourceVersionAnnotation]\n\t\tif ok && lastMirroredRemoteVersion != service.ResourceVersion {\n\t\t\trcsw.eventsQueue.Add(&RemoteExportedServiceUpdated{\n\t\t\t\tremoteUpdate: service,\n\t\t\t})\n\t\t}\n\t} else {\n\t\t// The desired state is that the local mirror service should not exist.\n\t\tlocalSvc, err := rcsw.localAPIClient.Svc().Lister().Services(service.Namespace).Get(mirrorName)\n\t\tif err == nil {\n\t\t\tif localSvc.Labels != nil {\n\t\t\t\t_, isMirroredRes := localSvc.Labels[consts.MirroredResourceLabel]\n\t\t\t\tclusterName := localSvc.Labels[consts.RemoteClusterNameLabel]\n\t\t\t\tif isMirroredRes && (clusterName == rcsw.link.Spec.TargetClusterName) {\n\t\t\t\t\trcsw.eventsQueue.Add(&RemoteServiceUnexported{\n\t\t\t\t\t\tName:      service.Name,\n\t\t\t\t\t\tNamespace: service.Namespace,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfederatedName := rcsw.federatedServiceName(service.Name)\n\n\tif rcsw.isFederatedServiceMember(service.Labels) {\n\t\t// The desired state is that the local federated service should exist.\n\t\trcsw.eventsQueue.Add(&RemoteServiceJoinsFederatedService{\n\t\t\tremoteUpdate: service,\n\t\t})\n\t} else {\n\t\t// The desired state is that the local federated service should not\n\t\t// include the remote service.\n\t\tlocalSvc, err := rcsw.localAPIClient.Svc().Lister().Services(service.Namespace).Get(federatedName)\n\t\tif err == nil {\n\t\t\tif localSvc.Labels != nil {\n\t\t\t\t_, isMirroredRes := localSvc.Labels[consts.MirroredResourceLabel]\n\t\t\t\tif isMirroredRes {\n\t\t\t\t\trcsw.eventsQueue.Add(&RemoteServiceLeavesFederatedService{\n\t\t\t\t\t\tName:      service.Name,\n\t\t\t\t\t\tNamespace: service.Namespace,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) getMirrorServices() (*corev1.ServiceList, error) {\n\tmatchLabels := map[string]string{\n\t\tconsts.MirroredResourceLabel:  \"true\",\n\t\tconsts.RemoteClusterNameLabel: rcsw.link.Spec.TargetClusterName,\n\t}\n\tservices, err := rcsw.localAPIClient.Client.CoreV1().Services(\"\").List(context.Background(), metav1.ListOptions{LabelSelector: labels.SelectorFromSet(matchLabels).String()})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn services, nil\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) handleOnDelete(service *corev1.Service) {\n\tif rcsw.isExported(service.Labels) || rcsw.isRemoteDiscovery(service.Labels) {\n\t\trcsw.eventsQueue.Add(&RemoteServiceUnexported{\n\t\t\tName:      service.Name,\n\t\t\tNamespace: service.Namespace,\n\t\t})\n\t}\n\tif rcsw.isFederatedServiceMember(service.Labels) {\n\t\trcsw.eventsQueue.Add(&RemoteServiceLeavesFederatedService{\n\t\t\tName:      service.Name,\n\t\t\tNamespace: service.Namespace,\n\t\t})\n\t}\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) processNextEvent(ctx context.Context) (bool, interface{}, error) {\n\tevent, done := rcsw.eventsQueue.Get()\n\tif event != nil {\n\t\trcsw.log.Infof(\"Received: %s\", event)\n\t} else if done {\n\t\trcsw.log.Infof(\"Received: Stop\")\n\t}\n\n\tvar err error\n\tswitch ev := event.(type) {\n\tcase *OnAddCalled:\n\t\terr = rcsw.createOrUpdateService(ev.svc)\n\tcase *OnAddEndpointsCalled:\n\t\terr = rcsw.handleCreateOrUpdateEndpoints(ctx, ev.ep)\n\tcase *OnUpdateCalled:\n\t\terr = rcsw.createOrUpdateService(ev.svc)\n\tcase *OnUpdateEndpointsCalled:\n\t\terr = rcsw.handleCreateOrUpdateEndpoints(ctx, ev.ep)\n\tcase *OnDeleteCalled:\n\t\trcsw.handleOnDelete(ev.svc)\n\tcase *RemoteServiceExported:\n\t\terr = rcsw.handleRemoteServiceExported(ctx, ev)\n\tcase *RemoteExportedServiceUpdated:\n\t\terr = rcsw.handleRemoteExportedServiceUpdated(ctx, ev)\n\tcase *RemoteServiceUnexported:\n\t\terr = rcsw.handleRemoteServiceUnexported(ctx, ev)\n\tcase *CreateFederatedService:\n\t\terr = rcsw.handleCreateFederatedService(ctx, ev)\n\tcase *RemoteServiceJoinsFederatedService:\n\t\terr = rcsw.handleFederatedServiceJoin(ctx, ev)\n\tcase *RemoteServiceLeavesFederatedService:\n\t\terr = rcsw.handleFederatedServiceLeave(ctx, ev)\n\tcase *ClusterUnregistered:\n\t\terr = rcsw.cleanupMirroredResources(ctx)\n\tcase *OrphanedServicesGcTriggered:\n\t\terr = rcsw.cleanupOrphanedServices(ctx)\n\tcase *RepairEndpoints:\n\t\terr = rcsw.repairEndpoints(ctx)\n\tcase *OnLocalNamespaceAdded:\n\t\terr = rcsw.handleLocalNamespaceAdded(ev.ns)\n\tdefault:\n\t\tif ev != nil || !done { // we get a nil in case we are shutting down...\n\t\t\trcsw.log.Warnf(\"Received unknown event: %v\", ev)\n\t\t}\n\t}\n\n\treturn done, event, err\n\n}\n\n// the main processing loop in which we handle more domain specific events\n// and deal with retries\nfunc (rcsw *RemoteClusterServiceWatcher) processEvents(ctx context.Context) {\n\tfor {\n\t\tdone, event, err := rcsw.processNextEvent(ctx)\n\t\trcsw.eventsQueue.Done(event)\n\t\t// the logic here is that there might have been an API\n\t\t// connectivity glitch or something. So its not a bad idea to requeue\n\t\t// the event and try again up to a number of limits, just to ensure\n\t\t// that we are not diverging in states due to bad luck...\n\t\tif err == nil {\n\t\t\trcsw.eventsQueue.Forget(event)\n\t\t} else {\n\t\t\tvar re RetryableError\n\t\t\tif errors.As(err, &re) {\n\t\t\t\trcsw.log.Warnf(\"Requeues: %d, Limit: %d for event %s\", rcsw.eventsQueue.NumRequeues(event), rcsw.requeueLimit, event)\n\t\t\t\tif (rcsw.eventsQueue.NumRequeues(event) < rcsw.requeueLimit) && !done {\n\t\t\t\t\trcsw.log.Errorf(\"Error processing %s (will retry): %s\", event, re)\n\t\t\t\t\trcsw.eventsQueue.AddRateLimited(event)\n\t\t\t\t} else {\n\t\t\t\t\trcsw.log.Errorf(\"Error processing %s (giving up): %s\", event, re)\n\t\t\t\t\trcsw.eventsQueue.Forget(event)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\trcsw.log.Errorf(\"Error processing %s (will not retry): %s\", event, err)\n\t\t\t}\n\t\t}\n\t\tif done {\n\t\t\trcsw.log.Infof(\"Shutting down events processor\")\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// Start starts watching the remote cluster\nfunc (rcsw *RemoteClusterServiceWatcher) Start(ctx context.Context) error {\n\trcsw.remoteAPIClient.Sync(rcsw.stopper)\n\trcsw.eventsQueue.Add(&OrphanedServicesGcTriggered{})\n\tvar err error\n\trcsw.svcHandler, err = rcsw.remoteAPIClient.Svc().Informer().AddEventHandler(\n\t\tcache.ResourceEventHandlerFuncs{\n\t\t\tAddFunc: func(svc interface{}) {\n\t\t\t\trcsw.eventsQueue.Add(&OnAddCalled{svc.(*corev1.Service)})\n\t\t\t},\n\t\t\tDeleteFunc: func(obj interface{}) {\n\t\t\t\tservice, ok := obj.(*corev1.Service)\n\t\t\t\tif !ok {\n\t\t\t\t\ttombstone, ok := obj.(cache.DeletedFinalStateUnknown)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\trcsw.log.Errorf(\"couldn't get object from DeletedFinalStateUnknown %#v\", obj)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tservice, ok = tombstone.Obj.(*corev1.Service)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\trcsw.log.Errorf(\"DeletedFinalStateUnknown contained object that is not a Service %#v\", obj)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trcsw.eventsQueue.Add(&OnDeleteCalled{service})\n\t\t\t},\n\t\t\tUpdateFunc: func(_, new interface{}) {\n\t\t\t\trcsw.eventsQueue.Add(&OnUpdateCalled{new.(*corev1.Service)})\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trcsw.epHandler, err = rcsw.remoteAPIClient.Endpoint().Informer().AddEventHandler(\n\t\tcache.ResourceEventHandlerFuncs{\n\t\t\t// AddFunc only relevant for exported headless endpoints\n\t\t\tAddFunc: func(obj interface{}) {\n\t\t\t\tep, ok := obj.(*corev1.Endpoints)\n\t\t\t\tif !ok {\n\t\t\t\t\trcsw.log.Errorf(\"error processing endpoints object: got %#v, expected *corev1.Endpoints\", ep)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif !rcsw.isExported(ep.Labels) {\n\t\t\t\t\trcsw.log.Debugf(\"skipped processing endpoints object %s/%s: missing %s label\", ep.Namespace, ep.Name, consts.DefaultExportedServiceSelector)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif !isHeadlessEndpoints(ep, rcsw.log) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\trcsw.eventsQueue.Add(&OnAddEndpointsCalled{obj.(*corev1.Endpoints)})\n\t\t\t},\n\t\t\t// AddFunc relevant for all kind of exported endpoints\n\t\t\tUpdateFunc: func(_, new interface{}) {\n\t\t\t\tepNew, ok := new.(*corev1.Endpoints)\n\t\t\t\tif !ok {\n\t\t\t\t\trcsw.log.Errorf(\"error processing endpoints object: got %#v, expected *corev1.Endpoints\", epNew)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif !rcsw.isExported(epNew.Labels) {\n\t\t\t\t\trcsw.log.Debugf(\"skipped processing endpoints object %s/%s: missing %s label\", epNew.Namespace, epNew.Name, consts.DefaultExportedServiceSelector)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif rcsw.isRemoteDiscovery(epNew.Labels) {\n\t\t\t\t\trcsw.log.Debugf(\"skipped processing endpoints object %s/%s (service labeled for remote-discovery mode)\", epNew.Namespace, epNew.Name)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\trcsw.eventsQueue.Add(&OnUpdateEndpointsCalled{epNew})\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trcsw.nsHandler, err = rcsw.localAPIClient.NS().Informer().AddEventHandler(\n\t\tcache.ResourceEventHandlerFuncs{\n\t\t\tAddFunc: func(obj interface{}) {\n\t\t\t\trcsw.eventsQueue.Add(&OnLocalNamespaceAdded{obj.(*corev1.Namespace)})\n\t\t\t},\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tgo rcsw.processEvents(ctx)\n\n\t// If no gateway address is present, do not repair endpoints\n\tif rcsw.link.Spec.GatewayAddress == \"\" {\n\t\treturn nil\n\t}\n\n\t// We need to issue a RepairEndpoints immediately to populate the gateway\n\t// mirror endpoints.\n\tev := RepairEndpoints{}\n\trcsw.eventsQueue.Add(&ev)\n\n\tgo func() {\n\t\tticker := time.NewTicker(rcsw.repairPeriod)\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ticker.C:\n\t\t\t\tev := RepairEndpoints{}\n\t\t\t\trcsw.eventsQueue.Add(&ev)\n\t\t\tcase alive := <-rcsw.liveness:\n\t\t\t\trcsw.log.Debugf(\"gateway liveness change from %t to %t\", rcsw.gatewayAlive, alive)\n\t\t\t\trcsw.gatewayAlive = alive\n\t\t\t\tev := RepairEndpoints{}\n\t\t\t\trcsw.eventsQueue.Add(&ev)\n\t\t\tcase <-rcsw.stopper:\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn nil\n}\n\n// Stop stops watching the cluster and cleans up all mirrored resources\nfunc (rcsw *RemoteClusterServiceWatcher) Stop(cleanupState bool) {\n\tclose(rcsw.stopper)\n\tif cleanupState {\n\t\trcsw.eventsQueue.Add(&ClusterUnregistered{})\n\t}\n\trcsw.eventsQueue.ShutDown()\n\trcsw.eventBroadcaster.Shutdown()\n\n\tif rcsw.svcHandler != nil {\n\t\tif err := rcsw.remoteAPIClient.Svc().Informer().RemoveEventHandler(rcsw.svcHandler); err != nil {\n\t\t\trcsw.log.Warnf(\"error removing service informer handler: %s\", err)\n\t\t}\n\t}\n\tif rcsw.epHandler != nil {\n\t\tif err := rcsw.remoteAPIClient.Endpoint().Informer().RemoveEventHandler(rcsw.epHandler); err != nil {\n\t\t\trcsw.log.Warnf(\"error removing service informer handler: %s\", err)\n\t\t}\n\t}\n\tif rcsw.nsHandler != nil {\n\t\tif err := rcsw.localAPIClient.NS().Informer().RemoveEventHandler(rcsw.nsHandler); err != nil {\n\t\t\trcsw.log.Warnf(\"error removing service informer handler: %s\", err)\n\t\t}\n\t}\n\n\tif rcsw.remoteAPIClient != nil {\n\t\trcsw.remoteAPIClient.UnregisterGauges()\n\t}\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) resolveGatewayAddress() ([]corev1.EndpointAddress, error) {\n\tvar gatewayEndpoints []corev1.EndpointAddress\n\tvar errors []error\n\tfor _, addr := range strings.Split(rcsw.link.Spec.GatewayAddress, \",\") {\n\t\tipAddrs, err := net.LookupIP(addr)\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"Error resolving '%s': %w\", addr, err)\n\t\t\trcsw.log.Warn(err)\n\t\t\terrors = append(errors, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, ipAddr := range ipAddrs {\n\t\t\tgatewayEndpoints = append(gatewayEndpoints, corev1.EndpointAddress{\n\t\t\t\tIP: ipAddr.String(),\n\t\t\t})\n\t\t}\n\t}\n\n\tif len(gatewayEndpoints) == 0 {\n\t\treturn nil, RetryableError{errors}\n\t}\n\n\tsort.SliceStable(gatewayEndpoints, func(i, j int) bool {\n\t\treturn gatewayEndpoints[i].IP < gatewayEndpoints[j].IP\n\t})\n\treturn gatewayEndpoints, nil\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) repairEndpoints(ctx context.Context) error {\n\tcounter, err := endpointRepairCounter.GetMetricWith(prometheus.Labels{\n\t\tgatewayClusterName: rcsw.link.Spec.TargetClusterName,\n\t})\n\tif err != nil {\n\t\trcsw.log.Errorf(\"Failed to get endpoint repair counter: %s\", err)\n\t} else {\n\t\tcounter.Inc()\n\t}\n\n\t// Create or update the gateway mirror endpoints responsible for driving\n\t// the cluster watcher's gateway liveness status.\n\tgatewayAddresses, err := rcsw.resolveGatewayAddress()\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = rcsw.createOrUpdateGatewayEndpoints(ctx, gatewayAddresses)\n\tif err != nil {\n\t\trcsw.log.Errorf(\"Failed to create/update gateway mirror endpoints: %s\", err)\n\t}\n\n\t// Repair mirror service endpoints.\n\tmirrorServices, err := rcsw.getMirrorServices()\n\tif err != nil {\n\t\treturn RetryableError{[]error{fmt.Errorf(\"Failed to list mirror services: %w\", err)}}\n\t}\n\tfor _, svc := range mirrorServices.Items {\n\t\tsvc := svc\n\n\t\t// Mirrors for headless services are also headless, and their\n\t\t// Endpoints point to auxiliary services instead of pointing to\n\t\t// the gateway, so they're skipped.\n\t\tif svc.Spec.ClusterIP == corev1.ClusterIPNone {\n\t\t\trcsw.log.Debugf(\"Skipped repairing endpoints for headless mirror %s/%s\", svc.Namespace, svc.Name)\n\t\t\tcontinue\n\t\t}\n\n\t\tif _, ok := svc.Labels[consts.RemoteDiscoveryLabel]; ok {\n\t\t\trcsw.log.Debugf(\"Skipped repairing endpoints for service in remote-discovery mode %s/%s\", svc.Namespace, svc.Name)\n\t\t\tcontinue\n\t\t}\n\n\t\tendpoints, err := rcsw.localAPIClient.Endpoint().Lister().Endpoints(svc.Namespace).Get(svc.Name)\n\t\tif err != nil {\n\t\t\tif !kerrors.IsNotFound(err) {\n\t\t\t\trcsw.log.Errorf(\"Failed to list local endpoints: %s\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tendpoints, err = rcsw.localAPIClient.Client.CoreV1().Endpoints(svc.Namespace).Get(ctx, svc.Name, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\trcsw.log.Errorf(\"Failed to get local endpoints %s/%s: %s\", svc.Namespace, svc.Name, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tupdatedEndpoints := endpoints.DeepCopy()\n\t\tports, err := rcsw.getEndpointsPorts(&svc)\n\t\tif err != nil {\n\t\t\trcsw.log.Errorf(\"Failed to get endpoints ports: %s\", err)\n\t\t\tcontinue\n\t\t}\n\t\tupdatedEndpoints.Subsets = []corev1.EndpointSubset{\n\t\t\t{\n\t\t\t\tAddresses: gatewayAddresses,\n\t\t\t\tPorts:     ports,\n\t\t\t},\n\t\t}\n\n\t\t// We want to skip this service empty check for auxiliary services --\n\t\t// services which are not headless but do belong to a headless\n\t\t// mirrored service. This is because they do not have a corresponding\n\t\t// endpoint on the target cluster, only a pod. If we attempt to find\n\t\t// endpoints for services like this, they'll always be set to empty.\n\t\tif _, found := svc.Labels[consts.MirroredHeadlessSvcNameLabel]; !found {\n\t\t\ttargetService := svc.DeepCopy()\n\t\t\ttargetService.Name = rcsw.targetResourceName(svc.Name)\n\t\t\tempty, err := rcsw.isEmptyService(targetService)\n\t\t\tif err != nil {\n\t\t\t\trcsw.log.Errorf(\"Could not check service emptiness: %s\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif empty {\n\t\t\t\trcsw.log.Warnf(\"Exported service %s/%s is empty\", targetService.Namespace, targetService.Name)\n\t\t\t\tupdatedEndpoints.Subsets = []corev1.EndpointSubset{}\n\t\t\t}\n\t\t}\n\n\t\tif updatedEndpoints.Annotations == nil {\n\t\t\tupdatedEndpoints.Annotations = make(map[string]string)\n\t\t}\n\t\tupdatedEndpoints.Annotations[consts.RemoteGatewayIdentity] = rcsw.link.Spec.GatewayIdentity\n\n\t\terr = rcsw.updateMirrorEndpoints(ctx, updatedEndpoints)\n\t\tif err != nil {\n\t\t\trcsw.log.Error(err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// createOrUpdateGatewayEndpoints will create or update the gateway mirror\n// endpoints for a remote cluster. These endpoints are required for the probe\n// worker responsible for probing gateway liveness, so these endpoints are\n// never in a not ready state.\nfunc (rcsw *RemoteClusterServiceWatcher) createOrUpdateGatewayEndpoints(ctx context.Context, addressses []corev1.EndpointAddress) error {\n\tprobePort, err := strconv.ParseInt(rcsw.link.Spec.ProbeSpec.Port, 10, 32)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse probe port: %w\", err)\n\t}\n\tendpoints := &corev1.Endpoints{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      rcsw.probeSvc,\n\t\t\tNamespace: rcsw.serviceMirrorNamespace,\n\t\t\tLabels: map[string]string{\n\t\t\t\tconsts.RemoteClusterNameLabel: rcsw.link.Spec.TargetClusterName,\n\t\t\t},\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tconsts.RemoteGatewayIdentity: rcsw.link.Spec.GatewayIdentity,\n\t\t\t},\n\t\t},\n\t\tSubsets: []corev1.EndpointSubset{\n\t\t\t{\n\t\t\t\tAddresses: addressses,\n\t\t\t\tPorts: []corev1.EndpointPort{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"mc-probe\",\n\t\t\t\t\t\tPort:     int32(probePort),\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t_, err = rcsw.localAPIClient.Client.CoreV1().Endpoints(endpoints.Namespace).Get(ctx, endpoints.Name, metav1.GetOptions{})\n\tif err != nil {\n\t\tif !kerrors.IsNotFound(err) {\n\t\t\treturn err\n\t\t}\n\n\t\t// Mirror endpoints for the gateway do not exist so they need to be\n\t\t// created. As mentioned above, these endpoints are required for the\n\t\t// probe worker and therefore should never be put in a not ready\n\t\t// state.\n\t\t_, err = rcsw.localAPIClient.Client.CoreV1().Endpoints(endpoints.Namespace).Create(ctx, endpoints, metav1.CreateOptions{})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Mirror endpoints for the gateway already exist so they need to be\n\t// updated. As mentioned above, these endpoints are required for the probe\n\t// worker and therefore should never be put in a not ready state.\n\t_, err = rcsw.localAPIClient.Client.CoreV1().Endpoints(endpoints.Namespace).Update(ctx, endpoints, metav1.UpdateOptions{})\n\treturn err\n}\n\n// handleCreateOrUpdateEndpoints forwards the call to\n// createOrUpdateHeadlessEndpoints when adding/updating exported headless\n// endpoints. Otherwise, it handles updates to endpoints to check if they've\n// become empty/filled since their creation, in order to empty/fill the\n// mirrored endpoints as well\nfunc (rcsw *RemoteClusterServiceWatcher) handleCreateOrUpdateEndpoints(\n\tctx context.Context,\n\texportedEndpoints *corev1.Endpoints,\n) error {\n\tif isHeadlessEndpoints(exportedEndpoints, rcsw.log) {\n\t\tif rcsw.headlessServicesEnabled {\n\t\t\treturn rcsw.createOrUpdateHeadlessEndpoints(ctx, exportedEndpoints)\n\t\t}\n\t\treturn nil\n\t}\n\n\tlocalServiceName := rcsw.mirrorServiceName(exportedEndpoints.Name)\n\tep, err := rcsw.localAPIClient.Endpoint().Lister().Endpoints(exportedEndpoints.Namespace).Get(localServiceName)\n\tif err != nil {\n\t\treturn RetryableError{[]error{err}}\n\t}\n\n\tif (rcsw.isEmptyEndpoints(ep) && rcsw.isEmptyEndpoints(exportedEndpoints)) ||\n\t\t(!rcsw.isEmptyEndpoints(ep) && !rcsw.isEmptyEndpoints(exportedEndpoints)) {\n\t\treturn nil\n\t}\n\n\trcsw.log.Infof(\"Updating subsets for mirror endpoint %s/%s\", exportedEndpoints.Namespace, exportedEndpoints.Name)\n\tif rcsw.isEmptyEndpoints(exportedEndpoints) {\n\t\tep.Subsets = []corev1.EndpointSubset{}\n\t} else {\n\t\texportedService, err := rcsw.remoteAPIClient.Svc().Lister().Services(exportedEndpoints.Namespace).Get(exportedEndpoints.Name)\n\t\tif err != nil {\n\t\t\treturn RetryableError{[]error{\n\t\t\t\tfmt.Errorf(\"error retrieving exported service %s/%s: %w\", exportedEndpoints.Namespace, exportedEndpoints.Name, err),\n\t\t\t}}\n\t\t}\n\t\tgatewayAddresses, err := rcsw.resolveGatewayAddress()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tports, err := rcsw.getEndpointsPorts(exportedService)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tep.Subsets = []corev1.EndpointSubset{\n\t\t\t{\n\t\t\t\tAddresses: gatewayAddresses,\n\t\t\t\tPorts:     ports,\n\t\t\t},\n\t\t}\n\t}\n\treturn rcsw.updateMirrorEndpoints(ctx, ep)\n}\n\n// createMirrorEndpoints will create endpoints based off gateway liveness. If\n// the gateway is not alive, then the addresses in each subset will be set to\n// not ready.\nfunc (rcsw *RemoteClusterServiceWatcher) createMirrorEndpoints(ctx context.Context, endpoints *corev1.Endpoints) error {\n\trcsw.updateReadiness(endpoints)\n\t_, err := rcsw.localAPIClient.Client.CoreV1().Endpoints(endpoints.Namespace).Create(ctx, endpoints, metav1.CreateOptions{})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create mirror endpoints for %s/%s: %w\", endpoints.Namespace, endpoints.Name, err)\n\t}\n\treturn nil\n}\n\n// updateMirrorEndpoints will update endpoints based off gateway liveness. If\n// the gateway is not alive, then the addresses in each subset will be set to\n// not ready. Future calls to updateMirrorEndpoints can set the addresses back\n// to ready if the gateway is alive.\nfunc (rcsw *RemoteClusterServiceWatcher) updateMirrorEndpoints(ctx context.Context, endpoints *corev1.Endpoints) error {\n\trcsw.updateReadiness(endpoints)\n\t_, err := rcsw.localAPIClient.Client.CoreV1().Endpoints(endpoints.Namespace).Update(ctx, endpoints, metav1.UpdateOptions{})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to update mirror endpoints for %s/%s: %w\", endpoints.Namespace, endpoints.Name, err)\n\t}\n\treturn err\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) updateReadiness(endpoints *corev1.Endpoints) {\n\tif !rcsw.gatewayAlive {\n\t\trcsw.log.Warnf(\"gateway for %s/%s does not have ready addresses; setting addresses to not ready\", endpoints.Namespace, endpoints.Name)\n\t\tfor i := range endpoints.Subsets {\n\t\t\tendpoints.Subsets[i].NotReadyAddresses = append(endpoints.Subsets[i].NotReadyAddresses, endpoints.Subsets[i].Addresses...)\n\t\t\tendpoints.Subsets[i].Addresses = nil\n\t\t}\n\t}\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) isExported(l map[string]string) bool {\n\t// Treat an empty selector as \"Nothing\" instead of \"Everything\" so that\n\t// when the selector field is unset, we don't export all Services.\n\tif rcsw.link.Spec.Selector == nil {\n\t\treturn false\n\t}\n\tif len(rcsw.link.Spec.Selector.MatchExpressions)+len(rcsw.link.Spec.Selector.MatchLabels) == 0 {\n\t\treturn false\n\t}\n\tselector, err := metav1.LabelSelectorAsSelector(rcsw.link.Spec.Selector)\n\tif err != nil {\n\t\trcsw.log.Errorf(\"Invalid selector: %s\", err)\n\t\treturn false\n\t}\n\treturn selector.Matches(labels.Set(l))\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) isRemoteDiscovery(l map[string]string) bool {\n\t// Treat an empty remoteDiscoverySelector as \"Nothing\" instead of\n\t// \"Everything\" so that when the remoteDiscoverySelector field is unset, we\n\t// don't export all Services.\n\tif rcsw.link.Spec.RemoteDiscoverySelector == nil {\n\t\treturn false\n\t}\n\tif len(rcsw.link.Spec.RemoteDiscoverySelector.MatchExpressions)+len(rcsw.link.Spec.RemoteDiscoverySelector.MatchLabels) == 0 {\n\t\treturn false\n\t}\n\tremoteDiscoverySelector, err := metav1.LabelSelectorAsSelector(rcsw.link.Spec.RemoteDiscoverySelector)\n\tif err != nil {\n\t\trcsw.log.Errorf(\"Invalid selector: %s\", err)\n\t\treturn false\n\t}\n\n\treturn remoteDiscoverySelector.Matches(labels.Set(l))\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) isFederatedServiceMember(l map[string]string) bool {\n\t// Treat an empty federatedServiceSelector as \"Nothing\" instead of\n\t// \"Everything\" so that when the federatedServiceSelector field is unset, we\n\t// don't export all Services.\n\tif rcsw.link.Spec.FederatedServiceSelector == nil {\n\t\treturn false\n\t}\n\tif len(rcsw.link.Spec.FederatedServiceSelector.MatchExpressions)+len(rcsw.link.Spec.FederatedServiceSelector.MatchLabels) == 0 {\n\t\treturn false\n\t}\n\tfederatedServiceSelector, err := metav1.LabelSelectorAsSelector(rcsw.link.Spec.FederatedServiceSelector)\n\tif err != nil {\n\t\trcsw.log.Errorf(\"Invalid selector: %s\", err)\n\t\treturn false\n\t}\n\n\treturn federatedServiceSelector.Matches(labels.Set(l))\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) updateLinkMirrorStatus(remoteName, namespace string, condition v1alpha3.LinkCondition) {\n\tif rcsw.link.Spec.TargetClusterName == \"\" {\n\t\t// The local cluster has no Link resource.\n\t\treturn\n\t}\n\tlink, err := rcsw.linksAPIClient.L5dClient.LinkV1alpha3().Links(rcsw.link.GetNamespace()).Get(context.Background(), rcsw.link.Name, metav1.GetOptions{})\n\tif err != nil {\n\t\trcsw.log.Errorf(\"Failed to get link %s/%s: %s\", rcsw.link.Namespace, rcsw.link.Name, err)\n\t\treturn\n\t}\n\tlink.Status.MirrorServices = updateServiceStatus(remoteName, namespace, condition, link.Status.MirrorServices)\n\trcsw.patchLinkStatus(link.Status)\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) updateLinkFederatedStatus(remoteName, namespace string, condition v1alpha3.LinkCondition) {\n\tif rcsw.link.Spec.TargetClusterName == \"\" {\n\t\t// The local cluster has no Link resource.\n\t\treturn\n\t}\n\tlink, err := rcsw.linksAPIClient.L5dClient.LinkV1alpha3().Links(rcsw.link.GetNamespace()).Get(context.Background(), rcsw.link.Name, metav1.GetOptions{})\n\tif err != nil {\n\t\trcsw.log.Errorf(\"Failed to get link %s/%s: %s\", rcsw.link.Namespace, rcsw.link.Name, err)\n\t}\n\tlink.Status.FederatedServices = updateServiceStatus(remoteName, namespace, condition, link.Status.FederatedServices)\n\trcsw.patchLinkStatus(link.Status)\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) deleteLinkMirrorStatus(remoteName, namespace string) {\n\tif rcsw.link.Spec.TargetClusterName == \"\" {\n\t\t// The local cluster has no Link resource.\n\t\treturn\n\t}\n\tlink, err := rcsw.linksAPIClient.L5dClient.LinkV1alpha3().Links(rcsw.link.GetNamespace()).Get(context.Background(), rcsw.link.Name, metav1.GetOptions{})\n\tif err != nil {\n\t\trcsw.log.Errorf(\"Failed to get link %s/%s: %s\", rcsw.link.Namespace, rcsw.link.Name, err)\n\t}\n\tlink.Status.MirrorServices = deleteServiceStatus(remoteName, namespace, link.Status.MirrorServices)\n\trcsw.patchLinkStatus(link.Status)\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) deleteLinkFederatedStatus(remoteName, namespace string) {\n\tif rcsw.link.Spec.TargetClusterName == \"\" {\n\t\t// The local cluster has no Link resource.\n\t\treturn\n\t}\n\tlink, err := rcsw.linksAPIClient.L5dClient.LinkV1alpha3().Links(rcsw.link.GetNamespace()).Get(context.Background(), rcsw.link.Name, metav1.GetOptions{})\n\tif err != nil {\n\t\trcsw.log.Errorf(\"Failed to get link %s/%s: %s\", rcsw.link.Namespace, rcsw.link.Name, err)\n\t}\n\tlink.Status.FederatedServices = deleteServiceStatus(remoteName, namespace, link.Status.FederatedServices)\n\trcsw.patchLinkStatus(link.Status)\n}\n\nfunc (rcsw *RemoteClusterServiceWatcher) patchLinkStatus(status v1alpha3.LinkStatus) {\n\trcsw.log.Infof(\"patching link status %s/%s\", rcsw.link.Namespace, rcsw.link.Name)\n\tstatusBytes, err := json.Marshal(status)\n\tif err != nil {\n\t\trcsw.log.Errorf(\"Failed to marshal link status: %s\", err)\n\t}\n\t_, err = rcsw.linksAPIClient.L5dClient.LinkV1alpha3().Links(rcsw.link.GetNamespace()).Patch(\n\t\tcontext.Background(),\n\t\trcsw.link.Name,\n\t\ttypes.MergePatchType,\n\t\t[]byte(fmt.Sprintf(`{\"status\": %s}`, string(statusBytes))),\n\t\tmetav1.PatchOptions{},\n\t\t\"status\",\n\t)\n\tif err != nil {\n\t\trcsw.log.Errorf(\"Failed to patch link status %s/%s: %s\", rcsw.link.Namespace, rcsw.link.Name, err)\n\t}\n}\n\nfunc updateServiceStatus(remoteName, namespace string, condition v1alpha3.LinkCondition, statuses []v1alpha3.ServiceStatus) []v1alpha3.ServiceStatus {\n\tfoundStatus := false\n\tfor i, status := range statuses {\n\t\tif status.RemoteRef.Name == remoteName && status.RemoteRef.Namespace == namespace {\n\t\t\tfoundStatus = true\n\t\t\tstatus.Conditions = []v1alpha3.LinkCondition{condition}\n\t\t\tstatuses[i] = status\n\t\t}\n\t}\n\tif !foundStatus {\n\t\tstatuses = append(statuses, v1alpha3.ServiceStatus{\n\t\t\tControllerName: \"linkerd.io/service-mirror\",\n\t\t\tRemoteRef: v1alpha3.ObjectRef{\n\t\t\t\tName:      remoteName,\n\t\t\t\tNamespace: namespace,\n\t\t\t\tKind:      \"Service\",\n\t\t\t\tGroup:     corev1.GroupName,\n\t\t\t},\n\t\t\tConditions: []v1alpha3.LinkCondition{condition},\n\t\t})\n\t}\n\treturn statuses\n}\n\nfunc deleteServiceStatus(remoteName, namespace string, statuses []v1alpha3.ServiceStatus) []v1alpha3.ServiceStatus {\n\tnewStatuses := make([]v1alpha3.ServiceStatus, 0)\n\tfor _, status := range statuses {\n\t\tif status.RemoteRef.Name == remoteName && status.RemoteRef.Namespace == namespace {\n\t\t\tcontinue\n\t\t}\n\t\tnewStatuses = append(newStatuses, status)\n\t}\n\treturn newStatuses\n}\n\nfunc mirrorStatusCondition(success bool, reason string, message string, localRef *corev1.Service) v1alpha3.LinkCondition {\n\tstatus := metav1.ConditionTrue\n\tif !success {\n\t\tstatus = metav1.ConditionFalse\n\t}\n\tcondition := v1alpha3.LinkCondition{\n\t\tLastTransitionTime: metav1.Now(),\n\t\tMessage:            message,\n\t\tReason:             reason,\n\t\tStatus:             status,\n\t\tType:               \"Mirrored\",\n\t}\n\tif localRef != nil {\n\t\tcondition.LocalRef = &v1alpha3.ObjectRef{\n\t\t\tName:      localRef.Name,\n\t\t\tNamespace: localRef.Namespace,\n\t\t\tKind:      \"Service\",\n\t\t\tGroup:     corev1.GroupName,\n\t\t}\n\t}\n\treturn condition\n}\n"
  },
  {
    "path": "multicluster/service-mirror/cluster_watcher_headless.go",
    "content": "package servicemirror\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tconsts \"github.com/linkerd/linkerd2/pkg/k8s\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n)\n\n// createOrUpdateHeadlessEndpoints processes endpoints objects for exported\n// headless services. When an endpoints object is created or updated in the\n// remote cluster, it will be processed here in order to reconcile the local\n// cluster state with the remote cluster state.\n//\n// createOrUpdateHeadlessEndpoints is also responsible for creating the service\n// mirror in the source cluster. In order for an exported headless service to be\n// mirrored as headless, it must have at least one port defined and at least one\n// named address in its endpoints object (e.g a deployment would not work since\n// pods may not have arbitrary hostnames). As such, when an endpoints object is\n// first processed, if there is no mirror service, we create one, by looking at\n// the endpoints object itself. If the exported service is deemed to be valid\n// for headless mirroring, then the function will create the headless mirror and\n// then create an endpoints object for it in the source cluster. If it is not\n// valid, the exported service will be mirrored as clusterIP and its endpoints\n// will point to the gateway.\n//\n// When creating endpoints for a headless mirror, we also create an endpoint\n// mirror (clusterIP) service for each of the endpoints' named addresses. If the\n// headless mirror exists and has an endpoints object, we simply update by\n// either creating or deleting endpoint mirror services.\nfunc (rcsw *RemoteClusterServiceWatcher) createOrUpdateHeadlessEndpoints(ctx context.Context, exportedEndpoints *corev1.Endpoints) error {\n\texportedService, err := rcsw.remoteAPIClient.Svc().Lister().Services(exportedEndpoints.Namespace).Get(exportedEndpoints.Name)\n\tif err != nil {\n\t\trcsw.log.Debugf(\"failed to retrieve exported service %s/%s when updating its headless mirror endpoints: %v\", exportedEndpoints.Namespace, exportedEndpoints.Name, err)\n\t\treturn fmt.Errorf(\"error retrieving exported service %s/%s: %w\", exportedEndpoints.Namespace, exportedEndpoints.Name, err)\n\t}\n\n\t// Check whether the endpoints should be processed for a headless exported\n\t// service. If the exported service does not have any ports exposed, then\n\t// neither will its corresponding endpoint mirrors, it should not be created\n\t// as a headless mirror. If the service does not have any named addresses in\n\t// its Endpoints object, then the endpoints should not be processed.\n\tif len(exportedService.Spec.Ports) == 0 {\n\t\trcsw.recorder.Event(exportedService, corev1.EventTypeNormal, eventTypeSkipped, \"Skipped mirroring service: object spec has no exposed ports\")\n\t\trcsw.log.Infof(\"Skipped creating headless mirror for %s/%s: service object spec has no exposed ports\", exportedService.Namespace, exportedService.Name)\n\t\treturn nil\n\t}\n\n\tmirrorServiceName := rcsw.mirrorServiceName(exportedService.Name)\n\tmirrorService, err := rcsw.localAPIClient.Svc().Lister().Services(exportedService.Namespace).Get(mirrorServiceName)\n\tif err != nil {\n\t\tif !kerrors.IsNotFound(err) {\n\t\t\treturn err\n\t\t}\n\n\t\t// If the mirror service does not exist, create it, either as clusterIP\n\t\t// or as headless.\n\t\tmirrorService, err = rcsw.createRemoteHeadlessService(ctx, exportedService, exportedEndpoints)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\theadlessMirrorEpName := rcsw.mirrorServiceName(exportedEndpoints.Name)\n\theadlessMirrorEndpoints, err := rcsw.localAPIClient.Endpoint().Lister().Endpoints(exportedEndpoints.Namespace).Get(headlessMirrorEpName)\n\tif err != nil {\n\t\tif !kerrors.IsNotFound(err) {\n\t\t\treturn err\n\t\t}\n\n\t\tif mirrorService.Spec.ClusterIP != corev1.ClusterIPNone {\n\t\t\treturn rcsw.createGatewayEndpoints(ctx, exportedService)\n\t\t}\n\n\t\t// Create endpoint mirrors for headless mirror\n\t\tif err := rcsw.createHeadlessMirrorEndpoints(ctx, exportedService, exportedEndpoints); err != nil {\n\t\t\trcsw.log.Debugf(\"failed to create headless mirrors for endpoints %s/%s: %v\", exportedEndpoints.Namespace, exportedEndpoints.Name, err)\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t}\n\n\t// Past this point, we do not want to process a mirror service that is not\n\t// headless. We want to process only endpoints for headless mirrors; before\n\t// this point it would have been possible to have a clusterIP mirror, since\n\t// we are creating the mirror service in the scope of the function.\n\tif mirrorService.Spec.ClusterIP != corev1.ClusterIPNone {\n\t\treturn nil\n\t}\n\n\tmirrorEndpoints := headlessMirrorEndpoints.DeepCopy()\n\tendpointMirrors := make(map[string]struct{})\n\tnewSubsets := make([]corev1.EndpointSubset, 0, len(exportedEndpoints.Subsets))\n\tfor _, subset := range exportedEndpoints.Subsets {\n\t\tnewAddresses := make([]corev1.EndpointAddress, 0, len(subset.Addresses))\n\t\tfor _, address := range subset.Addresses {\n\t\t\tif address.Hostname == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tendpointMirrorName := rcsw.mirrorServiceName(address.Hostname)\n\t\t\tendpointMirrorService, err := rcsw.localAPIClient.Svc().Lister().Services(exportedEndpoints.Namespace).Get(endpointMirrorName)\n\t\t\tif err != nil {\n\t\t\t\tif !kerrors.IsNotFound(err) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\t// If the error is 'NotFound' then the Endpoint Mirror service\n\t\t\t\t// does not exist, so create it.\n\t\t\t\tendpointMirrorService, err = rcsw.createEndpointMirrorService(ctx, address.Hostname, exportedEndpoints.ResourceVersion, endpointMirrorName, exportedService)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tendpointMirrors[endpointMirrorName] = struct{}{}\n\t\t\tnewAddresses = append(newAddresses, corev1.EndpointAddress{\n\t\t\t\tHostname: address.Hostname,\n\t\t\t\tIP:       endpointMirrorService.Spec.ClusterIP,\n\t\t\t})\n\t\t}\n\n\t\tif len(newAddresses) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\t// copy ports, create subset\n\t\tnewSubsets = append(newSubsets, corev1.EndpointSubset{\n\t\t\tAddresses: newAddresses,\n\t\t\tPorts:     subset.DeepCopy().Ports,\n\t\t})\n\t}\n\n\theadlessMirrorName := rcsw.mirrorServiceName(exportedService.Name)\n\tmatchLabels := map[string]string{\n\t\tconsts.MirroredHeadlessSvcNameLabel: headlessMirrorName,\n\t}\n\n\t// Fetch all Endpoint Mirror services that belong to the same Headless Mirror\n\tendpointMirrorServices, err := rcsw.localAPIClient.Svc().Lister().List(labels.Set(matchLabels).AsSelector())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar errors []error\n\tfor _, service := range endpointMirrorServices {\n\t\t// If the service's name does not show up in the up-to-date map of\n\t\t// Endpoint Mirror names, then we should delete it.\n\t\tif _, found := endpointMirrors[service.Name]; found {\n\t\t\tcontinue\n\t\t}\n\t\terr := rcsw.localAPIClient.Client.CoreV1().Services(service.Namespace).Delete(ctx, service.Name, metav1.DeleteOptions{})\n\t\tif err != nil {\n\t\t\tif !kerrors.IsNotFound(err) {\n\t\t\t\terrors = append(errors, fmt.Errorf(\"error deleting Endpoint Mirror service %s/%s: %w\", service.Namespace, service.Name, err))\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn RetryableError{errors}\n\t}\n\n\t// Update endpoints\n\tmirrorEndpoints.Subsets = newSubsets\n\terr = rcsw.updateMirrorEndpoints(ctx, mirrorEndpoints)\n\tif err != nil {\n\t\treturn RetryableError{[]error{err}}\n\t}\n\n\treturn nil\n}\n\n// createRemoteHeadlessService creates a mirror service for an exported headless\n// service. Whether the mirror will be created as a headless or clusterIP\n// service depends on the endpoints object associated with the exported service.\n// If there is at least one named address, then the service will be mirrored as\n// headless.\n//\n// Note: we do not check for any exposed ports because it was previously done\n// when the service was picked up by the service mirror. We also do not need to\n// check if the exported service is headless; its endpoints will be processed\n// only if it is headless so we are certain at this point that is the case.\nfunc (rcsw *RemoteClusterServiceWatcher) createRemoteHeadlessService(ctx context.Context, exportedService *corev1.Service, exportedEndpoints *corev1.Endpoints) (*corev1.Service, error) {\n\t// If we don't have any subsets to process then avoid creating the service.\n\t// We need at least one address to be make a decision (whether we should\n\t// create as clusterIP or headless), rely on the endpoints being eventually\n\t// consistent, will likely receive an update with subsets.\n\tif len(exportedEndpoints.Subsets) == 0 {\n\t\treturn &corev1.Service{}, nil\n\t}\n\n\tremoteService := exportedService.DeepCopy()\n\tserviceInfo := fmt.Sprintf(\"%s/%s\", remoteService.Namespace, remoteService.Name)\n\tlocalServiceName := rcsw.mirrorServiceName(remoteService.Name)\n\n\tif rcsw.namespaceCreationEnabled {\n\t\tif err := rcsw.mirrorNamespaceIfNecessary(ctx, remoteService.Namespace); err != nil {\n\t\t\treturn &corev1.Service{}, err\n\t\t}\n\t} else {\n\t\t// Ensure the namespace exists, and skip mirroring if it doesn't\n\t\tif _, err := rcsw.localAPIClient.NS().Lister().Get(remoteService.Namespace); err != nil {\n\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\trcsw.log.Warnf(\"Skipping mirroring of service %s: namespace %s does not exist\", serviceInfo, remoteService.Namespace)\n\t\t\t\treturn &corev1.Service{}, nil\n\t\t\t}\n\t\t\t// something else went wrong, so we can just retry\n\t\t\treturn nil, RetryableError{[]error{err}}\n\t\t}\n\t}\n\n\tserviceToCreate := &corev1.Service{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        localServiceName,\n\t\t\tNamespace:   remoteService.Namespace,\n\t\t\tAnnotations: rcsw.getMirrorServiceAnnotations(remoteService),\n\t\t\tLabels:      rcsw.getMirrorServiceLabels(remoteService),\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tPorts: remapRemoteServicePorts(remoteService.Spec.Ports),\n\t\t},\n\t}\n\n\tif shouldExportAsHeadlessService(exportedEndpoints, rcsw.log) {\n\t\tserviceToCreate.Spec.ClusterIP = corev1.ClusterIPNone\n\t\trcsw.log.Infof(\"Creating a new headless service mirror for %s\", serviceInfo)\n\t} else {\n\t\trcsw.log.Infof(\"Creating a new service mirror for %s\", serviceInfo)\n\t}\n\n\tsvc, err := rcsw.localAPIClient.Client.CoreV1().Services(remoteService.Namespace).Create(ctx, serviceToCreate, metav1.CreateOptions{})\n\tif err != nil {\n\t\tif !kerrors.IsAlreadyExists(err) {\n\t\t\t// we might have created it during earlier attempt, if that is not the case, we retry\n\t\t\treturn &corev1.Service{}, RetryableError{[]error{err}}\n\t\t}\n\t}\n\n\treturn svc, err\n}\n\n// createHeadlessMirrorEndpoints creates an endpoints object for a Headless\n// Mirror service. The endpoints object will contain the same subsets and hosts\n// as the endpoints object of the exported headless service. Each host in the\n// Headless Mirror's endpoints object will point to an Endpoint Mirror service.\nfunc (rcsw *RemoteClusterServiceWatcher) createHeadlessMirrorEndpoints(ctx context.Context, exportedService *corev1.Service, exportedEndpoints *corev1.Endpoints) error {\n\texportedServiceInfo := fmt.Sprintf(\"%s/%s\", exportedService.Namespace, exportedService.Name)\n\tendpointsHostnames := make(map[string]struct{})\n\tsubsetsToCreate := make([]corev1.EndpointSubset, 0, len(exportedEndpoints.Subsets))\n\tfor _, subset := range exportedEndpoints.Subsets {\n\t\tnewAddresses := make([]corev1.EndpointAddress, 0, len(subset.Addresses))\n\t\tfor _, addr := range subset.Addresses {\n\t\t\tif addr.Hostname == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tendpointMirrorName := rcsw.mirrorServiceName(addr.Hostname)\n\t\t\tcreatedService, err := rcsw.createEndpointMirrorService(ctx, addr.Hostname, exportedEndpoints.ResourceVersion, endpointMirrorName, exportedService)\n\t\t\tif err != nil {\n\t\t\t\trcsw.log.Errorf(\"error creating endpoint mirror service %s/%s for exported headless service %s: %v\", endpointMirrorName, exportedService.Namespace, exportedServiceInfo, err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tendpointsHostnames[addr.Hostname] = struct{}{}\n\t\t\tnewAddresses = append(newAddresses, corev1.EndpointAddress{\n\t\t\t\tHostname: addr.TargetRef.Name,\n\t\t\t\tIP:       createdService.Spec.ClusterIP,\n\t\t\t})\n\n\t\t}\n\n\t\tif len(newAddresses) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tsubsetsToCreate = append(subsetsToCreate, corev1.EndpointSubset{\n\t\t\tAddresses: newAddresses,\n\t\t\tPorts:     subset.DeepCopy().Ports,\n\t\t})\n\t}\n\n\theadlessMirrorServiceName := rcsw.mirrorServiceName(exportedService.Name)\n\theadlessMirrorEndpoints := &corev1.Endpoints{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      headlessMirrorServiceName,\n\t\t\tNamespace: exportedService.Namespace,\n\t\t\tLabels:    rcsw.getMirrorEndpointLabels(exportedService),\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tconsts.RemoteServiceFqName: fmt.Sprintf(\"%s.%s.svc.%s\", exportedService.Name, exportedService.Namespace, rcsw.link.Spec.TargetClusterDomain),\n\t\t\t},\n\t\t},\n\t\tSubsets: subsetsToCreate,\n\t}\n\n\tif rcsw.link.Spec.GatewayIdentity != \"\" {\n\t\theadlessMirrorEndpoints.Annotations[consts.RemoteGatewayIdentity] = rcsw.link.Spec.GatewayIdentity\n\t}\n\n\trcsw.log.Infof(\"Creating a new headless mirror endpoints object for headless mirror %s/%s\", headlessMirrorServiceName, exportedService.Namespace)\n\t// The addresses for the headless mirror service point to the Cluster IPs\n\t// of auxiliary services that are tied to gateway liveness. Therefore,\n\t// these addresses should always be considered ready.\n\t_, err := rcsw.localAPIClient.Client.CoreV1().Endpoints(exportedService.Namespace).Create(ctx, headlessMirrorEndpoints, metav1.CreateOptions{})\n\tif err != nil {\n\t\tif svcErr := rcsw.localAPIClient.Client.CoreV1().Services(exportedService.Namespace).Delete(ctx, headlessMirrorServiceName, metav1.DeleteOptions{}); svcErr != nil {\n\t\t\trcsw.log.Errorf(\"failed to delete Service %s after Endpoints creation failed: %s\", headlessMirrorServiceName, svcErr)\n\t\t}\n\t\treturn RetryableError{[]error{err}}\n\t}\n\n\treturn nil\n}\n\n// createEndpointMirrorService creates a new Endpoint Mirror service and its\n// corresponding endpoints object. It returns the newly created Endpoint Mirror\n// service object. When a headless service is exported, we create a Headless\n// Mirror service in the source cluster and then for each hostname in the\n// exported service's endpoints object, we also create an Endpoint Mirror\n// service (and its corresponding endpoints object).\nfunc (rcsw *RemoteClusterServiceWatcher) createEndpointMirrorService(ctx context.Context, endpointHostname, resourceVersion, endpointMirrorName string, exportedService *corev1.Service) (*corev1.Service, error) {\n\tgatewayAddresses, err := rcsw.resolveGatewayAddress()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tendpointMirrorAnnotations := map[string]string{\n\t\tconsts.RemoteResourceVersionAnnotation: resourceVersion, // needed to detect real changes\n\t\tconsts.RemoteServiceFqName:             fmt.Sprintf(\"%s.%s.%s.svc.%s\", endpointHostname, exportedService.Name, exportedService.Namespace, rcsw.link.Spec.TargetClusterDomain),\n\t}\n\n\tendpointMirrorLabels := rcsw.getMirrorServiceLabels(exportedService)\n\tmirrorServiceName := rcsw.mirrorServiceName(exportedService.Name)\n\tendpointMirrorLabels[consts.MirroredHeadlessSvcNameLabel] = mirrorServiceName\n\n\t// Create service spec, clusterIP\n\tendpointMirrorService := &corev1.Service{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        endpointMirrorName,\n\t\t\tNamespace:   exportedService.Namespace,\n\t\t\tAnnotations: endpointMirrorAnnotations,\n\t\t\tLabels:      endpointMirrorLabels,\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tPorts: remapRemoteServicePorts(exportedService.Spec.Ports),\n\t\t},\n\t}\n\tports, err := rcsw.getEndpointsPorts(exportedService)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tendpointMirrorEndpoints := &corev1.Endpoints{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      endpointMirrorService.Name,\n\t\t\tNamespace: endpointMirrorService.Namespace,\n\t\t\tLabels:    endpointMirrorLabels,\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tconsts.RemoteServiceFqName: endpointMirrorService.Annotations[consts.RemoteServiceFqName],\n\t\t\t},\n\t\t},\n\t\tSubsets: []corev1.EndpointSubset{\n\t\t\t{\n\t\t\t\tAddresses: gatewayAddresses,\n\t\t\t\tPorts:     ports,\n\t\t\t},\n\t\t},\n\t}\n\n\tif rcsw.link.Spec.GatewayIdentity != \"\" {\n\t\tendpointMirrorEndpoints.Annotations[consts.RemoteGatewayIdentity] = rcsw.link.Spec.GatewayIdentity\n\t}\n\n\texportedServiceInfo := fmt.Sprintf(\"%s/%s\", exportedService.Namespace, exportedService.Name)\n\tendpointMirrorInfo := fmt.Sprintf(\"%s/%s\", endpointMirrorService.Namespace, endpointMirrorName)\n\trcsw.log.Infof(\"Creating a new endpoint mirror service %s for exported headless service %s\", endpointMirrorInfo, exportedServiceInfo)\n\tcreatedService, err := rcsw.localAPIClient.Client.CoreV1().Services(endpointMirrorService.Namespace).Create(ctx, endpointMirrorService, metav1.CreateOptions{})\n\tif err != nil {\n\t\tif !kerrors.IsAlreadyExists(err) {\n\t\t\t// we might have created it during earlier attempt, if that is not the case, we retry\n\t\t\treturn createdService, RetryableError{[]error{err}}\n\t\t}\n\t}\n\n\trcsw.log.Infof(\"Creating a new endpoints object for endpoint mirror service %s\", endpointMirrorInfo)\n\terr = rcsw.createMirrorEndpoints(ctx, endpointMirrorEndpoints)\n\tif err != nil {\n\t\tif svcErr := rcsw.localAPIClient.Client.CoreV1().Services(endpointMirrorService.Namespace).Delete(ctx, endpointMirrorName, metav1.DeleteOptions{}); svcErr != nil {\n\t\t\trcsw.log.Errorf(\"Failed to delete service %s after endpoints creation failed: %s\", endpointMirrorName, svcErr)\n\t\t}\n\t\treturn createdService, RetryableError{[]error{err}}\n\t}\n\treturn createdService, nil\n}\n\n// shouldExportAsHeadlessService checks if an exported service should be\n// mirrored as a headless service or as a clusterIP service, based on its\n// endpoints object. For an exported service to be a headless mirror, it needs\n// to have at least one named address in its endpoints (that is, a pod with a\n// `hostname`). If the endpoints object does not contain at least one named\n// address, it should be exported as clusterIP.\nfunc shouldExportAsHeadlessService(endpoints *corev1.Endpoints, log *logging.Entry) bool {\n\tfor _, subset := range endpoints.Subsets {\n\t\tfor _, addr := range subset.Addresses {\n\t\t\tif addr.Hostname != \"\" {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\n\t\tfor _, addr := range subset.NotReadyAddresses {\n\t\t\tif addr.Hostname != \"\" {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\tlog.Infof(\"Service %s/%s should not be exported as headless: no named addresses in its endpoints object\", endpoints.Namespace, endpoints.Name)\n\treturn false\n}\n\n// isHeadlessEndpoints checks if an endpoints object belongs to a\n// headless service.\nfunc isHeadlessEndpoints(ep *corev1.Endpoints, log *logging.Entry) bool {\n\tif _, found := ep.Labels[corev1.IsHeadlessService]; !found {\n\t\t// Not an Endpoints object for a headless service? Then we likely don't want\n\t\t// to update anything.\n\t\tlog.Debugf(\"skipped processing endpoints object %s/%s: missing %s label\", ep.Namespace, ep.Name, corev1.IsHeadlessService)\n\t\treturn false\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "multicluster/service-mirror/cluster_watcher_mirroring_test.go",
    "content": "package servicemirror\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tconsts \"github.com/linkerd/linkerd2/pkg/k8s\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/tools/record\"\n\t\"k8s.io/client-go/util/workqueue\"\n)\n\ntype mirroringTestCase struct {\n\tdescription            string\n\tenvironment            *testEnvironment\n\texpectedLocalServices  []*corev1.Service\n\texpectedLocalEndpoints []*corev1.Endpoints\n\texpectedEventsInQueue  []interface{}\n}\n\nfunc (tc *mirroringTestCase) run(t *testing.T) {\n\tt.Helper()\n\tt.Run(tc.description, func(t *testing.T) {\n\t\tt.Helper()\n\n\t\tq := workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any]())\n\t\tlocalAPI, err := tc.environment.runEnvironment(q)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif tc.expectedLocalServices == nil {\n\t\t\t// ensure the are no local services\n\t\t\tservices, err := localAPI.Client.CoreV1().Services(corev1.NamespaceAll).List(context.Background(), metav1.ListOptions{})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tif len(services.Items) > 0 {\n\t\t\t\tt.Fatalf(\"Was expecting no local services but instead found %v\", services.Items)\n\n\t\t\t}\n\t\t} else {\n\t\t\tfor _, expected := range tc.expectedLocalServices {\n\t\t\t\tactual, err := localAPI.Client.CoreV1().Services(expected.Namespace).Get(context.Background(), expected.Name, metav1.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Could not find mirrored service with name %s\", expected.Name)\n\t\t\t\t}\n\n\t\t\t\tif err := diffServices(expected, actual); err != nil {\n\t\t\t\t\tt.Fatalf(\"service %s/%s: %v\", expected.Namespace, expected.Name, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif tc.expectedLocalEndpoints == nil {\n\t\t\t// In a real Kubernetes cluster, deleting the service is sufficient\n\t\t\t// to delete the endpoints.\n\t\t} else {\n\t\t\tfor _, expected := range tc.expectedLocalEndpoints {\n\t\t\t\tactual, err := localAPI.Client.CoreV1().Endpoints(expected.Namespace).Get(context.Background(), expected.Name, metav1.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Could not find endpoints with name %s\", expected.Name)\n\t\t\t\t}\n\n\t\t\t\tif err := diffEndpoints(expected, actual); err != nil {\n\t\t\t\t\tt.Fatalf(\"endpoint %s/%s: %v\", expected.Namespace, expected.Name, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\texpectedNumEvents := len(tc.expectedEventsInQueue)\n\t\tactualNumEvents := q.Len()\n\n\t\tif expectedNumEvents != actualNumEvents {\n\t\t\tt.Fatalf(\"Was expecting %d events but got %d\", expectedNumEvents, actualNumEvents)\n\t\t}\n\n\t\tfor _, ev := range tc.expectedEventsInQueue {\n\t\t\tevInQueue, _ := q.Get()\n\t\t\tif diff := deep.Equal(ev, evInQueue); diff != nil {\n\t\t\t\tt.Errorf(\"%v\", diff)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestRemoteServiceCreatedMirroring(t *testing.T) {\n\tfor _, tt := range []mirroringTestCase{\n\t\t{\n\t\t\tdescription: \"create service and endpoints when gateway can be resolved\",\n\t\t\tenvironment: createExportedService,\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\tmirrorService(\n\t\t\t\t\t\"service-one-remote\",\n\t\t\t\t\t\"ns1\",\n\t\t\t\t\t\"111\",\n\t\t\t\t\tmap[string]string{\"lk\": \"lv\"},\n\t\t\t\t\t[]corev1.ServicePort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t},\n\t\t\texpectedLocalEndpoints: []*corev1.Endpoints{\n\t\t\t\tendpoints(\"service-one-remote\", \"ns1\", map[string]string{\"lk\": \"lv\"}, \"192.0.2.127\", \"gateway-identity\", []corev1.EndpointPort{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\tPort:     888,\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\tPort:     888,\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"create headless service and endpoints when gateway can be resolved\",\n\t\t\tenvironment: createExportedHeadlessService,\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\theadlessMirrorService(\n\t\t\t\t\t\"service-one-remote\",\n\t\t\t\t\t\"ns2\",\n\t\t\t\t\t\"111\",\n\t\t\t\t\tmap[string]string{\"lk\": \"lv\"},\n\t\t\t\t\t[]corev1.ServicePort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\tendpointMirrorService(\n\t\t\t\t\t\"pod-0\",\n\t\t\t\t\t\"service-one-remote\",\n\t\t\t\t\t\"ns2\",\n\t\t\t\t\t\"112\",\n\t\t\t\t\tmap[string]string{\"lk\": \"lv\"},\n\t\t\t\t\t[]corev1.ServicePort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     666,\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\texpectedLocalEndpoints: []*corev1.Endpoints{\n\t\t\t\theadlessMirrorEndpoints(\"service-one-remote\", \"ns2\", map[string]string{\"lk\": \"lv\"}, \"gateway-identity\", []corev1.EndpointPort{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tendpointMirrorEndpoints(\n\t\t\t\t\t\"service-one-remote\",\n\t\t\t\t\t\"ns2\",\n\t\t\t\t\tmap[string]string{\"lk\": \"lv\"},\n\t\t\t\t\t\"pod-0\",\n\t\t\t\t\t\"192.0.2.129\",\n\t\t\t\t\t\"gateway-identity\",\n\t\t\t\t\t[]corev1.EndpointPort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tPort:     889,\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\t\tPort:     889,\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"remote discovery mirroring\",\n\t\t\tenvironment: createRemoteDiscoveryService,\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\tremoteDiscoveryMirrorService(\n\t\t\t\t\t\"service-one\",\n\t\t\t\t\t\"ns1\",\n\t\t\t\t\t\"111\",\n\t\t\t\t\t[]corev1.ServicePort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t},\n\t\t\texpectedLocalEndpoints: []*corev1.Endpoints{},\n\t\t},\n\t\t{\n\t\t\tdescription: \"link with no gateway mirrors only remote discovery\",\n\t\t\tenvironment: noGatewayLink,\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\tremoteDiscoveryMirrorService(\n\t\t\t\t\t\"service-one\",\n\t\t\t\t\t\"ns1\",\n\t\t\t\t\t\"111\",\n\t\t\t\t\t[]corev1.ServicePort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t},\n\t\t\texpectedLocalEndpoints: []*corev1.Endpoints{},\n\t\t},\n\t\t{\n\t\t\tdescription: \"federated service created\",\n\t\t\tenvironment: createFederatedService,\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\tfederatedService(\n\t\t\t\t\t\"service-one\",\n\t\t\t\t\t\"ns1\",\n\t\t\t\t\t[]corev1.ServicePort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"\", fmt.Sprintf(\"%s@%s\", \"service-one\", clusterName)),\n\t\t\t},\n\t\t\texpectedLocalEndpoints: []*corev1.Endpoints{},\n\t\t},\n\t\t{\n\t\t\tdescription: \"federated service joined\",\n\t\t\tenvironment: joinFederatedService(),\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\tfederatedService(\n\t\t\t\t\t\"service-one\",\n\t\t\t\t\t\"ns1\",\n\t\t\t\t\t[]corev1.ServicePort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t\t},\n\t\t\t\t\t}, \"\", fmt.Sprintf(\"%s@%s,%s@%s\", \"service-one\", \"other\", \"service-one\", clusterName)),\n\t\t\t},\n\t\t\texpectedLocalEndpoints: []*corev1.Endpoints{},\n\t\t},\n\t\t{\n\t\t\tdescription: \"federated service left\",\n\t\t\tenvironment: leftFederatedService,\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\tfederatedService(\n\t\t\t\t\t\"service-one\",\n\t\t\t\t\t\"ns1\",\n\t\t\t\t\t[]corev1.ServicePort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t\t},\n\t\t\t\t\t}, \"\", fmt.Sprintf(\"%s@%s\", \"service-one\", \"other\")),\n\t\t\t},\n\t\t\texpectedLocalEndpoints: []*corev1.Endpoints{},\n\t\t},\n\t\t{\n\t\t\tdescription: \"local federated service created\",\n\t\t\tenvironment: createLocalFederatedService,\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\tfederatedService(\n\t\t\t\t\t\"service-one\",\n\t\t\t\t\t\"ns1\",\n\t\t\t\t\t[]corev1.ServicePort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t\t},\n\t\t\t\t\t}, \"service-one\", \"\"),\n\t\t\t},\n\t\t\texpectedLocalEndpoints: []*corev1.Endpoints{},\n\t\t},\n\t\t{\n\t\t\tdescription: \"local federated service joined\",\n\t\t\tenvironment: joinLocalFederatedService(),\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\tfederatedService(\n\t\t\t\t\t\"service-one\",\n\t\t\t\t\t\"ns1\",\n\t\t\t\t\t[]corev1.ServicePort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t\t},\n\t\t\t\t\t}, \"service-one\", \"service-one@other\"),\n\t\t\t},\n\t\t\texpectedLocalEndpoints: []*corev1.Endpoints{},\n\t\t},\n\t\t{\n\t\t\tdescription: \"local federated service left\",\n\t\t\tenvironment: leftLocalFederatedService,\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\tfederatedService(\n\t\t\t\t\t\"service-one\",\n\t\t\t\t\t\"ns1\",\n\t\t\t\t\t[]corev1.ServicePort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t\t},\n\t\t\t\t\t}, \"\", \"service-one@other\"),\n\t\t\t},\n\t\t\texpectedLocalEndpoints: []*corev1.Endpoints{},\n\t\t},\n\t} {\n\t\ttc := tt // pin\n\t\ttc.run(t)\n\t}\n}\n\nfunc TestLocalNamespaceCreatedAfterServiceExport(t *testing.T) {\n\tremoteAPI, err := k8s.NewFakeAPI(\n\t\tasYaml(gateway(\"existing-gateway\", \"existing-namespace\", \"222\", \"192.0.2.127\", \"mc-gateway\", 888, \"gateway-identity\", defaultProbePort, defaultProbePath, defaultProbePeriod)),\n\t\tasYaml(remoteService(\"service-one\", \"ns1\", \"111\", map[string]string{consts.DefaultExportedServiceSelector: \"true\"}, []corev1.ServicePort{})),\n\t\tasYaml(endpoints(\"service-one\", \"ns1\", nil, \"192.0.2.127\", \"gateway-identity\", []corev1.EndpointPort{})),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tlocalAPI, err := k8s.NewFakeAPIWithL5dClient()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tlinksAPI := k8s.NewL5dNamespacedAPI(localAPI.L5dClient, \"linkerd-multicluster\", \"local\", k8s.Link)\n\tremoteAPI.Sync(nil)\n\tlocalAPI.Sync(nil)\n\tlinksAPI.Sync(nil)\n\n\tq := workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any]())\n\teventRecorder := record.NewFakeRecorder(100)\n\n\twatcher := RemoteClusterServiceWatcher{\n\t\tlink: &v1alpha3.Link{\n\t\t\tSpec: v1alpha3.LinkSpec{\n\t\t\t\tTargetClusterName:       clusterName,\n\t\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\t\tGatewayPort:             \"888\",\n\t\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\t\tSelector:                defaultSelector,\n\t\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t\t},\n\t\t},\n\t\tremoteAPIClient:         remoteAPI,\n\t\tlocalAPIClient:          localAPI,\n\t\tlinksAPIClient:          linksAPI,\n\t\tstopper:                 nil,\n\t\trecorder:                eventRecorder,\n\t\tlog:                     logging.WithFields(logging.Fields{\"cluster\": clusterName}),\n\t\teventsQueue:             q,\n\t\trequeueLimit:            0,\n\t\tgatewayAlive:            true,\n\t\theadlessServicesEnabled: true,\n\t}\n\n\tq.Add(&RemoteServiceExported{\n\t\tservice: remoteService(\"service-one\", \"ns1\", \"111\", map[string]string{\n\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t}, []corev1.ServicePort{\n\t\t\t{\n\t\t\t\tName:     \"port1\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     555,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:     \"port2\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     666,\n\t\t\t},\n\t\t}),\n\t})\n\tfor q.Len() > 0 {\n\t\twatcher.processNextEvent(context.Background())\n\t}\n\n\t_, err = localAPI.Svc().Lister().Services(\"ns1\").Get(\"service-one-remote\")\n\tif err == nil {\n\t\tt.Fatalf(\"service-one should not exist in local cluster before namespace is created\")\n\t} else if !errors.IsNotFound(err) {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tskippedEvent := <-eventRecorder.Events\n\tif skippedEvent != fmt.Sprintf(\"%s %s %s\", corev1.EventTypeNormal, eventTypeSkipped, \"Skipped mirroring service: namespace does not exist\") {\n\t\tt.Error(\"Expected skipped event, got:\", skippedEvent)\n\t}\n\n\tns, err := localAPI.Client.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: \"ns1\"}}, metav1.CreateOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tq.Add(&OnLocalNamespaceAdded{ns})\n\tfor q.Len() > 0 {\n\t\twatcher.processNextEvent(context.Background())\n\t}\n\n\t_, err = localAPI.Client.CoreV1().Services(\"ns1\").Get(context.Background(), \"service-one-remote\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"error getting service-one locally: %v\", err)\n\t}\n}\n\nfunc TestServiceCreatedGatewayAlive(t *testing.T) {\n\tremoteAPI, err := k8s.NewFakeAPI(\n\t\tasYaml(gateway(\"gateway\", \"gateway-ns\", \"1\", \"192.0.0.1\", \"gateway\", 888, \"gateway-identity\", defaultProbePort, defaultProbePath, defaultProbePeriod)),\n\t\tasYaml(remoteService(\"svc\", \"ns\", \"1\", map[string]string{consts.DefaultExportedServiceSelector: \"true\"}, []corev1.ServicePort{})),\n\t\tasYaml(endpoints(\"svc\", \"ns\", nil, \"192.0.0.1\", \"gateway-identity\", []corev1.EndpointPort{})),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tlocalAPI, err := k8s.NewFakeAPIWithL5dClient(\n\t\tasYaml(namespace(\"ns\")),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tlinksAPI := k8s.NewL5dNamespacedAPI(localAPI.L5dClient, \"linkerd-multicluster\", \"local\", k8s.Link)\n\tremoteAPI.Sync(nil)\n\tlocalAPI.Sync(nil)\n\tlinksAPI.Sync(nil)\n\n\tevents := workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any]())\n\twatcher := RemoteClusterServiceWatcher{\n\t\tlink: &v1alpha3.Link{\n\t\t\tSpec: v1alpha3.LinkSpec{\n\t\t\t\tTargetClusterName:       clusterName,\n\t\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\t\tGatewayAddress:          \"192.0.0.1\",\n\t\t\t\tGatewayPort:             \"888\",\n\t\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\t\tSelector:                defaultSelector,\n\t\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t\t},\n\t\t},\n\t\tremoteAPIClient: remoteAPI,\n\t\tlocalAPIClient:  localAPI,\n\t\tlinksAPIClient:  linksAPI,\n\t\tlog:             logging.WithFields(logging.Fields{\"cluster\": clusterName}),\n\t\teventsQueue:     events,\n\t\trequeueLimit:    0,\n\t\tgatewayAlive:    true,\n\t}\n\n\tevents.Add(&RemoteServiceExported{\n\t\tservice: remoteService(\"svc\", \"ns\", \"1\", map[string]string{\n\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t}, []corev1.ServicePort{\n\t\t\t{\n\t\t\t\tName:     \"port\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     111,\n\t\t\t},\n\t\t}),\n\t})\n\tfor events.Len() > 0 {\n\t\twatcher.processNextEvent(context.Background())\n\t}\n\n\t// Expect Service svc-remote to be created with ready endpoints because\n\t// the Namespace ns exists and the gateway is alive.\n\t_, err = localAPI.Client.CoreV1().Services(\"ns\").Get(context.Background(), \"svc-remote\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"error getting svc-remote Service: %v\", err)\n\t}\n\tendpoints, err := localAPI.Client.CoreV1().Endpoints(\"ns\").Get(context.Background(), \"svc-remote\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"error getting svc-remote Endpoints: %v\", err)\n\t}\n\tif len(endpoints.Subsets) == 0 {\n\t\tt.Fatal(\"expected svc-remote Endpoints subsets\")\n\t}\n\tfor _, ss := range endpoints.Subsets {\n\t\tif len(ss.Addresses) == 0 {\n\t\t\tt.Fatal(\"svc-remote Endpoints should have addresses\")\n\t\t}\n\t\tif len(ss.NotReadyAddresses) != 0 {\n\t\t\tt.Fatalf(\"svc-remote Endpoints should not have not ready addresses: %v\", ss.NotReadyAddresses)\n\t\t}\n\t}\n\n\t// The gateway is now down which triggers repairing Endpoints on the local\n\t// cluster.\n\twatcher.gatewayAlive = false\n\tevents.Add(&RepairEndpoints{})\n\tfor events.Len() > 0 {\n\t\twatcher.processNextEvent(context.Background())\n\t}\n\n\t// When repairing Endpoints on the local cluster, the gateway address\n\t// should have been moved to NotReadyAddresses meaning that Endpoints\n\t// for the mirrored Service svc-remote should have no ready addresses.\n\tendpoints, err = localAPI.Client.CoreV1().Endpoints(\"ns\").Get(context.Background(), \"svc-remote\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"error getting svc-remote Endpoints locally: %v\", err)\n\t}\n\tif len(endpoints.Subsets) == 0 {\n\t\tt.Fatal(\"expected svc-remote Endpoints subsets\")\n\t}\n\tfor _, ss := range endpoints.Subsets {\n\t\tif len(ss.NotReadyAddresses) == 0 {\n\t\t\tt.Fatal(\"svc-remote Endpoints should have not ready addresses\")\n\t\t}\n\t\tif len(ss.Addresses) != 0 {\n\t\t\tt.Fatalf(\"svc-remote Endpoints should not have addresses: %v\", ss.Addresses)\n\t\t}\n\t}\n\n\t// Issue an update for the remote Service which adds a new label\n\t// 'new-label'. This should exercise RemoteServiceUpdated which should\n\t// update svc-remote; the gateway is still not alive though so we expect\n\t// the Endpoints of svc-remote to still have no ready addresses.\n\tevents.Add(&RemoteExportedServiceUpdated{\n\t\tremoteUpdate: remoteService(\"svc\", \"ns\", \"2\", map[string]string{\n\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t\t\"new-label\":                           \"hi\",\n\t\t}, []corev1.ServicePort{\n\t\t\t{\n\t\t\t\tName:     \"port\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     111,\n\t\t\t},\n\t\t}),\n\t})\n\n\t// Processing the RemoteExportedServiceUpdated involves reading from the\n\t// localAPI informer cache. Since this cache is updated asyncronously, we\n\t// pause briefly here to give a chance for updates to the localAPI to be\n\t// reflected in the cache.\n\ttime.Sleep(100 * time.Millisecond)\n\n\tfor events.Len() > 0 {\n\t\twatcher.processNextEvent(context.Background())\n\t}\n\tservice, err := localAPI.Client.CoreV1().Services(\"ns\").Get(context.Background(), \"svc-remote\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"error getting svc-remote Service: %v\", err)\n\t}\n\t_, ok := service.Labels[\"new-label\"]\n\tif !ok {\n\t\tt.Fatalf(\"error updating svc-remote Service: %v\", err)\n\t}\n\tendpoints, err = localAPI.Client.CoreV1().Endpoints(\"ns\").Get(context.Background(), \"svc-remote\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"error getting svc-remote Endpoints: %v\", err)\n\t}\n\tif len(endpoints.Subsets) == 0 {\n\t\tt.Fatal(\"expected svc-remote Endpoints subsets\")\n\t}\n\tfor _, ss := range endpoints.Subsets {\n\t\tif len(ss.NotReadyAddresses) == 0 {\n\t\t\tt.Fatal(\"svc-remote Endpoints should have not ready addresses\")\n\t\t}\n\t\tif len(ss.Addresses) != 0 {\n\t\t\tt.Fatalf(\"svc-remote Endpoints should not have addresses: %v\", ss.Addresses)\n\t\t}\n\t}\n}\n\nfunc TestServiceCreatedGatewayDown(t *testing.T) {\n\tremoteAPI, err := k8s.NewFakeAPI(\n\t\tasYaml(gateway(\"gateway\", \"gateway-ns\", \"1\", \"192.0.0.1\", \"gateway\", 888, \"gateway-identity\", defaultProbePort, defaultProbePath, defaultProbePeriod)),\n\t\tasYaml(remoteService(\"svc\", \"ns\", \"1\", map[string]string{consts.DefaultExportedServiceSelector: \"true\"}, []corev1.ServicePort{})),\n\t\tasYaml(endpoints(\"svc\", \"ns\", nil, \"192.0.0.1\", \"gateway-identity\", []corev1.EndpointPort{})),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tlocalAPI, err := k8s.NewFakeAPIWithL5dClient(\n\t\tasYaml(namespace(\"ns\")),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tlinksAPI := k8s.NewL5dNamespacedAPI(localAPI.L5dClient, \"linkerd-multicluster\", \"local\", k8s.Link)\n\tremoteAPI.Sync(nil)\n\tlocalAPI.Sync(nil)\n\tlinksAPI.Sync(nil)\n\n\tevents := workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any]())\n\twatcher := RemoteClusterServiceWatcher{\n\t\tlink: &v1alpha3.Link{\n\t\t\tSpec: v1alpha3.LinkSpec{\n\t\t\t\tTargetClusterName:       clusterName,\n\t\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\t\tGatewayAddress:          \"192.0.0.1\",\n\t\t\t\tGatewayPort:             \"888\",\n\t\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\t\tSelector:                defaultSelector,\n\t\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t\t},\n\t\t},\n\t\tremoteAPIClient: remoteAPI,\n\t\tlocalAPIClient:  localAPI,\n\t\tlinksAPIClient:  linksAPI,\n\t\tlog:             logging.WithFields(logging.Fields{\"cluster\": clusterName}),\n\t\teventsQueue:     events,\n\t\trequeueLimit:    0,\n\t\tgatewayAlive:    false,\n\t}\n\n\tevents.Add(&RemoteServiceExported{\n\t\tservice: remoteService(\"svc\", \"ns\", \"1\", map[string]string{\n\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t}, []corev1.ServicePort{\n\t\t\t{\n\t\t\t\tName:     \"port\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     111,\n\t\t\t},\n\t\t}),\n\t})\n\tfor events.Len() > 0 {\n\t\twatcher.processNextEvent(context.Background())\n\t}\n\n\t// Expect Service svc-remote to be created with Endpoints subsets\n\t// that are not ready because the gateway is down.\n\t_, err = localAPI.Client.CoreV1().Services(\"ns\").Get(context.Background(), \"svc-remote\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"error getting svc-remote Service: %v\", err)\n\t}\n\tendpoints, err := localAPI.Client.CoreV1().Endpoints(\"ns\").Get(context.Background(), \"svc-remote\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"error getting svc-remote Endpoints: %v\", err)\n\t}\n\tif len(endpoints.Subsets) == 0 {\n\t\tt.Fatal(\"expected svc-remote Endpoints subsets\")\n\t}\n\tfor _, ss := range endpoints.Subsets {\n\t\tif len(ss.NotReadyAddresses) == 0 {\n\t\t\tt.Fatal(\"svc-remote Endpoints should have not ready addresses\")\n\t\t}\n\t\tif len(ss.Addresses) != 0 {\n\t\t\tt.Fatalf(\"svc-remote Endpoints should not have addresses: %v\", ss.Addresses)\n\t\t}\n\t}\n\n\t// The gateway is now alive which triggers repairing Endpoints on the\n\t// local cluster.\n\twatcher.gatewayAlive = true\n\tevents.Add(&RepairEndpoints{})\n\tfor events.Len() > 0 {\n\t\twatcher.processNextEvent(context.Background())\n\t}\n\tendpoints, err = localAPI.Client.CoreV1().Endpoints(\"ns\").Get(context.Background(), \"svc-remote\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"error getting svc-remote Endpoints locally: %v\", err)\n\t}\n\tif len(endpoints.Subsets) == 0 {\n\t\tt.Fatal(\"expected svc-remote Endpoints subsets\")\n\t}\n\tfor _, ss := range endpoints.Subsets {\n\t\tif len(ss.Addresses) == 0 {\n\t\t\tt.Fatal(\"svc-remote Endpoints should have addresses\")\n\t\t}\n\t\tif len(ss.NotReadyAddresses) != 0 {\n\t\t\tt.Fatalf(\"svc-remote Service endpoints should not have not ready addresses: %v\", ss.NotReadyAddresses)\n\t\t}\n\t}\n}\n\nfunc TestRemoteServiceDeletedMirroring(t *testing.T) {\n\tfor _, tt := range []mirroringTestCase{\n\t\t{\n\t\t\tdescription: \"deletes locally mirrored service\",\n\t\t\tenvironment: deleteMirrorService,\n\t\t},\n\t} {\n\t\ttc := tt // pin\n\t\ttc.run(t)\n\t}\n}\n\nfunc TestRemoteServiceUpdatedMirroring(t *testing.T) {\n\tfor _, tt := range []mirroringTestCase{\n\t\t{\n\t\t\tdescription: \"updates service ports on both service and endpoints\",\n\t\t\tenvironment: updateServiceWithChangedPorts,\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\tmirrorService(\"test-service-remote\", \"test-namespace\", \"currentServiceResVersion\", nil,\n\t\t\t\t\t[]corev1.ServicePort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     111,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port3\",\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t\tPort:     333,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t},\n\n\t\t\texpectedLocalEndpoints: []*corev1.Endpoints{\n\t\t\t\tendpoints(\"test-service-remote\", \"test-namespace\", nil, \"192.0.2.127\", \"gateway-identity\", []corev1.EndpointPort{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\tPort:     888,\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port3\",\n\t\t\t\t\t\tPort:     888,\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t} {\n\t\ttc := tt // pin\n\t\ttc.run(t)\n\t}\n}\n\n// TestEmptyRemoteSelectors asserts that empty selectors do not introduce side\n// effects, such as mirroring unexported services. An empty label selector\n// functions as a catch-all (i.e. matches everything), the cluster watcher must\n// uphold an invariant whereby empty selectors do not export everything by\n// default.\nfunc TestEmptyRemoteSelectors(t *testing.T) {\n\tfor _, tt := range []mirroringTestCase{\n\t\t{\n\t\t\tdescription: \"empty remote discovery selector does not result in exports\",\n\t\t\tenvironment: createEnvWithSelector(defaultSelector, &metav1.LabelSelector{}),\n\t\t\texpectedEventsInQueue: []interface{}{&RemoteServiceExported{\n\t\t\t\tservice: remoteService(\"service-one\", \"ns1\", \"111\", map[string]string{\n\t\t\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t\t\t}, []corev1.ServicePort{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"default1\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"default2\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"empty default selector does not result in exports\",\n\t\t\tenvironment: createEnvWithSelector(&metav1.LabelSelector{}, defaultRemoteDiscoverySelector),\n\t\t\texpectedEventsInQueue: []interface{}{&RemoteServiceExported{\n\t\t\t\tservice: remoteService(\"service-two\", \"ns1\", \"111\", map[string]string{\n\t\t\t\t\tconsts.DefaultExportedServiceSelector: \"remote-discovery\",\n\t\t\t\t}, []corev1.ServicePort{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"remote1\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     777,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"remote2\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     888,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t}},\n\t\t},\n\t\t{\n\t\t\tdescription: \"no selector in link does not result in exports\",\n\t\t\tenvironment: createEnvWithSelector(&metav1.LabelSelector{}, &metav1.LabelSelector{}),\n\t\t},\n\t} {\n\t\ttc := tt\n\t\ttc.run(t)\n\t}\n}\n\nfunc TestRemoteEndpointsUpdatedMirroring(t *testing.T) {\n\tfor _, tt := range []mirroringTestCase{\n\t\t{\n\t\t\tdescription: \"updates headless mirror service with new remote Endpoints hosts\",\n\t\t\tenvironment: updateEndpointsWithChangedHosts,\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\theadlessMirrorService(\"service-two-remote\", \"eptest\", \"222\", nil, []corev1.ServicePort{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tendpointMirrorService(\"pod-0\", \"service-two-remote\", \"eptest\", \"333\", nil, []corev1.ServicePort{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tendpointMirrorService(\"pod-1\", \"service-two-remote\", \"eptest\", \"112\", nil, []corev1.ServicePort{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t\texpectedLocalEndpoints: []*corev1.Endpoints{\n\t\t\t\theadlessMirrorEndpointsUpdated(\n\t\t\t\t\t\"service-two-remote\",\n\t\t\t\t\t\"eptest\",\n\t\t\t\t\t[]string{\"pod-0\", \"pod-1\"},\n\t\t\t\t\t[]string{\"\", \"\"},\n\t\t\t\t\t\"gateway-identity\",\n\t\t\t\t\t[]corev1.EndpointPort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\tendpointMirrorEndpoints(\n\t\t\t\t\t\"service-two-remote\",\n\t\t\t\t\t\"eptest\",\n\t\t\t\t\tnil,\n\t\t\t\t\t\"pod-0\",\n\t\t\t\t\t\"192.0.2.127\",\n\t\t\t\t\t\"gateway-identity\",\n\t\t\t\t\t[]corev1.EndpointPort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tPort:     888,\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\t\tPort:     888,\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\tendpointMirrorEndpoints(\n\t\t\t\t\t\"service-two-remote\",\n\t\t\t\t\t\"eptest\",\n\t\t\t\t\tnil,\n\t\t\t\t\t\"pod-1\",\n\t\t\t\t\t\"192.0.2.127\",\n\t\t\t\t\t\"gateway-identity\",\n\t\t\t\t\t[]corev1.EndpointPort{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\t\tPort:     888,\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\t\tPort:     888,\n\t\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t} {\n\t\ttc := tt // pin\n\t\ttc.run(t)\n\t}\n}\n\nfunc TestClusterUnregisteredMirroring(t *testing.T) {\n\tfor _, tt := range []mirroringTestCase{\n\t\t{\n\t\t\tdescription: \"unregisters cluster and cleans up all mirrored resources\",\n\t\t\tenvironment: clusterUnregistered,\n\t\t},\n\t} {\n\t\ttc := tt // pin\n\t\ttc.run(t)\n\t}\n}\n\nfunc TestGcOrphanedServicesMirroring(t *testing.T) {\n\tfor _, tt := range []mirroringTestCase{\n\t\t{\n\t\t\tdescription: \"deletes mirrored resources that are no longer present on the remote cluster\",\n\t\t\tenvironment: gcTriggered,\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\tmirrorService(\"test-service-1-remote\", \"test-namespace\", \"\", nil, nil),\n\t\t\t\theadlessMirrorService(\"test-headless-service-remote\", \"test-namespace\", \"\", nil, nil),\n\t\t\t\tendpointMirrorService(\"pod-0\", \"test-headless-service-remote\", \"test-namespace\", \"\", nil, nil),\n\t\t\t},\n\n\t\t\texpectedLocalEndpoints: []*corev1.Endpoints{\n\t\t\t\tendpoints(\"test-service-1-remote\", \"test-namespace\", nil, \"\", \"\", nil),\n\t\t\t\theadlessMirrorEndpoints(\"test-headless-service-remote\", \"test-namespace\", nil, \"\", nil),\n\t\t\t\tendpointMirrorEndpoints(\"test-headless-service-remote\", \"test-namespace\", nil, \"pod-0\", \"\", \"\", nil),\n\t\t\t},\n\t\t},\n\t} {\n\t\ttc := tt // pin\n\t\ttc.run(t)\n\t}\n}\n\nfunc onAddOrUpdateTestCases(isAdd bool) []mirroringTestCase {\n\n\ttestType := \"ADD\"\n\tif !isAdd {\n\t\ttestType = \"UPDATE\"\n\t}\n\n\treturn []mirroringTestCase{\n\t\t{\n\t\t\tdescription: fmt.Sprintf(\"enqueue a RemoteServiceCreated event when this is not a gateway and we have the needed annotations (%s)\", testType),\n\t\t\tenvironment: onAddOrUpdateExportedSvc(isAdd),\n\t\t\texpectedEventsInQueue: []interface{}{&RemoteServiceExported{\n\t\t\t\tservice: remoteService(\"test-service\", \"test-namespace\", \"resVersion\", map[string]string{\n\t\t\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t\t\t}, nil),\n\t\t\t}},\n\t\t},\n\t\t{\n\t\t\tdescription: fmt.Sprintf(\"enqueue a RemoteServiceUpdated event if this is a service that we have already mirrored and its res version is different (%s)\", testType),\n\t\t\tenvironment: onAddOrUpdateRemoteServiceUpdated(isAdd),\n\t\t\texpectedEventsInQueue: []interface{}{&RemoteExportedServiceUpdated{\n\t\t\t\tremoteUpdate: remoteService(\"test-service\", \"test-namespace\", \"currentResVersion\", map[string]string{\n\t\t\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t\t\t}, nil),\n\t\t\t}},\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\tmirrorService(\"test-service-remote\", \"test-namespace\", \"pastResourceVersion\", nil, nil),\n\t\t\t},\n\t\t\texpectedLocalEndpoints: []*corev1.Endpoints{\n\t\t\t\tendpoints(\"test-service-remote\", \"test-namespace\", nil, \"0.0.0.0\", \"\", nil),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: fmt.Sprintf(\"not enqueue any events as this update does not really tell us anything new (res version is the same...) (%s)\", testType),\n\t\t\tenvironment: onAddOrUpdateSameResVersion(isAdd),\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\tmirrorService(\"test-service-remote\", \"test-namespace\", \"currentResVersion\", nil, nil),\n\t\t\t},\n\t\t\texpectedLocalEndpoints: []*corev1.Endpoints{\n\t\t\t\tendpoints(\"test-service-remote\", \"test-namespace\", nil, \"0.0.0.0\", \"\", nil),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: fmt.Sprintf(\"enqueue RemoteServiceDeleted event as this service is not mirrorable anymore (%s)\", testType),\n\t\t\tenvironment: serviceNotExportedAnymore(isAdd),\n\t\t\texpectedEventsInQueue: []interface{}{&RemoteServiceUnexported{\n\t\t\t\tName:      \"test-service\",\n\t\t\t\tNamespace: \"test-namespace\",\n\t\t\t}},\n\n\t\t\texpectedLocalServices: []*corev1.Service{\n\t\t\t\tmirrorService(\"test-service-remote\", \"test-namespace\", \"currentResVersion\", nil, nil),\n\t\t\t},\n\t\t\texpectedLocalEndpoints: []*corev1.Endpoints{\n\t\t\t\tendpoints(\"test-service-remote\", \"test-namespace\", nil, \"0.0.0.0\", \"\", nil),\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc TestOnAdd(t *testing.T) {\n\tfor _, tt := range onAddOrUpdateTestCases(true) {\n\t\ttc := tt // pin\n\t\ttc.run(t)\n\t}\n}\n\nfunc TestOnUpdate(t *testing.T) {\n\tfor _, tt := range onAddOrUpdateTestCases(false) {\n\t\ttc := tt // pin\n\t\ttc.run(t)\n\t}\n}\n\nfunc TestOnDelete(t *testing.T) {\n\tfor _, tt := range []mirroringTestCase{\n\t\t{\n\t\t\tdescription: \"enqueues a RemoteServiceDeleted because there is gateway metadata present on the service\",\n\t\t\tenvironment: onDeleteExportedService,\n\t\t\texpectedEventsInQueue: []interface{}{\n\t\t\t\t&RemoteServiceUnexported{\n\t\t\t\t\tName:      \"test-service\",\n\t\t\t\t\tNamespace: \"test-namespace\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription:           \"skips because there is no gateway metadata present on the service\",\n\t\t\tenvironment:           onDeleteNonExportedService,\n\t\t\texpectedEventsInQueue: []interface{}{},\n\t\t},\n\t} {\n\t\ttc := tt // pin\n\t\ttc.run(t)\n\t}\n}\n"
  },
  {
    "path": "multicluster/service-mirror/cluster_watcher_test_util.go",
    "content": "package servicemirror\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"strings\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tconsts \"github.com/linkerd/linkerd2/pkg/k8s\"\n\tlogging \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/util/workqueue\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nconst (\n\tclusterName        = \"remote\"\n\tclusterDomain      = \"cluster.local\"\n\tdefaultProbePath   = \"/probe\"\n\tdefaultProbePort   = 12345\n\tdefaultProbePeriod = \"60\"\n)\n\nvar (\n\tdefaultProbeSpec = v1alpha3.ProbeSpec{\n\t\tPath:   defaultProbePath,\n\t\tPort:   fmt.Sprintf(\"%d\", defaultProbePort),\n\t\tPeriod: defaultProbePeriod,\n\t}\n\tdefaultSelector, _                = metav1.ParseToLabelSelector(consts.DefaultExportedServiceSelector + \"=true\")\n\tdefaultRemoteDiscoverySelector, _ = metav1.ParseToLabelSelector(consts.DefaultExportedServiceSelector + \"=remote-discovery\")\n)\n\ntype testEnvironment struct {\n\tevents          []interface{}\n\tremoteResources []string\n\tlocalResources  []string\n\tlink            v1alpha3.Link\n}\n\nfunc (te *testEnvironment) runEnvironment(watcherQueue workqueue.TypedRateLimitingInterface[any]) (*k8s.API, error) {\n\tremoteAPI, err := k8s.NewFakeAPI(te.remoteResources...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlocalAPI, err := k8s.NewFakeAPIWithL5dClient(te.localResources...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlinksAPI := k8s.NewNamespacedAPI(nil, nil, localAPI.L5dClient, \"default\", \"local\", k8s.Link)\n\tremoteAPI.Sync(nil)\n\tlocalAPI.Sync(nil)\n\tlinksAPI.Sync(nil)\n\n\twatcher := RemoteClusterServiceWatcher{\n\t\tlink:                    &te.link,\n\t\tremoteAPIClient:         remoteAPI,\n\t\tlocalAPIClient:          localAPI,\n\t\tlinksAPIClient:          linksAPI,\n\t\tstopper:                 nil,\n\t\tlog:                     logging.WithFields(logging.Fields{\"cluster\": clusterName}),\n\t\teventsQueue:             watcherQueue,\n\t\trequeueLimit:            0,\n\t\tgatewayAlive:            true,\n\t\theadlessServicesEnabled: true,\n\t}\n\n\tfor _, ev := range te.events {\n\t\twatcherQueue.Add(ev)\n\t}\n\n\tfor range te.events {\n\t\twatcher.processNextEvent(context.Background())\n\t}\n\n\tlocalAPI.Sync(nil)\n\tremoteAPI.Sync(nil)\n\n\treturn localAPI, nil\n}\n\nvar createExportedService = &testEnvironment{\n\tevents: []interface{}{\n\t\t&RemoteServiceExported{\n\t\t\tservice: remoteService(\"service-one\", \"ns1\", \"111\", map[string]string{\n\t\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t\t\t\"lk\":                                  \"lv\",\n\t\t\t}, []corev1.ServicePort{\n\t\t\t\t{\n\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     555,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     666,\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t},\n\tremoteResources: []string{\n\t\tasYaml(gateway(\"existing-gateway\", \"existing-namespace\", \"222\", \"192.0.2.127\", \"mc-gateway\", 888, \"gateway-identity\", defaultProbePort, defaultProbePath, defaultProbePeriod)),\n\t\tasYaml(endpoints(\"service-one\", \"ns1\", nil, \"192.0.2.127\", \"gateway-identity\", []corev1.EndpointPort{})),\n\t},\n\tlocalResources: []string{\n\t\tasYaml(namespace(\"ns1\")),\n\t},\n\tlink: v1alpha3.Link{\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName:       clusterName,\n\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\tGatewayPort:             \"888\",\n\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\tSelector:                defaultSelector,\n\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t},\n\t},\n}\n\nvar createRemoteDiscoveryService = &testEnvironment{\n\tevents: []interface{}{\n\t\t&RemoteServiceExported{\n\t\t\tservice: remoteService(\"service-one\", \"ns1\", \"111\", map[string]string{\n\t\t\t\tconsts.DefaultExportedServiceSelector: \"remote-discovery\",\n\t\t\t}, []corev1.ServicePort{\n\t\t\t\t{\n\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     555,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     666,\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t},\n\tremoteResources: []string{\n\t\tasYaml(endpoints(\"service-one\", \"ns1\", nil, \"192.0.2.127\", \"gateway-identity\", []corev1.EndpointPort{})),\n\t},\n\tlocalResources: []string{\n\t\tasYaml(namespace(\"ns1\")),\n\t},\n\tlink: v1alpha3.Link{\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName:       clusterName,\n\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\tGatewayPort:             \"888\",\n\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\tSelector:                defaultSelector,\n\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t},\n\t},\n}\n\nvar createFederatedService = &testEnvironment{\n\tevents: []interface{}{\n\t\t&CreateFederatedService{\n\t\t\tservice: remoteService(\"service-one\", \"ns1\", \"111\", map[string]string{\n\t\t\t\tconsts.DefaultFederatedServiceSelector: \"member\",\n\t\t\t}, []corev1.ServicePort{\n\t\t\t\t{\n\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     555,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     666,\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t},\n\tremoteResources: []string{\n\t\tasYaml(endpoints(\"service-one\", \"ns1\", nil, \"192.0.2.127\", \"gateway-identity\", []corev1.EndpointPort{})),\n\t},\n\tlocalResources: []string{\n\t\tasYaml(namespace(\"ns1\")),\n\t},\n\tlink: v1alpha3.Link{\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName:       clusterName,\n\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\tGatewayPort:             \"888\",\n\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\tSelector:                defaultSelector,\n\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t},\n\t},\n}\n\nfunc joinFederatedService() *testEnvironment {\n\tfedSvc := federatedService(\"service-one\", \"ns1\", []corev1.ServicePort{\n\t\t{\n\t\t\tName:     \"port1\",\n\t\t\tProtocol: \"TCP\",\n\t\t\tPort:     555,\n\t\t},\n\t\t{\n\t\t\tName:     \"port2\",\n\t\t\tProtocol: \"TCP\",\n\t\t\tPort:     666,\n\t\t},\n\t}, \"\", \"service-one@other\")\n\treturn &testEnvironment{\n\t\tevents: []interface{}{\n\t\t\t&RemoteServiceJoinsFederatedService{\n\t\t\t\tremoteUpdate: remoteService(\"service-one\", \"ns1\", \"111\", map[string]string{\n\t\t\t\t\tconsts.DefaultFederatedServiceSelector: \"member\",\n\t\t\t\t}, []corev1.ServicePort{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tremoteResources: []string{\n\t\t\tasYaml(endpoints(\"service-one\", \"ns1\", nil, \"192.0.2.127\", \"gateway-identity\", []corev1.EndpointPort{})),\n\t\t},\n\t\tlocalResources: []string{\n\t\t\tasYaml(namespace(\"ns1\")),\n\t\t\tasYaml(fedSvc),\n\t\t},\n\t\tlink: v1alpha3.Link{\n\t\t\tSpec: v1alpha3.LinkSpec{\n\t\t\t\tTargetClusterName:       clusterName,\n\t\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\t\tGatewayPort:             \"888\",\n\t\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\t\tSelector:                defaultSelector,\n\t\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t\t},\n\t\t},\n\t}\n}\n\nvar leftFederatedService = &testEnvironment{\n\tevents: []interface{}{\n\t\t&RemoteServiceLeavesFederatedService{\n\t\t\tName:      \"service-one\",\n\t\t\tNamespace: \"ns1\",\n\t\t},\n\t},\n\tremoteResources: []string{\n\t\tasYaml(endpoints(\"service-one\", \"ns1\", nil, \"192.0.2.127\", \"gateway-identity\", []corev1.EndpointPort{})),\n\t},\n\tlocalResources: []string{\n\t\tasYaml(namespace(\"ns1\")),\n\t\tasYaml(federatedService(\"service-one\", \"ns1\", []corev1.ServicePort{\n\t\t\t{\n\t\t\t\tName:     \"port1\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     555,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:     \"port2\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     666,\n\t\t\t},\n\t\t}, \"\", fmt.Sprintf(\"service-one@other,service-one@%s\", clusterName))),\n\t},\n\tlink: v1alpha3.Link{\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName:       clusterName,\n\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\tGatewayPort:             \"888\",\n\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\tSelector:                defaultSelector,\n\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t},\n\t},\n}\n\nvar createLocalFederatedService = &testEnvironment{\n\tevents: []interface{}{\n\t\t&CreateFederatedService{\n\t\t\tservice: remoteService(\"service-one\", \"ns1\", \"111\", map[string]string{\n\t\t\t\tconsts.DefaultFederatedServiceSelector: \"member\",\n\t\t\t}, []corev1.ServicePort{\n\t\t\t\t{\n\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     555,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     666,\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t},\n\tremoteResources: []string{\n\t\tasYaml(endpoints(\"service-one\", \"ns1\", nil, \"192.0.2.127\", \"gateway-identity\", []corev1.EndpointPort{})),\n\t},\n\tlocalResources: []string{\n\t\tasYaml(namespace(\"ns1\")),\n\t},\n\tlink: v1alpha3.Link{\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName:       \"\", // local cluster\n\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\tGatewayPort:             \"888\",\n\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\tSelector:                defaultSelector,\n\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t},\n\t},\n}\n\nfunc joinLocalFederatedService() *testEnvironment {\n\tfedSvc := federatedService(\"service-one\", \"ns1\", []corev1.ServicePort{\n\t\t{\n\t\t\tName:     \"port1\",\n\t\t\tProtocol: \"TCP\",\n\t\t\tPort:     555,\n\t\t},\n\t\t{\n\t\t\tName:     \"port2\",\n\t\t\tProtocol: \"TCP\",\n\t\t\tPort:     666,\n\t\t},\n\t}, \"\", \"service-one@other\")\n\treturn &testEnvironment{\n\t\tevents: []interface{}{\n\t\t\t&RemoteServiceJoinsFederatedService{\n\t\t\t\tremoteUpdate: remoteService(\"service-one\", \"ns1\", \"111\", map[string]string{\n\t\t\t\t\tconsts.DefaultFederatedServiceSelector: \"member\",\n\t\t\t\t}, []corev1.ServicePort{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tremoteResources: []string{\n\t\t\tasYaml(endpoints(\"service-one\", \"ns1\", nil, \"192.0.2.127\", \"gateway-identity\", []corev1.EndpointPort{})),\n\t\t},\n\t\tlocalResources: []string{\n\t\t\tasYaml(namespace(\"ns1\")),\n\t\t\tasYaml(fedSvc),\n\t\t},\n\t\tlink: v1alpha3.Link{\n\t\t\tSpec: v1alpha3.LinkSpec{\n\t\t\t\tTargetClusterName:       \"\", // local cluster\n\t\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\t\tGatewayPort:             \"888\",\n\t\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\t\tSelector:                defaultSelector,\n\t\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t\t},\n\t\t},\n\t}\n}\n\nvar leftLocalFederatedService = &testEnvironment{\n\tevents: []interface{}{\n\t\t&RemoteServiceLeavesFederatedService{\n\t\t\tName:      \"service-one\",\n\t\t\tNamespace: \"ns1\",\n\t\t},\n\t},\n\tremoteResources: []string{\n\t\tasYaml(endpoints(\"service-one\", \"ns1\", nil, \"192.0.2.127\", \"gateway-identity\", []corev1.EndpointPort{})),\n\t},\n\tlocalResources: []string{\n\t\tasYaml(namespace(\"ns1\")),\n\t\tasYaml(federatedService(\"service-one\", \"ns1\", []corev1.ServicePort{\n\t\t\t{\n\t\t\t\tName:     \"port1\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     555,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:     \"port2\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     666,\n\t\t\t},\n\t\t}, \"service-one\", \"service-one@other\")),\n\t},\n\tlink: v1alpha3.Link{\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName:       \"\", // local cluster\n\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\tGatewayPort:             \"888\",\n\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\tSelector:                defaultSelector,\n\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t},\n\t},\n}\n\nvar createExportedHeadlessService = &testEnvironment{\n\tevents: []interface{}{\n\t\t&RemoteServiceExported{\n\t\t\tservice: remoteHeadlessService(\"service-one\", \"ns2\", \"111\", map[string]string{\n\t\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t\t\t\"lk\":                                  \"lv\",\n\t\t\t}, []corev1.ServicePort{\n\t\t\t\t{\n\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     555,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     666,\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\t&OnAddEndpointsCalled{\n\t\t\tep: remoteHeadlessEndpoints(\"service-one\", \"ns2\", \"112\", \"192.0.0.1\", []corev1.EndpointPort{\n\t\t\t\t{\n\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     555,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     666,\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t},\n\tremoteResources: []string{\n\t\tasYaml(gateway(\"existing-gateway\", \"existing-namespace\", \"222\", \"192.0.2.129\", \"gateway\", 889, \"gateway-identity\", 123456, \"/probe1\", \"120s\")),\n\t\tasYaml(remoteHeadlessService(\"service-one\", \"ns2\", \"111\", map[string]string{\"lk\": \"lv\"},\n\t\t\t[]corev1.ServicePort{\n\t\t\t\t{\n\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     555,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     666,\n\t\t\t\t},\n\t\t\t})),\n\t\tasYaml(remoteHeadlessEndpoints(\"service-one\", \"ns2\", \"112\", \"192.0.0.1\", []corev1.EndpointPort{\n\t\t\t{\n\t\t\t\tName:     \"port1\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     555,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:     \"port2\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     666,\n\t\t\t},\n\t\t})),\n\t},\n\tlocalResources: []string{\n\t\tasYaml(namespace(\"ns2\")),\n\t},\n\tlink: v1alpha3.Link{\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName:   clusterName,\n\t\t\tTargetClusterDomain: clusterDomain,\n\t\t\tGatewayIdentity:     \"gateway-identity\",\n\t\t\tGatewayAddress:      \"192.0.2.129\",\n\t\t\tGatewayPort:         \"889\",\n\t\t\tProbeSpec: v1alpha3.ProbeSpec{\n\t\t\t\tPort:   \"123456\",\n\t\t\t\tPath:   \"/probe1\",\n\t\t\t\tPeriod: \"120\",\n\t\t\t},\n\t\t\tSelector:                defaultSelector,\n\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t},\n\t},\n}\n\nvar deleteMirrorService = &testEnvironment{\n\tevents: []interface{}{\n\t\t&RemoteServiceUnexported{\n\t\t\tName:      \"test-service-remote-to-delete\",\n\t\t\tNamespace: \"test-namespace-to-delete\",\n\t\t},\n\t},\n\tlocalResources: []string{\n\t\tasYaml(mirrorService(\"test-service-remote-to-delete-remote\", \"test-namespace-to-delete\", \"\", nil, nil)),\n\t\tasYaml(endpoints(\"test-service-remote-to-delete-remote\", \"test-namespace-to-delete\", nil, \"\", \"gateway-identity\", nil)),\n\t},\n\tlink: v1alpha3.Link{\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName:       clusterName,\n\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\tGatewayPort:             \"888\",\n\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\tSelector:                defaultSelector,\n\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t},\n\t},\n}\n\nvar updateServiceWithChangedPorts = &testEnvironment{\n\tevents: []interface{}{\n\t\t&RemoteExportedServiceUpdated{\n\t\t\tremoteUpdate: remoteService(\"test-service\", \"test-namespace\", \"currentServiceResVersion\", map[string]string{\n\t\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t\t}, []corev1.ServicePort{\n\t\t\t\t{\n\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     111,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"port3\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     333,\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t},\n\tremoteResources: []string{\n\t\tasYaml(gateway(\"gateway\", \"gateway-ns\", \"currentGatewayResVersion\", \"192.0.2.127\", \"mc-gateway\", 888, \"\", defaultProbePort, defaultProbePath, defaultProbePeriod)),\n\t},\n\tlocalResources: []string{\n\t\tasYaml(mirrorService(\"test-service-remote\", \"test-namespace\", \"past\", nil, []corev1.ServicePort{\n\t\t\t{\n\t\t\t\tName:     \"port1\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     111,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:     \"port2\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     222,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:     \"port3\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     333,\n\t\t\t},\n\t\t})),\n\t\tasYaml(endpoints(\"test-service-remote\", \"test-namespace\", nil, \"192.0.2.127\", \"\", []corev1.EndpointPort{\n\t\t\t{\n\t\t\t\tName:     \"port1\",\n\t\t\t\tPort:     888,\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:     \"port2\",\n\t\t\t\tPort:     888,\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:     \"port3\",\n\t\t\t\tPort:     888,\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t},\n\t\t})),\n\t},\n\tlink: v1alpha3.Link{\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName:       clusterName,\n\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\tGatewayPort:             \"888\",\n\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\tSelector:                defaultSelector,\n\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t},\n\t},\n}\n\nvar updateEndpointsWithChangedHosts = &testEnvironment{\n\tevents: []interface{}{\n\t\t&OnUpdateEndpointsCalled{\n\t\t\tep: remoteHeadlessEndpointsUpdate(\"service-two\", \"eptest\", \"112\", \"192.0.0.1\", []corev1.EndpointPort{\n\t\t\t\t{\n\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     555,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     666,\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t},\n\tremoteResources: []string{\n\t\tasYaml(gateway(\"gateway\", \"gateway-ns\", \"currentGatewayResVersion\", \"192.0.2.127\", \"mc-gateway\", 888, \"\", defaultProbePort, defaultProbePath, defaultProbePeriod)),\n\t\tasYaml(remoteHeadlessService(\"service-two\", \"eptest\", \"222\", nil,\n\t\t\t[]corev1.ServicePort{\n\t\t\t\t{\n\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     555,\n\t\t\t\t},\n\t\t\t\t{\n\n\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     666,\n\t\t\t\t},\n\t\t\t})),\n\t},\n\tlocalResources: []string{\n\t\tasYaml(headlessMirrorService(\"service-two-remote\", \"eptest\", \"222\", nil,\n\t\t\t[]corev1.ServicePort{\n\t\t\t\t{\n\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     555,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     666,\n\t\t\t\t},\n\t\t\t})),\n\t\tasYaml(endpointMirrorService(\"pod-0\", \"service-two-remote\", \"eptest\", \"333\", nil, []corev1.ServicePort{\n\t\t\t{\n\t\t\t\tName:     \"port1\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     555,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:     \"port2\",\n\t\t\t\tProtocol: \"TCP\",\n\t\t\t\tPort:     666,\n\t\t\t},\n\t\t})),\n\t\tasYaml(headlessMirrorEndpoints(\n\t\t\t\"service-two-remote\",\n\t\t\t\"eptest\",\n\t\t\tnil,\n\t\t\t\"gateway-identity\",\n\t\t\t[]corev1.EndpointPort{\n\t\t\t\t{\n\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     555,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     666,\n\t\t\t\t},\n\t\t\t})),\n\t\tasYaml(endpointMirrorEndpoints(\n\t\t\t\"service-two-remote\",\n\t\t\t\"eptest\",\n\t\t\tnil,\n\t\t\t\"pod-0\",\n\t\t\t\"192.0.2.127\",\n\t\t\t\"gateway-identity\",\n\t\t\t[]corev1.EndpointPort{\n\t\t\t\t{\n\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     888,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     888,\n\t\t\t\t},\n\t\t\t})),\n\t},\n\tlink: v1alpha3.Link{\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName:       clusterName,\n\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\tGatewayPort:             \"888\",\n\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\tSelector:                defaultSelector,\n\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t},\n\t},\n}\nvar clusterUnregistered = &testEnvironment{\n\tevents: []interface{}{\n\t\t&ClusterUnregistered{},\n\t},\n\tlocalResources: []string{\n\t\tasYaml(mirrorService(\"test-service-1-remote\", \"test-namespace\", \"\", nil, nil)),\n\t\tasYaml(endpoints(\"test-service-1-remote\", \"test-namespace\", nil, \"\", \"\", nil)),\n\t\tasYaml(mirrorService(\"test-service-2-remote\", \"test-namespace\", \"\", nil, nil)),\n\t\tasYaml(endpoints(\"test-service-2-remote\", \"test-namespace\", nil, \"\", \"\", nil)),\n\t},\n\tlink: v1alpha3.Link{\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName: clusterName,\n\t\t},\n\t},\n}\n\nvar gcTriggered = &testEnvironment{\n\tevents: []interface{}{\n\t\t&OrphanedServicesGcTriggered{},\n\t},\n\tlocalResources: []string{\n\t\tasYaml(mirrorService(\"test-service-1-remote\", \"test-namespace\", \"\", nil, nil)),\n\t\tasYaml(endpoints(\"test-service-1-remote\", \"test-namespace\", nil, \"\", \"\", nil)),\n\t\tasYaml(mirrorService(\"test-service-2-remote\", \"test-namespace\", \"\", nil, nil)),\n\t\tasYaml(endpoints(\"test-service-2-remote\", \"test-namespace\", nil, \"\", \"\", nil)),\n\t\tasYaml(headlessMirrorService(\"test-headless-service-remote\", \"test-namespace\", \"\", nil, nil)),\n\t\tasYaml(endpointMirrorService(\"pod-0\", \"test-headless-service-remote\", \"test-namespace\", \"\", nil, nil)),\n\t\tasYaml(headlessMirrorEndpoints(\"test-headless-service-remote\", \"test-namespace\", nil, \"\", nil)),\n\t\tasYaml(endpointMirrorEndpoints(\"test-headless-service-remote\", \"test-namespace\", nil, \"pod-0\", \"\", \"\", nil)),\n\t},\n\tremoteResources: []string{\n\t\tasYaml(remoteService(\"test-service-1\", \"test-namespace\", \"\", map[string]string{consts.DefaultExportedServiceSelector: \"true\"}, nil)),\n\t\tasYaml(remoteHeadlessService(\"test-headless-service\", \"test-namespace\", \"\", nil, nil)),\n\t},\n\tlink: v1alpha3.Link{\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName: clusterName,\n\t\t},\n\t},\n}\n\nvar noGatewayLink = &testEnvironment{\n\tevents: []interface{}{\n\t\t&RemoteServiceExported{\n\t\t\tservice: remoteService(\"service-one\", \"ns1\", \"111\", map[string]string{\n\t\t\t\tconsts.DefaultExportedServiceSelector: \"remote-discovery\",\n\t\t\t}, []corev1.ServicePort{\n\t\t\t\t{\n\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     555,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     666,\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t\t&RemoteServiceExported{\n\t\t\tservice: remoteService(\"service-two\", \"ns1\", \"111\", map[string]string{\n\t\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t\t}, []corev1.ServicePort{\n\t\t\t\t{\n\t\t\t\t\tName:     \"port1\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     555,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"port2\",\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     666,\n\t\t\t\t},\n\t\t\t}),\n\t\t},\n\t},\n\tlocalResources: []string{\n\t\tasYaml(namespace(\"ns1\")),\n\t},\n\tremoteResources: []string{\n\t\tasYaml(endpoints(\"service-one\", \"ns1\", nil, \"192.0.2.127\", \"gateway-identity\", []corev1.EndpointPort{})),\n\t\tasYaml(endpoints(\"service-two\", \"ns1\", nil, \"192.0.2.128\", \"gateway-identity\", []corev1.EndpointPort{})),\n\t},\n\tlink: v1alpha3.Link{\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName:       clusterName,\n\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\tSelector:                &metav1.LabelSelector{},\n\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t},\n\t},\n}\n\nfunc onAddOrUpdateExportedSvc(isAdd bool) *testEnvironment {\n\treturn &testEnvironment{\n\t\tevents: []interface{}{\n\t\t\tonAddOrUpdateEvent(isAdd, remoteService(\"test-service\", \"test-namespace\", \"resVersion\", map[string]string{\n\t\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t\t}, nil)),\n\t\t},\n\t\tlink: v1alpha3.Link{\n\t\t\tSpec: v1alpha3.LinkSpec{\n\t\t\t\tTargetClusterName:       clusterName,\n\t\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\t\tGatewayPort:             \"888\",\n\t\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\t\tSelector:                defaultSelector,\n\t\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t\t},\n\t\t},\n\t}\n\n}\n\nfunc onAddOrUpdateRemoteServiceUpdated(isAdd bool) *testEnvironment {\n\treturn &testEnvironment{\n\t\tevents: []interface{}{\n\t\t\tonAddOrUpdateEvent(isAdd, remoteService(\"test-service\", \"test-namespace\", \"currentResVersion\", map[string]string{\n\t\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t\t}, nil)),\n\t\t},\n\t\tlocalResources: []string{\n\t\t\tasYaml(mirrorService(\"test-service-remote\", \"test-namespace\", \"pastResourceVersion\", nil, nil)),\n\t\t\tasYaml(endpoints(\"test-service-remote\", \"test-namespace\", nil, \"0.0.0.0\", \"\", nil)),\n\t\t},\n\t\tlink: v1alpha3.Link{\n\t\t\tSpec: v1alpha3.LinkSpec{\n\t\t\t\tTargetClusterName:       clusterName,\n\t\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\t\tGatewayPort:             \"888\",\n\t\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\t\tSelector:                defaultSelector,\n\t\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc onAddOrUpdateSameResVersion(isAdd bool) *testEnvironment {\n\treturn &testEnvironment{\n\t\tevents: []interface{}{\n\t\t\tonAddOrUpdateEvent(isAdd, remoteService(\"test-service\", \"test-namespace\", \"currentResVersion\", map[string]string{\n\t\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t\t}, nil)),\n\t\t},\n\t\tlocalResources: []string{\n\t\t\tasYaml(mirrorService(\"test-service-remote\", \"test-namespace\", \"currentResVersion\", nil, nil)),\n\t\t\tasYaml(endpoints(\"test-service-remote\", \"test-namespace\", nil, \"0.0.0.0\", \"\", nil)),\n\t\t},\n\t\tlink: v1alpha3.Link{\n\t\t\tSpec: v1alpha3.LinkSpec{\n\t\t\t\tTargetClusterName:       clusterName,\n\t\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\t\tGatewayPort:             \"888\",\n\t\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\t\tSelector:                defaultSelector,\n\t\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc serviceNotExportedAnymore(isAdd bool) *testEnvironment {\n\treturn &testEnvironment{\n\t\tevents: []interface{}{\n\t\t\tonAddOrUpdateEvent(isAdd, remoteService(\"test-service\", \"test-namespace\", \"currentResVersion\", map[string]string{}, nil)),\n\t\t},\n\t\tlocalResources: []string{\n\t\t\tasYaml(mirrorService(\"test-service-remote\", \"test-namespace\", \"currentResVersion\", nil, nil)),\n\t\t\tasYaml(endpoints(\"test-service-remote\", \"test-namespace\", nil, \"0.0.0.0\", \"\", nil)),\n\t\t},\n\t\tlink: v1alpha3.Link{\n\t\t\tSpec: v1alpha3.LinkSpec{\n\t\t\t\tTargetClusterName:       clusterName,\n\t\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\t\tGatewayPort:             \"888\",\n\t\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\t\tSelector:                defaultSelector,\n\t\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t\t},\n\t\t},\n\t}\n}\n\nvar onDeleteExportedService = &testEnvironment{\n\tevents: []interface{}{\n\t\t&OnDeleteCalled{\n\t\t\tsvc: remoteService(\"test-service\", \"test-namespace\", \"currentResVersion\", map[string]string{\n\t\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t\t}, nil),\n\t\t},\n\t},\n\tlink: v1alpha3.Link{\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName:       clusterName,\n\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\tGatewayPort:             \"888\",\n\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\tSelector:                defaultSelector,\n\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t},\n\t},\n}\n\nvar onDeleteNonExportedService = &testEnvironment{\n\tevents: []interface{}{\n\t\t&OnDeleteCalled{\n\t\t\tsvc: remoteService(\"gateway\", \"test-namespace\", \"currentResVersion\", map[string]string{}, nil),\n\t\t},\n\t},\n\tlink: v1alpha3.Link{\n\t\tSpec: v1alpha3.LinkSpec{\n\t\t\tTargetClusterName:       clusterName,\n\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\tGatewayIdentity:         \"gateway-identity\",\n\t\t\tGatewayAddress:          \"192.0.2.127\",\n\t\t\tGatewayPort:             \"888\",\n\t\t\tProbeSpec:               defaultProbeSpec,\n\t\t\tSelector:                defaultSelector,\n\t\t\tRemoteDiscoverySelector: defaultRemoteDiscoverySelector,\n\t\t},\n\t},\n}\n\n// the following tests ensure that onAdd, onUpdate and onDelete result in\n// queueing more specific events to be processed\n\nfunc onAddOrUpdateEvent(isAdd bool, svc *corev1.Service) interface{} {\n\tif isAdd {\n\t\treturn &OnAddCalled{svc: svc}\n\t}\n\treturn &OnUpdateCalled{svc: svc}\n}\n\nfunc diffServices(expected, actual *corev1.Service) error {\n\tif expected.Name != actual.Name {\n\t\treturn fmt.Errorf(\"was expecting service with name %s but was %s\", expected.Name, actual.Name)\n\t}\n\n\tif expected.Namespace != actual.Namespace {\n\t\treturn fmt.Errorf(\"was expecting service with namespace %s but was %s\", expected.Namespace, actual.Namespace)\n\t}\n\n\tif diff := deep.Equal(expected.Annotations, actual.Annotations); diff != nil {\n\t\treturn fmt.Errorf(\"annotation mismatch %+v\", diff)\n\t}\n\n\tif diff := deep.Equal(expected.Labels, actual.Labels); diff != nil {\n\t\treturn fmt.Errorf(\"label mismatch %+v\", diff)\n\t}\n\n\treturn nil\n}\n\nfunc diffEndpoints(expected, actual *corev1.Endpoints) error {\n\tif expected.Name != actual.Name {\n\t\treturn fmt.Errorf(\"was expecting endpoints with name %s but was %s\", expected.Name, actual.Name)\n\t}\n\n\tif expected.Namespace != actual.Namespace {\n\t\treturn fmt.Errorf(\"was expecting endpoints with namespace %s but was %s\", expected.Namespace, actual.Namespace)\n\t}\n\n\tif diff := deep.Equal(expected.Annotations, actual.Annotations); diff != nil {\n\t\treturn fmt.Errorf(\"annotation mismatch %+v\", diff)\n\t}\n\n\tif diff := deep.Equal(expected.Labels, actual.Labels); diff != nil {\n\t\treturn fmt.Errorf(\"label mismatch %+v\", diff)\n\t}\n\n\tif diff := deep.Equal(expected.Subsets, actual.Subsets); diff != nil {\n\t\treturn fmt.Errorf(\"subsets mismatch %+v\", diff)\n\t}\n\n\treturn nil\n}\n\nfunc remoteService(name, namespace, resourceVersion string, labels map[string]string, ports []corev1.ServicePort) *corev1.Service {\n\treturn &corev1.Service{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Service\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:            name,\n\t\t\tNamespace:       namespace,\n\t\t\tResourceVersion: resourceVersion,\n\t\t\tLabels:          labels,\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tPorts: ports,\n\t\t},\n\t}\n}\n\nfunc remoteHeadlessService(name, namespace, resourceVersion string, labels map[string]string, ports []corev1.ServicePort) *corev1.Service {\n\treturn &corev1.Service{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Service\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:            name,\n\t\t\tNamespace:       namespace,\n\t\t\tResourceVersion: resourceVersion,\n\t\t\tLabels:          labels,\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tClusterIP: \"None\",\n\t\t\tPorts:     ports,\n\t\t},\n\t}\n}\n\nfunc remoteHeadlessEndpoints(name, namespace, resourceVersion, address string, ports []corev1.EndpointPort) *corev1.Endpoints {\n\treturn &corev1.Endpoints{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Endpoints\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:            name,\n\t\t\tNamespace:       namespace,\n\t\t\tResourceVersion: resourceVersion,\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"service.kubernetes.io/headless\":      \"\",\n\t\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t\t},\n\t\t},\n\t\tSubsets: []corev1.EndpointSubset{\n\t\t\t{\n\t\t\t\tAddresses: []corev1.EndpointAddress{\n\t\t\t\t\t{\n\t\t\t\t\t\tHostname: \"pod-0\",\n\t\t\t\t\t\tIP:       address,\n\t\t\t\t\t\tTargetRef: &corev1.ObjectReference{\n\t\t\t\t\t\t\tName:            \"pod-0\",\n\t\t\t\t\t\t\tResourceVersion: resourceVersion,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPorts: ports,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc remoteHeadlessEndpointsUpdate(name, namespace, resourceVersion, address string, ports []corev1.EndpointPort) *corev1.Endpoints {\n\treturn &corev1.Endpoints{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Endpoints\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:            name,\n\t\t\tNamespace:       namespace,\n\t\t\tResourceVersion: resourceVersion,\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"service.kubernetes.io/headless\":      \"\",\n\t\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t\t},\n\t\t},\n\t\tSubsets: []corev1.EndpointSubset{\n\t\t\t{\n\t\t\t\tAddresses: []corev1.EndpointAddress{\n\t\t\t\t\t{\n\t\t\t\t\t\tHostname: \"pod-0\",\n\t\t\t\t\t\tIP:       address,\n\t\t\t\t\t\tTargetRef: &corev1.ObjectReference{\n\t\t\t\t\t\t\tName:            \"pod-0\",\n\t\t\t\t\t\t\tResourceVersion: resourceVersion,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tHostname: \"pod-1\",\n\t\t\t\t\t\tIP:       address,\n\t\t\t\t\t\tTargetRef: &corev1.ObjectReference{\n\t\t\t\t\t\t\tName:            \"pod-1\",\n\t\t\t\t\t\t\tResourceVersion: resourceVersion,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPorts: ports,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc mirrorService(name, namespace, resourceVersion string, labels map[string]string, ports []corev1.ServicePort) *corev1.Service {\n\tannotations := make(map[string]string)\n\tannotations[consts.RemoteResourceVersionAnnotation] = resourceVersion\n\tannotations[consts.RemoteServiceFqName] = fmt.Sprintf(\"%s.%s.svc.cluster.local\", strings.Replace(name, \"-remote\", \"\", 1), namespace)\n\n\tif labels == nil {\n\t\tlabels = make(map[string]string)\n\t}\n\tlabels[consts.RemoteClusterNameLabel] = clusterName\n\tlabels[consts.MirroredResourceLabel] = \"true\"\n\n\treturn &corev1.Service{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Service\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        name,\n\t\t\tNamespace:   namespace,\n\t\t\tLabels:      labels,\n\t\t\tAnnotations: annotations,\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tPorts: ports,\n\t\t},\n\t}\n}\n\nfunc headlessMirrorService(name, namespace, resourceVersion string, labels map[string]string, ports []corev1.ServicePort) *corev1.Service {\n\tsvc := mirrorService(name, namespace, resourceVersion, labels, ports)\n\tsvc.Spec.ClusterIP = \"None\"\n\treturn svc\n}\n\nfunc endpointMirrorService(hostname, rootName, namespace, resourceVersion string, labels map[string]string, ports []corev1.ServicePort) *corev1.Service {\n\tannotations := make(map[string]string)\n\tannotations[consts.RemoteResourceVersionAnnotation] = resourceVersion\n\tannotations[consts.RemoteServiceFqName] = fmt.Sprintf(\"%s.%s.%s.svc.cluster.local\", hostname, strings.Replace(rootName, \"-remote\", \"\", 1), namespace)\n\n\tif labels == nil {\n\t\tlabels = make(map[string]string)\n\t}\n\tlabels[consts.MirroredHeadlessSvcNameLabel] = rootName\n\tlabels[consts.RemoteClusterNameLabel] = clusterName\n\tlabels[consts.MirroredResourceLabel] = \"true\"\n\n\treturn &corev1.Service{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Service\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        fmt.Sprintf(\"%s-%s\", hostname, clusterName),\n\t\t\tNamespace:   namespace,\n\t\t\tLabels:      labels,\n\t\t\tAnnotations: annotations,\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tPorts: ports,\n\t\t},\n\t}\n}\n\nfunc remoteDiscoveryMirrorService(name, namespace, resourceVersion string, ports []corev1.ServicePort) *corev1.Service {\n\tannotations := make(map[string]string)\n\tannotations[consts.RemoteResourceVersionAnnotation] = resourceVersion\n\tannotations[consts.RemoteServiceFqName] = fmt.Sprintf(\"%s.%s.svc.cluster.local\", name, namespace)\n\n\treturn &corev1.Service{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Service\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      fmt.Sprintf(\"%s-%s\", name, clusterName),\n\t\t\tNamespace: namespace,\n\t\t\tLabels: map[string]string{\n\t\t\t\tconsts.RemoteClusterNameLabel: clusterName,\n\t\t\t\tconsts.MirroredResourceLabel:  \"true\",\n\t\t\t\tconsts.RemoteDiscoveryLabel:   clusterName,\n\t\t\t\tconsts.RemoteServiceLabel:     name,\n\t\t\t},\n\t\t\tAnnotations: annotations,\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tPorts: ports,\n\t\t},\n\t}\n}\n\n//nolint:unparam\nfunc federatedService(name, namespace string, ports []corev1.ServicePort, localDiscovery, remoteDiscovery string) *corev1.Service {\n\tannotations := make(map[string]string)\n\tif localDiscovery != \"\" {\n\t\tannotations[consts.LocalDiscoveryAnnotation] = localDiscovery\n\t}\n\tif remoteDiscovery != \"\" {\n\t\tannotations[consts.RemoteDiscoveryAnnotation] = remoteDiscovery\n\t}\n\n\treturn &corev1.Service{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Service\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      fmt.Sprintf(\"%s-federated\", name),\n\t\t\tNamespace: namespace,\n\t\t\tLabels: map[string]string{\n\t\t\t\tconsts.MirroredResourceLabel: \"true\",\n\t\t\t},\n\t\t\tAnnotations: annotations,\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tPorts: ports,\n\t\t},\n\t}\n}\n\nfunc asYaml(obj interface{}) string {\n\tbytes, err := yaml.Marshal(obj)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\treturn string(bytes)\n}\n\nfunc gateway(name, namespace, resourceVersion, ip, portName string, port int32, identity string, probePort int32, probePath string, probePeriod string) *corev1.Service {\n\tsvc := corev1.Service{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Service\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:            name,\n\t\t\tNamespace:       namespace,\n\t\t\tResourceVersion: resourceVersion,\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tconsts.GatewayIdentity:    identity,\n\t\t\t\tconsts.GatewayProbePath:   probePath,\n\t\t\t\tconsts.GatewayProbePeriod: probePeriod,\n\t\t\t},\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tPorts: []corev1.ServicePort{\n\t\t\t\t{\n\t\t\t\t\tName:     portName,\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     port,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     consts.ProbePortName,\n\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\tPort:     probePort,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tif ip != \"\" {\n\t\tsvc.Status.LoadBalancer.Ingress = append(svc.Status.LoadBalancer.Ingress, corev1.LoadBalancerIngress{IP: ip})\n\t}\n\treturn &svc\n}\n\nfunc endpoints(name, namespace string, labels map[string]string, gatewayIP string, gatewayIdentity string, ports []corev1.EndpointPort) *corev1.Endpoints {\n\tvar subsets []corev1.EndpointSubset\n\tif gatewayIP != \"\" {\n\t\tsubsets = []corev1.EndpointSubset{\n\t\t\t{\n\t\t\t\tAddresses: []corev1.EndpointAddress{\n\t\t\t\t\t{\n\t\t\t\t\t\tIP: gatewayIP,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPorts: ports,\n\t\t\t},\n\t\t}\n\t}\n\n\tif labels == nil {\n\t\tlabels = make(map[string]string)\n\t}\n\tlabels[consts.RemoteClusterNameLabel] = clusterName\n\tlabels[consts.MirroredResourceLabel] = \"true\"\n\n\tendpoints := &corev1.Endpoints{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Endpoints\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: namespace,\n\t\t\tLabels:    labels,\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tconsts.RemoteServiceFqName: fmt.Sprintf(\"%s.%s.svc.cluster.local\", strings.Replace(name, \"-remote\", \"\", 1), namespace),\n\t\t\t},\n\t\t},\n\t\tSubsets: subsets,\n\t}\n\n\tif gatewayIdentity != \"\" {\n\t\tendpoints.Annotations[consts.RemoteGatewayIdentity] = gatewayIdentity\n\t}\n\n\treturn endpoints\n}\n\nfunc endpointMirrorEndpoints(rootName, namespace string, labels map[string]string, hostname, gatewayIP, gatewayIdentity string, ports []corev1.EndpointPort) *corev1.Endpoints {\n\tlocalName := fmt.Sprintf(\"%s-%s\", hostname, clusterName)\n\tep := endpoints(localName, namespace, labels, gatewayIP, gatewayIdentity, ports)\n\n\tep.Annotations[consts.RemoteServiceFqName] = fmt.Sprintf(\"%s.%s.%s.svc.cluster.local\", hostname, strings.Replace(rootName, \"-remote\", \"\", 1), namespace)\n\tep.Labels[consts.MirroredHeadlessSvcNameLabel] = rootName\n\n\treturn ep\n}\n\nfunc headlessMirrorEndpoints(name, namespace string, labels map[string]string, gatewayIdentity string, ports []corev1.EndpointPort) *corev1.Endpoints {\n\tif labels == nil {\n\t\tlabels = make(map[string]string)\n\t}\n\tlabels[consts.RemoteClusterNameLabel] = clusterName\n\tlabels[consts.MirroredResourceLabel] = \"true\"\n\tendpoints := &corev1.Endpoints{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Endpoints\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: namespace,\n\t\t\tLabels:    labels,\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tconsts.RemoteServiceFqName: fmt.Sprintf(\"%s.%s.svc.cluster.local\", strings.Replace(name, \"-remote\", \"\", 1), namespace),\n\t\t\t},\n\t\t},\n\t\tSubsets: []corev1.EndpointSubset{\n\t\t\t{\n\t\t\t\tAddresses: []corev1.EndpointAddress{\n\t\t\t\t\t{\n\t\t\t\t\t\tHostname: \"pod-0\",\n\t\t\t\t\t\tIP:       \"\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPorts: ports,\n\t\t\t},\n\t\t},\n\t}\n\n\tif gatewayIdentity != \"\" {\n\t\tendpoints.Annotations[consts.RemoteGatewayIdentity] = gatewayIdentity\n\t}\n\n\treturn endpoints\n}\n\nfunc headlessMirrorEndpointsUpdated(name, namespace string, hostnames, hostIPs []string, gatewayIdentity string, ports []corev1.EndpointPort) *corev1.Endpoints {\n\tendpoints := &corev1.Endpoints{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Endpoints\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: namespace,\n\t\t\tLabels: map[string]string{\n\t\t\t\tconsts.RemoteClusterNameLabel: clusterName,\n\t\t\t\tconsts.MirroredResourceLabel:  \"true\",\n\t\t\t},\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tconsts.RemoteServiceFqName: fmt.Sprintf(\"%s.%s.svc.cluster.local\", strings.Replace(name, \"-remote\", \"\", 1), namespace),\n\t\t\t},\n\t\t},\n\t\tSubsets: []corev1.EndpointSubset{\n\t\t\t{\n\t\t\t\tAddresses: []corev1.EndpointAddress{\n\t\t\t\t\t{\n\t\t\t\t\t\tHostname: hostnames[0],\n\t\t\t\t\t\tIP:       hostIPs[0],\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tHostname: hostnames[1],\n\t\t\t\t\t\tIP:       hostIPs[1],\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPorts: ports,\n\t\t\t},\n\t\t},\n\t}\n\n\tif gatewayIdentity != \"\" {\n\t\tendpoints.Annotations[consts.RemoteGatewayIdentity] = gatewayIdentity\n\t}\n\n\treturn endpoints\n}\n\nfunc namespace(name string) *corev1.Namespace {\n\treturn &corev1.Namespace{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"Namespace\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: name,\n\t\t},\n\t}\n}\n\n// createEnvWithSelector will create a test environment with two services. It\n// accepts a default and a remote discovery selector which it will use for the\n// link creation. This function is used to create environments that differ only\n// in the selector used in the link.\nfunc createEnvWithSelector(defaultSelector, remoteSelector *metav1.LabelSelector) *testEnvironment {\n\treturn &testEnvironment{\n\t\tevents: []interface{}{\n\t\t\t&OnAddCalled{\n\t\t\t\tsvc: remoteService(\"service-one\", \"ns1\", \"111\", map[string]string{\n\t\t\t\t\tconsts.DefaultExportedServiceSelector: \"true\",\n\t\t\t\t}, []corev1.ServicePort{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"default1\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     555,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"default2\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     666,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t\t&OnAddCalled{\n\t\t\t\tsvc: remoteService(\"service-two\", \"ns1\", \"111\", map[string]string{\n\t\t\t\t\tconsts.DefaultExportedServiceSelector: \"remote-discovery\",\n\t\t\t\t}, []corev1.ServicePort{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"remote1\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     777,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName:     \"remote2\",\n\t\t\t\t\t\tProtocol: \"TCP\",\n\t\t\t\t\t\tPort:     888,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\tlocalResources: []string{\n\t\t\tasYaml(namespace(\"ns1\")),\n\t\t},\n\t\tremoteResources: []string{\n\t\t\tasYaml(endpoints(\"service-one\", \"ns1\", nil, \"192.0.2.127\", \"gateway-identity\", []corev1.EndpointPort{})),\n\t\t\tasYaml(endpoints(\"service-two\", \"ns1\", nil, \"192.0.3.127\", \"gateway-identity\", []corev1.EndpointPort{})),\n\t\t},\n\t\tlink: v1alpha3.Link{\n\t\t\tSpec: v1alpha3.LinkSpec{\n\t\t\t\tTargetClusterName:       clusterName,\n\t\t\t\tTargetClusterDomain:     clusterDomain,\n\t\t\t\tSelector:                defaultSelector,\n\t\t\t\tRemoteDiscoverySelector: remoteSelector,\n\t\t\t},\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "multicluster/service-mirror/events_formatting.go",
    "content": "package servicemirror\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\tconsts \"github.com/linkerd/linkerd2/pkg/k8s\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\nfunc formatMetadata(meta map[string]string) string {\n\tvar metadata []string\n\n\tfor k, v := range meta {\n\t\tif strings.HasPrefix(k, consts.Prefix) || strings.HasPrefix(k, consts.ProxyConfigAnnotationsPrefix) {\n\t\t\tmetadata = append(metadata, fmt.Sprintf(\"%s=%s\", k, v))\n\t\t}\n\t}\n\treturn fmt.Sprintf(\"[%s]\", strings.Join(metadata, \",\"))\n}\n\nfunc formatService(svc *corev1.Service) string {\n\tif svc == nil {\n\t\treturn \"Service: nil\"\n\t}\n\treturn fmt.Sprintf(\"Service: {name: %s, namespace: %s, annotations: [%s], labels [%s]}\", svc.Name, svc.Namespace, formatMetadata(svc.Annotations), formatMetadata(svc.Labels))\n}\n\n// Events for cluster watcher\nfunc (rsc RemoteServiceExported) String() string {\n\treturn fmt.Sprintf(\"RemoteServiceExported: {service: %s}\", formatService(rsc.service))\n}\n\nfunc (rsu RemoteExportedServiceUpdated) String() string {\n\treturn fmt.Sprintf(\"RemoteExportedServiceUpdated: {remoteUpdate: %s}\", formatService(rsu.remoteUpdate))\n}\n\nfunc (rsd RemoteServiceUnexported) String() string {\n\treturn fmt.Sprintf(\"RemoteServiceUnexported: {name: %s, namespace: %s }\", rsd.Name, rsd.Namespace)\n}\n\nfunc (cfs CreateFederatedService) String() string {\n\treturn fmt.Sprintf(\"CreateFederatedService: {service: %s}\", formatService(cfs.service))\n}\n\nfunc (jfs RemoteServiceJoinsFederatedService) String() string {\n\treturn fmt.Sprintf(\"RemoteServiceJoinsFederatedService: {remoteUpdate: %s}\", formatService(jfs.remoteUpdate))\n}\n\nfunc (lfs RemoteServiceLeavesFederatedService) String() string {\n\treturn fmt.Sprintf(\"RemoteServiceLeavesFederatedService: {name: %s, namespace: %s }\", lfs.Name, lfs.Namespace)\n}\n\nfunc (cgu ClusterUnregistered) String() string {\n\treturn \"ClusterUnregistered: {}\"\n}\n\nfunc (cgu OrphanedServicesGcTriggered) String() string {\n\treturn \"OrphanedServicesGcTriggered: {}\"\n}\n\nfunc (oa OnAddCalled) String() string {\n\treturn fmt.Sprintf(\"OnAddCalled: {svc: %s}\", formatService(oa.svc))\n}\n\nfunc (ou OnUpdateCalled) String() string {\n\treturn fmt.Sprintf(\"OnUpdateCalled: {svc: %s}\", formatService(ou.svc))\n}\n\nfunc (od OnDeleteCalled) String() string {\n\treturn fmt.Sprintf(\"OnDeleteCalled: {svc: %s}\", formatService(od.svc))\n}\n\nfunc (re RepairEndpoints) String() string {\n\treturn \"RepairEndpoints\"\n}\n\nfunc (ol OnLocalNamespaceAdded) String() string {\n\treturn fmt.Sprintf(\"OnLocalNamespaceAdded: {namespace: %s}\", ol.ns)\n}\n"
  },
  {
    "path": "multicluster/service-mirror/jittered_ticker.go",
    "content": "// Copyright (c) 2016-2017 Tigera, Inc. 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//NB: This file was copied from: https://github.com/projectcalico/felix/blob/master/jitter/jittered_ticker.go\n\npackage servicemirror\n\nimport (\n\t\"math/rand\"\n\t\"time\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\n// Ticker tries to emit events on channel C at minDuration intervals plus up to maxJitter.\ntype Ticker struct {\n\tC           <-chan time.Time\n\tstop        chan bool\n\tMinDuration time.Duration\n\tMaxJitter   time.Duration\n}\n\n// NewTicker creates a new Ticker\nfunc NewTicker(minDuration time.Duration, maxJitter time.Duration) *Ticker {\n\tif minDuration < 0 {\n\t\tlog.WithField(\"duration\", minDuration).Panic(\"Negative duration\")\n\t}\n\tif maxJitter < 0 {\n\t\tlog.WithField(\"jitter\", minDuration).Panic(\"Negative jitter\")\n\t}\n\tc := make(chan time.Time)\n\tticker := &Ticker{\n\t\tC:           c,\n\t\tstop:        make(chan bool),\n\t\tMinDuration: minDuration,\n\t\tMaxJitter:   maxJitter,\n\t}\n\tgo ticker.loop(c)\n\treturn ticker\n}\n\nfunc (t *Ticker) loop(c chan time.Time) {\ntickLoop:\n\tfor {\n\t\ttime.Sleep(t.calculateDelay())\n\t\t// Send best-effort then go back to sleep.\n\t\tselect {\n\t\tcase <-t.stop:\n\t\t\tlog.Info(\"Stopping jittered ticker\")\n\t\t\tclose(c)\n\t\t\tbreak tickLoop\n\t\tcase c <- time.Now():\n\t\tdefault:\n\t\t}\n\t}\n}\n\nfunc (t *Ticker) calculateDelay() time.Duration {\n\t// The weakness of this random number generator is not significant for\n\t// calculating a delay.\n\t//nolint:gosec\n\tjitter := time.Duration(rand.Int63n(int64(t.MaxJitter)))\n\tdelay := t.MinDuration + jitter\n\treturn delay\n}\n\n// Stop the ticker\nfunc (t *Ticker) Stop() {\n\tt.stop <- true\n}\n"
  },
  {
    "path": "multicluster/service-mirror/link_handlers.go",
    "content": "package servicemirror\n\nimport (\n\t\"reflect\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\nfunc GetLinkHandlers(results chan<- *v1alpha3.Link, linkName string) cache.ResourceEventHandlerFuncs {\n\treturn cache.ResourceEventHandlerFuncs{\n\t\tAddFunc: func(obj interface{}) {\n\t\t\tlink, ok := obj.(*v1alpha3.Link)\n\t\t\tif !ok {\n\t\t\t\tlog.Errorf(\"object is not a Link: %+v\", obj)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif link.GetName() == linkName {\n\t\t\t\tselect {\n\t\t\t\tcase results <- link:\n\t\t\t\tdefault:\n\t\t\t\t\tlog.Errorf(\"Link update dropped (queue full): %s\", link.GetName())\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tUpdateFunc: func(oldObj, currentObj interface{}) {\n\t\t\toldLink, ok := oldObj.(*v1alpha3.Link)\n\t\t\tif !ok {\n\t\t\t\tlog.Errorf(\"object is not a Link: %+v\", oldObj)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcurrentLink, ok := currentObj.(*v1alpha3.Link)\n\t\t\tif !ok {\n\t\t\t\tlog.Errorf(\"object is not a Link: %+v\", currentObj)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif reflect.DeepEqual(oldLink.Spec, currentLink.Spec) {\n\t\t\t\tlog.Debugf(\"Link update ignored (only status changed): %s\", currentLink.GetName())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif currentLink.GetName() == linkName {\n\t\t\t\tselect {\n\t\t\t\tcase results <- currentLink:\n\t\t\t\tdefault:\n\t\t\t\t\tlog.Errorf(\"Link update dropped (queue full): %s\", currentLink.GetName())\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tDeleteFunc: func(obj interface{}) {\n\t\t\tlink, ok := obj.(*v1alpha3.Link)\n\t\t\tif !ok {\n\t\t\t\ttombstone, ok := obj.(cache.DeletedFinalStateUnknown)\n\t\t\t\tif !ok {\n\t\t\t\t\tlog.Errorf(\"couldn't get object from DeletedFinalStateUnknown %#v\", obj)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlink, ok = tombstone.Obj.(*v1alpha3.Link)\n\t\t\t\tif !ok {\n\t\t\t\t\tlog.Errorf(\"DeletedFinalStateUnknown contained object that is not a Link %#v\", obj)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tif link.GetName() == linkName {\n\t\t\t\tselect {\n\t\t\t\tcase results <- nil: // nil indicates the link was deleted\n\t\t\t\tdefault:\n\t\t\t\t\tlog.Errorf(\"Link delete dropped (queue full): %s\", link.GetName())\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "multicluster/service-mirror/link_handlers_test.go",
    "content": "package servicemirror\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"log\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\tl5dcrdinformer \"github.com/linkerd/linkerd2/controller/gen/client/informers/externalversions\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n)\n\nconst nsName = \"ns1\"\nconst linkName = \"linkName\"\n\nfunc TestLinkHandlers(t *testing.T) {\n\tk8sAPI, err := k8s.NewFakeAPIWithL5dClient()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tlinksAPI := k8s.NewL5dNamespacedAPI(k8sAPI.L5dClient, \"linkerd-multicluster\", \"local\", k8s.Link)\n\tk8sAPI.Sync(nil)\n\tlinksAPI.Sync(nil)\n\n\tinformerFactory := l5dcrdinformer.NewSharedInformerFactoryWithOptions(\n\t\tk8sAPI.L5dClient,\n\t\tk8s.ResyncTime,\n\t\tl5dcrdinformer.WithNamespace(nsName),\n\t)\n\tinformer := informerFactory.Link().V1alpha3().Links().Informer()\n\tinformerFactory.Start(context.Background().Done())\n\n\tresults := make(chan *v1alpha3.Link, 100)\n\t_, err = informer.AddEventHandler(GetLinkHandlers(results, linkName))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// test that a message is received when a link is created\n\t_, err = k8sAPI.Client.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: nsName}}, metav1.CreateOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tlink := &v1alpha3.Link{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      linkName,\n\t\t\tNamespace: nsName,\n\t\t},\n\t\tSpec: v1alpha3.LinkSpec{ProbeSpec: v1alpha3.ProbeSpec{Timeout: \"30s\"}},\n\t}\n\t_, err = linksAPI.L5dClient.LinkV1alpha3().Links(nsName).Create(context.Background(), link, metav1.CreateOptions{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tselect {\n\tcase link := <-results:\n\t\tif link.GetName() != linkName {\n\t\t\tt.Fatalf(\"Expected LinkName, got %s\", link.GetName())\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"Timed out waiting for message\")\n\t}\n\n\t// test that a message is received when a link spec is updated\n\tpatch := map[string]any{\n\t\t\"spec\": map[string]any{\n\t\t\t\"probeSpec\": map[string]any{\n\t\t\t\t\"timeout\": \"60s\",\n\t\t\t},\n\t\t},\n\t}\n\tpatchBytes, err := json.Marshal(patch)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to marshal patch: %v\", err)\n\t}\n\t_, err = linksAPI.L5dClient.LinkV1alpha3().Links(nsName).Patch(\n\t\tcontext.Background(),\n\t\tlinkName,\n\t\ttypes.MergePatchType,\n\t\tpatchBytes,\n\t\tmetav1.PatchOptions{},\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to patch link: %s\", err)\n\t}\n\n\tselect {\n\tcase link := <-results:\n\t\tif link.GetName() != linkName {\n\t\t\tt.Fatalf(\"Expected LinkName, got %s\", link.GetName())\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"Timed out waiting for message\")\n\t}\n\n\t// test that a message is _not_ received when a link status is updated\n\tpatch = map[string]any{\n\t\t\"status\": map[string]any{\n\t\t\t\"foo\": \"bar\",\n\t\t},\n\t}\n\tpatchBytes, err = json.Marshal(patch)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to marshal patch: %v\", err)\n\t}\n\t_, err = linksAPI.L5dClient.LinkV1alpha3().Links(nsName).Patch(\n\t\tcontext.Background(),\n\t\tlinkName,\n\t\ttypes.MergePatchType,\n\t\tpatchBytes,\n\t\tmetav1.PatchOptions{},\n\t\t\"status\",\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to patch link: %s\", err)\n\t}\n\n\tselect {\n\tcase link := <-results:\n\t\tt.Fatalf(\"Received unexpected message: %v\", link)\n\tcase <-time.After(time.Second):\n\t}\n\n\t// test that a nil message is received when a link is deleted\n\tif err := linksAPI.L5dClient.LinkV1alpha3().Links(nsName).Delete(context.Background(), linkName, metav1.DeleteOptions{}); err != nil {\n\t\tt.Fatalf(\"Failed to delete link: %s\", err)\n\t}\n\tselect {\n\tcase link := <-results:\n\t\tif link != nil {\n\t\t\tt.Fatalf(\"Expected nil, got %v\", link)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"Timed out waiting for message\")\n\t}\n}\n"
  },
  {
    "path": "multicluster/service-mirror/metrics.go",
    "content": "package servicemirror\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\tlogging \"github.com/sirupsen/logrus\"\n)\n\nconst (\n\tgatewayClusterName   = \"target_cluster_name\"\n\teventTypeLabelName   = \"event_type\"\n\tprobeSuccessfulLabel = \"probe_successful\"\n)\n\n// ProbeMetricVecs stores metrics about about gateways collected by probe\n// workers.\ntype ProbeMetricVecs struct {\n\tgatewayEnabled *prometheus.GaugeVec\n\talive          *prometheus.GaugeVec\n\tlatency        *prometheus.GaugeVec\n\tlatencies      *prometheus.HistogramVec\n\tenqueues       *prometheus.CounterVec\n\tdequeues       *prometheus.CounterVec\n\tprobes         *prometheus.CounterVec\n}\n\n// ProbeMetrics stores metrics about about a specific gateway collected by a\n// probe worker.\ntype ProbeMetrics struct {\n\tgatewayEnabled prometheus.Gauge\n\talive          prometheus.Gauge\n\tlatency        prometheus.Gauge\n\tlatencies      prometheus.Observer\n\tprobes         *prometheus.CounterVec\n\tunregister     func()\n}\n\nvar endpointRepairCounter *prometheus.CounterVec\n\nfunc init() {\n\tendpointRepairCounter = promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"service_mirror_endpoint_repairs\",\n\t\t\tHelp: \"Increments when the service mirror controller attempts to repair mirror endpoints\",\n\t\t},\n\t\t[]string{gatewayClusterName},\n\t)\n}\n\n// NewProbeMetricVecs creates a new ProbeMetricVecs.\nfunc NewProbeMetricVecs() ProbeMetricVecs {\n\tlabelNames := []string{gatewayClusterName}\n\n\tprobes := promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"gateway_probes\",\n\t\t\tHelp: \"A counter for the number of actual performed probes to a gateway\",\n\t\t},\n\t\t[]string{gatewayClusterName, probeSuccessfulLabel},\n\t)\n\n\tenqueues := promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"probe_manager_event_enqueues\",\n\t\t\tHelp: \"A counter for the number of enqueued events to the probe manager\",\n\t\t},\n\t\t[]string{eventTypeLabelName},\n\t)\n\n\tdequeues := promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"probe_manager_event_dequeues\",\n\t\t\tHelp: \"A counter for the number of dequeued events to the probe manager\",\n\t\t},\n\t\t[]string{eventTypeLabelName},\n\t)\n\n\talive := promauto.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tName: \"gateway_alive\",\n\t\t\tHelp: \"A gauge which is 1 if the gateway is alive and 0 if it is not.\",\n\t\t},\n\t\tlabelNames,\n\t)\n\n\tgatewayEnabled :=\n\t\tpromauto.NewGaugeVec(\n\t\t\tprometheus.GaugeOpts{\n\t\t\t\tName: \"gateway_enabled\",\n\t\t\t\tHelp: \"A gauge which is 1 if the gateway is enabled, and 0 if it is not\",\n\t\t\t},\n\t\t\tlabelNames,\n\t\t)\n\n\tlatency := promauto.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tName: \"gateway_latency\",\n\t\t\tHelp: \"A gauge which is the latency of the last probe to the gateway.\",\n\t\t},\n\t\tlabelNames,\n\t)\n\n\tlatencies := promauto.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tName: \"gateway_probe_latency_ms\",\n\t\t\tHelp: \"A histogram of latencies to a gateway in a target cluster.\",\n\t\t\tBuckets: []float64{\n\t\t\t\t1, 2, 3, 4, 5,\n\t\t\t\t10, 20, 30, 40, 50,\n\t\t\t\t100, 200, 300, 400, 500,\n\t\t\t\t1000, 2000, 3000, 4000, 5000,\n\t\t\t\t10000, 20000, 30000, 40000, 50000,\n\t\t\t},\n\t\t},\n\t\tlabelNames)\n\n\treturn ProbeMetricVecs{\n\t\talive:          alive,\n\t\tgatewayEnabled: gatewayEnabled,\n\t\tlatency:        latency,\n\t\tlatencies:      latencies,\n\t\tenqueues:       enqueues,\n\t\tdequeues:       dequeues,\n\t\tprobes:         probes,\n\t}\n}\n\n// NewWorkerMetrics creates a new ProbeMetrics by scoping to a specific target\n// cluster.\nfunc (mv ProbeMetricVecs) NewWorkerMetrics(remoteClusterName string) (*ProbeMetrics, error) {\n\n\tlabels := prometheus.Labels{\n\t\tgatewayClusterName: remoteClusterName,\n\t}\n\n\tcurriedProbes, err := mv.probes.CurryWith(labels)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgatewayEnabled, err := mv.gatewayEnabled.GetMetricWith(labels)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgatewayEnabled.Set(0)\n\n\talive, err := mv.alive.GetMetricWith(labels)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlatency, err := mv.latency.GetMetricWith(labels)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlatencies, err := mv.latencies.GetMetricWith(labels)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &ProbeMetrics{\n\t\tgatewayEnabled: gatewayEnabled,\n\t\talive:          alive,\n\t\tlatency:        latency,\n\t\tlatencies:      latencies,\n\t\tprobes:         curriedProbes,\n\t\tunregister: func() {\n\t\t\tmv.unregister(remoteClusterName)\n\t\t},\n\t}, nil\n}\n\nfunc (mv ProbeMetricVecs) unregister(remoteClusterName string) {\n\tlabels := prometheus.Labels{\n\t\tgatewayClusterName: remoteClusterName,\n\t}\n\n\tif !mv.gatewayEnabled.Delete(labels) {\n\t\tlogging.Warnf(\"unable to delete gateway_enabled metric with labels %s\", labels)\n\t}\n\n\tif !mv.alive.Delete(labels) {\n\t\tlogging.Warnf(\"unable to delete gateway_alive metric with labels %s\", labels)\n\t}\n\tif !mv.latencies.Delete(labels) {\n\t\tlogging.Warnf(\"unable to delete gateway_probe_latency_ms metric with labels %s\", labels)\n\t}\n}\n"
  },
  {
    "path": "multicluster/service-mirror/probe_worker.go",
    "content": "package servicemirror\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\tlogging \"github.com/sirupsen/logrus\"\n)\n\n// ProbeWorker is responsible for monitoring gateways using a probe specification\ntype ProbeWorker struct {\n\tlocalGatewayName string\n\talive            bool\n\tLiveness         chan bool\n\t*sync.RWMutex\n\tprobeSpec *v1alpha3.ProbeSpec\n\tstopCh    chan struct{}\n\tmetrics   *ProbeMetrics\n\tlog       *logging.Entry\n}\n\n// NewProbeWorker creates a new probe worker associated with a particular gateway\nfunc NewProbeWorker(localGatewayName string, spec *v1alpha3.ProbeSpec, metrics *ProbeMetrics, probekey string) *ProbeWorker {\n\tmetrics.gatewayEnabled.Set(1)\n\treturn &ProbeWorker{\n\t\tlocalGatewayName: localGatewayName,\n\t\tLiveness:         make(chan bool, 10),\n\t\tRWMutex:          &sync.RWMutex{},\n\t\tprobeSpec:        spec,\n\t\tstopCh:           make(chan struct{}),\n\t\tmetrics:          metrics,\n\t\tlog: logging.WithFields(logging.Fields{\n\t\t\t\"probe-key\": probekey,\n\t\t}),\n\t}\n}\n\n// UpdateProbeSpec is used to update the probe specification when something about the gateway changes\nfunc (pw *ProbeWorker) UpdateProbeSpec(spec *v1alpha3.ProbeSpec) {\n\tpw.Lock()\n\tpw.probeSpec = spec\n\tpw.Unlock()\n}\n\n// Stop this probe worker\nfunc (pw *ProbeWorker) Stop() {\n\tpw.metrics.unregister()\n\tpw.log.Infof(\"Stopping probe worker\")\n\tclose(pw.stopCh)\n}\n\n// Start this probe worker\nfunc (pw *ProbeWorker) Start() {\n\n\tpw.log.Infof(\"Starting probe worker\")\n\tgo pw.run()\n}\n\nfunc (pw *ProbeWorker) run() {\n\tsuccessLabel := prometheus.Labels{probeSuccessfulLabel: \"true\"}\n\tnotSuccessLabel := prometheus.Labels{probeSuccessfulLabel: \"false\"}\n\n\tif pw.probeSpec == nil {\n\t\tpw.log.Error(\"Probe spec is nil\")\n\t\treturn\n\t}\n\tprobeTickerPeriod, err := time.ParseDuration(pw.probeSpec.Period)\n\tif err != nil {\n\t\tpw.log.Errorf(\"could not parse probe period: %s\", err)\n\t\treturn\n\t}\n\tmaxJitter := probeTickerPeriod / 10 // max jitter is 10% of period\n\tprobeTicker := NewTicker(probeTickerPeriod, maxJitter)\n\tdefer probeTicker.Stop()\n\n\tfailureThreshold, err := strconv.ParseUint(pw.probeSpec.FailureThreshold, 10, 32)\n\tif err != nil {\n\t\tpw.log.Errorf(\"could not parse failure threshold: %s\", err)\n\t\treturn\n\t}\n\tvar failures uint64 = 0\n\nprobeLoop:\n\tfor {\n\t\tselect {\n\t\tcase <-pw.stopCh:\n\t\t\tbreak probeLoop\n\t\tcase <-probeTicker.C:\n\t\t\tstart := time.Now()\n\t\t\tif err := pw.doProbe(); err != nil {\n\t\t\t\tpw.log.Warn(err)\n\t\t\t\tfailures++\n\t\t\t\tif failures < failureThreshold {\n\t\t\t\t\tcontinue probeLoop\n\t\t\t\t}\n\n\t\t\t\tpw.log.Warnf(\"Failure threshold (%s) reached - Marking as unhealthy\", pw.probeSpec.FailureThreshold)\n\t\t\t\tpw.metrics.alive.Set(0)\n\n\t\t\t\tcounter, err := pw.metrics.probes.GetMetricWith(notSuccessLabel)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpw.log.Errorf(\"failed to get probe metric: %q\", err)\n\t\t\t\t} else {\n\t\t\t\t\tcounter.Inc()\n\t\t\t\t}\n\t\t\t\tif pw.alive {\n\t\t\t\t\tpw.alive = false\n\t\t\t\t\tpw.Liveness <- false\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tend := time.Since(start)\n\t\t\t\tfailures = 0\n\n\t\t\t\tpw.log.Debug(\"Gateway is healthy\")\n\t\t\t\tpw.metrics.alive.Set(1)\n\t\t\t\tpw.metrics.latency.Set(float64(end.Milliseconds()))\n\t\t\t\tpw.metrics.latencies.Observe(float64(end.Milliseconds()))\n\t\t\t\tcounter, err := pw.metrics.probes.GetMetricWith(successLabel)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpw.log.Errorf(\"failed to get probe metric: %q\", err)\n\t\t\t\t} else {\n\t\t\t\t\tcounter.Inc()\n\t\t\t\t}\n\t\t\t\tif !pw.alive {\n\t\t\t\t\tpw.alive = true\n\t\t\t\t\tpw.Liveness <- true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (pw *ProbeWorker) doProbe() error {\n\tpw.RLock()\n\tdefer pw.RUnlock()\n\n\ttimeout, err := time.ParseDuration(pw.probeSpec.Timeout)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not parse timeout: %w\", err)\n\t}\n\tclient := http.Client{\n\t\tTimeout: timeout,\n\t}\n\n\turlAddress := net.JoinHostPort(pw.localGatewayName, pw.probeSpec.Port)\n\treq, err := http.NewRequest(\"GET\", fmt.Sprintf(\"http://%s%s\", urlAddress, pw.probeSpec.Path), nil)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not create a GET request to gateway: %w\", err)\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"problem connecting with gateway: %w\", err)\n\t}\n\tif resp.StatusCode != 200 {\n\t\treturn fmt.Errorf(\"gateway returned unexpected status %d\", resp.StatusCode)\n\t}\n\n\tif err := resp.Body.Close(); err != nil {\n\t\tpw.log.Warnf(\"Failed to close response body %s\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "multicluster/static/generated_multicluster_templates.gogen.go",
    "content": "// Code generated by vfsgen; DO NOT EDIT.\n\n//go:build prod\n\npackage static\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\tpathpkg \"path\"\n\t\"time\"\n)\n\n// Templates statically implements the virtual filesystem provided to vfsgen.\nvar Templates = func() http.FileSystem {\n\tfs := vfsgen۰FS{\n\t\t\"/\": &vfsgen۰DirInfo{\n\t\t\tname:    \"/\",\n\t\t\tmodTime: time.Date(2025, 12, 12, 18, 49, 15, 325569498, time.UTC),\n\t\t},\n\t\t\"/linkerd-multicluster\": &vfsgen۰DirInfo{\n\t\t\tname:    \"linkerd-multicluster\",\n\t\t\tmodTime: time.Date(2025, 12, 12, 18, 49, 15, 369395450, time.UTC),\n\t\t},\n\t\t\"/linkerd-multicluster/.helmignore\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \".helmignore\",\n\t\t\tmodTime:          time.Date(2025, 4, 24, 21, 9, 23, 549666844, time.UTC),\n\t\t\tuncompressedSize: 340,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\x4c\\x8f\\xb1\\x6e\\xeb\\x30\\x0c\\x45\\x77\\x7e\\xc5\\x7d\\xf0\\xf2\\x9e\\xf1\\x20\\x7f\\x44\\x92\\xa1\\x4b\\x53\\xd4\\x45\\x3a\\x16\\xb2\\xcd\\x48\\x4c\\x64\\x49\\x90\\xe8\\xa4\\xed\\xd0\\x6f\\x2f\\x92\\x20\\x68\\x97\\x03\\xf0\\x80\\x24\\xee\\x6d\\xf0\\x64\\x55\\xb9\\xc4\\x0a\\x4d\\x10\\x17\\x53\\x61\\x9c\\x3d\\x47\\x0c\\x8b\\x84\\x49\\xa2\\x43\\xb6\\xe3\\xd1\\x3a\\xae\\x86\\x1a\\xbc\\x78\\xa9\\xa8\\x4b\\xce\\xa9\\x68\\x45\\xf5\\x1c\\x02\\x5c\\x48\\x03\\x66\\xab\\xa3\\x97\\xe8\\xfe\\xa3\\x70\\xb0\\x2a\\x27\\x46\\xb6\\xea\\x7f\\x79\\x1b\\x27\\x6a\\x10\\xd9\\x59\\x95\\x14\\xf1\\x37\\x17\\xde\\xcb\\x3b\\x4f\\x38\\x8b\\x7a\\xfc\\xf9\\x67\\xb0\\x8d\\xe1\\x03\\x29\\x5e\\x2f\\x2f\\x91\\x90\\xb9\\x20\\x48\\x64\\x43\\x66\\xdd\\xbf\\xf5\\x9a\\x0a\\xd3\\xf6\\xf5\\x71\\xf3\\xdc\\x53\\x83\\x55\\x9a\\xe7\\x14\\xb1\\x5b\\xf5\\x98\\xa4\\x54\\x32\\x4e\\xb4\\xbb\\xf2\\xd6\\x82\\xcc\\xf0\\x59\\xba\\x2b\\xef\\xc2\\xbb\\xee\\x82\\xfb\\x58\\x4f\\xb1\\xfb\\x79\\x34\\xd8\\xf1\\xb8\\x64\\xec\\x25\\x70\\xa5\\xd6\\xd4\\x73\\xa6\\xd6\\x0c\\xf6\\x48\\xad\\xd1\\x39\\x53\\xfb\\x45\\x0d\\x76\\xb6\\x48\\x5a\\x2a\\x1e\\xd6\\x9b\\x4a\\x26\\x97\\x74\\xe0\\x51\\xc9\\xc8\\xc4\\xb6\\xbb\\xed\\x95\\x74\\xa0\\xef\\x00\\x00\\x00\\xff\\xff\\x66\\xc2\\xf1\\x12\\x54\\x01\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/Chart.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"Chart.yaml\",\n\t\t\tmodTime:          time.Date(2025, 12, 12, 18, 49, 15, 368996883, time.UTC),\n\t\t\tuncompressedSize: 660,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xac\\x91\\xcb\\xaa\\x14\\x31\\x10\\x86\\xf7\\x79\\x8a\\xa2\\x5d\\x27\\xdd\\x67\\x96\\x0d\\x8a\\xe0\\x46\\x41\\x77\\x22\\xb3\\x4d\\x27\\x35\\x9d\\x62\\x72\\x23\\x95\\xf4\\x71\\xc0\\x87\\x97\\xbe\\xe9\\x80\\x2e\\xcf\\x2a\\x50\\x95\\xfc\\xf5\\xd5\\x17\\x9d\\xe9\\x07\\x16\\xa6\\x14\\x47\\xe8\\x96\\x97\\x4e\\xbc\\x83\\xea\\x88\\x61\\xd9\\x8b\\xf0\\x4a\\xde\\xc3\\x84\\xd0\\xb2\\xd5\\x15\\x2d\\x4c\\x0f\\xa8\\x0e\\xe1\\xd3\\x17\\x98\\xf0\\x96\\x0a\\x42\\x6e\\x93\\x27\\x76\\x14\\xe7\\xad\\xf1\\x19\\x7d\\x80\\xaa\\xcb\\xa4\\xbd\\x17\\x3a\\xe7\\x3f\\xe1\\x68\\x67\\x94\\xd7\\xab\\xba\\xaa\\xab\\xb0\\xc8\\xa6\\x50\\xae\\x5b\\xe3\\x97\\x00\\xf8\\xee\\x10\\xbe\\x52\\xbc\\x63\\xb1\\xf2\\x5b\\xf3\\x95\\x8c\\x6f\\x5c\\xb1\\x00\\xfe\\xac\\x18\\x37\\x10\\x93\\x62\\xd5\\x14\\x19\\x0a\\x72\\x6a\\xc5\\x20\\x43\\x4d\\xc0\\x2d\\xe7\\x54\\x2a\\x84\\xa7\\x47\\x02\\xc0\\x53\\xbc\\x6f\\x44\\x09\\x0a\\x86\\x54\\x11\\x8e\\x1e\\x0b\\x97\\x02\\x8e\\xe0\\x6a\\xcd\\x3c\\xf6\\xbd\\xdf\\x87\\x2a\\x4a\\xe2\\x8e\\x8f\\xd7\\x54\\x2c\\x8f\\x02\\x40\\x02\\x63\\x59\\xc8\\xa0\\x0c\\xc8\\x4e\\xdc\\xdb\\x84\\x7f\\x2d\\x7d\\x78\\xff\\xa2\\x2e\\x17\\x35\\xc8\\xa1\\x13\\x51\\xaf\\x69\\xdd\\x11\\x23\\x9f\\x31\\x3a\\x71\\x70\\xee\\x81\\xe7\\xc4\\x99\\xaa\\x6b\\x93\\x32\\x29\\x9c\\xc3\\xcf\\xf3\\xd2\\xbf\\xa9\\xfc\\xe5\\x04\\x1e\\xd4\\xa0\\x06\\xd9\\xa2\\xc5\\x1b\\x45\\xb4\\x82\\xcc\\x5a\\xfd\\xd7\\x40\\x4f\\x41\\xcf\\xc8\\xbd\\x4f\\x73\\x92\\x29\\xfa\\x87\\xbc\\x0c\\x83\\x53\\x39\\xce\\x22\\x68\\xda\\xec\\x63\\x39\\xb6\\xd9\\xf7\\x3e\\xbe\\x0c\\x74\\xab\\x2e\\x15\\x16\\x00\\x00\\x18\\x34\\xf9\\x11\\x4c\\x34\\x37\\x79\\x6a\\xb1\\xb8\\x7c\\xf4\\xc4\\x95\\xd5\\x5a\\x5e\\x65\\xaf\\x37\\x5b\\xf1\\xff\\xc5\\x10\\xbf\\x03\\x00\\x00\\xff\\xff\\x70\\x60\\x1a\\xdb\\x94\\x02\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/NOTES.txt\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"NOTES.txt\",\n\t\t\tmodTime:          time.Date(2025, 4, 24, 21, 9, 23, 549666844, time.UTC),\n\t\t\tuncompressedSize: 229,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\x4c\\xce\\xc1\\x4d\\x03\\x31\\x10\\x85\\xe1\\xbb\\xab\\x78\\x05\\x20\\x2c\\x71\\xcc\\x85\\x06\\xc2\\x2d\\xe2\\x6e\\x79\\xdf\\xc6\\x96\\xbd\\x9e\\x68\\x66\\xcc\\x26\\x25\\x70\\xa1\\x05\\x5a\\xa4\\x04\\x14\\x29\\x87\\x14\\xf0\\xff\\xfa\\x4e\\x85\\x38\\xd6\\xd1\\xa8\\x0b\\x3e\\x66\\xf7\\x9a\\xfb\\x34\\xa7\\x82\\x57\\xe7\\xb0\\x2a\\x03\\x7b\\x32\\xd8\\xcc\\x99\\x66\\xeb\\xec\\xfd\\x86\\x3a\\xcc\\x53\\xef\\x5c\\xf0\\xf7\\xfb\\xf3\\x1d\\xc2\\x49\\xb0\\xa5\\x46\\xd8\\x54\\x82\\x5f\\xd4\\x9b\\x97\\x3a\\xce\\xd8\\x45\\x9b\\x21\\x19\\x78\\xbd\\x30\\x3b\\x97\\x17\\xe8\\x1c\\xf0\\x42\\xac\\xd2\\xbb\\xec\\x75\\x9c\\x0f\\x21\\x00\\xfd\\x01\\xd8\\x9e\\x01\\xb9\\x30\\xb7\\x10\\x8e\\x22\\xed\\x3e\\x5b\\x45\\xb1\\x89\\xf2\\x1d\\x9f\\xd5\\xaa\\xa3\\xb8\\x5f\\xec\\x10\\xe3\\xa3\\x7d\\xad\\x12\\xdf\\xe2\\xca\\xe4\\x53\\x69\\xf1\\xf9\\x14\\xc3\\x7f\\x00\\x00\\x00\\xff\\xff\\xda\\x7a\\x9d\\xb4\\xe5\\x00\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/README.md\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"README.md\",\n\t\t\tmodTime:          time.Date(2025, 12, 12, 18, 49, 15, 368996883, time.UTC),\n\t\t\tuncompressedSize: 8128,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xa4\\x59\\xff\\x73\\xdb\\x36\\xb2\\xff\\x9d\\x7f\\xc5\\xbe\\xe6\\x87\\x26\\x99\\x88\\xb2\\xdd\\xbe\\x99\\xf7\\x7c\\xd7\\xde\\xb9\\x49\\x93\\xb8\\x4d\\x72\\x3a\\xdb\\xed\\xe4\\xc6\\xe3\\x39\\x43\\xc4\\x52\\x42\\x0d\\x62\\x59\\x00\\x94\\xaa\\x3b\\xdd\\xff\\x7e\\xb3\\x00\\x48\\x91\\x16\\x15\\xe7\\xdb\\x64\\xc6\\x14\\xb1\\xfb\\xd9\\xc5\\x62\\xbf\\x82\\x8f\\x40\\x2b\\x73\\x87\\x56\\x4e\\xaa\\x46\\x7b\\x55\\xe8\\xc6\\x79\\xb4\\x59\\x76\\xb5\\x44\\x78\\x93\\x56\\xde\\xf6\\x56\\x00\\xff\\xf0\\x68\\x9c\\x22\\x03\\x05\\x19\\x2f\\x94\\x71\\x60\\xd1\\x51\\x63\\x0b\\x74\\xe0\\x09\\x5c\\x53\\xd7\\x64\\x3d\\x0c\\xe0\\x58\\x86\\x32\\x0b\\x5e\\xb7\\x58\\x91\\x47\\x48\\x2b\\x2e\\xcb\\xfe\\xe7\\xfa\\x57\\xb4\\x0c\\x78\\x0a\\x47\\xf9\\x51\\x7e\\x34\\x69\\x8c\\xc4\\x52\\x19\\x94\\x37\\x8f\\x97\\xde\\xd7\\xee\\x74\\x3a\\x55\\xd5\\x22\\x77\\x4b\\x85\\x5a\\xba\\x5c\\xd1\\x74\\x2e\\xe4\\x02\\xa7\\x89\\x6d\\x12\\xb9\\x76\\x6c\\x13\\x65\\x4a\\xb2\\x95\\xf0\\x8a\\x8c\\xd0\\x7f\\x71\\x7e\\xa3\\xf1\\xbb\\x52\\x0b\\x3f\\x71\\xbf\\x37\\xc2\\xe2\\x13\\x96\\x79\\x56\\xd7\\x9d\\x58\\x94\\x0b\\x9c\\xbc\\x7f\\x9f\\xbf\\xcf\\xdf\\x3f\\x20\\x72\\xc7\\x35\\x09\\x4c\\x89\\xeb\\x23\\x24\\x3e\\x7d\\xfa\\x9a\\x2a\\xac\\xc5\\x02\\x4f\\x9f\\x3e\\x85\\x3f\\xb7\\x52\\x92\\xf1\\x73\\x45\\xdf\\x67\\xd9\\xa3\\x47\\xf0\\xf7\\x46\\x15\\x77\\xce\\x0b\\xeb\\x41\\x18\\x09\\x92\\x8a\\xa6\\x42\\xe3\\x03\\x72\\x96\\xfd\\x83\\x1a\\x28\\x84\\x01\\xdb\\x98\\xf6\\x70\\x80\\x0c\\x08\\xb3\\x81\\x9f\\x9b\\x39\\x5a\\x83\\x1e\\x5d\\x6b\\x5a\\x50\\x06\\x04\\x54\\xc2\\xf3\\x33\\x95\\xe0\\xb0\\x20\\x23\\x5d\\x0e\\x97\\x88\\xe0\\x97\\x98\\x5d\\xb7\\x10\\xaf\\xd0\\x7b\\x3e\\x9d\\x4b\\x96\\x8b\\x12\\x5e\\x35\\x4a\\xe2\\xcd\\xf5\\x22\\xbe\\x9e\\xb8\\xf8\\xfa\\x06\\x4a\\xb2\\xb0\\xa4\\x75\\x9e\\x65\\x2f\\xc9\\x42\\x45\\x16\\xa1\\xa0\\xaa\\xb6\\xb8\\x64\\x8f\\x58\\xe1\\x50\\xdd\\x67\\x10\\xf7\\xb1\\x56\\x7e\\xc9\\xf2\\xa0\\x95\\x97\\x49\\x2a\\xdc\\xcd\\x75\\xeb\\x77\\xe1\\x57\\x1e\\x76\\x3f\\xb3\\x68\\xf1\\xf7\\x46\\x39\\xe5\\xf1\\xb4\\xdb\\xe1\\x73\\x16\\xf4\\x9c\\x8c\\xb7\\xa4\\x27\\x33\\x2d\\x0c\\x66\\xd9\\x0f\\x58\\xf2\\x5b\\x65\\x9c\\x17\\x5a\\x07\\xd7\\xda\\x39\\x2c\\x8c\\x3b\\xec\\x33\\x60\\xa7\\x2e\\xa2\\xda\\x11\\xad\\x66\\x34\\x58\\x0a\\x97\\x79\\x82\\x79\\x87\\x87\\x12\\x4a\\x65\\x9d\\x87\\xf9\\x06\\x4a\\xd2\\x9a\\xd6\\xad\\x84\\xce\\x66\\xe7\\x91\\x32\\x8b\\xb6\\x7a\\xbc\\x7f\\x9e\\xd3\\x93\\xa9\\x17\\xee\\xce\\x4d\\x13\\xe6\\xf4\\x49\\xdc\\xe3\\x99\\x94\\x0c\\x96\\x70\\xbe\\x76\\xf0\\x1a\\x75\\x05\\x16\\x6b\\x72\\xca\\x93\\xdd\\x64\\xd9\\xed\\xed\\xed\\x5c\\xb8\\x65\\xf6\\x08\\xae\\x08\\x84\\x94\\x41\\x30\\x13\\x84\\x03\\x68\\x15\\x60\\xff\\x03\\x8b\\x1a\\x85\\x43\\x77\\x9a\\x2d\\x5b\\x94\\xc0\\x91\\xd4\\x80\\x56\\x2d\\x5e\\xcd\\x7b\\xba\\x31\\x33\\xcb\\x09\\x1a\\x9d\\x0f\\x8d\\x38\\x30\\xde\\x8f\\x5d\\xb4\\x3f\\x5f\\x0a\\xeb\\x77\\xca\\x05\\x79\\x69\\x6b\\xa3\\x29\\x04\\x26\\xe6\\xc0\\xfb\\x49\\x61\\x51\\x78\\x9c\\x18\\x51\\xa1\\xab\\x45\\x81\\x2d\\xdd\\x74\\x34\\x15\\xb5\\x6a\\xbe\\x42\\x0f\\xca\\xac\\x48\\xaf\\x50\\x66\\xd9\\x53\\x78\\xbe\\xc4\\xe2\\x0e\\xa8\\xf1\\x3d\\x5b\\xc6\\x3c\\x04\\x05\\x49\\x04\\xe1\\xe1\\xfa\\x95\\xf2\\xaf\\x9b\\x79\\xe7\\x6b\\x27\\x37\\x79\\xf6\\x14\\x7e\\x22\\x65\\x7a\\x3c\\xd7\\x8d\\x43\\x0b\\x95\\x50\\xc1\\x02\\x5a\\x39\\xbf\\xf3\\x4d\\x5e\\x72\\x37\\xcf\\xe0\\x5a\\xe2\\x0a\\x35\\xd5\\x3b\\xc2\\x0c\\xee\\x91\\x4a\\x5c\\xdd\\x3c\\x0b\\x41\\x7b\\x2d\\x8c\\xa1\\xc6\\x14\\xc8\\xa1\\xe0\\x0e\\x20\\xb7\\x34\\x41\\xa3\\x97\\xc1\\xc7\\xe0\\xfa\\xaf\\x69\\xf5\\xe6\\xda\\xaf\\x15\\xc7\\xed\\x0d\\x87\\xf7\\x55\\x7c\\xee\\x54\\x1f\\xf8\\xe1\\xa5\\x16\\xc5\\xdd\\xcd\\xb5\\x0b\\x7f\\xf2\\x2c\\xdb\\x8b\\xda\\x53\\x18\\xf5\\xcd\\x7b\\x64\\xd3\\x6c\\x67\\xa3\\x1d\\xc7\\x42\\xf9\\x65\\x33\\xcf\\x0b\\xaa\\xa6\\xf7\\x8e\\xe8\\x24\\xdb\\xdf\\x49\\x5f\\x92\\xf3\\x2e\\x2f\\x4c\\x51\\xb2\\xb0\\xc5\\x94\\x9f\\x26\\xf7\\xe9\\xb3\\x81\\xe9\\x3e\\x92\\x59\\xe2\\x2a\\x1b\\x66\\x8e\\x03\\xfb\\xa3\\x15\\xda\\x95\\xc2\\xf5\\x6e\\x63\\xe9\\x30\\x3f\\x52\\x50\\x20\\xce\\x92\\x5d\\x23\\xcf\\xe9\\x74\\x1a\\x7e\\xf6\\x02\\x29\\xeb\\x0e\\x6a\\x07\\x9b\\xde\\xf4\\xad\\x16\\xfc\\xf7\\x82\\x33\\x9b\\x8d\\x4e\\x91\\x65\\xbb\\x7c\\x7d\\x0a\\xb7\\xdf\\x7f\\x77\\x9c\\x9f\\x9c\\xe4\\x47\\x93\\xa3\\xdb\\x2c\\xdb\\xc2\\x45\\x97\\x0c\\x60\\x0b\\xef\\x44\\x85\\xb0\\x85\\x54\\x76\\x60\\x9b\\x6d\\x27\\xbd\\x7f\\xdb\\xc1\\x1f\\x7e\\xca\\xb6\\x50\\x2a\\x8d\\xa7\\xd3\\x69\\x9e\\xa7\\xff\\x05\\x87\\xae\\x9b\\xd6\\xc2\\x7a\\x25\\xb4\\x83\\x2d\\xf4\\x1e\\x8f\\xf2\\xe3\\xfc\\x08\\xb6\\x41\\xc5\\x5f\\x85\\x6e\\xd0\\xb1\\x0a\\x3f\\x23\\xcb\\xbe\\xda\\xd4\\x2c\\xfb\\x05\\x96\\xa2\\xd1\\x3e\\x3c\\xb9\\xc2\\xaa\\xda\\xf7\\x35\\xd9\\x57\\x61\\x32\\xd0\\x30\\xdb\\x72\\x95\\xa8\\xc8\\xbc\\x11\\x73\\x0c\\x22\\x69\\xfe\\x1b\\x16\\x0c\\x77\\xfb\\xef\\xff\\xdc\\xc2\\x16\\xd2\\x82\\x27\\x10\\x75\\xad\\x37\\xe1\\x41\\xeb\\x5e\\x5b\\x11\\x30\\x42\\xd2\\x78\\xd7\\xe6\\x8c\\xb7\\xe8\\x85\\x14\\x5e\\xfc\\x44\\x73\\xd8\\xc2\\x9c\\x48\\x33\\x9e\\xb7\\x0d\\x32\\xe2\\xf3\\x40\\xec\\x40\\x00\\xaf\\xfb\\xa5\\xf0\\x9c\\x16\\x1d\\x18\\x2c\\xd0\\x39\\x61\\x37\\x50\\x25\\x7e\\x16\\xc6\\x01\\xd5\\x55\\x89\\xaf\\x1d\\xec\\x12\\x93\\x6c\\x2c\\x87\\x6e\\xca\\x73\\x7f\\x02\\xa9\\x9c\\x98\\x6b\\x04\\x55\\x02\\x7b\\x02\\x97\\xd5\\xda\\xaa\\x95\\xd2\\xb8\\x40\\xee\\x83\\xc2\\x09\\x83\\xa4\\x98\\x4b\\x15\\x07\\xbf\\x69\\x84\\xd6\\x9b\\xb0\\x05\\x34\\xcc\\x3c\\xbb\\x9c\\xf5\\x34\\x2e\\x85\\x76\\x3d\\x95\\xe1\\x82\\x34\\xeb\\x6d\\x64\\x78\\xfa\\x41\\x19\\xae\\x16\\xd1\\x36\\xce\\x51\\xa1\\x98\\x26\\x20\\xf7\\x15\\xbe\\x64\\x5f\\x2f\\xf0\\xac\\x28\\xa8\\xe1\\x94\\x93\\xf6\\x94\\xaa\\x1c\\xc4\\x2a\\xc7\\x72\\x5b\\x93\\xe6\\x70\\xc5\\x18\\x49\\x61\\x17\\x2d\\x74\\x1b\\xf5\\x93\\xb3\\xcb\\xd9\\x2d\\x28\\x07\\x0e\\x7d\\x40\\xb2\\x0d\\x72\\x1a\\xda\\x47\\x4c\\x66\\xc9\\xe1\\x1d\\x77\\x74\\x8c\\xbf\\x14\\x0e\\xe6\\x88\\x06\\x24\\xd6\\x16\\x0b\\xc1\\xdd\\x84\\x53\\xa6\\x40\\xb8\\xfb\\x3f\\x07\\xab\\xe3\\xfc\\xe4\\xb8\\x6f\\x09\\x92\\x67\\xc6\\xab\\xb3\\xb2\\x54\\x46\\xf9\\xcd\\x98\\x55\\x7e\\x0c\\x84\\x0e\\x66\\x24\\x81\\x69\\xa1\\x23\\xd6\\xb4\\x50\\x05\\xeb\\x37\\x17\\x5a\\xb0\\x04\\xd6\\xaf\\xd6\\x22\\xa6\\x5d\\x3e\\x19\\x8b\\xb5\\x56\\x85\\x70\\x20\\x0a\\x4b\\xce\\xc1\\x92\\x9c\\x8f\\xa6\\xfd\\x17\\x19\\x74\\xa1\\x9a\\xbe\\x56\\x8b\\x25\\x9c\\xad\\x84\\xd2\\x62\\xae\\xb4\\xf2\\x9b\\x3c\\x89\\x8c\\x46\\x26\\xa3\\x37\\xb0\\x5e\\xa2\\x81\\x0d\\x35\\xb0\\x14\\x2b\\x8c\\x7d\\x6d\\xad\\x71\\x07\\x4f\\x65\\x68\\x83\\xc8\\x70\\x64\\xe7\\x61\\x7f\\x0b\\xe1\\x71\\x2d\\x36\\xf9\\xab\\xf3\\x17\\xb0\\x05\\x65\\x82\\xb3\\x9f\\x1c\\x1f\\x7d\\xc3\\x7b\\x7a\\x65\\xa9\\xa9\\x41\\x49\\xe0\\x8e\\xd5\\xc2\\x7a\\xa9\\x8a\\xd8\\x20\\x25\\x2e\\x70\\x4b\\x76\\xff\\x39\\x82\\x15\\x66\\x00\\xf7\\xcb\\x18\\xdc\\x2f\\x5c\\xbe\\x3e\\x07\\x4d\\x62\\xad\\x69\\xc3\\xd6\\x3a\\x33\\x86\\x62\\xd3\\x36\\x16\\x9e\\xfd\\x55\\x9f\\x3a\\x12\\x1a\\x88\\xd8\\x21\\x0d\\x04\\x24\\x7f\\x1a\\x89\\xcf\\xf3\\x72\\xc0\\xdf\\x99\\x0f\\xdc\\x92\\x1a\\x2d\\x87\\xad\\x58\\x1f\\x52\\x93\\x90\\x3f\\xc4\\x03\\xb7\\xcf\\xb5\\x70\\xac\\xaf\\xf3\\x21\\x48\\xb7\\x70\\xfb\\xd5\\x57\\x0c\\x7e\\x89\\x1e\\xf6\\xe9\\xc8\\xec\\x2c\\x12\\xc3\\xe5\\x20\\xf0\\xf9\\xec\\x23\\x50\\xcf\\x67\\x9f\\x02\\x79\\x19\\xc2\\xee\\x42\\x18\\xce\\x13\\xdb\\xd0\\x0b\\x30\\xf4\\xf5\\xcd\\x18\\xf4\\x80\\xf8\\x01\\x21\\x26\\x96\\x87\\x9e\\xb6\\x6d\\x05\\x4b\\x14\\x41\\x79\\x6e\\x7c\\x03\\x25\\x0d\\x0d\\x1f\\xe2\\x7e\\xad\\xa2\\x7f\\x8c\\x5b\\xdc\\x90\\xc4\\x4b\\xd4\\x58\\x78\\xb2\\x23\\xce\\xf1\\x8e\\x5b\\x2d\\x97\\xd6\\x63\\x4c\\xf5\\x05\\xd4\\x34\\x44\\xab\\x45\\xe3\\xf0\\xbc\\x12\\x8b\\x7b\\x4a\\x2f\\x0a\\x1b\\x8a\\x30\\xd1\\x42\\xe3\\x3f\\xd3\\x64\\x89\\x96\\x8b\\x56\\xe3\\xf0\\xf4\\x9b\\xfc\\xa4\\xdb\\x47\\x78\\x03\\x1d\\x09\\xfb\\x22\\xbf\\x18\\x48\\xe1\\x01\\xb4\\x0b\\x94\\x6f\\x8f\\xbf\\xfd\\xa6\\x63\\xe6\\x15\\x32\\x29\\x4c\\x38\\x30\\xfa\\xda\\x06\\x4b\\x88\\xa2\\xc0\\x9a\\x5b\\xcd\\x82\\xaa\\x90\\xc7\\xad\\x28\\x4b\\x55\\x0c\\x05\\x58\\x9a\\x63\\x5e\\x0b\\xbf\\x1c\\x6e\\x63\\x6a\\x51\\xc8\\x4d\\x4f\\xd5\\x30\\xfa\\xf4\\x6c\\xdc\\x38\\x94\\x3c\\x55\\xdc\\x9b\\x82\\x83\\xdd\\x24\\x7a\\xb4\\x95\\x32\\x0c\\xb6\\x5e\\xa2\\x5f\\xe2\\xd0\\x96\\xca\\x81\\xd0\\x3c\\x66\\x8d\\x68\\x72\\x6f\\xc3\\xff\\x7f\\x3c\\xd8\\x70\\x90\\xca\\x22\\x98\\x5d\\x2b\\x83\\xce\\x01\\x33\\x06\\xb5\\xf7\\xc0\\xd2\\xa0\\xb8\\xc3\\xeb\\xac\\xa7\\x8c\\x47\\xbb\\x12\\x1a\\x1e\\x2b\\xd3\\xce\\x93\\x4f\\x60\\x8e\\x7e\\xcd\\x39\\x9f\\xc1\\x3b\\xe8\\x54\\xb5\\x5b\\xe4\\x2e\\x59\\x76\\xa0\\x41\\xc3\\x77\\x4d\\x35\\x8f\\xd3\\x69\\x47\\xf0\\x90\\x07\\xa5\\x20\\xf8\\xd2\\x94\\x35\\x16\\x4b\\xe9\\x1d\\xcf\\x3c\\xd6\\x08\\x7d\\x15\\x0f\\x7e\\x46\\x5a\\x15\\x9b\\x03\\x19\\x01\\x47\\x69\\x1f\\x88\\xd9\\xf4\\x2e\\x75\\x57\\x3d\\xd8\\x37\\xbd\\x24\\x90\\x44\\x44\\xee\\x40\\x4a\\x65\\x87\\x7a\\x39\\x82\\x1a\\x9d\\x27\\x6c\\xfa\\x95\\x15\\x05\\xce\\xd0\\x2a\\x92\\x97\\xdd\\x61\\x8e\\xa9\\xff\\x61\\x9e\\xde\\x36\\x0e\\x64\\x78\\x4f\\x1a\\x6d\\x77\\x0a\\xc3\\xac\\x76\\xd5\\x5b\\x3b\\x74\\xa8\\x4a\\xa2\\xf1\\xca\\x6f\\xae\\x6c\\xe3\\xfc\\x0b\\xaa\\x84\\x32\\x43\\x4d\\x53\\x80\\xe4\\x9a\\x0a\\xa1\\x83\\xda\\xe7\\x89\\x05\\x02\\x0f\\x24\\xa6\\x94\\xd7\\x0a\\xb4\\x5e\\x95\\x8a\\x1b\\x0f\\x10\\x8d\\x5f\\x92\\x0d\\x7d\\x05\\x4b\\xe2\\x9c\\x33\\x6b\\xb4\\x1e\\x3b\\xce\\xf3\\xf2\\x1d\\xf9\\x99\\x45\\x87\\xc6\\x07\\x21\\x2f\\xa8\\xb8\\xe3\\xaa\\x7a\\x8f\\x89\\xb7\\xc1\\x39\\x63\\x30\\xcf\\xee\\x6a\\xff\\x50\\xd0\\x25\\x16\\x16\\xfd\\xbe\\x5d\\x5e\\x92\\x85\\x99\\x55\\x2b\\xd6\\x51\\x46\\x39\\x16\\x17\\x8a\\xd5\\x41\\xf7\\x2c\\xa8\\xcd\\x3b\\x2c\\x82\\xe9\\x38\\xe8\\x0d\\xa2\\x44\\x99\\x03\\x5c\\x44\\x32\\x76\\xaa\\x08\\x2d\\x2c\\x86\\x96\\x59\\x61\\xe7\\xdf\\x16\\x5d\\x8d\\x85\\xe7\\x34\\xd1\\xba\\x9e\\x68\\xdb\\x42\\x56\\x2f\\x55\\x87\\xae\\x8f\\x1e\\xad\\x1d\\xc1\\x06\\x3b\\x12\\x2a\\xbb\\x0b\\x85\\x54\\x23\\x44\\x3b\\x01\\xb4\\x0b\\xdd\\x78\\x32\\x82\\x96\\xd6\\xc2\\x70\\x11\\x90\\x9f\\x0f\\xba\\xc8\\xd5\\x6e\\xb2\\x81\\x70\\xcc\\xc9\\xb9\\xdf\\x2a\\x6b\\xc9\\x7e\\x62\\x0b\\xd5\\x06\\x46\\x64\\xde\\xef\\x7d\\x46\\x04\\x7c\\x52\\x53\\xf5\\x19\\xf8\\xa9\\xd7\\xad\\x2d\\x95\\x63\\x1d\\x2e\\xa6\\x0e\\x97\\xd1\\xb9\\x8e\\xf1\\x48\\x11\\x68\\xd1\\xc8\\x9a\\x14\\x9f\\x5b\\xba\\x6b\\xdd\\x99\\xac\\x73\\xb9\\xaf\\x1d\\x08\\x59\\x85\\x64\\x6c\\xd9\\x8e\\x87\\x74\\x28\\x51\\x72\\x2c\\xa2\\x4c\\xaf\\x7b\\x05\\xbd\\x77\\x5e\\x55\\x24\\xee\\xcd\\xd1\\x1d\\xdf\\x77\\x15\\x72\\xa6\\xfe\\xaa\\x1b\\xd7\\xba\\x9a\\x1f\\x82\\xa2\\xa3\\xeb\\x9c\\x2e\\xd2\\x3b\\x48\\xf7\\x15\\x41\\xa7\\xb6\\xdc\\xe5\\x87\\xd4\\x0c\\xb1\\x33\\xd2\\xd2\\x14\\x36\\xd7\\xff\\x1b\\x14\\x6a\\xaf\\x21\\x92\\x3d\\x74\\x52\\xa9\\x1f\\xaf\\x5d\\xb2\\x69\\xcf\\x2a\\x6e\\xab\\xd7\\x64\\x3e\\x6e\\x5c\\x32\\x78\\x7b\\x8b\\xb2\\x83\\x8b\\x18\\x4f\\x3e\\xac\\x61\\x3d\\x9a\\x47\\xf6\\x53\\x0c\\xff\\x80\\x7a\\x97\\x3b\\x46\\xb5\\x6a\\xfb\\x98\\xa8\\xfc\\x07\\xe5\\xae\\xf6\\xa3\\x6c\\x2f\\xfe\\xae\\xc4\\xe2\\x0b\\x65\\x69\\x5a\\xbc\\x0c\\xb7\\xd9\\xc3\\x43\\xa8\\xb5\\x50\\x26\\x3a\\x00\\x05\\x11\\x4c\\xf1\\xf8\\x36\\xbc\\xbe\\x05\\xb2\\x70\\xfb\\x9b\\x23\\x73\\xfb\\xe4\\x03\\xb0\\x6f\\x70\\x85\\x7a\\x88\\xaa\\x4c\\x49\\x1d\\xa8\\x0e\\xeb\\xad\\xf6\\x6f\\x3f\\x90\\x65\\x47\\xf0\\x1f\\x6c\\x31\\xa2\\x0f\\xba\\xa1\\x4d\\x3a\\x2e\\x4f\\xe1\\x5a\\xfd\\x20\\x78\\x77\\x17\\xb1\\xd7\\x6c\\x5c\\x74\\x6b\\x0f\\xda\\xfd\\x00\\xba\\xeb\\xff\\xba\\x40\\x6f\\x37\\x6f\\x54\\xa5\\xfc\\xbd\\x0e\\x6c\\xb7\\x13\\xaf\\x2a\\x74\\xe3\\xfb\\x69\\x6a\\x19\\x6f\\x3e\\xb8\\x34\\x68\\x4d\\xeb\\x58\\x1a\\x38\\x45\\xe1\\xef\\x0d\\x36\\x28\\xe1\\xb1\\x45\\xae\\x35\\x32\\x9e\\x94\\xb9\\x7f\\xa3\\x72\\x30\\x0a\\xbb\\x1b\\x87\\x09\\x4f\\xe0\\xfb\\x71\\x17\\x38\\x5a\\x13\\x74\\xb0\\x93\\xee\\xa6\\x25\\xd4\\x0e\\x93\\xba\\x96\\x43\\x62\\xbf\\x24\\xb4\\x3e\\x57\\xa6\\x6d\\xeb\\xea\\x87\\x93\\x4e\\x7f\\xc7\\x1d\\xcb\\x97\\xca\\xf6\\x62\\x31\\x14\\xbb\\x3a\\xca\\x8f\\xf3\\xe3\\x7d\\xeb\\xfa\\x5e\\x5c\\x7f\\xba\\xb0\\xcf\\x9b\\xe2\\x3e\\x5d\\xce\\xa7\\x36\\x84\\x0f\\x49\\xa8\\x49\\x3e\\xd0\\xe9\\x4b\\xa9\\xe2\\x67\\x37\\x10\\xa3\\x4d\\xbf\\x08\\x7e\\x22\\x5d\\x0b\\x77\\xf0\\x16\\xb2\\x87\\xa4\\x77\\x17\\x92\\x63\\x20\\x96\\xfe\\xd8\\xfc\\xad\\xf1\\x73\\x6a\\x8c\\x9c\\xdd\\x1f\\x35\\x8f\\xc6\\x47\\xcd\\x70\\x3b\\xc5\\x8c\\x69\\xbe\\x74\\x40\\x09\\x61\\x30\\x60\\xc6\\xb9\\x30\\x26\\x82\\xe1\\xad\\xde\\xe1\\x5b\\x94\\x34\\x4b\\xa6\\x04\\x70\\xaf\\xeb\\x3b\\x78\\xa3\\x72\\x58\\xd2\\xbb\\x83\\x57\\x0a\\x09\\x7b\\x12\\x45\\x4d\\x22\\xc4\\x84\\x37\\xe4\\xdc\\x44\\xc6\\x5b\\xe2\\xd1\\xfb\\x86\\xfb\\x4a\\x85\\x81\\x34\\xda\\x95\\xd6\\x7b\\xc3\\xb0\\xa7\\x76\\x33\\x83\\x14\\xe7\\x92\\xde\\x2b\\xc5\\x49\\xe8\\xb5\\x72\\x9e\\xf6\\x12\\xe5\\x71\\x30\\xff\\x65\\x8d\\x85\\x2a\\x55\\x2a\\xf2\\xa6\\xcb\\x9b\\xa4\\x25\\x5c\\xc4\\x84\\x7f\\x89\\xf1\\x9e\\xd4\\x22\\xa7\\xe6\\x9e\\x2e\\xa4\\xf5\\x5c\\x14\\x77\\xdc\\xa3\\x64\\x93\\x4f\\xfa\\x97\\x9d\\x35\\x9e\\x16\\x68\\x52\\x27\\x54\\x5a\\xaa\\x20\\x5c\\xbd\\xef\\x2e\\x9b\\x1b\\xc7\\x26\\xbd\\x5e\\xa2\\xae\\xc2\\xd7\\x0b\\x58\\x1d\\xe7\\xc7\\x27\\xf9\\xd1\\xee\\x03\\x62\\xef\\x93\\x8b\\x21\\xbb\\x26\\x92\\xbf\\x4d\\x3b\\xf2\\x69\\xfb\\xbd\\x6f\\x9a\\xf8\\x9e\\x64\\xff\\x0d\\x00\\x00\\xff\\xff\\x68\\xea\\xd8\\x90\\xc0\\x1f\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/README.md.gotmpl\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"README.md.gotmpl\",\n\t\t\tmodTime:          time.Date(2025, 4, 24, 21, 9, 23, 549666844, time.UTC),\n\t\t\tuncompressedSize: 1852,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\x8c\\x55\\x4d\\x6f\\xe3\\x36\\x10\\xbd\\xf3\\x57\\x0c\\x36\\x87\\xb6\\x8b\\x48\\x02\\x72\\xf4\\xa9\\xdd\\xa0\\xc9\\x6e\\xbb\\x05\\xb6\\x48\\x50\\xa0\\x10\\x04\\x84\\x26\\xc7\\x16\\x61\\x8a\\xa3\\x92\\x43\\xb9\\xc1\\x62\\xff\\x7b\\x41\\xea\\xc3\\x76\\x62\\x77\\x73\\xb2\\x48\\xbe\\x99\\xf7\\xe6\\x71\\xc6\\xfc\\xfa\\x15\\x18\\xbb\\xde\\x4a\\x46\\x78\\xa7\\x5a\\xe9\\xb9\\x6c\\x51\\x6a\\xf4\\xef\\xa0\\x84\\x6f\\xdf\\xc4\\x99\\x73\\x8d\\x41\\x79\\xd3\\xb3\\x21\\x37\\x81\\xce\\xa1\\x06\\xf4\\xc1\\x90\\xfb\\x20\\xf5\\x16\\x2f\\xe7\\xe2\\xe7\\x1e\\xbf\\x03\\x91\\x7d\\xff\\xd7\\xeb\\x5c\\xe7\\x90\\x2d\\x75\\xd8\\xcb\\x2d\\x7e\\x36\\x6e\\x81\\x5d\\x5d\\xc1\\x9f\\xd1\\xa8\\x5d\\x60\\xe9\\x19\\xa4\\xd3\\xa0\\x49\\xc5\\x0e\\x1d\\xcb\\x54\\x80\\x10\\x7f\\x53\\x04\\x25\\x1d\\xf8\\xe8\\xe0\\xb3\\x71\\x3b\\xf4\\x1a\\xc8\\x81\\x74\\xcf\\xf0\\x7b\\x5c\\xa3\\x77\\xc8\\x18\\x40\\xd9\\x18\\x18\\x3d\\x18\\x07\\x12\\x3a\\xc9\\xe9\\x9b\\x36\\x10\\x50\\x91\\xd3\\xa1\\x84\\x07\\x44\\xe0\\x16\\x45\\x3d\\xa7\\xb8\\x47\\x66\\xe3\\xb6\\xf0\\x90\\x78\\x51\\xc3\\x7d\\x34\\x1a\\x9b\\x7a\\x3b\\x6e\\x17\\x61\\xdc\\x6e\\x60\\x43\\x1e\\x5a\\xda\\x97\\x42\\xdc\\x91\\x87\\x8e\\x3c\\x82\\xa2\\xae\\xf7\\xd8\\xa2\\x0b\\x66\\xc0\\x53\\xb9\\xd7\\x30\\xd6\\xb1\\x37\\xdc\\x26\\x3e\\x98\\xf9\\x84\\x26\\x15\\x9a\\xda\\x8e\\xab\\x22\\xaf\\xca\\x5c\\xfd\\x17\\x8f\\x1e\\xff\\x89\\x26\\x18\\xc6\\xd5\\x52\\xe1\\x6d\\x22\\xba\\x25\\xc7\\x9e\\x6c\\xf1\\xc5\\x4a\\x87\\x42\\x7c\\xc0\\x4d\\xda\\x35\\x2e\\xb0\\xb4\\x36\\x89\\x4f\\x0c\\x73\\xc4\\x1f\\xd1\\xb2\\x99\\x6d\\xc0\\x7f\\x39\\xc9\\x4b\\x82\\x1e\\xdb\\xa4\\x38\\xcb\\x1e\\xb3\\xf5\\x29\\x1b\\xb4\\x32\\x08\\x26\\x58\\x2f\\xf9\\x50\\xc3\\xc6\\xf8\\xc0\\xb0\\x7e\\x86\\x0d\\x59\\x4b\\xfb\\x99\\x61\\xf1\\xec\\xd3\\x88\\x14\\xa3\\x57\\x3f\\xb6\\xcc\\x7d\\x58\\x55\\xd5\\x54\\x54\\x69\\xa8\\xba\\xa9\\x58\\x86\\x5d\\xa8\\xa6\\x9c\\xd5\\x4f\\x63\\x8d\\xbf\\x68\\x9d\\x92\\x4d\\x79\\x7e\\x08\\xf0\\x11\\x6d\\x07\\x1e\\x7b\\x0a\\x86\\xc9\\x3f\\x0b\\xf1\\xf4\\xf4\\xb4\\x96\\xa1\\x15\\x57\\xf0\\x48\\x20\\xb5\\xce\\xc4\\x09\\x90\\x2f\\x60\\x16\\x80\\x7a\\x9b\\x76\\x2d\\xca\\x80\\x61\\x25\\xda\\x39\\x4b\\x8e\\x98\\x64\\xc0\\x2c\\x2b\\x9d\\x96\\x47\\xda\\x52\\x70\\xe2\\xc9\\x8a\\x3e\\x9d\\x9a\\x78\\x62\\xde\\xaf\\xb3\\x79\\x70\\x9b\\xda\\xf6\\x20\\x2e\\xf3\\x4d\\xa5\\xcd\\x74\\x45\\x77\\x1c\\x5a\\xb8\\x0b\\xfb\\x85\\xf2\\x28\\x19\\x0b\\x27\\x3b\\x0c\\xbd\\x54\\x38\\xe3\\xaa\\x73\\xf8\\x45\\xe6\\x3d\\x32\\x18\\x37\\x90\\x1d\\x50\\x0b\\xf1\\x1e\\x6e\\x5b\\x54\\x3b\\xa0\\xc8\\x47\\x5e\\x06\\x8a\\x5e\\xa5\\xeb\\xd5\\x08\\x92\\xa1\\xbe\\x37\\xfc\\x31\\xae\\x97\\x5e\\xbb\\x69\\x4a\\xf1\\x1e\\x7e\\x23\\xe3\\x8e\\x62\\xea\\x18\\xd0\\x43\\x27\\x4d\\x76\\xc0\\x9a\\xc0\\x87\\xde\\x4c\\x47\\xa1\\xb9\\x86\\x5a\\xe3\\x80\\x96\\xfa\\x03\\x50\\xc0\\x0b\\xa8\\xc6\\xa1\\xb9\\xce\\x43\\x5b\\x4b\\xe7\\x28\\x3a\\x85\\x69\\x14\\xc2\\x85\\xcc\\x33\\x26\\x2b\\xba\\xcb\\x3d\\x06\\xf5\\xcf\\xd3\\x69\\x53\\xf3\\xde\\xa4\\xb9\\x6d\\xd2\\x78\\x3f\\x8e\\xdf\\x8b\\xf4\\x93\\x3e\\x7c\\xb0\\x52\\xed\\x9a\\x3a\\xe4\\x9f\\x52\\x88\\x57\\x53\\xbb\\x82\\xb3\\xbd\\xf9\\x02\\x56\\x89\\x83\\x47\\x87\\x88\\xad\\xe1\\x36\\xae\\x4b\\x45\\x5d\\xf5\\xe2\\x8a\\x6e\\xc4\\xeb\\x4a\\x8e\\x99\\x02\\x87\\x52\\x39\\xb5\\x49\\x64\\xdb\\x2a\\x7d\\x15\\x2f\\xf1\\xe2\\xc4\\xba\\x37\\x06\\x6b\\x1c\\xc4\\xe9\\x3f\\xc7\\x85\\xfa\\x68\\x40\\x3f\\x18\\xdc\\x1f\\x0a\\x9b\\x2e\\xf3\\x8d\\x44\\x19\\x2c\\x26\\x5f\\xc7\\x98\\x55\\x55\\xe5\\xe5\\xd1\\x20\\x89\\xe5\\xa2\\x0e\\x69\\xa7\\x9d\\x63\\xd7\\xce\\xbe\\x00\\xf9\\x9f\\xce\\x8f\\x4d\\xf2\\x80\\xea\\x7b\\x4f\\x94\\xb4\\x11\\xff\\x17\\x97\\x26\\x32\\x5b\\x32\\x3f\\x67\\x77\\x44\\xbc\\xbc\\x8d\\xff\\x05\\x00\\x00\\xff\\xff\\xb8\\xb5\\xa8\\x3c\\x3c\\x07\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/requirements.lock\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"requirements.lock\",\n\t\t\tmodTime:          time.Date(2025, 4, 24, 21, 9, 23, 549666844, time.UTC),\n\t\t\tuncompressedSize: 223,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\x4c\\xca\\x3d\\x6e\\xec\\x20\\x10\\x00\\xe0\\x9e\\x53\\xa0\\xed\\x8d\\x67\\x86\\xe5\\xc7\\x73\\x8e\\x77\\x01\\x0c\\xc3\\x2e\\xd2\\x3e\\x6c\\x01\\x8a\\x94\\xdb\\xa7\\x49\\x11\\xe9\\x2b\\xbf\\x22\\xb7\\xf4\\x22\\x3d\\x37\\x99\\xac\\x36\\xdd\\xd3\\x7f\\x61\\x7d\\xa7\\xb1\\x5a\\xfa\\x4c\\xa5\\xf5\\x90\\xfb\\x9a\\x6d\\x5d\\xe3\\x9b\\x75\\x6d\\x1f\\xe1\\x7d\\x37\\xe6\\x57\\x7e\\xa7\\xb1\\xe6\\xfe\\x67\\x7f\\xc9\\x98\\xed\\xea\\xac\\xc1\\xa0\\x01\\x55\\xda\\x4b\\xe6\\x62\\x3d\\xdf\\x89\\x9c\\x67\\xa1\\x8c\\x05\\x8a\\x8b\\x98\\xea\\x69\\x6d\\xa9\\x4f\\xff\\x44\\x2c\\x35\\xa4\\x78\\xd4\\x9c\\xc8\\x53\\xb4\\x14\\x6b\\x0e\\x67\\x81\\x23\\x38\\xf4\\x41\\x42\\x44\\x3a\\x2b\\x52\\x14\\x0a\\x55\\xbd\\xa4\\xcb\\x48\\x4b\\x0a\\xeb\\x07\\x01\\xe1\\x06\\xb4\\xa1\\xff\\x87\\xc0\\x2e\\x32\\x3a\\xe3\\xc8\\xfa\\x10\\xac\\x3f\\x36\\x70\\x0c\\xf0\\x50\\x3f\\x01\\x00\\x00\\xff\\xff\\x7d\\x64\\x4a\\xed\\xdf\\x00\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/requirements.yaml\": &vfsgen۰FileInfo{\n\t\t\tname:    \"requirements.yaml\",\n\t\t\tmodTime: time.Date(2025, 4, 24, 21, 9, 23, 549666844, time.UTC),\n\t\t\tcontent: []byte(\"\\x64\\x65\\x70\\x65\\x6e\\x64\\x65\\x6e\\x63\\x69\\x65\\x73\\x3a\\x0a\\x20\\x20\\x2d\\x20\\x6e\\x61\\x6d\\x65\\x3a\\x20\\x70\\x61\\x72\\x74\\x69\\x61\\x6c\\x73\\x0a\\x20\\x20\\x20\\x20\\x76\\x65\\x72\\x73\\x69\\x6f\\x6e\\x3a\\x20\\x30\\x2e\\x31\\x2e\\x30\\x0a\\x20\\x20\\x20\\x20\\x72\\x65\\x70\\x6f\\x73\\x69\\x74\\x6f\\x72\\x79\\x3a\\x20\\x66\\x69\\x6c\\x65\\x3a\\x2f\\x2f\\x2e\\x2e\\x2f\\x2e\\x2e\\x2f\\x2e\\x2e\\x2f\\x63\\x68\\x61\\x72\\x74\\x73\\x2f\\x70\\x61\\x72\\x74\\x69\\x61\\x6c\\x73\\x0a\"),\n\t\t},\n\t\t\"/linkerd-multicluster/templates\": &vfsgen۰DirInfo{\n\t\t\tname:    \"templates\",\n\t\t\tmodTime: time.Date(2025, 12, 12, 18, 49, 15, 368996883, time.UTC),\n\t\t},\n\t\t\"/linkerd-multicluster/templates/gateway-policy.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"gateway-policy.yaml\",\n\t\t\tmodTime:          time.Date(2025, 4, 24, 21, 9, 23, 549666844, time.UTC),\n\t\t\tuncompressedSize: 2279,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xec\\x56\\x4d\\x6f\\xd3\\x40\\x10\\xbd\\xe7\\x57\\x8c\\xc2\\x21\\x80\\xe4\\x6d\\x2b\\x38\\xf9\\x86\\x10\\x42\\x88\\x52\\x55\\x2d\\xaa\\xc4\\x71\\xe2\\x9d\\xc6\\xa3\\xae\\x67\\x97\\xdd\\x71\\xd3\\xe0\\xe6\\xbf\\xa3\\xb5\\x5d\\x70\\x21\\xf4\\xeb\\x5a\\x94\\xdb\\x64\\xf6\\xbd\\x37\\xef\\xbd\\x48\\xe9\\x3a\\x3e\\x07\\x73\\x86\\xae\\xa5\\x64\\x56\\xa8\\xb4\\xc6\\x8d\\x21\\xc1\\xa5\\x23\\x0b\\xc5\\x76\\x3b\\x2b\\x8a\\x62\\x86\\x81\\xcf\\x28\\x26\\xf6\\x52\\x42\\xf0\\x8e\\xab\\x8d\\x71\\x2c\\x17\\x14\\xad\\x61\\xbf\\x77\\x79\\xb0\\x24\\xc5\\x37\\xb3\\x0b\\x16\\x5b\\xc2\\x29\\xc5\\x4b\\x8a\\xb3\\x86\\x14\\x2d\\x2a\\x96\\x33\\x00\\xc1\\x86\\x52\\xc0\\x8a\\x4a\\xe8\\x3a\\x30\\x27\\xe4\\x08\\x13\\x99\\xa3\\x9b\\x31\\x6c\\xb7\\xe3\\x56\\x09\\x23\\x6e\\x31\\x4a\\x99\\x01\\x38\\x5c\\x92\\x4b\\x19\\x07\\x60\\xc2\\x4a\\x57\\x4a\\x32\\x48\\x6a\\x5a\\xa7\\x5c\\xb9\\x36\\x29\\xc5\\x7e\\x0d\\x43\\xc8\\x54\\x7f\\x9e\\x95\\x19\\x7a\\x2a\\x80\\xae\\x2b\\x60\\xcd\\x5a\\xff\\x3a\\xbd\\xf2\\x4d\\xe3\\xe5\\xb0\\xe7\\x82\\xed\\xb6\\xeb\\x40\\xfd\\x37\\x6c\\x1c\\x18\\xb8\\x06\\x8d\\xdc\\xc0\\x35\\x08\\x8b\\x25\\x51\\x78\\xdb\\x2f\\x14\\x40\\x62\\x07\\xe9\\x28\\xe2\\x15\\x95\\xbd\\x8c\\x3a\\xbb\\x0e\\x58\\x2a\\xd7\\x5a\\x82\\x79\\xc0\\xa8\\x8c\\x2e\\x99\\xc9\\x96\\xa9\\x22\\xa1\\x92\\x2d\\x96\\x9b\\x39\\x98\\x0c\\x92\\x02\\x55\\xf9\\x6d\\xf0\\xf6\\x94\\x1c\\x55\\xea\\xe3\\x00\\xd5\\xa0\\x56\\xf5\\xe1\\xc4\\x83\\xfb\\xcf\\x0b\\x3e\\xea\\x6f\\x27\\x43\\xf4\\x57\\x9b\\x07\\xc5\\x88\\x2e\\xd4\\x78\\x30\\xe6\\xf8\\xae\\xd5\\xda\\x47\\xfe\\xd1\\x0b\\x3e\\xee\\xb7\\xff\\x87\\xfa\\xc4\\x50\\x15\\xe3\\x8a\\xf4\\x84\\xce\\x07\\xa0\\x55\\xf4\\x6d\\xd8\\x11\\x41\\xff\\xe5\\xad\\x5f\\x51\\x1e\\xfc\\xcb\\xc2\\x48\\xdf\\x5b\\x8e\\x64\\x73\\x50\\x24\\xca\\x55\\xaf\\xe2\\x84\\xce\\x47\\xb9\\xc5\\xdd\\x3c\\x37\\x4c\\x5f\\x28\\xd5\\x5f\\x0f\\x4f\\x6f\\xa3\\x8c\\x1b\\x03\\x35\\xca\\xa6\\x68\\x28\\xd5\\x64\\x27\\xe3\\xfb\\xa3\\x7f\\xb0\\x82\\x23\\xd2\\xb5\\x8f\\x17\\x77\\x28\\x48\\xbe\\x8d\\x15\\x15\\xd3\\x36\\x3c\\x4c\\xc5\\xa3\\x5b\\xbf\\xdb\\x8d\\xa7\\xf6\\xfe\\x96\\x73\\xcf\\xab\\xf2\\x9c\\x19\\x58\\x99\\x7a\\xa4\\x02\\x16\\xaf\\x17\\x8f\\x4f\\x63\\x77\\x33\\x9e\\x9a\\xc6\\x5f\\x2d\\x7a\\x5e\\x89\\xc8\\x60\\xe6\\x88\\xf3\\x02\\xde\\xd7\\x28\\x2b\\x02\\xad\\x39\\x81\\x7a\\xd0\\x9a\\x46\\x87\\x60\\x3c\\x18\\x2a\\xb6\\x31\\x41\\xf0\\x2c\\xca\\xb2\\x1a\\x96\\x38\\xc1\\xcd\\xd1\\x23\\xce\\x91\\xd7\\x8c\\x82\\x3a\\x85\\xf8\\x74\\x0c\\x2c\\x90\\x7c\\x43\\x10\\xa2\\xbf\\x64\\x4b\\x31\\xc1\\x4b\\x32\\x2b\\x03\\x1f\\x3f\\x7f\\x78\\x05\\x6b\\x76\\x0e\\x96\\xd4\\x3f\\x71\\xbe\\x42\\x37\\x82\\x89\\xb7\\xb4\\x48\\xf9\\x39\\x8a\\x05\\xf1\\xba\\x43\\xd7\\x22\\xf5\\x8d\\xca\\xea\\x4a\\x98\\xef\\x9b\\xfe\\xb3\\xb7\\x3f\\x9f\\x4e\\xcb\\x32\\x0f\\xba\\x2e\\x1b\\x99\\xff\\xc5\\xfc\\x0c\\x00\\x00\\xff\\xff\\x3d\\x35\\xe0\\xe7\\xe7\\x08\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/templates/gateway.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"gateway.yaml\",\n\t\t\tmodTime:          time.Date(2025, 12, 12, 18, 49, 15, 368996883, time.UTC),\n\t\t\tuncompressedSize: 5440,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xdc\\x57\\x5b\\x6f\\xdb\\xb6\\x17\\x7f\\xf7\\xa7\\x38\\x10\\xfa\\xd0\\x3c\\x48\\x45\\x81\\x3f\\x8a\\x3f\\x04\\xec\\x21\\x4d\\xba\\x2e\\x80\\x91\\x19\\xb9\\x14\\xd8\\xd3\\x40\\x53\\xc7\\x0e\\x17\\x8a\\x64\\xc9\\x23\\x37\\x82\\xeb\\xef\\x3e\\x50\\x94\\x64\\xca\\x96\\xed\\xa4\\xcb\\x80\\x62\\x79\\x8a\\x75\\xee\\xf7\\x1f\\xd7\\x6b\\xb1\\x80\\xec\\x0b\\x93\\x15\\xba\\x6c\\xc9\\x08\\xbf\\xb1\\x3a\\x43\\xc5\\xe6\\x12\\x0b\\x48\\x37\\x9b\\x49\\x9a\\xa6\\x93\\xf5\\x3a\\x85\\x37\\x64\\x11\\x21\\xff\\x05\\x0a\\x44\\x73\\xa1\\x4d\\x0d\\x19\\x6c\\x36\\x13\\x66\\xc4\\x17\\xb4\\x4e\\x68\\x95\\x03\\x33\\xc6\\xbd\\x5b\\xbd\\x9f\\x3c\\x0a\\x55\\xe4\\x70\\x89\\x46\\xea\\xba\\x44\\x45\\x93\\x12\\x89\\x15\\x8c\\x58\\x3e\\x01\\x60\\x4a\\x69\\x62\\x24\\xb4\\x72\\xfe\\x27\\xc0\\x7a\\x0d\\x42\\x71\\x59\\x15\\x08\\x89\\x61\\x96\\x04\\x93\\x2e\\x8b\\xb8\\x32\\x6e\\x91\\x11\\x16\\xe9\\xbc\\x4e\\x82\\x4d\\x00\\xc9\\xe6\\x28\\x5b\\x79\\x66\\x4c\\xf6\\x58\\xcd\\xd1\\x2a\\x24\\x74\\x99\\xd0\\xef\\x14\\x2b\\x31\\x87\\x36\\x98\\x03\\x3c\\xde\\x52\\xaa\\x17\\x39\\x4c\\x85\\x7a\\x44\\x5b\\x1c\\x60\\x5b\\x75\\xb1\\xad\\xd7\\x5d\\x92\\x64\\x10\\x68\\xa3\\x6e\\xdc\\x01\\xe0\\xba\\x34\\x5a\\xa1\\xa2\\x3d\\xbb\\xb1\\x68\\x97\\x5f\\xef\\x60\\x2b\\xd8\\x6a\\xf3\\xb6\\xf0\\x89\\x50\\x05\\x6b\\x65\\x25\\x49\\x70\\x59\\x39\\x42\\xdb\\x26\\x29\\x85\\x6f\\x82\\x1e\\xfa\\x52\\x71\\x5d\\x96\\x5a\\x4d\\x9b\\x3c\\xc0\\x66\\xb3\\x5e\\x03\\xe9\\x3f\\x58\\x29\\x21\\x83\\xef\\x40\\x56\\x94\\xf0\\x1d\\x94\\x50\\x05\\x2a\\x82\\xff\\x35\\x0c\\x29\\xa0\\x2a\\x42\\xfa\\x42\\x82\\x8e\\xf8\\xe5\\xff\\x71\\x86\\xf1\\x86\\x0b\\xb2\\x1b\\x94\\xc8\\x1c\\x66\\xd7\\xdd\\x67\\xaf\\xc6\\x19\\xe4\\xbe\\x02\\x16\\x8d\\x14\\x9c\\xb9\\x31\\x85\\x1d\\xad\\x51\\x6a\\x71\\x25\\x7c\\x78\\xbf\\x09\\x47\\xda\\xd6\\x53\\x51\\x0a\\x8a\\x85\\xc6\\xe8\\x8d\\xa0\\x43\\x89\\x9c\\xb4\\x0d\\xf5\\x2e\\x19\\xf1\\x87\\x69\\xd4\\x00\\xa7\\xd3\\xec\\x83\\x8f\\xda\\x3c\\xb4\\xf7\\x4c\\x17\\xe7\\x8a\\xc4\\xf9\\x62\\x21\\x94\\xa0\\x3a\\x64\\xc6\\x91\\x65\\x84\\xcb\\x3a\\x68\\xb6\\x5a\\x4a\\xa1\\x96\\xf7\\xa6\\x60\\x84\\x9d\\xb1\\x92\\x3d\\xdd\\x2b\\xb6\\x62\\x42\\x7a\\x2d\\x39\\xbc\\x6f\\x0d\\xf4\\xd9\\x25\\x2c\\x8d\\xec\\x05\\xe2\\xde\\x6f\\x7c\\xdd\\xed\\xff\\x1f\\x9d\\x81\\xf0\\x17\\xf5\\x8f\\x50\\x7f\\x21\\xa7\\x1c\\xda\\xe9\\xed\\x59\\xb8\\x56\\x0b\\xb1\\xcc\\x22\\x4e\\x63\\xf5\\x53\\x9d\\x5a\\xfc\\x5a\\x09\\x8b\\xa9\\xf0\\x4d\\x22\\xa8\\x4e\\x85\\x9a\\xeb\\x4a\\x15\\xa9\\xd1\\x96\\x5c\\x0e\\xc9\\x7e\\x46\\x3d\\x65\\xb3\\x49\\x8e\\xa8\\x0e\\xc6\\xd3\\x56\\x20\\x87\\x84\\x6c\\x85\\xc7\\x04\\x0a\\x5c\\xb0\\x4a\\x52\\x64\\x5c\\x0a\\x5e\\xe7\\xc0\\xa4\\x4c\\x59\\x45\\x0f\\xde\\x37\\xee\\x63\\xdf\\xea\\x08\\x73\\xe1\\xa9\\xda\\x71\\x26\\xd1\\xee\\x0c\\xad\\x63\\x0b\\x4c\\x49\\xa7\\xb8\\x12\\x3e\\x1f\\x3b\\x2e\\xec\\x8d\\x52\\x17\\x5c\\xd1\\x2f\\xac\\xf3\\x6d\\xe6\\x4f\\xcd\\xd6\\xff\\x77\\x67\\xab\\x29\\xca\\xa0\\x3b\\x9f\\xb3\\x06\\x5e\\xb0\\x0a\\x46\\x63\\x30\\xba\\x78\\xde\\x2e\\x18\\xf1\\xb7\\x1b\\xe4\\x4e\\xf1\\x9b\\x3f\\xfd\\x9a\\x77\\x48\\xed\\xce\\x4f\\xfa\\xe5\\x96\\xc0\\x58\\x08\\xcd\\x99\\x38\\x22\\xde\\x64\\x23\\x81\\x84\\x19\\x93\\xec\\xf0\\xf6\\x4d\\xdf\\xc5\\xce\\xda\\x69\\x4c\\x5a\\xe1\\xad\\xe3\\x1f\\x60\\x28\\xb9\\x7f\\xb5\\x08\\x6d\\x29\\x54\\x53\\xb6\\xcf\\x96\\x71\\x9c\\xa1\\x15\\xba\\xb8\\x45\\xae\\x55\\xe1\\xb6\\xd2\\x47\\xd9\\xc6\\xca\\x74\\x54\\x60\\xe0\\xd4\\xa0\\x09\\x7c\\x7b\\x96\\xba\\x52\\x74\\x8b\\x76\\x25\\x38\\x9e\\x73\\xee\\x7f\\xdd\\xe9\\x47\\x54\\x39\\x2c\\x98\\x74\\x38\\xe9\\x87\\x82\\x98\\x50\\x68\\xfb\\x96\\x49\\xdb\\x15\\x6d\\x58\\xd5\\x73\\x01\\x88\\x92\\x2d\\xdb\\x8d\\xbc\\x3b\\x97\\x9e\\xef\\xca\\x93\\xe3\\xdd\\xe0\\x90\\x57\\x56\\x50\\x7d\\xa1\\x15\\xe1\\x13\\x6d\\xfb\\x11\\xfc\\x74\\xe9\\x6f\\x33\\x2b\\x56\\x42\\xe2\\x12\\x3f\\xf9\\x31\\x6a\\x02\\x1c\\x3a\\xd6\\x38\\xc7\\x0c\\x9b\\x0b\\x29\\x48\\xa0\\x8b\\x35\\x00\\x14\\x56\\x9b\\xe1\\x97\\x14\\xce\\xa7\\xd3\\xe8\\x8b\\x45\\x56\\xfc\\xae\\x64\\x7d\\xa3\\x35\\xfd\\x2a\\x24\\xba\\xda\\x11\\x96\\x39\\xf8\\x81\\x8c\\xd9\\x2a\\x75\\xee\\xae\\xb5\\xf2\\x6c\\xe3\\xc4\\x7b\\x87\\x76\\xac\\x34\\xf7\\x57\\x97\\x51\\xbc\\x2d\\xf3\\x67\\xab\\xab\\xd1\\x79\\xfb\\xbc\\xc3\\xed\\x90\\xfb\\xd6\\x9e\\x59\\xbd\\x10\\x12\\x87\\xa1\\x50\\x6d\\x30\\x87\\x9b\\x4a\\x91\\x28\\xf1\\x32\\xec\\xa8\\xc9\\x89\\xb4\\x1e\\xd6\\x77\\x54\\x5b\\xdc\\x1d\\xd7\\xa7\\x2e\\xf3\\xd1\\x0d\\xa6\\x74\\x81\\xb7\\xed\\xb1\\xdc\\x36\\x42\\xfc\\xb5\\xe9\\x9e\\x68\\x3b\\xc4\\x6b\\xe1\\x50\\x23\\x1f\\x34\\x47\\x5a\\xa2\\xed\\xd7\\x64\\x37\\x5e\\xdb\\x8f\\x87\\x8c\\x7d\\x18\\x35\\xf6\\xdc\\x23\\xed\\x01\\x69\\x40\\x98\\x33\\x5d\\x5c\\x0a\\x67\\x2b\\xe3\\xad\\x7d\\xac\\x8a\\x25\\xd2\\x00\\x8f\\x86\\x53\\xe2\\x11\\x69\\x7c\\x84\\x5f\\x05\\xfc\\xec\\x41\\xd0\\x9f\\x11\\xe6\\xbd\\x02\\xd4\\xee\\x6e\\xc3\\x18\\xe8\\xf9\\x87\\xb0\\x2c\\x72\\xd5\\x97\\x34\\x2e\\x5c\\xff\\x86\\x68\\x57\\xe7\\xbf\\x5d\\xbf\\x9f\\xa3\\x3a\\xa5\\xb0\\x56\\xdb\\x18\\x1b\\xb5\\xa1\\xf5\\x08\\xed\\x70\\xd8\\xd9\\x7a\\xbd\\x1f\\xe8\\x66\\x93\\xb5\\xdb\\x85\\x85\\xed\\x92\\x75\\x8a\\xb2\\xbd\\xe7\\x4c\\x2c\\xb4\\x25\\x76\\xfc\\x77\\xb6\\x72\\x74\\xa9\\x4b\\x26\\xba\\x07\\xcf\\xbe\\xb3\\xc6\\xea\\x39\\xa6\\xa6\\x39\\x8e\\xe3\\xe0\\xd1\\x33\\x64\\xae\\xbb\\x9b\\xc9\\x71\\x3d\\x8c\\x1e\\xc6\\xc2\\x0d\\x4a\\x3c\\xf5\\xa0\\x23\\x71\\xdd\\x46\\x81\\xe8\\x81\\xc7\\xda\\x8f\\x81\\xf0\\x83\\xdb\\xb1\\x5b\\xec\\xcf\\xc7\\x92\\x3b\\x2d\\xd2\\xcd\\x5e\\x80\\xe3\\xed\\x2b\\xe3\\x8d\\x43\\xba\\xd6\\x05\\xce\\xfc\\x47\\x0f\\xb4\\xde\\x6a\\x0b\\x6f\\xf1\\xeb\\x21\\xf3\\x77\\xb5\\x41\\x48\\x3a\\x89\\xe4\\xec\\x34\\xef\\x54\\xb3\\xe2\\x23\\x93\\x4c\\x71\\xb4\\xc9\\xd9\\x59\\x88\\xb3\\x03\\x24\\x25\\x4f\\xe3\\x94\\x79\\xd7\\x46\\xab\\xd4\\xbc\\x13\\x02\\x8b\\xd5\\xa4\\xb9\\x96\\x39\\xdc\\x5d\\xcc\\xb6\\x6f\\xb1\\xb7\\x4c\\x15\\x3b\\xc1\\x8c\\x5d\\x33\\x4f\\x39\\xeb\\x52\\xdd\\x7d\\x18\\x85\\x40\\x1d\\x11\\xfa\\x17\\x5f\\x3f\\x69\\x91\\xf3\\x4d\\xfb\\x1c\\x77\\x3d\\x34\\xd8\\x6b\\x04\\x10\\x54\\xbd\\x2c\\x8c\\xa1\\xcc\\x48\\x30\\xc3\\xad\\x7b\\xea\\xf2\\x04\\xe0\\x31\\x62\\x27\\x2e\\x79\\xbb\\x8c\\x8f\\x75\\xf1\\xa7\\x27\\x42\\xab\\x98\\xbc\\xb3\\x1e\\xa1\\xf3\\x59\\x73\\x59\\x83\\x43\\x38\\x46\\x0a\\x36\\x61\\xb8\\xe6\\x0f\\xe0\\x76\\x19\\x35\\xdc\\x85\\x64\\xae\\x05\\x13\\x7b\\x9f\\x47\\xe3\\x18\\x15\\x7e\\x99\\xcd\\xab\\xd9\\xbe\\xc1\\xab\\xd9\\x49\\x6b\\x41\\xec\\x65\\xa6\\x6e\\x75\\x65\\x39\\xde\\x30\\xb5\\xc4\\x91\\x28\\x63\\x6a\\x37\\xef\\xd6\\xff\\x7a\\xa1\\xce\\xb4\\x4f\\xfe\\x1e\\xc0\\x8a\\x0e\\xee\\xe0\\xc2\\xb6\\xf0\\x73\\xf7\\x0a\\xff\\xf7\\xee\\xee\\xe0\\xd5\\xd9\\x6f\\xf9\\xe6\\x65\\x95\\x9a\\x4a\\xca\\xd4\\x21\\xb7\\x48\\x6e\\xfb\\xd4\\x6d\\x68\\xb3\\x4a\\xca\\xdb\\x40\\x09\\x5a\\xbc\\x3a\\xff\\x96\\xfd\\x3b\\x00\\x00\\xff\\xff\\x7d\\xf5\\x6a\\xd5\\x40\\x15\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/templates/link-crd.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"link-crd.yaml\",\n\t\t\tmodTime:          time.Date(2025, 12, 12, 18, 49, 15, 368996883, time.UTC),\n\t\t\tuncompressedSize: 26535,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xec\\x5d\\xff\\x6f\\xdb\\x46\\xb2\\xff\\x5d\\x7f\\xc5\\x3c\\x27\\xef\\xd5\\x4e\\x4d\\xaa\\x4e\\xfa\\x8a\\x3b\\x1d\\x0e\\x81\\x61\\x27\\x3d\\xa3\\x4e\\x2e\\x17\\xbb\\x05\\xae\\xb1\\x5b\\x8c\\xc8\\x91\\xb4\\xe7\\xe5\\x2e\\xbb\\xbb\\x94\\xad\\xba\\xb9\\xbf\\xfd\\xb0\\xbb\\x24\\x25\\xcb\\xe2\\x17\\x39\\xce\\xf5\\x80\\x5b\\x01\\x41\\x2c\\x72\\x76\\xbe\\x71\\x76\\xf6\\x33\\x33\\x34\\x1c\\x45\\xd1\\xe0\\xc9\\x93\\x27\\xf6\\x1f\\x9c\\x32\\x71\\x05\\x47\\xef\\x8f\\xdd\\x05\\xcc\\xd9\\x0f\\xa4\\x34\\x93\\x62\\x04\\x98\\x33\\xba\\x31\\x24\\xec\\x37\\x1d\\x5f\\xfd\\x41\\xc7\\x4c\\x0e\\xe7\\x07\\x83\\x2b\\x26\\xd2\\x11\\x1c\\x15\\xda\\xc8\\xec\\x3d\\x69\\x59\\xa8\\x84\\x8e\\x69\\xc2\\x04\\x33\\x4c\\x8a\\x41\\x46\\x06\\x53\\x34\\x38\\x1a\\x00\\x08\\xcc\\x68\\x04\\x9c\\x89\\x2b\\x1d\\x67\\x05\\x37\\x2c\\xe1\\x85\\x36\\xa4\\x62\\x7b\\x89\\x54\\x1a\\x33\\x39\\x00\\xe0\\x38\\x26\\xae\\x2d\\x3d\\xc0\\xf2\\xc6\\xb0\\x16\\x3e\\x82\\xd5\\xb5\\x8e\\xec\\xf6\\x36\\x82\\x6b\\x66\\x66\\x10\\xff\\x80\\xbc\\x20\\x1d\\x27\\x32\\xcb\\xa4\\x38\\x75\\x9c\\xe0\\xe3\\xc7\\xdb\\x5b\\x30\\xf2\\xef\\x98\\x71\\x88\\xe1\\x37\\x30\\x8a\\x65\\xf0\\x1b\\x08\\x26\\x52\\x12\\x06\\xbe\\x76\\x04\\x11\\x90\\x48\\xe1\\xe3\\xc7\\x01\\x00\\x0a\\x21\\x0d\\x5a\\xed\\x4b\\x2d\\x6e\\x6f\\x81\\x89\\x84\\x17\\x29\\xc1\\x4e\\x8e\\xca\\x30\\xe4\\x3a\\x5e\\xa1\\x8a\\x13\\x45\\x68\\x28\\x8d\\xc6\\x8b\\x1d\\x88\\x2d\\x13\\x9d\\x53\\x62\\xd7\\x4e\\x95\\x2c\\xf2\\xbb\\x0a\\xdf\\x35\\x76\\xee\\xdd\\xeb\\x04\\x45\\xa5\\x83\\xe6\\x07\\xc8\\xf3\\x19\\x1e\\x38\\xd9\\x9a\\xd4\\x9c\\xd2\\x11\\x18\\x55\\x90\\xbf\\x60\\xa4\\xc2\\x29\\x8d\\x60\\x82\\x5c\\x97\\x97\\x92\\x19\\x65\\xe8\\x75\\x05\\x90\\x39\\x89\\xc3\\x77\\x27\\x3f\\xbc\\x38\\xbb\\x73\\x19\\xc0\\x2c\\x72\\x1a\\x81\\x1c\\xff\\x83\\x12\\x53\\x5f\\xcc\\x95\\xcc\\x49\\x19\\x46\\x7a\\x49\\x08\\x50\\xe9\\xbf\\xfc\\x6c\\x5c\\xdc\\xcc\\xc0\\x7e\\x4a\\x83\\x8f\\x14\\x59\\x3f\\x5b\\xa7\\x9d\\x51\\xa2\\xc8\\xac\\xd3\\x01\\xa4\\xa4\\x13\\xc5\\x72\\xe3\\x9e\\xee\\x77\\xc5\\x98\\x94\\x20\\x43\\x1a\\xb4\\xa3\\x07\\x39\\x01\\x83\\x6a\\x4a\\x06\\x56\\x1f\\xfa\\x7d\\xed\\xb4\\x51\\x4c\\x4c\\xd7\\x6e\\x4e\\xd1\\xd0\\x35\\x2e\\x0e\\xd3\\x54\\x91\\xd6\\x1d\\xb2\\xbf\\xf5\\xc4\\x80\\x9e\\xfa\\x51\\x24\\x9f\\x38\\xeb\\xcd\\xa2\\xa7\\xe8\\x8a\\x1c\\x5e\\xff\\xed\\xf8\\xed\\x03\\xc4\\xbd\\x93\\xca\\xf4\\x14\\x65\\x49\\xb7\\x91\\x90\\x2b\\x39\\xa6\\xb3\\x7b\\xa1\\x71\\x8f\\xbf\\x25\\x81\\x89\\x54\\x95\\x4a\\x30\\x23\\xe4\\x66\\xe6\\xd7\\x37\\x08\\xdc\\x10\\x59\\xed\\xd1\\x65\\x3f\\x13\\x64\\xbc\\x50\\x74\\x3e\\x53\\xa4\\x67\\x92\\xa7\\x9b\\x68\\xac\\x6e\\x13\\x2c\\xb8\\x19\\xc1\\xce\\x8b\\x9d\\x06\\x82\\x15\\xe5\\xdf\\x30\\xc1\\xb2\\x22\\x83\\x44\\x0a\\x4d\\x49\\x61\\xd8\\x9c\\x2a\\x39\\xda\\x19\\x65\\x66\\xe4\\x2d\\x01\\x23\\x61\\x4c\\x8e\\x90\\xa5\\xa4\\x28\\x75\\x74\\x94\\x6e\\x94\\xd1\\xe2\\x56\\x67\\x27\\x9a\\xd9\\xa8\\x5b\\xb9\\x77\\x68\\x66\\x36\\x28\\x15\\x65\\xd2\\xd0\\xba\\x7f\\x49\\xa4\\xb9\\x64\\xc2\\x3c\\x48\\x01\\x52\\x4c\\xa6\\x3d\\x54\\x38\\x11\\x86\\xd4\\x1c\\x39\\x30\\x01\\x63\\x32\\xd7\\x44\\xa2\\x74\\x87\\xa2\\x5f\\x0a\\xd2\\x46\\x3f\\x48\\xfc\\xc6\\xa8\\xbd\\x6f\\xbf\\x54\\xe6\\xf3\\xd8\\x6f\\x58\\x46\\xb2\\x30\\x1d\\x11\\xf4\\xe2\\x2b\\xdd\\x43\\xc9\\x55\\x6f\\x54\\x8c\\x37\\x2e\\x9b\\x48\\x95\\xa1\\x19\\x41\\x5a\\x28\\x77\\x92\\x6c\\xab\\xb8\\x26\\x4e\\x89\\x91\\xaa\\x7f\\x46\\x75\\x47\\x22\\x9c\\x95\\xeb\\x1e\\x73\\x2f\\x66\\x68\\x92\\xd9\\xe9\\xca\\xd9\\xbd\\x15\\x6f\\xfb\\xb9\\x89\\xae\\x6a\\x45\\xa3\\x5c\\x91\\x3b\\xfa\\xa2\\x42\\x5c\\x09\\x79\\x2d\\xa2\\x09\\x23\\x9e\\xea\\x95\\x83\\x70\\x83\\xf8\\x57\\x37\\x76\\x99\\x5e\\x9e\\xdc\\xad\\x6e\\x39\\x65\\xda\\x05\\x53\\xe5\\x45\\xf7\\xc8\\x98\\xa2\\x8c\\x44\\x6b\\x10\\xa3\\x52\\xb8\\xd8\\x78\\x9f\\x19\\xca\\x1a\\x24\\xaf\\xc9\\x3e\\x5c\\x4a\\xb5\\x8b\\x2a\\xd1\\x1a\\x10\\xae\\x68\\x01\\x28\\x52\\x40\\x61\\x8f\\x72\\x85\\x9b\\x9e\\x53\\x4f\\x8f\\x42\\xc5\\x37\\x6d\\x52\\x2a\\xb2\\xe2\\x1a\\xef\\x75\\xc8\\x6f\\x0f\\x08\\xff\\xb9\\xa2\\x45\\xf3\\xcd\\xf5\\x07\\xe2\\x82\\xd3\\xda\\x6f\\x66\\x68\\x96\\x1e\\xd2\\x33\\x59\\xf0\\x14\\x30\\xcf\\xf9\\x02\\x8c\\x6c\\x61\\xd7\\xb1\\xc9\\x6b\\x80\\xe4\\xac\\xea\\xad\\xd7\\xab\\x39\\xf2\\xc2\\x6d\\x50\\x1b\\x2e\\xe8\\x11\\xaa\\xcd\\x7f\\x8a\\xb8\\xbf\\x6c\\x24\\x68\\x32\\x9f\\xaa\\x18\\x00\\x89\\x22\\x1b\\xc1\\x87\\x13\\xb1\\x0f\\x6f\\xa5\\xb1\\xff\\xbd\\xba\\x61\\xda\\xe8\\x7d\\x38\\x96\\xa4\\xdf\\x4a\\xe3\\xbe\\x5e\\x36\\x72\\x98\\x3b\\xf8\\x3b\\xea\\x54\\xa4\\x39\\x84\\x7b\\x04\\x72\\xa7\\x45\\x3e\\x3b\\x1f\\x33\\x9d\\xc8\\x39\\xa9\\xc5\\x59\\xbf\\x2c\\x55\\x91\\xb9\\x43\\xf6\\x8c\\xd4\\x9c\\x25\\xa4\\xad\\x67\\x33\\xa6\\x94\\xdd\\x27\\xa2\\x4a\\xfb\\x69\\xc5\\x19\\x32\\x99\\x52\\x48\\x62\\x21\\x89\\x85\\x24\\x16\\x92\\xd8\\x23\\x27\\x31\\x5f\\xf7\\x1d\\xf9\\xb2\\xef\\xad\\x2d\\xcc\\xdb\\xd3\\x97\\x25\\xb9\\x5f\\x2f\\x5a\\xa7\\xda\\x82\\x7f\\xd3\\x13\\xef\\x2b\\xfc\\x58\\x66\\xc8\\x44\\x87\\x78\\x4f\\xe4\\x3a\\x08\\x9f\\x47\\x8b\\x53\\xdf\\xb6\\xb0\\x66\\xea\\x1c\\x93\\xbe\\xee\\x10\\x15\\x3d\\x94\\x0c\\x6c\\xbd\\x64\\x94\\xe4\\x90\\x73\\x14\\x04\\x4c\\x03\\x13\\xda\\x20\\xe7\\x94\\xda\\x68\\xb4\\x71\\xb8\\x55\\xc5\\xbd\\xd6\\x35\\x79\\xde\\xd1\\x35\\x59\\x5e\\x09\\x4d\\x93\\xd0\\x34\\x09\\x4d\\x93\\xd0\\x34\\x09\\x4d\\x93\\xd0\\x34\\x09\\x4d\\x93\\x50\\x6f\\x84\\x7a\\x23\\xd4\\x1b\\xa1\\x69\\x12\\x92\\x58\\x48\\x62\\x21\\x89\\x85\\x24\\xf6\\xb0\\x24\\x36\\xa1\\xd4\\x7a\\x98\\xd2\\x32\\x15\\x3d\\x24\\x8b\\xd5\\x3c\\x5c\\x15\\xcf\\x12\\x82\\x8c\\x32\\x1a\\x93\\xd2\\x21\\x69\\x85\\xa4\\x15\\x92\\x56\\x48\\x5a\\xa1\\xd3\\xfb\\x5f\\xd9\\xe9\\xad\\x0b\\x78\\x83\\xa6\\x58\\x7b\\xdc\\x77\\x8f\\x13\\x47\\x00\\x29\\x4d\\x98\\xb0\\x68\\x78\\x46\\x6e\\x0d\\xf9\\x26\\x88\\x7f\\x45\\x51\\x43\\x86\\x02\\xa7\\x94\\xc2\\xd8\\xee\\x5f\\xa6\\x9d\\xd6\\x3d\\x9b\\xb4\\x1e\\x5c\\x57\\x70\\xbb\\xc3\\x3f\\xcb\\xbc\\x5d\\xa2\\x73\\xbf\\xba\\x4d\\x72\\x57\\xe4\\x37\\xc6\\xfb\\x1d\\xc1\\xe7\\xa5\\xdd\\x85\\xf6\\x79\\xa0\\x96\\x5b\\x6a\\x32\\xd8\\x3e\\x45\\x26\\x52\\xa4\\xcc\\x34\\x1f\\x58\\x6b\\x1a\\x1c\\xd5\\xe4\\x2e\\x52\\x67\\xd4\\x47\\x87\\x7e\\xdb\\xbe\\x63\\xcb\\xb7\\x3a\\xa2\\xb6\\xa2\\x71\\x79\\x9f\\xa3\\x02\\x80\\xa3\\x36\\xe7\\x0a\\x85\\x76\\xcc\\xce\\xd9\\xa6\\xcc\\xd1\\xa8\\xd4\\xfd\\xc5\\x76\\x87\\x58\\x1f\\xd9\\x3b\\xae\\x03\\xe6\\xbe\\x75\\x2b\\x5b\\xba\\xac\\xe6\\x45\\x29\\x4c\\x94\\xcc\\x40\\x8a\\xda\\x6c\\x23\\x01\\x85\\x34\\x33\\x52\\x31\\x9c\\xdb\\x90\\xf3\\x67\\x54\\x07\\xcb\\x31\\xc1\\xf5\\x8c\\x84\\x53\\xa3\\x10\\x29\\x29\\xbe\\x60\\x62\\xba\\xd4\\x08\\x92\\x19\\x8a\\x29\\xa5\\x31\\xc0\\xc9\\xc4\\x1f\\x82\\x4c\\x77\\xf0\\x14\\xd2\\x80\\x43\\x48\\xfb\\x96\\xad\\x80\\x42\\x5b\\x96\\x56\\x82\\xb3\\xb8\\x96\\x77\\xf8\\xee\\x04\\x1c\\x86\\xaa\\x84\\x74\\xf0\\x65\\x1a\\x30\\x49\\x28\\x37\\x38\\xe6\\x14\\xb7\\x12\\xd7\\x0d\\x44\\x34\\x14\\x59\\xa9\\x0f\\x3d\\x38\\xee\\xa4\\x04\\xd2\\x1a\\xa7\\xdb\\x04\\x40\\xb9\\xc2\\x69\\x0e\\xb3\\x22\\x43\\x7b\\x40\\x63\\x6a\\xf5\\x5f\\xde\\x13\\x29\\x4b\\xd0\\xb4\\x8b\\xf6\\x9c\\x0d\\x32\\xae\\x01\\xc7\\xb2\\x30\\xde\\x9d\\x75\\x3c\\x94\\x8f\\x3c\\xc3\\x85\\x7d\\xa2\\x28\\x80\\xb2\\xdc\\x2c\\x4a\\x93\\xe2\\xc7\\x30\\x5e\\x11\\x6a\\x29\\xb6\\xb0\\xdd\\x2f\\x70\\x07\\x04\\x32\\x61\\x1d\\x90\\x2b\\x39\\x55\\x98\\x65\\x68\\x58\\x02\\xcc\\x8d\\x73\\x26\\x8c\\x54\\xd7\\x53\\xaf\\xfd\\xe3\\x6c\\x2e\\xd9\\x56\\x03\\x87\\x3a\\x50\\xbf\\xd0\\xe5\\xa6\\x5a\\xfa\\xa4\\x83\\xf1\\x3b\\x25\\xd3\\x22\\x21\\xe5\\x32\\x86\\xce\\x29\\x61\\x13\\x96\\xac\\x44\\xbe\\x75\\x8c\\x77\\xa9\\x3f\\x65\\x80\\x6e\\x72\\x4a\\x4c\\x67\\x98\\x7a\\xc4\\xe3\\x00\\x73\\x46\\x28\\x98\\x98\\x56\\xf3\\x11\\xa6\\x7d\\xbc\\xef\\xbb\\x9b\\xd7\\x33\\xb2\\xbb\\xd5\\x9a\\xd1\\x93\\xa3\\xba\\x33\\x54\\x41\\x98\\x16\\xa8\\x50\\x18\\xa2\\xd4\\xee\\xa5\\xd8\\x25\\x41\\x47\\xdb\\x7b\\xef\\x23\\x1c\\x61\\x46\\xfc\\x08\\x35\\x55\\xc1\\xe2\\x23\\xc9\\xef\\x4b\\x6b\\xbc\\xdd\\xcd\\x63\\xf2\\x01\\xd5\\xee\\xd2\\x0c\\x6f\\x4e\\x49\\x4c\\xcd\\x6c\\x04\\x07\\x5f\\x3d\\xff\\xba\\x9d\\x96\\x89\\x9a\\xb6\\x95\\x30\\x47\\x63\\x48\\x89\\x11\\xfc\\xf4\\xe1\\x30\\xfa\\x11\\xa3\\x5f\\x2f\\x77\\xcb\\x1f\\xbe\\x8a\\xfe\\xf8\\xf3\\xfe\\xe8\\xf2\\xd9\\xca\\xd7\\xcb\\xbd\\x97\\x4f\\x1f\\x23\\xd8\\x37\\xc1\\x8f\\xd6\\x60\\x5f\\x1e\\x3d\\x77\\x82\\x72\\xdf\\x25\\x68\\x39\\x81\\x73\\x55\\xd0\\x3e\\xbc\\x46\\xae\\x69\\xbf\\xe3\\xa9\\x7c\\xef\\xab\\xcb\\x76\\x57\\x3b\\x54\\xde\\x4a\\x11\\xc1\\x8e\\x15\\xba\\xd3\\x45\\xe4\\x74\\xea\\xa2\\x2a\\x75\\x7a\\x0c\\xd7\\x3a\\xb2\\xfe\\x8e\\xb5\\xe4\\xd6\\x81\\xcb\\x6d\\xc9\\xc4\\x4a\\xcc\\xfa\\xf6\\xe7\\x44\\xca\\x98\\x6e\\x30\\xcb\\x39\\xc5\\x89\\xcc\\x86\\xf5\\xfd\\xae\\x14\\x10\\x45\\x11\\xbc\\x41\\xb1\\x80\\xb8\\xe6\\x1f\\x3b\\x89\\xeb\\x3b\\x4e\\x1b\\x12\\x06\\x30\\x51\\x52\\x77\\x9d\\x7e\\x4b\\xf4\\xc9\\xd9\\x15\\xc1\\xe1\\x1c\\x19\\xb7\\x09\\x7f\\x1f\\xc6\\x85\\xdd\\x49\\x09\\x16\\x9a\\x00\\xd5\\x98\\x19\\x85\\x6a\\xb1\\x82\\xb7\\x3a\\x18\\x27\\x28\\xec\\x46\\x2c\\x34\\x4d\\x0a\\x0e\\xbb\\x9a\\x08\\x62\\x21\\x53\\x8a\\x7d\\xf0\\x2d\\x4d\\xd0\\x7b\\xee\\xe0\\x05\\x1c\\x33\\xce\\xcc\\xa2\\x0b\\x54\\x48\\x48\\x29\\x91\\x62\\xc2\\x59\\x62\\x1c\\x82\\xcf\\x72\\xa9\\x0c\\x0a\\xe3\\x13\\x8a\\xa2\\x29\\xdd\\x00\\x33\\xbe\\x9f\\x41\\xba\\xfb\\xfc\\xdf\\x4d\\x85\\x3e\\x38\\x78\\xfe\\xe2\\xac\\x18\\xa7\\xae\\x90\\x79\\x9d\\x99\\xe1\\xde\\xcb\\xdd\\x5f\\x0a\\xe4\\x36\\xe3\\xbb\\xb2\\xe3\\x75\\x66\\xf6\\xfa\\x66\\x93\\x17\\x07\\xdf\\xf4\\xcc\\x11\\xbb\\x1f\\x7c\\x26\\xb8\\xdc\\xfd\\x10\\x95\\x3f\\x3d\\xab\\x2e\\xed\\xbd\\xdc\\xbd\\x88\\x5b\\xef\\xef\\x3d\\xb3\\x6a\\xae\\xe4\\x97\\xcb\\x0f\\xd1\\x32\\xb9\\xc4\\x97\\xcf\\xf6\\x5e\\xae\\xdc\\xdb\\x7b\\x94\\x54\\xc3\\x65\\x82\\xfc\\x3d\\x4d\\xb6\\xd8\\x13\\xa7\\xe5\\x12\\x48\\xa4\\x52\\xa4\\x73\\x29\\x52\\xed\\x7f\\x37\\xca\\x3e\\xf5\\xb2\\x64\\xb1\\xfb\\xa2\\xfb\\x64\\x71\\xd2\\x97\\x85\\xa5\\x85\\x77\\x75\\xb9\\x02\\xac\\x2c\\xa0\\x3a\\x21\\x44\\x3f\\x28\\xed\\xde\\xc5\\x70\\xbf\\x2c\\xd5\\x41\\xb4\\x32\\x57\\x4e\\xa4\\xa2\\x1e\\xd4\\x2b\\xce\\xd9\\xf9\\xd6\\x8a\\xa8\\x70\\xb6\\x93\\x57\\xa5\\x65\\x45\\x13\\x52\\x24\\x4c\\xbc\\xd3\\xc9\\x72\\x25\\xf8\\x9e\\xff\\xff\\x8b\\x4e\\xf2\\x65\\x00\\x3e\\xfd\\xed\\xa7\\x4f\\x0c\\xc1\\xa7\\x9d\\xd2\\x7a\\x46\\x96\\xeb\\x75\\x31\\x91\\x6e\\xe1\\xee\\xb3\\xd6\\x8a\\xad\\xc9\\xe3\\xdf\\x31\\x91\\x5a\\x87\\x5b\\x61\\x9f\\xe6\\xeb\\x6f\\xba\\x5d\\xdd\\x1b\\x38\\xac\\x81\\x07\\x8c\\x7e\\x3d\\x8c\\x7e\\x2c\\x9d\\x7e\\x18\\xfd\\x58\\xfb\\xbd\\xfc\\xb9\\x0b\\x39\\x6c\\xe9\\x78\\x81\\x5d\\x95\\xe2\\x7d\\x3f\\xba\\x2e\\x4b\\x19\\xb8\\x75\\xeb\\xe7\\xb3\\xc6\\xed\\x56\\xce\\xdc\\xd2\\xfa\\x86\\xbe\\x52\\xb7\\x0b\\x7c\\x83\\x69\\xc5\\x0f\\xfe\\xc2\\x7f\\x70\\x60\\x35\\xee\\xe6\\x47\\x0c\\xa9\\x1e\\x9d\\xf5\\x7e\\xfd\\xf5\\x0a\\xd3\\x59\\xcf\\xf6\\x20\\x71\\xce\\x1f\\x3c\\x5c\\x58\\xb4\\xa1\\x0b\\xd2\\x42\\x5c\\x56\\xc5\\x2d\\x14\\xbe\\x00\\x6c\\x21\\xf0\\x70\\xa8\\x85\\xc0\\xba\\x72\\xf0\\x60\\x3f\\x97\\x1d\\x4f\\xde\\xd4\\x47\\xde\\x14\\xd6\\x47\\x77\\xd6\\xf8\\x76\\x80\\x87\\x46\\xc3\\x1c\\xcd\\xac\\x7c\\xfe\\x65\\x7b\\xc5\\xd7\\xbb\\xd4\\x6c\\xc1\\x7a\\x7e\\x58\\x6a\\xe4\\x39\\x5c\\x2b\\x69\\xc8\\x9f\\xe3\\x25\\x34\\xf4\\x35\\xdd\\x0a\\x5c\\x68\\xe4\\x5d\\xc3\\x88\\xbb\\x66\\x96\\xf5\\xa0\\x14\\xd5\\x3b\\x92\\x47\\x1c\\xb5\\x8e\\xe1\\x42\\xc0\\x2b\\x0f\\xbc\\x9b\\x23\\xe0\\x62\\xa7\\xc2\\xe6\\x82\\xcc\\xb0\\x7c\\x35\\x2d\\x5a\\xf2\\xbf\\xd8\\x71\\x7c\\x2c\\xd6\\xf4\\x6d\\x1b\\x6f\\x57\\x0b\\xcc\\xf4\\xca\\x30\\x0d\\xc7\\x7f\\x7d\\x73\\x78\\xf2\\x16\\x2e\\x76\\x86\\x17\\x3b\\xf0\\xee\\xf0\\xfc\\x2f\\xfb\\xb6\\xaa\\x56\\x54\\xdd\\xb0\\x65\\xb6\\xbd\\xec\\x80\\xfc\\x1c\\x39\\x6b\\x2e\\x89\\x57\\xde\\x05\\x73\\x61\\x0f\\xbb\\x33\\x63\\x72\\x3d\\x1a\\x0e\\x97\\x23\\xc0\\x98\\xc9\\x61\\x2a\\x13\\x3d\\x4c\\xa4\\x48\\x28\\x37\\x7a\\x28\\xe7\\xf6\\xc4\\xa4\\xeb\\xe1\\xb5\\x54\\x57\\x4c\\x4c\\x23\\xeb\\xbf\\xc8\\x47\\x8f\\x1e\\x3a\\x46\\xc3\\x27\\xee\\xbf\\xbd\\x66\\x08\\x75\\x21\\x60\\x19\\x20\\x1a\\xde\\x7c\\x7f\\x76\\x0e\\xb9\\xcc\\x0b\\x8e\\xd5\\x63\\xf4\\x06\\xbb\\xc6\\xd9\\xb5\\x62\\xae\\x17\\x52\\x3e\\xd9\\x46\\xa6\\xab\\x1c\\xcb\\x69\\x15\\x09\\x5d\\x28\\xf2\\x31\\x42\\xc2\\x28\\xe6\\x5f\\x23\\x29\\x6b\\xd7\\x4a\\x62\\xda\\x19\\x1b\\x4c\\xc1\\x5a\\x40\\xbb\\x3a\\x89\\x13\\x0a\\x4a\\xa1\\xc8\\xeb\\x0e\\xdf\\xc2\\xdd\\x10\\xcd\\xe3\\x31\\x2e\\xc5\\x94\\x14\\x08\\x4a\\xec\\xb6\\x57\\x8b\\xc6\\xa4\\xde\\xeb\\x5c\\xeb\\x91\\xc1\\x7b\\xe6\\xed\\x6e\\x94\\x76\\x31\\x5c\\x56\\x02\\x17\\xc3\\x8b\\x28\\xfe\\xf9\\x9f\\xff\\xfb\\x3f\\x4f\\xff\\xef\\x8b\\xdd\\xbd\\x67\\x5f\\xee\\xff\\xe9\\xcf\\xa3\\xcb\\x2f\\x9f\\x0e\\x1e\\x98\\xf1\\xfd\\xcb\\x3c\\x2d\\x25\\xc1\\x9d\\xdc\\xf2\\xbe\\xa2\\xfe\\xa4\\x6a\\x60\\x7d\\xc2\\x74\\xbf\\x10\\x70\\x4d\\xfd\\x96\\x4a\\xa0\\x4f\\x0d\\xd0\\x89\\xfe\\xfb\\xe2\\xfe\\x47\\x45\\xfc\\xbd\\x31\\xd3\\xbf\\x0b\\xe5\\xf7\\xc2\\x04\\x5d\\xc8\\x7e\\x0b\\x4c\\xff\\x48\\x68\\xbe\\x2f\\xdc\\xea\\x09\\xb4\\x1e\\x0d\\xbb\\xf7\\x72\\x67\\x17\\x5e\\x7f\\x44\\xa4\\xde\\x3b\\xde\\x7a\\x3a\\xaa\\xb7\\x7d\\x9d\\x88\\xfc\\xb3\\x60\\xf1\\xcf\\x1a\\x16\\x0f\\x42\\xde\\x3d\\x1c\\xd6\\xfd\\x92\\x4a\\x0b\\x7c\\xee\\x06\\xce\\x1d\\x20\\xb3\\xe3\\x76\\x9b\\x72\\xd1\\x1a\\x70\\x1b\\x6c\\x86\\xd0\\xe5\\x99\\xd1\\xf1\\x6a\\x57\\xdf\\xb1\\xf7\\xbd\\xd7\\xb9\\x7e\\xbf\\x01\\xf8\\x3d\\x55\\x7e\\x8f\\x09\\x78\\x1f\\x25\\xc2\\x08\\x3c\\x8c\\xc0\\xc3\\x08\\x3c\\x8c\\xc0\\xc3\\x08\\x3c\\x8c\\xc0\\xc3\\x08\\x3c\\x8c\\xc0\\xc3\\x08\\x3c\\x8c\\xc0\\xc3\\x08\\x3c\\x8c\\xc0\\xc3\\x08\\x3c\\x8c\\xc0\\xc3\\x08\\x3c\\x8c\\xc0\\xc3\\x08\\x3c\\x8c\\xc0\\xc3\\x08\\x3c\\x8c\\xc0\\xc3\\x08\\x3c\\x8c\\xc0\\xc3\\x08\\x3c\\x8c\\xc0\\xc3\\x08\\x3c\\x8c\\xc0\\xc3\\x08\\x3c\\x8c\\xc0\\xc3\\x08\\xfc\\x33\\x8e\\xc0\\xef\\x49\\xd5\\xc5\\xb8\\x6e\\x66\\x56\\xb2\\xca\\xce\\x38\\xdc\\x7e\\x1c\\x00\\xe8\\x44\\xda\\x25\\x75\\xa0\\xa4\\xe5\\xdf\\xc0\\x29\\xa9\\x73\\x5e\\x28\\xe4\\xe5\\x1f\\xc4\\xf1\\x1c\\x99\\x98\\x16\\x1c\\x95\\xbf\\x36\\xa8\\xf3\\x98\\x1f\\x90\\xff\\x2b\\x00\\x00\\xff\\xff\\x68\\x0c\\xfc\\x1d\\xa7\\x67\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/templates/local-service-mirror.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"local-service-mirror.yaml\",\n\t\t\tmodTime:          time.Date(2025, 12, 12, 18, 49, 15, 368996883, time.UTC),\n\t\t\tuncompressedSize: 5677,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xec\\x58\\xdf\\x6f\\xdb\\xb6\\x13\\x7f\\xf7\\x5f\\x71\\x10\\xfa\\xf0\\xfd\\x02\\xa5\\xdc\\x02\\x43\\xd7\\x08\\xe8\\x43\\xda\\xb4\\xdd\\x80\\xb4\\x33\\xd2\\xa5\\xc0\\x30\\x14\\x03\\x2d\\x9d\\x1d\\x2e\\x14\\xc9\\x91\\x27\\x27\\x9e\\xeb\\xff\\x7d\\x20\\x45\\xcb\\x94\\x2d\\x3b\\xee\\x3a\\x0c\\x28\\xb0\\xbc\\x44\\x21\\xef\\xee\\x73\\xfc\\xdc\\x2f\\x32\\x8c\\xb1\\xd1\\xad\\x50\\x55\\x01\\xaf\\x64\\xe3\\x08\\xed\\x95\\x96\\x38\\xe2\\x46\\x7c\\x44\\xeb\\x84\\x56\\x05\\xd8\\x29\\x2f\\x73\\xde\\xd0\\x8d\\xb6\\xe2\\x4f\\x4e\\x42\\xab\\xfc\\xf6\\xb9\\xcb\\x85\\x1e\\x2f\\x9e\\x8e\\x6a\\x24\\x5e\\x71\\xe2\\xc5\\x08\\x40\\xf1\\x1a\\x0b\\x90\\x42\\xdd\\xa2\\xad\\x98\\xd4\\x25\\x97\\xcc\\xa1\\x5d\\x88\\x12\\x59\\x2d\\xac\\xd5\\x96\\xf1\\xb2\\x44\\xe7\\xe2\\x9e\\x45\\xa7\\x1b\\x5b\\xa2\\x1b\\x01\\x48\\x3e\\x45\\xe9\\xbc\\x15\\xd8\\x58\\xf0\\x08\\x78\\x4f\\xa8\\x5a\\x37\\xea\\x46\\x92\\x28\\x5b\\x1f\\x83\\x58\\xa9\\x6b\\xa3\\x15\\x2a\\x2a\\x60\\x08\\x2b\\xc8\\xac\\x56\\x0c\\xee\\x04\\xdd\\x40\\xfe\\x91\\xcb\\x06\\x5d\\x5e\\xea\\xba\\xd6\\xea\\x32\\xa0\\xc1\\x7a\\xbd\\x5a\\x01\\xe9\\x5f\\x78\\x2d\\x21\\x87\\xcf\\x40\\x56\\xd4\\xf0\\x19\\x94\\x50\\x15\\x2a\\x82\\xef\\x82\\x00\\x03\\x54\\x15\\xac\\xd7\\x23\\xdb\\x48\\x74\\xc5\\x88\\x01\\x37\\xe2\\xad\\xd5\\x8d\\x71\\x05\\xfc\\x9a\\x65\\x9f\\x46\\x00\\xdd\\x49\\xfc\\x0a\\xaa\\xca\\x68\\xa1\\xc8\\x65\\x8f\\x21\\x8b\\x3e\\xb9\\x20\\xb6\\x40\\x3b\\x0d\\x22\\x52\\x38\\xf2\\xbb\\x73\\x0c\\xbf\\xee\\x38\\x95\\x37\\xfe\\xa3\\xb4\\xc8\\x09\\xfd\\x57\\x85\\x12\\xdb\\xaf\\xc6\\x54\\x7e\\xed\\xd3\\xc3\\xc0\\x9e\\x7f\\x67\\xf8\\x69\\x68\\x7b\\xf6\\x4a\\xad\\x6d\\x25\\x54\\x1a\\xe0\\x7d\\x08\\x89\\xdc\\xed\\x9a\\xdf\\x3a\\x1d\\x01\\xa2\\xc7\\x8f\\x21\\x33\\xc3\\x50\\x69\\x28\\xf3\\x6d\\xb4\\x07\\xe0\\x84\\xba\\xfd\\x5b\\x87\\xf9\\x32\\x84\\xb1\\x23\\x4e\\xcd\\x0e\\xd0\\x96\\xf7\\xa1\\x0a\\x79\\x29\\x54\\x25\\xd4\\xfc\\xbf\\x42\\x19\\x2a\\x14\\x2d\\xf1\\x0a\\x67\\xde\\xc7\\x4d\\x50\\x8e\\x90\\x33\\x02\\xd8\\xef\\x3f\\x5f\\xc3\\x91\\x6b\\xa6\\xbf\\x63\\x49\\xa1\\x54\\x5b\\xcb\\x1f\\x5a\\xbd\\xf3\\xb2\\xd4\\x8d\\xa2\\x93\\x8c\\x47\\xa1\\x50\\x4e\\x05\\xac\\x56\\xf9\\x15\\x86\\xdc\\xcf\\xdf\\x6f\\x56\\xd7\\xeb\\x24\\x33\\x76\\x10\\xd2\\xac\\xf8\\xe2\\xd8\\xef\\x42\\xc3\\x3e\\xb6\\x67\\xf9\\x9b\\x48\\x05\\xff\\x29\\x54\\x29\\x9b\\x0a\\x7d\\x37\\xb0\\x24\\xb8\\x74\\xb9\\xa8\\xf9\\x1c\\x99\\x69\\xa4\\xc7\\x2f\\x2d\\x92\\xcb\\x3a\\xb0\\xb0\\x37\\x69\\xa4\\xfc\\xd0\\xee\\x40\\xe4\\x39\\xa5\\x94\\x1b\\xe3\\x7c\\x4d\\xb5\\xdc\\x5f\\xa0\\x91\\x7a\\x59\\xa3\\xa2\\x1e\\xd1\\xdf\\x00\\x3b\\xff\\x60\\x36\\x38\\x83\\x65\\x11\\x9a\\x9b\\x91\\xa2\\xe4\\xae\\x15\\x8d\\x2e\\x06\\xbb\\x31\\x45\\xdf\\x05\\xab\\xf9\\x46\\xae\\x75\\xc3\\xe2\\x42\\x78\\x4e\\x7e\\x10\\x8e\\xb4\\x5d\\x5e\\x8a\\x5a\\x50\\xc8\\xfa\\x68\\x60\\x68\\x3f\\x28\\x3a\\x94\\x58\\x92\\xb6\\x2d\\xcd\\xb5\\x6f\\xc6\\x97\\x09\\xef\\xa7\\x50\\x1a\\x52\\x64\\xd6\\xf9\\x8a\\x8a\\x4f\\x25\\x4e\\x74\\x75\\xae\\x48\\x9c\\xcf\\x66\\x42\\x09\\x5a\\xb6\\x5e\\x3a\\xb2\\x9c\\x70\\xbe\\x6c\\x6d\\x5b\\x2d\\xa5\\x50\\xf3\\xeb\\xd0\\xa6\\x37\\x70\\x35\\xbf\\xbf\\x56\\x7c\\xc1\\x85\\xf4\\x56\\x0a\\x78\\x1a\\x01\\x3a\\xc2\\x09\\x6b\\x23\\x3b\\x85\\x34\\x5f\\xfc\\x0f\\x57\\x4a\\x53\\x68\\x4e\\xdd\\x01\\x7a\\xc9\\x23\\x94\\xef\\x2c\\x05\\xb4\\x4e\\x56\\x9d\\x48\\x4c\\x22\\xc6\\x1b\\xd2\\xae\\xe4\\x12\\x6d\\x7e\\xdb\\x4c\\xd1\\x2a\\x24\\x0c\\x03\\xc0\\xf1\\x19\\x32\\xd2\\x0c\\x17\\xc2\\xeb\\x67\\x64\\x1b\\xcc\\xb6\\xea\\x5a\\xcd\\xc4\\x3c\\xe7\\xd2\\xdc\\xf0\\x64\\x5a\\x8d\\x8d\\xd5\\xf7\\x4b\\x76\\xc7\\x05\\xb1\\x29\\xce\\xb4\\x45\\x86\\xf7\\x82\\x7c\\xcd\\x68\\x55\\xb9\\x02\\xb2\\x27\\x5b\\x1b\\x7b\\x79\\x69\\x3c\\x85\\xdd\\x71\\x1e\\xca\\xcc\\xe7\\xbb\\x99\\x19\\x4e\\xde\\x0b\\xe5\\xc9\\x65\\x74\\x6a\\x29\\x1d\\x72\\xfb\\xb4\\x5a\\x1a\\xf0\\x78\\x53\\x04\\xa7\\x25\\x55\\x54\\xea\\x3c\\x78\\x44\\x16\\x11\\x8a\\x17\\x50\\x21\\x9a\\x57\\xda\\x2c\\x21\\x87\\x44\\xe6\\xd1\\x6f\\x7e\\xcf\\x21\\x45\\xc1\\xac\\x3b\\x62\\x06\\xd9\\xd0\\x19\\x33\\x60\\xc7\\xd4\\x03\\xb9\\x59\\xcf\\x4c\\x2a\\xdf\\x75\\xcd\\x0d\\xe9\\x3c\\xba\\x9d\\x45\\x03\\x5b\\x26\\x9e\\xa5\\x6e\\x26\\x74\\xec\\xc5\\xd3\\xa7\\x67\\xed\\x07\\x54\\x7f\\x5c\\xfd\\xac\\x6f\\x51\\x15\\x30\\xe3\\xd2\\x61\\x57\\xb6\\x8a\\xb8\\x50\\x68\\xbb\\xe8\\x33\\xe0\\x76\\x9e\\xe4\\x02\\x83\\x03\\x11\\x65\\xc0\\xa4\\x9e\\x33\\x89\\x0b\\x94\\x2f\\xb6\\x2d\\x64\\xa0\\x07\\x49\\x3d\\xbf\\xf4\\x52\\x9d\\x7f\\x9d\\xf2\\x4c\\xdb\\x9a\\xd3\\x83\\xda\\x6f\\x82\\x58\\x5f\\x1d\\x17\\xa8\\x88\\x59\\xfc\\xa3\\xc1\\x06\\x99\\xf4\\x9d\\xea\\xb8\\x1d\\x97\\xfe\\x75\\x85\\x94\\xb6\\xb7\\xce\\x6a\\xd7\\x82\\x5f\\x1c\\xb8\\x0a\\xa4\\x1e\\x84\\x64\\x63\\xc6\\x58\\x3d\\x3b\\x0e\\x1d\\xd3\\xd2\\x0b\\xc2\\x67\\xa8\\x70\\xc6\\x1b\\x49\\x6d\\x1c\\x76\\x39\\xf1\\xe9\\xb5\\x4f\\xf4\\x0c\\x2b\\xf4\\x3d\\xb1\\xea\\x52\\x6f\\xd3\\x90\\x8f\\x03\\x77\\x7a\\x71\\xf9\\x43\\xd4\\x4a\\x50\\x63\\x05\\x69\\x7b\\x6c\\x8a\\xf0\\xaa\\x12\\xbe\\xbd\\x70\\xf9\\x5a\\x2d\\x8e\\x09\\xe2\\xbd\\x41\\x2b\\xfc\\x8c\\x6e\\x45\\x13\\x20\\x54\\x8b\\xe2\\x70\\x3b\\x78\\x10\\x74\\xc7\\xe5\\xa4\\x5f\\x6c\\x1b\\x05\\xdb\\x11\\xea\\x15\\xc5\\x69\\xa0\\x47\\x0e\\xf0\\x35\\xb0\\x3b\\x4b\\xe1\\xf2\\x93\\x0e\\xde\\x01\\x4f\\x82\\x4c\\xee\\x13\\x72\\xbd\\x2e\\x4e\\x90\\x5c\\xb4\\xb7\\xa6\\x04\\xa5\\xbd\\x74\\x1c\\x28\\x5f\\x87\\x65\\x63\\x05\\x2d\\x5f\\x69\\x45\\x78\\x4f\\xdb\\xd0\\x00\\x70\\x29\\xf5\\xdd\\xc4\\x8a\\x85\\x90\\x38\\xc7\\xd7\\x7e\\xd0\\x85\\xe1\\xd2\\x6f\\x1d\\xa1\\x7d\\x70\\xc3\\xa7\\x42\\x0a\\x12\\xe8\\x52\\x0b\\x00\\x95\\xd5\\xa6\\xbf\\xc2\\xe0\\xfc\\xf2\\x32\\x59\\xb1\\xc8\\xab\\x9f\\x94\\x5c\\x5e\\x69\\x4d\\x6f\\x84\\x44\\xb7\\x74\\x84\\x75\\x01\\x7e\\x64\\xa6\\x62\\x8d\\x3a\\x77\\xef\\xb5\\xf2\\x62\\xc3\\x9b\\xd7\\x0e\\xed\\x03\\x5c\\x5e\\xff\\x78\\x91\\xf0\\x12\\xf5\\xe2\\x83\\xe5\\xa8\\xe2\\xdb\\x1d\\x45\\x87\\xa5\\xef\\xe3\\x13\\xab\\x67\\x42\\x62\\xff\\x80\\xb4\\x34\\x58\\xc0\\x55\\xa3\\x48\\xd4\\x78\\xd1\\x16\\x79\\x27\\xb0\\xd0\\xb2\\xa9\\xf1\\x9d\\x6f\\xc3\\xbd\\xe6\\x1a\\xda\\xf4\\x84\\xd3\\x4d\\x01\\xe3\\x05\\xb7\\x63\\xdb\\xa8\\x71\\xbc\\x2b\\x8f\\x77\\xae\\x16\\xad\\x63\\xbc\\x7b\\xdc\\xf4\\xc3\\xec\\x85\\x19\\x37\\x22\\xbe\\x96\\x06\\x88\\xde\\x61\\xcf\\x68\\xdb\\x77\\xa5\\x9b\\x05\\x13\\x6d\\xa9\\x80\\xb3\\xb3\\xb3\\xb3\\x3d\\x14\\x5e\\xd5\\x42\\xb1\\x1b\\x22\\xf3\\x45\\x55\\xd5\\x3d\\xda\\xd2\\x22\\x48\\xde\\xe7\\xfd\\x0b\\xc0\\xa6\\xb2\\x9e\\x3e\\x39\\x5a\\x46\\x07\\x33\\xf8\\x70\\x90\\x8e\\x84\\xc8\\xf5\\x46\\xe5\\xfb\\x53\\xee\\xeb\\xdb\\xb8\\x26\\xd3\\x73\\xf8\\x25\\x14\\xc5\\xf2\\x9a\\xab\\xc6\\x77\\x78\\x8f\\xd1\\x19\\x8c\\x21\\x65\\xe4\\xe7\\x73\\x16\\x28\\xe8\\x7a\\x4b\\x7b\\x1d\\x9a\\x58\\x9c\\x89\\x7b\\xf8\\x9f\\x45\\x83\\x9c\\xe0\\x7b\\xc8\\x20\\xfb\\xff\\x96\\x89\\xbd\\x10\\x28\\x5d\\x75\\xbd\\x7e\\x2b\\x96\\xae\\x1e\\xe2\\xfc\\x79\\xdf\\x6a\\x8f\\xf0\\x3d\\x18\\xd2\\xd2\\x8f\\x97\\x78\\xef\\x8c\\x52\\xc9\\xe2\\x21\\x90\\x67\\x83\\x20\\xa7\\x3e\\x10\\xb6\\xaf\\xf1\\x89\\xae\\x2e\\x84\\xb3\\x8d\\xf1\\x68\\x2f\\x9b\\x6a\\x8e\\xfd\\x27\\xb9\\xd1\\x52\\x94\\xcb\\xf1\\xbf\\xf3\\x32\\x7f\\xf8\\x26\\xbc\\xf7\\xea\\x58\\xad\\x06\\x52\\x25\\x91\\xca\\xdb\\x7f\\xb9\\x55\\x6c\\xba\\xcc\\xda\\x4b\\xea\\xe6\\xe6\\x3b\\xf4\\xfe\\xf9\\xea\\x37\\x5a\\x8c\\xc5\\x7a\\x3d\\xfa\\x2b\\x00\\x00\\xff\\xff\\xc4\\x94\\xe5\\x8b\\x2d\\x16\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/templates/namespace-metadata-rbac.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"namespace-metadata-rbac.yaml\",\n\t\t\tmodTime:          time.Date(2025, 4, 24, 21, 9, 23, 550666848, time.UTC),\n\t\t\tuncompressedSize: 2497,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xec\\x95\\x4f\\x6b\\xdb\\x4c\\x10\\xc6\\xef\\xfa\\x14\\xc3\\x9e\\xb3\\xca\\x1b\\x78\\x0f\\x45\\xb7\\xf6\\x52\\x28\\x6d\\x29\\x09\\x04\\x4a\\xc9\\x61\\xb5\\x1a\\x5b\\x53\\x8f\\x76\\xc5\\xee\\xac\\x93\\x54\\xd1\\x77\\x2f\\x5a\\x2b\\xae\\x8d\\x4d\\x5a\\xda\\x1c\\x7c\\xf0\\xc5\\x58\\x3b\\x7f\\xf7\\x79\\x7e\\x42\\xc3\\xa0\\x81\\x16\\x50\\xde\\x1a\\x4e\\x18\\x4b\\x1b\\xd0\\x08\\x7e\\x36\\x1d\\xc6\\xde\\x58\\xfc\\x84\\x62\\x1a\\x23\\xe6\\x83\\xaf\\xc7\\xb1\\x58\\x91\\x6b\\x2a\\xb8\\xc1\\xb0\\x26\\x8b\\x6f\\xad\\xf5\\xc9\\x49\\x61\\x7a\\xba\\xc5\\x10\\xc9\\xbb\\x0a\\xd6\\x57\\x45\\x37\\x57\\x54\\x05\\x00\\x9b\\x1a\\x39\\x4e\\xff\\x00\\x98\\xdc\\x0a\\x43\\x53\\x92\\xbf\\xc4\\x07\\x41\\xb7\\x29\\xe8\\x12\\x0b\\x59\\x4e\\x51\\x30\\xe4\\xb4\\x69\\x9d\\x7b\\x92\\xf6\\xd7\\x42\\xbe\\xeb\\xbc\\xfb\\x98\\x3b\\xc1\\x38\\x0e\\x03\\x88\\xff\\x6a\\x3a\\x86\\x12\\x9e\\x40\\x02\\x75\\xf0\\x04\\x8e\\x5c\\x83\\x4e\\xe0\\xff\\x9c\\xa0\\x01\\x5d\\x03\\xe3\\x58\\x00\\x18\\xe7\\xbc\\x18\\x21\\xef\\xe6\\x2d\\x86\\x01\\xc8\\x59\\x4e\\x0d\\x82\\xea\\x4d\\x10\\x32\\x1c\\xcb\\x9d\\xac\\xf9\\xfe\\x8d\\xae\\x1f\\x15\\x94\\x9b\\x26\\x00\\xaa\\x45\\xee\\xca\\xd8\\x5e\\xb6\\xde\\xaf\\x54\\x05\\xbd\\x8f\\xa2\\xc9\\x45\\x31\\xcc\\x87\\x09\\xfa\\x1e\\x69\\xd9\\x8a\\xaa\\x40\\xfd\\xa7\\x8e\\x84\\x1b\\x64\\x14\\xd4\\xbd\\x67\\xb2\\x8f\\xaa\\x82\\x1a\\x17\\x3e\\xa0\\xce\\xb1\\x3c\\x9e\\xbc\\xbb\\xc8\\x4f\\x31\\x59\\x8b\\xd8\\x60\\x53\\x00\\x38\\xd3\\x61\\x95\\x7f\\xb3\\x31\\xfa\\x59\\xe7\\x39\\x94\\x0f\\x2b\\x18\\x86\\xf2\\x1a\\x19\\x4d\\xc4\\x72\\xeb\\xe1\\x38\\x16\\xd9\\xe4\\x83\\x7b\\x53\\x67\\x96\\xa8\\xfb\\xc4\\xac\\x23\\xda\\x80\\x12\\xd5\\x56\\xf6\\x1c\\xfb\\x92\\x98\\x6f\\x36\\x91\\x49\\x0a\\xad\\xf5\\x9e\\xdb\\xa1\\x36\\xb6\\x34\\x49\\x5a\\x1f\\xe8\\x47\\x5e\\xbb\\x5c\\xbd\\x89\\x93\\xc3\\xeb\\xab\\x99\\x95\\x6b\\xcf\\x78\\x46\\xe2\\x14\\x91\\x08\\x89\\x31\\x56\\x85\\x06\\xd3\\xd3\\xfb\\xe0\\x53\\x1f\\x2b\\xf8\\xa6\\xd4\\x5d\\x01\\x10\\x30\\xfa\\x14\\x2c\\xe6\\x93\\x6d\\xa7\\x98\\x63\\x6b\\x0c\\x75\\x3e\\x5f\\xa2\\xa8\\x8b\\x49\\x31\\xb1\\xed\\x5e\\x55\\x1e\\x32\\x65\\x1c\\x1f\\xac\\xee\\xfe\\x96\\xa3\\x77\\xe4\\x1a\\x72\\xcb\\x33\\x4e\\x27\\x89\\x93\\x67\\xbc\\xc6\\xc5\\xa4\\xc1\\xce\\x9b\\xff\\x62\\xd3\\x67\\xf0\\x5e\\x00\\xa0\\x88\\xa9\\xfe\\x8e\\x56\\x32\\xa8\\x47\\xbf\\x3e\\xff\\xb8\\xf6\\x2b\\xa2\\xb8\\x37\\x6c\\x0b\\xd7\\x0c\\xe5\\x76\\xe6\\xc6\\xf0\\x33\\xb6\\xaf\\x8d\\x6d\\x67\\xf5\\x21\\x02\\x7a\\xd6\\x56\\x5b\\xef\\x16\\xb4\\xfc\\x0d\\xa4\\xf8\\x20\\x7f\\xd0\\xe3\\x34\\xb8\\xdd\\x31\\xf3\\x67\\x00\\x00\\x00\\xff\\xff\\x03\\x2b\\xd5\\x72\\xc1\\x09\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/templates/namespace-metadata.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"namespace-metadata.yaml\",\n\t\t\tmodTime:          time.Date(2025, 12, 12, 18, 49, 15, 368996883, time.UTC),\n\t\t\tuncompressedSize: 2675,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xb4\\x96\\x51\\x6f\\xdb\\x36\\x10\\xc7\\xdf\\xfd\\x29\\x0e\\x7a\\xda\\x80\\x51\\x46\\x81\\x61\\x1b\\xf4\\x16\\xac\\x5b\\xb1\\x22\\xcd\\x8c\\x74\\x2d\\xb0\\xc7\\x13\\x75\\xb6\\x39\\x53\\x24\\x41\\x1e\\x9d\\x18\\x8e\\xbe\\xfb\\x20\\x4a\\x96\\x69\\xc7\\x4e\\xda\\x02\\x7d\\xf4\\xe9\\x4f\\xde\\xf1\\x7f\\x3f\\x1e\\xbd\\xdf\\x0b\\x50\\x4b\\x28\\x3f\\xa3\\x8e\\x14\\x4a\\xe9\\x09\\x99\\xee\\xb0\\xa5\\xe0\\x50\\xd2\\x07\\x62\\x6c\\x90\\xf1\\xbd\\xad\\xbb\\x6e\\x86\\x4e\\x7d\\x26\\x1f\\x94\\x35\\x15\\xd4\\xc8\\x72\\x3d\\xdf\\xbe\\x99\\x6d\\x94\\x69\\x2a\\x78\\x6f\\xeb\\x59\\x3b\\x8a\\xab\\x19\\x00\\x1a\\x63\\x19\\x59\\x59\\x13\\xfa\\x9f\\x00\\xfb\\x3d\\x28\\x23\\x75\\x6c\\x08\\x0a\\x87\\x9e\\x15\\xea\\x50\\x66\\xaa\\x31\\x73\\x23\\xea\\x5d\\x01\\x25\\x74\\x5d\\x5a\\x55\\xac\\x49\\xb7\\x65\\x58\\xcf\\xd7\\xd6\\x6e\\x8a\\x0a\\x9c\\x0d\\x2c\\x94\\x09\\x8c\\x5a\\x3f\\x17\\x88\\x07\\x52\\xab\\x35\\x17\\x15\\x14\\x6f\\x8a\\x0b\\x9f\\x1b\\xd2\\xc4\\x24\\x9c\\xd5\\x4a\\xee\\x8a\\x0a\\x6a\\x5a\\x5a\\x4f\\x22\\x7d\\x4b\\xe9\\x95\\x35\\x3f\\xa5\\x5f\\x21\\x4a\\x49\\xd4\\x50\\x33\\x03\\xd0\\x58\\x93\\x1e\\x4f\\xa1\\x95\\xd9\\x90\\x6f\\x4a\\x65\\xe7\\xf4\\xc8\\x64\\x06\\x2f\\xda\\xa8\\x59\\x49\\x1d\\x03\\x93\\x4f\\x32\\x74\\xae\\xdc\\xc4\\x9a\\xbc\\x21\\xa6\\xd0\\xab\\x0d\\xb6\\x54\\x81\\x39\\xf8\\x2a\\x0e\\x5e\\x5d\\x91\\xf7\\x0e\\x09\\xbb\\xac\\xe0\\x76\\x48\\x78\\x45\\xb6\\x3d\\x74\\x63\\xbf\\x6f\\x68\\x89\\x51\\xf3\\xd4\\xc8\\xb1\\xd2\\xb1\\x5f\\xc7\\xfe\\xea\\x43\\x0b\\x47\\x87\\xfb\\xfe\\x3f\\x28\\x5e\\x1f\\x15\\xb6\\x6d\\xad\\xb9\\x4d\\x87\\x86\\xae\\xdb\\xef\\x81\\xed\\xbf\\xd8\\x6a\\x28\\xe1\\x09\\xd8\\xab\\x16\\x9e\\xc0\\x28\\xd3\\x90\\x61\\xf8\\x39\\x09\\x04\\x90\\x69\\x86\\x8e\\xbd\\x70\\xcc\\x29\\xd8\\x57\\x5b\\xde\\x93\\x26\\x0c\\x54\\x4e\\xa4\\x75\\xdd\\x2c\\x38\\x92\\xbd\\xcd\\x4c\\xad\\xd3\\xc8\\x34\\x58\\x9e\\x53\\x95\\x5c\\x38\\x27\\xeb\\xdb\\xe9\\x3a\\xeb\\xa8\\x32\\xff\\x91\\xe4\\x0a\\x1a\\x15\\xb0\\xd6\\xd4\\x8c\\x9a\\xbc\\xff\\x5f\\xc1\\xc0\\x37\\x70\\xf0\\x15\\x2c\\x7c\\x37\\x1e\\x2e\\x32\\xe1\\x6c\\xf3\\x65\\x40\\xfc\\x76\\x0e\\x04\\xc0\\xa1\\xab\\x17\\x37\\x36\\xe7\\x83\\xa6\\x34\\xb6\\xa1\\x8f\\xa4\\x49\\xb2\\xf5\\xc7\\x3e\\xe5\\xd1\\xfe\\x74\\x79\\x09\\x79\\xee\\x2c\\x4f\\x56\\xc2\\x17\\x25\\x66\\xab\\xc9\\x0f\\xb4\\x1c\\xd7\\x65\\xc1\\x6b\\x69\\x7f\\x79\\x21\\xad\\xa7\\xc0\\xe8\\x79\\x91\\x06\\x4e\\x05\\x77\\xb4\\x9d\\xe0\\x08\\x24\\xa3\\x57\\xbc\\xfb\\xdd\\x1a\\xa6\\x47\\x3e\\xe2\\x15\\x48\\x4a\\xdb\\xba\\x85\\xb7\\x4b\\xa5\\xe9\\x18\\x07\\xe0\\x9d\\xa3\\x0a\\xee\\xa3\\x61\\xd5\\xd2\\xdb\\xa1\\xbf\\xd3\\x6e\\x7e\\xab\\x24\\xdd\\x48\\x69\\xa3\\xe1\\xbb\\x97\\x21\\xc3\\xc8\\xb6\\xed\\x75\\x1f\\x4f\\x56\\xfd\\x63\\x37\\x64\\x2a\\x58\\xa2\\x0e\\x34\\x2a\\xa5\\x35\\x8c\\xca\\x90\\x9f\\xf0\\x17\\xf0\\x2a\\xc1\\xaa\\xc5\\xd5\\x70\\xc9\\xaf\\x7a\\x9d\\x24\\xa5\\xa7\\x95\\x0a\\xec\\x77\\x5d\\x37\\x7f\\x5d\\xdc\\xc7\\xbb\\xae\\x7a\\x5d\\xc8\\xb8\\xca\\x50\\x4e\\xb1\\x45\\xd4\\xfa\\xd0\\x83\\xd7\\x37\\x70\\x93\\x1a\\x9e\\xe0\\xfc\\x1a\\x9d\\xed\\x97\\x65\\xba\\xda\\x50\\x00\\xd4\\xda\\x3e\\x2c\\xbc\\xda\\x2a\\x4d\\x2b\\xfa\\x23\\x48\\xd4\\x89\\xa9\\x53\\xb3\\x93\\xe1\\xe8\\xb0\\x56\\x5a\\xb1\\xa2\\x90\\xef\\x00\\xd0\\x78\\xeb\\x4e\\x23\\x02\\x6e\\x6e\\x6f\\xb3\\x88\\x27\\x6c\\xfe\\x36\\x7a\\x77\\x6f\\x2d\\xff\\xa9\\x34\\x85\\x5d\\x60\\x6a\\x2b\\x60\\x1f\\xf3\\x14\\x3e\\x9a\\x9b\\x70\\x67\\x4d\\x2f\\xbb\\xfc\\xf1\\x53\\x20\\x9f\\x1b\\xb5\\x42\\xa6\\x07\\xdc\\x95\\x9f\\xfe\\x7a\\x9b\\x9d\\x77\\x14\\xbf\\xf3\\x36\\xba\\x4b\\xea\\x77\\x67\\xea\\xeb\\x5c\\xbf\\x48\\x36\\xc0\\xd6\\xea\\xd8\\xd2\\x87\\x1e\\xd1\\xcc\\x13\\x01\\x09\\xe1\\x05\\xf2\\xba\\x82\\xf9\\x16\\xfd\\xdc\\x47\\x33\\x0f\\x24\\x3d\\x71\\x98\\x9f\\x8e\\xc5\\xf1\\x76\\xe0\\xc0\\x79\\x96\\x79\\x60\\xb9\\x17\\x0b\\x74\\x4a\\xa0\\x94\\x14\\xc2\\x05\\x4b\\xcf\\x7c\\x42\\xbf\\x3a\\xa9\\x44\\x88\\xe9\\x21\\xc8\\xeb\\xbb\\xf4\\x22\\xf4\\xe2\\x09\\xbc\\x2c\\x7a\\xe5\\x49\\xcc\\x97\\x8d\\x13\\xfc\\xda\\xf2\\xd3\\x39\\xff\\x7c\\x93\\xc1\\xc6\\xec\\x22\\xa7\\x7f\\x7e\\xcf\\x1e\\xcd\\x51\\x56\\xb6\\x68\\x22\\x6a\\x91\\x4c\\x16\\xa3\\x81\\x62\\x74\\x50\\x70\\x3f\\x2a\\x8a\\x34\\x06\\xa7\\xe1\\x3b\\xbc\\x05\\x0b\\x4f\\x4b\\xf5\\x08\\x3f\\x78\\x72\\x84\\x0c\\xbf\\x42\\x01\\xc5\\x8f\\xfd\\x40\\xcc\\x66\\xe3\\xff\\x01\\x00\\x00\\xff\\xff\\x3d\\x4d\\x01\\x75\\x73\\x0a\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/templates/namespace.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"namespace.yaml\",\n\t\t\tmodTime:          time.Date(2025, 4, 24, 21, 9, 23, 550666848, time.UTC),\n\t\t\tuncompressedSize: 370,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\x44\\x90\\xb1\\x6a\\xc3\\x40\\x10\\x44\\x7b\\x7d\\xc5\\xe0\\xd2\\x20\\x99\\xb4\\x6a\\x43\\x8a\\x40\\x48\\x91\\x80\\xfb\\xd5\\xdd\\xd8\\x2c\\x3e\\x9d\\xe4\\xdb\\x93\\x49\\x38\\xf4\\xef\\xe1\\xe4\\x24\\xee\\x96\\xe5\\xcd\\xec\\xcc\\x96\\xd2\\x42\\x4f\\xe0\\x15\\xdd\\x07\\x03\\xc5\\xd8\\x7d\\x32\\xdd\\xd4\\x11\\xbb\\xe7\\xb7\\xd7\\x1d\\xda\\x75\\x6d\\x2e\\x1a\\x7d\\x8f\\x77\\x19\\x69\\xb3\\x38\\x36\\x32\\xeb\\x91\\xc9\\x74\\x8a\\x3d\\x6e\\x4f\\xcd\\xc8\\x2c\\x5e\\xb2\\xf4\\x0d\\x10\\x65\\x64\\x8f\\x52\\x1e\\x76\\xff\\x32\\xac\\x6b\\x03\\x04\\x19\\x18\\xac\\xa2\\x40\\xd0\\x78\\x61\\xf2\\x9d\\x4e\\x07\\x7e\\x65\\xc6\\xbb\\xe3\\xb8\\x84\\xac\\x2e\\x2c\\x96\\x99\\x36\\xac\\x66\\x3c\\xec\\xff\\xe8\\x56\\xa3\\x66\\x24\\x5e\\x17\\x4d\\x34\\x6c\\x42\\x4f\\x0f\\x27\\xb3\\x0c\\x1a\\x34\\x2b\\x0d\\x12\\x3d\\x6c\\x7a\\x50\\x73\\xd2\\x9b\\x06\\xfa\\x33\\x3d\\xc6\\xc9\\x13\\xfb\\xc3\\x96\\x06\\x98\\x27\\xdf\\x1a\\xdd\\x92\\x34\\x7f\\x77\\x97\\x65\\x60\\x8a\\xcc\\xb4\\x2d\\x53\\x3c\\x4d\\xc9\\xdd\\xeb\\xe8\\x09\\xdd\\x51\\xc2\\x42\\xeb\\x5c\\xd4\\x97\\x28\\x43\\xa0\\xc7\\xba\\x26\\x5a\\x4e\\xea\\x32\\x7d\\x29\\x60\\xb0\\xda\\xf2\\xf7\\xd8\\xf9\\xbe\\x8b\\x15\\x6b\\x4a\\xa9\\x43\\x7d\\xe6\\x4f\\x00\\x00\\x00\\xff\\xff\\xd2\\x93\\x4a\\x91\\x72\\x01\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/templates/psp.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"psp.yaml\",\n\t\t\tmodTime:          time.Date(2025, 4, 24, 21, 9, 23, 550666848, time.UTC),\n\t\t\tuncompressedSize: 1026,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xb4\\x93\\x4f\\x8b\\x14\\x31\\x10\\xc5\\xef\\xf9\\x14\\x75\\x9b\\x8b\\x89\\x08\\x1e\\xa4\\x6f\\x7a\\xf1\\x22\\xb2\\xcc\\xc2\\x82\\x88\\x87\\xea\\x74\\xed\\x6e\\x39\\xe9\\x4a\\x93\\x3f\\xb3\\x8e\\xd9\\x7c\\x77\\xe9\\xcc\\x74\\xbb\\x83\\xe2\\x88\\xb8\\xb7\\xa6\\xa8\\xfa\\xf5\\x7b\\xf5\\x2a\\xa5\\x00\\xdf\\x82\\xb9\\x41\\x97\\x29\\x1a\\x12\\xec\\x1d\\x5d\\x5d\\x5f\\x81\\xae\\x55\\x69\\xad\\x15\\x4e\\x7c\\x43\\x21\\xb2\\x97\\x0e\\x42\\x8f\\xd6\\x60\\x4e\\xf7\\x3e\\xf0\\x77\\x4c\\xec\\xc5\\xec\\xde\\x44\\xc3\\xfe\\xe5\\xfe\\x95\\xda\\xb1\\x0c\\x1d\\x6c\\xbd\\x23\\x35\\x52\\xc2\\x01\\x13\\x76\\x0a\\x40\\x70\\xa4\\x0e\\xa6\\x38\\x9d\\xbe\\xe3\\x84\\x96\\x3a\\x28\\x05\\xcc\\x96\\x1c\\x61\\x24\\xf3\\x71\\x29\\x43\\xad\\x0a\\xc0\\x61\\x4f\\x2e\\xce\\xb3\\x00\\x8e\\x65\\x47\\x61\\x98\\x7f\\x41\\xdf\\x12\\xc9\\x51\\xc7\\x98\\x5d\\x62\\xeb\\x72\\x4c\\x14\\x5a\\x5b\\x29\\x1a\\x1e\\x38\\xdd\\xaf\\x3e\\xac\\x1f\\x47\\x2f\\x1f\\x1a\\x09\\x6a\\x2d\\x05\\x92\\xff\\x84\\xa3\\x03\\x03\\x8f\\x90\\x02\\x8f\\xf0\\x08\\xc2\\x32\\x90\\x24\\x78\\xdd\\x1a\\x34\\x90\\x0c\\xb3\\x80\\x90\\x1d\\xc5\\x4e\\x69\\xc0\\x89\\xdf\\x07\\x9f\\xa7\\xd8\\xc1\\xe7\\xcd\\xe4\\x1d\\xdb\\xc3\\xe6\\x05\\x6c\\x56\\x1d\\x71\\xf3\\x45\\x01\\x04\\x8a\\x3e\\x07\\x4b\\xa7\\xae\\x21\\x92\\xcd\\x81\\xd3\\xa1\\x0d\\x30\\x1d\\x9b\\xf6\\x14\\xfa\\xd6\\x90\\x23\\x9d\\x4d\\x35\\xeb\\xb3\\x55\\xbd\\x58\\xd5\\xa5\\x2c\\x26\\x4e\\x95\\x75\\x3d\\xb5\\x6a\\xeb\\x25\\x05\\xef\\xf4\\xe4\\x50\\xe8\\x5f\\xf3\\x79\\xc7\\x32\\xb0\\xdc\\xfd\\x26\\xa6\\x45\\xc3\\xd3\\x05\\xeb\\x67\\xce\\xee\\x0c\\xfc\\x2b\\xb7\\x61\\xff\\x73\\xc2\\xde\\xd1\\x96\\x6e\\x67\\x95\\x4f\\x8e\\xf6\\xfc\\x54\\x97\\xf0\\xff\\xb0\\x54\\x15\\x73\\xff\\x95\\x6c\\x6a\\xc7\\x72\\x04\\x5d\\x53\\xd8\\xb3\\xa5\\xb7\\xd6\\xfa\\x2c\\x69\\x45\\xfe\\x8c\\xf4\\x0e\\x13\\x3d\\xe0\\xc1\\xcc\\xf5\\xe6\\xec\\xb2\\xfb\\x0b\\xec\\x15\\xa0\\x97\\x38\\xff\\x8a\\x5a\\x4a\\xdb\\xc6\\xfc\\xca\\x7f\\x04\\x00\\x00\\xff\\xff\\x9e\\x4c\\x3c\\x36\\x02\\x04\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/templates/remote-access-service-mirror-rbac.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"remote-access-service-mirror-rbac.yaml\",\n\t\t\tmodTime:          time.Date(2025, 4, 24, 21, 9, 23, 550666848, time.UTC),\n\t\t\tuncompressedSize: 2432,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xdc\\x56\\x41\\x6b\\x1b\\x3b\\x10\\xbe\\xef\\xaf\\x18\\x16\\x1f\\xde\\x83\\xec\\x3e\\x1e\\xbc\\xc3\\x63\\x21\\x87\\xb6\\x87\\x52\\x48\\x43\\x49\\x20\\x50\\x4a\\x0e\\xb3\\xda\\xb1\\x33\\xb1\\x56\\x12\\xd2\\xc8\\xa9\\xbb\\xf1\\x7f\\x2f\\xd2\\xda\\x8e\\x63\\xa7\\x21\\x75\\x7b\\x69\\x4e\\x96\\x47\\x33\\xdf\\xf7\\xcd\\x78\\x66\\xe4\\x61\\xe0\\x29\\xd4\\x57\\xa8\\x23\\x85\\xda\\x53\\x6f\\x85\\x3e\\xb2\\xf7\\xd6\\x5f\\x92\\x5f\\xb0\\xa2\\x37\\x4a\\xd9\\x68\\x04\\xaa\\xd5\\xaa\\x18\\x86\\x0a\\x26\\x06\\x7b\\x0a\\xd0\\x9c\\xbe\\x20\\xe8\\x1c\\x7b\\xda\\x06\\xf2\\x14\\x8c\\x15\\xf8\\x6b\\xce\\xa6\\xfb\\x10\\xa0\\x0c\\x9a\\x15\\x95\\x2f\\x44\\xf9\\x3b\\xc3\\x00\\xec\\x28\\x38\\x85\\xe0\\x34\\xcb\\x19\\x07\\x81\\xf2\\xa4\\xfc\\x59\\x39\\x64\\xba\\xed\\xd9\\xa3\\x99\\xd1\\x06\\x37\\x19\\xab\\xaa\\x2a\\xd0\\xf1\\x15\\xf9\\xc0\\xd6\\x34\\xe0\\x5b\\x54\\x35\\x46\\xb9\\xb1\\x9e\\xbf\\xa1\\xb0\\x35\\xf5\\xfc\\xff\\x50\\xb3\\xfd\\x67\\xf1\\x6f\\x91\\x12\\x6a\\xe0\\x9d\\x8e\\x41\\xc8\\x5f\\x58\\x4d\\x45\\x4f\\x82\\x1d\\x0a\\x36\\x05\\x40\\x02\\x6d\\x60\\x18\\xea\\xac\\x5f\\x63\\x4b\\x3a\\x24\\x3b\\x80\\x66\\x33\\x27\\xdf\\x25\\x14\\xfa\\x2a\\x64\\x46\\xaa\\x3e\\x6a\\x61\\x35\\x82\\x65\\xb7\\x24\\xf0\\x8e\\xe5\\x06\\x26\\x9b\\x0c\\x95\\xed\\x7b\\x6b\\xce\\x32\\x14\\xac\\x56\\xc3\\x00\\x62\\x3f\\x63\\xaf\\xa1\\x86\\x7b\\x10\\xcf\\x3d\\xdc\\x83\\x61\\xd3\\x91\\x11\\xf8\\x2f\\x3b\\x8c\\xe9\\x66\\x05\\x68\\x8c\\x95\\x9c\\xc2\\x5a\\xc6\\x30\\x00\\x1b\\xa5\\x63\\x47\\x50\\x3a\\xf4\\xc2\\xa8\\x43\\xbd\\xe3\\x55\\x2b\\x4f\\x28\\xd4\\x55\\xed\\xb2\\x84\\x49\\x02\\xf1\\x51\\x53\\x68\\x8a\\x0a\\xd0\\xf1\\x7b\\x6f\\xa3\\x0b\\x0d\\x7c\\x29\\xd1\\xb9\\x50\\x5e\\x17\\x00\\x9e\\x82\\x8d\\x5e\\x51\\xb6\\x7a\\x72\\x9a\\x15\\x06\\x92\\xf1\\x72\\x41\\xbe\\xcd\\x17\\x9a\\x83\\x94\\x27\\x50\\xce\\x28\\x7f\\xdc\\xa1\\xa8\\x9b\\xf2\\x7a\\x1f\\xb4\\x5d\\x9b\\xf7\\x50\\x6f\\x6d\\x7b\\x14\\xdc\\x21\\x92\\xb3\\x5d\\x48\\x01\\x64\\x3a\\x67\\xd9\\x48\\xfe\\x12\\xc6\\xb6\\x39\\x8a\\xa2\\xe3\\xa0\\xec\\x82\\xfc\\x72\\xdd\\x21\\x87\\x94\\x5b\\x2a\\x7d\\x2c\\x87\\xb3\\x9a\\xd5\\xb2\\x7e\\x68\\xa0\\x43\\x92\\x94\\x02\\xf9\\xdf\\x54\\x24\\x65\\xcd\\x94\\x67\\x3d\\xba\\x3d\\xbc\\x84\\xb3\\xeb\\x9c\\x06\\x6c\\x4d\\x94\\x95\\x55\\x63\\xe0\\x4b\\x28\\x68\\x41\\x66\\xbf\\x45\\xc6\\xc6\\x4b\\x4a\\xdd\\x46\\xe9\\xde\\x5c\\x6e\\xa7\\xef\\xf1\\xa0\\x3f\\x33\\x80\\x79\\xc6\\x1d\\xaa\\x6c\\x9a\\xd4\\x17\\xa4\\x09\\x03\\xd5\\xe7\\x1b\\xf3\\xeb\\x99\\xd2\\xbc\\x73\\x0f\\x42\\xb8\\xc7\\x19\\x55\\x2e\\x6a\\x5d\\x05\\x52\\x3e\\x8d\\xe5\\x83\\xe4\\x7c\\xf9\\x29\\x6a\\x7d\\x39\\x5e\\xc1\\x13\\xab\\x70\\xa7\\xe4\\xc9\\xe7\\x47\\xa5\\xae\\xc4\\xce\\xc9\\xfc\\xc1\\x05\\x9f\\xc7\\x96\\xbc\\x21\\xa1\\xbc\\xe6\\xd7\\x1b\\xa1\\xc2\\xb1\\xc1\\xea\\xc7\\x4d\\x75\\xcc\\xcf\\x23\\x4b\\x47\\xcd\\xf3\\x2c\\xeb\\x1a\\xfe\\xda\\x63\\xf4\\x96\\x4d\\xc7\\x66\\xf6\\xea\\xdf\\x24\\xab\\xe9\\x82\\xa6\\x29\\x7c\\xb3\\x69\\x9e\\xa9\\x54\\x01\\x70\\xf8\\x6a\\x3f\\xae\\x4b\\x88\\xed\\x2d\\x29\\xc9\\xef\\xdc\\x93\\x3b\\xe6\\x88\\xcd\\x32\\x0c\\x0f\\xff\\x3a\\x36\\xa7\\xef\\x01\\x00\\x00\\xff\\xff\\xd0\\xc3\\x32\\x93\\x80\\x09\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/templates/service-mirror-policy.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"service-mirror-policy.yaml\",\n\t\t\tmodTime:          time.Date(2025, 12, 12, 18, 49, 15, 369395450, time.UTC),\n\t\t\tuncompressedSize: 1188,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xe4\\x93\\x41\\x4f\\xdc\\x3e\\x10\\xc5\\xef\\xf9\\x14\\x23\\xfd\\x0f\\xff\\x53\\x82\\x50\\x7b\\xca\\x8d\\x5b\\x2b\\xa1\\x6a\\x05\\x08\\xa9\\x37\\x66\\xed\\x07\\xb1\\xb0\\x3d\\xee\\x78\\xb2\\xb0\\x2c\\xfb\\xdd\\xab\\x64\\xb3\\xd5\\xd2\\x52\\x89\\x7b\\x6f\\xd1\\xe4\\xe5\\xbd\\x9f\\x9f\\x27\\x6d\\xdb\\x36\\x5c\\xc2\\x2d\\xb4\\x06\\xc9\\x3d\\x15\\x89\\xc1\\x6d\\xbb\\x18\\xf2\\x23\\xd4\\x77\\x41\\xce\\x36\\xe7\\x6b\\x18\\x7f\\x6a\\x1e\\x43\\xf6\\x3d\\x5d\\x43\\x37\\xd0\\x26\\xc1\\xd8\\xb3\\x71\\xdf\\x10\\x65\\x4e\\xa8\\x85\\x1d\\x7a\\xda\\xed\\xa8\\xbb\\x42\\x04\\x57\\x74\\xdf\\x8e\\x63\\xda\\xef\\x17\\x55\\x4f\\x15\\xba\\x09\\x0e\\x6d\\x0a\\xaa\\xa2\\x0d\\x51\\xe4\\x35\\x62\\x9d\\x6c\\x88\\x4e\\x42\\xf1\\x6c\\xc8\\x07\\xa2\\x34\\x46\\x0b\\x2e\\x8e\\xd5\\xa0\\xb3\\xcc\\x49\\x2a\\x92\\x91\\xad\\x3f\\x7e\\xd1\\xfe\\x61\\x4b\\xb4\\xdb\\xb5\\xf4\\x14\\x6c\\xa0\\xee\\x96\\xe3\\x88\\xda\\x39\\x49\\x49\\xf2\\xe5\\x9c\\x47\\xfb\\xfd\\x6e\\x47\\x26\\xdf\\x39\\x45\\xea\\xe8\\x95\\x4c\\x43\\xa2\\x57\\xca\\x21\\x7b\\x64\\xa3\\xcf\\xb3\\xa0\\x25\\x64\\x3f\\xd1\\xd7\\x02\\x37\\x21\\x16\\xf1\\xd7\\x88\\x70\\x26\\x7a\\x20\\x4e\\x6c\\x6e\\xb8\\x3c\\x39\\xc2\\xc7\\xe8\\x8a\\xa8\\xf5\\xc4\\x3e\\x85\\xdc\\x0e\\x66\\x65\\x1a\\xa9\\x3c\\x6f\\x57\\x2a\\x26\\x4e\\x62\\x4f\\x5f\\x6e\\x6e\\x56\\x67\\xe7\\xcd\\x47\\x6e\\x87\\x63\\x19\\xf8\\x7c\\xb9\\x9e\\x8b\\xd1\\x06\\xd1\\xf0\\xc2\\x16\\x24\\xaf\\x66\\xf5\\x3f\\x7a\\x57\\xc6\\xfa\\x00\\xbb\\xc2\\xfd\\x81\\xf7\\x41\\x65\\x2c\\xef\\x14\\x38\\xbf\\x7c\\xb3\\xda\\xd3\\xe0\\x2f\\x05\\x28\\x7e\\x8c\\x41\\xe1\\xa7\\x96\\x91\\x2d\\xb8\\xb9\\xe6\\x2b\\xdc\\x2f\\xa5\\xfc\\x47\\x5f\\x33\\x89\\x7a\\x28\\x99\\xd0\\x58\\x41\\x77\\x4b\\x12\\x25\\x47\\x0f\\x6c\\x78\\xe2\\x6d\\xbd\\xa3\\xad\\x8c\\x94\\x01\\x4f\\x9b\\xf0\\xf2\\x3f\\xad\\x54\\x12\\x6c\\xc0\\x58\\x29\\xe4\\x6a\\x9c\\x1d\\x16\\x37\\x13\\x5a\\x83\\x78\\x1d\\x31\\x3d\\x2a\\xd8\\x0d\\x64\\x03\\x7e\\x03\\xeb\\xde\\xa4\\x72\\xac\\x42\\x03\\x6f\\x40\\x4c\\x15\\x85\\x95\\xed\\x68\\x77\\x12\\x54\\x9d\\x72\\xc1\\x3b\\x66\\xc4\\x99\\xd8\\xfb\\x30\\x1d\\x8c\\xe3\\xbb\\xfb\\x74\\x30\\x53\\x54\\x19\\xd5\\x81\\xea\\x20\\x63\\xf4\\x13\\xa8\\x53\\xb0\\xc1\\x77\\xb3\\xa2\\x3d\\x69\\x35\\x38\\x5c\\x38\\x27\\x63\\xb6\\xe5\\x27\\x39\\xf4\\x5b\\x7e\\xf1\\x9c\\x8c\\x97\\xed\\x3c\\x6e\\xcb\\x26\\xbc\\x34\\x3f\\x03\\x00\\x00\\xff\\xff\\xc2\\xfc\\xe5\\x61\\xa4\\x04\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster/values-ha.yaml\": &vfsgen۰FileInfo{\n\t\t\tname:    \"values-ha.yaml\",\n\t\t\tmodTime: time.Date(2025, 4, 24, 21, 9, 23, 550666848, time.UTC),\n\t\t\tcontent: []byte(\"\\x67\\x61\\x74\\x65\\x77\\x61\\x79\\x3a\\x0a\\x20\\x20\\x72\\x65\\x70\\x6c\\x69\\x63\\x61\\x73\\x3a\\x20\\x33\\x0a\\x0a\\x65\\x6e\\x61\\x62\\x6c\\x65\\x50\\x6f\\x64\\x41\\x6e\\x74\\x69\\x41\\x66\\x66\\x69\\x6e\\x69\\x74\\x79\\x3a\\x20\\x74\\x72\\x75\\x65\\x0a\\x0a\\x23\\x20\\x6e\\x6f\\x64\\x65\\x41\\x66\\x66\\x69\\x6e\\x69\\x74\\x79\\x3a\\x0a\"),\n\t\t},\n\t\t\"/linkerd-multicluster/values.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"values.yaml\",\n\t\t\tmodTime:          time.Date(2025, 12, 12, 18, 49, 15, 369395450, time.UTC),\n\t\t\tuncompressedSize: 5457,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xa4\\x58\\x51\\x73\\x23\\xb7\\x0d\\x7e\\xdf\\x5f\\x81\\xb1\\x1e\\x62\\xcf\\x64\\xa5\\xf3\\x5d\\x3a\\x93\\x6e\\xa7\\x33\\x75\\x72\\xc9\\xc5\\xa9\\xcf\\xd5\\x58\\x4e\\x5e\\x32\\x99\\x9a\\x22\\x21\\x89\\x35\\x97\\xd8\\x90\\x5c\\x39\\xdb\\x4e\\xff\\x7b\\x07\\x24\\x77\\xb5\\x92\\x2c\\x9f\\xaf\\x79\\xb9\\xb3\\x76\\x81\\x0f\\x20\\x08\\xe0\\x03\\x76\\x2d\\x02\\x3e\\x89\\xae\\x2a\\x00\\x26\\x50\\x96\\x70\\xbd\\x82\\xb0\\x41\\xc8\\x8f\\x41\\x52\\xdd\\x90\\x45\\x1b\\xc0\\x6f\\xa8\\x35\\x0a\\x96\\x08\\xda\\xfa\\x20\\x8c\\x41\\x55\\x00\\xa0\\x15\\x4b\\x83\\xaa\\x82\\xe0\\x5a\\xec\\x31\\x6e\\xdb\\x7a\\x89\\x0e\\x68\\x05\\x0e\\x1b\\xa3\\xa5\\xf0\\xb0\\x22\\xb7\\x87\\xdb\\x10\\x6b\\xf7\\xaf\\x2b\\xb8\\xec\\x75\\xef\\x37\\x08\\x56\\xd4\\xc8\\xda\\x63\\x85\\xb0\\x11\\x01\\x9e\\xb4\\x31\\x87\\x1e\\xb0\\x70\\x05\\x46\\xdb\\x47\\x74\\xaa\\xcc\\xe2\\x63\\xb4\\x86\\x5c\\x00\\xb2\\xf0\\xb4\\xd1\\x72\\x03\\xc2\\x98\\x3d\\xdc\\x08\\x29\\xa4\\xc4\\x26\\x80\\xb6\\x92\\x6a\\x6d\\xd7\\x10\\x9c\\x58\\xad\\xb4\\x2c\\x20\\x2a\\x57\\xf0\\xd5\\xe5\\x57\\xef\\x7a\\xc8\\x05\\xba\\xad\\x96\\x08\\xf7\\x5d\\x13\\x9d\\xec\\x81\\xf2\\xf3\\x02\\xc0\\xa7\\xbf\\x58\\xa0\\x82\\x1b\\x12\\xea\\x1b\\x61\\x84\\x95\\xe8\\x22\\x84\\x25\\x85\\x73\\x76\\x29\\x62\\x85\\x3d\\x67\\x86\\x77\\xe7\\x1c\\xaf\\xb1\\x2a\\x90\\x83\\xdb\\xfc\\xf6\\x02\\x02\\x81\\x00\\xdf\\xa0\\xd4\\x2b\\x2d\\x61\\x2b\\x4c\\x8e\\x7d\\xaf\\xcf\\xb7\\xd9\\x38\\x5a\\x22\\xff\\x31\\x0e\\x85\\x08\\x9b\\xfd\\x48\\xb6\\x1e\\x15\\x2c\\x3b\\x70\\x58\\x53\\x40\\x90\\xa6\\xf5\\x01\\x5d\\xba\\x2f\\x85\\x01\\x5d\\xad\\x2d\\x47\\xe4\\x69\\x83\\x61\\x83\\xf1\\x0e\\x33\\x64\\xef\\xb3\\xf6\\x20\\x8c\\xde\\xa6\\xc7\\x6c\\xa0\\x82\\x99\\x43\\xa1\\xba\\x03\\xd3\\x7c\\xac\\x68\\x8d\\xa1\\x59\\xc1\\x68\\x8b\\xde\\x47\\x3f\\xb5\\x5d\\x27\\xf5\\x1c\\xed\\x3f\\x5f\\x66\\xe5\\xe7\\x82\\x15\\x0f\\xf6\\x07\\x43\\x75\\x18\\xac\\x91\\xa3\\xda\\x06\\x74\\x5b\\x61\\xe0\\x5c\\x5b\\xf0\\x28\\xc9\\x2a\\x7f\\x01\\x4b\\x0c\\x4f\\x88\\x36\\x3a\\x3e\\xb8\\x8d\\x3e\\xaa\\x66\\xa1\\x0a\\x86\\x1c\\xb9\\xb2\\x96\\x82\\x08\\x9a\\xac\\x8f\\x0e\\x28\\xc5\\xff\\x8d\\x6f\\xda\\x1f\\x66\\xcb\\x48\\xa5\\x82\\xff\\xfc\\x77\\x97\\x6d\\x01\\xf0\\xf7\\x80\\xce\\x0a\\x73\\x9f\\xb2\\x72\\x4e\\x46\\xcb\\x8e\\x33\\xfa\\x24\\xd8\\x77\\xcf\\x69\\x54\\x70\\x76\\xf6\\x7a\\x07\\x15\\x36\\x86\\xba\\x1a\\x6d\\x28\\x60\\xf4\\xe3\\x05\\x37\\xcd\\xe8\\x0e\\xbe\\x35\\xc2\\xfb\\xe7\\x5d\\x3c\\x12\\x1b\\xfb\\x75\\x88\\x73\\x3d\\xff\\x34\\xc8\\xf5\\xfc\\x25\\x84\\x05\\xb5\\x4e\\xe2\\x9d\\xb0\\x6b\\x7c\\x85\\x43\\x63\\xe9\\x0a\\x7e\\xf9\\x75\\x8c\\x9a\\x8a\\x21\\x9e\\xfd\\x83\\x13\\x12\\xe7\\xe8\\x34\\xa9\\x45\\xba\\xfc\\x31\\xf4\\x5e\\xe8\\x5e\\xd4\\x1a\\x3b\\xce\\x29\\x0b\\x1e\\x0d\\xca\\x40\\xee\\x54\\xc3\\xe4\\x9c\\x5d\\x64\\x99\\x71\\xfc\\xef\\xc9\\xa0\\xcb\\xf7\\xf9\\xbc\\x66\\xd8\\x49\\xc4\\x83\\xed\\x75\\x48\\xd1\\x7a\\x04\\x49\\x36\\x08\\x6d\\xb9\\xc8\\x89\\x2b\\x95\\x7b\\x08\\xbf\\xb8\\xae\\xc5\\x1a\\x2b\\x38\\x5b\\x4b\\x37\\xd5\\x34\\x5b\\x13\\xad\\x0d\\xfe\\x73\\x90\\xf6\\xb3\\x28\\x55\\xbd\\x9b\\xbe\\x3d\\x1b\\x50\\x7f\\xf2\\xe8\\x40\\x2b\\x68\\xad\\x42\\x97\\x3b\\xef\\x5e\\xfa\\x6f\\x44\\xea\\x3f\\x4e\\xd8\\x02\\xe0\\xa7\\xeb\\xf7\\x15\\xbc\\xbd\\x7c\\xf3\\x6e\\x00\\xf8\\xe0\\xa8\\x6d\\x3e\\x03\\xe1\\xc3\\x0e\\x21\\xea\\x7f\\x4b\\x36\\x38\\x32\\xd0\\x18\\x61\\x11\\xb6\\xe8\\xbc\\x26\\x5b\\x64\\x96\\xf8\\x39\\xfd\\x1c\\x58\\x23\\xff\\xfe\\x39\\x36\\x87\\x54\\x1f\\x4a\\x69\\x8e\\x95\\x30\\x20\\x9e\\x2d\\x15\\x36\\xde\\x90\\xf2\\x45\\x43\\xea\\xb0\\x26\\x0e\\x11\\x8c\\x58\\xa2\\x39\\xa5\\x7c\\x13\\x5f\\xee\\xf4\\x6e\\x76\\xc2\\x4d\\x63\\xba\\x5e\\xdc\\xa1\\x8f\\xa9\\xe9\\x0b\\x49\\x75\\x4d\\xf6\\x50\\xed\\x3d\\xc9\\x47\\x0e\\x39\\xdf\\xd5\\xbc\\x35\\x26\\x37\\x09\\xce\\x05\\x56\\xaf\\x5b\\x13\\x74\\x6e\\xee\\x3b\\x4e\\xf7\\xc5\\x81\\x7c\\x05\\xd7\\xab\\x5b\\x0a\\x73\\x87\\x9e\\xb3\\x37\\x22\\x7f\\x4f\\x0e\\xe6\\x4e\\x6f\\x45\\x40\\x50\\xc9\\x8a\\xc3\\xb5\\xf6\\xc1\\x69\\xf4\\x5f\\x82\\x68\\xc3\\x06\\x6d\\xd0\\x32\\x06\\x80\\xd9\\xc0\\x22\\x2a\\x54\\xd3\\x62\\x02\\x70\\x97\\x04\\xb9\\xda\\xa4\\xc3\\xe0\\x41\\x38\\x8c\\xe7\\xd2\\x38\\x34\\x1c\\x87\\xdc\\x9d\\x83\\xde\\x62\\x5f\\x93\\x4c\\xc7\\xd4\\xee\\xb9\\xb7\\x48\\xfa\\x31\\x73\\x27\\x50\\x66\\xd2\\xaf\\xbb\\xb2\\x49\\x9e\\x95\\xc9\\xb3\\x32\\x7b\\xd6\\x95\\x86\\xd6\\xda\\x96\\xc9\\x6c\\x71\\x62\\x14\\xc8\\x64\\xf2\\x7b\\x97\\xf9\\xdf\\x03\\xb5\\x61\\x49\\xad\\x55\\xc3\\x00\\x10\\x5f\\xff\\x23\\x3f\\x9d\\xf7\\xb3\\xc0\\x9b\\x62\\x3c\\x2a\\x65\\xea\\xac\\xb5\\x73\\xe4\\x0e\\xcf\\xf0\\xec\\xd8\\x94\\x34\\x3e\\x46\\x85\\x3c\\x36\\x5c\\x25\\xf1\\x3c\\x47\\x3d\\x3b\\x09\\x1d\\x22\\x47\\x32\\x4d\\xe9\\x41\\x4f\\x47\\x04\\x1e\\x28\\x7b\\x54\\x4c\\xc0\\x90\\x14\\xa6\\xd7\\xf7\\x2f\\x98\\xbf\\xdd\\x9b\\xa5\\xb2\\x42\\x99\\x70\\xca\\xa4\\x56\\x72\\xac\\xbc\\x2f\\x15\\xae\\x44\\x6b\\x72\\x6c\\x59\\xcf\\x37\\x42\\x46\\x5f\\xb3\\x7a\\x7f\\xde\\x98\\x17\\x7d\\xe5\\x0d\\x82\\x83\\x95\\x1c\\x4a\\xc5\\x39\\x14\\x3a\\xb8\\x77\\xad\\x0f\\xf0\\x9e\\x6a\\xa1\\x6d\\x7f\\x70\\x89\\x2e\\x30\\x7b\\x73\\x06\\x72\\xba\\x91\\xd3\\xa1\\x2b\\x74\\x56\\x89\\x1a\\x49\\xa1\\xea\\x8f\\x3f\\x8d\\x27\\x2e\\x0a\\xdb\\x9b\\xfb\\x88\\x41\\x28\\x11\\x04\\x73\\x7d\\x4c\\xaa\\x11\\xe9\\xbf\\xdf\\x4b\\xea\\x6e\\xe8\\x9f\\x83\\x72\\x59\\x67\\xed\\x74\\x24\\x2b\\xd3\\x04\\xd1\\x2b\\x54\\x20\\xdd\\xd4\\xfc\\x49\\x71\\x73\\xec\\x0f\\x75\\x08\\x1e\\x6d\\xa6\\xdb\\x7c\\x2d\\x7c\\x4a\\x71\\xe6\\x7e\\xcb\\xcd\\xa9\\xd4\\x56\\x87\\x13\\xb8\\x41\\xac\\x5f\\x0d\\x1b\\xc4\\xba\\x82\\xed\\x9b\\xe9\\xe5\\xf4\\x72\\x87\\xc6\\x25\\x06\\xcd\\xae\\x65\\xbc\\x06\\x68\\x02\\x7f\\xcb\\x39\\xc0\\x08\\x07\\x8d\\x24\\x4d\\x73\\xa3\\xbe\\x72\\xb6\\xe3\\x86\\x13\\x44\\xf7\\xb2\\xbd\\xcf\\x21\\xbe\\x97\\x91\\x8e\\x88\\x30\\xf1\\x85\\x43\\x4e\\xaf\\x3b\\x32\\xe8\\x41\\x58\\x15\\xff\\xfa\\x46\\x5b\\xa5\\xed\\x3a\\x75\\x63\\xef\\x49\\x6a\\x96\\x09\\x1b\\xed\\x77\\xf7\\xf2\\x85\\x2f\\x26\\xb0\\x5f\\x45\\xbe\\x6f\\x6d\\x72\\x8f\\x84\\xe6\\x8b\\xf9\\xd0\\xc6\\xa7\\x70\\xcf\\x28\\x0e\\x7f\\x6b\\xb5\\x43\\x1f\\x67\\xf2\\x62\\x02\\x0f\\x79\\x9d\\x9a\\x2f\\xe6\\x0f\\xdc\\x4a\\x3d\\x8f\\x1d\\x14\\x7b\\x02\\xb7\\xad\\x63\\xcc\\x5c\\x63\\x53\\xb8\\xe5\\xe2\\x67\\x0b\\x1b\\xe1\\x61\\x89\\x68\\x8b\\x09\\xcf\\x21\\x0e\\xb9\\x6a\\x14\\x78\\x6d\\x25\\xc2\\xe3\\xd7\\x1e\\xb6\\x97\\xd3\\xb7\\x97\\x45\\xb2\\x33\\x5f\\xcc\\x2b\\x58\\x09\\xe3\\x31\\x47\\xe1\\xbb\\xf8\\xd8\\xc3\\x9c\\x14\\x5c\\xd9\\xa0\\xe1\\x6a\\xb5\\xe2\\xbc\\xeb\\x80\\xfb\\xa9\\x64\\x5f\\x96\\x69\\x52\\x4a\\xcd\\xd3\\x08\\x89\\x3c\\xe7\\x8c\\x57\\xbd\\x62\\x02\\x42\\x3a\\xf2\\x1e\\x36\\xe4\\x43\\x0a\\xe6\\xbf\\xc9\\x62\\xba\\x9f\\x1f\\xf4\\x7a\\x03\\x57\\x5b\\xa1\\x8d\\x58\\x6a\\xa3\\x43\\xc7\\x3c\\x91\\xcc\\xa6\\xc0\\x92\\x35\\x1d\\x6f\\x1b\\x16\\x3a\\x6a\\x61\\x23\\xb6\\x98\\xc8\\xab\\x31\\xb8\\xdb\\x26\\x69\\x35\\x22\\xb1\\x69\\x7f\\x1a\\x66\\xe2\\xa0\\x7b\\x97\\xf7\\x4f\\xc6\\x49\\x37\\x1c\\xc6\\x33\\xdf\\x90\\xfd\\x12\\x16\\x18\\x0f\\x52\\x4c\\xe0\\x97\\xbf\\x7f\\xbd\\x60\\x7a\\x6b\\xf9\\x38\\x31\\x3b\\x7e\\x3d\\xdf\\x84\\xd0\\xf8\\x6a\\x36\\x7b\\x6c\\x97\\xe8\\x2c\\x06\\xf4\\x5c\\xe2\\x8a\\xa4\\x9f\\x49\\xb2\\x91\\x31\\x66\\x5e\\x6e\\x50\\xb5\\x46\\xdb\\x75\\x89\\x5b\\x1d\\x61\\x67\\xc2\\x7b\\xbd\\xb6\\x65\\x43\\xaa\\xe4\\xb4\\x9d\\x4d\\xf8\\xdf\\x52\\x64\\xeb\\x17\\xc5\\x24\\x46\\xa2\\x26\\xc7\\xf7\\xb7\\x22\\x57\\xa7\\x16\\x99\\x36\\x92\\xc1\\xfd\\xbd\\xc4\\xf4\\x20\\xe0\\x47\\x5a\\xa6\\xdd\\x4d\\x28\\xc5\\x2c\\xcb\\x6d\\x58\\xb8\\x0e\\x86\\x24\\xcf\\x59\\x37\\x4a\\xcd\\x5d\\x29\\x70\\x3a\\xb4\\x8e\\x37\\xb9\\x9c\\x32\\x7f\\x01\\xa5\\x7d\\x0c\\xbb\\x5e\\x81\\x11\\xf2\\x91\\x83\\xca\\x6c\\xaa\\x0d\\xf2\\xa8\\x9c\\x53\\x13\\x14\\xc5\\x85\\x98\\xaf\\xa6\\x16\\xb6\\x15\\xc6\\x74\\x85\\x8c\\x3e\\xdd\\x1e\\x36\\xd7\\x1f\\x69\\x99\\xf9\\x2b\\xb9\\xbe\\x48\\x5b\\x57\\x4c\\x6f\\x04\\x3b\\x7c\\x14\\x20\\xa3\\xe0\\x2e\\x5d\\xe5\\x02\\x53\\xb5\\x38\\xe4\\x61\\x72\\xc4\\x66\\x64\\xcc\\x52\\xc8\\xc7\\x69\\xe1\\x70\\xab\\xf9\\x30\\x3f\\x68\\x1f\\xc8\\x75\\x37\\xba\\xd6\\xa1\\x82\\xcb\\x37\\x45\\x11\\x7b\\x7c\\xae\\xbc\\x44\\x66\\xd5\\xf1\\x07\\x88\\xa0\\x6b\\xf4\\xfb\\x04\\xd8\\x33\\x75\\xdb\\xa8\\x14\\x59\\x9e\\x49\\xd8\\x6a\\xa2\\x53\\x1e\\x29\\xf1\\xb7\\x16\\xdb\\xf8\\x75\\x61\\x02\\xe7\\x0e\\x79\\xd4\\x51\\x17\\xbb\\xf5\\x2a\\x59\\xbb\\xc3\\x30\\xf8\\xb3\\x1b\\x5c\\xe3\\x60\\x36\\xb4\\xb7\\x78\\xd3\\x2b\\x54\\xdc\\x71\\xb8\\x0c\\x7b\\x0f\\x90\\xfd\\xf3\\xa0\\x53\\x4d\\x27\\xf7\\x7a\\xee\\x2a\\x60\\xa7\\x91\\x8f\\xb7\\xeb\\x7c\\x67\\xc9\\xf9\\x69\\x26\\x1a\\x4e\\xc8\\x41\\xf8\\xaf\\x09\\x76\\xd4\\x69\\x87\\x30\\x3c\\x1b\\x80\\xa1\\x9c\\xf8\\x02\\x5a\\x7b\\xf0\\x3d\\xe6\\x34\\x53\\x26\\xd2\\xe9\\xbb\\xed\\x62\\x1f\\x74\\xf7\\xc1\\xe8\\xbc\\xf5\\xf9\\xea\\x6f\\xf2\\x44\\x90\\x7b\\x97\\x89\\x5f\\x42\\x18\\x33\\x22\\x5d\\x8c\\xe8\\xee\\x88\\x49\\x67\\x47\\x3a\\x27\\xe8\\xea\\xc8\\x8d\\x7e\\x99\\x89\\x36\\xfe\\x3f\\xc6\\xda\\x7d\\x17\\x18\\x11\\xec\\x67\\x1b\\xda\\x5f\\x32\\xa2\\xc0\\xf6\\xa5\\x05\\x64\\xc8\\x24\\x5a\\x83\\xc1\\x2d\\x9a\\xc1\\xf4\\xc7\\x13\\x53\\x3c\\xef\\xb0\\xeb\\x1b\\x16\\xad\\x62\\x47\\xd9\\x83\\x48\\x0d\\x06\\xce\\x1f\\x1a\\x23\\xb4\\x7d\\x00\\x72\\xf0\\xf0\\x2f\\x4f\\xf6\\xe1\\x22\\xe9\\x7d\\x1f\\xdf\\x57\\x10\\x5f\\x0f\\x9a\\x98\\x99\\x80\\xcd\\xf2\\x72\\xc8\\xcd\\xa1\\x71\\xb4\\x02\\xb4\\xaa\\x21\\xcd\\x24\\x47\\xf6\\x80\\x8c\\x06\\x8f\\xbe\\xf0\\x20\\x54\\xad\\x6d\\x04\\xe3\\xa4\\x43\\xe7\\x87\\x6f\\x85\\x73\\x86\\x19\\x9a\\xf3\\x27\\xf6\\xc5\\x3e\\xd8\\x1f\\xf3\\x70\\x7d\\x6a\\x6d\\xfc\\xd4\\xd6\\xf8\\x32\\xce\\x87\\xa3\\xf5\\xf3\\xae\\x5f\\xb7\\x3e\\x79\\xed\\xb1\\x6a\\xb2\\x70\\x1c\\x4c\\xfe\\x17\\x00\\x00\\xff\\xff\\x53\\x71\\x57\\xab\\x51\\x15\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster-link\": &vfsgen۰DirInfo{\n\t\t\tname:    \"linkerd-multicluster-link\",\n\t\t\tmodTime: time.Date(2025, 12, 12, 18, 49, 15, 368996883, time.UTC),\n\t\t},\n\t\t\"/linkerd-multicluster-link/.helmignore\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \".helmignore\",\n\t\t\tmodTime:          time.Date(2025, 4, 24, 21, 9, 23, 549666844, time.UTC),\n\t\t\tuncompressedSize: 340,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\x4c\\x8f\\xb1\\x6e\\xeb\\x30\\x0c\\x45\\x77\\x7e\\xc5\\x7d\\xf0\\xf2\\x9e\\xf1\\x20\\x7f\\x44\\x92\\xa1\\x4b\\x53\\xd4\\x45\\x3a\\x16\\xb2\\xcd\\x48\\x4c\\x64\\x49\\x90\\xe8\\xa4\\xed\\xd0\\x6f\\x2f\\x92\\x20\\x68\\x97\\x03\\xf0\\x80\\x24\\xee\\x6d\\xf0\\x64\\x55\\xb9\\xc4\\x0a\\x4d\\x10\\x17\\x53\\x61\\x9c\\x3d\\x47\\x0c\\x8b\\x84\\x49\\xa2\\x43\\xb6\\xe3\\xd1\\x3a\\xae\\x86\\x1a\\xbc\\x78\\xa9\\xa8\\x4b\\xce\\xa9\\x68\\x45\\xf5\\x1c\\x02\\x5c\\x48\\x03\\x66\\xab\\xa3\\x97\\xe8\\xfe\\xa3\\x70\\xb0\\x2a\\x27\\x46\\xb6\\xea\\x7f\\x79\\x1b\\x27\\x6a\\x10\\xd9\\x59\\x95\\x14\\xf1\\x37\\x17\\xde\\xcb\\x3b\\x4f\\x38\\x8b\\x7a\\xfc\\xf9\\x67\\xb0\\x8d\\xe1\\x03\\x29\\x5e\\x2f\\x2f\\x91\\x90\\xb9\\x20\\x48\\x64\\x43\\x66\\xdd\\xbf\\xf5\\x9a\\x0a\\xd3\\xf6\\xf5\\x71\\xf3\\xdc\\x53\\x83\\x55\\x9a\\xe7\\x14\\xb1\\x5b\\xf5\\x98\\xa4\\x54\\x32\\x4e\\xb4\\xbb\\xf2\\xd6\\x82\\xcc\\xf0\\x59\\xba\\x2b\\xef\\xc2\\xbb\\xee\\x82\\xfb\\x58\\x4f\\xb1\\xfb\\x79\\x34\\xd8\\xf1\\xb8\\x64\\xec\\x25\\x70\\xa5\\xd6\\xd4\\x73\\xa6\\xd6\\x0c\\xf6\\x48\\xad\\xd1\\x39\\x53\\xfb\\x45\\x0d\\x76\\xb6\\x48\\x5a\\x2a\\x1e\\xd6\\x9b\\x4a\\x26\\x97\\x74\\xe0\\x51\\xc9\\xc8\\xc4\\xb6\\xbb\\xed\\x95\\x74\\xa0\\xef\\x00\\x00\\x00\\xff\\xff\\x66\\xc2\\xf1\\x12\\x54\\x01\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster-link/Chart.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"Chart.yaml\",\n\t\t\tmodTime:          time.Date(2025, 12, 12, 18, 49, 15, 368996883, time.UTC),\n\t\t\tuncompressedSize: 625,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\x5c\\x51\\x4f\\xab\\x13\\x31\\x10\\xbf\\xe7\\x53\\xfc\\xe8\\xe5\\x21\\xb8\\xdb\\x7d\\x3d\\x56\\x14\\xe4\\x9d\\x84\\x77\\x12\\xd1\\x1e\\x9b\\xee\\x4e\\x37\\xb1\\xd9\\x99\\x30\\x93\\xb4\\x14\\xfc\\xf0\\x92\\x5a\\xab\\xbc\\x63\\x26\\xf3\\xfb\\x3b\\x3e\\xc7\\xef\\xa4\\x16\\x85\\xb7\\x38\\x3f\\x3b\\x9f\\xf3\\xe3\\x49\\xd3\\x4c\\xdd\\x6e\\xd7\\xef\\xfa\\x9d\\x9b\\xc8\\x46\\x8d\\xb9\\xdc\\x3e\\x7e\\x39\\xe0\\x33\\x02\\xa5\\x05\\x63\\xf0\\x5a\\x30\\x0a\\x17\\x1f\\x39\\xf2\\x8c\\x12\\x08\\x4a\\x26\\x55\\x47\\x32\\x14\\x01\\xb1\\x3f\\x24\\xc2\\x12\\x55\\x45\\x23\\xcf\\x0e\\x90\\x23\\x8c\\xf4\\x1c\\xdb\\xc6\\x51\\x65\\x81\\x87\\xd2\\x22\\x85\\x30\\xa6\\x6a\\x85\\xb4\\x77\\x0e\\xf8\\xe1\\xb5\\x31\\x6e\\xf1\\x2d\\x10\\x72\\xd5\\x2c\\x46\\x0d\\x5a\\x42\\xb4\\xbb\\x6e\\x34\\xfc\\xac\\x56\\x9a\\x8c\\xd5\\x9c\\x45\\xcb\\x4d\\x7f\\x9f\\x22\\x9f\\x48\\x27\\x07\\x2c\\x35\\x95\\x78\\x67\\x45\\x1b\\xef\\xf1\\xf2\\xfa\\x05\\xa3\\x2c\\x8b\\xe7\\xe9\\x3d\\x2e\\x21\\x8e\\x01\\x3e\\x99\\x20\\xab\\x4c\\xf5\\x66\\x3a\\x90\\x03\\xf6\\x77\\x54\\x37\\x2a\\x4d\\xc4\\x25\\xfa\\x64\\x7b\\x18\\x8d\\x4a\\x05\\x9e\\xa7\\x9b\\xd2\\x6b\\xe4\\x13\\x5e\\xbe\\x3e\\x78\\x94\\xc0\\x52\\x70\\x94\\xca\\x13\\x22\\xdf\\xbc\\x3a\\xfc\\x71\\xdb\\xb7\\x20\\x4a\\x47\\x51\\x7a\\x93\\xa1\\x41\\x5a\\x05\\x39\\xf9\\x91\\x16\\xe2\\x46\\xa0\\x28\\xc1\\x97\\x7f\\x46\\x3d\\xb7\\x38\\x16\\xa4\\xa6\\x89\\x9f\\x0a\\x0e\\x84\\x6a\\x34\\xc1\\x5b\\x17\\x0d\\x95\\x13\\x99\\xe1\\x2a\\x15\\x4a\\x3e\\xa5\\x2b\\x4e\\x2c\\x17\\x5c\\x1a\\xc5\\x55\\xea\\x93\\x12\\x26\\x69\\xe7\\xf9\\xd0\\xbd\\x73\\xa7\\x7a\\xa0\\xc7\\x91\\x57\\x9f\\x3e\\x3e\\xf7\\x9b\\x4d\\x3f\\x74\\xc3\\xca\\xc5\\xb1\\x8d\\x42\\x29\\xd9\\xb6\\xeb\\xf5\\xbd\\xc5\\x3e\\xca\\x3a\\x2e\\x7e\\x26\\x5b\\x27\\x99\\xa5\\x13\\x4e\\xd7\\x6e\\x33\\x0c\\xa1\\xcf\\x3c\\x3b\\xf6\\x0b\\x6d\\xb1\\xba\\xef\\x76\\xff\\xf7\\xdd\\xb5\\xe1\\xca\\x9d\\xff\\x4a\\x0d\\xfd\\xa6\\x1f\\xdc\\xef\\x00\\x00\\x00\\xff\\xff\\x64\\x38\\xc1\\x3b\\x71\\x02\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster-link/README.md\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"README.md\",\n\t\t\tmodTime:          time.Date(2025, 12, 12, 18, 49, 15, 368996883, time.UTC),\n\t\t\tuncompressedSize: 3573,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\x94\\x57\\x61\\x6f\\x23\\xb7\\x11\\xfd\\xbe\\xbf\\x62\\x9a\\xfb\\x70\\x67\\x20\\x5a\\x59\\x97\\x16\\x68\\x5d\\xa4\\x85\\x7a\\xd7\\xe4\\x0e\\x71\\x0c\\xc3\\x72\\x52\\x03\\x87\\x00\\xa2\\x96\\xa3\\x5d\\xda\\x5c\\xce\\x1e\\x87\\x94\\xaa\\x56\\xfd\\xef\\xc5\\x90\\xab\\xd5\\xea\\x2c\\xb7\\x89\\x71\\xc0\\x09\\x22\\xf9\\xde\\x9b\\xe1\\xbc\\x19\\xea\\x15\\x58\\xe3\\x9e\\xd0\\xeb\\x49\\x1b\\x6d\\x30\\x95\\x8d\\x1c\\xd0\\x4f\\xe4\\xcb\\xa2\\x98\\x43\\x83\\xb6\\x85\\xaa\\x51\\x3e\\x40\\x45\\x2e\\x28\\xe3\\x8c\\xab\\x21\\x34\\x08\\x1e\\x99\\xa2\\xaf\\x90\\x21\\x10\\xa0\\x53\\x2b\\x8b\\xd0\\x1a\\xef\\xc9\\x1b\\x57\\x17\\xb4\\x06\\x46\\xbf\\x31\\xb2\\xbe\\xf6\\xd4\\x82\\x02\\x8f\\x2d\\x05\\x84\\x9e\\xa1\\x2c\\x8a\\x7f\\x28\\x2f\\x68\\x57\\x70\\xdf\\x20\\x74\\xd1\\x77\\xc4\\x08\\xb4\\x86\\xd0\\x18\\xee\\x39\\x0d\\xc3\\x63\\xe4\\x20\\x14\\x1c\\xbb\\x8e\\x7c\\x48\\xdc\\xcb\\x5e\\x73\\x31\\xd6\\x9c\\x02\\x59\\xc2\\xbb\\xeb\\x8f\\x50\\x51\\xdb\\x2a\\xa7\\xbf\\x86\\x6d\\x63\\xaa\\x06\\x94\\x65\\x82\\xce\\x93\\x8e\\x49\\x6e\\x83\\xc5\\xf2\\x10\\x67\\xe5\\x51\\xa3\\x0b\\x46\\x59\\x5e\\x02\\x63\\xe5\\x31\\x80\\x72\\x3a\\xb1\\x5c\\x1b\\xf7\\x04\\xef\\xee\\x06\\x14\\x8f\\xe0\\x28\\xc0\\x9a\\xa2\\xd3\\x60\\x5c\\xd2\\x59\\x24\\x9d\\xa5\\x84\\xe0\\x71\\x4d\\x1e\\xbf\\x50\\x2f\\x07\\x24\\xf4\\xce\\xaa\\x0a\\x5b\\x74\\x72\\xdc\\x43\\x68\\x54\\x38\\x8a\\x54\\x4e\\x17\\xdc\\x50\\xb4\\xda\\xbd\\x0e\\xb0\\x42\\x88\\x8c\\x1a\\x14\\x4f\\x0c\\x43\\x74\\x16\\x99\\x61\\x47\\x11\\x3c\\x2a\\x6b\\x77\\xf0\\xe4\\x68\\x0b\\x5b\\x01\\xd8\\x51\\x7c\\xed\\x11\\x34\\xc9\\x95\\xfc\\x79\\x72\\x51\\x14\\xbf\\xfb\\xf4\\x33\\x7a\\x36\\xe4\\xae\\xe0\\xb2\\x7c\\x5b\\x5e\\xfe\\xf2\\xa6\\x09\\xa1\\xe3\\xab\\xe9\\xd4\\xb4\\x75\\xc9\\x8d\\x41\\xab\\xb9\\x34\\x34\\x5d\\x29\\x5d\\xe3\\xb4\\xdf\\x3c\\x49\\x7b\\x27\\xc6\\xad\\xc9\\xb7\\x2a\\x18\\x72\\xca\\xfe\\x95\\xc3\\xce\\xe2\\xb7\\x6b\\xab\\xc2\\x84\\x3f\\x47\\xe5\\x31\\xc1\\xcf\\xbb\\x6e\\x60\\x40\\x5d\\xe3\\xe4\\xe1\\xa1\\x7c\\x28\\x1f\\xfe\\x0f\\xcf\\xf1\\xd4\\x24\\x1d\\xea\\x4f\\xfd\\x0a\\xc6\\x57\\xaf\\xe0\\x0e\\x3f\\x47\\xe3\\x53\\xee\\xb8\\x28\\x7e\\x88\\x2b\\xf4\\x0e\\x03\\xf2\\x15\\x2c\\xff\\xf2\\xed\\xac\\x7c\\x2b\\xd2\\x2f\\x97\\x45\\xb1\\x87\\x3b\\xec\\x88\\x4d\\x20\\xbf\\x83\\x3d\\xdc\\xa8\\x16\\x61\\x0f\\x3d\\x2f\\xec\\x8b\\xfd\\x64\\xf4\\xb7\\x3f\\xf9\\x4f\\x3e\\x15\\x7b\\x58\\x1b\\x8b\\x57\\xd3\\x69\\x59\\xf6\\xff\\xd2\\x25\\xf2\\xb4\\x53\\x3e\\xd5\\x07\\xec\\x61\\xf4\\xf1\\xb2\\x9c\\x95\\x97\\xb0\\x4f\\x12\\x7f\\x56\\x36\\x22\\x8b\\x84\\x1f\\x50\\xb8\\xef\\x77\\x9d\\x70\\xbf\\xc7\\xb5\\x8a\\x36\\xa4\\x4f\\x5c\\x79\\xd3\\x85\\xb1\\x92\\xe7\\x12\\x26\\x27\\x0a\\x8b\\x7d\\xaa\\x10\\x72\\xd7\\x6a\\x85\\x89\\x92\\x56\\x8f\\x58\\x09\\xdc\\xf2\\xdf\\xff\\x59\\xc2\\x1e\\xfa\\x85\\x40\\xa0\\xba\\xce\\xee\\xd2\\x07\\x6b\\x47\\xc6\\xcc\\x18\\x2e\\x78\\xb2\\x16\\xfd\\xc7\\x56\\xd5\\x22\\x8b\\x83\\x38\\x54\\x60\\xbe\\xaa\\x7c\\x69\\xff\\xa0\\xe5\\xa2\\x7a\\x43\\x4d\\x8f\\xdb\\xbf\\x12\\x8a\\xf7\\x54\\x3d\\xa1\\x07\\x93\\x8e\\xe6\\xca\\x45\\x58\\x64\\x63\\xf7\\x6e\\x17\\x95\\x1d\\x39\\x29\\xed\\x37\\x91\\xb3\\xbf\\x92\\x75\\xd0\\xeb\\x11\\x7b\\xc6\\xb8\\x38\\xa7\\x69\\xb8\\xa3\\xb1\\xb4\\x5e\\x50\\xbf\\x96\\x32\\x9c\\x14\\xdd\\xab\\xfa\\x99\\x90\\x1f\\x0f\\x42\\x52\\x83\\x42\\x7f\\x2a\\x5b\\x18\\x73\\x7f\\xfa\\x80\\x4a\\x8b\\x9d\\x16\\x87\\xce\\xb4\\x87\\x15\\x91\\x15\\xbe\\xb5\\xb2\\x8c\\x09\\x9f\\xea\\xda\\xe2\\xd0\\x6d\\x84\\x6a\\xe8\\x6a\\xd0\\xf4\\x00\\xc7\\xde\\x76\\x04\\x97\\x82\\xe3\\x4e\\x55\\xf8\\xce\\xa3\\xca\\x37\\xfd\\xab\\xd0\\xab\\xb4\\xdd\\xd5\\xe0\\x0e\\x00\\x3c\\x62\\x3d\\x32\\x6d\\x1b\\x74\\xe0\\xb0\\x42\\x66\\x25\\x15\\x3e\\x10\\xdf\\x2e\\x6e\\xcf\\x51\\x25\\x19\\x08\\x77\\x64\\xf1\\x6f\\xc6\\x69\\xe3\\xea\\x5c\\x2a\\xcc\\x54\\x19\\x59\\xe9\\xb3\\x30\\xaf\\x2a\\x8a\\x2e\\xa4\\xc6\\xab\\x7c\\x8d\\xe1\\xd0\\xa1\\xbf\\x4c\\x6f\\xa0\\x94\\xf4\\xfe\\xf2\\xa0\\xb3\\xca\\x21\\x08\\xf9\\xa1\\xe0\\xa4\\x0b\\x1a\\x06\\x9f\\x0d\\xcb\\xb9\\xcb\\x2d\\xb3\\x48\\x7d\\xbb\\xb8\\x5d\\x4a\\x43\\x64\\x4c\\xdd\\x3c\\xf8\\x88\\x40\\x2e\\x21\\xe2\\x3f\\x03\\xba\\x54\\x01\\xd2\\x79\\x4f\\xf1\\x8d\\xe3\\xa0\\xac\\x2d\\xe1\\x46\\x66\\x87\\xb0\\x35\\x8a\\x61\\x85\\xe8\\x40\\x63\\xe7\\xb1\\x52\\x01\\x35\\xb0\\x71\\x15\\xc2\\xd3\\x1f\\x19\\x36\\xb3\\xf2\\xed\\x6c\\x9c\\x1c\\xd2\\x73\\x17\\xcc\\x7c\\xbd\\x36\\xce\\x84\\xdd\\xb9\\x44\\xfd\\x3d\\x6d\\x64\\xb8\\x25\\x0d\\xb2\\x17\\x86\\xcd\\x96\\x6a\\x53\\x89\\xda\\x95\\xb2\\x4a\\x18\\x44\\xed\\xb1\\x93\\xd3\\x3a\\x35\\x76\\x53\\x29\\x06\\x55\\x79\\x62\\x86\\x86\\x38\\x70\\x0a\\xe3\\x5f\\xe4\\xfa\\x8b\\xfc\\x60\\xea\\x06\\xe6\\x1b\\x65\\xac\\x5a\\x19\\x6b\\xc2\\xae\\xec\\x29\\xf3\\xa8\\x20\\x67\\x77\\xf9\\x72\\xa5\\xc9\\x37\\x6a\\x83\\x90\\xa6\\x5a\\x67\\xf1\\x08\\x4f\\xeb\\xa3\\xcd\\xb8\\x4c\\xf1\\xd5\\x2a\\xe0\\x56\\xed\\xca\\x3e\\xbf\\xa3\\xc8\\x24\\xb7\\xa9\\x02\\x72\\x26\\x53\\xe9\\x84\\xa6\\x1f\\x90\\xb0\\x35\\xd6\\xe6\\xa2\\x43\\x50\\x32\\x15\\x57\\x78\\x28\\xb2\\xc1\\x58\\x3d\\xf6\\x09\\x4f\\xda\\x59\\xa6\\xb2\\xdd\\x83\\x71\\xa9\\x0f\\xfd\\x7e\\xf6\\xa7\\x59\\x2a\\x6b\\xc9\\x8b\\xac\\xa4\\xc9\\x25\\x28\\xd6\\x6c\\xd0\\x1a\\x27\\x5e\\x91\\x83\\xc9\\xd5\\xc5\\x3e\\x3b\\xf2\\x36\\x5a\\xbb\\x48\\xa3\\x56\\x1c\\x68\\x0d\\x27\\xac\\x4f\\xbf\\x08\\xd2\\x77\\xe4\\xe1\\xd6\\x9b\\x8d\\xa8\\xd3\\xd9\\xc5\\x1e\\x6b\\x23\\x8d\\x01\\xf9\\x6b\\x50\\x31\\x34\\x32\\xae\\xab\\xec\\x30\\x19\\xb0\\x88\\x1a\\x75\\x09\\x70\\x97\\xb7\\xed\\xfa\\x29\\xce\\x69\\x5e\\x4b\\x63\\x34\\xa8\\x0f\\xb5\\xeb\\x91\\x3b\\xac\\x82\\xd9\\x1c\\x43\\x56\\xb9\\xfc\\xb3\\x93\\x2d\\xd5\\xdf\\xa5\\x89\\x74\\xda\\x8b\\x3a\\xab\\x8c\\x4b\\xdd\\xe7\\x9a\\x52\\xf7\\x91\\x1d\\x6f\\x96\\xe9\\xeb\\x25\\x90\\x87\\xe5\\x23\\x93\\x5b\\x5e\\x1c\\x30\\xae\\x71\\x83\\xf6\\x14\\x42\\x46\\xdd\\x80\\x60\\xd3\\xfa\\x21\\xd9\\x3f\\x8e\\xdf\\x30\\xc7\\x5b\\x4e\\x60\\x8e\\x34\\x2e\\xd0\\x62\\x15\\xc8\\x9f\\x99\\x00\\x37\\xa4\\x25\\x92\\xbc\\xce\\x2f\\x35\\xe8\\x8e\\x74\\x02\\xeb\\xc4\\x0b\\x8e\\x42\\xca\\xdd\\xb9\\x81\\x32\\xd7\\xda\\xe4\\x61\\x0c\\x6a\\xb4\\x51\\xba\\x86\\xd6\\x87\\xf1\\xd2\\x91\\xe6\\x03\\xdc\\x8b\\xa3\\x69\\x84\\x64\\x8f\\x53\\xea\\x0c\\xc8\\x50\\xe1\\x43\\x4d\\xa5\\x82\\xba\\x89\\xed\\x0a\\x3d\\x1c\\x5f\\x91\\x87\\x58\\x86\\xfd\\x81\\xc0\\x47\\xd7\\x63\\x0c\\x03\\xef\\x99\\x90\\xbb\\x61\\xed\\xc5\\xe9\\x75\\x18\\x1a\\x19\\x6a\\x63\\xa4\\x13\\x7d\\x30\\x2c\\xef\\x88\\x6b\\xd3\\x9a\\x51\\xb9\\xcf\\x2e\\x05\\x72\\xd1\\x61\\x65\\xd6\\xa6\\x9f\\x74\\x6e\\x50\\x4a\\x56\\xcb\\x13\\x44\\xe4\\x2d\\xa4\\xfe\\x44\\x21\\x0a\\x76\\x1f\\x34\\x6d\\x41\\x86\\xde\\x4a\\x55\\x4f\\xd9\\xc1\\x7d\\x68\\xb9\\xbd\\x7e\\xff\\xf1\\xfd\\x91\\xe8\\xed\\xec\\xf2\\x1b\\xa1\\xfa\\xde\\x53\\xec\\xc0\\x68\\x88\\x4e\\xa3\\xef\\xdf\\xa1\\x67\\x26\\x1f\\x37\\x92\\xd4\\x15\\x82\\x57\\xee\\x39\\xf4\\x1d\\x86\\x67\\xa1\\x7c\\x73\\x9a\\xe5\\x60\\x5a\\x64\\x88\\x9d\\x16\\xdf\\xa5\\xf7\\x7a\\xb6\\xcb\\xf8\\xc5\\x2e\\x6e\\x4b\\x61\\x64\\x3f\\x09\\x1b\\x7e\\x8e\\x18\\x51\\xc3\\x1b\\x8f\\x62\\x50\\x7d\\xf1\\x9c\\xfc\\xa7\\x73\\x71\\xfd\\xc4\\x02\\xf7\\xdb\\xc3\\xca\\x03\\xea\\x5d\\xd6\\xd3\\xbf\\xf0\\x46\\x26\\x4b\\x06\\x4b\\x5f\\xa7\\x5f\\x11\\xf8\\xe5\\x40\\x93\\x81\\xf4\\x9a\\xa1\\x4e\\x0f\\xe6\\x1c\\x42\\x7a\\x66\\x64\\x7b\\x04\\xb2\\xe8\\x5f\\xf4\\xc6\\xfd\\x68\\xf5\\x7f\\xfa\\xac\\x98\\xfc\\xa6\\xbf\\x62\\x1e\\x03\\xd5\\xe8\\x04\\x5c\\x7a\\xa7\\xe4\\x3e\\xff\\x7a\\x68\\x31\\x28\\xad\\x82\\x82\\xc8\\xa2\\xf7\\x93\\xfc\\x14\\x9b\\x68\\xaa\\xd2\\x90\\x9b\\x9d\\xbc\\xee\\x6b\\x13\\x9a\\xb8\\x2a\\x2b\\x6a\\xa7\\x8e\\xfc\\x96\\x48\\x3f\\x4e\\x87\\xed\\x53\\x8f\\x16\\x15\\x23\\x4f\\xfb\\x73\\x17\\xc5\\x7f\\x03\\x00\\x00\\xff\\xff\\x55\\x3d\\x18\\xdf\\xf5\\x0d\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster-link/README.md.gotmpl\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"README.md.gotmpl\",\n\t\t\tmodTime:          time.Date(2025, 4, 24, 21, 9, 23, 549666844, time.UTC),\n\t\t\tuncompressedSize: 353,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\x84\\xd0\\xb1\\x0a\\xc2\\x30\\x10\\xc6\\xf1\\xdd\\xa7\\x08\\xdd\\xed\\x43\\x38\\x38\\xb9\\x09\\xee\\x47\\xf2\\xd1\\x04\\x92\\xdc\\x79\\xb9\\x0a\\x52\\xfa\\xee\\x2e\\xea\\x62\\x6c\\xe7\\xfb\\xf1\\xc1\\xff\\x96\\xc5\\x19\\x8a\\x64\\x32\\xb8\\xc1\\x47\\x52\\x1b\\x23\\x28\\x40\\x07\\x37\\xba\\x75\\x3d\\x74\\xee\\x01\\xcd\\x6b\\x12\\x4b\\x5c\\xdf\\xa8\\xa7\\x1e\\xd0\\x96\\xb8\\x9e\\x28\\x4c\\xf8\\xbf\\x65\\x4f\\xc1\\x0e\\x21\\x91\\xdb\\xef\\x56\\x4f\\x46\\x2e\\x10\\x9a\\x70\\x49\\x75\\x8b\\x29\\xee\\x73\\x52\\x14\\x54\\x6b\\x57\\xf8\\xbd\\x0e\\xca\\x33\\x36\\x5d\\x44\\x2e\\xc7\\xc0\\xbe\\x7d\\x9a\\xcf\\xcc\\xf6\\x7d\\xe0\\x2b\\x00\\x00\\xff\\xff\\xc1\\x67\\x27\\x4f\\x61\\x01\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster-link/requirements.lock\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"requirements.lock\",\n\t\t\tmodTime:          time.Date(2025, 4, 24, 21, 9, 23, 549666844, time.UTC),\n\t\t\tuncompressedSize: 223,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\x4c\\xca\\x3b\\x6e\\xc4\\x20\\x10\\x00\\xd0\\x9e\\x53\\xa0\\xed\\x8d\\x99\\xe1\\xeb\\x39\\x47\\x2e\\x80\\x61\\xd8\\x45\\xda\\x60\\x0b\\x50\\xa4\\xdc\\x3e\\x4d\\x8a\\x95\\x5e\\xf9\\x0a\\xdf\\xdc\\x0b\\xf7\\xdc\\x78\\x92\\xd8\\x64\\x4f\\xdf\\x4c\\xf2\\x4e\\x63\\xb5\\xf4\\x9e\\x42\\xca\\xc1\\xf7\\x35\\xdb\\xba\\xc6\\x2f\\xc9\\xda\\xde\\x4c\\xfb\\xae\\xd4\\xbf\\xfc\\x4a\\x63\\xcd\\xfd\\x63\\xff\\xf0\\x98\\xed\\xea\\x24\\xb5\\x02\\xa5\\x45\\x69\\x4f\\x9e\\x8b\\xe4\\x7c\\x25\\x74\\x9e\\x18\\x33\\x14\\x5d\\x5c\\x84\\x54\\x4f\\x63\\x4a\\xb5\\xde\\x02\\x94\\x1a\\x52\\x3c\\x6a\\x4e\\xe8\\x31\\x1a\\x8c\\x35\\x87\\xb3\\xe8\\x23\\x38\\xf0\\x81\\x43\\x04\\x3c\\x2b\\x60\\x64\\x0c\\x55\\x3c\\xb9\\xf3\\x48\\x8b\\x0b\\xc9\\x07\\x6a\\x84\\x4d\\xc7\\x0d\\xe0\\x0b\\x2c\\x19\\x4b\\xd6\\xa9\\x00\\x68\\xcc\\xe1\\xac\\xdf\\xb4\\x23\\xad\\x1f\\xe2\\x2f\\x00\\x00\\xff\\xff\\x0d\\xa0\\x71\\x81\\xdf\\x00\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster-link/requirements.yaml\": &vfsgen۰FileInfo{\n\t\t\tname:    \"requirements.yaml\",\n\t\t\tmodTime: time.Date(2025, 4, 24, 21, 9, 23, 549666844, time.UTC),\n\t\t\tcontent: []byte(\"\\x64\\x65\\x70\\x65\\x6e\\x64\\x65\\x6e\\x63\\x69\\x65\\x73\\x3a\\x0a\\x20\\x20\\x2d\\x20\\x6e\\x61\\x6d\\x65\\x3a\\x20\\x70\\x61\\x72\\x74\\x69\\x61\\x6c\\x73\\x0a\\x20\\x20\\x20\\x20\\x76\\x65\\x72\\x73\\x69\\x6f\\x6e\\x3a\\x20\\x30\\x2e\\x31\\x2e\\x30\\x0a\\x20\\x20\\x20\\x20\\x72\\x65\\x70\\x6f\\x73\\x69\\x74\\x6f\\x72\\x79\\x3a\\x20\\x66\\x69\\x6c\\x65\\x3a\\x2f\\x2f\\x2e\\x2e\\x2f\\x2e\\x2e\\x2f\\x2e\\x2e\\x2f\\x63\\x68\\x61\\x72\\x74\\x73\\x2f\\x70\\x61\\x72\\x74\\x69\\x61\\x6c\\x73\\x0a\"),\n\t\t},\n\t\t\"/linkerd-multicluster-link/templates\": &vfsgen۰DirInfo{\n\t\t\tname:    \"templates\",\n\t\t\tmodTime: time.Date(2025, 12, 12, 18, 49, 15, 368996883, time.UTC),\n\t\t},\n\t\t\"/linkerd-multicluster-link/templates/gateway-mirror.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"gateway-mirror.yaml\",\n\t\t\tmodTime:          time.Date(2025, 4, 24, 21, 9, 23, 549666844, time.UTC),\n\t\t\tuncompressedSize: 513,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\x84\\x91\\xbd\\x6e\\xe3\\x30\\x10\\x84\\x7b\\x3d\\xc5\\xc0\\x3d\\x79\\x38\\xe0\\x2a\\xb5\\x6e\\x0f\\xc6\\xe1\\x12\\x18\\x48\\xb9\\x26\\x37\\x0e\\x61\\xfe\\x08\\xe4\\xca\\x4e\\x40\\xeb\\xdd\\x03\\x51\\x52\\xe0\\x26\\x48\\xcb\\xfd\\x38\\x3b\\x33\\x5b\\x2b\\xdc\\x2b\\xf4\\x91\\xfc\\xc8\\x45\\x9f\\x49\\xf8\\x46\\x1f\\x9a\\x23\\x9d\\x3c\\x5b\\xa8\\x69\\xea\\x94\\x52\\x1d\\x0d\\xee\\xc8\\xb9\\xb8\\x14\\x7b\\x5c\\x7f\\x77\\x17\\x17\\x6d\\x8f\\x27\\xce\\x57\\x67\\xb8\\x0b\\x2c\\x64\\x49\\xa8\\xef\\x80\\x48\\x81\\x7b\\x0c\\x39\\x9d\\x58\\xad\\x62\\xaa\\xd6\\x4d\\x5e\\x28\\x9f\\x59\\xf6\\x7e\\x2c\\xc2\\xf9\\x40\\x81\\xa7\\x69\\xfd\\x53\\x06\\x32\\xdc\\xa3\\x56\\xe8\\xff\\xec\\x99\\x0a\\xeb\\xc3\\xf6\\x8c\\x46\\x79\\x3a\\xb1\\x2f\\xf3\\x0e\\xc0\\xbb\\x78\\xe1\\x6c\\xb5\\x4b\\xbf\\xf8\\x5d\\x38\\x2e\\xc6\\xc2\\xe8\\xc5\\x99\\x45\\xbc\\x61\\xc1\\xe5\\x9c\\xb2\\x7e\\xa0\\x97\\x17\\xb6\\x9b\\xb7\\x1e\\x3b\\xc9\\x23\\xef\\xbe\\xc1\\x57\\x31\\xb5\\xa4\\xfa\\x29\\x07\\x50\\xab\\xc2\\xcd\\xc9\\xdb\\x57\\x9f\\x26\\x85\\x90\\xe2\\xdf\\x66\\x1d\\xd3\\x54\\x2b\\x24\\xbd\\x50\\xf0\\xd0\\xb8\\x43\\xb2\\x0b\\xb8\\x23\\xba\\x68\\x39\\x0a\\xfe\\x34\\x40\\x81\\xa3\\x9d\\x13\\x97\\x81\\xcd\\x9c\\x76\\x48\\x59\\x5a\\x6c\\xb5\\x96\\x1b\\x8c\\x6a\\xfd\\xb6\\x8d\\xf3\\xf4\\xd1\\xd9\\x76\\xc0\\x46\\xe8\\x79\\xba\\x5a\\x1b\\x72\\x92\\x64\\x92\\xef\\xf1\\xbc\\xff\\xd7\\xd5\\xda\\xd6\\xcc\\xe7\\xfd\\x0c\\x00\\x00\\xff\\xff\\xed\\x3b\\x35\\x42\\x01\\x02\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster-link/templates/psp.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"psp.yaml\",\n\t\t\tmodTime:          time.Date(2025, 4, 24, 21, 9, 23, 549666844, time.UTC),\n\t\t\tuncompressedSize: 586,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\x94\\x92\\xcd\\x4a\\x3b\\x31\\x14\\xc5\\xf7\\x79\\x8a\\xfb\\x02\\xc9\\x9f\\x3f\\xb8\\x90\\xd9\\xa9\\x0b\\x37\\x52\\x4a\\x0b\\x05\\x97\\x77\\x32\\xb7\\xed\\xb5\\xf9\\x22\\xb9\\x53\\xc5\\x74\\xde\\x5d\\x66\\xfa\\xa1\\x52\\x11\\xdc\\x9e\\x1c\\xce\\x39\\xf9\\x25\\xb5\\xf2\\x1a\\xcc\\x0a\\x5d\\x4f\\xc5\\x50\\xc0\\xd6\\xd1\\x7c\\x39\\x07\\x3d\\x0c\\x4a\\x6b\\xad\\x76\\x1c\\xba\\x06\\x16\\xd1\\xd1\\x3d\\x87\\x8e\\xc3\\x46\\x61\\xe2\\x15\\xe5\\xc2\\x31\\x34\\x90\\x5b\\xb4\\x06\\x7b\\xd9\\xc6\\xcc\\xef\\x28\\x1c\\x83\\xd9\\xdd\\x16\\xc3\\xf1\\xdf\\xfe\\xbf\\xf2\\x24\\xd8\\xa1\\x60\\xa3\\x00\\x02\\x7a\\x6a\\xc0\\x71\\xd8\\x51\\xee\\xb4\\xef\\x9d\\xb0\\x75\\x7d\\x11\\xca\\x7a\\x14\\x75\\x2a\\x49\\xd7\\x7a\\x9e\\x21\\x98\\x37\\x24\\x0f\\x47\\xc3\\x0c\\x3d\\x0d\\xc3\\x29\\xa3\\x24\\xb4\\xd4\\x40\\xad\\x60\\x16\\xe4\\x08\\x0b\\x99\\xd9\\x59\\x86\\xc9\\xe5\\xb0\\x25\\x57\\xc6\\x4e\\x38\\xf7\\x8d\\x7b\\xe8\\x4d\\x28\\x1c\\x47\\x7f\\x6d\\x9f\\x6c\\xdf\\x82\\xaf\\x73\\xa7\\x58\\x80\\x5a\\x35\\xbc\\xb2\\x6c\\x2f\\xb0\\x6c\\xf4\\x3e\\x86\\xa7\\xa9\\x0f\\x86\\xa1\\x56\\x90\\xf8\\x8c\\xde\\x81\\x81\\x03\\x48\\x66\\x0f\\x07\\x08\\x1c\\x3a\\x0a\\x02\\x37\\x93\\x41\\x03\\x85\\x6e\\x9c\\x99\\xa3\\xa3\\x05\\xad\\xc7\\x95\\x98\\xf8\\x31\\xc7\\x3e\\xfd\\x02\\x53\\x01\\x7c\\x3e\\xc3\\x05\\x66\\x2a\\x49\\x95\\xbe\\x7d\\x21\\x2b\\xa5\\x51\\xfa\\x64\\x59\\x52\\xde\\xb3\\xa5\\x3b\\x6b\\x63\\x1f\\xe4\\x8a\\x7c\\x39\\x1e\\x6b\\xcf\\x39\\xc7\\xfc\\x37\\xe4\\x3f\\x92\\xa9\\x75\\xba\\xd3\\xf8\\x5d\\x3e\\x02\\x00\\x00\\xff\\xff\\xfb\\x8e\\x2a\\xeb\\x4a\\x02\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster-link/templates/service-mirror.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"service-mirror.yaml\",\n\t\t\tmodTime:          time.Date(2025, 12, 12, 18, 49, 15, 368996883, time.UTC),\n\t\t\tuncompressedSize: 7698,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xec\\x59\\x5f\\x6f\\xdb\\xba\\x15\\x7f\\xf7\\xa7\\x38\\x10\\xfa\\xb0\\x01\\xa5\\xd3\\x02\\x43\\xd7\\x08\\xe8\\x43\\x9a\\xf4\\x1f\\x90\\x66\\x46\\xba\\x14\\x18\\x86\\x62\\xa0\\xa5\\x63\\x87\\x0b\\x45\\x72\\xe4\\x91\\x13\\xcf\\xf5\\x77\\x1f\\x28\\xd1\\x32\\x65\\x49\\x8e\\xb3\\xdc\\xe6\\x5e\\xdc\\xdb\\xbe\\xd4\\x20\\x0f\\xcf\\xdf\\xdf\\xf9\\x1d\\x52\\xb9\\x11\\x2a\\x4f\\xe1\\x54\\x96\\x8e\\xd0\\x5e\\x6a\\x89\\x23\\x6e\\xc4\\x57\\xb4\\x4e\\x68\\x95\\x82\\x9d\\xf2\\x6c\\xcc\\x4b\\xba\\xd6\\x56\\xfc\\x97\\x93\\xd0\\x6a\\x7c\\xf3\\xda\\x8d\\x85\\x3e\\x5a\\xbc\\x1c\\x15\\x48\\x3c\\xe7\\xc4\\xd3\\x11\\x80\\xe2\\x05\\xa6\\x20\\x85\\xba\\x41\\x9b\\x33\\x87\\x76\\x21\\x32\\x64\\x85\\xb0\\x56\\x5b\\xc6\\xb3\\x0c\\x9d\\x63\\x52\\x67\\x5c\\x32\\x8b\\x4e\\x97\\x36\\x43\\xc7\\x56\\xab\\xf1\\x57\\x2e\\x4b\\x74\\x63\\xe2\\x76\\x8e\\x14\\x9c\\xb8\\xe0\\x05\\xae\\xd7\\x23\\x00\\xc9\\xa7\\x28\\x9d\\xd7\\x0e\\x1b\\xcd\\xde\\x32\\xde\\x11\\xaa\\xda\\xbd\\xa2\\x94\\x24\\xb2\\xfa\\x58\\x25\\x96\\xe9\\xc2\\x68\\x85\\x8a\\x52\\x68\\xfb\\x50\\xed\\xd6\\x3f\\xc7\\x91\\xae\\x70\\x96\\xd5\\xee\\xdf\\xe7\\x10\\xc0\\x6a\\xc5\\xe0\\x56\\xd0\\x35\\x6c\\x04\\x33\\x5d\\x14\\x5a\\x9d\\x57\\x9e\\xc2\\x7a\\xbd\\x5a\\x01\\xe9\\x7f\\xf0\\x42\\xc2\\x18\\xbe\\x03\\x59\\x51\\xc0\\x77\\x50\\x42\\xe5\\xa8\\x08\\xfe\\x52\\x09\\x30\\x40\\x95\\xc3\\x7a\\x3d\\xb2\\xa5\\x44\\x97\\x8e\\x18\\x70\\x23\\x3e\\x58\\x5d\\x1a\\x97\\xc2\\x3f\\x93\\xe4\\xdb\\x08\\xa0\\xc9\\x91\\x5f\\x41\\x95\\x1b\\x2d\\x14\\xb9\\xe4\\x39\\x24\\x21\\x2a\\x57\\x89\\x2d\\xd0\\x4e\\x2b\\x11\\x29\\x1c\\xf9\\xdd\\x39\\x56\\xff\\xdd\\x72\\xca\\xae\\xfd\\x8f\\xcc\\x22\\x27\\xf4\\xbf\\x72\\x94\\x58\\xff\\x2a\\x4d\\xee\\xd7\\xbe\\xdd\\x6f\\xd8\\x27\\xc5\\x19\\x7e\\x98\\xb5\\x6f\\x23\\x1f\\x9a\\x98\\x35\\x99\\x41\\xc5\\xa7\\x12\\x2f\\x36\\x3a\\x4e\\xbd\\x2b\\x42\\x2b\\x1f\\xf9\\xff\\x6d\\x39\\xc4\\x53\\xdb\\x42\\x95\\x7b\\x5d\\x8c\\x8d\\x3a\\x18\\x7e\\x2b\\x54\\x2e\\xd4\\xfc\\x27\\x94\\x9f\\x0a\\xca\\x5a\\xe2\\x25\\xce\\x7c\\x7c\\x9b\\xca\\xee\\x49\\xf8\\x08\\xa0\\xcb\\x3a\\x3f\\x22\\xef\\xae\\x9c\\xfe\\x1b\\x33\\xaa\\x9a\\xac\\xb6\\xf8\\xa5\\xd6\\x7a\\x92\\x65\\xba\\x54\\x74\\x9f\\xd1\\xfb\\x92\\xd8\\xc0\\xb4\\xca\\xf7\\x25\\x4a\\xe4\\x0e\\xc7\\x0d\\xe4\\x5b\\xe8\\xfc\\x41\\xd4\\x6a\\x91\\xe7\\xcc\\x62\\xa1\\x09\\x59\\x66\\x31\\x77\\x0f\\x73\\x1a\\xba\\x5e\\xc3\\x1f\\x88\\x7b\\x01\\xfa\\xa8\\x68\\x87\\x8c\\x1c\\x66\\x16\\xc9\\xed\\x6c\\x55\\xf9\\xaa\\x38\\x29\\x84\\xe1\\xb3\\x8f\\x8a\\x04\\x97\\xf7\\xd4\\x20\\x28\\xba\\x8f\\x4e\\x3b\\xbe\\xc5\\x09\\x8f\\xf2\\xd8\\xe7\\xb2\\xdf\\x75\\x4f\\x65\\xe7\\xc8\\x11\\xa7\\xb2\\x63\\xae\\x99\\x33\\x1d\\x03\\x99\\xd6\\x36\\x17\\x2a\\xc6\\x7c\\xaf\\x72\\x8f\\xcb\\x8e\\xda\\xed\\x48\\x0b\\x71\\x04\\x3b\\xcf\\x21\\x31\\x21\\xa2\\x76\\xd3\\xfd\\xb8\\x59\\xf0\\xb3\\xf7\\x9e\\x72\\x58\\x1c\\x32\\x25\\x1e\\x5a\\x91\\xed\\x80\\xf0\\x20\\x1d\\x18\\x11\\x8f\\x1c\\x12\\x0f\\x1b\\x13\\x3b\\xd6\\x63\\xd0\\x3e\\x04\\x9a\\x3f\\x81\\x78\\x00\\x10\\xab\\x0b\\xab\\xca\\x64\\x99\\xa3\\x27\\x0f\\x5b\\x71\\xf7\\x58\\x14\\x7c\\x8e\\xcc\\x94\\x52\\xb2\\x0d\\xf3\\x37\\xc6\\xaa\\xbd\\x49\\x29\\xe5\\x97\\x7a\\x07\\x42\\xed\\xe2\\x32\\x71\\x63\\x9c\\xa7\\x91\\xba\\x9e\\x67\\x68\\xa4\\x5e\\x16\\xa8\\xa8\\x55\\xbc\\xdf\\x79\\x66\\x9f\\x00\\x9d\\xce\\x60\\x96\\x56\\xef\\x06\\x23\\x45\\xc6\\x5d\\x2d\\x1a\\xd4\\x6e\\x16\\x6b\\x5f\\x2c\\x2e\\x84\\x4f\\xea\\x47\\xe1\\x48\\xdb\\xe5\\xb9\\x28\\x04\\xc5\\x19\\xe9\\xdb\\xaf\\x0e\\x3a\\x94\\x98\\x91\\xb6\\x75\\x9d\\x0a\\x3f\\x5e\\xce\\xa3\\xc2\\xb5\\x6a\\xd2\\x1f\\x6a\\x90\\x7b\\x6c\\x75\\x7a\\x9f\\x56\\x13\\x9d\\x9f\\x28\\x12\\x27\\xb3\\x99\\x50\\x82\\x96\\x75\\xac\\x8e\\x2c\\x27\\x9c\\x2f\\x6b\\x0f\\xad\\x96\\x52\\xa8\\xf9\\x55\\x35\\x22\\x37\\x4e\\x17\\xfc\\xee\\x4a\\xf1\\x05\\x17\\xd2\\x6b\\x49\\xe1\\x65\\x30\\xd0\\xd4\\x8e\\xb0\\x30\\xb2\\x39\\x10\\xc3\\xd6\\xff\\xe3\\x4a\\x69\\xaa\\x18\\xba\\x49\\x43\\x0b\\xc3\\x42\\x79\\x4a\\x4d\\xa1\\x76\\x32\\x6f\\x44\\x36\\x11\\xf3\\x92\\xb4\\xcb\\xb8\\x44\\x3b\\xbe\\x29\\xa7\\x68\\x15\\x12\\x56\\xa3\\xd7\\xf1\\x19\\x32\\xd2\\x0c\\x17\\xc2\\x9f\\x4f\\xc8\\x96\\x98\\x6c\\x8f\\x6b\\x35\\x13\\xf3\\x31\\x97\\xe6\\x9a\\xc7\\x89\\x34\\x56\\xdf\\x2d\\xd9\\x2d\\x17\\xc4\\xa6\\x38\\xd3\\x16\\x19\\xde\\x09\\xf2\\xad\\xab\\x55\\xee\\x52\\x48\\x5e\\x6c\\x75\\x74\\x20\\x6e\\x7c\\x0a\\x9b\\x70\\xee\\x03\\xf9\\xeb\\x5d\\x90\\x57\\x91\\xb7\\x00\\x71\\x70\\x37\\x3f\\x04\\x3d\\xbf\\x44\\x77\\x0f\\x85\\x7f\\x58\\x7b\\xf7\\x44\\xbe\\xe9\\xbf\\xc3\\xc0\\xb9\\xcb\\x31\\xcf\\xc8\\x22\\x42\\xfa\\x06\\x72\\x44\\x73\\xaa\\xcd\\x12\\xc6\\x10\\xc9\\x3c\\xfb\\x97\\xdf\\x73\\x48\\x41\\x30\\x69\\x52\\xb5\\xe5\\xe2\\x4e\\xa4\\xc0\\xf6\\x69\\xa8\\xea\\x94\\x40\\xb2\\x3f\\x95\\x49\\x4b\\x49\\x33\\x1c\\x36\\xd2\\x3c\\x84\\x93\\x04\\xad\\xdb\\x0c\\xbd\\x8a\\xdd\\x8f\\xd2\\xd4\\xc1\\x8b\\x87\\x7f\\xe1\\x67\\x7b\\x7b\\xd2\\xff\\x5d\\xdf\\xa0\\x4a\\x61\\xc6\\xa5\\xc3\\x86\\x5c\\x14\\x71\\xa1\\xd0\\x36\\xe8\\x62\\xc0\\xed\\x3c\\xc2\\x1a\\x83\\x01\\xbc\\x30\\x60\\x52\\xcf\\x99\\xc4\\x05\\xca\\x37\\x5b\\x70\\x48\\x3d\\x3f\\xf7\\x4b\\x11\\x26\\x82\\xe4\\x4c\\xdb\\x82\\x53\\x5b\\xf4\\x7d\\xb5\\xd6\\x96\\xc5\\x05\\x2a\\x62\\x16\\xff\\x53\\x62\\x89\\x4c\\x7a\\xa6\\x8c\\x0e\\x05\\x6f\\x3e\\x57\\xce\\x5c\\x22\\xc5\\x5c\\xda\\xa8\\x68\\xc8\\xfd\\xcd\\xc0\\x65\\x28\\x86\\x6b\\x07\\x57\\x1f\\x91\\xe7\\x12\\x9d\\x0b\\xd9\\x73\\xb0\\xe3\\x5f\\x25\\xc4\\xae\\x83\\xd4\\xa6\\x9f\\x5c\\x4b\\x69\\xab\\x1e\\x03\\x76\\x7a\\xbf\\x5b\\x75\\x0d\\x35\\xc1\\xb0\\x2c\\xc8\\xed\\xb3\\xb4\\x3d\\x67\\x8c\\xd5\\xb3\\x28\\x73\\xa1\\x69\\xfc\\x2a\\x7c\\x87\\x1c\\x67\\xbc\\x94\\x54\\xa3\\xa1\\x75\\xfe\\xf0\\x46\\x17\\x33\\xd0\\x16\\x7a\\x2b\\x73\\x92\\xe7\\xc2\\xbb\\xca\\xe5\\x3b\\xb5\\xe8\\x17\\x79\\x77\\x67\\xd0\\x0a\\x7f\\x6d\\xa9\\x85\\x22\\xe5\\xa8\\x16\\xe9\\x30\\xa5\\xec\\x31\\xb4\\xe3\\x60\\xc4\\x36\\x5b\\x9a\\x61\\x3b\\x42\\x3d\\xa5\\x1a\\x36\\xb7\\xc7\\xe9\\xc7\\x18\\xdc\\x59\\xaa\\xee\\x80\\x31\\xe5\\xfa\\x3e\\xf5\\x83\\x16\\xed\\x27\\xbf\\xb5\\x5e\\xa7\\x83\\x7b\\xe1\\x92\\x18\\x69\\xab\\xf9\\x7b\\xa0\\x8d\\x1d\\x66\\xa5\\x15\\xb4\\x3c\\xd5\\x8a\\xf0\\x8e\\xb6\\x69\\x07\\xe0\\x52\\xea\\xdb\\x89\\x15\\x0b\\x21\\x71\\x8e\\xef\\xfc\\x40\\xad\\xe0\\xd7\\xa6\\x90\\x8a\\x46\\xb8\\xe1\\x53\\x21\\x05\\x09\\x74\\xb1\\x06\\x80\\xdc\\x6a\\xd3\\x5e\\x61\\x70\\x72\\x7e\\x1e\\xad\\xf8\\xf7\\xd4\\xdf\\x94\\x5c\\x5e\\x6a\\x4d\\xef\\x85\\x44\\xb7\\x74\\x84\\x45\\x0a\\x7e\\x34\\xc7\\x62\\xa5\\x3a\\x71\\x17\\x5a\\x79\\xb1\\xfe\\xcd\\x2b\\x87\\x36\\xce\\x59\\xab\\x6e\\x57\\x9f\\xce\\xa2\\x8c\\x84\\x13\\xe1\\x5d\\x38\\x70\\xe4\\xc3\\xce\\x11\\x87\\x99\\x1f\\x11\\x13\\xab\\x67\\x42\\x62\\x3b\\x28\\x5a\\x1a\\x4c\\xe1\\xb2\\x54\\x24\\x0a\\x3c\\xab\\x5b\\xab\\x11\\x58\\x68\\x59\\x16\\xf8\\xd9\\x53\\x70\\x8b\\x58\\x2b\\x8a\\x9e\\x70\\xba\\x4e\\xe1\\x68\\xc1\\xed\\x91\\x2d\\xd5\\x51\\x78\\x0e\\x1c\\xed\\x5c\\x5b\\x6a\\xbf\\x78\\xf4\\x5e\\x8c\\x4b\\xeb\\x85\\x19\\x37\\x22\\x7c\\xc1\\xec\\x49\\xee\\x4e\\xc6\\x8c\\xb6\\x6d\\x57\\x9a\\x39\\x30\\xd1\\x96\\x52\\x38\\x3e\\x3e\\x3e\\xee\\x58\\xe1\\x79\\x21\\x14\\xbb\\x26\\x32\\xc3\\xbd\\xd2\\x7c\\x5c\\x89\\xd1\\x1c\\x7d\\x71\\x69\\xdf\\x00\\x36\\x2d\\xf2\\xf2\\xc5\\xde\\x7e\\x18\\x84\\xe8\\x70\\x45\\xf6\\xd4\\xc3\\xb5\\x66\\xe2\\xc5\\xe3\\x9e\\xdd\\x9b\\xe2\\x46\\xe3\\xb3\\xff\\xc5\\x17\\xc4\\xc6\\x05\\x57\\x25\\x97\\xac\\x2a\\x7d\\x63\\x2e\\xd4\\x95\\x91\\x1f\\xd0\\x49\\x95\\x9a\\x86\\x3c\\xea\\x7b\\xd2\\xc4\\xe2\\x4c\\xdc\\xc1\\x9f\\x2c\\x1a\\xe4\\x04\\x7f\\x85\\x04\\x92\\x3f\\x6f\\x33\\xd4\\xa9\\x83\\xd2\\x39\\x7e\\x09\\x2f\\x8b\\xad\\x58\\xbc\\x3a\\x54\\x8b\\xd7\\x6d\\xad\\xad\\x42\\x74\\xcc\\x90\\x96\\x68\\x9b\\x8b\\x6d\\x90\\x8a\\x16\\x87\\x8c\\xbc\\xea\\x35\\x72\\xe8\\x0b\\x64\\xfb\\x25\\x63\\xa2\\xf3\\x33\\xe1\\x6c\\x69\\xbc\\xb5\\xb7\\x65\\x3e\\xc7\\xf6\\xe7\\x0c\\xa3\\xa5\\xc8\\x96\\x47\\xbf\\xee\\x57\\x8d\\x43\\xae\\xe0\\x9d\\x07\\xcf\\x6a\\xd5\\x03\\xa2\\x48\\x6a\\x5c\\x7f\\xa3\\xcc\\xd9\\x74\\x99\\xd4\\xf7\\xda\\xcd\\x65\\xb9\\xef\\xe9\\xf5\\x1b\\x79\\x64\\x6e\\xff\\xa2\\xf6\\xbf\\x00\\x00\\x00\\xff\\xff\\xe5\\x61\\xeb\\x69\\x12\\x1e\\x00\\x00\"),\n\t\t},\n\t\t\"/linkerd-multicluster-link/values-ha.yaml\": &vfsgen۰FileInfo{\n\t\t\tname:    \"values-ha.yaml\",\n\t\t\tmodTime: time.Date(2025, 4, 24, 21, 9, 23, 549666844, time.UTC),\n\t\t\tcontent: []byte(\"\\x65\\x6e\\x61\\x62\\x6c\\x65\\x50\\x6f\\x64\\x41\\x6e\\x74\\x69\\x41\\x66\\x66\\x69\\x6e\\x69\\x74\\x79\\x3a\\x20\\x74\\x72\\x75\\x65\\x0a\\x72\\x65\\x70\\x6c\\x69\\x63\\x61\\x73\\x3a\\x20\\x33\\x0a\"),\n\t\t},\n\t\t\"/linkerd-multicluster-link/values.yaml\": &vfsgen۰CompressedFileInfo{\n\t\t\tname:             \"values.yaml\",\n\t\t\tmodTime:          time.Date(2025, 4, 24, 21, 9, 23, 549666844, time.UTC),\n\t\t\tuncompressedSize: 2236,\n\n\t\t\tcompressedContent: []byte(\"\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x02\\xff\\xa4\\x56\\x4d\\x6f\\x1b\\x37\\x10\\xbd\\xef\\xaf\\x18\\xc4\\x87\\x26\\x40\\xad\\xc4\\x69\\x0b\\xb4\\x7b\\x73\\x9d\\xe6\\x03\\x70\\x0c\\xc1\\x72\\x72\\x29\\x0a\\x98\\x22\\x47\\xbb\\x53\\x73\\x39\\x5b\\x0e\\x57\\xaa\\x5a\\xf4\\xbf\\x17\\x43\\x72\\xa5\\x28\\x89\\x7b\\xe9\\x51\\xcb\\x99\\xf7\\xe6\\xf3\\x8d\\xce\\xe0\\xfc\\x1c\\x5e\\xb1\\x7d\\xc0\\x08\\x34\\x98\\x0e\\x61\\xc3\\x11\\x52\\x8f\\xb0\\xc2\\xb8\\x25\\x8b\\x30\\x50\\x8c\\x1c\\xc1\\xf2\\x30\\x72\\xc0\\x90\\xe0\\xe9\\x24\\x28\\xd9\\xe4\\x9a\\xc2\\x03\\x46\\x07\\x96\\x43\\x8a\\xec\\x3d\\xc6\\xe6\\xac\\xa0\\x3c\\x6b\\x8e\\xdf\\xde\\xe9\\x87\\x16\\x6c\\x5c\\xf8\\x1f\\xdc\\x82\\xf8\\xb9\\x2f\\x6e\\xcf\\x4f\\xdc\\xce\\xcf\\xe1\\xce\\x74\\x5f\\xb0\\xbf\\x9f\\xd9\\x43\\x32\\x14\\x30\\x9e\\xc4\\xfa\\x39\\xc9\\x47\\x8c\\x42\\x1c\\x5a\\xa8\\x0c\\xf5\\xf7\\x47\\xe3\\x27\\x2c\\x14\\xaf\\x39\\xc2\\x32\\xd2\\xd6\\x24\\x04\\x57\\x90\\x22\\x76\\x24\\x29\\x12\\xca\\xb7\\x60\\xa6\\xd4\\x63\\x48\\x64\\x4d\\x22\\x0e\\x40\\x02\\x01\\xd1\\xa1\\x5b\\x34\\x67\\x00\\xb7\\xc5\\x70\\x0f\\x82\\x36\\x62\\x12\\x30\\x11\\xc1\\x8c\\xa3\\x27\\x74\\x90\\x38\\x87\\x1d\\x51\\x46\\xb4\\x89\\xb6\\x08\\x52\\x33\\x30\\xd6\\xf2\\x14\\x92\\x34\\x39\\xe4\\xe5\\xe4\\xfd\\xaa\\xf8\\xb7\\xf0\\xeb\\x6f\\x25\\xaa\\x4b\\xe7\\x48\\x09\\x8d\\x07\\x13\\x02\\xa7\\xcc\\x2e\\x8a\\x69\\x5c\\x86\\x36\\xde\\xc3\\xc8\\x4e\\x9a\\x91\\xdd\\xe5\\xd1\\xa2\\x85\\xbf\\xff\\xf9\\x02\\xc1\\x9b\\x35\\xfa\\xc7\\x9c\\xaf\\xf3\\xe3\\xd1\\xef\\xfa\\x68\\x3c\\x8e\\x7e\\x3f\\x9b\\x47\\x14\\x9e\\xa2\\x45\\x69\\x2c\\x0f\\x03\\x87\\xcf\\xdd\\xee\\xb8\\xeb\\x3c\\x82\\x4c\\xe3\\xc8\\x31\\xe5\\xa6\\x95\\x31\\xa1\\xd0\\x41\\x8f\\xc6\\x79\\x14\\x99\\x2b\\x20\\x0d\\x06\\xb3\\xf6\\xf8\\xb6\\x7e\\xaf\\xad\\x95\\x16\\x36\\xc6\\x0b\\x3e\\x8a\\x68\\x23\\x9a\\xa4\\x80\\xc1\\x0c\\x28\\xa3\\xb1\\x28\\x9f\\x30\\x1d\\xd0\\x61\\xd7\\x63\\x80\\x80\\x16\\x45\\x4c\\xdc\\x57\\xb2\\x9b\\xd9\\xe7\\x2a\\xa3\\xe8\\x50\\x7c\\xc2\\xf6\\x4b\\xb6\\x11\\x58\\xb2\\x83\\xcb\\x90\\x08\\x2e\\x37\\x1b\\x0a\\x94\\xf6\\xe0\\xb9\\x23\\xab\\x65\\x58\\x1b\\x6f\\x82\\xc5\\xdc\\xd5\\xd1\\x1b\\x8b\\x83\\xce\\x3e\\x6f\\x20\\xe2\\xe8\\xc9\\x1a\\x69\\xce\\xc0\\xd8\\xc8\\x22\\xd0\\xb3\\xe8\\x34\\x04\\x07\\x7f\\x71\\xa8\\x41\\xbe\\xa5\\xae\\x87\\xcb\\xad\\x21\\x6f\\xd6\\xe4\\x29\\xed\\x75\\x84\\x0a\\x2d\\xa4\\x9e\\x04\\x38\\xf8\\x7d\\x09\\x7d\\xcf\\x13\\xf4\\x66\\x8b\\x30\\x4c\\x3e\\xd1\\xe8\\xf1\\x40\\xa1\\x74\\x87\\xbd\\x93\\x45\\x4d\\x6d\\xa9\\x33\\x90\\x68\\x0e\\x79\\x4e\\xac\\x33\\x09\\x77\\x66\\xdf\\x36\\x00\\x39\\xc7\\xab\\xb2\\x1b\\xb9\\x3e\\xa9\\xc7\\x98\\x97\\x02\\x76\\xe4\\x7d\\xa9\\x2c\\x82\\x81\\x31\\xf2\\xfa\\x38\\xa9\\xf3\\xee\\x55\\xa4\\x06\\xa0\\x10\\xba\\x16\\x52\\x9c\\xb0\\x81\\x62\\xaf\\x0c\\x95\\xe3\\x4e\\x8b\\xa3\\xfd\\x9a\\x04\\x5d\\xf6\\xf7\\xb4\\x45\\x4f\\x41\\xdb\\xaf\\xc6\\x14\\xba\\x6c\\xad\\x46\\x2d\\x7c\\x7f\\xf1\\xd3\\x45\\x9d\\x3b\\xee\\xc0\\xe3\\x16\\xfd\\x81\\xf4\\xbd\\x26\\x6f\\xfd\\x24\\x09\\x3f\\x11\\x1b\\x69\\x3c\\x77\\xd7\\x6a\\xd8\\x02\\x85\\x0d\\x1f\\xbd\\x37\\x1c\\x07\\x93\\xe0\\xe9\\xfd\\xe8\\x0d\\x85\\x7b\\xe0\\x08\\xf7\\xbf\\x0b\\x87\\xfb\\x67\\xea\\xf2\\x3a\\xbf\\xb6\\x90\\x1f\\x8b\\xd3\\x0d\\x3b\\x4d\\xd5\\xa3\\x4d\\x1c\\xe5\\x31\\x99\\x1b\\xd9\\x35\\x81\\x1d\\xae\\xaa\\xe1\\x71\\xe6\\x6f\\xa6\\x61\\x8d\\x51\\x5b\\x22\\xa7\\x2e\\x87\\x6e\\x25\\x86\\x38\\x85\\x66\\xfe\\xdd\\x42\\x4d\\xf6\\x76\\xde\\xa6\\xc7\\xb5\\xb5\\xaa\\x5b\\x73\\x58\\xbc\\xaf\\xf1\\x26\\x1a\\x50\\x60\\x1a\\x9d\\x36\\x6f\\x13\\x79\\xa8\\x92\\x33\\x70\\x42\\x98\\x4b\\x47\\xa2\\x1b\\xcc\\xbb\\xa2\\x49\\x6b\\x7d\\xff\\x63\\xc2\\x09\\x5d\\x73\\x06\\x4f\\x23\\xaa\\xd0\\xb9\\x67\\x4d\\x4d\\xa1\\xa8\\xeb\\x2d\\xa6\\xb8\\xbf\\xa6\\x81\\x52\\x0b\\xdf\\x15\\xd2\\x0f\\xa2\\x50\\x0e\\xa6\\xe0\\x30\\xc2\\xae\\x27\\xdb\\x7f\\x4d\\x96\\xa5\\x57\\xb5\\x50\\x12\\x13\\x4e\\x31\\x3f\\xbc\\x7b\\xd5\\xc2\\xcb\\x8b\\x17\\x15\\xef\\x4d\\xe4\\x69\\xfc\\x5f\\x80\\x6f\\x4e\\x01\\x75\\xbf\\x73\\x4d\\x7a\\x84\\x64\\x62\\x87\\xe9\\x50\\x81\\xd4\\x9b\\xf4\\x8d\\x40\\xc7\\xaa\\x1d\\xa5\\x06\\xf9\\x1e\\xb8\\xa6\\x18\\x5e\\x15\\x3b\\x45\\x68\\xe1\\xc9\\x93\\x59\\x7f\\x3c\\xc6\\xaa\\xbb\\xff\\x31\\x1a\\xe9\\x68\\x96\\x3b\\x54\\x9c\\xaf\\xca\\x3e\\xdd\\xb2\\xc7\\x9f\\x29\\x38\\x0a\\x5d\\x91\\x54\\x11\\xb6\\xa4\\x2f\\x15\\xe9\\xb2\\x9c\\x82\\x1c\\xf7\\x69\\xcc\\xd5\\xa0\\x39\\x9b\\x0b\\x51\\xef\\x49\\x3d\\x70\\x3a\\xc7\\x01\\x61\\xb9\\x5a\\x1e\\xa4\\x79\\x01\\x77\\x2a\\x23\\xda\\x5d\\x8a\\xf9\\x1e\\x9b\\x04\\xf7\\x75\\x61\\x97\\xab\\xe5\\x3d\\x90\\x4a\\x94\\x60\\xca\\x58\\x71\\x42\\xe0\\x90\\x31\\xf1\\xcf\\x84\\x41\\xef\\x62\\x96\\xac\\x53\\x06\\x0a\\x92\\x8c\\xf7\\x0b\\xb8\\xd1\\x99\\x52\\xbe\\xde\\x08\\xac\\x11\\x75\\x89\\x1c\\x8e\\x11\\xad\\x49\\xe8\\x40\\x48\\xb5\\xf1\\xe1\\x47\\x81\\xed\\xc5\\xe2\\xe5\\xc5\\xac\\x4c\\xab\\xe5\\xac\\x46\\xa5\\x30\\xab\\x11\\x2d\\x6d\\xa8\\xfe\\x5d\\x08\\x87\\x49\\x66\\xef\\xe0\\xb6\\xac\\xc9\\x4a\\x2f\\xa9\\x6e\\x0e\\xea\\x0a\\xd4\\x03\\xc4\\x3b\\xd0\\xb3\\xbe\\x36\\xf6\\x61\\xd1\\x44\\xdc\\x92\\x86\\xfb\\x96\\x24\\xf1\\x61\\x50\\x2f\\x5e\\x34\\xff\\x06\\x00\\x00\\xff\\xff\\x14\\x84\\xda\\x87\\xbc\\x08\\x00\\x00\"),\n\t\t},\n\t}\n\tfs[\"/\"].(*vfsgen۰DirInfo).entries = []os.FileInfo{\n\t\tfs[\"/linkerd-multicluster\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster-link\"].(os.FileInfo),\n\t}\n\tfs[\"/linkerd-multicluster\"].(*vfsgen۰DirInfo).entries = []os.FileInfo{\n\t\tfs[\"/linkerd-multicluster/.helmignore\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/Chart.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/NOTES.txt\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/README.md\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/README.md.gotmpl\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/requirements.lock\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/requirements.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/templates\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/values-ha.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/values.yaml\"].(os.FileInfo),\n\t}\n\tfs[\"/linkerd-multicluster/templates\"].(*vfsgen۰DirInfo).entries = []os.FileInfo{\n\t\tfs[\"/linkerd-multicluster/templates/gateway-policy.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/templates/gateway.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/templates/link-crd.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/templates/local-service-mirror.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/templates/namespace-metadata-rbac.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/templates/namespace-metadata.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/templates/namespace.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/templates/psp.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/templates/remote-access-service-mirror-rbac.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster/templates/service-mirror-policy.yaml\"].(os.FileInfo),\n\t}\n\tfs[\"/linkerd-multicluster-link\"].(*vfsgen۰DirInfo).entries = []os.FileInfo{\n\t\tfs[\"/linkerd-multicluster-link/.helmignore\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster-link/Chart.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster-link/README.md\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster-link/README.md.gotmpl\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster-link/requirements.lock\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster-link/requirements.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster-link/templates\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster-link/values-ha.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster-link/values.yaml\"].(os.FileInfo),\n\t}\n\tfs[\"/linkerd-multicluster-link/templates\"].(*vfsgen۰DirInfo).entries = []os.FileInfo{\n\t\tfs[\"/linkerd-multicluster-link/templates/gateway-mirror.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster-link/templates/psp.yaml\"].(os.FileInfo),\n\t\tfs[\"/linkerd-multicluster-link/templates/service-mirror.yaml\"].(os.FileInfo),\n\t}\n\n\treturn fs\n}()\n\ntype vfsgen۰FS map[string]interface{}\n\nfunc (fs vfsgen۰FS) Open(path string) (http.File, error) {\n\tpath = pathpkg.Clean(\"/\" + path)\n\tf, ok := fs[path]\n\tif !ok {\n\t\treturn nil, &os.PathError{Op: \"open\", Path: path, Err: os.ErrNotExist}\n\t}\n\n\tswitch f := f.(type) {\n\tcase *vfsgen۰CompressedFileInfo:\n\t\tgr, err := gzip.NewReader(bytes.NewReader(f.compressedContent))\n\t\tif err != nil {\n\t\t\t// This should never happen because we generate the gzip bytes such that they are always valid.\n\t\t\tpanic(\"unexpected error reading own gzip compressed bytes: \" + err.Error())\n\t\t}\n\t\treturn &vfsgen۰CompressedFile{\n\t\t\tvfsgen۰CompressedFileInfo: f,\n\t\t\tgr:                        gr,\n\t\t}, nil\n\tcase *vfsgen۰FileInfo:\n\t\treturn &vfsgen۰File{\n\t\t\tvfsgen۰FileInfo: f,\n\t\t\tReader:          bytes.NewReader(f.content),\n\t\t}, nil\n\tcase *vfsgen۰DirInfo:\n\t\treturn &vfsgen۰Dir{\n\t\t\tvfsgen۰DirInfo: f,\n\t\t}, nil\n\tdefault:\n\t\t// This should never happen because we generate only the above types.\n\t\tpanic(fmt.Sprintf(\"unexpected type %T\", f))\n\t}\n}\n\n// vfsgen۰CompressedFileInfo is a static definition of a gzip compressed file.\ntype vfsgen۰CompressedFileInfo struct {\n\tname              string\n\tmodTime           time.Time\n\tcompressedContent []byte\n\tuncompressedSize  int64\n}\n\nfunc (f *vfsgen۰CompressedFileInfo) Readdir(count int) ([]os.FileInfo, error) {\n\treturn nil, fmt.Errorf(\"cannot Readdir from file %s\", f.name)\n}\nfunc (f *vfsgen۰CompressedFileInfo) Stat() (os.FileInfo, error) { return f, nil }\n\nfunc (f *vfsgen۰CompressedFileInfo) GzipBytes() []byte {\n\treturn f.compressedContent\n}\n\nfunc (f *vfsgen۰CompressedFileInfo) Name() string       { return f.name }\nfunc (f *vfsgen۰CompressedFileInfo) Size() int64        { return f.uncompressedSize }\nfunc (f *vfsgen۰CompressedFileInfo) Mode() os.FileMode  { return 0444 }\nfunc (f *vfsgen۰CompressedFileInfo) ModTime() time.Time { return f.modTime }\nfunc (f *vfsgen۰CompressedFileInfo) IsDir() bool        { return false }\nfunc (f *vfsgen۰CompressedFileInfo) Sys() interface{}   { return nil }\n\n// vfsgen۰CompressedFile is an opened compressedFile instance.\ntype vfsgen۰CompressedFile struct {\n\t*vfsgen۰CompressedFileInfo\n\tgr      *gzip.Reader\n\tgrPos   int64 // Actual gr uncompressed position.\n\tseekPos int64 // Seek uncompressed position.\n}\n\nfunc (f *vfsgen۰CompressedFile) Read(p []byte) (n int, err error) {\n\tif f.grPos > f.seekPos {\n\t\t// Rewind to beginning.\n\t\terr = f.gr.Reset(bytes.NewReader(f.compressedContent))\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tf.grPos = 0\n\t}\n\tif f.grPos < f.seekPos {\n\t\t// Fast-forward.\n\t\t_, err = io.CopyN(io.Discard, f.gr, f.seekPos-f.grPos)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tf.grPos = f.seekPos\n\t}\n\tn, err = f.gr.Read(p)\n\tf.grPos += int64(n)\n\tf.seekPos = f.grPos\n\treturn n, err\n}\nfunc (f *vfsgen۰CompressedFile) Seek(offset int64, whence int) (int64, error) {\n\tswitch whence {\n\tcase io.SeekStart:\n\t\tf.seekPos = 0 + offset\n\tcase io.SeekCurrent:\n\t\tf.seekPos += offset\n\tcase io.SeekEnd:\n\t\tf.seekPos = f.uncompressedSize + offset\n\tdefault:\n\t\tpanic(fmt.Errorf(\"invalid whence value: %v\", whence))\n\t}\n\treturn f.seekPos, nil\n}\nfunc (f *vfsgen۰CompressedFile) Close() error {\n\treturn f.gr.Close()\n}\n\n// vfsgen۰FileInfo is a static definition of an uncompressed file (because it's not worth gzip compressing).\ntype vfsgen۰FileInfo struct {\n\tname    string\n\tmodTime time.Time\n\tcontent []byte\n}\n\nfunc (f *vfsgen۰FileInfo) Readdir(count int) ([]os.FileInfo, error) {\n\treturn nil, fmt.Errorf(\"cannot Readdir from file %s\", f.name)\n}\nfunc (f *vfsgen۰FileInfo) Stat() (os.FileInfo, error) { return f, nil }\n\nfunc (f *vfsgen۰FileInfo) NotWorthGzipCompressing() {}\n\nfunc (f *vfsgen۰FileInfo) Name() string       { return f.name }\nfunc (f *vfsgen۰FileInfo) Size() int64        { return int64(len(f.content)) }\nfunc (f *vfsgen۰FileInfo) Mode() os.FileMode  { return 0444 }\nfunc (f *vfsgen۰FileInfo) ModTime() time.Time { return f.modTime }\nfunc (f *vfsgen۰FileInfo) IsDir() bool        { return false }\nfunc (f *vfsgen۰FileInfo) Sys() interface{}   { return nil }\n\n// vfsgen۰File is an opened file instance.\ntype vfsgen۰File struct {\n\t*vfsgen۰FileInfo\n\t*bytes.Reader\n}\n\nfunc (f *vfsgen۰File) Close() error {\n\treturn nil\n}\n\n// vfsgen۰DirInfo is a static definition of a directory.\ntype vfsgen۰DirInfo struct {\n\tname    string\n\tmodTime time.Time\n\tentries []os.FileInfo\n}\n\nfunc (d *vfsgen۰DirInfo) Read([]byte) (int, error) {\n\treturn 0, fmt.Errorf(\"cannot Read from directory %s\", d.name)\n}\nfunc (d *vfsgen۰DirInfo) Close() error               { return nil }\nfunc (d *vfsgen۰DirInfo) Stat() (os.FileInfo, error) { return d, nil }\n\nfunc (d *vfsgen۰DirInfo) Name() string       { return d.name }\nfunc (d *vfsgen۰DirInfo) Size() int64        { return 0 }\nfunc (d *vfsgen۰DirInfo) Mode() os.FileMode  { return 0755 | os.ModeDir }\nfunc (d *vfsgen۰DirInfo) ModTime() time.Time { return d.modTime }\nfunc (d *vfsgen۰DirInfo) IsDir() bool        { return true }\nfunc (d *vfsgen۰DirInfo) Sys() interface{}   { return nil }\n\n// vfsgen۰Dir is an opened dir instance.\ntype vfsgen۰Dir struct {\n\t*vfsgen۰DirInfo\n\tpos int // Position within entries for Seek and Readdir.\n}\n\nfunc (d *vfsgen۰Dir) Seek(offset int64, whence int) (int64, error) {\n\tif offset == 0 && whence == io.SeekStart {\n\t\td.pos = 0\n\t\treturn 0, nil\n\t}\n\treturn 0, fmt.Errorf(\"unsupported Seek in directory %s\", d.name)\n}\n\nfunc (d *vfsgen۰Dir) Readdir(count int) ([]os.FileInfo, error) {\n\tif d.pos >= len(d.entries) && count > 0 {\n\t\treturn nil, io.EOF\n\t}\n\tif count <= 0 || count > len(d.entries)-d.pos {\n\t\tcount = len(d.entries) - d.pos\n\t}\n\te := d.entries[d.pos : d.pos+count]\n\td.pos += count\n\treturn e, nil\n}\n"
  },
  {
    "path": "multicluster/values/values.go",
    "content": "package values\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/linkerd/linkerd2/multicluster/charts\"\n\tchartspkg \"github.com/linkerd/linkerd2/pkg/charts\"\n\t\"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"helm.sh/helm/v3/pkg/chart/loader\"\n\t\"helm.sh/helm/v3/pkg/chartutil\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nvar (\n\tHelmDefaultChartDir     = \"linkerd-multicluster\"\n\tHelmDefaultLinkChartDir = \"linkerd-multicluster-link\"\n)\n\n// Values contains the top-level elements in the Helm charts\ntype Values struct {\n\tCliVersion                     string   `json:\"cliVersion\"`\n\tControllerImage                string   `json:\"controllerImage\"`\n\tControllerImageVersion         string   `json:\"controllerImageVersion\"`\n\tGateway                        *Gateway `json:\"gateway\"`\n\tIdentityTrustDomain            string   `json:\"identityTrustDomain\"`\n\tLinkerdNamespace               string   `json:\"linkerdNamespace\"`\n\tLinkerdVersion                 string   `json:\"linkerdVersion\"`\n\tProxyOutboundPort              uint32   `json:\"proxyOutboundPort\"`\n\tServiceMirror                  bool     `json:\"serviceMirror\"`\n\tLogLevel                       string   `json:\"logLevel\"`\n\tLogFormat                      string   `json:\"logFormat\"`\n\tServiceMirrorRetryLimit        uint32   `json:\"serviceMirrorRetryLimit\"`\n\tServiceMirrorUID               int64    `json:\"serviceMirrorUID\"`\n\tServiceMirrorGID               int64    `json:\"serviceMirrorGID\"`\n\tReplicas                       uint32   `json:\"replicas\"`\n\tRemoteMirrorServiceAccount     bool     `json:\"remoteMirrorServiceAccount\"`\n\tRemoteMirrorServiceAccountName string   `json:\"remoteMirrorServiceAccountName\"`\n\tTargetClusterName              string   `json:\"targetClusterName\"`\n\tEnablePodAntiAffinity          bool     `json:\"enablePodAntiAffinity\"`\n\tRevisionHistoryLimit           uint32   `json:\"revisionHistoryLimit\"`\n\n\tServiceMirrorAdditionalEnv   []corev1.EnvVar `json:\"serviceMirrorAdditionalEnv\"`\n\tServiceMirrorExperimentalEnv []corev1.EnvVar `json:\"serviceMirrorExperimentalEnv\"`\n\n\tLocalServiceMirror *LocalServiceMirror `json:\"localServiceMirror\"`\n\tControllerDefaults *ControllerDefaults `json:\"controllerDefaults\"`\n}\n\n// Gateway contains all options related to the Gateway Service\ntype Gateway struct {\n\tEnabled            bool              `json:\"enabled\"`\n\tReplicas           uint32            `json:\"replicas\"`\n\tName               string            `json:\"name\"`\n\tPort               uint32            `json:\"port\"`\n\tNodePort           uint32            `json:\"nodePort\"`\n\tServiceType        string            `json:\"serviceType\"`\n\tProbe              *Probe            `json:\"probe\"`\n\tServiceAnnotations map[string]string `json:\"serviceAnnotations\"`\n\tLoadBalancerIP     string            `json:\"loadBalancerIP\"`\n\tPauseImage         string            `json:\"pauseImage\"`\n\tUID                int64             `json:\"UID\"`\n\tGID                int64             `json:\"GID\"`\n}\n\n// Probe contains all options for the Probe Service\ntype Probe struct {\n\tFailureThreshold uint32 `json:\"failureThreshold\"`\n\tPath             string `json:\"path\"`\n\tPort             uint32 `json:\"port\"`\n\tNodePort         uint32 `json:\"nodePort\"`\n\tSeconds          uint32 `json:\"seconds\"`\n\tTimeout          string `json:\"timeout\"`\n}\n\ntype LocalServiceMirror struct {\n\tServiceMirrorRetryLimit  uint32          `json:\"serviceMirrorRetryLimit\"`\n\tFederatedServiceSelector string          `json:\"federatedServiceSelector\"`\n\tReplias                  uint32          `json:\"replicas\"`\n\tImage                    *linkerd2.Image `json:\"image\"`\n\tLogLevel                 string          `json:\"logLevel\"`\n\tLogFormat                string          `json:\"logFormat\"`\n\tEnablePprof              bool            `json:\"enablePprof\"`\n\tUID                      int64           `json:\"UID\"`\n\tGID                      int64           `json:\"GID\"`\n}\n\n// ControllerDefaults contains all the entries for the controllerDefaults\n// section that are not empty by default\ntype ControllerDefaults struct {\n\tReplicas               uint32                     `json:\"replicas\"`\n\tImage                  *linkerd2.Image            `json:\"image\"`\n\tGateway                *ControllerDefaultsGateway `json:\"gateway\"`\n\tLogLevel               string                     `json:\"logLevel\"`\n\tLogFormat              string                     `json:\"logFormat\"`\n\tEnableHeadlessServices bool                       `json:\"enableHeadlessServices\"`\n\tEnablePprof            bool                       `json:\"enablePprof\"`\n\tUID                    int64                      `json:\"UID\"`\n\tGID                    int64                      `json:\"GID\"`\n\tRetryLimit             uint32                     `json:\"retryLimit\"`\n\tEnablePodAntiAffinity  bool                       `json:\"enablePodAntiAffinity\"`\n}\n\ntype ControllerDefaultsGateway struct {\n\tEnabled bool                     `json:\"enabled\"`\n\tProbe   *ControllerDefaultsProbe `json:\"probe\"`\n}\n\ntype ControllerDefaultsProbe struct {\n\tPort uint32 `json:\"port\"`\n}\n\n// NewInstallValues returns a new instance of the Values type.\nfunc NewInstallValues() (*Values, error) {\n\tchartDir := fmt.Sprintf(\"%s/\", HelmDefaultChartDir)\n\tv, err := readDefaults(chartDir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv.CliVersion = k8s.CreatedByAnnotationValue()\n\treturn v, nil\n}\n\n// NewLinkValues returns a new instance of the Values type.\nfunc NewLinkValues() (*Values, error) {\n\tchartDir := fmt.Sprintf(\"%s/\", HelmDefaultLinkChartDir)\n\tv, err := readDefaults(chartDir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv.CliVersion = k8s.CreatedByAnnotationValue()\n\treturn v, nil\n}\n\n// readDefaults read all the default variables from the values.yaml file.\n// chartDir is the root directory of the Helm chart where values.yaml is.\nfunc readDefaults(chartDir string) (*Values, error) {\n\tfile := &loader.BufferedFile{\n\t\tName: chartutil.ValuesfileName,\n\t}\n\tif err := chartspkg.ReadFile(charts.Templates, chartDir, file); err != nil {\n\t\treturn nil, err\n\t}\n\tvalues := Values{}\n\tif err := yaml.Unmarshal(chartspkg.InsertVersion(file.Data), &values); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &values, nil\n}\n"
  },
  {
    "path": "pkg/addr/addr.go",
    "content": "package addr\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"math/big\"\n\t\"net\"\n\t\"net/netip\"\n\t\"strconv\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/net\"\n\tl5dNetPb \"github.com/linkerd/linkerd2/controller/gen/common/net\"\n)\n\n// DefaultWeight is the default address weight sent by the Destination service\n// to the Linkerd proxies.\nconst DefaultWeight = 1\n\n// PublicAddressToString formats a Viz API TCPAddress as a string.\n//\n// If Ipv6, the bytes should be ordered big-endian. When formatted as a\n// string, the IP address should be enclosed in square brackets followed by\n// the port.\nfunc PublicAddressToString(addr *l5dNetPb.TcpAddress) string {\n\tstrIP := PublicIPToString(addr.GetIp())\n\tstrPort := strconv.Itoa(int(addr.GetPort()))\n\treturn net.JoinHostPort(strIP, strPort)\n}\n\n// PublicIPToString formats a Viz API IPAddress as a string.\nfunc PublicIPToString(ip *l5dNetPb.IPAddress) string {\n\tvar netIP net.IP\n\tif ip.GetIpv6() != nil {\n\t\tb := make([]byte, net.IPv6len)\n\t\tbinary.BigEndian.PutUint64(b[:8], ip.GetIpv6().GetFirst())\n\t\tbinary.BigEndian.PutUint64(b[8:], ip.GetIpv6().GetLast())\n\t\tnetIP = net.IP(b)\n\t} else if ip.GetIpv4() != 0 {\n\t\tnetIP = decodeIPv4ToNetIP(ip.GetIpv4())\n\t}\n\tif netIP == nil {\n\t\treturn \"\"\n\t}\n\treturn netIP.String()\n}\n\n// ProxyAddressToString formats a Proxy API TCPAddress as a string.\nfunc ProxyAddressToString(addr *pb.TcpAddress) string {\n\tvizIP := FromProxyAPI(addr.GetIp())\n\tif vizIP == nil {\n\t\treturn \"\"\n\t}\n\tstrIP := PublicIPToString(vizIP)\n\tstrPort := strconv.Itoa(int(addr.GetPort()))\n\treturn net.JoinHostPort(strIP, strPort)\n}\n\n// ParseProxyIP parses an IP Address string into a Proxy API IPAddress.\nfunc ParseProxyIP(ip string) (*pb.IPAddress, error) {\n\taddr, err := netip.ParseAddr(ip)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid IP address: %s\", ip)\n\t}\n\n\tif addr.Is4() {\n\t\tipBytes := addr.As4()\n\t\treturn &pb.IPAddress{\n\t\t\tIp: &pb.IPAddress_Ipv4{\n\t\t\t\tIpv4: binary.BigEndian.Uint32(ipBytes[:]),\n\t\t\t},\n\t\t}, nil\n\t} else if addr.Is6() {\n\t\tipBytes := addr.As16()\n\t\treturn &pb.IPAddress{\n\t\t\tIp: &pb.IPAddress_Ipv6{\n\t\t\t\tIpv6: &pb.IPv6{\n\t\t\t\t\tFirst: binary.BigEndian.Uint64(ipBytes[:8]),\n\t\t\t\t\tLast:  binary.BigEndian.Uint64(ipBytes[8:]),\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"invalid IP address: %s\", ip)\n}\n\n// ParsePublicIP parses an IP Address string into a Viz API IPAddress.\nfunc ParsePublicIP(ip string) (*l5dNetPb.IPAddress, error) {\n\taddr, err := ParseProxyIP(ip)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn FromProxyAPI(addr), nil\n}\n\n// NetToPublic converts a Proxy API TCPAddress to a Viz API\n// TCPAddress.\nfunc NetToPublic(net *pb.TcpAddress) *l5dNetPb.TcpAddress {\n\tip := FromProxyAPI(net.GetIp())\n\n\treturn &l5dNetPb.TcpAddress{\n\t\tIp:   ip,\n\t\tPort: net.GetPort(),\n\t}\n}\n\n// FromProxyAPI casts an IPAddress from the linkerd2-proxy-api.go.net package\n// to the linkerd2.controller.gen.common.net package\nfunc FromProxyAPI(net *pb.IPAddress) *l5dNetPb.IPAddress {\n\tswitch ip := net.GetIp().(type) {\n\tcase *pb.IPAddress_Ipv6:\n\t\treturn &l5dNetPb.IPAddress{\n\t\t\tIp: &l5dNetPb.IPAddress_Ipv6{\n\t\t\t\tIpv6: &l5dNetPb.IPv6{\n\t\t\t\t\tFirst: ip.Ipv6.First,\n\t\t\t\t\tLast:  ip.Ipv6.Last,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\tcase *pb.IPAddress_Ipv4:\n\t\treturn &l5dNetPb.IPAddress{\n\t\t\tIp: &l5dNetPb.IPAddress_Ipv4{\n\t\t\t\tIpv4: ip.Ipv4,\n\t\t\t},\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// decodeIPv4ToNetIP converts IPv4 uint32 to an IPv4 net IP.\nfunc decodeIPv4ToNetIP(ip uint32) net.IP {\n\toBigInt := big.NewInt(0)\n\toBigInt = oBigInt.SetUint64(uint64(ip))\n\treturn IntToIPv4(oBigInt)\n}\n\n// IPToInt converts net.IP to bigInt\n// It can support both IPv4 and IPv6.\nfunc IPToInt(ip net.IP) *big.Int {\n\toBigInt := big.NewInt(0)\n\toBigInt.SetBytes(ip)\n\treturn oBigInt\n}\n\n// IntToIPv4 converts IPv4 bigInt into an IPv4 net IP.\nfunc IntToIPv4(intip *big.Int) net.IP {\n\tipByte := make([]byte, net.IPv4len)\n\tuint32IP := intip.Uint64()\n\tbinary.BigEndian.PutUint32(ipByte, uint32(uint32IP))\n\treturn net.IP(ipByte)\n}\n"
  },
  {
    "path": "pkg/addr/addr_test.go",
    "content": "package addr\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/net\"\n\tl5dNetPb \"github.com/linkerd/linkerd2/controller/gen/common/net\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc TestPublicAddressToString(t *testing.T) {\n\tcases := []struct {\n\t\tname     string\n\t\taddr     *l5dNetPb.TcpAddress\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"ipv4\",\n\t\t\taddr: &l5dNetPb.TcpAddress{\n\t\t\t\tIp: &l5dNetPb.IPAddress{\n\t\t\t\t\tIp: &l5dNetPb.IPAddress_Ipv4{\n\t\t\t\t\t\tIpv4: 3232235521,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPort: 1234,\n\t\t\t},\n\t\t\texpected: \"192.168.0.1:1234\",\n\t\t},\n\t\t{\n\t\t\tname: \"ipv6\",\n\t\t\taddr: &l5dNetPb.TcpAddress{\n\t\t\t\tIp: &l5dNetPb.IPAddress{\n\t\t\t\t\tIp: &l5dNetPb.IPAddress_Ipv6{\n\t\t\t\t\t\tIpv6: &l5dNetPb.IPv6{\n\t\t\t\t\t\t\tFirst: 49320,\n\t\t\t\t\t\t\tLast:  1,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPort: 1234,\n\t\t\t},\n\t\t\texpected: \"[::c0a8:0:0:0:1]:1234\",\n\t\t},\n\t\t{\n\t\t\tname:     \"nil\",\n\t\t\taddr:     nil,\n\t\t\texpected: \":0\",\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tc := c\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tgot := PublicAddressToString(c.addr)\n\t\t\tif c.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v, got: %v\", c.expected, got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPublicIPToString(t *testing.T) {\n\tcases := []struct {\n\t\tname     string\n\t\taddr     *l5dNetPb.IPAddress\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"ipv4\",\n\t\t\taddr: &l5dNetPb.IPAddress{\n\t\t\t\tIp: &l5dNetPb.IPAddress_Ipv4{\n\t\t\t\t\tIpv4: 3232235521,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"192.168.0.1\",\n\t\t},\n\t\t{\n\t\t\tname: \"normal ipv6\",\n\t\t\taddr: &l5dNetPb.IPAddress{\n\t\t\t\tIp: &l5dNetPb.IPAddress_Ipv6{\n\t\t\t\t\tIpv6: &l5dNetPb.IPv6{\n\t\t\t\t\t\tFirst: 2306139570357600256,\n\t\t\t\t\t\tLast:  151930230829876,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"2001:db8:85a3::8a2e:370:7334\",\n\t\t},\n\t\t{\n\t\t\tname: \"ipv6 with zero as prefix\",\n\t\t\taddr: &l5dNetPb.IPAddress{\n\t\t\t\tIp: &l5dNetPb.IPAddress_Ipv6{\n\t\t\t\t\tIpv6: &l5dNetPb.IPv6{\n\t\t\t\t\t\tFirst: 49320,\n\t\t\t\t\t\tLast:  1,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"::c0a8:0:0:0:1\",\n\t\t},\n\t\t{\n\t\t\tname:     \"nil\",\n\t\t\taddr:     nil,\n\t\t\texpected: \"\",\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tc := c\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tgot := PublicIPToString(c.addr)\n\t\t\tif c.expected != got {\n\t\t\t\tt.Errorf(\"expected: %v, got: %v\", c.expected, got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNetToPublic(t *testing.T) {\n\n\ttype addrExp struct {\n\t\tproxyAddr     *pb.TcpAddress\n\t\tpublicAddress *l5dNetPb.TcpAddress\n\t}\n\n\texpectations := []addrExp{\n\t\t{\n\t\t\tproxyAddr:     &pb.TcpAddress{},\n\t\t\tpublicAddress: &l5dNetPb.TcpAddress{},\n\t\t},\n\t\t{\n\t\t\tproxyAddr: &pb.TcpAddress{\n\t\t\t\tIp:   &pb.IPAddress{Ip: &pb.IPAddress_Ipv4{Ipv4: 1}},\n\t\t\t\tPort: 1234,\n\t\t\t},\n\t\t\tpublicAddress: &l5dNetPb.TcpAddress{\n\t\t\t\tIp:   &l5dNetPb.IPAddress{Ip: &l5dNetPb.IPAddress_Ipv4{Ipv4: 1}},\n\t\t\t\tPort: 1234,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tproxyAddr: &pb.TcpAddress{\n\t\t\t\tIp: &pb.IPAddress{\n\t\t\t\t\tIp: &pb.IPAddress_Ipv6{\n\t\t\t\t\t\tIpv6: &pb.IPv6{\n\t\t\t\t\t\t\tFirst: 2345,\n\t\t\t\t\t\t\tLast:  6789,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPort: 1234,\n\t\t\t},\n\t\t\tpublicAddress: &l5dNetPb.TcpAddress{\n\t\t\t\tIp: &l5dNetPb.IPAddress{\n\t\t\t\t\tIp: &l5dNetPb.IPAddress_Ipv6{\n\t\t\t\t\t\tIpv6: &l5dNetPb.IPv6{\n\t\t\t\t\t\t\tFirst: 2345,\n\t\t\t\t\t\t\tLast:  6789,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPort: 1234,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor i, exp := range expectations {\n\t\texp := exp // pin\n\t\tt.Run(fmt.Sprintf(\"%d returns expected Viz API TCPAddress\", i), func(t *testing.T) {\n\t\t\tres := NetToPublic(exp.proxyAddr)\n\t\t\tif !proto.Equal(res, exp.publicAddress) {\n\t\t\t\tt.Fatalf(\"Unexpected TCP Address: [%+v] expected: [%+v]\", res, exp.publicAddress)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseProxyIP(t *testing.T) {\n\tvar testCases = []struct {\n\t\tip      string\n\t\texpAddr *pb.IPAddress\n\t\texpErr  bool\n\t}{\n\t\t{\n\t\t\tip:      \"10.0\",\n\t\t\texpAddr: nil,\n\t\t\texpErr:  true,\n\t\t},\n\t\t{\n\t\t\tip:      \"x.x.x.x\",\n\t\t\texpAddr: nil,\n\t\t\texpErr:  true,\n\t\t},\n\t\t{\n\t\t\tip: \"10.10.10.10\",\n\t\t\texpAddr: &pb.IPAddress{\n\t\t\t\tIp: &pb.IPAddress_Ipv4{Ipv4: 168430090},\n\t\t\t},\n\t\t\texpErr: false,\n\t\t},\n\t\t{\n\t\t\tip: \"2001:db8:85a3::8a2e:370:7334\",\n\t\t\texpAddr: &pb.IPAddress{\n\t\t\t\tIp: &pb.IPAddress_Ipv6{\n\t\t\t\t\tIpv6: &pb.IPv6{\n\t\t\t\t\t\tFirst: 2306139570357600256,\n\t\t\t\t\t\tLast:  151930230829876,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpErr: false,\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tres, err := ParseProxyIP(testCase.ip)\n\t\tif testCase.expErr && err == nil {\n\t\t\tt.Fatalf(\"expected get err, but get nil\")\n\t\t}\n\t\tif !testCase.expErr {\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected err %v\", err)\n\t\t\t}\n\t\t\tif !proto.Equal(res, testCase.expAddr) {\n\t\t\t\tt.Fatalf(\"Unexpected TCP Address: [%+v] expected: [%+v]\", res, testCase.expAddr)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestParsePublicIP(t *testing.T) {\n\tvar testCases = []struct {\n\t\tip      string\n\t\texpAddr *l5dNetPb.IPAddress\n\t\texpErr  bool\n\t}{\n\t\t{\n\t\t\tip:      \"10.0\",\n\t\t\texpAddr: nil,\n\t\t\texpErr:  true,\n\t\t},\n\t\t{\n\t\t\tip:      \"x.x.x.x\",\n\t\t\texpAddr: nil,\n\t\t\texpErr:  true,\n\t\t},\n\t\t{\n\t\t\tip: \"10.10.10.11\",\n\t\t\texpAddr: &l5dNetPb.IPAddress{\n\t\t\t\tIp: &l5dNetPb.IPAddress_Ipv4{Ipv4: 168430091},\n\t\t\t},\n\t\t\texpErr: false,\n\t\t},\n\t\t{\n\t\t\tip: \"2001:db8:85a3::8a2e:370:7334\",\n\t\t\texpAddr: &l5dNetPb.IPAddress{\n\t\t\t\tIp: &l5dNetPb.IPAddress_Ipv6{\n\t\t\t\t\tIpv6: &l5dNetPb.IPv6{\n\t\t\t\t\t\tFirst: 2306139570357600256,\n\t\t\t\t\t\tLast:  151930230829876,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpErr: false,\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tres, err := ParsePublicIP(testCase.ip)\n\t\tif testCase.expErr && err == nil {\n\t\t\tt.Fatalf(\"expected get err, but get nil\")\n\t\t}\n\t\tif !testCase.expErr {\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected err %v\", err)\n\t\t\t}\n\t\t\tif !proto.Equal(res, testCase.expAddr) {\n\t\t\t\tt.Fatalf(\"Unexpected TCP Address: [%+v] expected: [%+v]\", res, testCase.expAddr)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestProxyAddressToString(t *testing.T) {\n\tvar testCases = []struct {\n\t\taddr   *pb.TcpAddress\n\t\texpStr string\n\t}{\n\t\t{\n\t\t\taddr: &pb.TcpAddress{\n\t\t\t\tIp:   &pb.IPAddress{Ip: &pb.IPAddress_Ipv4{Ipv4: 1}},\n\t\t\t\tPort: 1234,\n\t\t\t},\n\t\t\texpStr: \"0.0.0.1:1234\",\n\t\t},\n\t\t{\n\t\t\taddr: &pb.TcpAddress{\n\t\t\t\tIp:   &pb.IPAddress{Ip: &pb.IPAddress_Ipv4{Ipv4: 65535}},\n\t\t\t\tPort: 5678,\n\t\t\t},\n\t\t\texpStr: \"0.0.255.255:5678\",\n\t\t},\n\t\t{\n\t\t\taddr: &pb.TcpAddress{\n\t\t\t\tIp: &pb.IPAddress{\n\t\t\t\t\tIp: &pb.IPAddress_Ipv6{\n\t\t\t\t\t\tIpv6: &pb.IPv6{\n\t\t\t\t\t\t\tFirst: 2306139570357600256,\n\t\t\t\t\t\t\tLast:  151930230829876,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPort: 5678,\n\t\t\t},\n\t\t\texpStr: \"[2001:db8:85a3::8a2e:370:7334]:5678\",\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tres := ProxyAddressToString(testCase.addr)\n\t\tif !(res == testCase.expStr) {\n\t\t\tt.Fatalf(\"Unexpected string: %s expected: %s\", res, testCase.expStr)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/admin/admin.go",
    "content": "package admin\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/pprof\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n)\n\ntype handler struct {\n\tpromHandler http.Handler\n\tenablePprof bool\n\tready       *bool\n}\n\n// NewServer returns an initialized `http.Server`, configured to listen on an address.\nfunc NewServer(addr string, enablePprof bool, ready *bool) *http.Server {\n\th := &handler{\n\t\tpromHandler: promhttp.Handler(),\n\t\tenablePprof: enablePprof,\n\t\tready:       ready,\n\t}\n\n\treturn &http.Server{\n\t\tAddr:              addr,\n\t\tHandler:           h,\n\t\tReadHeaderTimeout: 15 * time.Second,\n\t}\n}\n\nfunc (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {\n\tdebugPathPrefix := \"/debug/pprof/\"\n\tif h.enablePprof && strings.HasPrefix(req.URL.Path, debugPathPrefix) {\n\t\tswitch req.URL.Path {\n\t\tcase fmt.Sprintf(\"%scmdline\", debugPathPrefix):\n\t\t\tpprof.Cmdline(w, req)\n\t\tcase fmt.Sprintf(\"%sprofile\", debugPathPrefix):\n\t\t\tpprof.Profile(w, req)\n\t\tcase fmt.Sprintf(\"%strace\", debugPathPrefix):\n\t\t\tpprof.Trace(w, req)\n\t\tcase fmt.Sprintf(\"%ssymbol\", debugPathPrefix):\n\t\t\tpprof.Symbol(w, req)\n\t\tdefault:\n\t\t\tpprof.Index(w, req)\n\t\t}\n\t\treturn\n\t}\n\tswitch req.URL.Path {\n\tcase \"/metrics\":\n\t\th.promHandler.ServeHTTP(w, req)\n\tcase \"/ping\":\n\t\th.servePing(w)\n\tcase \"/ready\":\n\t\th.serveReady(w)\n\tdefault:\n\t\thttp.NotFound(w, req)\n\t}\n}\n\nfunc (h *handler) servePing(w http.ResponseWriter) {\n\tw.Write([]byte(\"pong\\n\"))\n}\n\nfunc (h *handler) serveReady(w http.ResponseWriter) {\n\tif *h.ready {\n\t\tw.Write([]byte(\"ok\\n\"))\n\t} else {\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\tw.Write([]byte(\"not ready\\n\"))\n\t}\n}\n"
  },
  {
    "path": "pkg/charts/charts.go",
    "content": "package charts\n\nimport (\n\t\"bytes\"\n\t\"embed\"\n\t\"errors\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/charts\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\t\"helm.sh/helm/v3/pkg/chart/loader\"\n\t\"helm.sh/helm/v3/pkg/chartutil\"\n\t\"helm.sh/helm/v3/pkg/engine\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nconst versionPlaceholder = \"linkerdVersionValue\"\n\nvar (\n\t// L5dPartials is the list of templates in partials chart\n\t// Keep this slice synced with the contents of charts/partials\n\tL5dPartials = []string{\n\t\t\"partials/\" + chartutil.ChartfileName,\n\t\t\"partials/templates/_affinity.tpl\",\n\t\t\"partials/templates/_capabilities.tpl\",\n\t\t\"partials/templates/_debug.tpl\",\n\t\t\"partials/templates/_helpers.tpl\",\n\t\t\"partials/templates/_metadata.tpl\",\n\t\t\"partials/templates/_nodeselector.tpl\",\n\t\t\"partials/templates/_network-validator.tpl\",\n\t\t\"partials/templates/_proxy-config-ann.tpl\",\n\t\t\"partials/templates/_proxy-init.tpl\",\n\t\t\"partials/templates/_proxy.tpl\",\n\t\t\"partials/templates/_pull-secrets.tpl\",\n\t\t\"partials/templates/_resources.tpl\",\n\t\t\"partials/templates/_tolerations.tpl\",\n\t\t\"partials/templates/_trace.tpl\",\n\t\t\"partials/templates/_validate.tpl\",\n\t\t\"partials/templates/_volumes.tpl\",\n\t}\n)\n\n// Chart holds the necessary info to render a Helm chart\ntype Chart struct {\n\tName      string\n\tDir       string\n\tNamespace string\n\n\t// RawValues are yaml-formatted values entries. Either this or Values\n\t// should be set, but not both\n\tRawValues []byte\n\n\t// Values are the config key-value entries. Either this or RawValues should\n\t// be set, but not both\n\tValues map[string]any\n\n\tFiles []*loader.BufferedFile\n\tFs    embed.FS\n}\n\nfunc (c *Chart) render(partialsFiles []*loader.BufferedFile) (bytes.Buffer, error) {\n\tif err := FilesReader(c.Fs, c.Dir+\"/\", c.Files); err != nil {\n\t\treturn bytes.Buffer{}, err\n\t}\n\n\t// Create chart and render templates\n\tchart, err := loader.LoadFiles(append(c.Files, partialsFiles...))\n\tif err != nil {\n\t\treturn bytes.Buffer{}, err\n\t}\n\n\treleaseOptions := chartutil.ReleaseOptions{\n\t\tName:      c.Name,\n\t\tIsInstall: true,\n\t\tIsUpgrade: false,\n\t\tNamespace: c.Namespace,\n\t}\n\n\tif len(c.RawValues) > 0 {\n\t\tif c.Values != nil {\n\t\t\treturn bytes.Buffer{}, errors.New(\"either RawValues or Values should be set, but not both\")\n\t\t}\n\t\terr = yaml.Unmarshal(c.RawValues, &c.Values)\n\t\tif err != nil {\n\t\t\treturn bytes.Buffer{}, err\n\t\t}\n\t}\n\n\tvaluesToRender, err := chartutil.ToRenderValues(chart, c.Values, releaseOptions, nil)\n\tif err != nil {\n\t\treturn bytes.Buffer{}, err\n\t}\n\trelease, _ := valuesToRender[\"Release\"].(map[string]interface{})\n\trelease[\"Service\"] = \"CLI\"\n\n\trenderedTemplates, err := engine.Render(chart, valuesToRender)\n\tif err != nil {\n\t\treturn bytes.Buffer{}, err\n\t}\n\n\t// Merge templates and inject\n\tvar buf bytes.Buffer\n\tfor _, tmpl := range c.Files {\n\t\tt := path.Join(releaseOptions.Name, tmpl.Name)\n\t\tif _, err := buf.WriteString(renderedTemplates[t]); err != nil {\n\t\t\treturn bytes.Buffer{}, err\n\t\t}\n\t}\n\n\treturn buf, nil\n}\n\n// Render returns a bytes buffer with the result of rendering a Helm chart\nfunc (c *Chart) Render() (bytes.Buffer, error) {\n\n\tpartials, err := LoadPartials()\n\tif err != nil {\n\t\treturn bytes.Buffer{}, err\n\t}\n\n\treturn c.render(partials)\n}\n\n// RenderCNI returns a bytes buffer with the result of rendering a Helm chart\nfunc (c *Chart) RenderCNI() (bytes.Buffer, error) {\n\tcniPartials := []*loader.BufferedFile{\n\t\t{Name: \"partials/\" + chartutil.ChartfileName},\n\t\t{Name: \"partials/templates/_helpers.tpl\"},\n\t\t{Name: \"partials/templates/_metadata.tpl\"},\n\t\t{Name: \"partials/templates/_pull-secrets.tpl\"},\n\t\t{Name: \"partials/templates/_tolerations.tpl\"},\n\t\t{Name: \"partials/templates/_resources.tpl\"},\n\t}\n\n\t// Load all partial chart files into buffer\n\tif err := FilesReader(charts.Templates, \"\", cniPartials); err != nil {\n\t\treturn bytes.Buffer{}, err\n\t}\n\n\t// The partials files must have the \"charts/\" prefix to be loaded correctly.\n\tfor i, f := range cniPartials {\n\t\tcniPartials[i].Name = path.Join(\"charts\", f.Name)\n\t}\n\treturn c.render(cniPartials)\n}\n\n// ReadFile updates the buffered file with the data read from disk\nfunc ReadFile(fs embed.FS, dir string, f *loader.BufferedFile) error {\n\tfile, err := fs.Open(dir + f.Name)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\n\tbuf := new(bytes.Buffer)\n\tif _, err := buf.ReadFrom(file); err != nil {\n\t\treturn err\n\t}\n\n\tf.Data = buf.Bytes()\n\treturn nil\n}\n\n// FilesReader reads all the files from a directory\nfunc FilesReader(fs embed.FS, dir string, files []*loader.BufferedFile) error {\n\tfor _, f := range files {\n\t\tif err := ReadFile(fs, dir, f); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc LoadPartials() ([]*loader.BufferedFile, error) {\n\tvar partialFiles []*loader.BufferedFile\n\tfor _, template := range L5dPartials {\n\t\tpartialFiles = append(partialFiles,\n\t\t\t&loader.BufferedFile{Name: template},\n\t\t)\n\t}\n\n\t// Load all partial chart files into buffer\n\tif err := FilesReader(charts.Templates, \"\", partialFiles); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// The partials files must have the \"charts/\" prefix to be loaded correctly.\n\tfor i, f := range partialFiles {\n\t\tpartialFiles[i].Name = path.Join(\"charts\", f.Name)\n\t}\n\treturn partialFiles, nil\n}\n\n// InsertVersion returns the chart values file contents passed in\n// with the version placeholder replaced with the current version\nfunc InsertVersion(data []byte) []byte {\n\tdataWithVersion := strings.ReplaceAll(string(data), versionPlaceholder, version.Version)\n\treturn []byte(dataWithVersion)\n}\n\n// InsertVersionValues returns the chart values with the version placeholder\n// replaced with the current version.\nfunc InsertVersionValues(values chartutil.Values) (chartutil.Values, error) {\n\traw, err := values.YAML()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn chartutil.ReadValues(InsertVersion([]byte(raw)))\n}\n\n// OverrideFromFile overrides the given map with the given file from FS\nfunc OverrideFromFile(values map[string]interface{}, fs embed.FS, chartName, name string) (map[string]interface{}, error) {\n\t// Load Values file\n\tvaluesOverride := loader.BufferedFile{\n\t\tName: name,\n\t}\n\tif err := ReadFile(fs, chartName+\"/\", &valuesOverride); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar valuesOverrideMap map[string]interface{}\n\terr := yaml.Unmarshal(valuesOverride.Data, &valuesOverrideMap)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn MergeMaps(valuesOverrideMap, values), nil\n}\n\n// MergeMaps returns the resultant map after merging given two maps of type map[string]interface{}\n// The inputs are not mutated and the second map i.e b's values take precedence during merge.\n// This gives semantically correct merge compared with `mergo.Merge` (with boolean values).\n// See https://github.com/imdario/mergo/issues/129\nfunc MergeMaps(a, b map[string]interface{}) map[string]interface{} {\n\tout := make(map[string]interface{}, len(a))\n\tfor k, v := range a {\n\t\tout[k] = v\n\t}\n\tfor k, v := range b {\n\t\tif v, ok := v.(map[string]interface{}); ok {\n\t\t\tif av, ok := out[k]; ok {\n\t\t\t\tif av, ok := av.(map[string]interface{}); ok {\n\t\t\t\t\tout[k] = MergeMaps(av, v)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tout[k] = v\n\t}\n\treturn out\n}\n"
  },
  {
    "path": "pkg/charts/charts_test.go",
    "content": "package charts\n\nimport (\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n)\n\nfunc TestMergeMaps(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\ta, b, expected map[string]interface{}\n\t}{\n\t\t{\n\t\t\ta:        map[string]interface{}{\"aaa\": \"foo\"},\n\t\t\tb:        map[string]interface{}{\"bbb\": \"bar\"},\n\t\t\texpected: map[string]interface{}{\"aaa\": \"foo\", \"bbb\": \"bar\"},\n\t\t},\n\t\t{\n\t\t\ta:        map[string]interface{}{\"aaa\": \"foo\"},\n\t\t\tb:        map[string]interface{}{\"aaa\": \"bar\", \"bbb\": \"bar\"},\n\t\t\texpected: map[string]interface{}{\"aaa\": \"bar\", \"bbb\": \"bar\"},\n\t\t},\n\t\t{\n\t\t\ta:        map[string]interface{}{\"aaa\": \"foo\", \"bbb\": map[string]interface{}{\"aaa\": \"foo\"}},\n\t\t\tb:        map[string]interface{}{\"aaa\": \"bar\", \"bbb\": map[string]interface{}{\"aaa\": \"bar\"}},\n\t\t\texpected: map[string]interface{}{\"aaa\": \"bar\", \"bbb\": map[string]interface{}{\"aaa\": \"bar\"}},\n\t\t},\n\t\t{\n\t\t\ta:        map[string]interface{}{\"aaa\": \"foo\", \"bbb\": map[string]interface{}{\"aaa\": \"foo\"}},\n\t\t\tb:        map[string]interface{}{\"aaa\": \"foo\", \"bbb\": map[string]interface{}{\"aaa\": \"bar\", \"ccc\": \"foo\"}},\n\t\t\texpected: map[string]interface{}{\"aaa\": \"foo\", \"bbb\": map[string]interface{}{\"aaa\": \"bar\", \"ccc\": \"foo\"}},\n\t\t},\n\t} {\n\t\tif diff := deep.Equal(MergeMaps(tc.a, tc.b), tc.expected); diff != nil {\n\t\t\tt.Errorf(\"mismatch: %+v\", diff)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/charts/cni/values.go",
    "content": "package cni\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/linkerd/linkerd2/charts\"\n\tchartspkg \"github.com/linkerd/linkerd2/pkg/charts\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"helm.sh/helm/v3/pkg/chart/loader\"\n\t\"helm.sh/helm/v3/pkg/chartutil\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nconst (\n\thelmDefaultCNIChartDir = \"linkerd2-cni\"\n)\n\n// Image contains details about the location of the container image\ntype Image struct {\n\tName       string      `json:\"name\"`\n\tVersion    string      `json:\"version\"`\n\tPullPolicy interface{} `json:\"pullPolicy\"`\n}\n\n// Constraints wraps the Limit and Request settings for computational resources\ntype Constraints struct {\n\tLimit   string `json:\"limit\"`\n\tRequest string `json:\"request\"`\n}\n\n// Resources represents the computational resources setup for a given container\ntype Resources struct {\n\tCPU              Constraints `json:\"cpu\"`\n\tMemory           Constraints `json:\"memory\"`\n\tEphemeralStorage Constraints `json:\"ephemeral-storage\"`\n}\n\n// RepairController contains the config for the repair-controller container\ntype RepairController struct {\n\tImage                 Image     `json:\"image\"`\n\tLogLevel              string    `json:\"logLevel\"`\n\tLogFormat             string    `json:\"logFormat\"`\n\tEnableSecurityContext bool      `json:\"enableSecurityContext\"`\n\tResources             Resources `json:\"resources\"`\n}\n\n// Values contains the top-level elements in the cni Helm chart\ntype Values struct {\n\tInboundProxyPort     uint                   `json:\"inboundProxyPort\"`\n\tOutboundProxyPort    uint                   `json:\"outboundProxyPort\"`\n\tIgnoreInboundPorts   string                 `json:\"ignoreInboundPorts\"`\n\tIgnoreOutboundPorts  string                 `json:\"ignoreOutboundPorts\"`\n\tCliVersion           string                 `json:\"cliVersion\"`\n\tImage                Image                  `json:\"image\"`\n\tLogLevel             string                 `json:\"logLevel\"`\n\tPortsToRedirect      string                 `json:\"portsToRedirect\"`\n\tProxyUID             int64                  `json:\"proxyUID\"`\n\tProxyGID             int64                  `json:\"proxyGID\"`\n\tDestCNINetDir        string                 `json:\"destCNINetDir\"`\n\tDestCNIBinDir        string                 `json:\"destCNIBinDir\"`\n\tUseWaitFlag          bool                   `json:\"useWaitFlag\"`\n\tPriorityClassName    string                 `json:\"priorityClassName\"`\n\tProxyAdminPort       string                 `json:\"proxyAdminPort\"`\n\tProxyControlPort     string                 `json:\"proxyControlPort\"`\n\tTolerations          []interface{}          `json:\"tolerations\"`\n\tPodLabels            map[string]string      `json:\"podLabels\"`\n\tCommonLabels         map[string]string      `json:\"commonLabels\"`\n\tImagePullSecrets     []map[string]string    `json:\"imagePullSecrets\"`\n\tExtraInitContainers  []interface{}          `json:\"extraInitContainers\"`\n\tIptablesMode         string                 `json:\"iptablesMode\"`\n\tDisableIPv6          bool                   `json:\"disableIPv6\"`\n\tEnablePSP            bool                   `json:\"enablePSP\"`\n\tPrivileged           bool                   `json:\"privileged\"`\n\tResources            Resources              `json:\"resources\"`\n\tRepairController     RepairController       `json:\"repairController\"`\n\tRevisionHistoryLimit uint                   `json:\"revisionHistoryLimit\"`\n\tUpdateStrategy       map[string]interface{} `json:\"updateStrategy\"`\n}\n\n// NewValues returns a new instance of the Values type.\nfunc NewValues() (*Values, error) {\n\tchartDir := fmt.Sprintf(\"%s/\", helmDefaultCNIChartDir)\n\tv, err := readDefaults(chartDir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv.CliVersion = k8s.CreatedByAnnotationValue()\n\treturn v, nil\n}\n\n// ToMap converts Values into a map[string]interface{}\nfunc (v *Values) ToMap() (map[string]interface{}, error) {\n\tvar valuesMap map[string]interface{}\n\trawValues, err := yaml.Marshal(v)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to marshal the values struct: %w\", err)\n\t}\n\n\terr = yaml.Unmarshal(rawValues, &valuesMap)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to Unmarshal Values into a map: %w\", err)\n\t}\n\n\treturn valuesMap, nil\n}\n\n// readDefaults reads all the default variables from the values.yaml file.\n// chartDir is the root directory of the Helm chart where values.yaml is.\nfunc readDefaults(chartDir string) (*Values, error) {\n\tfile := &loader.BufferedFile{\n\t\tName: chartutil.ValuesfileName,\n\t}\n\tif err := chartspkg.ReadFile(charts.Templates, chartDir, file); err != nil {\n\t\treturn nil, err\n\t}\n\tvalues := Values{}\n\tif err := yaml.Unmarshal(chartspkg.InsertVersion(file.Data), &values); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &values, nil\n}\n"
  },
  {
    "path": "pkg/charts/linkerd2/values.go",
    "content": "package linkerd2\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"dario.cat/mergo\"\n\t\"github.com/linkerd/linkerd2/charts\"\n\tchartspkg \"github.com/linkerd/linkerd2/pkg/charts\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\t\"helm.sh/helm/v3/pkg/chart/loader\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nvar (\n\t// HelmChartDirCrds is the directory name for the linkerd-crds chart\n\tHelmChartDirCrds = \"linkerd-crds\"\n\n\t// HelmChartDirCP is the directory name for the linkerd-control-plane chart\n\tHelmChartDirCP = \"linkerd-control-plane\"\n)\n\ntype (\n\t// Values contains the top-level elements in the Helm charts\n\tValues struct {\n\t\tControllerImage           string                 `json:\"controllerImage\"`\n\t\tControllerReplicas        uint                   `json:\"controllerReplicas\"`\n\t\tControllerUID             int64                  `json:\"controllerUID\"`\n\t\tControllerGID             int64                  `json:\"controllerGID\"`\n\t\tEnableH2Upgrade           bool                   `json:\"enableH2Upgrade\"`\n\t\tEnablePodAntiAffinity     bool                   `json:\"enablePodAntiAffinity\"`\n\t\tNodeAffinity              map[string]interface{} `json:\"nodeAffinity\"`\n\t\tEnablePodDisruptionBudget bool                   `json:\"enablePodDisruptionBudget\"`\n\t\tController                *Controller            `json:\"controller\"`\n\t\tWebhookFailurePolicy      string                 `json:\"webhookFailurePolicy\"`\n\t\tDeploymentStrategy        map[string]interface{} `json:\"deploymentStrategy,omitempty\"`\n\t\tDisableHeartBeat          bool                   `json:\"disableHeartBeat\"`\n\t\tHeartbeatSchedule         string                 `json:\"heartbeatSchedule\"`\n\t\tConfigs                   ConfigJSONs            `json:\"configs\"`\n\t\tClusterDomain             string                 `json:\"clusterDomain\"`\n\t\tClusterNetworks           string                 `json:\"clusterNetworks\"`\n\t\tImagePullPolicy           string                 `json:\"imagePullPolicy\"`\n\t\tCliVersion                string                 `json:\"cliVersion\"`\n\t\tControllerLogLevel        string                 `json:\"controllerLogLevel\"`\n\t\tControllerLogFormat       string                 `json:\"controllerLogFormat\"`\n\t\tProxyContainerName        string                 `json:\"proxyContainerName\"`\n\t\tHighAvailability          bool                   `json:\"highAvailability\"`\n\t\tCNIEnabled                bool                   `json:\"cniEnabled\"`\n\t\tEnableEndpointSlices      bool                   `json:\"enableEndpointSlices\"`\n\t\tDisableIPv6               bool                   `json:\"disableIPv6\"`\n\t\tIdentityTrustAnchorsPEM   string                 `json:\"identityTrustAnchorsPEM\"`\n\t\tIdentityTrustDomain       string                 `json:\"identityTrustDomain\"`\n\t\tPrometheusURL             string                 `json:\"prometheusUrl\"`\n\t\tImagePullSecrets          []map[string]string    `json:\"imagePullSecrets\"`\n\t\tLinkerdVersion            string                 `json:\"linkerdVersion\"`\n\t\tRevisionHistoryLimit      uint                   `json:\"revisionHistoryLimit\"`\n\n\t\tDestinationController *DestinationController `json:\"destinationController\"`\n\t\tHeartbeat             map[string]interface{} `json:\"heartbeat\"`\n\t\tSPValidator           map[string]interface{} `json:\"spValidator\"`\n\n\t\tPodAnnotations    map[string]string `json:\"podAnnotations\"`\n\t\tPodLabels         map[string]string `json:\"podLabels\"`\n\t\tPriorityClassName string            `json:\"priorityClassName\"`\n\n\t\tPodMonitor       *PodMonitor       `json:\"podMonitor\"`\n\t\tPolicyController *PolicyController `json:\"policyController\"`\n\t\tProxy            *Proxy            `json:\"proxy\"`\n\t\tProxyInit        *ProxyInit        `json:\"proxyInit\"`\n\t\tNetworkValidator *NetworkValidator `json:\"networkValidator\"`\n\t\tIdentity         *Identity         `json:\"identity\"`\n\t\tDebugContainer   *DebugContainer   `json:\"debugContainer\"`\n\t\tProxyInjector    *ProxyInjector    `json:\"proxyInjector\"`\n\t\tProfileValidator *Webhook          `json:\"profileValidator\"`\n\t\tPolicyValidator  *Webhook          `json:\"policyValidator\"`\n\t\tNodeSelector     map[string]string `json:\"nodeSelector\"`\n\t\tTolerations      []interface{}     `json:\"tolerations\"`\n\n\t\tDestinationResources   *Resources `json:\"destinationResources\"`\n\t\tHeartbeatResources     *Resources `json:\"heartbeatResources\"`\n\t\tIdentityResources      *Resources `json:\"identityResources\"`\n\t\tProxyInjectorResources *Resources `json:\"proxyInjectorResources\"`\n\n\t\tDestinationProxyResources   *Resources `json:\"destinationProxyResources\"`\n\t\tIdentityProxyResources      *Resources `json:\"identityProxyResources\"`\n\t\tProxyInjectorProxyResources *Resources `json:\"proxyInjectorProxyResources\"`\n\t\tEgress                      *Egress    `json:\"egress\"`\n\t}\n\n\t// Resources represents the computational resources setup for a given container\n\tEgress struct {\n\t\tGlobalEgressNetworkNamespace string `json:\"globalEgressNetworkNamespace\"`\n\t}\n\n\tDestinationController struct {\n\t\tMeshedHttp2ClientProtobuf map[string]interface{} `json:\"meshedHttp2ClientProtobuf\"`\n\t\tPodAnnotations            map[string]string      `json:\"podAnnotations\"`\n\t}\n\n\t// PodDisruptionBudget contains the fields to set the PDB\n\tPodDisruptionBudget struct {\n\t\tMaxUnavailable string `json:\"maxUnavailable\"`\n\t}\n\n\t// ConfigJSONs is the JSON encoding of the Linkerd configuration\n\tConfigJSONs struct {\n\t\tGlobal  string `json:\"global\"`\n\t\tProxy   string `json:\"proxy\"`\n\t\tInstall string `json:\"install\"`\n\t}\n\n\tController struct {\n\t\tPodDisruptionBudget *PodDisruptionBudget `json:\"podDisruptionBudget\"`\n\t\tTracing             *Tracing             `json:\"tracing\"`\n\t}\n\n\t// Proxy contains the fields to set the proxy sidecar container\n\tProxy struct {\n\t\tCapabilities                         *Capabilities    `json:\"capabilities\"`\n\t\tEnableExternalProfiles               bool             `json:\"enableExternalProfiles\"`\n\t\tImage                                *Image           `json:\"image\"`\n\t\tEnableShutdownEndpoint               bool             `json:\"enableShutdownEndpoint\"`\n\t\tLogLevel                             string           `json:\"logLevel\"`\n\t\tLogFormat                            string           `json:\"logFormat\"`\n\t\tLogHTTPHeaders                       string           `json:\"logHTTPHeaders\"`\n\t\tSAMountPath                          *VolumeMountPath `json:\"saMountPath\"`\n\t\tPorts                                *Ports           `json:\"ports\"`\n\t\tResources                            *Resources       `json:\"resources\"`\n\t\tUID                                  int64            `json:\"uid\"`\n\t\tGID                                  int64            `json:\"gid\"`\n\t\tWaitBeforeExitSeconds                uint64           `json:\"waitBeforeExitSeconds\"`\n\t\tIsGateway                            bool             `json:\"isGateway\"`\n\t\tIsIngress                            bool             `json:\"isIngress\"`\n\t\tRequireIdentityOnInboundPorts        string           `json:\"requireIdentityOnInboundPorts\"`\n\t\tOutboundConnectTimeout               string           `json:\"outboundConnectTimeout\"`\n\t\tInboundConnectTimeout                string           `json:\"inboundConnectTimeout\"`\n\t\tOutboundDiscoveryCacheUnusedTimeout  string           `json:\"outboundDiscoveryCacheUnusedTimeout\"`\n\t\tInboundDiscoveryCacheUnusedTimeout   string           `json:\"inboundDiscoveryCacheUnusedTimeout\"`\n\t\tDisableOutboundProtocolDetectTimeout bool             `json:\"disableOutboundProtocolDetectTimeout\"`\n\t\tDisableInboundProtocolDetectTimeout  bool             `json:\"disableInboundProtocolDetectTimeout\"`\n\t\tPodInboundPorts                      string           `json:\"podInboundPorts\"`\n\t\tOpaquePorts                          string           `json:\"opaquePorts\"`\n\t\tAwait                                bool             `json:\"await\"`\n\t\tDefaultInboundPolicy                 string           `json:\"defaultInboundPolicy\"`\n\t\tOutboundTransportMode                string           `json:\"outboundTransportMode\"`\n\t\tAccessLog                            string           `json:\"accessLog\"`\n\t\tShutdownGracePeriod                  string           `json:\"shutdownGracePeriod\"`\n\t\tNativeSidecar                        bool             `json:\"nativeSidecar\"`\n\t\tStartupProbe                         *StartupProbe    `json:\"startupProbe\"`\n\t\tReadinessProbe                       *Probe           `json:\"readinessProbe\"`\n\t\tLivenessProbe                        *Probe           `json:\"livenessProbe\"`\n\t\tControl                              *ProxyControl    `json:\"control\"`\n\t\tMetrics                              *ProxyMetrics    `json:\"metrics\"`\n\t\tTracing                              *Tracing         `json:\"tracing\"`\n\n\t\tAdditionalEnv   []corev1.EnvVar `json:\"additionalEnv\"`\n\t\tExperimentalEnv []corev1.EnvVar `json:\"experimentalEnv\"`\n\n\t\tInbound  ProxyParams `json:\"inbound,omitempty\"`\n\t\tOutbound ProxyParams `json:\"outbound,omitempty\"`\n\n\t\t// Deprecated: Use Runtime.Workers.Minimum.\n\t\tCores int64 `json:\"cores,omitempty\"`\n\n\t\tRuntime         ProxyRuntime           `json:\"runtime,omitempty\"`\n\t\tSecurityContext map[string]interface{} `json:\"securityContext\"`\n\t}\n\n\tProxyParams      = map[string]ProxyScopeParams\n\tProxyScopeParams = map[string]ProxyProtoParams\n\tProxyProtoParams = map[string]interface{}\n\n\tProxyControl struct {\n\t\tStreams *ProxyControlStreams `json:\"streams\"`\n\t}\n\n\tProxyControlStreams struct {\n\t\tInitialTimeout string `json:\"initialTimeout\"`\n\t\tIdleTimeout    string `json:\"idleTimeout\"`\n\t\tLifetime       string `json:\"lifetime\"`\n\t}\n\n\tProxyMetrics struct {\n\t\tHostnameLabels bool `json:\"hostnameLabels\"`\n\t}\n\n\tTracing struct {\n\t\tEnabled          bool              `json:\"enabled\"`\n\t\tProtocol         string            `json:\"protocol\"`\n\t\tLabels           map[string]string `json:\"labels\"`\n\t\tTraceServiceName string            `json:\"traceServiceName\"`\n\t\tCollector        *TracingCollector `json:\"collector\"`\n\t}\n\n\tTracingCollector struct {\n\t\tEndpoint     string                    `json:\"endpoint\"`\n\t\tMeshIdentity *TracingCollectorIdentity `json:\"meshIdentity\"`\n\t}\n\n\tTracingCollectorIdentity struct {\n\t\tServiceAccountName string `json:\"serviceAccountName\"`\n\t\tNamespace          string `json:\"namespace\"`\n\t}\n\n\tProxyRuntime struct {\n\t\tWorkers ProxyRuntimeWorkers `json:\"workers,omitempty\"`\n\t}\n\n\tProxyRuntimeWorkers struct {\n\t\tMaximum int64 `json:\"maximum,omitempty\"`\n\t\tMinimum int64 `json:\"minimum,omitempty\"`\n\n\t\tMaximumCPURatio float64 `json:\"maximumCPURatio,omitempty\"`\n\t}\n\n\t// ProxyInit contains the fields to set the proxy-init container\n\tProxyInit struct {\n\t\tCapabilities        *Capabilities    `json:\"capabilities\"`\n\t\tIgnoreInboundPorts  string           `json:\"ignoreInboundPorts\"`\n\t\tIgnoreOutboundPorts string           `json:\"ignoreOutboundPorts\"`\n\t\tKubeAPIServerPorts  string           `json:\"kubeAPIServerPorts\"`\n\t\tSkipSubnets         string           `json:\"skipSubnets\"`\n\t\tLogLevel            string           `json:\"logLevel\"`\n\t\tLogFormat           string           `json:\"logFormat\"`\n\t\tSAMountPath         *VolumeMountPath `json:\"saMountPath\"`\n\t\tXTMountPath         *VolumeMountPath `json:\"xtMountPath\"`\n\t\t/* DEPRECATED: should be removed after stable-2.16.0, left in for bc */\n\t\tResources            *Resources `json:\"resources\"`\n\t\tCloseWaitTimeoutSecs int64      `json:\"closeWaitTimeoutSecs\"`\n\t\tPrivileged           bool       `json:\"privileged\"`\n\t\tRunAsRoot            bool       `json:\"runAsRoot\"`\n\t\tRunAsUser            int64      `json:\"runAsUser\"`\n\t\tRunAsGroup           int64      `json:\"runAsGroup\"`\n\t\tIptablesMode         string     `json:\"iptablesMode\"`\n\t}\n\n\tNetworkValidator struct {\n\t\tLogLevel        string                 `json:\"logLevel\"`\n\t\tLogFormat       string                 `json:\"logFormat\"`\n\t\tConnectAddr     string                 `json:\"connectAddr\"`\n\t\tListenAddr      string                 `json:\"listenAddr\"`\n\t\tTimeout         string                 `json:\"timeout\"`\n\t\tSecurityContext map[string]interface{} `json:\"securityContext\"`\n\t}\n\n\t// DebugContainer contains the fields to set the debugging sidecar\n\tDebugContainer struct {\n\t\tImage *Image `json:\"image\"`\n\t}\n\n\t// PodMonitor contains the fields to configure the Prometheus Operator `PodMonitor`\n\tPodMonitor struct {\n\t\tEnabled        bool                  `json:\"enabled\"`\n\t\tScrapeInterval string                `json:\"scrapeInterval\"`\n\t\tScrapeTimeout  string                `json:\"scrapeTimeout\"`\n\t\tController     *PodMonitorController `json:\"controller\"`\n\t\tServiceMirror  *PodMonitorComponent  `json:\"serviceMirror\"`\n\t\tProxy          *PodMonitorComponent  `json:\"proxy\"`\n\t}\n\n\t// PodMonitorController contains the fields to configure the Prometheus Operator `PodMonitor` for the control-plane\n\tPodMonitorController struct {\n\t\tEnabled           bool   `json:\"enabled\"`\n\t\tNamespaceSelector string `json:\"namespaceSelector\"`\n\t}\n\n\t// PodMonitorComponent contains the fields to configure the Prometheus Operator `PodMonitor` for other components\n\tPodMonitorComponent struct {\n\t\tEnabled bool `json:\"enabled\"`\n\t}\n\n\t// PolicyController contains the fields to configure the policy controller container\n\tPolicyController struct {\n\t\tResources     *Resources `json:\"resources\"`\n\t\tLogLevel      string     `json:\"logLevel\"`\n\t\tProbeNetworks []string   `json:\"probeNetworks\"`\n\t}\n\n\t// Image contains the details to define a container image\n\tImage struct {\n\t\tName       string `json:\"name\"`\n\t\tPullPolicy string `json:\"pullPolicy\"`\n\t\tVersion    string `json:\"version\"`\n\t}\n\n\t// Ports contains all the port-related setups\n\tPorts struct {\n\t\tAdmin    int32 `json:\"admin\"`\n\t\tControl  int32 `json:\"control\"`\n\t\tInbound  int32 `json:\"inbound\"`\n\t\tOutbound int32 `json:\"outbound\"`\n\t}\n\n\tProbe struct {\n\t\tInitialDelaySeconds uint `json:\"initialDelaySeconds\"`\n\t\tTimeoutSeconds      uint `json:\"timeoutSeconds\"`\n\t}\n\n\t// Constraints wraps the Limit and Request settings for computational resources\n\tConstraints struct {\n\t\tLimit   string `json:\"limit\"`\n\t\tRequest string `json:\"request\"`\n\t}\n\n\t// Capabilities contains the SecurityContext capabilities to add/drop into the injected\n\t// containers\n\tCapabilities struct {\n\t\tAdd  []string `json:\"add\"`\n\t\tDrop []string `json:\"drop\"`\n\t}\n\n\t// VolumeMountPath contains the details for volume mounts\n\tVolumeMountPath struct {\n\t\tName      string `json:\"name\"`\n\t\tMountPath string `json:\"mountPath\"`\n\t\tReadOnly  bool   `json:\"readOnly\"`\n\t}\n\n\t// Resources represents the computational resources setup for a given container\n\tResources struct {\n\t\tCPU              Constraints `json:\"cpu\"`\n\t\tMemory           Constraints `json:\"memory\"`\n\t\tEphemeralStorage Constraints `json:\"ephemeral-storage\"`\n\t}\n\n\t// StartupProbe represents the initContainer startup probe parameters for the proxy\n\tStartupProbe struct {\n\t\tInitialDelaySeconds uint `json:\"initialDelaySeconds\"`\n\t\tPeriodSeconds       uint `json:\"periodSeconds\"`\n\t\tFailureThreshold    uint `json:\"failureThreshold\"`\n\t}\n\n\t// Identity contains the fields to set the identity variables in the proxy\n\t// sidecar container\n\tIdentity struct {\n\t\tExternalCA                    bool              `json:\"externalCA\"`\n\t\tServiceAccountTokenProjection bool              `json:\"serviceAccountTokenProjection\"`\n\t\tIssuer                        *Issuer           `json:\"issuer\"`\n\t\tKubeAPI                       *KubeAPI          `json:\"kubeAPI\"`\n\t\tPodAnnotations                map[string]string `json:\"podAnnotations\"`\n\n\t\tAdditionalEnv   []corev1.EnvVar `json:\"additionalEnv\"`\n\t\tExperimentalEnv []corev1.EnvVar `json:\"experimentalEnv\"`\n\t}\n\n\t// Issuer has the Helm variables of the identity issuer\n\tIssuer struct {\n\t\tScheme             string     `json:\"scheme\"`\n\t\tClockSkewAllowance string     `json:\"clockSkewAllowance\"`\n\t\tIssuanceLifetime   string     `json:\"issuanceLifetime\"`\n\t\tTLS                *IssuerTLS `json:\"tls\"`\n\t}\n\n\t// KubeAPI contains the kube-apiserver client config\n\tKubeAPI struct {\n\t\tClientQPS   float32 `json:\"clientQPS\"`\n\t\tClientBurst int     `json:\"clientBurst\"`\n\t}\n\n\t// ProxyInjector configures the proxy-injector webhook\n\tProxyInjector struct {\n\t\tWebhook\n\t\tPodAnnotations  map[string]string `json:\"podAnnotations\"`\n\t\tAdditionalEnv   []corev1.EnvVar   `json:\"additionalEnv\"`\n\t\tExperimentalEnv []corev1.EnvVar   `json:\"experimentalEnv\"`\n\t}\n\n\t// Webhook Helm variables for a webhook\n\tWebhook struct {\n\t\t*TLS\n\t\tNamespaceSelector *metav1.LabelSelector `json:\"namespaceSelector\"`\n\t}\n\n\t// TLS has a pair of PEM-encoded key and certificate variables used in the\n\t// Helm templates\n\tTLS struct {\n\t\tExternalSecret     bool   `json:\"externalSecret\"`\n\t\tKeyPEM             string `json:\"keyPEM\"`\n\t\tCrtPEM             string `json:\"crtPEM\"`\n\t\tCaBundle           string `json:\"caBundle\"`\n\t\tInjectCaFrom       string `json:\"injectCaFrom\"`\n\t\tInjectCaFromSecret string `json:\"injectCaFromSecret\"`\n\t}\n\n\t// IssuerTLS is a stripped down version of TLS that lacks the integral caBundle.\n\t// It is tracked separately in the field 'IdentityTrustAnchorsPEM'\n\tIssuerTLS struct {\n\t\tKeyPEM string `json:\"keyPEM\"`\n\t\tCrtPEM string `json:\"crtPEM\"`\n\t}\n)\n\n// NewValues returns a new instance of the Values type.\nfunc NewValues() (*Values, error) {\n\tv, err := readDefaults(HelmChartDirCrds + \"/values.yaml\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvCP, err := readDefaults(HelmChartDirCP + \"/values.yaml\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t*v, err = v.Merge(*vCP)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tv.DebugContainer.Image.Version = version.Version\n\tv.CliVersion = k8s.CreatedByAnnotationValue()\n\tv.ProfileValidator.TLS = &TLS{}\n\tv.ProxyInjector.TLS = &TLS{}\n\tv.ProxyContainerName = k8s.ProxyContainerName\n\n\treturn v, nil\n}\n\n// ValuesFromConfigMap converts the data in linkerd-config into\n// a Values struct\nfunc ValuesFromConfigMap(cm *corev1.ConfigMap) (*Values, error) {\n\traw, ok := cm.Data[\"values\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"Linkerd values not found in ConfigMap\")\n\t}\n\tv := &Values{}\n\terr := yaml.Unmarshal([]byte(raw), &v)\n\treturn v, err\n}\n\n// MergeHAValues retrieves the default HA values and merges them into the received values\nfunc MergeHAValues(values *Values) error {\n\thaValues, err := readDefaults(HelmChartDirCP + \"/values-ha.yaml\")\n\tif err != nil {\n\t\treturn err\n\t}\n\t*values, err = values.Merge(*haValues)\n\treturn err\n}\n\n// readDefaults read all the default variables from filename.\nfunc readDefaults(filename string) (*Values, error) {\n\tvaluesFile := &loader.BufferedFile{Name: filename}\n\tif err := chartspkg.ReadFile(charts.Templates, \"\", valuesFile); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar values Values\n\terr := yaml.Unmarshal(chartspkg.InsertVersion(valuesFile.Data), &values)\n\n\treturn &values, err\n}\n\n// Merge merges the non-empty properties of src into v.\n// A new Values instance is returned. Neither src nor v are mutated after\n// calling Merge.\nfunc (v Values) Merge(src Values) (Values, error) {\n\t// By default, mergo.Merge doesn't overwrite any existing non-empty values\n\t// in its first argument. So in HA mode, we are merging values.yaml into\n\t// values-ha.yaml, instead of the other way round (like Helm). This ensures\n\t// that all the HA values take precedence.\n\tif err := mergo.Merge(&src, v); err != nil {\n\t\treturn Values{}, err\n\t}\n\n\treturn src, nil\n}\n\n// ToMap converts the Values intro a map[string]interface{}\nfunc (v *Values) ToMap() (map[string]interface{}, error) {\n\tvar valuesMap map[string]interface{}\n\trawValues, err := yaml.Marshal(v)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Failed to marshal the values struct: %w\", err)\n\t}\n\n\terr = yaml.Unmarshal(rawValues, &valuesMap)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Failed to Unmarshal Values into a map: %w\", err)\n\t}\n\n\treturn valuesMap, nil\n}\n\n// DeepCopy creates a deep copy of the Values struct by marshalling to yaml and\n// then unmarshalling a new struct.\nfunc (v *Values) DeepCopy() (*Values, error) {\n\tdst := Values{}\n\tbytes, err := yaml.Marshal(v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = yaml.Unmarshal(bytes, &dst)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &dst, nil\n}\n\nfunc (v *Values) String() string {\n\tbytes, _ := yaml.Marshal(v)\n\treturn string(bytes)\n}\n"
  },
  {
    "path": "pkg/charts/linkerd2/values_test.go",
    "content": "package linkerd2\n\nimport (\n\t\"testing\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n)\n\nfunc TestNewValues(t *testing.T) {\n\tactual, err := NewValues()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t}\n\n\tmatchExpressionsSimple := []metav1.LabelSelectorRequirement{\n\t\t{\n\t\t\tKey:      \"config.linkerd.io/admission-webhooks\",\n\t\t\tOperator: \"NotIn\",\n\t\t\tValues:   []string{\"disabled\"},\n\t\t},\n\t}\n\tmatchExpressionsInjector := append(matchExpressionsSimple, metav1.LabelSelectorRequirement{\n\t\tKey:      \"kubernetes.io/metadata.name\",\n\t\tOperator: \"NotIn\",\n\t\tValues:   []string{\"kube-system\", \"cert-manager\"},\n\t},\n\t)\n\n\tnamespaceSelectorSimple := &metav1.LabelSelector{MatchExpressions: matchExpressionsSimple}\n\tnamespaceSelectorInjector := &metav1.LabelSelector{MatchExpressions: matchExpressionsInjector}\n\n\tdefaultDeploymentStrategy := map[string]interface{}{\n\t\t\"rollingUpdate\": map[string]interface{}{\n\t\t\t\"maxUnavailable\": \"25%\",\n\t\t\t\"maxSurge\":       \"25%\",\n\t\t},\n\t}\n\tdefaultController := Controller{\n\t\tPodDisruptionBudget: &PodDisruptionBudget{\n\t\t\tMaxUnavailable: \"1\",\n\t\t},\n\t\tTracing: &Tracing{\n\t\t\tCollector: &TracingCollector{\n\t\t\t\tEndpoint: \"\",\n\t\t\t},\n\t\t},\n\t}\n\texpected := &Values{\n\t\tControllerImage:           \"cr.l5d.io/linkerd/controller\",\n\t\tControllerReplicas:        1,\n\t\tRevisionHistoryLimit:      10,\n\t\tControllerUID:             2103,\n\t\tControllerGID:             -1,\n\t\tEnableH2Upgrade:           true,\n\t\tEnablePodAntiAffinity:     false,\n\t\tWebhookFailurePolicy:      \"Ignore\",\n\t\tDisableHeartBeat:          false,\n\t\tDeploymentStrategy:        defaultDeploymentStrategy,\n\t\tHeartbeatSchedule:         \"\",\n\t\tClusterDomain:             \"cluster.local\",\n\t\tClusterNetworks:           \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16,fd00::/8\",\n\t\tImagePullPolicy:           \"IfNotPresent\",\n\t\tCliVersion:                \"linkerd/cli dev-undefined\",\n\t\tControllerLogLevel:        \"info\",\n\t\tControllerLogFormat:       \"plain\",\n\t\tLinkerdVersion:            version.Version,\n\t\tProxyContainerName:        \"linkerd-proxy\",\n\t\tCNIEnabled:                false,\n\t\tHighAvailability:          false,\n\t\tPodAnnotations:            map[string]string{},\n\t\tPodLabels:                 map[string]string{},\n\t\tEnableEndpointSlices:      true,\n\t\tDisableIPv6:               true,\n\t\tEnablePodDisruptionBudget: false,\n\t\tController:                &defaultController,\n\t\tPodMonitor: &PodMonitor{\n\t\t\tEnabled:        false,\n\t\t\tScrapeInterval: \"10s\",\n\t\t\tScrapeTimeout:  \"10s\",\n\t\t\tController: &PodMonitorController{\n\t\t\t\tEnabled: true,\n\t\t\t\tNamespaceSelector: `matchNames:\n  - {{ .Release.Namespace }}\n  - linkerd-viz\n`,\n\t\t\t},\n\t\t\tServiceMirror: &PodMonitorComponent{Enabled: true},\n\t\t\tProxy:         &PodMonitorComponent{Enabled: true},\n\t\t},\n\t\tDestinationController: &DestinationController{\n\t\t\tMeshedHttp2ClientProtobuf: map[string]interface{}{\n\t\t\t\t\"keep_alive\": map[string]interface{}{\n\t\t\t\t\t\"interval\":   map[string]interface{}{\"seconds\": 10.0},\n\t\t\t\t\t\"timeout\":    map[string]interface{}{\"seconds\": 3.0},\n\t\t\t\t\t\"while_idle\": true,\n\t\t\t\t},\n\t\t\t},\n\t\t\tPodAnnotations: map[string]string{},\n\t\t},\n\t\tSPValidator: map[string]interface{}{\n\t\t\t\"livenessProbe\":  map[string]interface{}{\"timeoutSeconds\": 1.0},\n\t\t\t\"readinessProbe\": map[string]interface{}{\"timeoutSeconds\": 1.0},\n\t\t},\n\t\tPolicyController: &PolicyController{\n\t\t\tLogLevel: \"info\",\n\t\t\tResources: &Resources{\n\t\t\t\tCPU: Constraints{\n\t\t\t\t\tLimit:   \"\",\n\t\t\t\t\tRequest: \"\",\n\t\t\t\t},\n\t\t\t\tMemory: Constraints{\n\t\t\t\t\tLimit:   \"\",\n\t\t\t\t\tRequest: \"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tProbeNetworks: []string{\"0.0.0.0/0\", \"::/0\"},\n\t\t},\n\t\tProxy: &Proxy{\n\t\t\tEnableExternalProfiles: false,\n\t\t\tImage: &Image{\n\t\t\t\tName:    \"cr.l5d.io/linkerd/proxy\",\n\t\t\t\tVersion: \"\",\n\t\t\t},\n\t\t\tLogLevel:       \"warn,linkerd=info,hickory=error\",\n\t\t\tLogFormat:      \"plain\",\n\t\t\tLogHTTPHeaders: \"off\",\n\t\t\tPorts: &Ports{\n\t\t\t\tAdmin:    4191,\n\t\t\t\tControl:  4190,\n\t\t\t\tInbound:  4143,\n\t\t\t\tOutbound: 4140,\n\t\t\t},\n\t\t\tResources: &Resources{\n\t\t\t\tCPU: Constraints{\n\t\t\t\t\tLimit:   \"\",\n\t\t\t\t\tRequest: \"\",\n\t\t\t\t},\n\t\t\t\tMemory: Constraints{\n\t\t\t\t\tLimit:   \"\",\n\t\t\t\t\tRequest: \"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tUID:                                  2102,\n\t\t\tGID:                                  -1,\n\t\t\tSecurityContext:                      map[string]interface{}{},\n\t\t\tWaitBeforeExitSeconds:                0,\n\t\t\tOutboundConnectTimeout:               \"1000ms\",\n\t\t\tInboundConnectTimeout:                \"100ms\",\n\t\t\tOpaquePorts:                          \"25,587,3306,4444,5432,6379,9300,11211\",\n\t\t\tAwait:                                true,\n\t\t\tDefaultInboundPolicy:                 \"all-unauthenticated\",\n\t\t\tOutboundTransportMode:                \"transport-header\",\n\t\t\tOutboundDiscoveryCacheUnusedTimeout:  \"5s\",\n\t\t\tInboundDiscoveryCacheUnusedTimeout:   \"90s\",\n\t\t\tDisableOutboundProtocolDetectTimeout: false,\n\t\t\tDisableInboundProtocolDetectTimeout:  false,\n\t\t\tStartupProbe: &StartupProbe{\n\t\t\t\tFailureThreshold:    120,\n\t\t\t\tInitialDelaySeconds: 0,\n\t\t\t\tPeriodSeconds:       1,\n\t\t\t},\n\t\t\tReadinessProbe: &Probe{\n\t\t\t\tInitialDelaySeconds: 2,\n\t\t\t\tTimeoutSeconds:      1,\n\t\t\t},\n\t\t\tLivenessProbe: &Probe{\n\t\t\t\tInitialDelaySeconds: 10,\n\t\t\t\tTimeoutSeconds:      1,\n\t\t\t},\n\t\t\tControl: &ProxyControl{\n\t\t\t\tStreams: &ProxyControlStreams{\n\t\t\t\t\tInitialTimeout: \"3s\",\n\t\t\t\t\tIdleTimeout:    \"5m\",\n\t\t\t\t\tLifetime:       \"1h\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tMetrics: &ProxyMetrics{\n\t\t\t\tHostnameLabels: false,\n\t\t\t},\n\t\t\tTracing: &Tracing{\n\t\t\t\tEnabled: false,\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"k8s.pod.ip\":         \"$(_pod_ip)\",\n\t\t\t\t\t\"k8s.pod.uid\":        \"$(_pod_uid)\",\n\t\t\t\t\t\"k8s.container.name\": \"$(_pod_containerName)\",\n\t\t\t\t},\n\t\t\t\tTraceServiceName: \"linkerd-proxy\",\n\t\t\t\tCollector: &TracingCollector{\n\t\t\t\t\tEndpoint: \"\",\n\t\t\t\t\tMeshIdentity: &TracingCollectorIdentity{\n\t\t\t\t\t\tServiceAccountName: \"\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tRuntime: ProxyRuntime{\n\t\t\t\tWorkers: ProxyRuntimeWorkers{\n\t\t\t\t\tMinimum: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t\tInbound: ProxyParams{\n\t\t\t\t\"server\": ProxyScopeParams{\n\t\t\t\t\t\"http2\": ProxyProtoParams{\n\t\t\t\t\t\t\"keepAliveInterval\": \"10s\",\n\t\t\t\t\t\t\"keepAliveTimeout\":  \"3s\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tOutbound: ProxyParams{\n\t\t\t\t\"server\": ProxyScopeParams{\n\t\t\t\t\t\"http2\": ProxyProtoParams{\n\t\t\t\t\t\t\"keepAliveInterval\": \"10s\",\n\t\t\t\t\t\t\"keepAliveTimeout\":  \"3s\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tProxyInit: &ProxyInit{\n\t\t\tIptablesMode:        \"nft\",\n\t\t\tIgnoreInboundPorts:  \"4567,4568\",\n\t\t\tIgnoreOutboundPorts: \"4567,4568\",\n\t\t\tKubeAPIServerPorts:  \"443,6443\",\n\t\t\tLogLevel:            \"\",\n\t\t\tLogFormat:           \"\",\n\t\t\tXTMountPath: &VolumeMountPath{\n\t\t\t\tName:      \"linkerd-proxy-init-xtables-lock\",\n\t\t\t\tMountPath: \"/run\",\n\t\t\t},\n\t\t\tRunAsRoot:  false,\n\t\t\tRunAsUser:  65534,\n\t\t\tRunAsGroup: 65534,\n\t\t},\n\t\tNetworkValidator: &NetworkValidator{\n\t\t\tLogLevel:    \"debug\",\n\t\t\tLogFormat:   \"plain\",\n\t\t\tConnectAddr: \"\",\n\t\t\tListenAddr:  \"\",\n\t\t\tTimeout:     \"10s\",\n\t\t\tSecurityContext: map[string]interface{}{\n\t\t\t\t\"allowPrivilegeEscalation\": false,\n\t\t\t\t\"capabilities\": map[string]interface{}{\n\t\t\t\t\t\"drop\": []interface{}{\"ALL\"},\n\t\t\t\t},\n\t\t\t\t\"readOnlyRootFilesystem\": true,\n\t\t\t\t\"runAsGroup\":             float64(65534),\n\t\t\t\t\"runAsNonRoot\":           true,\n\t\t\t\t\"runAsUser\":              float64(65534),\n\t\t\t\t\"seccompProfile\": map[string]interface{}{\n\t\t\t\t\t\"type\": \"RuntimeDefault\"},\n\t\t\t},\n\t\t},\n\t\tIdentity: &Identity{\n\t\t\tServiceAccountTokenProjection: true,\n\t\t\tIssuer: &Issuer{\n\t\t\t\tClockSkewAllowance: \"20s\",\n\t\t\t\tIssuanceLifetime:   \"24h0m0s\",\n\t\t\t\tTLS:                &IssuerTLS{},\n\t\t\t\tScheme:             \"linkerd.io/tls\",\n\t\t\t},\n\t\t\tKubeAPI: &KubeAPI{\n\t\t\t\tClientQPS:   100,\n\t\t\t\tClientBurst: 200,\n\t\t\t},\n\t\t\tPodAnnotations: map[string]string{},\n\t\t},\n\t\tNodeSelector: map[string]string{\n\t\t\t\"kubernetes.io/os\": \"linux\",\n\t\t},\n\t\tDebugContainer: &DebugContainer{\n\t\t\tImage: &Image{\n\t\t\t\tName:    \"cr.l5d.io/linkerd/debug\",\n\t\t\t\tVersion: \"dev-undefined\",\n\t\t\t},\n\t\t},\n\n\t\tProxyInjector: &ProxyInjector{\n\t\t\tWebhook:        Webhook{TLS: &TLS{}, NamespaceSelector: namespaceSelectorInjector},\n\t\t\tPodAnnotations: map[string]string{},\n\t\t},\n\t\tProfileValidator: &Webhook{TLS: &TLS{}, NamespaceSelector: namespaceSelectorSimple},\n\t\tPolicyValidator:  &Webhook{TLS: &TLS{}, NamespaceSelector: namespaceSelectorSimple},\n\t\tEgress:           &Egress{GlobalEgressNetworkNamespace: \"linkerd-egress\"},\n\t}\n\n\t// Make Add-On Values nil to not have to check for their defaults\n\tactual.ImagePullSecrets = nil\n\n\tif diff := deep.Equal(expected, actual); diff != nil {\n\t\tt.Errorf(\"Helm values\\n%+v\", diff)\n\t}\n\n\tt.Run(\"HA\", func(t *testing.T) {\n\n\t\terr := MergeHAValues(actual)\n\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\\n\", err)\n\t\t}\n\n\t\thaDeploymentStrategy := map[string]interface{}{\n\t\t\t\"rollingUpdate\": map[string]interface{}{\n\t\t\t\t\"maxUnavailable\": 1.0,\n\t\t\t\t\"maxSurge\":       \"25%\",\n\t\t\t},\n\t\t}\n\n\t\texpected.HighAvailability = true\n\t\texpected.ControllerReplicas = 3\n\t\texpected.EnablePodAntiAffinity = true\n\t\texpected.EnablePodDisruptionBudget = true\n\t\texpected.Controller = &defaultController\n\t\texpected.DeploymentStrategy = haDeploymentStrategy\n\t\texpected.WebhookFailurePolicy = \"Fail\"\n\n\t\tcontrollerResources := &Resources{\n\t\t\tCPU: Constraints{\n\t\t\t\tRequest: \"100m\",\n\t\t\t},\n\t\t\tMemory: Constraints{\n\t\t\t\tLimit:   \"250Mi\",\n\t\t\t\tRequest: \"50Mi\",\n\t\t\t},\n\t\t}\n\t\texpected.DestinationResources = controllerResources\n\t\texpected.ProxyInjectorResources = controllerResources\n\t\texpected.HeartbeatResources = controllerResources\n\n\t\texpected.IdentityResources = &Resources{\n\t\t\tCPU: Constraints{\n\t\t\t\tLimit:   controllerResources.CPU.Limit,\n\t\t\t\tRequest: controllerResources.CPU.Request,\n\t\t\t},\n\t\t\tMemory: Constraints{\n\t\t\t\tLimit:   controllerResources.Memory.Limit,\n\t\t\t\tRequest: \"10Mi\",\n\t\t\t},\n\t\t}\n\n\t\texpected.Proxy.Resources = &Resources{\n\t\t\tCPU: Constraints{\n\t\t\t\tLimit:   \"\",\n\t\t\t\tRequest: controllerResources.CPU.Request,\n\t\t\t},\n\t\t\tMemory: Constraints{\n\t\t\t\tLimit:   controllerResources.Memory.Limit,\n\t\t\t\tRequest: \"20Mi\",\n\t\t\t},\n\t\t}\n\n\t\tif diff := deep.Equal(expected, actual); diff != nil {\n\t\t\tt.Errorf(\"HA Helm values\\n%+v\", diff)\n\t\t}\n\t})\n}\n\n// TestHAValuesParsing tests whether values commonly used in HA deployments have\n// appropriate types and can be successfully parsed.\nfunc TestHAValuesParsing(t *testing.T) {\n\tyml := `\nenablePodDisruptionBudget: true\nPodDisruptionBudget:\n  maxUnavailable: 1\ndeploymentStrategy:\n  rollingUpdate:\n    maxUnavailable: 1\n    maxSurge: 25%\nenablePodAntiAffinity: true\nnodeAffinity:\n  requiredDuringSchedulingIgnoredDuringExecution:\n    nodeSelectorTerms:\n    - matchExpressions:\n      - key: cloud.google.com/gke-preemptible\n        operator: DoesNotExist\nnodeSelector:\n  kubernetes.io/os: linux\nproxy:\n  resources:\n    cpu:\n      request: 100m\n    memory:\n      limit: 250Mi\n      request: 20Mi`\n\n\terr := yaml.Unmarshal([]byte(yml), &Values{})\n\tif err != nil {\n\t\tt.Errorf(\"Failed to unamarshal HA values from yaml: %v\\nValues: %v\", err, yml)\n\t}\n}\n\n// TestHAValuesParsing tests whether a percentage value for PDB\n// can be parsed\nfunc TestControlerValueParsing(t *testing.T) {\n\tyml := `\ncontroller:\n  podDisruptionBudget:\n    maxUnavailable: 25%`\n\n\terr := yaml.Unmarshal([]byte(yml), &Values{})\n\tif err != nil {\n\t\tt.Errorf(\"Failed to unamarshal maxUnavailable as percentage from yaml: %v\\nValues: %v\", err, yml)\n\t}\n}\n"
  },
  {
    "path": "pkg/cmd/cmd.go",
    "content": "package cmd\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s/resource\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/selection\"\n\tyamlDecoder \"k8s.io/apimachinery/pkg/util/yaml\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nvar (\n\t// DefaultDockerRegistry specifies the default location for Linkerd's images.\n\tDefaultDockerRegistry = \"cr.l5d.io/linkerd\"\n)\n\nconst (\n\tJsonOutput = \"json\"\n\tYamlOutput = \"yaml\"\n)\n\n// GetDefaultNamespace fetches the default namespace\n// used in the current KubeConfig context\nfunc GetDefaultNamespace(kubeconfigPath, kubeContext string) string {\n\trules := clientcmd.NewDefaultClientConfigLoadingRules()\n\n\tif kubeconfigPath != \"\" {\n\t\trules.ExplicitPath = kubeconfigPath\n\t}\n\n\toverrides := &clientcmd.ConfigOverrides{CurrentContext: kubeContext}\n\tkubeCfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)\n\tns, _, err := kubeCfg.Namespace()\n\n\tif err != nil {\n\t\tlog.Warnf(`could not set namespace from kubectl context, using 'default' namespace: %s\n\t\t ensure the KUBECONFIG path %s is valid`, err, kubeconfigPath)\n\t\treturn corev1.NamespaceDefault\n\t}\n\n\treturn ns\n}\n\n// Uninstall prints all cluster-scoped resources matching the given selector\n// for the purposes of deleting them.\nfunc Uninstall(ctx context.Context, k8sAPI *k8s.KubernetesAPI, selector string, format string) error {\n\tresources, err := resource.FetchKubernetesResources(ctx, k8sAPI,\n\t\tmetav1.ListOptions{LabelSelector: selector},\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(resources) == 0 {\n\t\treturn errors.New(\"No resources found to uninstall\")\n\t}\n\tfor _, r := range resources {\n\t\tif format == YamlOutput {\n\t\t\tif err := r.RenderResource(os.Stdout); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error rendering Kubernetes resource: %w\", err)\n\t\t\t}\n\t\t} else if format == JsonOutput {\n\t\t\tif err := r.RenderResourceJSON(os.Stdout); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error rendering Kubernetes resource: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"unsupported format %s\", format)\n\t\t}\n\t}\n\treturn nil\n}\n\n// Prune takes an install manifest and prints all resources on the cluster which\n// match the given label selector but are not in the given manifest. Users are\n// expected to pipe these resources to `kubectl delete` to clean up resources\n// left on the cluster which are no longer part of the install manifest.\nfunc Prune(ctx context.Context, k8sAPI *k8s.KubernetesAPI, expectedManifests string, selector string, format string) error {\n\texpectedResources := []resource.Kubernetes{}\n\treader := yamlDecoder.NewYAMLReader(bufio.NewReaderSize(strings.NewReader(expectedManifests), 4096))\n\tfor {\n\t\tmanifest, err := reader.Read()\n\t\tif err != nil {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\tresource := resource.Kubernetes{}\n\t\terr = yaml.Unmarshal(manifest, &resource)\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"error parsing manifest: %s\", manifest)\n\t\t\tos.Exit(1)\n\t\t}\n\t\texpectedResources = append(expectedResources, resource)\n\t}\n\n\tlistOptions := metav1.ListOptions{\n\t\tLabelSelector: selector,\n\t}\n\tresources, err := resource.FetchPrunableResources(ctx, k8sAPI, metav1.NamespaceAll, listOptions)\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error fetching resources: %s\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\tfor _, resource := range resources {\n\t\t// If the resource is not in the expected resource list, render it for\n\t\t// pruning.\n\t\tif !resourceListContains(expectedResources, resource) {\n\t\t\tif format == YamlOutput {\n\t\t\t\terr = resource.RenderResource(os.Stdout)\n\t\t\t} else if format == JsonOutput {\n\t\t\t\terr = resource.RenderResourceJSON(os.Stdout)\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"unsupported format %s\", format)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error rendering Kubernetes resource: %w\\n\", err)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc resourceListContains(list []resource.Kubernetes, a resource.Kubernetes) bool {\n\tfor _, r := range list {\n\t\tif resourceEquals(a, r) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc resourceEquals(a resource.Kubernetes, b resource.Kubernetes) bool {\n\treturn a.GroupVersionKind().GroupKind() == b.GroupVersionKind().GroupKind() &&\n\t\ta.GetName() == b.GetName() &&\n\t\ta.GetNamespace() == b.GetNamespace()\n}\n\n// ConfigureNamespaceFlagCompletion sets up resource-aware completion for command\n// flags that accept a namespace name\nfunc ConfigureNamespaceFlagCompletion(\n\tcmd *cobra.Command,\n\tflagNames []string,\n\tkubeconfigPath string,\n\timpersonate string,\n\timpersonateGroup []string,\n\tkubeContext string,\n) {\n\tfor _, flagName := range flagNames {\n\t\tcmd.RegisterFlagCompletionFunc(flagName,\n\t\t\tfunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t\t}\n\n\t\t\t\tcc := k8s.NewCommandCompletion(k8sAPI, \"\")\n\t\t\t\tresults, err := cc.Complete([]string{k8s.Namespace}, toComplete)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t\t}\n\n\t\t\t\treturn results, cobra.ShellCompDirectiveDefault\n\t\t\t})\n\t}\n}\n\n// ConfigureOutputFlagCompletion sets up resource-aware completion for command\n// flags that accept an output name.\nfunc ConfigureOutputFlagCompletion(cmd *cobra.Command) {\n\tcmd.RegisterFlagCompletionFunc(\"output\",\n\t\tfunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\t\treturn []string{\"basic\", \"json\", \"short\", \"table\"}, cobra.ShellCompDirectiveDefault\n\t\t})\n}\n\n// ConfigureKubeContextFlagCompletion sets up resource-aware completion for command\n// flags based off of a kubeconfig\nfunc ConfigureKubeContextFlagCompletion(cmd *cobra.Command, kubeconfigPath string) {\n\tcmd.RegisterFlagCompletionFunc(\"context\",\n\t\tfunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\t\trules := clientcmd.NewDefaultClientConfigLoadingRules()\n\t\t\trules.ExplicitPath = kubeconfigPath\n\t\t\tloader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{})\n\t\t\tconfig, err := loader.RawConfig()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tsuggestions := []string{}\n\t\t\tuniqContexts := map[string]struct{}{}\n\t\t\tfor ctxName := range config.Contexts {\n\t\t\t\tif strings.HasPrefix(ctxName, toComplete) {\n\t\t\t\t\tif _, ok := uniqContexts[ctxName]; !ok {\n\t\t\t\t\t\tsuggestions = append(suggestions, ctxName)\n\t\t\t\t\t\tuniqContexts[ctxName] = struct{}{}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn suggestions, cobra.ShellCompDirectiveDefault\n\t\t})\n}\n\n// GetLabelSelector creates a label selector as a string based on a label key\n// whose value may be in the set provided as an argument to the function. If the\n// value set is empty then the selector will match resources where the label key\n// exists regardless of value.\nfunc GetLabelSelector(labelKey string, labelValues ...string) (string, error) {\n\tselectionOp := selection.In\n\tif len(labelValues) < 1 {\n\t\tselectionOp = selection.Exists\n\t}\n\n\tlabelRequirement, err := labels.NewRequirement(labelKey, selectionOp, labelValues)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tselector := labels.NewSelector().Add(*labelRequirement)\n\treturn selector.String(), nil\n}\n\n// RegistryOverride replaces the registry-portion of the provided image with the provided registry.\nfunc RegistryOverride(image, newRegistry string) string {\n\tif image == \"\" {\n\t\treturn image\n\t}\n\tregistry := newRegistry\n\tif registry != \"\" && !strings.HasSuffix(registry, \"/\") {\n\t\tregistry += \"/\"\n\t}\n\timageName := image\n\tif strings.Contains(image, \"/\") {\n\t\timageName = image[strings.LastIndex(image, \"/\")+1:]\n\t}\n\treturn registry + imageName\n}\n\n// Given a buffer containing one or more YAML documents separated by `---`,\n// render each document to the writer in the specified format: json or yaml.\n// Json documents are separated by a newline character.\nfunc RenderYAMLAs(buf *bytes.Buffer, writer io.Writer, format string) error {\n\tif format == JsonOutput {\n\t\treader := yamlDecoder.NewYAMLReader(bufio.NewReaderSize(buf, 4096))\n\t\tfor {\n\t\t\tmanifest, err := reader.Read()\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tbytes, err := yaml.YAMLToJSON(manifest)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err = writer.Write(append(bytes, '\\n'))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif format == YamlOutput {\n\t\t_, err := writer.Write(buf.Bytes())\n\t\treturn err\n\t}\n\treturn fmt.Errorf(\"unsupported format %s\", format)\n}\n"
  },
  {
    "path": "pkg/config/config.go",
    "content": "package config\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\n\tl5dcharts \"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"sigs.k8s.io/yaml\"\n)\n\n// Values returns the Value struct from the linkerd-config ConfigMap\nfunc Values(path string) (*l5dcharts.Values, error) {\n\tp := filepath.Clean(path)\n\tconfigYaml, err := os.ReadFile(p)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read config file: %w\", err)\n\t}\n\tlog.Debugf(\"%s config YAML: %s\", p, configYaml)\n\tvalues := &l5dcharts.Values{}\n\tif err = yaml.Unmarshal(configYaml, values); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal JSON from: %s: %w\", p, err)\n\t}\n\treturn values, err\n}\n\n// RemoveGlobalFieldIfPresent removes the `global` node and\n// attaches the children nodes there.\nfunc RemoveGlobalFieldIfPresent(bytes []byte) ([]byte, error) {\n\t// Check if Globals is present and remove that node if it has\n\tvar valuesMap map[string]interface{}\n\terr := yaml.Unmarshal(bytes, &valuesMap)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif globalValues, ok := valuesMap[\"global\"]; ok {\n\t\t// attach those values\n\t\t// Check if its a map\n\t\tif val, ok := globalValues.(map[string]interface{}); ok {\n\t\t\tfor k, v := range val {\n\t\t\t\tvaluesMap[k] = v\n\t\t\t}\n\t\t}\n\t\t// Remove global now\n\t\tdelete(valuesMap, \"global\")\n\t}\n\n\tbytes, err = yaml.Marshal(valuesMap)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn bytes, nil\n}\n\n// FetchLinkerdConfigMap retrieves the `linkerd-config` ConfigMap from\n// Kubernetes.\nfunc FetchLinkerdConfigMap(ctx context.Context, k kubernetes.Interface, controlPlaneNamespace string) (*corev1.ConfigMap, error) {\n\tcm, err := k.CoreV1().ConfigMaps(controlPlaneNamespace).Get(ctx, k8s.ConfigConfigMapName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn cm, nil\n}\n"
  },
  {
    "path": "pkg/filesonly/filesonly.go",
    "content": "package filesonly\n\n/* An implementation of the http.FileSystem interface that disallows\n   listing the contents of directories. This approach is adapted from:\n\t https://groups.google.com/d/topic/golang-nuts/bStLPdIVM6w/discussion\n\n\t Source: https://github.com/BuoyantIO/util\n*/\n\nimport (\n\t\"net/http\"\n\t\"os\"\n)\n\n// FileSystem provides access to a collection of named files via\n// http.FileSystem, given a directory.\nfunc FileSystem(dir string) http.FileSystem {\n\treturn fileSystem{http.Dir(dir)}\n}\n\ntype fileSystem struct {\n\tfs http.FileSystem\n}\n\nfunc (fs fileSystem) Open(name string) (http.File, error) {\n\tf, err := fs.fs.Open(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn file{f}, nil\n}\n\ntype file struct {\n\thttp.File\n}\n\nfunc (f file) Readdir(count int) ([]os.FileInfo, error) {\n\treturn nil, nil\n}\n"
  },
  {
    "path": "pkg/flags/flags.go",
    "content": "package flags\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\n\t\"github.com/bombsimon/logrusr/v4\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/pflag\"\n\t\"helm.sh/helm/v3/pkg/cli/values\"\n\tklog \"k8s.io/klog/v2\"\n)\n\nconst (\n\n\t// EnvOverrideNamespace is the environment variable used in the CLI to\n\t// overidde the control-plane's namespace\n\tEnvOverrideNamespace = \"LINKERD_NAMESPACE\"\n\n\t// EnvOverrideDockerRegistry is the environment variable used in the\n\t// CLI to override the docker images' registry in the control-plane\n\t// manifests\n\tEnvOverrideDockerRegistry = \"LINKERD_DOCKER_REGISTRY\"\n)\n\n// ConfigureAndParse adds flags that are common to all go processes. This\n// func calls flag.Parse(), so it should be called after all other flags have\n// been configured.\nfunc ConfigureAndParse(cmd *flag.FlagSet, args []string) {\n\tlogLevel := cmd.String(\"log-level\", log.InfoLevel.String(),\n\t\t\"log level, must be one of: panic, fatal, error, warn, info, debug, trace\")\n\tlogFormat := cmd.String(\"log-format\", \"plain\",\n\t\t\"log format, must be one of: plain, json\")\n\tprintVersion := cmd.Bool(\"version\", false, \"print version and exit\")\n\n\t// We'll assume the args being passed in by calling functions have already\n\t// been validated and that parsing does not have errors.\n\t//nolint:errcheck\n\tcmd.Parse(args)\n\n\tlog.SetFormatter(getFormatter(*logFormat))\n\n\tklog.InitFlags(nil)\n\tklog.SetLogger(logrusr.New(log.StandardLogger()))\n\n\tsetLogLevel(*logLevel)\n\tmaybePrintVersionAndExit(*printVersion)\n}\n\n// AddTraceFlags adds the trace-collector flag\n// to the flagSet and returns their pointers for usage\nfunc AddTraceFlags(cmd *flag.FlagSet) *string {\n\ttraceCollector := cmd.String(\"trace-collector\", \"\", \"Enables OC Tracing with the specified endpoint as collector\")\n\n\treturn traceCollector\n}\n\nfunc setLogLevel(logLevel string) {\n\tlevel, err := log.ParseLevel(logLevel)\n\tif err != nil {\n\t\tlog.Fatalf(\"invalid log-level: %s\", logLevel)\n\t}\n\tlog.SetLevel(level)\n\n\t// Loosely based on k8s logging conventions, except for 'tracing' that we\n\t// bump to 10 (we can see in client-go source code that level is actually\n\t// used) and `debug` to 6 (given that at level 7 and higher auth tokens get\n\t// logged)\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md\n\tswitch level {\n\tcase log.PanicLevel:\n\t\tflag.Set(\"v\", \"0\")\n\tcase log.FatalLevel:\n\t\tflag.Set(\"v\", \"0\")\n\tcase log.ErrorLevel:\n\t\tflag.Set(\"v\", \"0\")\n\tcase log.WarnLevel:\n\t\tflag.Set(\"v\", \"0\")\n\tcase log.InfoLevel:\n\t\tflag.Set(\"v\", \"2\")\n\tcase log.DebugLevel:\n\t\tflag.Set(\"v\", \"6\")\n\tcase log.TraceLevel:\n\t\tflag.Set(\"v\", \"10\")\n\t}\n}\n\nfunc maybePrintVersionAndExit(printVersion bool) {\n\tif printVersion {\n\t\tfmt.Println(version.Version)\n\t\tos.Exit(0)\n\t}\n\tlog.Infof(\"running version %s\", version.Version)\n}\n\nfunc getFormatter(format string) log.Formatter {\n\tswitch format {\n\tcase \"json\":\n\t\treturn &log.JSONFormatter{}\n\tdefault:\n\t\treturn &log.TextFormatter{FullTimestamp: true}\n\t}\n}\n\n// AddValueOptionsFlags adds flags used to override default values\nfunc AddValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {\n\tf.StringSliceVarP(&v.ValueFiles, \"values\", \"f\", []string{}, \"specify values in a YAML file or a URL (can specify multiple)\")\n\tf.StringArrayVar(&v.Values, \"set\", []string{}, \"set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)\")\n\tf.StringArrayVar(&v.StringValues, \"set-string\", []string{}, \"set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)\")\n\tf.StringArrayVar(&v.FileValues, \"set-file\", []string{}, \"set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)\")\n}\n"
  },
  {
    "path": "pkg/healthcheck/healthcheck.go",
    "content": "package healthcheck\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"crypto/x509\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tcontrollerK8s \"github.com/linkerd/linkerd2/controller/k8s\"\n\tl5dcharts \"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/linkerd/linkerd2/pkg/config\"\n\t\"github.com/linkerd/linkerd2/pkg/identity\"\n\t\"github.com/linkerd/linkerd2/pkg/issuercerts\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n\t\"github.com/linkerd/linkerd2/pkg/util\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\tlog \"github.com/sirupsen/logrus\"\n\tadmissionRegistration \"k8s.io/api/admissionregistration/v1\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapiextv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tyamlDecoder \"k8s.io/apimachinery/pkg/util/yaml\"\n\tk8sVersion \"k8s.io/apimachinery/pkg/version\"\n\t\"k8s.io/client-go/kubernetes\"\n\tapiregistrationv1client \"k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1\"\n\t\"sigs.k8s.io/yaml\"\n)\n\n// CategoryID is an identifier for the types of health checks.\ntype CategoryID string\n\nconst (\n\t// KubernetesAPIChecks adds a series of checks to validate that the caller is\n\t// configured to interact with a working Kubernetes cluster.\n\tKubernetesAPIChecks CategoryID = \"kubernetes-api\"\n\n\t// KubernetesVersionChecks validate that the cluster meets the minimum version\n\t// requirements.\n\tKubernetesVersionChecks CategoryID = \"kubernetes-version\"\n\n\t// LinkerdPreInstall* checks enabled by `linkerd check --pre`\n\n\t// LinkerdPreInstallChecks adds checks to validate that the control plane\n\t// namespace does not already exist, and that the user can create cluster-wide\n\t// resources, including ClusterRole, ClusterRoleBinding, and\n\t// CustomResourceDefinition, as well as namespace-wide resources, including\n\t// Service, Deployment, and ConfigMap. This check only runs as part of the set\n\t// of pre-install checks.\n\t// This check is dependent on the output of KubernetesAPIChecks, so those\n\t// checks must be added first.\n\tLinkerdPreInstallChecks CategoryID = \"pre-kubernetes-setup\"\n\n\t// LinkerdCRDChecks adds checks to validate that the control plane CRDs\n\t// exist. These checks can be run after installing the control plane CRDs\n\t// but before installing the control plane itself.\n\tLinkerdCRDChecks CategoryID = \"linkerd-crd\"\n\n\t// LinkerdConfigChecks enabled by `linkerd check config`\n\n\t// LinkerdConfigChecks adds a series of checks to validate that the Linkerd\n\t// namespace, RBAC, ServiceAccounts, and CRDs were successfully created.\n\t// These checks specifically validate that the `linkerd install config`\n\t// command succeeded in a multi-stage install, but also applies to a default\n\t// `linkerd install`.\n\t// These checks are dependent on the output of KubernetesAPIChecks, so those\n\t// checks must be added first.\n\tLinkerdConfigChecks CategoryID = \"linkerd-config\"\n\n\t// LinkerdIdentity Checks the integrity of the mTLS certificates\n\t// that the control plane is configured with\n\tLinkerdIdentity CategoryID = \"linkerd-identity\"\n\n\t// LinkerdWebhooksAndAPISvcTLS the integrity of the mTLS certificates\n\t// that of the for the injector and sp webhooks and the tap api svc\n\tLinkerdWebhooksAndAPISvcTLS CategoryID = \"linkerd-webhooks-and-apisvc-tls\"\n\n\t// LinkerdIdentityDataPlane checks that integrity of the mTLS\n\t// certificates that the proxies are configured with and tries to\n\t// report useful information with respect to whether the configuration\n\t// is compatible with the one of the control plane\n\tLinkerdIdentityDataPlane CategoryID = \"linkerd-identity-data-plane\"\n\n\t// LinkerdControlPlaneExistenceChecks adds a series of checks to validate that\n\t// the control plane namespace and controller pod exist.\n\t// These checks are dependent on the output of KubernetesAPIChecks, so those\n\t// checks must be added first.\n\tLinkerdControlPlaneExistenceChecks CategoryID = \"linkerd-existence\"\n\n\t// LinkerdVersionChecks adds a series of checks to query for the latest\n\t// version, and validate the CLI is up to date.\n\tLinkerdVersionChecks CategoryID = \"linkerd-version\"\n\n\t// LinkerdControlPlaneVersionChecks adds a series of checks to validate that\n\t// the control plane is running the latest available version.\n\t// These checks are dependent on the following:\n\t// 1) `latestVersions` from LinkerdVersionChecks\n\t// 2) `serverVersion` from `LinkerdControlPlaneExistenceChecks`\n\tLinkerdControlPlaneVersionChecks CategoryID = \"control-plane-version\"\n\n\t// LinkerdDataPlaneChecks adds data plane checks to validate that the\n\t// data plane namespace exists, and that the proxy containers are in a\n\t// ready state and running the latest available version.  These checks\n\t// are dependent on the output of KubernetesAPIChecks and\n\t// `latestVersions` from LinkerdVersionChecks, so those checks must be\n\t// added first.\n\tLinkerdDataPlaneChecks CategoryID = \"linkerd-data-plane\"\n\n\t// LinkerdControlPlaneProxyChecks adds data plane checks to validate the\n\t// control-plane proxies. The checkers include running and version checks\n\tLinkerdControlPlaneProxyChecks CategoryID = \"linkerd-control-plane-proxy\"\n\n\t// LinkerdHAChecks adds checks to validate that the HA configuration\n\t// is correct. These checks are no ops if linkerd is not in HA mode\n\tLinkerdHAChecks CategoryID = \"linkerd-ha-checks\"\n\n\t// LinkerdCNIPluginChecks adds checks to validate that the CNI\n\t/// plugin is installed and ready\n\tLinkerdCNIPluginChecks CategoryID = \"linkerd-cni-plugin\"\n\n\t// LinkerdOpaquePortsDefinitionChecks adds checks to validate that the\n\t// \"opaque ports\" annotation has been defined both in the service and the\n\t// corresponding pods\n\tLinkerdOpaquePortsDefinitionChecks CategoryID = \"linkerd-opaque-ports-definition\"\n\n\t// LinkerdExtensionChecks adds checks to validate configuration for all\n\t// extensions discovered in the cluster at runtime\n\tLinkerdExtensionChecks CategoryID = \"linkerd-extension-checks\"\n\n\t// LinkerdCNIResourceLabel is the label key that is used to identify\n\t// whether a Kubernetes resource is related to the install-cni command\n\t// The value is expected to be \"true\", \"false\" or \"\", where \"false\" and\n\t// \"\" are equal, making \"false\" the default\n\tLinkerdCNIResourceLabel = \"linkerd.io/cni-resource\"\n\n\tlinkerdCNIDisabledSkipReason = \"skipping check because CNI is not enabled\"\n\tlinkerdCNIResourceName       = \"linkerd-cni\"\n\tlinkerdCNIConfigMapName      = \"linkerd-cni-config\"\n\n\tpodCIDRUnavailableSkipReason    = \"skipping check because the nodes aren't exposing podCIDR\"\n\tconfigMapDoesNotExistSkipReason = \"skipping check because ConigMap does not exist\"\n\n\tproxyInjectorOldTLSSecretName = \"linkerd-proxy-injector-tls\"\n\tproxyInjectorTLSSecretName    = \"linkerd-proxy-injector-k8s-tls\"\n\n\tspValidatorOldTLSSecretName = \"linkerd-sp-validator-tls\"\n\tspValidatorTLSSecretName    = \"linkerd-sp-validator-k8s-tls\"\n\n\tpolicyValidatorTLSSecretName = \"linkerd-policy-validator-k8s-tls\"\n\tcertOldKeyName               = \"crt.pem\"\n\tcertKeyName                  = \"tls.crt\"\n\tkeyOldKeyName                = \"key.pem\"\n\tkeyKeyName                   = \"tls.key\"\n)\n\n// AllowedClockSkew sets the allowed skew in clock synchronization\n// between the system running inject command and the node(s), being\n// based on assumed node's heartbeat interval (5 minutes) plus default TLS\n// clock skew allowance.\n//\n// TODO: Make this default value overridable, e.g. by CLI flag\nconst AllowedClockSkew = 5*time.Minute + tls.DefaultClockSkewAllowance\n\nvar linkerdHAControlPlaneComponents = []string{\n\t\"linkerd-destination\",\n\t\"linkerd-identity\",\n\t\"linkerd-proxy-injector\",\n}\n\n// ExpectedServiceAccountNames is a list of the service accounts that a healthy\n// Linkerd installation should have. Note that linkerd-heartbeat is optional,\n// so it doesn't appear here.\nvar ExpectedServiceAccountNames = []string{\n\t\"linkerd-destination\",\n\t\"linkerd-identity\",\n\t\"linkerd-proxy-injector\",\n}\n\nvar (\n\tretryWindow = 5 * time.Second\n\t// RequestTimeout is the time it takes for a request to timeout\n\tRequestTimeout = 30 * time.Second\n)\n\n// Resource provides a way to describe a Kubernetes object, kind, and name.\n// TODO: Consider sharing with the inject package's ResourceConfig.workload\n// struct, as it wraps both runtime.Object and metav1.TypeMeta.\ntype Resource struct {\n\tgroupVersionKind schema.GroupVersionKind\n\tname             string\n}\n\n// String outputs the resource in kind.group/name format, intended for\n// `linkerd install`.\nfunc (r *Resource) String() string {\n\treturn fmt.Sprintf(\"%s/%s\", strings.ToLower(r.groupVersionKind.GroupKind().String()), r.name)\n}\n\n// ResourceError provides a custom error type for resource existence checks,\n// useful in printing detailed error messages in `linkerd check` and\n// `linkerd install`.\ntype ResourceError struct {\n\tresourceName string\n\tResources    []Resource\n}\n\n// Error satisfies the error interface for ResourceError. The output is intended\n// for `linkerd check`.\nfunc (e ResourceError) Error() string {\n\tnames := []string{}\n\tfor _, res := range e.Resources {\n\t\tnames = append(names, res.name)\n\t}\n\treturn fmt.Sprintf(\"%s found but should not exist: %s\", e.resourceName, strings.Join(names, \" \"))\n}\n\n// CategoryError provides a custom error type that also contains check category that emitted the error,\n// useful when needed to distinguish between errors from multiple categories\ntype CategoryError struct {\n\tCategory CategoryID\n\tErr      error\n}\n\n// Error satisfies the error interface for CategoryError.\nfunc (e CategoryError) Error() string {\n\treturn e.Err.Error()\n}\n\n// IsCategoryError returns true if passed in error is of type CategoryError and belong to the given category\nfunc IsCategoryError(err error, categoryID CategoryID) bool {\n\tvar ce CategoryError\n\tif errors.As(err, &ce) {\n\t\treturn ce.Category == categoryID\n\t}\n\treturn false\n}\n\n// SkipError is returned by a check in case this check needs to be ignored.\ntype SkipError struct {\n\tReason string\n}\n\n// Error satisfies the error interface for SkipError.\nfunc (e SkipError) Error() string {\n\treturn e.Reason\n}\n\n// VerboseSuccess implements the error interface but represents a success with\n// a message.\ntype VerboseSuccess struct {\n\tMessage string\n}\n\n// Error satisfies the error interface for VerboseSuccess.  Since VerboseSuccess\n// does not actually represent a failure, this returns the empty string.\nfunc (e VerboseSuccess) Error() string {\n\treturn \"\"\n}\n\n// Checker is a smallest unit performing a single check\ntype Checker struct {\n\t// description is the short description that's printed to the command line\n\t// when the check is executed\n\tdescription string\n\n\t// hintAnchor, when appended to `HintBaseURL`, provides a URL to more\n\t// information about the check\n\thintAnchor string\n\n\t// fatal indicates that all remaining checks should be aborted if this check\n\t// fails; it should only be used if subsequent checks cannot possibly succeed\n\t// (default false)\n\tfatal bool\n\n\t// warning indicates that if this check fails, it should be reported, but it\n\t// should not impact the overall outcome of the health check (default false)\n\twarning bool\n\n\t// retryDeadline establishes a deadline before which this check should be\n\t// retried; if the deadline has passed, the check fails (default: no retries)\n\tretryDeadline time.Time\n\n\t// surfaceErrorOnRetry indicates that the error message should be displayed\n\t// even if the check will be retried.  This is useful if the error message\n\t// contains the current status of the check.\n\tsurfaceErrorOnRetry bool\n\n\t// check is the function that's called to execute the check; if the function\n\t// returns an error, the check fails\n\tcheck func(context.Context) error\n}\n\n// NewChecker returns a new instance of checker type\nfunc NewChecker(description string) *Checker {\n\treturn &Checker{\n\t\tdescription:   description,\n\t\tretryDeadline: time.Time{},\n\t}\n}\n\n// WithHintAnchor returns a checker with the given hint anchor\nfunc (c *Checker) WithHintAnchor(hint string) *Checker {\n\tc.hintAnchor = hint\n\treturn c\n}\n\n// Fatal returns a checker with the fatal field set\nfunc (c *Checker) Fatal() *Checker {\n\tc.fatal = true\n\treturn c\n}\n\n// Warning returns a checker with the warning field set\nfunc (c *Checker) Warning() *Checker {\n\tc.warning = true\n\treturn c\n}\n\n// WithRetryDeadline returns a checker with the provided retry timeout\nfunc (c *Checker) WithRetryDeadline(retryDeadLine time.Time) *Checker {\n\tc.retryDeadline = retryDeadLine\n\treturn c\n}\n\n// SurfaceErrorOnRetry returns a checker with the surfaceErrorOnRetry set\nfunc (c *Checker) SurfaceErrorOnRetry() *Checker {\n\tc.surfaceErrorOnRetry = true\n\treturn c\n}\n\n// WithCheck returns a checker with the provided check func\nfunc (c *Checker) WithCheck(check func(context.Context) error) *Checker {\n\tc.check = check\n\treturn c\n}\n\n// CheckResult encapsulates a check's identifying information and output\n// Note there exists an analogous user-facing type, `cmd.check`, for output via\n// `linkerd check -o json`.\ntype CheckResult struct {\n\tCategory    CategoryID\n\tDescription string\n\tHintURL     string\n\tRetry       bool\n\tWarning     bool\n\tErr         error\n}\n\n// CheckObserver receives the results of each check.\ntype CheckObserver func(*CheckResult)\n\n// Category is a group of checkers, to check a particular component or use-case\ntype Category struct {\n\tID       CategoryID\n\tcheckers []Checker\n\tenabled  bool\n\t// hintBaseURL provides a base URL with more information\n\t// about the check\n\thintBaseURL string\n}\n\n// NewCategory returns an instance of Category with the specified data\nfunc NewCategory(id CategoryID, checkers []Checker, enabled bool) *Category {\n\treturn &Category{\n\t\tID:          id,\n\t\tcheckers:    checkers,\n\t\tenabled:     enabled,\n\t\thintBaseURL: HintBaseURL(version.Version),\n\t}\n}\n\n// WithHintBaseURL returns a Category with the provided hintBaseURL\nfunc (c *Category) WithHintBaseURL(hintBaseURL string) *Category {\n\tc.hintBaseURL = hintBaseURL\n\treturn c\n}\n\n// Options specifies configuration for a HealthChecker.\ntype Options struct {\n\tIsMainCheckCommand    bool\n\tControlPlaneNamespace string\n\tCNINamespace          string\n\tDataPlaneNamespace    string\n\tKubeConfig            string\n\tKubeContext           string\n\tImpersonate           string\n\tImpersonateGroup      []string\n\tAPIAddr               string\n\tVersionOverride       string\n\tRetryDeadline         time.Time\n\tCNIEnabled            bool\n\tInstallManifest       string\n\tCRDManifest           string\n\tChartValues           *l5dcharts.Values\n}\n\n// HealthChecker encapsulates all health check checkers, and clients required to\n// perform those checks.\ntype HealthChecker struct {\n\tcategories []*Category\n\t*Options\n\n\t// these fields are set in the process of running checks\n\tkubeAPI          *k8s.KubernetesAPI\n\tkubeVersion      *k8sVersion.Info\n\tcontrolPlanePods []corev1.Pod\n\tLatestVersions   version.Channels\n\tserverVersion    string\n\tlinkerdConfig    *l5dcharts.Values\n\tuuid             string\n\tissuerCert       *tls.Cred\n\ttrustAnchors     []*x509.Certificate\n\tcniDaemonSet     *appsv1.DaemonSet\n}\n\n// Runner is implemented by any health-checkers that can be triggered with RunChecks()\ntype Runner interface {\n\tRunChecks(observer CheckObserver) (bool, bool)\n}\n\n// NewHealthChecker returns an initialized HealthChecker\nfunc NewHealthChecker(categoryIDs []CategoryID, options *Options) *HealthChecker {\n\thc := &HealthChecker{\n\t\tOptions: options,\n\t}\n\n\thc.categories = hc.allCategories()\n\n\tcheckMap := map[CategoryID]struct{}{}\n\tfor _, category := range categoryIDs {\n\t\tcheckMap[category] = struct{}{}\n\t}\n\tfor i := range hc.categories {\n\t\tif _, ok := checkMap[hc.categories[i].ID]; ok {\n\t\t\thc.categories[i].enabled = true\n\t\t}\n\t}\n\n\treturn hc\n}\n\nfunc NewWithCoreChecks(options *Options) *HealthChecker {\n\tchecks := []CategoryID{KubernetesAPIChecks, LinkerdControlPlaneExistenceChecks}\n\treturn NewHealthChecker(checks, options)\n}\n\n// InitializeKubeAPIClient creates a client for the HealthChecker. It avoids\n// having to require the KubernetesAPIChecks check to run in order for the\n// HealthChecker to run other checks.\nfunc (hc *HealthChecker) InitializeKubeAPIClient() error {\n\tk8sAPI, err := k8s.NewAPI(hc.KubeConfig, hc.KubeContext, hc.Impersonate, hc.ImpersonateGroup, RequestTimeout)\n\tif err != nil {\n\t\treturn err\n\t}\n\thc.kubeAPI = k8sAPI\n\n\treturn nil\n}\n\n// InitializeLinkerdGlobalConfig populates the linkerd config object in the\n// healthchecker. It avoids having to require the LinkerdControlPlaneExistenceChecks\n// check to run before running other checks\nfunc (hc *HealthChecker) InitializeLinkerdGlobalConfig(ctx context.Context) error {\n\tuuid, l5dConfig, err := hc.checkLinkerdConfigConfigMap(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif l5dConfig != nil {\n\t\thc.CNIEnabled = l5dConfig.CNIEnabled\n\t}\n\thc.uuid = uuid\n\thc.linkerdConfig = l5dConfig\n\n\treturn nil\n}\n\n// AppendCategories returns a HealthChecker instance appending the provided Categories\nfunc (hc *HealthChecker) AppendCategories(categories ...*Category) *HealthChecker {\n\thc.categories = append(hc.categories, categories...)\n\treturn hc\n}\n\n// GetCategories returns all the categories\nfunc (hc *HealthChecker) GetCategories() []*Category {\n\treturn hc.categories\n}\n\n// allCategories is the global, ordered list of all checkers, grouped by\n// category. This method is attached to the HealthChecker struct because the\n// checkers directly reference other members of the struct, such as kubeAPI,\n// controlPlanePods, etc.\n//\n// Ordering is important because checks rely on specific `HealthChecker` members\n// getting populated by earlier checks, such as kubeAPI, controlPlanePods, etc.\n//\n// Note that all checks should include a `hintAnchor` with a corresponding section\n// in the linkerd check faq:\n// https://linkerd.io/{major-version}/checks/#\nfunc (hc *HealthChecker) allCategories() []*Category {\n\treturn []*Category{\n\t\tNewCategory(\n\t\t\tKubernetesAPIChecks,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription: \"can initialize the client\",\n\t\t\t\t\thintAnchor:  \"k8s-api\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(context.Context) (err error) {\n\t\t\t\t\t\terr = hc.InitializeKubeAPIClient()\n\t\t\t\t\t\treturn\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"can query the Kubernetes API\",\n\t\t\t\t\thintAnchor:  \"k8s-api\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) (err error) {\n\t\t\t\t\t\thc.kubeVersion, err = hc.kubeAPI.GetVersionInfo()\n\t\t\t\t\t\treturn\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t\tNewCategory(\n\t\t\tKubernetesVersionChecks,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription: \"is running the minimum Kubernetes API version\",\n\t\t\t\t\thintAnchor:  \"k8s-version\",\n\t\t\t\t\tcheck: func(context.Context) error {\n\t\t\t\t\t\treturn hc.kubeAPI.CheckVersion(hc.kubeVersion)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t\tNewCategory(\n\t\t\tLinkerdPreInstallChecks,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription: \"control plane namespace does not already exist\",\n\t\t\t\t\thintAnchor:  \"pre-ns\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.CheckNamespace(ctx, hc.ControlPlaneNamespace, false)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"can create non-namespaced resources\",\n\t\t\t\t\thintAnchor:  \"pre-k8s-cluster-k8s\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkCanCreateNonNamespacedResources(ctx)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"can create ServiceAccounts\",\n\t\t\t\t\thintAnchor:  \"pre-k8s\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkCanCreate(ctx, hc.ControlPlaneNamespace, \"\", \"v1\", \"serviceaccounts\")\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"can create Services\",\n\t\t\t\t\thintAnchor:  \"pre-k8s\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkCanCreate(ctx, hc.ControlPlaneNamespace, \"\", \"v1\", \"services\")\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"can create Deployments\",\n\t\t\t\t\thintAnchor:  \"pre-k8s\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkCanCreate(ctx, hc.ControlPlaneNamespace, \"apps\", \"v1\", \"deployments\")\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"can create CronJobs\",\n\t\t\t\t\thintAnchor:  \"pre-k8s\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkCanCreate(ctx, hc.ControlPlaneNamespace, \"batch\", \"v1beta1\", \"cronjobs\")\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"can create ConfigMaps\",\n\t\t\t\t\thintAnchor:  \"pre-k8s\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkCanCreate(ctx, hc.ControlPlaneNamespace, \"\", \"v1\", \"configmaps\")\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"can create Secrets\",\n\t\t\t\t\thintAnchor:  \"pre-k8s\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkCanCreate(ctx, hc.ControlPlaneNamespace, \"\", \"v1\", \"secrets\")\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"can read Secrets\",\n\t\t\t\t\thintAnchor:  \"pre-k8s\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkCanGet(ctx, hc.ControlPlaneNamespace, \"\", \"v1\", \"secrets\")\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"can read extension-apiserver-authentication configmap\",\n\t\t\t\t\thintAnchor:  \"pre-k8s\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkExtensionAPIServerAuthentication(ctx)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"no clock skew detected\",\n\t\t\t\t\thintAnchor:  \"pre-k8s-clock-skew\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkClockSkew(ctx)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t\tNewCategory(\n\t\t\tLinkerdCRDChecks,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription:   \"control plane CustomResourceDefinitions exist\",\n\t\t\t\t\thintAnchor:    \"l5d-existence-crd\",\n\t\t\t\t\tfatal:         true,\n\t\t\t\t\tretryDeadline: hc.RetryDeadline,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn CheckCustomResourceDefinitions(ctx, hc.kubeAPI, hc.CRDManifest)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t\tNewCategory(\n\t\t\tLinkerdControlPlaneExistenceChecks,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription: \"'linkerd-config' config map exists\",\n\t\t\t\t\thintAnchor:  \"l5d-existence-linkerd-config\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) (err error) {\n\t\t\t\t\t\terr = hc.InitializeLinkerdGlobalConfig(ctx)\n\t\t\t\t\t\treturn\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"heartbeat ServiceAccount exist\",\n\t\t\t\t\thintAnchor:  \"l5d-existence-sa\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tif hc.isHeartbeatDisabled() {\n\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn hc.checkServiceAccounts(ctx, []string{\"linkerd-heartbeat\"}, hc.ControlPlaneNamespace, controlPlaneComponentsSelector())\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:   \"control plane replica sets are ready\",\n\t\t\t\t\thintAnchor:    \"l5d-existence-replicasets\",\n\t\t\t\t\tretryDeadline: hc.RetryDeadline,\n\t\t\t\t\tfatal:         true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tcontrolPlaneReplicaSet, err := hc.kubeAPI.GetReplicaSets(ctx, hc.ControlPlaneNamespace)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn checkControlPlaneReplicaSets(controlPlaneReplicaSet)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:         \"no unschedulable pods\",\n\t\t\t\t\thintAnchor:          \"l5d-existence-unschedulable-pods\",\n\t\t\t\t\tretryDeadline:       hc.RetryDeadline,\n\t\t\t\t\tsurfaceErrorOnRetry: true,\n\t\t\t\t\twarning:             true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\t// do not save this into hc.controlPlanePods, as this check may\n\t\t\t\t\t\t// succeed prior to all expected control plane pods being up\n\t\t\t\t\t\tcontrolPlanePods, err := hc.kubeAPI.GetPodsByNamespace(ctx, hc.ControlPlaneNamespace)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn checkUnschedulablePods(controlPlanePods)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:         \"control plane pods are ready\",\n\t\t\t\t\thintAnchor:          \"l5d-api-control-ready\",\n\t\t\t\t\tretryDeadline:       hc.RetryDeadline,\n\t\t\t\t\tsurfaceErrorOnRetry: true,\n\t\t\t\t\tfatal:               true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tvar err error\n\t\t\t\t\t\tpodList, err := hc.kubeAPI.CoreV1().Pods(hc.ControlPlaneNamespace).List(ctx, metav1.ListOptions{\n\t\t\t\t\t\t\tLabelSelector: k8s.ControllerComponentLabel,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\thc.controlPlanePods = podList.Items\n\t\t\t\t\t\treturn validateControlPlanePods(hc.controlPlanePods)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"cluster networks contains all node podCIDRs\",\n\t\t\t\t\thintAnchor:  \"l5d-cluster-networks-cidr\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\t// We explicitly initialize the config here so that we dont rely on the \"l5d-existence-linkerd-config\"\n\t\t\t\t\t\t// check to set the clusterNetworks value, since `linkerd check config` will skip that check.\n\t\t\t\t\t\terr := hc.InitializeLinkerdGlobalConfig(ctx)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn hc.checkClusterNetworks(ctx)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"cluster networks contains all pods\",\n\t\t\t\t\thintAnchor:  \"l5d-cluster-networks-pods\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkClusterNetworksContainAllPods(ctx)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"cluster networks contains all services\",\n\t\t\t\t\thintAnchor:  \"l5d-cluster-networks-pods\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkClusterNetworksContainAllServices(ctx)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t\tNewCategory(\n\t\t\tLinkerdConfigChecks,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription: \"control plane Namespace exists\",\n\t\t\t\t\thintAnchor:  \"l5d-existence-ns\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.CheckNamespace(ctx, hc.ControlPlaneNamespace, true)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"control plane ClusterRoles exist\",\n\t\t\t\t\thintAnchor:  \"l5d-existence-cr\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkClusterRoles(ctx, true, hc.expectedRBACNames(), controlPlaneComponentsSelector())\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"control plane ClusterRoleBindings exist\",\n\t\t\t\t\thintAnchor:  \"l5d-existence-crb\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkClusterRoleBindings(ctx, true, hc.expectedRBACNames(), controlPlaneComponentsSelector())\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"control plane ServiceAccounts exist\",\n\t\t\t\t\thintAnchor:  \"l5d-existence-sa\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkServiceAccounts(ctx, ExpectedServiceAccountNames, hc.ControlPlaneNamespace, controlPlaneComponentsSelector())\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"control plane CustomResourceDefinitions exist\",\n\t\t\t\t\thintAnchor:  \"l5d-existence-crd\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn CheckCustomResourceDefinitions(ctx, hc.kubeAPI, hc.CRDManifest)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"control plane MutatingWebhookConfigurations exist\",\n\t\t\t\t\thintAnchor:  \"l5d-existence-mwc\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkMutatingWebhookConfigurations(ctx, true)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"control plane ValidatingWebhookConfigurations exist\",\n\t\t\t\t\thintAnchor:  \"l5d-existence-vwc\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkValidatingWebhookConfigurations(ctx, true)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"proxy-init container runs as root user if docker container runtime is used\",\n\t\t\t\t\thintAnchor:  \"l5d-proxy-init-run-as-root\",\n\t\t\t\t\tfatal:       false,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\t// We explicitly initialize the config here so that we dont rely on the \"l5d-existence-linkerd-config\"\n\t\t\t\t\t\t// check to set the clusterNetworks value, since `linkerd check config` will skip that check.\n\t\t\t\t\t\terr := hc.InitializeLinkerdGlobalConfig(ctx)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\t\t\t\treturn SkipError{Reason: configMapDoesNotExistSkipReason}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconfig := hc.LinkerdConfig()\n\t\t\t\t\t\trunAsRoot := config != nil && config.ProxyInit != nil && config.ProxyInit.RunAsRoot\n\t\t\t\t\t\tif !runAsRoot {\n\t\t\t\t\t\t\treturn CheckNodesHaveNonDockerRuntime(ctx, hc.KubeAPIClient())\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t\tNewCategory(\n\t\t\tLinkerdCNIPluginChecks,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription: \"cni plugin ConfigMap exists\",\n\t\t\t\t\thintAnchor:  \"cni-plugin-cm-exists\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tif !hc.CNIEnabled {\n\t\t\t\t\t\t\treturn SkipError{Reason: linkerdCNIDisabledSkipReason}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_, err := hc.kubeAPI.CoreV1().ConfigMaps(hc.CNINamespace).Get(ctx, linkerdCNIConfigMapName, metav1.GetOptions{})\n\t\t\t\t\t\treturn err\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"cni plugin ClusterRole exists\",\n\t\t\t\t\thintAnchor:  \"cni-plugin-cr-exists\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tif !hc.CNIEnabled {\n\t\t\t\t\t\t\treturn SkipError{Reason: linkerdCNIDisabledSkipReason}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_, err := hc.kubeAPI.RbacV1().ClusterRoles().Get(ctx, linkerdCNIResourceName, metav1.GetOptions{})\n\t\t\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"missing ClusterRole: %s\", linkerdCNIResourceName)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn err\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"cni plugin ClusterRoleBinding exists\",\n\t\t\t\t\thintAnchor:  \"cni-plugin-crb-exists\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tif !hc.CNIEnabled {\n\t\t\t\t\t\t\treturn SkipError{Reason: linkerdCNIDisabledSkipReason}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_, err := hc.kubeAPI.RbacV1().ClusterRoleBindings().Get(ctx, linkerdCNIResourceName, metav1.GetOptions{})\n\t\t\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"missing ClusterRoleBinding: %s\", linkerdCNIResourceName)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn err\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"cni plugin ServiceAccount exists\",\n\t\t\t\t\thintAnchor:  \"cni-plugin-sa-exists\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tif !hc.CNIEnabled {\n\t\t\t\t\t\t\treturn SkipError{Reason: linkerdCNIDisabledSkipReason}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_, err := hc.kubeAPI.CoreV1().ServiceAccounts(hc.CNINamespace).Get(ctx, linkerdCNIResourceName, metav1.GetOptions{})\n\t\t\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"missing ServiceAccount: %s\", linkerdCNIResourceName)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn err\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"cni plugin DaemonSet exists\",\n\t\t\t\t\thintAnchor:  \"cni-plugin-ds-exists\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) (err error) {\n\t\t\t\t\t\tif !hc.CNIEnabled {\n\t\t\t\t\t\t\treturn SkipError{Reason: linkerdCNIDisabledSkipReason}\n\t\t\t\t\t\t}\n\t\t\t\t\t\thc.cniDaemonSet, err = hc.kubeAPI.Interface.AppsV1().DaemonSets(hc.CNINamespace).Get(ctx, linkerdCNIResourceName, metav1.GetOptions{})\n\t\t\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"missing DaemonSet: %s\", linkerdCNIResourceName)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn err\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:         \"cni plugin pod is running on all nodes\",\n\t\t\t\t\thintAnchor:          \"cni-plugin-ready\",\n\t\t\t\t\tretryDeadline:       hc.RetryDeadline,\n\t\t\t\t\tsurfaceErrorOnRetry: true,\n\t\t\t\t\tfatal:               true,\n\t\t\t\t\tcheck: func(ctx context.Context) (err error) {\n\t\t\t\t\t\tif !hc.CNIEnabled {\n\t\t\t\t\t\t\treturn SkipError{Reason: linkerdCNIDisabledSkipReason}\n\t\t\t\t\t\t}\n\t\t\t\t\t\thc.cniDaemonSet, err = hc.kubeAPI.Interface.AppsV1().DaemonSets(hc.CNINamespace).Get(ctx, linkerdCNIResourceName, metav1.GetOptions{})\n\t\t\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"missing DaemonSet: %s\", linkerdCNIResourceName)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tscheduled := hc.cniDaemonSet.Status.DesiredNumberScheduled\n\t\t\t\t\t\tready := hc.cniDaemonSet.Status.NumberReady\n\t\t\t\t\t\tif scheduled != ready {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"number ready: %d, number scheduled: %d\", ready, scheduled)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t\tNewCategory(\n\t\t\tLinkerdIdentity,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription: \"certificate config is valid\",\n\t\t\t\t\thintAnchor:  \"l5d-identity-cert-config-valid\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) (err error) {\n\t\t\t\t\t\thc.issuerCert, hc.trustAnchors, err = hc.checkCertificatesConfig(ctx)\n\t\t\t\t\t\treturn\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"trust anchors are using supported crypto algorithm\",\n\t\t\t\t\thintAnchor:  \"l5d-identity-trustAnchors-use-supported-crypto\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(context.Context) error {\n\t\t\t\t\t\tvar invalidAnchors []string\n\t\t\t\t\t\tfor _, anchor := range hc.trustAnchors {\n\t\t\t\t\t\t\tif err := issuercerts.CheckTrustAnchorAlgoRequirements(anchor); err != nil {\n\t\t\t\t\t\t\t\tinvalidAnchors = append(invalidAnchors, fmt.Sprintf(\"* %v %s %s\", anchor.SerialNumber, anchor.Subject.CommonName, err))\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif len(invalidAnchors) > 0 {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"Invalid trustAnchors:\\n\\t%s\", strings.Join(invalidAnchors, \"\\n\\t\"))\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"trust anchors are within their validity period\",\n\t\t\t\t\thintAnchor:  \"l5d-identity-trustAnchors-are-time-valid\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tvar expiredAnchors []string\n\t\t\t\t\t\tfor _, anchor := range hc.trustAnchors {\n\t\t\t\t\t\t\tif err := issuercerts.CheckCertValidityPeriod(anchor); err != nil {\n\t\t\t\t\t\t\t\texpiredAnchors = append(expiredAnchors, fmt.Sprintf(\"* %v %s %s\", anchor.SerialNumber, anchor.Subject.CommonName, err))\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif len(expiredAnchors) > 0 {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"Invalid anchors:\\n\\t%s\", strings.Join(expiredAnchors, \"\\n\\t\"))\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"trust anchors are valid for at least 60 days\",\n\t\t\t\t\thintAnchor:  \"l5d-identity-trustAnchors-not-expiring-soon\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tvar expiringAnchors []string\n\t\t\t\t\t\tfor _, anchor := range hc.trustAnchors {\n\t\t\t\t\t\t\tif err := issuercerts.CheckExpiringSoon(anchor); err != nil {\n\t\t\t\t\t\t\t\texpiringAnchors = append(expiringAnchors, fmt.Sprintf(\"* %v %s %s\", anchor.SerialNumber, anchor.Subject.CommonName, err))\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif len(expiringAnchors) > 0 {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"Anchors expiring soon:\\n\\t%s\", strings.Join(expiringAnchors, \"\\n\\t\"))\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"issuer cert is using supported crypto algorithm\",\n\t\t\t\t\thintAnchor:  \"l5d-identity-issuer-cert-uses-supported-crypto\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(context.Context) error {\n\t\t\t\t\t\tif err := issuercerts.CheckIssuerCertAlgoRequirements(hc.issuerCert.Certificate); err != nil {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"issuer certificate %w\", err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"issuer cert is within its validity period\",\n\t\t\t\t\thintAnchor:  \"l5d-identity-issuer-cert-is-time-valid\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tif err := issuercerts.CheckCertValidityPeriod(hc.issuerCert.Certificate); err != nil {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"issuer certificate is %w\", err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"issuer cert is valid for at least 60 days\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\thintAnchor:  \"l5d-identity-issuer-cert-not-expiring-soon\",\n\t\t\t\t\tcheck: func(context.Context) error {\n\t\t\t\t\t\tif err := issuercerts.CheckExpiringSoon(hc.issuerCert.Certificate); err != nil {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"issuer certificate %w\", err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"issuer cert is issued by the trust anchor\",\n\t\t\t\t\thintAnchor:  \"l5d-identity-issuer-cert-issued-by-trust-anchor\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.issuerCert.Verify(tls.CertificatesToPool(hc.trustAnchors), \"\", time.Time{})\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t\tNewCategory(\n\t\t\tLinkerdWebhooksAndAPISvcTLS,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription: \"proxy-injector webhook has valid cert\",\n\t\t\t\t\thintAnchor:  \"l5d-proxy-injector-webhook-cert-valid\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) (err error) {\n\t\t\t\t\t\tanchors, err := hc.fetchProxyInjectorCaBundle(ctx)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcert, err := hc.FetchCredsFromSecret(ctx, hc.ControlPlaneNamespace, proxyInjectorTLSSecretName)\n\t\t\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\t\t\tcert, err = hc.FetchCredsFromOldSecret(ctx, hc.ControlPlaneNamespace, proxyInjectorOldTLSSecretName)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tidentityName := fmt.Sprintf(\"linkerd-proxy-injector.%s.svc\", hc.ControlPlaneNamespace)\n\t\t\t\t\t\treturn hc.CheckCertAndAnchors(cert, anchors, identityName)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"proxy-injector cert is valid for at least 60 days\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\thintAnchor:  \"l5d-proxy-injector-webhook-cert-not-expiring-soon\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tcert, err := hc.FetchCredsFromSecret(ctx, hc.ControlPlaneNamespace, proxyInjectorTLSSecretName)\n\t\t\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\t\t\tcert, err = hc.FetchCredsFromOldSecret(ctx, hc.ControlPlaneNamespace, proxyInjectorOldTLSSecretName)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn hc.CheckCertAndAnchorsExpiringSoon(cert)\n\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"sp-validator webhook has valid cert\",\n\t\t\t\t\thintAnchor:  \"l5d-sp-validator-webhook-cert-valid\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) (err error) {\n\t\t\t\t\t\tanchors, err := hc.fetchWebhookCaBundle(ctx, k8s.SPValidatorWebhookConfigName)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcert, err := hc.FetchCredsFromSecret(ctx, hc.ControlPlaneNamespace, spValidatorTLSSecretName)\n\t\t\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\t\t\tcert, err = hc.FetchCredsFromOldSecret(ctx, hc.ControlPlaneNamespace, spValidatorOldTLSSecretName)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tidentityName := fmt.Sprintf(\"linkerd-sp-validator.%s.svc\", hc.ControlPlaneNamespace)\n\t\t\t\t\t\treturn hc.CheckCertAndAnchors(cert, anchors, identityName)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"sp-validator cert is valid for at least 60 days\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\thintAnchor:  \"l5d-sp-validator-webhook-cert-not-expiring-soon\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tcert, err := hc.FetchCredsFromSecret(ctx, hc.ControlPlaneNamespace, spValidatorTLSSecretName)\n\t\t\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\t\t\tcert, err = hc.FetchCredsFromOldSecret(ctx, hc.ControlPlaneNamespace, spValidatorOldTLSSecretName)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn hc.CheckCertAndAnchorsExpiringSoon(cert)\n\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"policy-validator webhook has valid cert\",\n\t\t\t\t\thintAnchor:  \"l5d-policy-validator-webhook-cert-valid\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) (err error) {\n\t\t\t\t\t\tanchors, err := hc.fetchWebhookCaBundle(ctx, k8s.PolicyValidatorWebhookConfigName)\n\t\t\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\t\t\treturn SkipError{Reason: \"policy-validator not installed\"}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcert, err := hc.FetchCredsFromSecret(ctx, hc.ControlPlaneNamespace, policyValidatorTLSSecretName)\n\t\t\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\t\t\treturn SkipError{Reason: \"policy-validator not installed\"}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tidentityName := fmt.Sprintf(\"linkerd-policy-validator.%s.svc\", hc.ControlPlaneNamespace)\n\t\t\t\t\t\treturn hc.CheckCertAndAnchors(cert, anchors, identityName)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"policy-validator cert is valid for at least 60 days\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\thintAnchor:  \"l5d-policy-validator-webhook-cert-not-expiring-soon\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tcert, err := hc.FetchCredsFromSecret(ctx, hc.ControlPlaneNamespace, policyValidatorTLSSecretName)\n\t\t\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\t\t\treturn SkipError{Reason: \"policy-validator not installed\"}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn hc.CheckCertAndAnchorsExpiringSoon(cert)\n\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t\tNewCategory(\n\t\t\tLinkerdIdentityDataPlane,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription: \"data plane proxies certificate match CA\",\n\t\t\t\t\thintAnchor:  \"l5d-identity-data-plane-proxies-certs-match-ca\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkDataPlaneProxiesCertificate(ctx)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t\tNewCategory(\n\t\t\tLinkerdVersionChecks,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription: \"can determine the latest version\",\n\t\t\t\t\thintAnchor:  \"l5d-version-latest\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\tcheck: func(ctx context.Context) (err error) {\n\t\t\t\t\t\tif hc.VersionOverride != \"\" {\n\t\t\t\t\t\t\thc.LatestVersions, err = version.NewChannels(hc.VersionOverride)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tuuid := \"unknown\"\n\t\t\t\t\t\t\tif hc.uuid != \"\" {\n\t\t\t\t\t\t\t\tuuid = hc.uuid\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\thc.LatestVersions, err = version.GetLatestVersions(ctx, uuid, \"cli\")\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"cli is up-to-date\",\n\t\t\t\t\thintAnchor:  \"l5d-version-cli\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\tcheck: func(context.Context) error {\n\t\t\t\t\t\treturn hc.LatestVersions.Match(version.Version)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t\tNewCategory(\n\t\t\tLinkerdControlPlaneVersionChecks,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription:   \"can retrieve the control plane version\",\n\t\t\t\t\thintAnchor:    \"l5d-version-control\",\n\t\t\t\t\tretryDeadline: hc.RetryDeadline,\n\t\t\t\t\tfatal:         true,\n\t\t\t\t\tcheck: func(ctx context.Context) (err error) {\n\t\t\t\t\t\thc.serverVersion, err = GetServerVersion(ctx, hc.ControlPlaneNamespace, hc.kubeAPI)\n\t\t\t\t\t\treturn\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"control plane is up-to-date\",\n\t\t\t\t\thintAnchor:  \"l5d-version-control\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\tcheck: func(context.Context) error {\n\t\t\t\t\t\treturn hc.LatestVersions.Match(hc.serverVersion)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"control plane and cli versions match\",\n\t\t\t\t\thintAnchor:  \"l5d-version-control\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\tcheck: func(context.Context) error {\n\t\t\t\t\t\tif hc.serverVersion != version.Version {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"control plane running %s but cli running %s\", hc.serverVersion, version.Version)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t\tNewCategory(\n\t\t\tLinkerdControlPlaneProxyChecks,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription:         \"control plane proxies are healthy\",\n\t\t\t\t\thintAnchor:          \"l5d-cp-proxy-healthy\",\n\t\t\t\t\tretryDeadline:       hc.RetryDeadline,\n\t\t\t\t\tsurfaceErrorOnRetry: true,\n\t\t\t\t\tfatal:               true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.CheckProxyHealth(ctx, hc.ControlPlaneNamespace, hc.ControlPlaneNamespace)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"control plane proxies are up-to-date\",\n\t\t\t\t\thintAnchor:  \"l5d-cp-proxy-version\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tpodList, err := hc.kubeAPI.CoreV1().Pods(hc.ControlPlaneNamespace).List(ctx, metav1.ListOptions{LabelSelector: k8s.ControllerNSLabel})\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn hc.CheckProxyVersionsUpToDate(podList.Items)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"control plane proxies and cli versions match\",\n\t\t\t\t\thintAnchor:  \"l5d-cp-proxy-cli-version\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tpodList, err := hc.kubeAPI.CoreV1().Pods(hc.ControlPlaneNamespace).List(ctx, metav1.ListOptions{LabelSelector: k8s.ControllerNSLabel})\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn CheckIfProxyVersionsMatchWithCLI(podList.Items)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t\tNewCategory(\n\t\t\tLinkerdDataPlaneChecks,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription: \"data plane namespace exists\",\n\t\t\t\t\thintAnchor:  \"l5d-data-plane-exists\",\n\t\t\t\t\tfatal:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tif hc.DataPlaneNamespace == \"\" {\n\t\t\t\t\t\t\t// when checking proxies in all namespaces, this check is a no-op\n\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn hc.CheckNamespace(ctx, hc.DataPlaneNamespace, true)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription:   \"data plane proxies are ready\",\n\t\t\t\t\thintAnchor:    \"l5d-data-plane-ready\",\n\t\t\t\t\tretryDeadline: hc.RetryDeadline,\n\t\t\t\t\tfatal:         true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tpods, err := hc.GetDataPlanePods(ctx)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn CheckPodsRunning(pods, hc.DataPlaneNamespace)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"data plane is up-to-date\",\n\t\t\t\t\thintAnchor:  \"l5d-data-plane-version\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tpods, err := hc.GetDataPlanePods(ctx)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn hc.CheckProxyVersionsUpToDate(pods)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"data plane and cli versions match\",\n\t\t\t\t\thintAnchor:  \"l5d-data-plane-cli-version\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tpods, err := hc.GetDataPlanePods(ctx)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn CheckIfProxyVersionsMatchWithCLI(pods)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"data plane pod labels are configured correctly\",\n\t\t\t\t\thintAnchor:  \"l5d-data-plane-pod-labels\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tpods, err := hc.GetDataPlanePods(ctx)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn checkMisconfiguredPodsLabels(pods)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"data plane service labels are configured correctly\",\n\t\t\t\t\thintAnchor:  \"l5d-data-plane-services-labels\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tservices, err := hc.GetServices(ctx)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn checkMisconfiguredServiceLabels(services)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"data plane service annotations are configured correctly\",\n\t\t\t\t\thintAnchor:  \"l5d-data-plane-services-annotations\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tservices, err := hc.GetServices(ctx)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn checkMisconfiguredServiceAnnotations(services)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"opaque ports are properly annotated\",\n\t\t\t\t\thintAnchor:  \"linkerd-opaque-ports-definition\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkMisconfiguredOpaquePortAnnotations(ctx)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t\tNewCategory(\n\t\t\tLinkerdHAChecks,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription:   \"multiple replicas of control plane pods\",\n\t\t\t\t\thintAnchor:    \"l5d-control-plane-replicas\",\n\t\t\t\t\tretryDeadline: hc.RetryDeadline,\n\t\t\t\t\twarning:       true,\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\tif hc.isHA() {\n\t\t\t\t\t\t\treturn hc.checkMinReplicasAvailable(ctx)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn SkipError{Reason: \"not run for non HA installs\"}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t\tNewCategory(\n\t\t\tLinkerdExtensionChecks,\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription: \"namespace configuration for extensions\",\n\t\t\t\t\twarning:     true,\n\t\t\t\t\thintAnchor:  \"l5d-extension-namespaces\",\n\t\t\t\t\tcheck: func(ctx context.Context) error {\n\t\t\t\t\t\treturn hc.checkExtensionNsLabels(ctx)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfalse,\n\t\t),\n\t}\n}\n\n// CheckProxyVersionsUpToDate checks if all the proxies are on the latest\n// installed version\nfunc (hc *HealthChecker) CheckProxyVersionsUpToDate(pods []corev1.Pod) error {\n\treturn CheckProxyVersionsUpToDate(pods, hc.LatestVersions)\n}\n\n// CheckProxyVersionsUpToDate checks if all the proxies are on the latest\n// installed version\nfunc CheckProxyVersionsUpToDate(pods []corev1.Pod, versions version.Channels) error {\n\toutdatedPods := []string{}\n\tfor _, pod := range pods {\n\t\tstatus := k8s.GetPodStatus(pod)\n\t\tif status == string(corev1.PodRunning) {\n\t\t\tproxyVersion := k8s.GetProxyVersion(pod)\n\t\t\tif proxyVersion == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err := versions.Match(proxyVersion); err != nil {\n\t\t\t\toutdatedPods = append(outdatedPods, fmt.Sprintf(\"\\t* %s (%s)\", pod.Name, proxyVersion))\n\t\t\t}\n\t\t}\n\t}\n\tif versions.Empty() {\n\t\treturn errors.New(\"unable to determine version channel\")\n\t}\n\tif len(outdatedPods) > 0 {\n\t\tpodList := strings.Join(outdatedPods, \"\\n\")\n\t\treturn fmt.Errorf(\"some proxies are not running the current version:\\n%s\", podList)\n\t}\n\treturn nil\n}\n\n// CheckIfProxyVersionsMatchWithCLI checks if the latest proxy version\n// matches that of the CLI\nfunc CheckIfProxyVersionsMatchWithCLI(pods []corev1.Pod) error {\n\tfor _, pod := range pods {\n\t\tstatus := k8s.GetPodStatus(pod)\n\t\tproxyVersion := k8s.GetProxyVersion(pod)\n\t\tif status == string(corev1.PodRunning) && proxyVersion != \"\" && proxyVersion != version.Version {\n\t\t\treturn fmt.Errorf(\"%s running %s but cli running %s\", pod.Name, proxyVersion, version.Version)\n\t\t}\n\t}\n\treturn nil\n}\n\n// CheckCertAndAnchors checks if the given cert and anchors are valid\nfunc (hc *HealthChecker) CheckCertAndAnchors(cert *tls.Cred, trustAnchors []*x509.Certificate, identityName string) error {\n\n\t// check anchors time validity\n\tvar expiredAnchors []string\n\tfor _, anchor := range trustAnchors {\n\t\tif err := issuercerts.CheckCertValidityPeriod(anchor); err != nil {\n\t\t\texpiredAnchors = append(expiredAnchors, fmt.Sprintf(\"* %v %s %s\", anchor.SerialNumber, anchor.Subject.CommonName, err))\n\t\t}\n\t}\n\tif len(expiredAnchors) > 0 {\n\t\treturn fmt.Errorf(\"anchors not within their validity period:\\n\\t%s\", strings.Join(expiredAnchors, \"\\n\\t\"))\n\t}\n\n\t// check cert validity\n\tif err := issuercerts.CheckCertValidityPeriod(cert.Certificate); err != nil {\n\t\treturn fmt.Errorf(\"certificate is %w\", err)\n\t}\n\n\tif err := cert.Verify(tls.CertificatesToPool(trustAnchors), identityName, time.Time{}); err != nil {\n\t\treturn fmt.Errorf(\"cert is not issued by the trust anchor: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// CheckProxyHealth checks for the data-plane proxies health in the given namespace\n// These checks consist of status and identity\nfunc (hc *HealthChecker) CheckProxyHealth(ctx context.Context, controlPlaneNamespace, namespace string) error {\n\tpodList, err := hc.kubeAPI.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: k8s.ControllerNSLabel})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Validate the status of the pods\n\terr = CheckPodsRunning(podList.Items, controlPlaneNamespace)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Check proxy certificates\n\treturn checkPodsProxiesCertificate(ctx, *hc.kubeAPI, namespace, controlPlaneNamespace)\n}\n\n// CheckCertAndAnchorsExpiringSoon checks if the given cert and anchors expire soon, and returns an\n// error if they do.\nfunc (hc *HealthChecker) CheckCertAndAnchorsExpiringSoon(cert *tls.Cred) error {\n\t// check anchors not expiring soon\n\tvar expiringAnchors []string\n\tfor _, anchor := range cert.TrustChain {\n\t\tanchor := anchor\n\t\tif err := issuercerts.CheckExpiringSoon(anchor); err != nil {\n\t\t\texpiringAnchors = append(expiringAnchors, fmt.Sprintf(\"* %v %s %s\", anchor.SerialNumber, anchor.Subject.CommonName, err))\n\t\t}\n\t}\n\tif len(expiringAnchors) > 0 {\n\t\treturn fmt.Errorf(\"Anchors expiring soon:\\n\\t%s\", strings.Join(expiringAnchors, \"\\n\\t\"))\n\t}\n\n\t// check cert not expiring soon\n\tif err := issuercerts.CheckExpiringSoon(cert.Certificate); err != nil {\n\t\treturn fmt.Errorf(\"certificate %w\", err)\n\t}\n\treturn nil\n}\n\n// CheckAPIService checks the status of the given API Service and returns an error if it's not running\nfunc (hc *HealthChecker) CheckAPIService(ctx context.Context, serviceName string) error {\n\tapiServiceClient, err := apiregistrationv1client.NewForConfig(hc.kubeAPI.Config)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tapiStatus, err := apiServiceClient.APIServices().Get(ctx, serviceName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, condition := range apiStatus.Status.Conditions {\n\t\tif condition.Type == \"Available\" {\n\t\t\tif condition.Status == \"True\" {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"%s: %s\", condition.Reason, condition.Message)\n\t\t}\n\t}\n\n\treturn fmt.Errorf(\"%s service not available\", apiStatus.Name)\n}\n\nfunc (hc *HealthChecker) checkMinReplicasAvailable(ctx context.Context) error {\n\tfaulty := []string{}\n\n\tfor _, component := range linkerdHAControlPlaneComponents {\n\t\tconf, err := hc.kubeAPI.AppsV1().Deployments(hc.ControlPlaneNamespace).Get(ctx, component, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif conf.Status.AvailableReplicas <= 1 {\n\t\t\tfaulty = append(faulty, component)\n\t\t}\n\t}\n\n\tif len(faulty) > 0 {\n\t\treturn fmt.Errorf(\"not enough replicas available for %v\", faulty)\n\t}\n\treturn nil\n}\n\n// RunChecks runs all configured checkers, and passes the results of each\n// check to the observer. If a check fails and is marked as fatal, then all\n// remaining checks are skipped. If at least one check fails, RunChecks returns\n// false; if all checks passed, RunChecks returns true.  Checks which are\n// designated as warnings will not cause RunCheck to return false, however.\nfunc (hc *HealthChecker) RunChecks(observer CheckObserver) (bool, bool) {\n\tsuccess := true\n\twarning := false\n\tfor _, c := range hc.categories {\n\t\tif c.enabled {\n\t\t\tfor _, checker := range c.checkers {\n\t\t\t\tchecker := checker // pin\n\t\t\t\tif checker.check != nil {\n\t\t\t\t\tif !hc.runCheck(c, &checker, observer) {\n\t\t\t\t\t\tif !checker.warning {\n\t\t\t\t\t\t\tsuccess = false\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twarning = true\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif checker.fatal {\n\t\t\t\t\t\t\treturn success, warning\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\treturn success, warning\n}\n\nfunc (hc *HealthChecker) RunWithExitOnError() (bool, bool) {\n\treturn hc.RunChecks(func(result *CheckResult) {\n\t\tif result.Retry {\n\t\t\tfmt.Fprintln(os.Stderr, \"Waiting for control plane to become available\")\n\t\t\treturn\n\t\t}\n\n\t\tif result.Err != nil && !result.Warning {\n\t\t\tvar msg string\n\t\t\tswitch result.Category {\n\t\t\tcase KubernetesAPIChecks:\n\t\t\t\tmsg = \"Cannot connect to Kubernetes\"\n\t\t\tcase LinkerdControlPlaneExistenceChecks:\n\t\t\t\tmsg = \"Cannot find Linkerd\"\n\t\t\t}\n\t\t\tfmt.Fprintf(os.Stderr, \"%s: %s\\nValidate the install with: 'linkerd check'\\n\",\n\t\t\t\tmsg, result.Err)\n\t\t\tos.Exit(1)\n\t\t}\n\t})\n}\n\n// LinkerdConfig gets the Linkerd configuration values.\nfunc (hc *HealthChecker) LinkerdConfig() *l5dcharts.Values {\n\treturn hc.linkerdConfig\n}\n\nfunc (hc *HealthChecker) runCheck(category *Category, c *Checker, observer CheckObserver) bool {\n\tfor {\n\t\tctx, cancel := context.WithTimeout(context.Background(), RequestTimeout)\n\t\terr := c.check(ctx)\n\t\tcancel()\n\t\tvar se SkipError\n\t\tif errors.As(err, &se) {\n\t\t\tlog.Debugf(\"Skipping check: %s. Reason: %s\", c.description, se.Reason)\n\t\t\treturn true\n\t\t}\n\n\t\tcheckResult := &CheckResult{\n\t\t\tCategory:    category.ID,\n\t\t\tDescription: c.description,\n\t\t\tWarning:     c.warning,\n\t\t\tHintURL:     fmt.Sprintf(\"%s%s\", category.hintBaseURL, c.hintAnchor),\n\t\t}\n\t\tvar vs VerboseSuccess\n\t\tif errors.As(err, &vs) {\n\t\t\tcheckResult.Description = fmt.Sprintf(\"%s\\n%s\", checkResult.Description, vs.Message)\n\t\t} else if err != nil {\n\t\t\tcheckResult.Err = CategoryError{category.ID, err}\n\t\t}\n\n\t\tif checkResult.Err != nil && time.Now().Before(c.retryDeadline) {\n\t\t\tcheckResult.Retry = true\n\t\t\tif !c.surfaceErrorOnRetry {\n\t\t\t\tcheckResult.Err = errors.New(\"waiting for check to complete\")\n\t\t\t}\n\t\t\tlog.Debugf(\"Retrying on error: %s\", err)\n\n\t\t\tobserver(checkResult)\n\t\t\ttime.Sleep(retryWindow)\n\t\t\tcontinue\n\t\t}\n\n\t\tobserver(checkResult)\n\t\treturn checkResult.Err == nil\n\t}\n}\n\nfunc controlPlaneComponentsSelector() string {\n\treturn fmt.Sprintf(\"%s,!%s\", k8s.ControllerNSLabel, LinkerdCNIResourceLabel)\n}\n\n// KubeAPIClient returns a fully configured k8s API client. This client is\n// only configured if the KubernetesAPIChecks are configured and run first.\nfunc (hc *HealthChecker) KubeAPIClient() *k8s.KubernetesAPI {\n\treturn hc.kubeAPI\n}\n\n// UUID returns the UUID of the installation\nfunc (hc *HealthChecker) UUID() string {\n\treturn hc.uuid\n}\n\nfunc (hc *HealthChecker) checkLinkerdConfigConfigMap(ctx context.Context) (string, *l5dcharts.Values, error) {\n\tconfigMap, values, err := FetchCurrentConfiguration(ctx, hc.kubeAPI, hc.ControlPlaneNamespace)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\treturn string(configMap.GetUID()), values, nil\n}\n\n// Checks whether the configuration of the linkerd-identity-issuer is correct. This means:\n// 1. There is a config map present with identity context\n// 2. The scheme in the identity context corresponds to the format of the issuer secret\n// 3. The trust anchors (if scheme == kubernetes.io/tls) in the secret equal the ones in config\n// 4. The certs and key are parsable\nfunc (hc *HealthChecker) checkCertificatesConfig(ctx context.Context) (*tls.Cred, []*x509.Certificate, error) {\n\t_, values, err := FetchCurrentConfiguration(ctx, hc.kubeAPI, hc.ControlPlaneNamespace)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tvar data *issuercerts.IssuerCertData\n\n\tif values.Identity.Issuer.Scheme == \"\" || values.Identity.Issuer.Scheme == k8s.IdentityIssuerSchemeLinkerd {\n\t\tdata, err = issuercerts.FetchIssuerData(ctx, hc.kubeAPI, values.IdentityTrustAnchorsPEM, hc.ControlPlaneNamespace)\n\t} else {\n\t\tdata, err = issuercerts.FetchExternalIssuerData(ctx, hc.kubeAPI, hc.ControlPlaneNamespace)\n\t}\n\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tissuerCreds, err := tls.ValidateAndCreateCreds(data.IssuerCrt, data.IssuerKey)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tanchors, err := tls.DecodePEMCertificates(data.TrustAnchors)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn issuerCreds, anchors, nil\n}\n\n// FetchCurrentConfiguration retrieves the current Linkerd configuration\nfunc FetchCurrentConfiguration(ctx context.Context, k kubernetes.Interface, controlPlaneNamespace string) (*corev1.ConfigMap, *l5dcharts.Values, error) {\n\t// Get the linkerd-config values if present.\n\tconfigMap, err := config.FetchLinkerdConfigMap(ctx, k, controlPlaneNamespace)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\trawValues := configMap.Data[\"values\"]\n\tif rawValues == \"\" {\n\t\treturn configMap, nil, nil\n\t}\n\n\t// Convert into latest values, where global field is removed.\n\trawValuesBytes, err := config.RemoveGlobalFieldIfPresent([]byte(rawValues))\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\trawValues = string(rawValuesBytes)\n\tvar fullValues l5dcharts.Values\n\n\terr = yaml.Unmarshal([]byte(rawValues), &fullValues)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn configMap, &fullValues, nil\n}\n\nfunc (hc *HealthChecker) fetchProxyInjectorCaBundle(ctx context.Context) ([]*x509.Certificate, error) {\n\tmwh, err := hc.getProxyInjectorMutatingWebhook(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcaBundle, err := tls.DecodePEMCertificates(string(mwh.ClientConfig.CABundle))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn caBundle, nil\n}\n\nfunc (hc *HealthChecker) fetchWebhookCaBundle(ctx context.Context, webhook string) ([]*x509.Certificate, error) {\n\tvwc, err := hc.kubeAPI.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, webhook, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(vwc.Webhooks) != 1 {\n\t\treturn nil, fmt.Errorf(\"expected 1 webhooks, found %d\", len(vwc.Webhooks))\n\t}\n\n\tcaBundle, err := tls.DecodePEMCertificates(string(vwc.Webhooks[0].ClientConfig.CABundle))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn caBundle, nil\n}\n\n// FetchTrustBundle retrieves the ca-bundle from the config-map linkerd-identity-trust-roots\nfunc FetchTrustBundle(ctx context.Context, kubeAPI k8s.KubernetesAPI, controlPlaneNamespace string) (string, error) {\n\tconfigMap, err := kubeAPI.CoreV1().ConfigMaps(controlPlaneNamespace).Get(ctx, \"linkerd-identity-trust-roots\", metav1.GetOptions{})\n\n\treturn configMap.Data[\"ca-bundle.crt\"], err\n}\n\n// FetchCredsFromSecret retrieves the TLS creds given a secret name\nfunc (hc *HealthChecker) FetchCredsFromSecret(ctx context.Context, namespace string, secretName string) (*tls.Cred, error) {\n\tsecret, err := hc.kubeAPI.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcrt, ok := secret.Data[certKeyName]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"key %s needs to exist in secret %s\", certKeyName, secretName)\n\t}\n\n\tkey, ok := secret.Data[keyKeyName]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"key %s needs to exist in secret %s\", keyKeyName, secretName)\n\t}\n\n\tcred, err := tls.ValidateAndCreateCreds(string(crt), string(key))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cred, nil\n}\n\n// FetchCredsFromOldSecret function can be removed in later versions, once either all webhook secrets are recreated for each update\n// (see https://github.com/linkerd/linkerd2/issues/4813)\n// or later releases are only expected to update from the new names.\nfunc (hc *HealthChecker) FetchCredsFromOldSecret(ctx context.Context, namespace string, secretName string) (*tls.Cred, error) {\n\tsecret, err := hc.kubeAPI.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcrt, ok := secret.Data[certOldKeyName]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"key %s needs to exist in secret %s\", certOldKeyName, secretName)\n\t}\n\n\tkey, ok := secret.Data[keyOldKeyName]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"key %s needs to exist in secret %s\", keyOldKeyName, secretName)\n\t}\n\n\tcred, err := tls.ValidateAndCreateCreds(string(crt), string(key))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cred, nil\n}\n\n// CheckNamespace checks whether the given namespace exists, and returns an\n// error if it does not match `shouldExist`.\nfunc (hc *HealthChecker) CheckNamespace(ctx context.Context, namespace string, shouldExist bool) error {\n\texists, err := hc.kubeAPI.NamespaceExists(ctx, namespace)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif shouldExist && !exists {\n\t\treturn fmt.Errorf(\"The \\\"%s\\\" namespace does not exist\", namespace)\n\t}\n\tif !shouldExist && exists {\n\t\treturn fmt.Errorf(\"The \\\"%s\\\" namespace already exists\", namespace)\n\t}\n\treturn nil\n}\n\nfunc (hc *HealthChecker) checkClusterNetworks(ctx context.Context) error {\n\tnodes, err := hc.kubeAPI.GetNodes(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclusterNetworks := strings.Split(hc.linkerdConfig.ClusterNetworks, \",\")\n\tclusterIPNets := make([]*net.IPNet, len(clusterNetworks))\n\tfor i, clusterNetwork := range clusterNetworks {\n\t\t_, clusterIPNets[i], err = net.ParseCIDR(clusterNetwork)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tvar badPodCIDRS []string\n\tvar podCIDRExists bool\n\tfor _, node := range nodes {\n\t\tpodCIDR := node.Spec.PodCIDR\n\t\tif podCIDR == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tpodCIDRExists = true\n\t\tpodIP, podIPNet, err := net.ParseCIDR(podCIDR)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\texists := cluterNetworksContainCIDR(clusterIPNets, podIPNet, podIP)\n\t\tif !exists {\n\t\t\tbadPodCIDRS = append(badPodCIDRS, podCIDR)\n\t\t}\n\t}\n\t// If none of the nodes exposed a podCIDR then we cannot verify the clusterNetworks.\n\tif !podCIDRExists {\n\t\t// DigitalOcean for example, doesn't expose spec.podCIDR (#6398)\n\t\treturn SkipError{Reason: podCIDRUnavailableSkipReason}\n\t}\n\tif len(badPodCIDRS) > 0 {\n\t\tsort.Strings(badPodCIDRS)\n\t\treturn fmt.Errorf(\"node has podCIDR(s) %v which are not contained in the Linkerd clusterNetworks.\\n\\tTry installing linkerd via --set clusterNetworks=\\\"%s\\\"\",\n\t\t\tbadPodCIDRS, strings.Join(badPodCIDRS, \"\\\\,\"))\n\t}\n\treturn nil\n}\n\nfunc cluterNetworksContainCIDR(clusterIPNets []*net.IPNet, podIPNet *net.IPNet, podIP net.IP) bool {\n\tfor _, clusterIPNet := range clusterIPNets {\n\t\tclusterIPMaskOnes, _ := clusterIPNet.Mask.Size()\n\t\tpodCIDRMaskOnes, _ := podIPNet.Mask.Size()\n\t\tif clusterIPNet.Contains(podIP) && podCIDRMaskOnes >= clusterIPMaskOnes {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc clusterNetworksContainIP(clusterIPNets []*net.IPNet, ip string) bool {\n\tfor _, clusterIPNet := range clusterIPNets {\n\t\tif clusterIPNet.Contains(net.ParseIP(ip)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (hc *HealthChecker) checkClusterNetworksContainAllPods(ctx context.Context) error {\n\tclusterNetworks := strings.Split(hc.linkerdConfig.ClusterNetworks, \",\")\n\tclusterIPNets := make([]*net.IPNet, len(clusterNetworks))\n\tvar err error\n\tfor i, clusterNetwork := range clusterNetworks {\n\t\t_, clusterIPNets[i], err = net.ParseCIDR(clusterNetwork)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tpods, err := hc.kubeAPI.CoreV1().Pods(corev1.NamespaceAll).List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, pod := range pods.Items {\n\t\tif pod.Spec.HostNetwork {\n\t\t\tcontinue\n\t\t}\n\t\tif len(pod.Status.PodIP) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif !clusterNetworksContainIP(clusterIPNets, pod.Status.PodIP) {\n\t\t\treturn fmt.Errorf(\"the Linkerd clusterNetworks [%q] do not include pod %s/%s (%s)\", hc.linkerdConfig.ClusterNetworks, pod.Namespace, pod.Name, pod.Status.PodIP)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (hc *HealthChecker) checkClusterNetworksContainAllServices(ctx context.Context) error {\n\tclusterNetworks := strings.Split(hc.linkerdConfig.ClusterNetworks, \",\")\n\tclusterIPNets := make([]*net.IPNet, len(clusterNetworks))\n\tvar err error\n\tfor i, clusterNetwork := range clusterNetworks {\n\t\t_, clusterIPNets[i], err = net.ParseCIDR(clusterNetwork)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tsvcs, err := hc.kubeAPI.CoreV1().Services(corev1.NamespaceAll).List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, svc := range svcs.Items {\n\t\tclusterIP := svc.Spec.ClusterIP\n\t\tif clusterIP != \"\" && clusterIP != \"None\" && !clusterNetworksContainIP(clusterIPNets, svc.Spec.ClusterIP) {\n\t\t\treturn fmt.Errorf(\"the Linkerd clusterNetworks [%q] do not include svc %s/%s (%s)\", hc.linkerdConfig.ClusterNetworks, svc.Namespace, svc.Name, svc.Spec.ClusterIP)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (hc *HealthChecker) expectedRBACNames() []string {\n\treturn []string{\n\t\tfmt.Sprintf(\"linkerd-%s-identity\", hc.ControlPlaneNamespace),\n\t\tfmt.Sprintf(\"linkerd-%s-proxy-injector\", hc.ControlPlaneNamespace),\n\t}\n}\n\nfunc (hc *HealthChecker) checkClusterRoles(ctx context.Context, shouldExist bool, expectedNames []string, labelSelector string) error {\n\treturn CheckClusterRoles(ctx, hc.kubeAPI, shouldExist, expectedNames, labelSelector)\n}\n\n// CheckClusterRoles checks that the expected ClusterRoles exist.\nfunc CheckClusterRoles(ctx context.Context, kubeAPI *k8s.KubernetesAPI, shouldExist bool, expectedNames []string, labelSelector string) error {\n\toptions := metav1.ListOptions{\n\t\tLabelSelector: labelSelector,\n\t}\n\tcrList, err := kubeAPI.RbacV1().ClusterRoles().List(ctx, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tobjects := []runtime.Object{}\n\n\tfor _, item := range crList.Items {\n\t\titem := item // pin\n\t\tobjects = append(objects, &item)\n\t}\n\n\treturn checkResources(\"ClusterRoles\", objects, expectedNames, shouldExist)\n}\n\nfunc (hc *HealthChecker) checkClusterRoleBindings(ctx context.Context, shouldExist bool, expectedNames []string, labelSelector string) error {\n\treturn CheckClusterRoleBindings(ctx, hc.kubeAPI, shouldExist, expectedNames, labelSelector)\n}\n\n// CheckClusterRoleBindings checks that the expected ClusterRoleBindings exist.\nfunc CheckClusterRoleBindings(ctx context.Context, kubeAPI *k8s.KubernetesAPI, shouldExist bool, expectedNames []string, labelSelector string) error {\n\toptions := metav1.ListOptions{\n\t\tLabelSelector: labelSelector,\n\t}\n\tcrbList, err := kubeAPI.RbacV1().ClusterRoleBindings().List(ctx, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tobjects := []runtime.Object{}\n\n\tfor _, item := range crbList.Items {\n\t\titem := item // pin\n\t\tobjects = append(objects, &item)\n\t}\n\n\treturn checkResources(\"ClusterRoleBindings\", objects, expectedNames, shouldExist)\n}\n\n// CheckConfigMaps checks that the expected ConfigMaps  exist.\nfunc CheckConfigMaps(ctx context.Context, kubeAPI *k8s.KubernetesAPI, namespace string, shouldExist bool, expectedNames []string, labelSelector string) error {\n\toptions := metav1.ListOptions{\n\t\tLabelSelector: labelSelector,\n\t}\n\tcrbList, err := kubeAPI.CoreV1().ConfigMaps(namespace).List(ctx, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tobjects := []runtime.Object{}\n\n\tfor _, item := range crbList.Items {\n\t\titem := item // pin\n\t\tobjects = append(objects, &item)\n\t}\n\n\treturn checkResources(\"ConfigMaps\", objects, expectedNames, shouldExist)\n}\n\nfunc (hc *HealthChecker) isHA() bool {\n\treturn hc.linkerdConfig.HighAvailability\n}\n\nfunc (hc *HealthChecker) isHeartbeatDisabled() bool {\n\treturn hc.linkerdConfig.DisableHeartBeat\n}\n\nfunc (hc *HealthChecker) checkServiceAccounts(ctx context.Context, saNames []string, ns, labelSelector string) error {\n\treturn CheckServiceAccounts(ctx, hc.kubeAPI, saNames, ns, labelSelector)\n}\n\n// CheckServiceAccounts check for serviceaccounts\nfunc CheckServiceAccounts(ctx context.Context, api *k8s.KubernetesAPI, saNames []string, ns, labelSelector string) error {\n\toptions := metav1.ListOptions{\n\t\tLabelSelector: labelSelector,\n\t}\n\tsaList, err := api.CoreV1().ServiceAccounts(ns).List(ctx, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tobjects := []runtime.Object{}\n\n\tfor _, item := range saList.Items {\n\t\titem := item // pin\n\t\tobjects = append(objects, &item)\n\t}\n\n\treturn checkResources(\"ServiceAccounts\", objects, saNames, true)\n}\n\n// CheckIfLinkerdExists checks if Linkerd exists\nfunc CheckIfLinkerdExists(ctx context.Context, kubeAPI *k8s.KubernetesAPI, controlPlaneNamespace string) (bool, error) {\n\t_, err := kubeAPI.CoreV1().Namespaces().Get(ctx, controlPlaneNamespace, metav1.GetOptions{})\n\tif err != nil {\n\t\tif kerrors.IsNotFound(err) {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, err\n\t}\n\n\t_, _, err = FetchCurrentConfiguration(ctx, kubeAPI, controlPlaneNamespace)\n\tif err != nil {\n\t\tif kerrors.IsNotFound(err) {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (hc *HealthChecker) getProxyInjectorMutatingWebhook(ctx context.Context) (*admissionRegistration.MutatingWebhook, error) {\n\tmwc, err := hc.kubeAPI.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, k8s.ProxyInjectorWebhookConfigName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(mwc.Webhooks) != 1 {\n\t\treturn nil, fmt.Errorf(\"expected 1 webhooks, found %d\", len(mwc.Webhooks))\n\t}\n\treturn &mwc.Webhooks[0], nil\n}\n\nfunc (hc *HealthChecker) checkMutatingWebhookConfigurations(ctx context.Context, shouldExist bool) error {\n\toptions := metav1.ListOptions{\n\t\tLabelSelector: controlPlaneComponentsSelector(),\n\t}\n\tmwc, err := hc.kubeAPI.AdmissionregistrationV1().MutatingWebhookConfigurations().List(ctx, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tobjects := []runtime.Object{}\n\tfor _, item := range mwc.Items {\n\t\titem := item // pin\n\t\tobjects = append(objects, &item)\n\t}\n\n\treturn checkResources(\"MutatingWebhookConfigurations\", objects, []string{k8s.ProxyInjectorWebhookConfigName}, shouldExist)\n}\n\nfunc (hc *HealthChecker) checkValidatingWebhookConfigurations(ctx context.Context, shouldExist bool) error {\n\toptions := metav1.ListOptions{\n\t\tLabelSelector: controlPlaneComponentsSelector(),\n\t}\n\tvwc, err := hc.kubeAPI.AdmissionregistrationV1().ValidatingWebhookConfigurations().List(ctx, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tobjects := []runtime.Object{}\n\tfor _, item := range vwc.Items {\n\t\titem := item // pin\n\t\tobjects = append(objects, &item)\n\t}\n\n\treturn checkResources(\"ValidatingWebhookConfigurations\", objects, []string{k8s.SPValidatorWebhookConfigName}, shouldExist)\n}\n\n// CheckCustomResourceDefinitions checks that all of the Linkerd CRDs are\n// installed on the cluster.\nfunc CheckCustomResourceDefinitions(ctx context.Context, k8sAPI *k8s.KubernetesAPI, expectedCRDManifests string) error {\n\n\tcrdYamls := strings.Split(expectedCRDManifests, \"\\n---\\n\")\n\tcrdVersions := []struct{ name, version string }{}\n\tfor _, crdYaml := range crdYamls {\n\t\tvar crd apiextv1.CustomResourceDefinition\n\t\terr := yaml.Unmarshal([]byte(crdYaml), &crd)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(crd.Spec.Versions) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tversionIndex := len(crd.Spec.Versions) - 1\n\t\tcrdVersions = append(crdVersions, struct{ name, version string }{\n\t\t\tname:    crd.Name,\n\t\t\tversion: crd.Spec.Versions[versionIndex].Name,\n\t\t})\n\t}\n\n\terrMsgs := []string{}\n\n\tfor _, crdVersion := range crdVersions {\n\t\tname := crdVersion.name\n\t\tversion := crdVersion.version\n\n\t\tcrd, err := k8sAPI.Apiextensions.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, name, metav1.GetOptions{})\n\t\tif err != nil && kerrors.IsNotFound(err) {\n\t\t\terrMsgs = append(errMsgs, fmt.Sprintf(\"missing %s\", name))\n\t\t\tcontinue\n\t\t} else if err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !crdHasVersion(crd, version) {\n\t\t\terrMsgs = append(errMsgs, fmt.Sprintf(\"CRD %s is missing version %s\", name, version))\n\t\t}\n\t}\n\tif len(errMsgs) > 0 {\n\t\treturn errors.New(strings.Join(errMsgs, \", \"))\n\t}\n\treturn nil\n}\n\nfunc crdHasVersion(crd *apiextv1.CustomResourceDefinition, version string) bool {\n\tfor _, crdVersion := range crd.Spec.Versions {\n\t\tif crdVersion.Name == version {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// CheckNodesHaveNonDockerRuntime checks that each node has a non-Docker\n// runtime. This check is only called if proxyInit is not running as root\n// which is a problem for clusters with a Docker container runtime.\nfunc CheckNodesHaveNonDockerRuntime(ctx context.Context, k8sAPI *k8s.KubernetesAPI) error {\n\thasDockerNodes := false\n\tcontinueToken := \"\"\n\tfor {\n\t\tnodes, err := k8sAPI.CoreV1().Nodes().List(ctx, metav1.ListOptions{Continue: continueToken})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tcontinueToken = nodes.Continue\n\t\tfor _, node := range nodes.Items {\n\t\t\tcrv := node.Status.NodeInfo.ContainerRuntimeVersion\n\t\t\tif strings.HasPrefix(crv, \"docker:\") {\n\t\t\t\thasDockerNodes = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif continueToken == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\tif hasDockerNodes {\n\t\treturn fmt.Errorf(\"there are nodes using the docker container runtime and proxy-init container must run as root user.\\ntry installing linkerd via --set proxyInit.runAsRoot=true\")\n\t}\n\treturn nil\n}\n\n// MeshedPodIdentityData contains meshed pod details + trust anchors of the proxy\ntype MeshedPodIdentityData struct {\n\tName      string\n\tNamespace string\n\tAnchors   string\n}\n\n// GetMeshedPodsIdentityData obtains the identity data (trust anchors) for all meshed pods\nfunc GetMeshedPodsIdentityData(ctx context.Context, api kubernetes.Interface, dataPlaneNamespace string) ([]MeshedPodIdentityData, error) {\n\tpodList, err := api.CoreV1().Pods(dataPlaneNamespace).List(ctx, metav1.ListOptions{LabelSelector: k8s.ControllerNSLabel})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(podList.Items) == 0 {\n\t\treturn nil, nil\n\t}\n\tpods := []MeshedPodIdentityData{}\n\tfor _, pod := range podList.Items {\n\t\tcontainers := append(pod.Spec.InitContainers, pod.Spec.Containers...)\n\t\tfor _, containerSpec := range containers {\n\t\t\tif containerSpec.Name != k8s.ProxyContainerName {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor _, envVar := range containerSpec.Env {\n\t\t\t\tif envVar.Name != identity.EnvTrustAnchors {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tpods = append(pods, MeshedPodIdentityData{\n\t\t\t\t\tpod.Name, pod.Namespace, strings.TrimSpace(envVar.Value),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\treturn pods, nil\n}\n\nfunc (hc *HealthChecker) checkDataPlaneProxiesCertificate(ctx context.Context) error {\n\treturn checkPodsProxiesCertificate(ctx, *hc.kubeAPI, hc.DataPlaneNamespace, hc.ControlPlaneNamespace)\n}\n\nfunc checkPodsProxiesCertificate(ctx context.Context, kubeAPI k8s.KubernetesAPI, targetNamespace, controlPlaneNamespace string) error {\n\tmeshedPods, err := GetMeshedPodsIdentityData(ctx, kubeAPI, targetNamespace)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttrustAnchorsPem, err := FetchTrustBundle(ctx, kubeAPI, controlPlaneNamespace)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\toffendingPods := []string{}\n\tfor _, pod := range meshedPods {\n\t\t// Skip control plane pods since they load their trust anchors from the linkerd-identity-trust-anchors configmap.\n\t\tif pod.Namespace == controlPlaneNamespace {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.TrimSpace(pod.Anchors) != strings.TrimSpace(trustAnchorsPem) {\n\t\t\tif targetNamespace == \"\" {\n\t\t\t\toffendingPods = append(offendingPods, fmt.Sprintf(\"* %s/%s\", pod.Namespace, pod.Name))\n\t\t\t} else {\n\t\t\t\toffendingPods = append(offendingPods, fmt.Sprintf(\"* %s\", pod.Name))\n\t\t\t}\n\t\t}\n\t}\n\tif len(offendingPods) == 0 {\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"Some pods do not have the current trust bundle and must be restarted:\\n\\t%s\", strings.Join(offendingPods, \"\\n\\t\"))\n}\n\nfunc checkResources(resourceName string, objects []runtime.Object, expectedNames []string, shouldExist bool) error {\n\tif !shouldExist {\n\t\tif len(objects) > 0 {\n\t\t\tresources := []Resource{}\n\t\t\tfor _, obj := range objects {\n\t\t\t\tm, err := meta.Accessor(obj)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tres := Resource{name: m.GetName()}\n\t\t\t\tgvks, _, err := k8s.ObjectKinds(obj)\n\t\t\t\tif err == nil && len(gvks) > 0 {\n\t\t\t\t\tres.groupVersionKind = gvks[0]\n\t\t\t\t}\n\t\t\t\tresources = append(resources, res)\n\t\t\t}\n\t\t\treturn ResourceError{resourceName, resources}\n\t\t}\n\t\treturn nil\n\t}\n\n\texpected := map[string]bool{}\n\tfor _, name := range expectedNames {\n\t\texpected[name] = false\n\t}\n\n\tfor _, obj := range objects {\n\t\tmetaObj, err := meta.Accessor(obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif _, ok := expected[metaObj.GetName()]; ok {\n\t\t\texpected[metaObj.GetName()] = true\n\t\t}\n\t}\n\n\tmissing := []string{}\n\tfor name, found := range expected {\n\t\tif !found {\n\t\t\tmissing = append(missing, name)\n\t\t}\n\t}\n\tif len(missing) > 0 {\n\t\tsort.Strings(missing)\n\t\treturn fmt.Errorf(\"missing %s: %s\", resourceName, strings.Join(missing, \", \"))\n\t}\n\n\treturn nil\n}\n\n// Check if there's a pod with the \"opaque ports\" annotation defined but a\n// service selecting the aforementioned pod doesn't define it\nfunc (hc *HealthChecker) checkMisconfiguredOpaquePortAnnotations(ctx context.Context) error {\n\t// Initialize and sync the kubernetes API\n\t// This is used instead of `hc.kubeAPI` to limit multiple k8s API requests\n\t// and use the caching logic in the shared informers\n\t// TODO: move the shared informer code out of `controller/`, and into `pkg` to simplify the dependency tree.\n\tkubeAPI := controllerK8s.NewClusterScopedAPI(hc.kubeAPI.Interface, nil, nil, \"local\", controllerK8s.Endpoint, controllerK8s.Pod, controllerK8s.Svc)\n\tkubeAPI.Sync(ctx.Done())\n\n\tservices, err := kubeAPI.Svc().Lister().Services(hc.DataPlaneNamespace).List(labels.Everything())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar errStrings []string\n\tfor _, service := range services {\n\t\tif service.Spec.ClusterIP == \"None\" {\n\t\t\t// skip headless services; they're handled differently\n\t\t\tcontinue\n\t\t}\n\n\t\tendpoints, err := kubeAPI.Endpoint().Lister().Endpoints(service.Namespace).Get(service.Name)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tpods, err := getEndpointsPods(endpoints, kubeAPI, service.Namespace)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor pod := range pods {\n\t\t\terr := misconfiguredOpaqueAnnotation(service, pod)\n\t\t\tif err != nil {\n\t\t\t\terrStrings = append(errStrings, fmt.Sprintf(\"\\t* %s\", err.Error()))\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(errStrings) >= 1 {\n\t\treturn errors.New(strings.Join(errStrings, \"\\n    \"))\n\t}\n\n\treturn nil\n}\n\n// getEndpointsPods takes a collection of endpoints and returns the set of all\n// the pods that they target.\nfunc getEndpointsPods(endpoints *corev1.Endpoints, kubeAPI *controllerK8s.API, namespace string) (map[*corev1.Pod]struct{}, error) {\n\tpods := make(map[*corev1.Pod]struct{})\n\tfor _, subset := range endpoints.Subsets {\n\t\tfor _, addr := range subset.Addresses {\n\t\t\tif addr.TargetRef != nil && addr.TargetRef.Kind == \"Pod\" {\n\t\t\t\tpod, err := kubeAPI.Pod().Lister().Pods(namespace).Get(addr.TargetRef.Name)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif _, ok := pods[pod]; !ok {\n\t\t\t\t\tpods[pod] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn pods, nil\n}\n\nfunc misconfiguredOpaqueAnnotation(service *corev1.Service, pod *corev1.Pod) error {\n\tvar svcPorts, podPorts []string\n\tif v, ok := service.Annotations[k8s.ProxyOpaquePortsAnnotation]; ok {\n\t\tsvcPorts = strings.Split(v, \",\")\n\t}\n\tif v, ok := pod.Annotations[k8s.ProxyOpaquePortsAnnotation]; ok {\n\t\tpodPorts = strings.Split(v, \",\")\n\t}\n\n\t// First loop through the services opaque ports and assert that if the pod\n\t// exposes a port that is targeted by one of these ports, then it is\n\t// marked as opaque on the pod.\n\tfor _, p := range svcPorts {\n\t\tport, err := strconv.Atoi(p)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to convert %s to port number for pod %s\", p, pod.Name)\n\t\t}\n\t\terr = checkPodPorts(service, pod, podPorts, port)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Next loop through the pod's opaque ports and assert that if one of\n\t// the ports is targeted by a service port, then it is marked as opaque\n\t// on the service.\n\tfor _, p := range podPorts {\n\t\tif util.ContainsString(p, svcPorts) {\n\t\t\t// The service exposes p and is marked as opaque.\n\t\t\tcontinue\n\t\t}\n\t\tport, err := strconv.Atoi(p)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to convert %s to port number for pod %s\", p, pod.Name)\n\t\t}\n\n\t\t// p is marked as opaque on the pod, but the service that selects it\n\t\t// does not have it marked as opaque. We first check if the service\n\t\t// exposes it as a service or integer targetPort.\n\t\tok, err := checkServiceIntPorts(service, svcPorts, port)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif ok {\n\t\t\t// The service targets the port as an integer and is marked as\n\t\t\t// opaque so continue checking other pod ports.\n\t\t\tcontinue\n\t\t}\n\n\t\t// The service does not expose p as a service or integer targetPort.\n\t\t// We now check if it targets it as a named port, and if so, that the\n\t\t// service port is marked as opaque.\n\t\terr = checkServiceNamePorts(service, pod, port, svcPorts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc checkPodPorts(service *corev1.Service, pod *corev1.Pod, podPorts []string, port int) error {\n\tfor _, sp := range service.Spec.Ports {\n\t\tif int(sp.Port) == port {\n\t\t\tfor _, c := range append(pod.Spec.InitContainers, pod.Spec.Containers...) {\n\t\t\t\tfor _, cp := range c.Ports {\n\t\t\t\t\tif cp.ContainerPort == sp.TargetPort.IntVal || cp.Name == sp.TargetPort.StrVal {\n\t\t\t\t\t\t// The pod exposes a container port that would be\n\t\t\t\t\t\t// targeted by this service port\n\t\t\t\t\t\tvar strPort string\n\t\t\t\t\t\tif sp.TargetPort.Type == 0 {\n\t\t\t\t\t\t\tstrPort = strconv.Itoa(int(sp.TargetPort.IntVal))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tstrPort = strconv.Itoa(int(cp.ContainerPort))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif util.ContainsString(strPort, podPorts) {\n\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn fmt.Errorf(\"service %s expects target port %s to be opaque; add it to pod %s %s annotation\", service.Name, strPort, pod.Name, k8s.ProxyOpaquePortsAnnotation)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc checkServiceIntPorts(service *corev1.Service, svcPorts []string, port int) (bool, error) {\n\tfor _, p := range service.Spec.Ports {\n\t\tif p.TargetPort.Type == 0 && p.TargetPort.IntVal == 0 {\n\t\t\tif int(p.Port) == port {\n\t\t\t\t// The service does not have a target port, so its service\n\t\t\t\t// port should be marked as opaque.\n\t\t\t\treturn false, fmt.Errorf(\"service %s targets the opaque port %d; add it to its %s annotation\", service.Name, port, k8s.ProxyOpaquePortsAnnotation)\n\t\t\t}\n\t\t}\n\t\tif int(p.TargetPort.IntVal) == port {\n\t\t\tsvcPort := strconv.Itoa(int(p.Port))\n\t\t\tif util.ContainsString(svcPort, svcPorts) {\n\t\t\t\t// The service exposes svcPort which targets p and svcPort\n\t\t\t\t// is properly as opaque.\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t\treturn false, fmt.Errorf(\"service %s targets the opaque port %d through %d; add %d to its %s annotation\", service.Name, port, p.Port, p.Port, k8s.ProxyOpaquePortsAnnotation)\n\t\t}\n\t}\n\treturn false, nil\n}\n\nfunc checkServiceNamePorts(service *corev1.Service, pod *corev1.Pod, port int, svcPorts []string) error {\n\tfor _, p := range service.Spec.Ports {\n\t\tif p.TargetPort.StrVal == \"\" {\n\t\t\t// The target port is not named so there is no named container\n\t\t\t// port to check.\n\t\t\tcontinue\n\t\t}\n\t\tfor _, c := range append(pod.Spec.InitContainers, pod.Spec.Containers...) {\n\t\t\tfor _, cp := range c.Ports {\n\t\t\t\tif int(cp.ContainerPort) == port {\n\t\t\t\t\t// This is the containerPort that maps to the opaque port\n\t\t\t\t\t// we are currently checking.\n\t\t\t\t\tif cp.Name == p.TargetPort.StrVal {\n\t\t\t\t\t\tsvcPort := strconv.Itoa(int(p.Port))\n\t\t\t\t\t\tif util.ContainsString(svcPort, svcPorts) {\n\t\t\t\t\t\t\t// The service targets the container port by name\n\t\t\t\t\t\t\t// and is marked as opaque.\n\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn fmt.Errorf(\"service %s targets the opaque port %s through %d; add %d to its %s annotation\", service.Name, cp.Name, p.Port, p.Port, k8s.ProxyOpaquePortsAnnotation)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// GetDataPlanePods returns all the pods with data plane\nfunc (hc *HealthChecker) GetDataPlanePods(ctx context.Context) ([]corev1.Pod, error) {\n\tselector := fmt.Sprintf(\"%s=%s\", k8s.ControllerNSLabel, hc.ControlPlaneNamespace)\n\tpodList, err := hc.kubeAPI.CoreV1().Pods(hc.DataPlaneNamespace).List(ctx, metav1.ListOptions{LabelSelector: selector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn podList.Items, nil\n}\n\n// GetServices returns all services within data plane namespace\nfunc (hc *HealthChecker) GetServices(ctx context.Context) ([]corev1.Service, error) {\n\tsvcList, err := hc.kubeAPI.CoreV1().Services(hc.DataPlaneNamespace).List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn svcList.Items, nil\n}\n\nfunc (hc *HealthChecker) checkCanCreate(ctx context.Context, namespace, group, version, resource string) error {\n\treturn CheckCanPerformAction(ctx, hc.kubeAPI, \"create\", namespace, group, version, resource)\n}\n\nfunc (hc *HealthChecker) checkCanCreateNonNamespacedResources(ctx context.Context) error {\n\tvar errs []string\n\tdryRun := metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}\n\n\t// Iterate over all resources in install manifest\n\tinstallManifestReader := strings.NewReader(hc.Options.InstallManifest)\n\tyamlReader := yamlDecoder.NewYAMLReader(bufio.NewReader(installManifestReader))\n\tfor {\n\t\t// Read single object YAML\n\t\tobjYAML, err := yamlReader.Read()\n\t\tif err != nil {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"error reading install manifest: %w\", err)\n\t\t}\n\n\t\t// Create unstructured object from YAML\n\t\tobjMap := map[string]interface{}{}\n\t\terr = yaml.Unmarshal(objYAML, &objMap)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error unmarshaling yaml object %s: %w\", objYAML, err)\n\t\t}\n\t\tif len(objMap) == 0 {\n\t\t\t// Ignore header blocks with only comments\n\t\t\tcontinue\n\t\t}\n\t\tobj := &unstructured.Unstructured{Object: objMap}\n\n\t\t// Skip namespaced resources (dry-run requires namespace to exist)\n\t\tif obj.GetNamespace() != \"\" {\n\t\t\tcontinue\n\t\t}\n\t\t// Attempt to create resource using dry-run\n\t\tresource, _ := meta.UnsafeGuessKindToResource(obj.GroupVersionKind())\n\t\t_, err = hc.kubeAPI.DynamicClient.Resource(resource).Create(ctx, obj, dryRun)\n\t\tif err != nil {\n\t\t\terrs = append(errs, fmt.Sprintf(\"cannot create %s/%s: %v\", obj.GetKind(), obj.GetName(), err))\n\t\t}\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn errors.New(strings.Join(errs, \"\\n    \"))\n\t}\n\treturn nil\n}\n\nfunc (hc *HealthChecker) checkCanGet(ctx context.Context, namespace, group, version, resource string) error {\n\treturn CheckCanPerformAction(ctx, hc.kubeAPI, \"get\", namespace, group, version, resource)\n}\n\nfunc (hc *HealthChecker) checkExtensionAPIServerAuthentication(ctx context.Context) error {\n\tif hc.kubeAPI == nil {\n\t\treturn fmt.Errorf(\"unexpected error: Kubernetes ClientSet not initialized\")\n\t}\n\tm, err := hc.kubeAPI.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(ctx, k8s.ExtensionAPIServerAuthenticationConfigMapName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tif v, exists := m.Data[k8s.ExtensionAPIServerAuthenticationRequestHeaderClientCAFileKey]; !exists || v == \"\" {\n\t\treturn fmt.Errorf(\"--%s is not configured\", k8s.ExtensionAPIServerAuthenticationRequestHeaderClientCAFileKey)\n\t}\n\treturn nil\n}\nfunc (hc *HealthChecker) checkClockSkew(ctx context.Context) error {\n\tif hc.kubeAPI == nil {\n\t\t// we should never get here\n\t\treturn errors.New(\"unexpected error: Kubernetes ClientSet not initialized\")\n\t}\n\n\tvar clockSkewNodes []string\n\n\tnodeList, err := hc.kubeAPI.CoreV1().Nodes().List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, node := range nodeList.Items {\n\t\tfor _, condition := range node.Status.Conditions {\n\t\t\t// we want to check only KubeletReady condition and only execute if the node is ready\n\t\t\tif condition.Type == corev1.NodeReady && condition.Status == corev1.ConditionTrue {\n\t\t\t\tsince := time.Since(condition.LastHeartbeatTime.Time)\n\t\t\t\tif (since > AllowedClockSkew) || (since < -AllowedClockSkew) {\n\t\t\t\t\tclockSkewNodes = append(clockSkewNodes, node.Name)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(clockSkewNodes) > 0 {\n\t\treturn fmt.Errorf(\"clock skew detected for node(s): %s\", strings.Join(clockSkewNodes, \", \"))\n\t}\n\n\treturn nil\n}\n\nfunc (hc *HealthChecker) checkExtensionNsLabels(ctx context.Context) error {\n\tif hc.kubeAPI == nil {\n\t\t// oops something wrong happened\n\t\treturn errors.New(\"unexpected error: Kubernetes ClientSet not initialized\")\n\t}\n\n\tnamespaces, err := hc.kubeAPI.GetAllNamespacesWithExtensionLabel(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unexpected error when retrieving namespaces: %w\", err)\n\t}\n\n\tfreq := make(map[string][]string)\n\tfor _, ns := range namespaces {\n\t\t// We can guarantee the namespace has the extension label since we used\n\t\t// a label selector when retrieving namespaces\n\t\text := ns.Labels[k8s.LinkerdExtensionLabel]\n\t\t// To make it easier to print, store already error-formatted namespace\n\t\t// in freq table\n\t\tfreq[ext] = append(freq[ext], fmt.Sprintf(\"\\t\\t* %s\", ns.Name))\n\t}\n\n\terrs := []string{}\n\tfor ext, namespaces := range freq {\n\t\tif len(namespaces) == 1 {\n\t\t\tcontinue\n\t\t}\n\t\terrs = append(errs, fmt.Sprintf(\"\\t* label \\\"%s=%s\\\" is present on more than one namespace:\\n%s\", k8s.LinkerdExtensionLabel, ext, strings.Join(namespaces, \"\\n\")))\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn errors.New(strings.Join(\n\t\t\tappend([]string{\"some extensions have invalid configuration\"}, errs...), \"\\n\"))\n\t}\n\n\treturn nil\n}\n\n// CheckRoles checks that the expected roles exist.\nfunc CheckRoles(ctx context.Context, kubeAPI *k8s.KubernetesAPI, shouldExist bool, namespace string, expectedNames []string, labelSelector string) error {\n\toptions := metav1.ListOptions{\n\t\tLabelSelector: labelSelector,\n\t}\n\tcrList, err := kubeAPI.RbacV1().Roles(namespace).List(ctx, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tobjects := []runtime.Object{}\n\n\tfor _, item := range crList.Items {\n\t\titem := item // pin\n\t\tobjects = append(objects, &item)\n\t}\n\n\treturn checkResources(\"Roles\", objects, expectedNames, shouldExist)\n}\n\n// CheckRoleBindings checks that the expected RoleBindings exist.\nfunc CheckRoleBindings(ctx context.Context, kubeAPI *k8s.KubernetesAPI, shouldExist bool, namespace string, expectedNames []string, labelSelector string) error {\n\toptions := metav1.ListOptions{\n\t\tLabelSelector: labelSelector,\n\t}\n\tcrbList, err := kubeAPI.RbacV1().RoleBindings(namespace).List(ctx, options)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tobjects := []runtime.Object{}\n\n\tfor _, item := range crbList.Items {\n\t\titem := item // pin\n\t\tobjects = append(objects, &item)\n\t}\n\n\treturn checkResources(\"RoleBindings\", objects, expectedNames, shouldExist)\n}\n\n// CheckCanPerformAction checks if a given k8s client is authorized to perform a given action.\nfunc CheckCanPerformAction(ctx context.Context, api *k8s.KubernetesAPI, verb, namespace, group, version, resource string) error {\n\tif api == nil {\n\t\t// we should never get here\n\t\treturn fmt.Errorf(\"unexpected error: Kubernetes ClientSet not initialized\")\n\t}\n\n\treturn k8s.ResourceAuthz(\n\t\tctx,\n\t\tapi,\n\t\tnamespace,\n\t\tverb,\n\t\tgroup,\n\t\tversion,\n\t\tresource,\n\t\t\"\",\n\t)\n}\n\n// getPodStatuses returns a map of all Linkerd container statuses:\n// component =>\n//\n//\tpod name =>\n//\t  container statuses\nfunc getPodStatuses(pods []corev1.Pod) map[string]map[string][]corev1.ContainerStatus {\n\tstatuses := make(map[string]map[string][]corev1.ContainerStatus)\n\n\tfor _, pod := range pods {\n\t\tif pod.Status.Phase == corev1.PodRunning && strings.HasPrefix(pod.Name, \"linkerd-\") {\n\t\t\tparts := strings.Split(pod.Name, \"-\")\n\t\t\t// All control plane pods should have a name that results in at least 4\n\t\t\t// substrings when string.Split on '-'\n\t\t\tif len(parts) >= 4 {\n\t\t\t\tname := strings.Join(parts[1:len(parts)-2], \"-\")\n\t\t\t\tif _, found := statuses[name]; !found {\n\t\t\t\t\tstatuses[name] = make(map[string][]corev1.ContainerStatus)\n\t\t\t\t}\n\t\t\t\tstatuses[name][pod.Name] = pod.Status.ContainerStatuses\n\t\t\t}\n\t\t}\n\t}\n\n\treturn statuses\n}\n\nfunc validateControlPlanePods(pods []corev1.Pod) error {\n\tstatuses := getPodStatuses(pods)\n\n\tnames := []string{\"destination\", \"identity\", \"proxy-injector\"}\n\n\tfor _, name := range names {\n\t\tpods, found := statuses[name]\n\t\tif !found {\n\t\t\treturn fmt.Errorf(\"No running pods for \\\"linkerd-%s\\\"\", name)\n\t\t}\n\t\tvar err error\n\t\tvar ready bool\n\t\tfor pod, containers := range pods {\n\t\t\tcontainersReady := true\n\t\t\tfor _, container := range containers {\n\t\t\t\tif !container.Ready {\n\t\t\t\t\t// TODO: Save this as a warning, allow check to pass but let the user\n\t\t\t\t\t// know there is at least one pod not ready. This might imply\n\t\t\t\t\t// restructuring health checks to allow individual checks to return\n\t\t\t\t\t// either fatal or warning, rather than setting this property at\n\t\t\t\t\t// compile time.\n\t\t\t\t\terr = fmt.Errorf(\"pod/%s container %s is not ready\", pod, container.Name)\n\t\t\t\t\tcontainersReady = false\n\t\t\t\t}\n\t\t\t}\n\t\t\tif containersReady {\n\t\t\t\t// at least one pod has all containers ready\n\t\t\t\tready = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !ready {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc checkUnschedulablePods(pods []corev1.Pod) error {\n\tfor _, pod := range pods {\n\t\tfor _, condition := range pod.Status.Conditions {\n\t\t\tif condition.Reason == corev1.PodReasonUnschedulable {\n\t\t\t\treturn fmt.Errorf(\"%s: %s\", pod.Name, condition.Message)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc checkControlPlaneReplicaSets(rst []appsv1.ReplicaSet) error {\n\tvar errors []string\n\tfor _, rs := range rst {\n\t\tfor _, r := range rs.Status.Conditions {\n\t\t\tif r.Type == appsv1.ReplicaSetReplicaFailure && r.Status == corev1.ConditionTrue {\n\t\t\t\terrors = append(errors, fmt.Sprintf(\"%s: %s\", r.Reason, r.Message))\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn fmt.Errorf(\"%s\", strings.Join(errors, \"\\n   \"))\n\t}\n\n\treturn nil\n}\n\n// CheckForPods checks if the given deployments have pod resources present\nfunc CheckForPods(pods []corev1.Pod, deployNames []string) error {\n\texists := make(map[string]bool)\n\n\tfor _, pod := range pods {\n\t\tfor label, value := range pod.Labels {\n\t\t\t// When the label value is `linkerd.io/control-plane-component` or\n\t\t\t// `component`, we'll take its value as the name of the deployment\n\t\t\t// that the pod is part of\n\t\t\tif label == k8s.ControllerComponentLabel || label == \"component\" {\n\t\t\t\texists[value] = true\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, expected := range deployNames {\n\t\tif !exists[expected] {\n\t\t\treturn fmt.Errorf(\"Could not find pods for deployment %s\", expected)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// CheckPodsRunning checks if the given pods are in running state\n// along with containers to be in ready state\nfunc CheckPodsRunning(pods []corev1.Pod, namespace string) error {\n\tif len(pods) == 0 {\n\t\tmsg := fmt.Sprintf(\"no \\\"%s\\\" containers found\", k8s.ProxyContainerName)\n\t\tif namespace != \"\" {\n\t\t\tmsg += fmt.Sprintf(\" in the \\\"%s\\\" namespace\", namespace)\n\t\t}\n\t\treturn errors.New(msg)\n\t}\n\tfor _, pod := range pods {\n\t\tstatus := k8s.GetPodStatus(pod)\n\n\t\t// Skip validating pods that have a status which indicates there would\n\t\t// be no running proxy container.\n\t\tswitch status {\n\t\tcase \"Completed\", \"NodeShutdown\", \"Shutdown\", \"Terminated\":\n\t\t\tcontinue\n\t\t}\n\t\tif status != string(corev1.PodRunning) && status != \"Evicted\" {\n\t\t\treturn fmt.Errorf(\"pod \\\"%s\\\" status is %s\", pod.Name, pod.Status.Phase)\n\t\t}\n\t\tif !k8s.GetProxyReady(pod) {\n\t\t\treturn fmt.Errorf(\"container \\\"%s\\\" in pod \\\"%s\\\" is not ready\", k8s.ProxyContainerName, pod.Name)\n\t\t}\n\t}\n\treturn nil\n}\n\n// CheckIfDataPlanePodsExist checks if the proxy is present in the given pods\nfunc CheckIfDataPlanePodsExist(pods []corev1.Pod) error {\n\tfor _, pod := range pods {\n\t\tif !containsProxy(pod) {\n\t\t\treturn fmt.Errorf(\"could not find proxy container for %s pod\", pod.Name)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc containsProxy(pod corev1.Pod) bool {\n\tcontainers := append(pod.Spec.InitContainers, pod.Spec.Containers...)\n\tfor _, containerSpec := range containers {\n\t\tif containerSpec.Name == k8s.ProxyContainerName {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/healthcheck/healthcheck_fuzzer.go",
    "content": "package healthcheck\n\nimport (\n\t\"context\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n)\n\n// FuzzFetchCurrentConfiguration fuzzes the FetchCurrentConfiguration function.\nfunc FuzzFetchCurrentConfiguration(data []byte) int {\n\tclientset, err := k8s.NewFakeAPI(string(data))\n\tif err != nil {\n\t\treturn 0\n\t}\n\n\t_, _, _ = FetchCurrentConfiguration(context.Background(), clientset, \"linkerd\")\n\treturn 1\n}\n"
  },
  {
    "path": "pkg/healthcheck/healthcheck_labels.go",
    "content": "package healthcheck\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/util\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nvar (\n\tvalidAsLabelOnly = []string{\n\t\tk8s.DefaultExportedServiceSelector,\n\t}\n\tvalidAsAnnotationOnly = []string{\n\t\tk8s.ProxyInjectAnnotation,\n\t}\n\tvalidAsAnnotationPrefixOnly = []string{\n\t\tk8s.ProxyConfigAnnotationsPrefix,\n\t\tk8s.ProxyConfigAnnotationsPrefixAlpha,\n\t}\n)\n\nfunc checkMisconfiguredPodsLabels(pods []corev1.Pod) error {\n\tvar invalid []string\n\n\tfor _, pod := range pods {\n\t\tinvalidLabels := getMisconfiguredLabels(pod.ObjectMeta)\n\t\tif len(invalidLabels) > 0 {\n\t\t\tinvalid = append(invalid,\n\t\t\t\tfmt.Sprintf(\"\\t* %s/%s\\n\\t\\t%s\", pod.Namespace, pod.Name, strings.Join(invalidLabels, \"\\n\\t\\t\")))\n\t\t}\n\t}\n\tif len(invalid) > 0 {\n\t\treturn fmt.Errorf(\"Some labels on data plane pods should be annotations:\\n%s\", strings.Join(invalid, \"\\n\"))\n\t}\n\treturn nil\n}\n\nfunc checkMisconfiguredServiceLabels(services []corev1.Service) error {\n\tvar invalid []string\n\n\tfor _, svc := range services {\n\t\tinvalidLabels := getMisconfiguredLabels(svc.ObjectMeta)\n\t\tif len(invalidLabels) > 0 {\n\t\t\tinvalid = append(invalid,\n\t\t\t\tfmt.Sprintf(\"\\t* %s/%s\\n\\t\\t%s\", svc.Namespace, svc.Name, strings.Join(invalidLabels, \"\\n\\t\\t\")))\n\t\t}\n\t}\n\tif len(invalid) > 0 {\n\t\treturn fmt.Errorf(\"Some labels on data plane services should be annotations:\\n%s\", strings.Join(invalid, \"\\n\"))\n\t}\n\treturn nil\n}\n\nfunc checkMisconfiguredServiceAnnotations(services []corev1.Service) error {\n\tvar invalid []string\n\n\tfor _, svc := range services {\n\t\tinvalidAnnotations := getMisconfiguredAnnotations(svc.ObjectMeta)\n\t\tif len(invalidAnnotations) > 0 {\n\t\t\tinvalid = append(invalid,\n\t\t\t\tfmt.Sprintf(\"\\t* %s/%s\\n\\t\\t%s\", svc.Namespace, svc.Name, strings.Join(invalidAnnotations, \"\\n\\t\\t\")))\n\t\t}\n\t}\n\tif len(invalid) > 0 {\n\t\treturn fmt.Errorf(\"Some annotations on data plane services should be labels:\\n%s\", strings.Join(invalid, \"\\n\"))\n\t}\n\treturn nil\n}\n\nfunc getMisconfiguredLabels(objectMeta metav1.ObjectMeta) []string {\n\tvar invalid []string\n\n\tfor label := range objectMeta.Labels {\n\t\tif hasAnyPrefix(label, validAsAnnotationPrefixOnly) ||\n\t\t\tutil.ContainsString(label, validAsAnnotationOnly) {\n\t\t\tinvalid = append(invalid, label)\n\t\t}\n\t}\n\n\treturn invalid\n}\n\nfunc getMisconfiguredAnnotations(objectMeta metav1.ObjectMeta) []string {\n\tvar invalid []string\n\n\tfor ann := range objectMeta.Annotations {\n\t\tif util.ContainsString(ann, validAsLabelOnly) {\n\t\t\tinvalid = append(invalid, ann)\n\t\t}\n\t}\n\n\treturn invalid\n}\n\nfunc hasAnyPrefix(str string, prefixes []string) bool {\n\tfor _, pref := range prefixes {\n\t\tif strings.HasPrefix(str, pref) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/healthcheck/healthcheck_output.go",
    "content": "package healthcheck\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/briandowns/spinner\"\n\t\"github.com/fatih/color\"\n\t\"github.com/mattn/go-isatty\"\n)\n\nconst (\n\t// JSONOutput is used to specify the json output format\n\tJSONOutput = \"json\"\n\t// TableOutput is used to specify the table output format\n\tTableOutput = \"table\"\n\t// WideOutput is used to specify the wide output format\n\tWideOutput = \"wide\"\n\t// ShortOutput is used to specify the short output format\n\tShortOutput = \"short\"\n\n\t// DefaultHintBaseURL is the default base URL on the linkerd.io website\n\t// that all check hints for the latest linkerd version point to. Each\n\t// check adds its own `hintAnchor` to specify a location on the page.\n\tDefaultHintBaseURL = \"https://linkerd.io/2/checks/#\"\n)\n\nvar (\n\tokStatus   = color.New(color.FgGreen, color.Bold).SprintFunc()(\"\\u221A\")  // √\n\twarnStatus = color.New(color.FgYellow, color.Bold).SprintFunc()(\"\\u203C\") // ‼\n\tfailStatus = color.New(color.FgRed, color.Bold).SprintFunc()(\"\\u00D7\")    // ×\n\n\treStableVersion = regexp.MustCompile(`stable-(\\d\\.\\d+)\\.`)\n)\n\n// Checks describes the \"checks\" field on a CheckCLIOutput\ntype Checks string\n\nconst (\n\t// ExtensionMetadataSubcommand is the subcommand name an extension must\n\t// support in order to provide config metadata to the \"linkerd\" CLI.\n\tExtensionMetadataSubcommand = \"_extension-metadata\"\n\n\t// Always run the check, regardless of cluster state\n\tAlways Checks = \"always\"\n\t// // TODO:\n\t// // Cluster informs \"linkerd check\" to only run this extension if there are\n\t// // on-cluster resources.\n\t// Cluster Checks = \"cluster\"\n\t// // Never informs \"linkerd check\" to never run this extension.\n\t// Never Checks = \"never\"\n)\n\n// ExtensionMetadataOutput contains the output of a _extension-metadata subcommand.\ntype ExtensionMetadataOutput struct {\n\tName   string `json:\"name\"`\n\tChecks Checks `json:\"checks\"`\n}\n\n// CheckResults contains a slice of CheckResult structs.\ntype CheckResults struct {\n\tResults []CheckResult\n}\n\n// CheckOutput groups the check results for all categories\ntype CheckOutput struct {\n\tSuccess    bool             `json:\"success\"`\n\tCategories []*CheckCategory `json:\"categories\"`\n}\n\n// CheckCategory groups a series of check for a category\ntype CheckCategory struct {\n\tName   CategoryID `json:\"categoryName\"`\n\tChecks []*Check   `json:\"checks\"`\n}\n\n// Check is a user-facing version of `healthcheck.CheckResult`, for output via\n// `linkerd check -o json`.\ntype Check struct {\n\tDescription string         `json:\"description\"`\n\tHint        string         `json:\"hint,omitempty\"`\n\tError       string         `json:\"error,omitempty\"`\n\tResult      CheckResultStr `json:\"result\"`\n}\n\n// RunChecks submits each of the individual CheckResult structs to the given\n// observer.\nfunc (cr CheckResults) RunChecks(observer CheckObserver) (bool, bool) {\n\tsuccess := true\n\twarning := false\n\tfor _, result := range cr.Results {\n\t\tresult := result // Copy loop variable to make lint happy.\n\t\tif result.Err != nil {\n\t\t\tif !result.Warning {\n\t\t\t\tsuccess = false\n\t\t\t} else {\n\t\t\t\twarning = true\n\t\t\t}\n\t\t}\n\t\tobserver(&result)\n\t}\n\treturn success, warning\n}\n\n// PrintChecksResult writes the checks result.\nfunc PrintChecksResult(wout io.Writer, output string, success bool, warning bool) {\n\tif output == JSONOutput {\n\t\treturn\n\t}\n\n\tswitch success {\n\tcase true:\n\t\tfmt.Fprintf(wout, \"Status check results are %s\\n\", okStatus)\n\tcase false:\n\t\tfmt.Fprintf(wout, \"Status check results are %s\\n\", failStatus)\n\t}\n}\n\n// RunChecks runs the checks that are part of hc\nfunc RunChecks(wout io.Writer, werr io.Writer, hc Runner, output string) (bool, bool) {\n\tif output == JSONOutput {\n\t\treturn runChecksJSON(wout, werr, hc)\n\t}\n\n\treturn runChecksTable(wout, hc, output)\n}\n\nfunc runChecksTable(wout io.Writer, hc Runner, output string) (bool, bool) {\n\tvar lastCategory CategoryID\n\tspin := spinner.New(spinner.CharSets[9], 100*time.Millisecond)\n\tspin.Writer = wout\n\n\t// We set up different printing functions because we need to handle\n\t// 2 check formatting output use cases:\n\t//  1. the default check output in `table` format\n\t//  2. the summarized output in `short` format\n\tprettyPrintResults := func(result *CheckResult) {\n\t\tlastCategory = printCategory(wout, lastCategory, result)\n\n\t\tspin.Stop()\n\t\tif result.Retry {\n\t\t\trestartSpinner(spin, result)\n\t\t\treturn\n\t\t}\n\n\t\tstatus := getResultStatus(result)\n\n\t\tprintResultDescription(wout, status, result)\n\t}\n\n\tprettyPrintResultsShort := func(result *CheckResult) {\n\t\t// bail out early and skip printing if we've got an okStatus\n\t\tif result.Err == nil {\n\t\t\treturn\n\t\t}\n\n\t\tlastCategory = printCategory(wout, lastCategory, result)\n\n\t\tspin.Stop()\n\t\tif result.Retry {\n\t\t\trestartSpinner(spin, result)\n\t\t\treturn\n\t\t}\n\n\t\tstatus := getResultStatus(result)\n\n\t\tprintResultDescription(wout, status, result)\n\t}\n\n\tvar (\n\t\tsuccess bool\n\t\twarning bool\n\t)\n\tswitch output {\n\tcase ShortOutput:\n\t\tsuccess, warning = hc.RunChecks(prettyPrintResultsShort)\n\tdefault:\n\t\tsuccess, warning = hc.RunChecks(prettyPrintResults)\n\t}\n\n\t// This ensures there is a newline separating check categories from each\n\t// other as well as the check result. When running in ShortOutput mode and\n\t// there are no warnings, there is no newline printed.\n\tif output != ShortOutput || !success || warning {\n\t\tfmt.Fprintln(wout)\n\t}\n\n\treturn success, warning\n}\n\n// CheckResultStr is a string describing the result of a check\ntype CheckResultStr string\n\nconst (\n\tCheckSuccess CheckResultStr = \"success\"\n\tCheckWarn    CheckResultStr = \"warning\"\n\tCheckErr     CheckResultStr = \"error\"\n)\n\nfunc runChecksJSON(wout io.Writer, werr io.Writer, hc Runner) (bool, bool) {\n\tvar categories []*CheckCategory\n\n\tcollectJSONOutput := func(result *CheckResult) {\n\t\tif categories == nil || categories[len(categories)-1].Name != result.Category {\n\t\t\tcategories = append(categories, &CheckCategory{\n\t\t\t\tName:   result.Category,\n\t\t\t\tChecks: []*Check{},\n\t\t\t})\n\t\t}\n\n\t\tif !result.Retry {\n\t\t\tcurrentCategory := categories[len(categories)-1]\n\t\t\t// ignore checks that are going to be retried, we want only final results\n\t\t\tstatus := CheckSuccess\n\t\t\tif result.Err != nil {\n\t\t\t\tstatus = CheckErr\n\t\t\t\tif result.Warning {\n\t\t\t\t\tstatus = CheckWarn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcurrentCheck := &Check{\n\t\t\t\tDescription: result.Description,\n\t\t\t\tResult:      status,\n\t\t\t}\n\n\t\t\tif result.Err != nil {\n\t\t\t\tcurrentCheck.Error = result.Err.Error()\n\n\t\t\t\tif result.HintURL != \"\" {\n\t\t\t\t\tcurrentCheck.Hint = result.HintURL\n\t\t\t\t}\n\t\t\t}\n\t\t\tcurrentCategory.Checks = append(currentCategory.Checks, currentCheck)\n\t\t}\n\t}\n\n\tsuccess, warning := hc.RunChecks(collectJSONOutput)\n\n\toutputJSON := CheckOutput{\n\t\tSuccess:    success,\n\t\tCategories: categories,\n\t}\n\n\tresultJSON, err := json.MarshalIndent(outputJSON, \"\", \"  \")\n\tif err == nil {\n\t\tfmt.Fprintf(wout, \"%s\\n\", string(resultJSON))\n\t} else {\n\t\tfmt.Fprintf(werr, \"JSON serialization of the check result failed with %s\", err)\n\t}\n\treturn success, warning\n}\n\nfunc printResultDescription(wout io.Writer, status string, result *CheckResult) {\n\tfmt.Fprintf(wout, \"%s %s\\n\", status, result.Description)\n\n\tif result.Err == nil {\n\t\treturn\n\t}\n\n\tfmt.Fprintf(wout, \"    %s\\n\", result.Err)\n\tif result.HintURL != \"\" {\n\t\tfmt.Fprintf(wout, \"    see %s for hints\\n\", result.HintURL)\n\t}\n}\n\nfunc getResultStatus(result *CheckResult) string {\n\tstatus := okStatus\n\tif result.Err != nil {\n\t\tstatus = failStatus\n\t\tif result.Warning {\n\t\t\tstatus = warnStatus\n\t\t}\n\t}\n\n\treturn status\n}\n\nfunc restartSpinner(spin *spinner.Spinner, result *CheckResult) {\n\tif isatty.IsTerminal(os.Stdout.Fd()) {\n\t\tspin.Suffix = fmt.Sprintf(\" %s\", result.Err)\n\t\tspin.Color(\"bold\") // this calls spin.Restart()\n\t}\n}\n\nfunc printCategory(wout io.Writer, lastCategory CategoryID, result *CheckResult) CategoryID {\n\tif lastCategory == result.Category {\n\t\treturn lastCategory\n\t}\n\n\tif lastCategory != \"\" {\n\t\tfmt.Fprintln(wout)\n\t}\n\n\tfmt.Fprintln(wout, result.Category)\n\tfmt.Fprintln(wout, strings.Repeat(\"-\", len(result.Category)))\n\n\treturn result.Category\n}\n\n// HintBaseURL returns the base URL on the linkerd.io website that check hints\n// point to, depending on the version\nfunc HintBaseURL(ver string) string {\n\tstableVersion := reStableVersion.FindStringSubmatch(ver)\n\tif stableVersion == nil {\n\t\treturn DefaultHintBaseURL\n\t}\n\treturn fmt.Sprintf(\"https://linkerd.io/%s/checks/#\", stableVersion[1])\n}\n"
  },
  {
    "path": "pkg/healthcheck/healthcheck_test.go",
    "content": "package healthcheck\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/linkerd/linkerd2/pkg/identity\"\n\t\"github.com/linkerd/linkerd2/pkg/issuercerts\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\ntype observer struct {\n\tresults []string\n}\n\nfunc newObserver() *observer {\n\treturn &observer{\n\t\tresults: []string{},\n\t}\n}\nfunc (o *observer) resultFn(result *CheckResult) {\n\tres := fmt.Sprintf(\"%s %s\", result.Category, result.Description)\n\tif result.Err != nil {\n\t\tres += fmt.Sprintf(\": %s\", result.Err)\n\t}\n\to.results = append(o.results, res)\n}\n\nfunc (o *observer) resultWithHintFn(result *CheckResult) {\n\tres := fmt.Sprintf(\"%s %s\", result.Category, result.Description)\n\tif result.Err != nil {\n\t\tres += fmt.Sprintf(\": %s\", result.Err)\n\t}\n\n\tif result.HintURL != \"\" {\n\t\tres += fmt.Sprintf(\": %s\", result.HintURL)\n\t}\n\to.results = append(o.results, res)\n}\n\nfunc (hc *HealthChecker) addCheckAsCategory(\n\ttestCategoryID CategoryID,\n\tcategoryID CategoryID,\n\tdesc string,\n) {\n\ttestCategory := NewCategory(\n\t\ttestCategoryID,\n\t\t[]Checker{},\n\t\tfalse,\n\t)\n\n\tfor _, cat := range hc.categories {\n\t\tif cat.ID == categoryID {\n\t\t\tfor _, ch := range cat.checkers {\n\t\t\t\tif ch.description == desc {\n\t\t\t\t\ttestCategory.checkers = append(testCategory.checkers, ch)\n\t\t\t\t\ttestCategory.enabled = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\thc.AppendCategories(testCategory)\n}\n\nfunc TestHealthChecker(t *testing.T) {\n\tnullObserver := func(*CheckResult) {}\n\n\tpassingCheck1 := NewCategory(\n\t\t\"cat1\",\n\t\t[]Checker{\n\t\t\t{\n\t\t\t\tdescription: \"desc1\",\n\t\t\t\tcheck: func(context.Context) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t\tretryDeadline: time.Time{},\n\t\t\t},\n\t\t},\n\t\ttrue,\n\t)\n\n\tpassingCheck2 := NewCategory(\n\t\t\"cat2\",\n\t\t[]Checker{\n\t\t\t{\n\t\t\t\tdescription: \"desc2\",\n\t\t\t\tcheck: func(context.Context) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t\tretryDeadline: time.Time{},\n\t\t\t},\n\t\t},\n\t\ttrue,\n\t)\n\n\tfailingCheck := NewCategory(\n\t\t\"cat3\",\n\t\t[]Checker{\n\t\t\t{\n\t\t\t\tdescription: \"desc3\",\n\t\t\t\tcheck: func(context.Context) error {\n\t\t\t\t\treturn fmt.Errorf(\"error\")\n\t\t\t\t},\n\t\t\t\tretryDeadline: time.Time{},\n\t\t\t},\n\t\t},\n\t\ttrue,\n\t)\n\n\tfatalCheck := NewCategory(\n\t\t\"cat6\",\n\t\t[]Checker{\n\t\t\t{\n\t\t\t\tdescription: \"desc6\",\n\t\t\t\tfatal:       true,\n\t\t\t\tcheck: func(context.Context) error {\n\t\t\t\t\treturn fmt.Errorf(\"fatal\")\n\t\t\t\t},\n\t\t\t\tretryDeadline: time.Time{},\n\t\t\t},\n\t\t},\n\t\ttrue,\n\t)\n\n\tskippingCheck := NewCategory(\n\t\t\"cat7\",\n\t\t[]Checker{\n\t\t\t{\n\t\t\t\tdescription: \"skip\",\n\t\t\t\tcheck: func(context.Context) error {\n\t\t\t\t\treturn SkipError{Reason: \"needs skipping\"}\n\t\t\t\t},\n\t\t\t\tretryDeadline: time.Time{},\n\t\t\t},\n\t\t},\n\t\ttrue,\n\t)\n\n\tskippingRPCCheck := NewCategory(\n\t\t\"cat8\",\n\t\t[]Checker{\n\t\t\t{\n\t\t\t\tdescription: \"skipRpc\",\n\t\t\t\tcheck: func(context.Context) error {\n\t\t\t\t\treturn SkipError{Reason: \"needs skipping\"}\n\t\t\t\t},\n\t\t\t\tretryDeadline: time.Time{},\n\t\t\t},\n\t\t},\n\t\ttrue,\n\t)\n\n\ttroubleshootingCheck := NewCategory(\n\t\t\"cat9\",\n\t\t[]Checker{\n\t\t\t{\n\t\t\t\tdescription: \"failCheck\",\n\t\t\t\thintAnchor:  \"cat9\",\n\t\t\t\tcheck: func(context.Context) error {\n\t\t\t\t\treturn fmt.Errorf(\"fatal\")\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\ttrue,\n\t)\n\n\tt.Run(\"Notifies observer of all results\", func(t *testing.T) {\n\t\thc := NewHealthChecker(\n\t\t\t[]CategoryID{},\n\t\t\t&Options{},\n\t\t)\n\n\t\thc.AppendCategories(passingCheck1)\n\t\thc.AppendCategories(passingCheck2)\n\t\thc.AppendCategories(failingCheck)\n\n\t\texpectedResults := []string{\n\t\t\t\"cat1 desc1\",\n\t\t\t\"cat2 desc2\",\n\t\t\t\"cat3 desc3: error\",\n\t\t}\n\n\t\tobs := newObserver()\n\t\thc.RunChecks(obs.resultFn)\n\n\t\tif diff := deep.Equal(obs.results, expectedResults); diff != nil {\n\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"Is successful if all checks were successful\", func(t *testing.T) {\n\t\thc := NewHealthChecker(\n\t\t\t[]CategoryID{},\n\t\t\t&Options{},\n\t\t)\n\t\thc.AppendCategories(passingCheck1)\n\t\thc.AppendCategories(passingCheck2)\n\n\t\tsuccess, _ := hc.RunChecks(nullObserver)\n\n\t\tif !success {\n\t\t\tt.Fatalf(\"Expecting checks to be successful, but got [%t]\", success)\n\t\t}\n\t})\n\n\tt.Run(\"Is not successful if one check fails\", func(t *testing.T) {\n\t\thc := NewHealthChecker(\n\t\t\t[]CategoryID{},\n\t\t\t&Options{},\n\t\t)\n\t\thc.AppendCategories(passingCheck1)\n\t\thc.AppendCategories(failingCheck)\n\t\thc.AppendCategories(passingCheck2)\n\n\t\tsuccess, _ := hc.RunChecks(nullObserver)\n\n\t\tif success {\n\t\t\tt.Fatalf(\"Expecting checks to not be successful, but got [%t]\", success)\n\t\t}\n\t})\n\n\tt.Run(\"Check for troubleshooting URL\", func(t *testing.T) {\n\t\thc := NewHealthChecker(\n\t\t\t[]CategoryID{},\n\t\t\t&Options{},\n\t\t)\n\t\ttroubleshootingCheck.WithHintBaseURL(\"www.extension.com/troubleshooting/#\")\n\t\thc.AppendCategories(troubleshootingCheck)\n\t\texpectedResults := []string{\n\t\t\t\"cat9 failCheck: fatal: www.extension.com/troubleshooting/#cat9\",\n\t\t}\n\n\t\tobs := newObserver()\n\t\thc.RunChecks(obs.resultWithHintFn)\n\n\t\tif diff := deep.Equal(obs.results, expectedResults); diff != nil {\n\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"Does not run remaining check if fatal check fails\", func(t *testing.T) {\n\t\thc := NewHealthChecker(\n\t\t\t[]CategoryID{},\n\t\t\t&Options{},\n\t\t)\n\t\thc.AppendCategories(passingCheck1)\n\t\thc.AppendCategories(fatalCheck)\n\t\thc.AppendCategories(passingCheck2)\n\n\t\texpectedResults := []string{\n\t\t\t\"cat1 desc1\",\n\t\t\t\"cat6 desc6: fatal\",\n\t\t}\n\n\t\tobs := newObserver()\n\t\thc.RunChecks(obs.resultFn)\n\n\t\tif diff := deep.Equal(obs.results, expectedResults); diff != nil {\n\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"Retries checks if retry is specified\", func(t *testing.T) {\n\t\tretryWindow = 0\n\t\treturnError := true\n\n\t\tretryCheck := NewCategory(\n\t\t\t\"cat7\",\n\t\t\t[]Checker{\n\t\t\t\t{\n\t\t\t\t\tdescription:   \"desc7\",\n\t\t\t\t\tretryDeadline: time.Now().Add(100 * time.Second),\n\t\t\t\t\tcheck: func(context.Context) error {\n\t\t\t\t\t\tif returnError {\n\t\t\t\t\t\t\treturnError = false\n\t\t\t\t\t\t\treturn fmt.Errorf(\"retry\")\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\ttrue,\n\t\t)\n\n\t\thc := NewHealthChecker(\n\t\t\t[]CategoryID{},\n\t\t\t&Options{},\n\t\t)\n\t\thc.AppendCategories(passingCheck1)\n\t\thc.AppendCategories(retryCheck)\n\n\t\tobservedResults := make([]string, 0)\n\t\tobserver := func(result *CheckResult) {\n\t\t\tres := fmt.Sprintf(\"%s %s retry=%t\", result.Category, result.Description, result.Retry)\n\t\t\tif result.Err != nil {\n\t\t\t\tres += fmt.Sprintf(\": %s\", result.Err)\n\t\t\t}\n\t\t\tobservedResults = append(observedResults, res)\n\t\t}\n\n\t\texpectedResults := []string{\n\t\t\t\"cat1 desc1 retry=false\",\n\t\t\t\"cat7 desc7 retry=true: waiting for check to complete\",\n\t\t\t\"cat7 desc7 retry=false\",\n\t\t}\n\n\t\thc.RunChecks(observer)\n\n\t\tif diff := deep.Equal(observedResults, expectedResults); diff != nil {\n\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"Does not notify observer of skipped checks\", func(t *testing.T) {\n\t\thc := NewHealthChecker(\n\t\t\t[]CategoryID{},\n\t\t\t&Options{},\n\t\t)\n\t\thc.AppendCategories(passingCheck1)\n\t\thc.AppendCategories(skippingCheck)\n\t\thc.AppendCategories(skippingRPCCheck)\n\n\t\texpectedResults := []string{\n\t\t\t\"cat1 desc1\",\n\t\t}\n\n\t\tobs := newObserver()\n\t\thc.RunChecks(obs.resultFn)\n\n\t\tif diff := deep.Equal(obs.results, expectedResults); diff != nil {\n\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t}\n\t})\n}\n\nfunc TestCheckCanCreate(t *testing.T) {\n\texp := fmt.Errorf(\"not authorized to access deployments.apps\")\n\n\thc := NewHealthChecker(\n\t\t[]CategoryID{},\n\t\t&Options{},\n\t)\n\tvar err error\n\thc.kubeAPI, err = k8s.NewFakeAPI()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\terr = hc.checkCanCreate(context.Background(), \"\", \"apps\", \"v1\", \"deployments\")\n\tif err == nil ||\n\t\terr.Error() != exp.Error() {\n\t\tt.Fatalf(\"Unexpected error (Expected: %s, Got: %s)\", exp, err)\n\t}\n}\n\nfunc TestCheckExtensionAPIServerAuthentication(t *testing.T) {\n\ttests := []struct {\n\t\tk8sConfigs []string\n\t\terr        error\n\t}{\n\t\t{\n\t\t\t[]string{},\n\t\t\tfmt.Errorf(\"configmaps %q not found\", k8s.ExtensionAPIServerAuthenticationConfigMapName),\n\t\t},\n\t\t{\n\t\t\t[]string{`\napiVersion: v1\nkind: ConfigMap\nmetadata:\n name: extension-apiserver-authentication\n namespace: kube-system\ndata:\n foo : 'bar'\n `,\n\t\t\t},\n\t\t\tfmt.Errorf(\"--%s is not configured\", k8s.ExtensionAPIServerAuthenticationRequestHeaderClientCAFileKey),\n\t\t},\n\t\t{\n\n\t\t\t[]string{fmt.Sprintf(`\napiVersion: v1\nkind: ConfigMap\nmetadata:\n name: extension-apiserver-authentication\n namespace: kube-system\ndata:\n  %s : 'bar'\n  `, k8s.ExtensionAPIServerAuthenticationRequestHeaderClientCAFileKey)},\n\t\t\tnil,\n\t\t},\n\t}\n\tfor i, test := range tests {\n\t\ttest := test\n\t\tt.Run(fmt.Sprintf(\"%d: returns expected extension apiserver authentication check result\", i), func(t *testing.T) {\n\t\t\thc := NewHealthChecker([]CategoryID{}, &Options{})\n\t\t\tvar err error\n\t\t\thc.kubeAPI, err = k8s.NewFakeAPI(test.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\terr = hc.checkExtensionAPIServerAuthentication(context.Background())\n\t\t\tif err != nil || test.err != nil {\n\t\t\t\tif (err == nil && test.err != nil) ||\n\t\t\t\t\t(err != nil && test.err == nil) ||\n\t\t\t\t\t(err.Error() != test.err.Error()) {\n\t\t\t\t\tt.Fatalf(\"Unexpected error (Expected: %s, Got: %s)\", test.err, err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCheckClockSkew(t *testing.T) {\n\ttests := []struct {\n\t\tk8sConfigs []string\n\t\terr        error\n\t}{\n\t\t{\n\t\t\t[]string{},\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t[]string{`apiVersion: v1\nkind: Node\nmetadata:\n  name: test-node\nstatus:\n  conditions:\n  - lastHeartbeatTime: \"2000-01-01T01:00:00Z\"\n    status: \"True\"\n    type: Ready`,\n\t\t\t},\n\t\t\tfmt.Errorf(\"clock skew detected for node(s): test-node\"),\n\t\t},\n\t}\n\n\tfor i, test := range tests {\n\t\ttest := test // pin\n\t\tt.Run(fmt.Sprintf(\"%d: returns expected clock skew check result\", i), func(t *testing.T) {\n\t\t\thc := NewHealthChecker(\n\t\t\t\t[]CategoryID{},\n\t\t\t\t&Options{},\n\t\t\t)\n\n\t\t\tvar err error\n\t\t\thc.kubeAPI, err = k8s.NewFakeAPI(test.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\terr = hc.checkClockSkew(context.Background())\n\t\t\tif err != nil || test.err != nil {\n\t\t\t\tif (err == nil && test.err != nil) ||\n\t\t\t\t\t(err != nil && test.err == nil) ||\n\t\t\t\t\t(err.Error() != test.err.Error()) {\n\t\t\t\t\tt.Fatalf(\"Unexpected error (Expected: %s, Got: %s)\", test.err, err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n}\n\nfunc TestNamespaceExtCfg(t *testing.T) {\n\tnamespaces := map[string]string{\n\t\t\"vizOne\": `\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: viz-1\n  labels:\n    linkerd.io/extension: viz\n`,\n\t\t\"mcOne\": `\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: mc-1\n  labels:\n    linkerd.io/extension: multicluster\n`,\n\t\t\"mcTwo\": `\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: mc-2\n  labels:\n    linkerd.io/extension: multicluster\n`}\n\n\ttestCases := []struct {\n\t\tdescription string\n\t\tk8sConfigs  []string\n\t\tresults     []string\n\t}{\n\t\t{\n\t\t\tdescription: \"successfully passes checks\",\n\t\t\tk8sConfigs:  []string{namespaces[\"vizOne\"], namespaces[\"mcOne\"]},\n\t\t\tresults: []string{\n\t\t\t\t\"linkerd-extension-checks namespace configuration for extensions\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdescription: \"fails invalid configuration\",\n\t\t\tk8sConfigs:  []string{namespaces[\"vizOne\"], namespaces[\"mcOne\"], namespaces[\"mcTwo\"]},\n\t\t\tresults: []string{\n\t\t\t\t\"linkerd-extension-checks namespace configuration for extensions: some extensions have invalid configuration\\n\\t* label \\\"linkerd.io/extension=multicluster\\\" is present on more than one namespace:\\n\\t\\t* mc-1\\n\\t\\t* mc-2\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\t// pin tc\n\t\ttc := tc\n\t\tt.Run(tc.description, func(t *testing.T) {\n\t\t\thc := NewHealthChecker(\n\t\t\t\t[]CategoryID{LinkerdExtensionChecks},\n\t\t\t\t&Options{\n\t\t\t\t\tControlPlaneNamespace: \"test-ns\",\n\t\t\t\t},\n\t\t\t)\n\n\t\t\tvar err error\n\t\t\thc.kubeAPI, err = k8s.NewFakeAPI(tc.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\tobs := newObserver()\n\t\t\thc.RunChecks(obs.resultFn)\n\t\t\tif diff := deep.Equal(obs.results, tc.results); diff != nil {\n\t\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestConfigExists(t *testing.T) {\n\n\tnamespace := []string{`\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: test-ns\n`}\n\tclusterRoles := []string{`\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-test-ns-identity\n  labels:\n    linkerd.io/control-plane-ns: test-ns\n`,\n\t\t`\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-test-ns-proxy-injector\n  labels:\n    linkerd.io/control-plane-ns: test-ns\n`}\n\tclusterRoleBindings := []string{`\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-test-ns-identity\n  labels:\n    linkerd.io/control-plane-ns: test-ns\n`,\n\t\t`\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-test-ns-proxy-injector\n  labels:\n    linkerd.io/control-plane-ns: test-ns\n`}\n\tserviceAccounts := []string{`\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-destination\n  namespace: test-ns\n  labels:\n    linkerd.io/control-plane-ns: test-ns\n`,\n\t\t`\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-identity\n  namespace: test-ns\n  labels:\n    linkerd.io/control-plane-ns: test-ns\n`,\n\t\t`\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-proxy-injector\n  namespace: test-ns\n  labels:\n    linkerd.io/control-plane-ns: test-ns\n`,\n\t\t`\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: linkerd-heartbeat\n  namespace: test-ns\n  labels:\n    linkerd.io/control-plane-ns: test-ns\n`}\n\tcrds := []string{}\n\tfor _, crd := range []struct{ name, version string }{\n\t\t{name: \"authorizationpolicies.policy.linkerd.io\", version: \"v1alpha1\"},\n\t\t{name: \"meshtlsauthentications.policy.linkerd.io\", version: \"v1alpha1\"},\n\t\t{name: \"networkauthentications.policy.linkerd.io\", version: \"v1alpha1\"},\n\t\t{name: \"serverauthorizations.policy.linkerd.io\", version: \"v1beta1\"},\n\t\t{name: \"servers.policy.linkerd.io\", version: \"v1beta1\"},\n\t\t{name: \"serviceprofiles.linkerd.io\", version: \"v1alpha2\"},\n\t} {\n\t\tcrds = append(crds, fmt.Sprintf(`\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: %s\n  labels:\n    linkerd.io/control-plane-ns: test-ns\nspec:\n  versions:\n  - name: %s`, crd.name, crd.version))\n\t}\n\tmutatingWebhooks := []string{`\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-proxy-injector-webhook-config\n  labels:\n    linkerd.io/control-plane-ns: test-ns\n`}\n\tvalidatingWebhooks := []string{`\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: linkerd-sp-validator-webhook-config\n  labels:\n    linkerd.io/control-plane-ns: test-ns\n`}\n\n\ttestCases := []struct {\n\t\tk8sConfigs []string\n\t\tresults    []string\n\t}{\n\t\t{\n\t\t\t[]string{},\n\t\t\t[]string{\"linkerd-config control plane Namespace exists: The \\\"test-ns\\\" namespace does not exist\"},\n\t\t},\n\t\t{\n\t\t\tnamespace,\n\t\t\t[]string{\n\t\t\t\t\"linkerd-config control plane Namespace exists\",\n\t\t\t\t\"linkerd-config control plane ClusterRoles exist: missing ClusterRoles: linkerd-test-ns-identity, linkerd-test-ns-proxy-injector\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmultiappend(\n\t\t\t\tnamespace,\n\t\t\t\tclusterRoles,\n\t\t\t),\n\t\t\t[]string{\n\t\t\t\t\"linkerd-config control plane Namespace exists\",\n\t\t\t\t\"linkerd-config control plane ClusterRoles exist\",\n\t\t\t\t\"linkerd-config control plane ClusterRoleBindings exist: missing ClusterRoleBindings: linkerd-test-ns-identity, linkerd-test-ns-proxy-injector\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmultiappend(\n\t\t\t\tnamespace,\n\t\t\t\tclusterRoles,\n\t\t\t\tclusterRoleBindings,\n\t\t\t\tserviceAccounts,\n\t\t\t),\n\t\t\t[]string{\n\t\t\t\t\"linkerd-config control plane Namespace exists\",\n\t\t\t\t\"linkerd-config control plane ClusterRoles exist\",\n\t\t\t\t\"linkerd-config control plane ClusterRoleBindings exist\",\n\t\t\t\t\"linkerd-config control plane ServiceAccounts exist\",\n\t\t\t\t\"linkerd-config control plane CustomResourceDefinitions exist: missing authorizationpolicies.policy.linkerd.io, missing meshtlsauthentications.policy.linkerd.io, missing networkauthentications.policy.linkerd.io, missing serverauthorizations.policy.linkerd.io, missing servers.policy.linkerd.io, missing serviceprofiles.linkerd.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmultiappend(\n\t\t\t\tnamespace,\n\t\t\t\tclusterRoles,\n\t\t\t\tclusterRoleBindings,\n\t\t\t\tserviceAccounts,\n\t\t\t\tcrds,\n\t\t\t),\n\t\t\t[]string{\n\t\t\t\t\"linkerd-config control plane Namespace exists\",\n\t\t\t\t\"linkerd-config control plane ClusterRoles exist\",\n\t\t\t\t\"linkerd-config control plane ClusterRoleBindings exist\",\n\t\t\t\t\"linkerd-config control plane ServiceAccounts exist\",\n\t\t\t\t\"linkerd-config control plane CustomResourceDefinitions exist\",\n\t\t\t\t\"linkerd-config control plane MutatingWebhookConfigurations exist: missing MutatingWebhookConfigurations: linkerd-proxy-injector-webhook-config\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmultiappend(\n\t\t\t\tnamespace,\n\t\t\t\tclusterRoles,\n\t\t\t\tclusterRoleBindings,\n\t\t\t\tserviceAccounts,\n\t\t\t\tcrds,\n\t\t\t\tmutatingWebhooks,\n\t\t\t),\n\t\t\t[]string{\n\t\t\t\t\"linkerd-config control plane Namespace exists\",\n\t\t\t\t\"linkerd-config control plane ClusterRoles exist\",\n\t\t\t\t\"linkerd-config control plane ClusterRoleBindings exist\",\n\t\t\t\t\"linkerd-config control plane ServiceAccounts exist\",\n\t\t\t\t\"linkerd-config control plane CustomResourceDefinitions exist\",\n\t\t\t\t\"linkerd-config control plane MutatingWebhookConfigurations exist\",\n\t\t\t\t\"linkerd-config control plane ValidatingWebhookConfigurations exist: missing ValidatingWebhookConfigurations: linkerd-sp-validator-webhook-config\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmultiappend(\n\t\t\t\tnamespace,\n\t\t\t\tclusterRoles,\n\t\t\t\tclusterRoleBindings,\n\t\t\t\tserviceAccounts,\n\t\t\t\tcrds,\n\t\t\t\tmutatingWebhooks,\n\t\t\t\tvalidatingWebhooks,\n\t\t\t),\n\t\t\t[]string{\n\t\t\t\t\"linkerd-config control plane Namespace exists\",\n\t\t\t\t\"linkerd-config control plane ClusterRoles exist\",\n\t\t\t\t\"linkerd-config control plane ClusterRoleBindings exist\",\n\t\t\t\t\"linkerd-config control plane ServiceAccounts exist\",\n\t\t\t\t\"linkerd-config control plane CustomResourceDefinitions exist\",\n\t\t\t\t\"linkerd-config control plane MutatingWebhookConfigurations exist\",\n\t\t\t\t\"linkerd-config control plane ValidatingWebhookConfigurations exist\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d: returns expected config result\", i), func(t *testing.T) {\n\t\t\thc := NewHealthChecker(\n\t\t\t\t[]CategoryID{LinkerdConfigChecks},\n\t\t\t\t&Options{\n\t\t\t\t\tControlPlaneNamespace: \"test-ns\",\n\t\t\t\t\tCRDManifest:           strings.Join(crds, \"\\n---\\n\"),\n\t\t\t\t},\n\t\t\t)\n\n\t\t\tvar err error\n\t\t\thc.kubeAPI, err = k8s.NewFakeAPI(tc.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\tobs := newObserver()\n\t\t\thc.RunChecks(obs.resultFn)\n\t\t\tif diff := deep.Equal(obs.results, tc.results); diff != nil {\n\t\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCheckControlPlanePodExistence(t *testing.T) {\n\tvar testCases = []struct {\n\t\tcheckDescription string\n\t\tresources        []string\n\t\texpected         []string\n\t}{\n\t\t{\n\t\t\tcheckDescription: \"'linkerd-config' config map exists\",\n\t\t\tresources: []string{`\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: linkerd-config\n  namespace: test-ns\ndata:\n  values: \"{}\"\n`,\n\t\t\t},\n\t\t\texpected: []string{\n\t\t\t\t\"cat1 'linkerd-config' config map exists\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor id, testCase := range testCases {\n\t\ttestCase := testCase\n\t\tt.Run(fmt.Sprintf(\"%d\", id), func(t *testing.T) {\n\t\t\thc := NewHealthChecker(\n\t\t\t\t[]CategoryID{},\n\t\t\t\t&Options{\n\t\t\t\t\tControlPlaneNamespace: \"test-ns\",\n\t\t\t\t},\n\t\t\t)\n\n\t\t\tvar err error\n\t\t\thc.kubeAPI, err = k8s.NewFakeAPI(testCase.resources...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\t// validate that this check relies on the k8s api, not on hc.controlPlanePods\n\t\t\thc.addCheckAsCategory(\"cat1\", LinkerdControlPlaneExistenceChecks,\n\t\t\t\ttestCase.checkDescription)\n\n\t\t\tobs := newObserver()\n\t\t\thc.RunChecks(obs.resultFn)\n\t\t\tif diff := deep.Equal(obs.results, testCase.expected); diff != nil {\n\t\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCheckClusterNetworks(t *testing.T) {\n\tvar testCases = []struct {\n\t\tcheckDescription string\n\t\tk8sConfigs       []string\n\t\texpected         []string\n\t}{\n\t\t{\n\t\t\tcheckDescription: \"cluster networks contains all node podCIDRs\",\n\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: test-ns\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Node\nmetadata:\n  name: linkerd-test-ns-identity\nspec:\n  podCIDR: 90.10.90.24/24\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Node\nmetadata:\n  name: linkerd-test-ns-identity2\nspec:\n  podCIDR: 242.3.64.0/25\n`,\n\t\t\t\t`\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: test-ns\n  labels:\n    linkerd.io/control-plane-ns: test-ns\ndata:\n  values: |\n    clusterNetworks: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16\"\n`,\n\t\t\t},\n\t\t\texpected: []string{\n\t\t\t\t\"linkerd-existence cluster networks contains all node podCIDRs: node has podCIDR(s) [242.3.64.0/25 90.10.90.24/24] which are not contained in the Linkerd clusterNetworks.\\n\\tTry installing linkerd via --set clusterNetworks=\\\"242.3.64.0/25\\\\,90.10.90.24/24\\\"\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tcheckDescription: \"cluster networks contains all node podCIDRs\",\n\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: test-ns\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Node\nmetadata:\n  name: linkerd-test-ns-identity\nspec:\n  podCIDR: 10.0.0.24/24\n`,\n\t\t\t\t`\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: test-ns\n  labels:\n    linkerd.io/control-plane-ns: test-ns\ndata:\n  values: |\n    clusterNetworks: \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16\"\n`,\n\t\t\t},\n\t\t\texpected: []string{\n\t\t\t\t\"linkerd-existence cluster networks contains all node podCIDRs\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d: returns expected config result\", i), func(t *testing.T) {\n\t\t\thc := NewHealthChecker(\n\t\t\t\t[]CategoryID{},\n\t\t\t\t&Options{\n\t\t\t\t\tControlPlaneNamespace: \"test-ns\",\n\t\t\t\t},\n\t\t\t)\n\n\t\t\tvar err error\n\t\t\thc.kubeAPI, err = k8s.NewFakeAPI(tc.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\tobs := newObserver()\n\t\t\thc.addCheckAsCategory(\"linkerd-existence\", LinkerdControlPlaneExistenceChecks,\n\t\t\t\ttc.checkDescription)\n\t\t\thc.RunChecks(obs.resultFn)\n\t\t\tif diff := deep.Equal(obs.results, tc.expected); diff != nil {\n\t\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc proxiesWithCertificates(certificates ...string) []string {\n\tresult := []string{}\n\tfor i, certificate := range certificates {\n\t\tresult = append(result, fmt.Sprintf(`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: pod-%d\n  namespace: namespace-%d\n  labels:\n    %s: linkerd\nspec:\n  containers:\n  - name: %s\n    env:\n    - name: %s\n      value: %s\n`, i, i, k8s.ControllerNSLabel, k8s.ProxyContainerName, identity.EnvTrustAnchors, certificate))\n\t}\n\treturn result\n}\n\nfunc TestCheckDataPlaneProxiesCertificate(t *testing.T) {\n\tconst currentCertificate = \"current-certificate\"\n\tconst oldCertificate = \"old-certificate\"\n\n\tlinkerdIdentityTrustRoots := fmt.Sprintf(`\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: %s\ndata:\n  ca-bundle.crt: %s\n\n`, \"linkerd-identity-trust-roots\", currentCertificate)\n\n\tvar testCases = []struct {\n\t\tcheckDescription string\n\t\tresources        []string\n\t\tnamespace        string\n\t\texpectedErr      error\n\t}{\n\t\t{\n\t\t\tcheckDescription: \"all proxies match CA certificate (all namespaces)\",\n\t\t\tresources:        proxiesWithCertificates(currentCertificate, currentCertificate),\n\t\t\tnamespace:        \"\",\n\t\t\texpectedErr:      nil,\n\t\t},\n\t\t{\n\t\t\tcheckDescription: \"some proxies match CA certificate (all namespaces)\",\n\t\t\tresources:        proxiesWithCertificates(currentCertificate, oldCertificate),\n\t\t\tnamespace:        \"\",\n\t\t\texpectedErr:      errors.New(\"Some pods do not have the current trust bundle and must be restarted:\\n\\t* namespace-1/pod-1\"),\n\t\t},\n\t\t{\n\t\t\tcheckDescription: \"no proxies match CA certificate (all namespaces)\",\n\t\t\tresources:        proxiesWithCertificates(oldCertificate, oldCertificate),\n\t\t\tnamespace:        \"\",\n\t\t\texpectedErr:      errors.New(\"Some pods do not have the current trust bundle and must be restarted:\\n\\t* namespace-0/pod-0\\n\\t* namespace-1/pod-1\"),\n\t\t},\n\t\t{\n\t\t\tcheckDescription: \"some proxies match CA certificate (match in target namespace)\",\n\t\t\tresources:        proxiesWithCertificates(currentCertificate, oldCertificate),\n\t\t\tnamespace:        \"namespace-0\",\n\t\t\texpectedErr:      nil,\n\t\t},\n\t\t{\n\t\t\tcheckDescription: \"some proxies match CA certificate (unmatch in target namespace)\",\n\t\t\tresources:        proxiesWithCertificates(currentCertificate, oldCertificate),\n\t\t\tnamespace:        \"namespace-1\",\n\t\t\texpectedErr:      errors.New(\"Some pods do not have the current trust bundle and must be restarted:\\n\\t* pod-1\"),\n\t\t},\n\t\t{\n\t\t\tcheckDescription: \"no proxies match CA certificate (specific namespace)\",\n\t\t\tresources:        proxiesWithCertificates(oldCertificate, oldCertificate),\n\t\t\tnamespace:        \"namespace-0\",\n\t\t\texpectedErr:      errors.New(\"Some pods do not have the current trust bundle and must be restarted:\\n\\t* pod-0\"),\n\t\t},\n\t}\n\n\tfor id, testCase := range testCases {\n\t\ttestCase := testCase\n\t\tt.Run(fmt.Sprintf(\"%d\", id), func(t *testing.T) {\n\t\t\thc := NewHealthChecker([]CategoryID{}, &Options{})\n\t\t\thc.DataPlaneNamespace = testCase.namespace\n\n\t\t\tvar err error\n\t\t\thc.kubeAPI, err = k8s.NewFakeAPI(append(testCase.resources, linkerdIdentityTrustRoots)...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %q\", err)\n\t\t\t}\n\n\t\t\terr = hc.checkDataPlaneProxiesCertificate(context.Background())\n\t\t\tif diff := deep.Equal(err, testCase.expectedErr); diff != nil {\n\t\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestValidateControlPlanePods(t *testing.T) {\n\tpod := func(name string, phase corev1.PodPhase, ready bool) corev1.Pod {\n\t\treturn corev1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{Name: name},\n\t\t\tStatus: corev1.PodStatus{\n\t\t\t\tPhase: phase,\n\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:  strings.Split(name, \"-\")[1],\n\t\t\t\t\t\tReady: ready,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\n\tt.Run(\"Returns an error if not all pods are running\", func(t *testing.T) {\n\t\tpods := []corev1.Pod{\n\t\t\tpod(\"linkerd-destination-9849948665-37082\", corev1.PodFailed, true),\n\t\t\tpod(\"linkerd-identity-6849948664-27982\", corev1.PodFailed, true),\n\t\t}\n\n\t\terr := validateControlPlanePods(pods)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error, got nothing\")\n\t\t}\n\t\tif err.Error() != \"No running pods for \\\"linkerd-destination\\\"\" {\n\t\t\tt.Fatalf(\"Unexpected error message: %s\", err.Error())\n\t\t}\n\t})\n\n\tt.Run(\"Returns an error if not all containers are ready\", func(t *testing.T) {\n\t\tpods := []corev1.Pod{\n\t\t\tpod(\"linkerd-identity-6849948664-27982\", corev1.PodRunning, true),\n\t\t\tpod(\"linkerd-tap-6c878df6c8-2hmtd\", corev1.PodRunning, true),\n\t\t}\n\n\t\terr := validateControlPlanePods(pods)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error, got nothing\")\n\t\t}\n\t})\n\n\tt.Run(\"Returns nil if all pods are running and all containers are ready\", func(t *testing.T) {\n\t\tpods := []corev1.Pod{\n\t\t\tpod(\"linkerd-destination-9849948665-37082\", corev1.PodRunning, true),\n\t\t\tpod(\"linkerd-identity-6849948664-27982\", corev1.PodRunning, true),\n\t\t\tpod(\"linkerd-proxy-injector-5f79ff4844-\", corev1.PodRunning, true),\n\t\t}\n\n\t\terr := validateControlPlanePods(pods)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\t})\n\n\t// This test is just for ensuring full coverage of the validateControlPlanePods function\n\tt.Run(\"Returns an error if all the controller pods are not ready\", func(t *testing.T) {\n\t\tpods := []corev1.Pod{\n\t\t\tpod(\"linkerd-destination-9849948665-37082\", corev1.PodRunning, false),\n\t\t\tpod(\"linkerd-identity-6849948664-27982\", corev1.PodRunning, false),\n\t\t\tpod(\"linkerd-proxy-injector-5f79ff4844-\", corev1.PodRunning, false),\n\t\t}\n\n\t\terr := validateControlPlanePods(pods)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error, got nothing\")\n\t\t}\n\t})\n\n\tt.Run(\"Returns nil if, HA mode, at least one pod of each control plane component is ready\", func(t *testing.T) {\n\t\tpods := []corev1.Pod{\n\t\t\tpod(\"linkerd-destination-9843948665-48082\", corev1.PodRunning, true),\n\t\t\tpod(\"linkerd-destination-9843948665-48083\", corev1.PodRunning, false),\n\t\t\tpod(\"linkerd-destination-9843948665-48084\", corev1.PodFailed, false),\n\t\t\tpod(\"linkerd-identity-6849948664-27982\", corev1.PodRunning, true),\n\t\t\tpod(\"linkerd-identity-6849948664-27983\", corev1.PodRunning, false),\n\t\t\tpod(\"linkerd-identity-6849948664-27984\", corev1.PodFailed, false),\n\t\t\tpod(\"linkerd-proxy-injector-5f79ff4844-\", corev1.PodRunning, true),\n\t\t}\n\n\t\terr := validateControlPlanePods(pods)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"Returns nil if all linkerd pods are running and pod list includes non-linkerd pod\", func(t *testing.T) {\n\t\tpods := []corev1.Pod{\n\t\t\tpod(\"linkerd-destination-9843948665-48082\", corev1.PodRunning, true),\n\t\t\tpod(\"linkerd-identity-6849948664-27982\", corev1.PodRunning, true),\n\t\t\tpod(\"linkerd-proxy-injector-5f79ff4844-\", corev1.PodRunning, true),\n\t\t\tpod(\"hello-43c25d\", corev1.PodRunning, true),\n\t\t}\n\n\t\terr := validateControlPlanePods(pods)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error message: %s\", err.Error())\n\t\t}\n\t})\n}\n\nfunc TestValidateDataPlaneNamespace(t *testing.T) {\n\ttestCases := []struct {\n\t\tns     string\n\t\tresult string\n\t}{\n\t\t{\n\t\t\t\"\",\n\t\t\t\"data-plane-ns-test-cat data plane namespace exists\",\n\t\t},\n\t\t{\n\t\t\t\"bad-ns\",\n\t\t\t\"data-plane-ns-test-cat data plane namespace exists: The \\\"bad-ns\\\" namespace does not exist\",\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d/%s\", i, tc.ns), func(t *testing.T) {\n\t\t\thc := NewHealthChecker(\n\t\t\t\t[]CategoryID{},\n\t\t\t\t&Options{\n\t\t\t\t\tDataPlaneNamespace: tc.ns,\n\t\t\t\t},\n\t\t\t)\n\t\t\tvar err error\n\t\t\thc.kubeAPI, err = k8s.NewFakeAPI()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\t// create a synthetic category that only includes the \"data plane namespace exists\" check\n\t\t\thc.addCheckAsCategory(\"data-plane-ns-test-cat\", LinkerdDataPlaneChecks, \"data plane namespace exists\")\n\n\t\t\texpectedResults := []string{\n\t\t\t\ttc.result,\n\t\t\t}\n\t\t\tobs := newObserver()\n\t\t\thc.RunChecks(obs.resultFn)\n\t\t\tif diff := deep.Equal(obs.results, expectedResults); diff != nil {\n\t\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCheckDataPlanePods(t *testing.T) {\n\n\tt.Run(\"Returns an error if no inject pods were found\", func(t *testing.T) {\n\t\terr := CheckPodsRunning([]corev1.Pod{}, \"emojivoto\")\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error, got nothing\")\n\t\t}\n\t\tif err.Error() != \"no \\\"linkerd-proxy\\\" containers found in the \\\"emojivoto\\\" namespace\" {\n\t\t\tt.Fatalf(\"Unexpected error message: %s\", err.Error())\n\t\t}\n\t})\n\n\tt.Run(\"Returns an error if not all pods are running\", func(t *testing.T) {\n\t\tpods := []corev1.Pod{\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"emoji-d9c7866bb-7v74n\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase: \"Running\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"vote-bot-644b8cb6b4-g8nlr\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase: \"Running\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"voting-65b9fffd77-rlwsd\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase: \"Failed\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"web-6cfbccc48-5g8px\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase: \"Running\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: true,\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\n\t\terr := CheckPodsRunning(pods, \"emojivoto\")\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error, got nothing\")\n\t\t}\n\t\tif err.Error() != \"pod \\\"voting-65b9fffd77-rlwsd\\\" status is Failed\" {\n\t\t\tt.Fatalf(\"Unexpected error message: %s\", err.Error())\n\t\t}\n\t})\n\n\tt.Run(\"Does not return an error if the pod is Evicted\", func(t *testing.T) {\n\t\tpods := []corev1.Pod{\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"emoji-d9c7866bb-7v74n\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase: \"Evicted\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: true,\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\n\t\terr := CheckPodsRunning(pods, \"emojivoto\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Expected no error, got %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"Does not return an error if the pod is in Shutdown state\", func(t *testing.T) {\n\t\tpods := []corev1.Pod{\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"emoji-d9c7866bb-7v74n\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase:  \"Failed\",\n\t\t\t\t\tReason: \"Shutdown\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\terr := CheckPodsRunning(pods, \"emojivoto\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Expected no error, got %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"Does not return an error if the pod is in NodeShutdown state\", func(t *testing.T) {\n\t\tpods := []corev1.Pod{\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"emoji-d9c7866bb-7v74n\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase:  \"Failed\",\n\t\t\t\t\tReason: \"NodeShutdown\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\terr := CheckPodsRunning(pods, \"emojivoto\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Expected no error, got %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"Does not return an error if the pod is in Terminated state\", func(t *testing.T) {\n\t\tpods := []corev1.Pod{\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"emoji-d9c7866bb-7v74n\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase:  \"Failed\",\n\t\t\t\t\tReason: \"Terminated\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\terr := CheckPodsRunning(pods, \"emojivoto\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Expected no error, got %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"Returns an error if the proxy container is not ready\", func(t *testing.T) {\n\t\tpods := []corev1.Pod{\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"emoji-d9c7866bb-7v74n\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase: \"Running\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"vote-bot-644b8cb6b4-g8nlr\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase: \"Running\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"voting-65b9fffd77-rlwsd\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase: \"Running\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"web-6cfbccc48-5g8px\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase: \"Running\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: true,\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\n\t\terr := CheckPodsRunning(pods, \"emojivoto\")\n\t\tif err == nil {\n\t\t\tt.Fatal(\"Expected error, got nothing\")\n\t\t}\n\t\tif err.Error() != \"container \\\"linkerd-proxy\\\" in pod \\\"vote-bot-644b8cb6b4-g8nlr\\\" is not ready\" {\n\t\t\tt.Fatalf(\"Unexpected error message: %s\", err.Error())\n\t\t}\n\t})\n\n\tt.Run(\"Returns nil if all pods are running and all proxy containers are ready\", func(t *testing.T) {\n\t\tpods := []corev1.Pod{\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"emoji-d9c7866bb-7v74n\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase: \"Running\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"vote-bot-644b8cb6b4-g8nlr\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase: \"Running\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"voting-65b9fffd77-rlwsd\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase: \"Running\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"web-6cfbccc48-5g8px\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase: \"Running\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: true,\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\n\t\terr := CheckPodsRunning(pods, \"emojivoto\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\t})\n\n\t// This test relates to https://github.com/linkerd/linkerd2/issues/6128\n\tt.Run(\"Returns nil if some pods are in the Succeeded phase and their proxies are no longer ready\", func(t *testing.T) {\n\t\tpods := []corev1.Pod{\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"emoji-d9c7866bb-7v74n\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase:  \"Succeeded\",\n\t\t\t\t\tReason: \"Completed\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"vote-bot-644b8cb6b4-g8nlr\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase: \"Running\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"voting-65b9fffd77-rlwsd\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase: \"Running\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"web-6cfbccc48-5g8px\"},\n\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\tPhase:  \"Succeeded\",\n\t\t\t\t\tReason: \"Completed\",\n\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: false,\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\n\t\terr := CheckPodsRunning(pods, \"emojivoto\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\t})\n}\n\nfunc TestDataPlanePodLabels(t *testing.T) {\n\n\tt.Run(\"Returns nil if pod labels are ok\", func(t *testing.T) {\n\t\tpods := []corev1.Pod{\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:        \"emoji-d9c7866bb-7v74n\",\n\t\t\t\t\tAnnotations: map[string]string{k8s.ProxyControlPortAnnotation: \"3000\"},\n\t\t\t\t\tLabels:      map[string]string{\"app\": \"test\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\terr := checkMisconfiguredPodsLabels(pods)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"Returns error if any labels are misconfigured\", func(t *testing.T) {\n\t\tfor _, tc := range []struct {\n\t\t\tdescription      string\n\t\t\tpods             []corev1.Pod\n\t\t\texpectedErrorMsg string\n\t\t}{\n\t\t\t{\n\t\t\t\tdescription: \"config as label\",\n\t\t\t\tpods: []corev1.Pod{\n\t\t\t\t\t{\n\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\tName:   \"emoji-d9c7866bb-7v74n\",\n\t\t\t\t\t\t\tLabels: map[string]string{k8s.ProxyControlPortAnnotation: \"3000\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedErrorMsg: \"Some labels on data plane pods should be annotations:\\n\\t* /emoji-d9c7866bb-7v74n\\n\\t\\tconfig.linkerd.io/control-port\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tdescription: \"alpha config as label\",\n\t\t\t\tpods: []corev1.Pod{\n\t\t\t\t\t{\n\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\tName:   \"emoji-d9c7866bb-7v74n\",\n\t\t\t\t\t\t\tLabels: map[string]string{k8s.ProxyConfigAnnotationsPrefixAlpha + \"/alpha-setting\": \"3000\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedErrorMsg: \"Some labels on data plane pods should be annotations:\\n\\t* /emoji-d9c7866bb-7v74n\\n\\t\\tconfig.alpha.linkerd.io/alpha-setting\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tdescription: \"inject annotation as label\",\n\t\t\t\tpods: []corev1.Pod{\n\t\t\t\t\t{\n\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\tName:   \"emoji-d9c7866bb-7v74n\",\n\t\t\t\t\t\t\tLabels: map[string]string{k8s.ProxyInjectAnnotation: \"enable\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedErrorMsg: \"Some labels on data plane pods should be annotations:\\n\\t* /emoji-d9c7866bb-7v74n\\n\\t\\tlinkerd.io/inject\",\n\t\t\t},\n\t\t} {\n\t\t\ttc := tc // pin\n\t\t\tt.Run(tc.description, func(t *testing.T) {\n\t\t\t\terr := checkMisconfiguredPodsLabels(tc.pods)\n\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatal(\"Expected error, got nothing\")\n\t\t\t\t}\n\n\t\t\t\tif err.Error() != tc.expectedErrorMsg {\n\t\t\t\t\tt.Fatalf(\"Unexpected error message: %s\", err.Error())\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestServicesLabels(t *testing.T) {\n\n\tt.Run(\"Returns nil if service labels are ok\", func(t *testing.T) {\n\t\tservices := []corev1.Service{\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:        \"emoji-d9c7866bb-7v74n\",\n\t\t\t\t\tAnnotations: map[string]string{k8s.ProxyControlPortAnnotation: \"3000\"},\n\t\t\t\t\tLabels:      map[string]string{\"app\": \"test\", k8s.DefaultExportedServiceSelector: \"true\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\terr := checkMisconfiguredServiceLabels(services)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"Returns error if service labels or annotation misconfigured\", func(t *testing.T) {\n\t\tfor _, tc := range []struct {\n\t\t\tdescription      string\n\t\t\tservices         []corev1.Service\n\t\t\texpectedErrorMsg string\n\t\t}{\n\t\t\t{\n\t\t\t\tdescription: \"config as label\",\n\t\t\t\tservices: []corev1.Service{\n\t\t\t\t\t{\n\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\tName:   \"emoji-d9c7866bb-7v74n\",\n\t\t\t\t\t\t\tLabels: map[string]string{k8s.ProxyControlPortAnnotation: \"3000\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedErrorMsg: \"Some labels on data plane services should be annotations:\\n\\t* /emoji-d9c7866bb-7v74n\\n\\t\\tconfig.linkerd.io/control-port\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tdescription: \"alpha config as label\",\n\t\t\t\tservices: []corev1.Service{\n\t\t\t\t\t{\n\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\tName:   \"emoji-d9c7866bb-7v74n\",\n\t\t\t\t\t\t\tLabels: map[string]string{k8s.ProxyConfigAnnotationsPrefixAlpha + \"/alpha-setting\": \"3000\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedErrorMsg: \"Some labels on data plane services should be annotations:\\n\\t* /emoji-d9c7866bb-7v74n\\n\\t\\tconfig.alpha.linkerd.io/alpha-setting\",\n\t\t\t},\n\t\t} {\n\t\t\ttc := tc // pin\n\t\t\tt.Run(tc.description, func(t *testing.T) {\n\t\t\t\terr := checkMisconfiguredServiceLabels(tc.services)\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatal(\"Expected error, got nothing\")\n\t\t\t\t}\n\t\t\t\tif err.Error() != tc.expectedErrorMsg {\n\t\t\t\t\tt.Fatalf(\"Unexpected error message: %s\", err.Error())\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestServicesAnnotations(t *testing.T) {\n\n\tt.Run(\"Returns nil if service annotations are ok\", func(t *testing.T) {\n\t\tservices := []corev1.Service{\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:        \"emoji-d9c7866bb-7v74n\",\n\t\t\t\t\tAnnotations: map[string]string{k8s.ProxyControlPortAnnotation: \"3000\"},\n\t\t\t\t\tLabels:      map[string]string{\"app\": \"test\", k8s.DefaultExportedServiceSelector: \"true\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\terr := checkMisconfiguredServiceAnnotations(services)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\t})\n\n\tt.Run(\"Returns error if service annotations are misconfigured\", func(t *testing.T) {\n\t\tfor _, tc := range []struct {\n\t\t\tdescription      string\n\t\t\tservices         []corev1.Service\n\t\t\texpectedErrorMsg string\n\t\t}{\n\t\t\t{\n\t\t\t\tdescription: \"mirror as annotations\",\n\t\t\t\tservices: []corev1.Service{\n\t\t\t\t\t{\n\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\tName:        \"emoji-d9c7866bb-7v74n\",\n\t\t\t\t\t\t\tAnnotations: map[string]string{k8s.DefaultExportedServiceSelector: \"true\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedErrorMsg: \"Some annotations on data plane services should be labels:\\n\\t* /emoji-d9c7866bb-7v74n\\n\\t\\tmirror.linkerd.io/exported\",\n\t\t\t},\n\t\t} {\n\t\t\ttc := tc // pin\n\t\t\tt.Run(tc.description, func(t *testing.T) {\n\t\t\t\terr := checkMisconfiguredServiceAnnotations(tc.services)\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatal(\"Expected error, got nothing\")\n\t\t\t\t}\n\t\t\t\tif err.Error() != tc.expectedErrorMsg {\n\t\t\t\t\tt.Fatalf(\"Unexpected error message: %s\", err.Error())\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc TestFetchCurrentConfiguration(t *testing.T) {\n\tdefaultValues, err := linkerd2.NewValues()\n\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error validating options: %v\", err)\n\t}\n\n\ttestCases := []struct {\n\t\tk8sConfigs []string\n\t\texpected   *linkerd2.Values\n\t\terr        error\n\t}{\n\t\t{\n\t\t\t[]string{`\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\ndata:\n  global: |\n    {\"linkerdNamespace\":\"linkerd\",\"cniEnabled\":false,\"version\":\"install-control-plane-version\",\"identityContext\":{\"trustDomain\":\"cluster.local\",\"trustAnchorsPem\":\"fake-trust-anchors-pem\",\"issuanceLifetime\":\"86400s\",\"clockSkewAllowance\":\"20s\"}}\n  proxy: |\n    {\"proxyImage\":{\"imageName\":\"cr.l5d.io/linkerd/proxy\",\"pullPolicy\":\"IfNotPresent\"},\"proxyInitImage\":{\"imageName\":\"cr.l5d.io/linkerd/proxy-init\",\"pullPolicy\":\"IfNotPresent\"},\"controlPort\":{\"port\":4190},\"ignoreInboundPorts\":[],\"ignoreOutboundPorts\":[],\"inboundPort\":{\"port\":4143},\"adminPort\":{\"port\":4191},\"outboundPort\":{\"port\":4140},\"resource\":{\"requestCpu\":\"\",\"requestMemory\":\"\",\"limitCpu\":\"\",\"limitMemory\":\"\"},\"proxyUid\":\"2102\",\"proxyGid\":\"2102\",\"logLevel\":{\"level\":\"warn,linkerd=info\"},\"disableExternalProfiles\":true,\"proxyVersion\":\"install-proxy-version\",\"proxy_init_image_version\":\"v2.3.0\",\"debugImage\":{\"imageName\":\"cr.l5d.io/linkerd/debug\",\"pullPolicy\":\"IfNotPresent\"},\"debugImageVersion\":\"install-debug-version\"}\n  install: |\n    {\"cliVersion\":\"dev-undefined\",\"flags\":[]}\n  values: |\n    controllerImage: ControllerImage\n    controllerReplicas: 1\n    controllerUID: 2103\n    controllerGID: 2103\n    debugContainer: null\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: false\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    nodeAffinity: null\n    cliVersion: CliVersion\n    clusterDomain: cluster.local\n    clusterNetworks: ClusterNetworks\n    cniEnabled: false\n    controlPlaneTracing: false\n    controllerLogLevel: ControllerLogLevel\n    enableEndpointSlices: false\n    highAvailability: false\n    imagePullPolicy: ImagePullPolicy\n    imagePullSecrets: null\n    linkerdVersion: \"\"\n    prometheusUrl: \"\"\n    proxy:\n      capabilities: null\n      component: linkerd-controller\n      disableTap: false\n      enableExternalProfiles: false\n      image:\n        name: ProxyImageName\n        pullPolicy: ImagePullPolicy\n        version: ProxyVersion\n      inboundConnectTimeout: \"\"\n      isGateway: false\n      logFormat: plain\n      logLevel: warn,linkerd=info\n      opaquePorts: \"\"\n      outboundConnectTimeout: \"\"\n      ports:\n        admin: 4191\n        control: 4190\n        inbound: 4143\n        outbound: 4140\n      requireIdentityOnInboundPorts: \"\"\n      resources: null\n      saMountPath: null\n      uid: 2102\n      gid: 2102\n      waitBeforeExitSeconds: 0\n      workloadKind: deployment\n    proxyContainerName: ProxyContainerName\n    proxyInit:\n      capabilities: null\n      closeWaitTimeoutSecs: 0\n      ignoreInboundPorts: \"\"\n      ignoreOutboundPorts: \"\"\n      image:\n        name: ProxyInitImageName\n        pullPolicy: ImagePullPolicy\n        version: ProxyInitVersion\n      resources: null\n      saMountPath: null\n      xtMountPath:\n        mountPath: /run\n        name: linkerd-proxy-init-xtables-lock\n        readOnly: false\n    heartbeatResources: null\n    heartbeatSchedule: \"\"\n    identityProxyResources: null\n    identityResources: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    stage: \"\"\n    tolerations: null\n    webhookFailurePolicy: WebhookFailurePolicy\n`,\n\t\t\t},\n\t\t\t&linkerd2.Values{\n\t\t\t\tControllerImage:      \"ControllerImage\",\n\t\t\t\tControllerUID:        2103,\n\t\t\t\tControllerGID:        2103,\n\t\t\t\tEnableH2Upgrade:      true,\n\t\t\t\tWebhookFailurePolicy: \"WebhookFailurePolicy\",\n\t\t\t\tNodeSelector:         defaultValues.NodeSelector,\n\t\t\t\tTolerations:          defaultValues.Tolerations,\n\t\t\t\tClusterDomain:        \"cluster.local\",\n\t\t\t\tClusterNetworks:      \"ClusterNetworks\",\n\t\t\t\tImagePullPolicy:      \"ImagePullPolicy\",\n\t\t\t\tCliVersion:           \"CliVersion\",\n\t\t\t\tControllerLogLevel:   \"ControllerLogLevel\",\n\t\t\t\tProxyContainerName:   \"ProxyContainerName\",\n\t\t\t\tCNIEnabled:           false,\n\t\t\t\tProxy: &linkerd2.Proxy{\n\t\t\t\t\tImage: &linkerd2.Image{\n\t\t\t\t\t\tName:       \"ProxyImageName\",\n\t\t\t\t\t\tPullPolicy: \"ImagePullPolicy\",\n\t\t\t\t\t\tVersion:    \"ProxyVersion\",\n\t\t\t\t\t},\n\t\t\t\t\tLogLevel:  \"warn,linkerd=info\",\n\t\t\t\t\tLogFormat: \"plain\",\n\t\t\t\t\tPorts: &linkerd2.Ports{\n\t\t\t\t\t\tAdmin:    4191,\n\t\t\t\t\t\tControl:  4190,\n\t\t\t\t\t\tInbound:  4143,\n\t\t\t\t\t\tOutbound: 4140,\n\t\t\t\t\t},\n\t\t\t\t\tUID: 2102,\n\t\t\t\t\tGID: 2102,\n\t\t\t\t},\n\t\t\t\tProxyInit: &linkerd2.ProxyInit{\n\t\t\t\t\tXTMountPath: &linkerd2.VolumeMountPath{\n\t\t\t\t\t\tMountPath: \"/run\",\n\t\t\t\t\t\tName:      \"linkerd-proxy-init-xtables-lock\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tControllerReplicas: 1,\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t[]string{`\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\ndata:\n  global: |\n    {\"linkerdNamespace\":\"linkerd\",\"cniEnabled\":false,\"version\":\"install-control-plane-version\",\"identityContext\":{\"trustDomain\":\"cluster.local\",\"trustAnchorsPem\":\"fake-trust-anchors-pem\",\"issuanceLifetime\":\"86400s\",\"clockSkewAllowance\":\"20s\"}}\n  proxy: |\n    {\"proxyImage\":{\"imageName\":\"cr.l5d.io/linkerd/proxy\",\"pullPolicy\":\"IfNotPresent\"},\"controlPort\":{\"port\":4190},\"ignoreInboundPorts\":[],\"ignoreOutboundPorts\":[],\"inboundPort\":{\"port\":4143},\"adminPort\":{\"port\":4191},\"outboundPort\":{\"port\":4140},\"resource\":{\"requestCpu\":\"\",\"requestMemory\":\"\",\"limitCpu\":\"\",\"limitMemory\":\"\"},\"proxyUid\":\"2102\",\"proxyGid\":\"2102\",\"logLevel\":{\"level\":\"warn,linkerd=info\"},\"disableExternalProfiles\":true,\"proxyVersion\":\"install-proxy-version\",\"proxy_init_image_version\":\"v2.3.0\",\"debugImage\":{\"imageName\":\"cr.l5d.io/linkerd/debug\",\"pullPolicy\":\"IfNotPresent\"},\"debugImageVersion\":\"install-debug-version\"}\n  install: |\n    {\"cliVersion\":\"dev-undefined\",\"flags\":[]}\n  values: |\n    controllerImage: ControllerImage\n    controllerReplicas: 1\n    controllerUID: 2103\n    controllerGID: 2103\n    debugContainer: null\n    destinationProxyResources: null\n    destinationResources: null\n    disableHeartBeat: false\n    enableH2Upgrade: true\n    enablePodAntiAffinity: false\n    global:\n      cliVersion: CliVersion\n      clusterDomain: cluster.local\n      clusterNetworks: ClusterNetworks\n      cniEnabled: false\n      controlPlaneTracing: false\n      controllerLogLevel: ControllerLogLevel\n      enableEndpointSlices: false\n      highAvailability: false\n      imagePullPolicy: ImagePullPolicy\n      imagePullSecrets: null\n      linkerdVersion: \"\"\n      prometheusUrl: \"\"\n      proxy:\n        capabilities: null\n        component: linkerd-controller\n        disableTap: false\n        enableExternalProfiles: false\n        image:\n          name: ProxyImageName\n          pullPolicy: ImagePullPolicy\n          version: ProxyVersion\n        inboundConnectTimeout: \"\"\n        isGateway: false\n        logFormat: plain\n        logLevel: warn,linkerd=info\n        opaquePorts: \"\"\n        outboundConnectTimeout: \"\"\n        ports:\n          admin: 4191\n          control: 4190\n          inbound: 4143\n          outbound: 4140\n        requireIdentityOnInboundPorts: \"\"\n        resources: null\n        saMountPath: null\n        uid: 2102\n        gid: 2102\n        waitBeforeExitSeconds: 0\n        workloadKind: deployment\n      proxyContainerName: ProxyContainerName\n      proxyInit:\n        capabilities: null\n        closeWaitTimeoutSecs: 0\n        ignoreInboundPorts: \"\"\n        ignoreOutboundPorts: \"\"\n        resources:\n        saMountPath: null\n        xtMountPath:\n          mountPath: /run\n          name: linkerd-proxy-init-xtables-lock\n          readOnly: false\n    heartbeatResources: null\n    heartbeatSchedule: \"\"\n    identityProxyResources: null\n    identityResources: null\n    nodeSelector:\n      kubernetes.io/os: linux\n    proxyInjectorProxyResources: null\n    proxyInjectorResources: null\n    stage: \"\"\n    tolerations: null\n    webhookFailurePolicy: WebhookFailurePolicy\n`,\n\t\t\t},\n\t\t\t&linkerd2.Values{\n\t\t\t\tControllerImage:      \"ControllerImage\",\n\t\t\t\tControllerUID:        2103,\n\t\t\t\tControllerGID:        2103,\n\t\t\t\tEnableH2Upgrade:      true,\n\t\t\t\tWebhookFailurePolicy: \"WebhookFailurePolicy\",\n\t\t\t\tNodeSelector:         defaultValues.NodeSelector,\n\t\t\t\tTolerations:          defaultValues.Tolerations,\n\t\t\t\tClusterDomain:        \"cluster.local\",\n\t\t\t\tClusterNetworks:      \"ClusterNetworks\",\n\t\t\t\tImagePullPolicy:      \"ImagePullPolicy\",\n\t\t\t\tCliVersion:           \"CliVersion\",\n\t\t\t\tControllerLogLevel:   \"ControllerLogLevel\",\n\t\t\t\tProxyContainerName:   \"ProxyContainerName\",\n\t\t\t\tCNIEnabled:           false,\n\t\t\t\tProxy: &linkerd2.Proxy{\n\t\t\t\t\tImage: &linkerd2.Image{\n\t\t\t\t\t\tName:       \"ProxyImageName\",\n\t\t\t\t\t\tPullPolicy: \"ImagePullPolicy\",\n\t\t\t\t\t\tVersion:    \"ProxyVersion\",\n\t\t\t\t\t},\n\t\t\t\t\tLogLevel:  \"warn,linkerd=info\",\n\t\t\t\t\tLogFormat: \"plain\",\n\t\t\t\t\tPorts: &linkerd2.Ports{\n\t\t\t\t\t\tAdmin:    4191,\n\t\t\t\t\t\tControl:  4190,\n\t\t\t\t\t\tInbound:  4143,\n\t\t\t\t\t\tOutbound: 4140,\n\t\t\t\t\t},\n\t\t\t\t\tUID: 2102,\n\t\t\t\t\tGID: 2102,\n\t\t\t\t},\n\t\t\t\tProxyInit: &linkerd2.ProxyInit{\n\t\t\t\t\tXTMountPath: &linkerd2.VolumeMountPath{\n\t\t\t\t\t\tMountPath: \"/run\",\n\t\t\t\t\t\tName:      \"linkerd-proxy-init-xtables-lock\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tControllerReplicas: 1,\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\tclientset, err := k8s.NewFakeAPI(tc.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\t_, values, err := FetchCurrentConfiguration(context.Background(), clientset, \"linkerd\")\n\t\t\tif diff := deep.Equal(err, tc.err); diff != nil {\n\t\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t\t}\n\t\t\tif diff := deep.Equal(values, tc.expected); diff != nil {\n\t\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc getFakeConfigMap(scheme string, issuerCerts *issuercerts.IssuerCertData) string {\n\tanchors, _ := json.Marshal(issuerCerts.TrustAnchors)\n\treturn fmt.Sprintf(`\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-config\n  namespace: linkerd\ndata:\n  values: |\n    namespace: linkerd\n    identityTrustAnchorsPEM: %s\n    identityTrustDomain: cluster.local\n    identity:\n      issuer:\n        scheme: %s\n---\n`, anchors, scheme)\n}\n\nfunc getFakeSecret(scheme string, issuerCerts *issuercerts.IssuerCertData) string {\n\tif scheme == k8s.IdentityIssuerSchemeLinkerd {\n\t\treturn fmt.Sprintf(`\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\ndata:\n  crt.pem: %s\n  key.pem: %s\n---\n`, base64.StdEncoding.EncodeToString([]byte(issuerCerts.IssuerCrt)), base64.StdEncoding.EncodeToString([]byte(issuerCerts.IssuerKey)))\n\t}\n\treturn fmt.Sprintf(\n\t\t`\nkind: Secret\napiVersion: v1\nmetadata:\n  name: linkerd-identity-issuer\n  namespace: linkerd\ndata:\n  ca.crt: %s\n  tls.crt: %s\n  tls.key: %s\n---\n`, base64.StdEncoding.EncodeToString([]byte(issuerCerts.TrustAnchors)), base64.StdEncoding.EncodeToString([]byte(issuerCerts.IssuerCrt)), base64.StdEncoding.EncodeToString([]byte(issuerCerts.IssuerKey)))\n}\n\nfunc createIssuerData(dnsName string, notBefore, notAfter time.Time) *issuercerts.IssuerCertData {\n\t// Generate a new root key.\n\tkey, _ := tls.GenerateKey()\n\n\trootCa, _ := tls.CreateRootCA(dnsName, key, tls.Validity{\n\t\tLifetime:  notAfter.Sub(notBefore),\n\t\tValidFrom: &notBefore,\n\t})\n\n\treturn &issuercerts.IssuerCertData{\n\t\tTrustAnchors: rootCa.Cred.Crt.EncodeCertificatePEM(),\n\t\tIssuerCrt:    rootCa.Cred.Crt.EncodeCertificatePEM(),\n\t\tIssuerKey:    rootCa.Cred.EncodePrivateKeyPEM(),\n\t}\n}\n\ntype lifeSpan struct {\n\tstarts time.Time\n\tends   time.Time\n}\n\nfunc runIdentityCheckTestCase(ctx context.Context, t *testing.T, testID int, testDescription string, checkerToTest string, fakeConfigMap string, fakeSecret string, expectedOutput []string) {\n\tt.Run(fmt.Sprintf(\"%d/%s\", testID, testDescription), func(t *testing.T) {\n\t\thc := NewHealthChecker(\n\t\t\t[]CategoryID{},\n\t\t\t&Options{\n\t\t\t\tDataPlaneNamespace: \"linkerd\",\n\t\t\t},\n\t\t)\n\t\thc.addCheckAsCategory(\"linkerd-identity-test-cat\", LinkerdIdentity, checkerToTest)\n\t\tvar err error\n\t\thc.ControlPlaneNamespace = \"linkerd\"\n\t\thc.kubeAPI, err = k8s.NewFakeAPI(fakeConfigMap, fakeSecret)\n\t\t_, hc.linkerdConfig, _ = hc.checkLinkerdConfigConfigMap(ctx)\n\n\t\tif testDescription != \"certificate config is valid\" {\n\t\t\thc.issuerCert, hc.trustAnchors, _ = hc.checkCertificatesConfig(ctx)\n\t\t}\n\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t}\n\n\t\tobs := newObserver()\n\t\thc.RunChecks(obs.resultFn)\n\t\tif diff := deep.Equal(obs.results, expectedOutput); diff != nil {\n\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t}\n\t})\n}\n\nfunc TestLinkerdIdentityCheckCertConfig(t *testing.T) {\n\tvar testCases = []struct {\n\t\tcheckDescription            string\n\t\ttlsSecretScheme             string\n\t\tschemeInConfig              string\n\t\texpectedOutput              []string\n\t\tconfigMapIssuerDataModifier func(issuercerts.IssuerCertData) issuercerts.IssuerCertData\n\t\ttlsSecretIssuerDataModifier func(issuercerts.IssuerCertData) issuercerts.IssuerCertData\n\t}{\n\t\t{\n\t\t\tcheckDescription: \"works with valid cert and linkerd.io/tls secret\",\n\t\t\ttlsSecretScheme:  k8s.IdentityIssuerSchemeLinkerd,\n\t\t\tschemeInConfig:   k8s.IdentityIssuerSchemeLinkerd,\n\t\t\texpectedOutput:   []string{\"linkerd-identity-test-cat certificate config is valid\"},\n\t\t},\n\t\t{\n\t\t\tcheckDescription: \"works with valid cert and kubernetes.io/tls secret\",\n\t\t\ttlsSecretScheme:  string(corev1.SecretTypeTLS),\n\t\t\tschemeInConfig:   string(corev1.SecretTypeTLS),\n\t\t\texpectedOutput:   []string{\"linkerd-identity-test-cat certificate config is valid\"},\n\t\t},\n\t\t{\n\t\t\tcheckDescription: \"works if config scheme is empty and secret scheme is linkerd.io/tls (pre 2.7)\",\n\t\t\ttlsSecretScheme:  k8s.IdentityIssuerSchemeLinkerd,\n\t\t\tschemeInConfig:   \"\",\n\t\t\texpectedOutput:   []string{\"linkerd-identity-test-cat certificate config is valid\"},\n\t\t},\n\t\t{\n\t\t\tcheckDescription: \"fails if config scheme is empty and secret scheme is kubernetes.io/tls (pre 2.7)\",\n\t\t\ttlsSecretScheme:  string(corev1.SecretTypeTLS),\n\t\t\tschemeInConfig:   \"\",\n\t\t\texpectedOutput:   []string{\"linkerd-identity-test-cat certificate config is valid: key crt.pem containing the issuer certificate needs to exist in secret linkerd-identity-issuer if --identity-external-issuer=false\"},\n\t\t},\n\t\t{\n\t\t\tcheckDescription: \"fails when config scheme is linkerd.io/tls but secret scheme is kubernetes.io/tls in config is different than the one in the issuer secret\",\n\t\t\ttlsSecretScheme:  string(corev1.SecretTypeTLS),\n\t\t\tschemeInConfig:   k8s.IdentityIssuerSchemeLinkerd,\n\t\t\texpectedOutput:   []string{\"linkerd-identity-test-cat certificate config is valid: key crt.pem containing the issuer certificate needs to exist in secret linkerd-identity-issuer if --identity-external-issuer=false\"},\n\t\t},\n\t\t{\n\t\t\tcheckDescription: \"fails when config scheme is kubernetes.io/tls but secret scheme is linkerd.io/tls in config is different than the one in the issuer secret\",\n\t\t\ttlsSecretScheme:  k8s.IdentityIssuerSchemeLinkerd,\n\t\t\tschemeInConfig:   string(corev1.SecretTypeTLS),\n\t\t\texpectedOutput:   []string{\"linkerd-identity-test-cat certificate config is valid: key ca.crt containing the trust anchors needs to exist in secret linkerd-identity-issuer if --identity-external-issuer=true\"},\n\t\t},\n\t\t{\n\t\t\tcheckDescription: \"fails when trying to parse trust anchors from secret (extra newline in secret)\",\n\t\t\ttlsSecretScheme:  string(corev1.SecretTypeTLS),\n\t\t\tschemeInConfig:   string(corev1.SecretTypeTLS),\n\t\t\texpectedOutput:   []string{\"linkerd-identity-test-cat certificate config is valid: not a PEM certificate\"},\n\t\t\ttlsSecretIssuerDataModifier: func(issuerData issuercerts.IssuerCertData) issuercerts.IssuerCertData {\n\t\t\t\tissuerData.TrustAnchors += \"\\n\"\n\t\t\t\treturn issuerData\n\t\t\t},\n\t\t},\n\t}\n\n\tfor id, testCase := range testCases {\n\t\ttestCase := testCase\n\t\tissuerData := createIssuerData(\"identity.linkerd.cluster.local\", time.Now().AddDate(-1, 0, 0), time.Now().AddDate(1, 0, 0))\n\t\tvar fakeConfigMap string\n\t\tif testCase.configMapIssuerDataModifier != nil {\n\t\t\tmodifiedIssuerData := testCase.configMapIssuerDataModifier(*issuerData)\n\t\t\tfakeConfigMap = getFakeConfigMap(testCase.schemeInConfig, &modifiedIssuerData)\n\t\t} else {\n\t\t\tfakeConfigMap = getFakeConfigMap(testCase.schemeInConfig, issuerData)\n\t\t}\n\n\t\tvar fakeSecret string\n\t\tif testCase.tlsSecretIssuerDataModifier != nil {\n\t\t\tmodifiedIssuerData := testCase.tlsSecretIssuerDataModifier(*issuerData)\n\t\t\tfakeSecret = getFakeSecret(testCase.tlsSecretScheme, &modifiedIssuerData)\n\t\t} else {\n\t\t\tfakeSecret = getFakeSecret(testCase.tlsSecretScheme, issuerData)\n\t\t}\n\t\trunIdentityCheckTestCase(context.Background(), t, id, testCase.checkDescription, \"certificate config is valid\", fakeConfigMap, fakeSecret, testCase.expectedOutput)\n\t}\n}\n\nfunc TestLinkerdIdentityCheckCertValidity(t *testing.T) {\n\tvar testCases = []struct {\n\t\tcheckDescription string\n\t\tcheckerToTest    string\n\t\tlifespan         *lifeSpan\n\t\texpectedOutput   []string\n\t}{\n\t\t{\n\t\t\tcheckerToTest:    \"trust anchors are within their validity period\",\n\t\t\tcheckDescription: \"fails when the only anchor is not valid yet\",\n\t\t\tlifespan: &lifeSpan{\n\t\t\t\tstarts: time.Date(2100, 1, 1, 1, 1, 1, 1, time.UTC),\n\t\t\t\tends:   time.Date(2101, 1, 1, 1, 1, 1, 1, time.UTC),\n\t\t\t},\n\t\t\texpectedOutput: []string{\"linkerd-identity-test-cat trust anchors are within their validity period: Invalid anchors:\\n\\t* 1 identity.linkerd.cluster.local not valid before: 2100-01-01T01:00:51Z\"},\n\t\t},\n\t\t{\n\t\t\tcheckerToTest:    \"trust anchors are within their validity period\",\n\t\t\tcheckDescription: \"fails when the only trust anchor is expired\",\n\t\t\tlifespan: &lifeSpan{\n\t\t\t\tstarts: time.Date(1989, 1, 1, 1, 1, 1, 1, time.UTC),\n\t\t\t\tends:   time.Date(1990, 1, 1, 1, 1, 1, 1, time.UTC),\n\t\t\t},\n\t\t\texpectedOutput: []string{\"linkerd-identity-test-cat trust anchors are within their validity period: Invalid anchors:\\n\\t* 1 identity.linkerd.cluster.local not valid anymore. Expired on 1990-01-01T01:01:11Z\"},\n\t\t},\n\t\t{\n\t\t\tcheckerToTest:    \"issuer cert is within its validity period\",\n\t\t\tcheckDescription: \"fails when the issuer cert is not valid yet\",\n\t\t\tlifespan: &lifeSpan{\n\t\t\t\tstarts: time.Date(2100, 1, 1, 1, 1, 1, 1, time.UTC),\n\t\t\t\tends:   time.Date(2101, 1, 1, 1, 1, 1, 1, time.UTC),\n\t\t\t},\n\t\t\texpectedOutput: []string{\"linkerd-identity-test-cat issuer cert is within its validity period: issuer certificate is not valid before: 2100-01-01T01:00:51Z\"},\n\t\t},\n\t\t{\n\t\t\tcheckerToTest:    \"issuer cert is within its validity period\",\n\t\t\tcheckDescription: \"fails when the issuer cert is expired\",\n\t\t\tlifespan: &lifeSpan{\n\t\t\t\tstarts: time.Date(1989, 1, 1, 1, 1, 1, 1, time.UTC),\n\t\t\t\tends:   time.Date(1990, 1, 1, 1, 1, 1, 1, time.UTC),\n\t\t\t},\n\t\t\texpectedOutput: []string{\"linkerd-identity-test-cat issuer cert is within its validity period: issuer certificate is not valid anymore. Expired on 1990-01-01T01:01:11Z\"},\n\t\t},\n\t}\n\n\tfor id, testCase := range testCases {\n\t\ttestCase := testCase\n\t\tissuerData := createIssuerData(\"identity.linkerd.cluster.local\", testCase.lifespan.starts, testCase.lifespan.ends)\n\t\tfakeConfigMap := getFakeConfigMap(k8s.IdentityIssuerSchemeLinkerd, issuerData)\n\t\tfakeSecret := getFakeSecret(k8s.IdentityIssuerSchemeLinkerd, issuerData)\n\t\trunIdentityCheckTestCase(context.Background(), t, id, testCase.checkDescription, testCase.checkerToTest, fakeConfigMap, fakeSecret, testCase.expectedOutput)\n\t}\n}\n\ntype fakeCniResourcesOpts struct {\n\thasConfigMap          bool\n\thasClusterRole        bool\n\thasClusterRoleBinding bool\n\thasServiceAccount     bool\n\thasDaemonSet          bool\n\tscheduled             int\n\tready                 int\n}\n\nfunc getFakeCniResources(opts fakeCniResourcesOpts) []string {\n\tvar resources []string\n\n\tif opts.hasConfigMap {\n\t\tresources = append(resources, `\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: linkerd-cni-config\n  namespace: test-ns\n  labels:\n    linkerd.io/cni-resource: \"true\"\ndata:\n  dest_cni_net_dir: \"/etc/cni/net.d\"\n---\n`)\n\t}\n\n\tif opts.hasClusterRole {\n\t\tresources = append(resources, `\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"nodes\", \"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n---\n`)\n\t}\n\n\tif opts.hasClusterRoleBinding {\n\t\tresources = append(resources, `\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-cni\n  labels:\n    linkerd.io/cni-resource: \"true\"\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-cni\nsubjects:\n- kind: ServiceAccount\n  name: linkerd-cni\n  namespace: test-ns\n---\n`)\n\t}\n\n\tif opts.hasServiceAccount {\n\t\tresources = append(resources, `\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: linkerd-cni\n  namespace: test-ns\n  labels:\n    linkerd.io/cni-resource: \"true\"\n---\n`)\n\t}\n\n\tif opts.hasDaemonSet {\n\t\tresources = append(resources, fmt.Sprintf(`\nkind: DaemonSet\napiVersion: apps/v1\nmetadata:\n  name: linkerd-cni\n  namespace: test-ns\n  labels:\n    k8s-app: linkerd-cni\n    linkerd.io/cni-resource: \"true\"\n  annotations:\n    linkerd.io/created-by: linkerd/cli git-b4266c93\nspec:\n  selector:\n    matchLabels:\n      k8s-app: linkerd-cni\n  updateStrategy:\n    type: RollingUpdate\n    rollingUpdate:\n      maxUnavailable: 1\n  template:\n    metadata:\n      labels:\n        k8s-app: linkerd-cni\n      annotations:\n        linkerd.io/created-by: linkerd/cli git-b4266c93\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      serviceAccountName: linkerd-cni\n      containers:\n      - name: install-cni\n        image: cr.l5d.io/linkerd/cni-plugin:v1.6.6\n        env:\n        - name: DEST_CNI_NET_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_net_dir\n        - name: DEST_CNI_BIN_DIR\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: dest_cni_bin_dir\n        - name: CNI_NETWORK_CONFIG\n          valueFrom:\n            configMapKeyRef:\n              name: linkerd-cni-config\n              key: cni_network_config\n        - name: SLEEP\n          value: \"true\"\n        lifecycle:\n          preStop:\n            exec:\n              command: [\"kill\",\"-15\",\"1\"]\n        volumeMounts:\n        - mountPath: /host/opt/cni/bin\n          name: cni-bin-dir\n        - mountPath: /host/etc/cni/net.d\n          name: cni-net-dir\n      volumes:\n      - name: cni-bin-dir\n        hostPath:\n          path: /opt/cni/bin\n      - name: cni-net-dir\n        hostPath:\n          path: /etc/cni/net.d\nstatus:\n  desiredNumberScheduled: %d\n  numberReady: %d\n---\n`, opts.scheduled, opts.ready))\n\t}\n\n\treturn resources\n\n}\n\nfunc TestCniChecks(t *testing.T) {\n\ttestCases := []struct {\n\t\tdescription  string\n\t\ttestCaseOpts fakeCniResourcesOpts\n\t\tresults      []string\n\t}{\n\t\t{\n\t\t\t\"fails when there is no config map\",\n\t\t\tfakeCniResourcesOpts{},\n\t\t\t[]string{\"linkerd-cni-plugin cni plugin ConfigMap exists: configmaps \\\"linkerd-cni-config\\\" not found\"},\n\t\t},\n\t\t{\n\t\t\t\"fails then there is no ClusterRole\",\n\t\t\tfakeCniResourcesOpts{hasConfigMap: true},\n\t\t\t[]string{\n\t\t\t\t\"linkerd-cni-plugin cni plugin ConfigMap exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin ClusterRole exists: missing ClusterRole: linkerd-cni\"},\n\t\t},\n\t\t{\n\t\t\t\"fails then there is no ClusterRoleBinding\",\n\t\t\tfakeCniResourcesOpts{hasConfigMap: true, hasClusterRole: true},\n\t\t\t[]string{\n\t\t\t\t\"linkerd-cni-plugin cni plugin ConfigMap exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin ClusterRole exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin ClusterRoleBinding exists: missing ClusterRoleBinding: linkerd-cni\"},\n\t\t},\n\t\t{\n\t\t\t\"fails then there is no ServiceAccount\",\n\t\t\tfakeCniResourcesOpts{hasConfigMap: true, hasClusterRole: true, hasClusterRoleBinding: true},\n\t\t\t[]string{\n\t\t\t\t\"linkerd-cni-plugin cni plugin ConfigMap exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin ClusterRole exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin ClusterRoleBinding exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin ServiceAccount exists: missing ServiceAccount: linkerd-cni\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"fails then there is no DaemonSet\",\n\t\t\tfakeCniResourcesOpts{hasConfigMap: true, hasClusterRole: true, hasClusterRoleBinding: true, hasServiceAccount: true},\n\t\t\t[]string{\n\t\t\t\t\"linkerd-cni-plugin cni plugin ConfigMap exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin ClusterRole exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin ClusterRoleBinding exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin ServiceAccount exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin DaemonSet exists: missing DaemonSet: linkerd-cni\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"fails then there is nodes are not ready\",\n\t\t\tfakeCniResourcesOpts{hasConfigMap: true, hasClusterRole: true, hasClusterRoleBinding: true, hasServiceAccount: true, hasDaemonSet: true, scheduled: 5, ready: 4},\n\t\t\t[]string{\n\t\t\t\t\"linkerd-cni-plugin cni plugin ConfigMap exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin ClusterRole exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin ClusterRoleBinding exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin ServiceAccount exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin DaemonSet exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin pod is running on all nodes: number ready: 4, number scheduled: 5\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"fails then there is nodes are not ready\",\n\t\t\tfakeCniResourcesOpts{hasConfigMap: true, hasClusterRole: true, hasClusterRoleBinding: true, hasServiceAccount: true, hasDaemonSet: true, scheduled: 5, ready: 5},\n\t\t\t[]string{\n\t\t\t\t\"linkerd-cni-plugin cni plugin ConfigMap exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin ClusterRole exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin ClusterRoleBinding exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin ServiceAccount exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin DaemonSet exists\",\n\t\t\t\t\"linkerd-cni-plugin cni plugin pod is running on all nodes\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(tc.description, func(t *testing.T) {\n\t\t\thc := NewHealthChecker(\n\t\t\t\t[]CategoryID{LinkerdCNIPluginChecks},\n\t\t\t\t&Options{\n\t\t\t\t\tCNINamespace: \"test-ns\",\n\t\t\t\t},\n\t\t\t)\n\n\t\t\tk8sConfigs := getFakeCniResources(tc.testCaseOpts)\n\t\t\tvar err error\n\t\t\thc.kubeAPI, err = k8s.NewFakeAPI(k8sConfigs...)\n\t\t\thc.CNIEnabled = true\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\tobs := newObserver()\n\t\t\thc.RunChecks(obs.resultFn)\n\t\t\tif diff := deep.Equal(obs.results, tc.results); diff != nil {\n\t\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t\t}\n\t\t})\n\t}\n\n}\n\nfunc TestMinReplicaCheck(t *testing.T) {\n\thc := NewHealthChecker(\n\t\t[]CategoryID{LinkerdHAChecks},\n\t\t&Options{\n\t\t\tControlPlaneNamespace: \"linkerd\",\n\t\t},\n\t)\n\n\tvar err error\n\n\ttestCases := []struct {\n\t\tcontrolPlaneResourceDefs []string\n\t\texpected                 error\n\t}{\n\t\t{\n\t\t\tcontrolPlaneResourceDefs: generateAllControlPlaneDef(&controlPlaneReplicaOptions{\n\t\t\t\tdestination:   1,\n\t\t\t\tidentity:      3,\n\t\t\t\tproxyInjector: 3,\n\t\t\t\ttap:           3,\n\t\t\t}, t),\n\t\t\texpected: fmt.Errorf(\"not enough replicas available for [linkerd-destination]\"),\n\t\t},\n\t\t{\n\t\t\tcontrolPlaneResourceDefs: generateAllControlPlaneDef(&controlPlaneReplicaOptions{\n\t\t\t\tdestination:   2,\n\t\t\t\tidentity:      1,\n\t\t\t\tproxyInjector: 1,\n\t\t\t\ttap:           3,\n\t\t\t}, t),\n\t\t\texpected: fmt.Errorf(\"not enough replicas available for [linkerd-identity linkerd-proxy-injector]\"),\n\t\t},\n\t\t{\n\t\t\tcontrolPlaneResourceDefs: generateAllControlPlaneDef(&controlPlaneReplicaOptions{\n\t\t\t\tdestination:   2,\n\t\t\t\tidentity:      2,\n\t\t\t\tproxyInjector: 3,\n\t\t\t\ttap:           3,\n\t\t\t}, t),\n\t\t\texpected: nil,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\thc.kubeAPI, err = k8s.NewFakeAPI(tc.controlPlaneResourceDefs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\terr = hc.checkMinReplicasAvailable(context.Background())\n\t\t\tif err == nil && tc.expected != nil {\n\t\t\t\tt.Log(\"Expected error: nil\")\n\t\t\t\tt.Logf(\"Received error: %s\\n\", err)\n\t\t\t\tt.Fatal(\"test case failed\")\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tif err.Error() != tc.expected.Error() {\n\t\t\t\t\tt.Logf(\"Expected error: %s\\n\", tc.expected)\n\t\t\t\t\tt.Logf(\"Received error: %s\\n\", err)\n\t\t\t\t\tt.Fatal(\"test case failed\")\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCheckOpaquePortAnnotations(t *testing.T) {\n\thc := NewHealthChecker(\n\t\t[]CategoryID{LinkerdOpaquePortsDefinitionChecks},\n\t\t&Options{\n\t\t\tDataPlaneNamespace: \"test-ns\",\n\t\t},\n\t)\n\n\tvar err error\n\n\tvar testCases = []struct {\n\t\tresources []string\n\t\texpected  error\n\t}{\n\t\t{\n\t\t\tresources: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc\n  namespace: test-ns\n  annotations:\n    config.linkerd.io/opaque-ports: \"9200\"\nspec:\n  selector:\n    app: test\n  ports:\n  - name: test\n    port: 9200\n    targetPort: 9200\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: pod\n  namespace: test-ns\n  labels:\n    app: test\n  annotations:\n    config.linkerd.io/opaque-ports: \"9200\"\nspec:\n  containers:\n  - name: test\n    image: test\n    ports:\n    - name: test\n      containerPort: 9200\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: svc\n  namespace: test-ns\nsubsets:\n- addresses:\n  - ip: 10.244.3.12\n    nodeName: nod\n    targetRef:\n      kind: Pod\n      name: pod\n      namespace: test-ns\n  ports:\n  - name: test\n    port: 9200\n    protocol: TCP\n`,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tresources: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc\n  namespace: test-ns\nspec:\n  selector:\n    app: test\n  ports:\n  - name: http\n    port: 9200\n    targetPort: 9200\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: pod\n  namespace: test-ns\n  labels:\n    app: test\n  annotations:\n    config.linkerd.io/opaque-ports: \"9200\"\nspec:\n  containers:\n  - name: test\n    image: test\n    ports:\n    - name: test\n      containerPort: 9200\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: svc\n  namespace: test-ns\nsubsets:\n- addresses:\n  - ip: 10.244.3.12\n    nodeName: nod\n    targetRef:\n      kind: Pod\n      name: pod\n      namespace: test-ns\n  ports:\n  - name: test\n    port: 9200\n    protocol: TCP\n`,\n\t\t\t},\n\t\t\texpected: fmt.Errorf(\"\\t* service svc targets the opaque port 9200 through 9200; add 9200 to its config.linkerd.io/opaque-ports annotation\"),\n\t\t},\n\t\t{\n\t\t\tresources: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc\n  namespace: test-ns\n  annotations:\n    config.linkerd.io/opaque-ports: \"9200\"\nspec:\n  selector:\n    app: test\n  ports:\n  - name: test\n    port: 9200\n    targetPort: 9200\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: pod\n  namespace: test-ns\n  labels:\n    app: test\nspec:\n  containers:\n  - name: test\n    image: test\n    ports:\n    - name: test\n      containerPort: 9200\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: svc\n  namespace: test-ns\nsubsets:\n- addresses:\n  - ip: 10.244.3.12\n    nodeName: nod\n    targetRef:\n      kind: Pod\n      name: pod\n      namespace: test-ns\n  ports:\n  - name: test\n    port: 9200\n    protocol: TCP\n`,\n\t\t\t},\n\t\t\texpected: fmt.Errorf(\"\\t* service svc expects target port 9200 to be opaque; add it to pod pod config.linkerd.io/opaque-ports annotation\"),\n\t\t},\n\t\t{\n\t\t\tresources: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc\n  namespace: test-ns\n  annotations:\n    config.linkerd.io/opaque-ports: \"9200\"\nspec:\n  selector:\n    app: test\n  ports:\n    - name: test\n      port: 9200\n      targetPort: 9200\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: pod\n  namespace: test-ns\n  labels:\n    app: test\n  annotations:\n    config.linkerd.io/opaque-ports: \"9300\"\nspec:\n  containers:\n  - name: test\n    image: test\n    ports:\n    - name: test\n      containerPort: 9300\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: svc\n  namespace: test-ns\nsubsets:\n- addresses:\n  - ip: 10.244.3.12\n    nodeName: node\n    targetRef:\n      kind: Pod\n      name: pod\n      namespace: test-ns\n  ports:\n  - name: test\n    port: 9200\n    protocol: TCP\n`,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tresources: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc\n  namespace: test-ns\nspec:\n  selector:\n    app: test\n  ports:\n  - name: test\n    port: 1002\n    targetPort: 2002\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: pod\n  namespace: test-ns\n  annotations:\n    config.linkerd.io/opaque-ports: \"2002\"\n  labels:\n    app: test\nspec:\n  containers:\n  - name: test\n    image: test\n    ports:\n    - name: test\n      containerPort: 2002\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: svc\n  namespace: test-ns\nsubsets:\n- addresses:\n  - ip: 10.42.0.111\n    nodeName: node\n    targetRef:\n      kind: Pod\n      name: pod\n  ports:\n  - name: test\n    port: 2002\n    protocol: TCP\n`,\n\t\t\t},\n\t\t\texpected: fmt.Errorf(\"\\t* service svc targets the opaque port 2002 through 1002; add 1002 to its config.linkerd.io/opaque-ports annotation\"),\n\t\t},\n\t\t{\n\t\t\tresources: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc\n  namespace: test-ns\nspec:\n  selector:\n    app: test\n  ports:\n  - name: test\n    port: 1003\n    targetPort: pod-test\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: pod\n  namespace: test-ns\n  annotations:\n    config.linkerd.io/opaque-ports: \"2003\"\n  labels:\n    app: test\nspec:\n  containers:\n  - name: test\n    image: test\n    ports:\n    - name: pod-test\n      containerPort: 2003\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: svc\n  namespace: test-ns\nsubsets:\n- addresses:\n  - ip: 10.42.0.112\n    nodeName: node\n    targetRef:\n      kind: Pod\n      name: pod\n  ports:\n  - name: test\n    port: 2003\n    protocol: TCP\n`,\n\t\t\t},\n\t\t\texpected: fmt.Errorf(\"\\t* service svc targets the opaque port pod-test through 1003; add 1003 to its config.linkerd.io/opaque-ports annotation\"),\n\t\t},\n\t\t{\n\t\t\tresources: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc\n  namespace: test-ns\nspec:\n  selector:\n    app: test\n  ports:\n  - port: 80\n    targetPort: 6502\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: pod\n  namespace: test-ns\n  annotations:\n    config.linkerd.io/opaque-ports: \"5432\"\n  labels:\n    app: test\nspec:\n  containers:\n  - name: c1\n    image: test\n    ports:\n    - containerPort: 6502\n  - name: c2\n    image: test\n    ports:\n    - containerPort: 5432\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: svc\n  namespace: test-ns\nsubsets:\n- addresses:\n  - ip: 10.42.0.112\n    nodeName: node\n    targetRef:\n      kind: Pod\n      name: pod\n  ports:\n  - port: 6502\n    protocol: TCP\n  - port: 5432\n    protocol: TCP\n`,\n\t\t\t},\n\t\t\texpected: nil,\n\t\t},\n\t\t{\n\t\t\tresources: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc\n  namespace: test-ns\n  annotations:\n    config.linkerd.io/opaque-ports: \"9200\"\nspec:\n  selector:\n    app: test\n  ports:\n  - name: test\n    port: 9200\n    targetPort: 9200\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: pod\n  namespace: test-ns\n  labels:\n    app: test\nspec:\n  initContainers:\n  - name: test\n    image: test\n    restartPolicy: Always\n    ports:\n    - name: test\n      containerPort: 9200\n`,\n\t\t\t\t`\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: svc\n  namespace: test-ns\nsubsets:\n- addresses:\n  - ip: 10.244.3.12\n    nodeName: nod\n    targetRef:\n      kind: Pod\n      name: pod\n      namespace: test-ns\n  ports:\n  - name: test\n    port: 9200\n    protocol: TCP\n`,\n\t\t\t},\n\t\t\texpected: fmt.Errorf(\"\\t* service svc expects target port 9200 to be opaque; add it to pod pod config.linkerd.io/opaque-ports annotation\"),\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\thc.kubeAPI, err = k8s.NewFakeAPI(tc.resources...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t\terr = hc.checkMisconfiguredOpaquePortAnnotations(context.Background())\n\t\t\tif err == nil && tc.expected != nil {\n\t\t\t\tt.Fatalf(\"Expected check to fail with %s\", tc.expected.Error())\n\t\t\t}\n\t\t\tif err != nil && tc.expected != nil {\n\t\t\t\tif err.Error() != tc.expected.Error() {\n\t\t\t\t\tt.Fatalf(\"Expected error: %s, received: %s\", tc.expected, err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif err != nil && tc.expected == nil {\n\t\t\t\tt.Fatalf(\"Did not expect error but got: %s\", err.Error())\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype controlPlaneReplicaOptions struct {\n\tdestination   int\n\tidentity      int\n\tproxyInjector int\n\ttap           int\n}\n\nfunc getSingleControlPlaneDef(component string, availableReplicas int) string {\n\treturn fmt.Sprintf(`\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: %s\n  namespace: linkerd\nspec:\n  template:\n    spec:\n      containers:\n        - image: \"hello-world\"\n          name: test\nstatus:\n  availableReplicas: %d`, component, availableReplicas)\n}\n\nfunc generateAllControlPlaneDef(replicaOptions *controlPlaneReplicaOptions, t *testing.T) []string {\n\tresourceDefs := []string{}\n\tfor _, component := range linkerdHAControlPlaneComponents {\n\t\tswitch component {\n\t\tcase \"linkerd-destination\":\n\t\t\tresourceDefs = append(resourceDefs, getSingleControlPlaneDef(component, replicaOptions.destination))\n\t\tcase \"linkerd-identity\":\n\t\t\tresourceDefs = append(resourceDefs, getSingleControlPlaneDef(component, replicaOptions.identity))\n\t\tcase \"linkerd-proxy-injector\":\n\t\t\tresourceDefs = append(resourceDefs, getSingleControlPlaneDef(component, replicaOptions.proxyInjector))\n\t\tcase \"linkerd-tap\":\n\t\t\tresourceDefs = append(resourceDefs, getSingleControlPlaneDef(component, replicaOptions.tap))\n\t\tdefault:\n\t\t\tt.Fatal(\"Could not find the resource\")\n\t\t}\n\t}\n\treturn resourceDefs\n}\n\nfunc multiappend(slices ...[]string) []string {\n\tres := []string{}\n\tfor _, slice := range slices {\n\t\tres = append(res, slice...)\n\t}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/healthcheck/sidecar.go",
    "content": "package healthcheck\n\nimport (\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// HasExistingSidecars returns true if the pod spec already has the proxy init\n// and sidecar containers injected. Otherwise, it returns false.\nfunc HasExistingSidecars(podSpec *corev1.PodSpec) bool {\n\tcontainers := append(podSpec.InitContainers, podSpec.Containers...)\n\tfor _, container := range containers {\n\t\tif strings.HasPrefix(container.Image, \"cr.l5d.io/linkerd/proxy:\") ||\n\t\t\tstrings.HasPrefix(container.Image, \"gcr.io/istio-release/proxyv2:\") ||\n\t\t\tcontainer.Name == k8s.ProxyContainerName ||\n\t\t\tcontainer.Name == \"istio-proxy\" {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tfor _, ic := range podSpec.InitContainers {\n\t\tif strings.HasPrefix(ic.Image, \"cr.l5d.io/linkerd/proxy-init:\") ||\n\t\t\tstrings.HasPrefix(ic.Image, \"gcr.io/istio-release/proxy_init:\") ||\n\t\t\tic.Name == \"linkerd-init\" ||\n\t\t\tic.Name == \"istio-init\" {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "pkg/healthcheck/sidecar_test.go",
    "content": "package healthcheck\n\nimport (\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\nfunc TestHasExistingSidecars(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tpodSpec  *corev1.PodSpec\n\t\texpected bool\n\t}{\n\t\t{\n\t\t\tpodSpec:  &corev1.PodSpec{},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:  \"foo\",\n\t\t\t\t\t\tImage: \"bar\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tInitContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:  \"foo\",\n\t\t\t\t\t\tImage: \"bar\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: k8s.ProxyContainerName,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tInitContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: k8s.ProxyContainerName,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"istio-proxy\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tImage: \"cr.l5d.io/linkerd/proxy:1.0.0\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tImage: \"gcr.io/istio-release/proxyv2:1.0.0\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tInitContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"linkerd-init\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tInitContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"istio-init\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tInitContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tImage: \"cr.l5d.io/linkerd/proxy:1.0.0\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tInitContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tImage: \"gcr.io/istio-release/proxy_init:1.0.0\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t} {\n\t\tif diff := deep.Equal(tc.expected, HasExistingSidecars(tc.podSpec)); diff != nil {\n\t\t\tt.Errorf(\"%+v\", diff)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/healthcheck/version.go",
    "content": "package healthcheck\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/linkerd/linkerd2/pkg/config\"\n\n\t\"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n)\n\n// GetServerVersion returns Linkerd's version, as set in linkerd-config\nfunc GetServerVersion(ctx context.Context, controlPlaneNamespace string, kubeAPI *k8s.KubernetesAPI) (string, error) {\n\tcm, err := config.FetchLinkerdConfigMap(ctx, kubeAPI, controlPlaneNamespace)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to fetch linkerd-config: %w\", err)\n\t}\n\n\tvalues, err := linkerd2.ValuesFromConfigMap(cm)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to load values from linkerd-config: %w\", err)\n\t}\n\n\treturn values.LinkerdVersion, nil\n}\n"
  },
  {
    "path": "pkg/identity/identity_fuzzer.go",
    "content": "package identity\n\nimport (\n\t\"context\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/identity\"\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n\n\tfuzz \"github.com/AdaLogics/go-fuzz-headers\"\n)\n\n// FuzzServiceCertify fuzzes the Service.Certify method.\nfunc FuzzServiceCertify(data []byte) int {\n\tf := fuzz.NewConsumer(data)\n\treq := &pb.CertifyRequest{}\n\terr := f.GenerateStruct(req)\n\tif err != nil {\n\t\treturn 0\n\t}\n\n\tsvc := NewService(&fakeValidator{\"successful-result\", nil}, nil, nil, nil, \"\", \"\", \"\")\n\tsvc.updateIssuer(&fakeIssuer{tls.Crt{}, nil})\n\n\t_, _ = svc.Certify(context.Background(), req)\n\treturn 1\n}\n"
  },
  {
    "path": "pkg/identity/service.go",
    "content": "package identity\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"crypto/x509\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/identity\"\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\tlog \"github.com/sirupsen/logrus\"\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/timestamppb\"\n)\n\nconst (\n\t// DefaultIssuanceLifetime is the default lifetime of certificates issued by\n\t// the identity service.\n\tDefaultIssuanceLifetime = 24 * time.Hour\n\n\t// EnvTrustAnchors is the environment variable holding the trust anchors for\n\t// the proxy identity.\n\tEnvTrustAnchors         = \"LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\"\n\teventTypeSkipped        = \"IssuerUpdateSkipped\"\n\teventTypeUpdated        = \"IssuerUpdated\"\n\teventTypeFailed         = \"IssuerValidationFailed\"\n\teventTypeIssuedLeafCert = \"IssuedLeafCertificate\"\n)\n\ntype (\n\t// Service implements the gRPC service in terms of a Validator and Issuer.\n\tService struct {\n\t\tpb.UnimplementedIdentityServer\n\t\tvalidator    Validator\n\t\ttrustAnchors *x509.CertPool\n\t\tissuer       *tls.Issuer\n\t\tissuerMutex  *sync.RWMutex\n\t\tvalidity     *tls.Validity\n\t\trecordEvent  func(parent runtime.Object, eventType, reason, message string)\n\n\t\texpectedName, issuerPathCrt, issuerPathKey string\n\t\tissuerCertTTL                              time.Time\n\t}\n\n\t// Validator implementors accept a bearer token, validates it, and returns a\n\t// DNS-form identity.\n\tValidator interface {\n\t\t// Validate takes an opaque authentication token, attempts to validate its\n\t\t// authenticity, and produces a DNS-like identifier.\n\t\t//\n\t\t// An InvalidToken error should be returned if the provided token was not in a\n\t\t// correct form.\n\t\t//\n\t\t// A NotAuthenticated error should be returned if the authenticity of the\n\t\t// token cannot be validated.\n\t\tValidate(context.Context, []byte) (string, error)\n\t}\n\n\t// InvalidToken is an error type returned by Validators to indicate that the\n\t// provided authentication token was not valid.\n\tInvalidToken struct{ Reason string }\n\n\t// NotAuthenticated is an error type returned by Validators to indicate that the\n\t// provided authentication token could not be authenticated.\n\tNotAuthenticated struct{}\n)\n\n// Initialize loads the issuer certs from disk so it can start service CSRs to proxies\nfunc (svc *Service) Initialize() error {\n\tcredentials, err := svc.loadCredentials()\n\tif err != nil {\n\t\treturn err\n\t}\n\tsvc.updateIssuer(credentials)\n\treturn nil\n}\n\nfunc (svc *Service) updateIssuer(newIssuer tls.Issuer) {\n\tsvc.issuerMutex.Lock()\n\tsvc.issuer = &newIssuer\n\tlog.Debug(\"Issuer has been updated\")\n\tsvc.issuerMutex.Unlock()\n}\n\nfunc (svc *Service) getIssuerCertTTL() float64 {\n\tif svc.issuerCertTTL.IsZero() {\n\t\tlog.Warn(\"Issuer certificate not ready: cannot get TTL\")\n\t\treturn float64(0)\n\t}\n\treturn time.Until(svc.issuerCertTTL).Seconds()\n}\n\n// Run reads from the issuer and error channels and reloads the issuer certs when necessary\nfunc (svc *Service) Run(issuerEvent <-chan struct{}, issuerError <-chan error) {\n\tfor {\n\t\tselect {\n\t\tcase <-issuerEvent:\n\t\t\tif err := svc.Initialize(); err != nil {\n\t\t\t\tmessage := fmt.Sprintf(\"Skipping issuer update as certs could not be read from disk: %s\", err)\n\t\t\t\tlog.Warn(message)\n\t\t\t\tsvc.recordEvent(nil, v1.EventTypeWarning, eventTypeSkipped, message)\n\t\t\t} else {\n\t\t\t\tmessage := \"Updated identity issuer\"\n\t\t\t\tlog.Info(message)\n\t\t\t\tsvc.recordEvent(nil, v1.EventTypeNormal, eventTypeUpdated, message)\n\t\t\t}\n\t\tcase err := <-issuerError:\n\t\t\tlog.Warnf(\"Received error from fs watcher: %s\", err)\n\t\t}\n\t}\n}\n\nfunc (svc *Service) loadCredentials() (tls.Issuer, error) {\n\tcreds, err := tls.ReadPEMCreds(\n\t\tsvc.issuerPathKey,\n\t\tsvc.issuerPathCrt,\n\t)\n\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read CA from disk: %w\", err)\n\t}\n\n\t// Don't verify with dns name as this is not a leaf certificate\n\tif err := creds.Crt.Verify(svc.trustAnchors, \"\", time.Time{}); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to verify issuer credentials for '%s' with trust anchors: %w\", svc.expectedName, err)\n\t}\n\n\tif !creds.Certificate.IsCA {\n\t\treturn nil, fmt.Errorf(\"failed to verify issuer certificate: it must be an intermediate-CA, but it is not\")\n\t}\n\n\tsvc.issuerCertTTL = creds.Certificate.NotAfter\n\n\tlog.Debugf(\"Loaded issuer cert: %s\", creds.EncodeCertificatePEM())\n\tnow := time.Now().Unix()\n\tlog.WithFields(log.Fields{\n\t\t\"invalid_after\":      creds.Certificate.NotAfter.Unix(),\n\t\t\"process_clock_time\": now,\n\t\t\"ttl_seconds\":        creds.Certificate.NotAfter.Unix() - now,\n\t}).Info(\"Issuer cert loaded\")\n\treturn tls.NewCA(*creds, *svc.validity), nil\n}\n\nfunc (svc *Service) registerCertExpirationMetrics() {\n\t// register a metric for the expiration of the issuer cert\n\tissuerCertExpireGauge := prometheus.NewGaugeFunc(prometheus.GaugeOpts{\n\t\tName: \"issuer_cert_ttl_seconds\",\n\t\tHelp: \"The remaining seconds until the issuer certificate expires\",\n\t}, svc.getIssuerCertTTL)\n\tif err := prometheus.Register(issuerCertExpireGauge); err != nil {\n\t\tvar are prometheus.AlreadyRegisteredError\n\t\tif errors.As(err, &are) {\n\t\t\tlog.Warn(\"issuer_cert_ttl_seconds metric already registered\")\n\t\t} else {\n\t\t\tlog.WithError(err).Error(\"failed to register issuer_cert_ttl_seconds metric\")\n\t\t}\n\t}\n\t// TODO: register a metric for the expiration of the trust anchor cert with the lowest TTL\n}\n\n// NewService creates a new identity service.\nfunc NewService(validator Validator, trustAnchors *x509.CertPool, validity *tls.Validity, recordEvent func(parent runtime.Object, eventType, reason, message string), expectedName, issuerPathCrt, issuerPathKey string) *Service {\n\tsvc := &Service{\n\t\tpb.UnimplementedIdentityServer{},\n\t\tvalidator,\n\t\ttrustAnchors,\n\t\tnil,\n\t\t&sync.RWMutex{},\n\t\tvalidity,\n\t\trecordEvent,\n\t\texpectedName,\n\t\tissuerPathCrt,\n\t\tissuerPathKey,\n\t\ttime.Time{},\n\t}\n\tsvc.registerCertExpirationMetrics()\n\treturn svc\n}\n\n// Register registers an identity service implementation in the provided gRPC\n// server.\nfunc Register(g *grpc.Server, s *Service) {\n\tpb.RegisterIdentityServer(g, s)\n}\n\n// ensureIssuerStillValid should check that the CA is still good time wise\n// and verifies just fine with the provided trust anchors\nfunc (svc *Service) ensureIssuerStillValid() error {\n\tissuer := *svc.issuer\n\tswitch is := issuer.(type) {\n\tcase *tls.CA:\n\t\t// Don't verify with dns name as this is not a leaf certificate\n\t\treturn is.Cred.Verify(svc.trustAnchors, \"\", time.Time{})\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported issuer type. Expected *tls.CA, got %v\", is)\n\t}\n}\n\n// Certify validates identity and signs certificates.\nfunc (svc *Service) Certify(ctx context.Context, req *pb.CertifyRequest) (*pb.CertifyResponse, error) {\n\tsvc.issuerMutex.RLock()\n\tdefer svc.issuerMutex.RUnlock()\n\n\tif svc.issuer == nil {\n\t\tlog.Warn(\"Certificate issuer is not ready\")\n\t\treturn nil, status.Error(codes.Unavailable, \"cert issuer not ready yet\")\n\t}\n\n\t// Extract the relevant info from the request.\n\treqIdentity, tok, csr, err := checkRequest(req)\n\tif err != nil {\n\t\treturn nil, status.Error(codes.InvalidArgument, err.Error())\n\t}\n\n\tif err := svc.ensureIssuerStillValid(); err != nil {\n\t\tlog.Errorf(\"could not process CSR because of CA cert validation failure: %s - CSR Identity : %s\", err, reqIdentity)\n\t\tmessage := fmt.Sprintf(\"%s - CSR Identity : %s\", err.Error(), reqIdentity)\n\t\tsvc.recordEvent(nil, v1.EventTypeWarning, eventTypeFailed, message)\n\t\treturn nil, err\n\t}\n\n\tif err = checkCSR(csr, reqIdentity); err != nil {\n\t\tlog.Debugf(\"requester sent invalid CSR: %s\", err)\n\t\treturn nil, status.Error(codes.FailedPrecondition, err.Error())\n\t}\n\n\t// Authenticate the provided token against the Kubernetes API.\n\tlog.Debugf(\"Validating token for %s\", reqIdentity)\n\ttokIdentity, err := svc.validator.Validate(ctx, tok)\n\tif err != nil {\n\t\tvar nae NotAuthenticated\n\t\tif errors.As(err, &nae) {\n\t\t\tlog.Infof(\"authentication failed for %s: %s\", reqIdentity, nae)\n\t\t\treturn nil, status.Error(codes.FailedPrecondition, nae.Error())\n\t\t}\n\t\tvar ite InvalidToken\n\t\tif errors.As(err, &ite) {\n\t\t\tlog.Infof(\"invalid token provided for %s: %s\", reqIdentity, ite)\n\t\t\treturn nil, status.Error(codes.InvalidArgument, ite.Error())\n\t\t}\n\n\t\tmsg := fmt.Sprintf(\"error validating token for %s: %s\", reqIdentity, err)\n\t\tlog.Error(msg)\n\t\treturn nil, status.Error(codes.Internal, msg)\n\t}\n\n\t// Ensure the requested identity matches the token's identity.\n\tif reqIdentity != tokIdentity {\n\t\tmsg := fmt.Sprintf(\"requested identity did not match provided token: requested=%s; found=%s\",\n\t\t\treqIdentity, tokIdentity)\n\t\tlog.Info(msg)\n\t\treturn nil, status.Error(codes.FailedPrecondition, msg)\n\t}\n\n\t// Create a certificate\n\tissuer := *svc.issuer\n\tcrt, err := issuer.IssueEndEntityCrt(csr)\n\tif err != nil {\n\t\treturn nil, status.Error(codes.Internal, err.Error())\n\t}\n\tcrts := crt.ExtractRaw()\n\tif len(crts) == 0 {\n\t\t//nolint:gocritic\n\t\tlog.Fatal(\"the issuer provided a certificate without key material\")\n\t}\n\tvalidUntil := timestamppb.New(crt.Certificate.NotAfter)\n\n\thasher := sha256.New()\n\thasher.Write(crts[0])\n\thash := hex.EncodeToString(hasher.Sum(nil))\n\tidentitySegments := strings.Split(tokIdentity, \".\")\n\tmsg := fmt.Sprintf(\"issued certificate for %s until %s: %s\", tokIdentity, crt.Certificate.NotAfter, hash)\n\tsa := v1.ServiceAccount{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      identitySegments[0],\n\t\t\tNamespace: identitySegments[1],\n\t\t},\n\t}\n\tsvc.recordEvent(&sa, v1.EventTypeNormal, eventTypeIssuedLeafCert, msg)\n\tlog.Info(msg)\n\n\t// Bundle issuer crt with certificate so the trust path to the root can be verified.\n\trsp := &pb.CertifyResponse{\n\t\tLeafCertificate:          crts[0],\n\t\tIntermediateCertificates: crts[1:],\n\n\t\tValidUntil: validUntil,\n\t}\n\treturn rsp, nil\n}\n\nfunc checkRequest(req *pb.CertifyRequest) (string, []byte, *x509.CertificateRequest, error) {\n\treqIdentity := req.GetIdentity()\n\tif reqIdentity == \"\" {\n\t\treturn \"\", nil, nil, errors.New(\"missing identity\")\n\t}\n\n\ttok := req.GetToken()\n\tif len(tok) == 0 {\n\t\treturn \"\", nil, nil, errors.New(\"missing token\")\n\t}\n\n\tder := req.GetCertificateSigningRequest()\n\tif len(der) == 0 {\n\t\treturn \"\", nil, nil,\n\t\t\terrors.New(\"missing certificate signing request\")\n\t}\n\tcsr, err := x509.ParseCertificateRequest(der)\n\tif err != nil {\n\t\treturn \"\", nil, nil, err\n\t}\n\n\treturn reqIdentity, tok, csr, nil\n}\n\nfunc checkCSR(csr *x509.CertificateRequest, identity string) error {\n\tif len(csr.DNSNames) != 1 {\n\t\treturn errors.New(\"CSR must have exactly one DNSName\")\n\t}\n\tif csr.DNSNames[0] != identity {\n\t\treturn fmt.Errorf(\"CSR name does not match requested identity: csr=%s; req=%s\", csr.DNSNames[0], identity)\n\t}\n\n\tswitch csr.Subject.CommonName {\n\tcase \"\", identity:\n\tdefault:\n\t\treturn fmt.Errorf(\"invalid CommonName: %s\", csr.Subject.CommonName)\n\t}\n\n\tif len(csr.EmailAddresses) > 0 {\n\t\treturn errors.New(\"cannot validate email addresses\")\n\t}\n\tif len(csr.IPAddresses) > 0 {\n\t\treturn errors.New(\"cannot validate IP addresses\")\n\t}\n\tif len(csr.URIs) > 0 {\n\t\treturn errors.New(\"cannot validate URIs\")\n\t}\n\n\treturn nil\n}\n\nfunc (NotAuthenticated) Error() string {\n\treturn \"authentication token could not be authenticated\"\n}\n\nfunc (e InvalidToken) Error() string {\n\treturn e.Reason\n}\n"
  },
  {
    "path": "pkg/identity/service_test.go",
    "content": "package identity\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\tpb \"github.com/linkerd/linkerd2-proxy-api/go/identity\"\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n)\n\nfunc TestServiceNotReady(t *testing.T) {\n\t// ch := make(chan tls.Issuer, 1)\n\tsvc := NewService(&fakeValidator{\"successful-result\", nil}, nil, nil, nil, \"\", \"\", \"\")\n\treq := &pb.CertifyRequest{\n\t\tIdentity:                  \"some-identity\",\n\t\tToken:                     []byte{},\n\t\tCertificateSigningRequest: []byte{},\n\t}\n\n\t_, err := svc.Certify(context.TODO(), req)\n\n\texpectedError := \"rpc error: code = Unavailable desc = cert issuer not ready yet\"\n\n\tif err != nil {\n\t\tif err.Error() != expectedError {\n\t\t\tt.Fatalf(\"Expected error string\\\"%s\\\", got \\\"%s\\\"\", expectedError, err)\n\t\t}\n\t} else {\n\t\tt.Fatalf(\"Expected error but got got nothing\")\n\t}\n}\n\nfunc TestInvalidRequestArguments(t *testing.T) {\n\tsvc := NewService(&fakeValidator{\"successful-result\", nil}, nil, nil, nil, \"\", \"\", \"\")\n\tsvc.updateIssuer(&fakeIssuer{tls.Crt{}, nil})\n\tfakeData := \"fake-data\"\n\tinvalidCsr := func() *pb.CertifyRequest {\n\t\treturn &pb.CertifyRequest{\n\t\t\tIdentity:                  fakeData,\n\t\t\tToken:                     []byte(fakeData),\n\t\t\tCertificateSigningRequest: []byte(fakeData),\n\t\t}\n\t}\n\n\treqNoIdentity := invalidCsr()\n\treqNoIdentity.Identity = \"\"\n\n\treqNoToken := invalidCsr()\n\treqNoToken.Token = []byte{}\n\n\treqNoCsr := invalidCsr()\n\treqNoCsr.CertificateSigningRequest = []byte{}\n\n\ttestCases := []struct {\n\t\tinput         *pb.CertifyRequest\n\t\texpectedError string\n\t}{\n\t\t{reqNoIdentity, \"rpc error: code = InvalidArgument desc = missing identity\"},\n\t\t{reqNoToken, \"rpc error: code = InvalidArgument desc = missing token\"},\n\t\t{reqNoCsr, \"rpc error: code = InvalidArgument desc = missing certificate signing request\"},\n\t\t{invalidCsr(), \"rpc error: code = InvalidArgument desc = asn1: structure error: tags don't match \" +\n\t\t\t\"(16 vs {class:1 tag:6 length:97 isCompound:true}) \" +\n\t\t\t\"{optional:false explicit:false application:false private:false defaultValue:<nil> \" +\n\t\t\t\"tag:<nil> stringType:0 timeType:0 set:false omitEmpty:false} certificateRequest @2\"},\n\t}\n\n\tfor _, tc := range testCases {\n\n\t\t_, err := svc.Certify(context.TODO(), tc.input)\n\t\tif tc.expectedError != \"\" {\n\t\t\tif err == nil {\n\t\t\t\tt.Fatal(\"Expected error, got nothing\")\n\t\t\t}\n\t\t\tif err.Error() != tc.expectedError {\n\t\t\t\tt.Fatalf(\"Expected error string\\\"%s\\\", got \\\"%s\\\"\", tc.expectedError, err)\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "pkg/identity/test_util.go",
    "content": "package identity\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n)\n\ntype fakeValidator struct {\n\tresult string\n\terr    error\n}\n\nfunc (fk *fakeValidator) Validate(context.Context, []byte) (string, error) {\n\treturn fk.result, fk.err\n}\n\ntype fakeIssuer struct {\n\tresult tls.Crt\n\terr    error\n}\n\nfunc (fi *fakeIssuer) IssueEndEntityCrt(*x509.CertificateRequest) (tls.Crt, error) {\n\treturn fi.result, fi.err\n}\n"
  },
  {
    "path": "pkg/inject/annotation_patch.go",
    "content": "package inject\n\nvar tpl = `[\n  {{- if .AddRootAnnotations }}\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations\",\n    \"value\": {}\n  },\n  {{- end }}\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/config.linkerd.io~1opaque-ports\",\n    \"value\": \"{{.OpaquePorts}}\"\n  }\n]`\n"
  },
  {
    "path": "pkg/inject/inject.go",
    "content": "package inject\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"net\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tjsonfilter \"github.com/clarketm/json\"\n\t\"github.com/linkerd/linkerd2/charts\"\n\tchartspkg \"github.com/linkerd/linkerd2/pkg/charts\"\n\tl5dcharts \"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/util\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"helm.sh/helm/v3/pkg/chart/loader\"\n\t\"helm.sh/helm/v3/pkg/chartutil\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tk8sResource \"k8s.io/apimachinery/pkg/api/resource\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nvar (\n\trTrail = regexp.MustCompile(`\\},\\s*\\]`)\n\n\t// ProxyAnnotations is the list of possible annotations that can be applied on a pod or namespace.\n\tProxyAnnotations = []string{\n\t\tk8s.ProxyAdminPortAnnotation,\n\t\tk8s.ProxyControlPortAnnotation,\n\t\tk8s.ProxyEnableDebugAnnotation,\n\t\tk8s.ProxyEnableExternalProfilesAnnotation,\n\t\tk8s.ProxyImagePullPolicyAnnotation,\n\t\tk8s.ProxyInboundPortAnnotation,\n\t\tk8s.ProxyOutboundPortAnnotation,\n\t\tk8s.ProxyPodInboundPortsAnnotation,\n\t\tk8s.ProxyCPULimitAnnotation,\n\t\tk8s.ProxyCPURequestAnnotation,\n\t\tk8s.ProxyImageAnnotation,\n\t\tk8s.ProxyAdminShutdownAnnotation,\n\t\tk8s.ProxyLogFormatAnnotation,\n\t\tk8s.ProxyLogLevelAnnotation,\n\t\tk8s.ProxyLogHTTPHeaders,\n\t\tk8s.ProxyMemoryLimitAnnotation,\n\t\tk8s.ProxyMemoryRequestAnnotation,\n\t\tk8s.ProxyEphemeralStorageLimitAnnotation,\n\t\tk8s.ProxyEphemeralStorageRequestAnnotation,\n\t\tk8s.ProxyUIDAnnotation,\n\t\tk8s.ProxyGIDAnnotation,\n\t\tk8s.ProxyVersionOverrideAnnotation,\n\t\tk8s.ProxyRequireIdentityOnInboundPortsAnnotation,\n\t\tk8s.ProxyIgnoreInboundPortsAnnotation,\n\t\tk8s.ProxyOpaquePortsAnnotation,\n\t\tk8s.ProxyIgnoreOutboundPortsAnnotation,\n\t\tk8s.ProxyEnableHostnameLabels,\n\t\tk8s.ProxyOutboundConnectTimeout,\n\t\tk8s.ProxyInboundConnectTimeout,\n\t\tk8s.ProxyAwait,\n\t\tk8s.ProxyDefaultInboundPolicyAnnotation,\n\t\tk8s.ProxySkipSubnetsAnnotation,\n\t\tk8s.ProxyAccessLogAnnotation,\n\t\tk8s.ProxyShutdownGracePeriodAnnotation,\n\t\tk8s.ProxyOutboundDiscoveryCacheUnusedTimeout,\n\t\tk8s.ProxyInboundDiscoveryCacheUnusedTimeout,\n\t\tk8s.ProxyDisableOutboundProtocolDetectTimeout,\n\t\tk8s.ProxyDisableInboundProtocolDetectTimeout,\n\t\tk8s.ProxyEnableNativeSidecarAnnotationBeta,\n\t}\n\t// ProxyAlphaConfigAnnotations is the list of all alpha configuration\n\t// (config.alpha prefix) that can be applied to a pod or namespace.\n\tProxyAlphaConfigAnnotations = []string{\n\t\tk8s.ProxyWaitBeforeExitSecondsAnnotation,\n\t\tk8s.ProxyEnableNativeSidecarAnnotationAlpha,\n\t}\n)\n\n// ValueOverrider is used to override the default values that are used in chart rendering based\n// on the annotations provided in overrides.\ntype ValueOverrider func(rc *ResourceConfig) (*l5dcharts.Values, error)\n\n// Origin defines where the input YAML comes from. Refer the ResourceConfig's\n// 'origin' field\ntype Origin int\n\nconst (\n\t// OriginCLI is the value of the ResourceConfig's 'origin' field if the input\n\t// YAML comes from the CLI\n\tOriginCLI Origin = iota\n\n\t// OriginWebhook is the value of the ResourceConfig's 'origin' field if the input\n\t// YAML comes from the CLI\n\tOriginWebhook\n\n\t// OriginUnknown is the value of the ResourceConfig's 'origin' field if the\n\t// input YAML comes from an unknown source\n\tOriginUnknown\n)\n\n// OwnerRetrieverFunc is a function that returns a pod's owner reference\n// kind and name\ntype OwnerRetrieverFunc func(*corev1.Pod) (string, string, error)\n\n// ResourceConfig contains the parsed information for a given workload\ntype ResourceConfig struct {\n\t// These values used for the rendering of the patch may be further\n\t// overridden by the annotations on the resource or the resource's\n\t// namespace.\n\tvalues *l5dcharts.Values\n\n\tnamespace string\n\n\t// These annotations from the resources's namespace are used as a base.\n\t// The resources's annotations will be applied on top of these, which\n\t// allows the nsAnnotations to act as a default.\n\tnsAnnotations  map[string]string\n\townerRetriever OwnerRetrieverFunc\n\torigin         Origin\n\n\tworkload struct {\n\t\tobj      runtime.Object\n\t\tmetaType metav1.TypeMeta\n\t\t// Meta is the workload's metadata. It's exported so that metadata of\n\t\t// non-workload resources can be unmarshalled by the YAML parser\n\t\tMeta     *metav1.ObjectMeta `json:\"metadata,omitempty\" protobuf:\"bytes,1,opt,name=metadata\"`\n\t\townerRef *metav1.OwnerReference\n\t}\n\n\tpod struct {\n\t\tmeta *metav1.ObjectMeta\n\t\t// This fields hold labels and annotations which are to be added to the\n\t\t// injected resource. This is different from meta.Labels and\n\t\t// meta.Annotations which are the labels and annotations on the original\n\t\t// resource before injection.\n\t\tlabels      map[string]string\n\t\tannotations map[string]string\n\t\tspec        *corev1.PodSpec\n\t}\n}\n\ntype podPatch struct {\n\tl5dcharts.Values\n\tPathPrefix            string                    `json:\"pathPrefix\"`\n\tAddRootMetadata       bool                      `json:\"addRootMetadata\"`\n\tAddRootAnnotations    bool                      `json:\"addRootAnnotations\"`\n\tAnnotations           map[string]string         `json:\"annotations\"`\n\tAddRootLabels         bool                      `json:\"addRootLabels\"`\n\tAddRootInitContainers bool                      `json:\"addRootInitContainers\"`\n\tAddRootVolumes        bool                      `json:\"addRootVolumes\"`\n\tLabels                map[string]string         `json:\"labels\"`\n\tDebugContainer        *l5dcharts.DebugContainer `json:\"debugContainer\"`\n}\n\ntype annotationPatch struct {\n\tAddRootAnnotations bool\n\tOpaquePorts        string\n}\n\n// AppendNamespaceAnnotations allows pods to inherit config specific annotations\n// from the namespace they belong to. If the namespace has a valid config key\n// that the pod does not, then it is appended to the pod's template\nfunc AppendNamespaceAnnotations(base map[string]string, nsAnn map[string]string, workloadAnn map[string]string) {\n\tann := append(ProxyAnnotations, ProxyAlphaConfigAnnotations...)\n\tann = append(ann, k8s.ProxyInjectAnnotation)\n\n\tfor _, key := range ann {\n\t\tif _, found := nsAnn[key]; !found {\n\t\t\tcontinue\n\t\t}\n\t\tif val, ok := GetConfigOverride(key, workloadAnn, nsAnn); ok {\n\t\t\tbase[key] = val\n\t\t}\n\t}\n}\n\n// GetOverriddenValues returns the final Values struct which is created\n// by overriding annotated configuration on top of default Values\nfunc GetOverriddenValues(rc *ResourceConfig) (*l5dcharts.Values, error) {\n\t// Make a copy of Values and mutate that\n\tcopyValues, err := rc.GetValues().DeepCopy()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnamedPorts := make(map[string]int32)\n\tif rc.HasPodTemplate() {\n\t\tnamedPorts = util.GetNamedPorts(append(rc.pod.spec.InitContainers, rc.pod.spec.Containers...))\n\t}\n\n\tApplyAnnotationOverrides(copyValues, rc.GetAnnotationOverrides(), rc.GetLabelOverrides(), namedPorts)\n\treturn copyValues, nil\n}\n\nfunc ApplyAnnotationOverrides(values *l5dcharts.Values, annotations map[string]string, labels map[string]string, namedPorts map[string]int32) {\n\tif override, ok := annotations[k8s.ProxyInjectAnnotation]; ok {\n\t\tif override == k8s.ProxyInjectIngress {\n\t\t\tvalues.Proxy.IsIngress = true\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyImageAnnotation]; ok {\n\t\tvalues.Proxy.Image.Name = override\n\t}\n\n\tif override, ok := annotations[k8s.ProxyVersionOverrideAnnotation]; ok {\n\t\tvalues.Proxy.Image.Version = override\n\t}\n\n\tif override, ok := annotations[k8s.ProxyImagePullPolicyAnnotation]; ok {\n\t\tvalues.Proxy.Image.PullPolicy = override\n\t}\n\n\tif override, ok := annotations[k8s.ProxyControlPortAnnotation]; ok {\n\t\tcontrolPort, err := strconv.ParseInt(override, 10, 32)\n\t\tif err == nil {\n\t\t\tvalues.Proxy.Ports.Control = int32(controlPort)\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyInboundPortAnnotation]; ok {\n\t\tinboundPort, err := strconv.ParseInt(override, 10, 32)\n\t\tif err == nil {\n\t\t\tvalues.Proxy.Ports.Inbound = int32(inboundPort)\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyAdminPortAnnotation]; ok {\n\t\tadminPort, err := strconv.ParseInt(override, 10, 32)\n\t\tif err == nil {\n\t\t\tvalues.Proxy.Ports.Admin = int32(adminPort)\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyOutboundPortAnnotation]; ok {\n\t\toutboundPort, err := strconv.ParseInt(override, 10, 32)\n\t\tif err == nil {\n\t\t\tvalues.Proxy.Ports.Outbound = int32(outboundPort)\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyPodInboundPortsAnnotation]; ok {\n\t\tvalues.Proxy.PodInboundPorts = override\n\t}\n\n\tif override, ok := annotations[k8s.ProxyAdminShutdownAnnotation]; ok {\n\t\tif override == k8s.Enabled || override == k8s.Disabled {\n\t\t\tvalues.Proxy.EnableShutdownEndpoint = override == k8s.Enabled\n\t\t} else {\n\t\t\tlog.Warnf(\"unrecognized value used for the %s annotation, valid values are: [%s, %s]\", k8s.ProxyAdminShutdownAnnotation, k8s.Enabled, k8s.Disabled)\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyLogLevelAnnotation]; ok {\n\t\tvalues.Proxy.LogLevel = override\n\t}\n\n\tif override, ok := annotations[k8s.ProxyLogHTTPHeaders]; ok {\n\t\tvalues.Proxy.LogHTTPHeaders = override\n\t}\n\n\tif override, ok := annotations[k8s.ProxyLogFormatAnnotation]; ok {\n\t\tvalues.Proxy.LogFormat = override\n\t}\n\n\tif override, ok := annotations[k8s.ProxyRequireIdentityOnInboundPortsAnnotation]; ok {\n\t\tvalues.Proxy.RequireIdentityOnInboundPorts = override\n\t}\n\n\tif override, ok := annotations[k8s.ProxyEnableHostnameLabels]; ok {\n\t\tvalue, err := strconv.ParseBool(override)\n\t\tif err == nil {\n\t\t\tvalues.Proxy.Metrics.HostnameLabels = value\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyOutboundConnectTimeout]; ok {\n\t\tduration, err := time.ParseDuration(override)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"unrecognized proxy-outbound-connect-timeout duration value found on pod annotation: %s\", err.Error())\n\t\t} else {\n\t\t\tvalues.Proxy.OutboundConnectTimeout = fmt.Sprintf(\"%dms\", int(duration.Seconds()*1000))\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyInboundConnectTimeout]; ok {\n\t\tduration, err := time.ParseDuration(override)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"unrecognized proxy-inbound-connect-timeout duration value found on pod annotation: %s\", err.Error())\n\t\t} else {\n\t\t\tvalues.Proxy.InboundConnectTimeout = fmt.Sprintf(\"%dms\", int(duration.Seconds()*1000))\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyOutboundDiscoveryCacheUnusedTimeout]; ok {\n\t\tduration, err := time.ParseDuration(override)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"unrecognized duration value used on pod annotation %s: %s\", k8s.ProxyOutboundDiscoveryCacheUnusedTimeout, err.Error())\n\t\t} else {\n\t\t\tvalues.Proxy.OutboundDiscoveryCacheUnusedTimeout = fmt.Sprintf(\"%ds\", int(duration.Seconds()))\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyInboundDiscoveryCacheUnusedTimeout]; ok {\n\t\tduration, err := time.ParseDuration(override)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"unrecognized duration value used on pod annotation %s: %s\", k8s.ProxyInboundDiscoveryCacheUnusedTimeout, err.Error())\n\t\t} else {\n\t\t\tvalues.Proxy.InboundDiscoveryCacheUnusedTimeout = fmt.Sprintf(\"%ds\", int(duration.Seconds()))\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyDisableOutboundProtocolDetectTimeout]; ok {\n\t\tvalue, err := strconv.ParseBool(override)\n\t\tif err == nil {\n\t\t\tvalues.Proxy.DisableOutboundProtocolDetectTimeout = value\n\t\t} else {\n\t\t\tlog.Warnf(\"unrecognised value used on pod annotation %s: %s\", k8s.ProxyDisableOutboundProtocolDetectTimeout, err.Error())\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyDisableInboundProtocolDetectTimeout]; ok {\n\t\tvalue, err := strconv.ParseBool(override)\n\t\tif err == nil {\n\t\t\tvalues.Proxy.DisableInboundProtocolDetectTimeout = value\n\t\t} else {\n\t\t\tlog.Warnf(\"unrecognised value used on pod annotation %s: %s\", k8s.ProxyDisableInboundProtocolDetectTimeout, err.Error())\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyShutdownGracePeriodAnnotation]; ok {\n\t\tduration, err := time.ParseDuration(override)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"unrecognized proxy-shutdown-grace-period duration value found on pod annotation: %s\", err.Error())\n\t\t} else {\n\t\t\tvalues.Proxy.ShutdownGracePeriod = fmt.Sprintf(\"%dms\", int(duration.Seconds()*1000))\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyEnableGatewayAnnotation]; ok {\n\t\tvalue, err := strconv.ParseBool(override)\n\t\tif err == nil {\n\t\t\tvalues.Proxy.IsGateway = value\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyWaitBeforeExitSecondsAnnotation]; ok {\n\t\twaitBeforeExitSeconds, err := strconv.ParseUint(override, 10, 64)\n\t\tif nil != err {\n\t\t\tlog.Warnf(\"unrecognized value used for the %s annotation, uint64 is expected: %s\",\n\t\t\t\tk8s.ProxyWaitBeforeExitSecondsAnnotation, override)\n\t\t} else {\n\t\t\tvalues.Proxy.WaitBeforeExitSeconds = waitBeforeExitSeconds\n\t\t}\n\t}\n\n\t// ProxyEnableNativeSidecarAnnotationBeta should take precedence over ProxyEnableNativeSidecarAnnotationAlpha\n\tif override, ok := annotations[k8s.ProxyEnableNativeSidecarAnnotationBeta]; ok {\n\t\tvalue, err := strconv.ParseBool(override)\n\t\tif err == nil {\n\t\t\tvalues.Proxy.NativeSidecar = value\n\t\t}\n\t} else if override, ok = annotations[k8s.ProxyEnableNativeSidecarAnnotationAlpha]; ok {\n\t\tvalue, err := strconv.ParseBool(override)\n\t\tif err == nil {\n\t\t\tvalues.Proxy.NativeSidecar = value\n\t\t}\n\t}\n\n\t// Proxy CPU resources\n\n\tif override, ok := annotations[k8s.ProxyCPURequestAnnotation]; ok {\n\t\tq, err := k8sResource.ParseQuantity(override)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"%s (%s)\", err, k8s.ProxyCPURequestAnnotation)\n\t\t} else {\n\t\t\tvalues.Proxy.Resources.CPU.Request = override\n\n\t\t\tn, err := ToWholeCPUCores(q)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warnf(\"%s (%s)\", err, k8s.ProxyCPULimitAnnotation)\n\t\t\t}\n\t\t\tvalues.Proxy.Runtime.Workers.Minimum = n\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyCPULimitAnnotation]; ok {\n\t\tq, err := k8sResource.ParseQuantity(override)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"%s (%s)\", err, k8s.ProxyCPULimitAnnotation)\n\t\t} else {\n\t\t\tvalues.Proxy.Resources.CPU.Limit = override\n\n\t\t\tn, err := ToWholeCPUCores(q)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warnf(\"%s (%s)\", err, k8s.ProxyCPULimitAnnotation)\n\t\t\t}\n\t\t\tvalues.Proxy.Runtime.Workers.Maximum = n\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyCPURatioLimitAnnotation]; ok {\n\t\tratio, err := strconv.ParseFloat(override, 64)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"%s (%s)\", err, k8s.ProxyCPURatioLimitAnnotation)\n\t\t} else if (ratio <= 0.0) || (ratio >= 1.0) {\n\t\t\tlog.Warnf(\"invalid value used for the %s annotation, valid values are between 0.0 and 1.0\",\n\t\t\t\tk8s.ProxyCPURatioLimitAnnotation)\n\t\t} else {\n\t\t\tvalues.Proxy.Runtime.Workers.MaximumCPURatio = ratio\n\t\t}\n\t}\n\n\t// Proxy memory resources\n\n\tif override, ok := annotations[k8s.ProxyMemoryRequestAnnotation]; ok {\n\t\t_, err := k8sResource.ParseQuantity(override)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"%s (%s)\", err, k8s.ProxyMemoryRequestAnnotation)\n\t\t} else {\n\t\t\tvalues.Proxy.Resources.Memory.Request = override\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyMemoryLimitAnnotation]; ok {\n\t\t_, err := k8sResource.ParseQuantity(override)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"%s (%s)\", err, k8s.ProxyMemoryLimitAnnotation)\n\t\t} else {\n\t\t\tvalues.Proxy.Resources.Memory.Limit = override\n\t\t}\n\t}\n\n\t// Proxy ephemeral storage resources\n\n\tif override, ok := annotations[k8s.ProxyEphemeralStorageRequestAnnotation]; ok {\n\t\t_, err := k8sResource.ParseQuantity(override)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"%s (%s)\", err, k8s.ProxyEphemeralStorageRequestAnnotation)\n\t\t} else {\n\t\t\tvalues.Proxy.Resources.EphemeralStorage.Request = override\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyEphemeralStorageLimitAnnotation]; ok {\n\t\t_, err := k8sResource.ParseQuantity(override)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"%s (%s)\", err, k8s.ProxyEphemeralStorageLimitAnnotation)\n\t\t} else {\n\t\t\tvalues.Proxy.Resources.EphemeralStorage.Limit = override\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyUIDAnnotation]; ok {\n\t\tv, err := strconv.ParseInt(override, 10, 64)\n\t\tif err == nil {\n\t\t\tvalues.Proxy.UID = v\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyGIDAnnotation]; ok {\n\t\tv, err := strconv.ParseInt(override, 10, 64)\n\t\tif err == nil {\n\t\t\tvalues.Proxy.GID = v\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyEnableExternalProfilesAnnotation]; ok {\n\t\tvalue, err := strconv.ParseBool(override)\n\t\tif err == nil {\n\t\t\tvalues.Proxy.EnableExternalProfiles = value\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyIgnoreInboundPortsAnnotation]; ok {\n\t\tvalues.ProxyInit.IgnoreInboundPorts = override\n\t}\n\n\tif override, ok := annotations[k8s.ProxyIgnoreOutboundPortsAnnotation]; ok {\n\t\tvalues.ProxyInit.IgnoreOutboundPorts = override\n\t}\n\n\tif override, ok := annotations[k8s.ProxyOpaquePortsAnnotation]; ok {\n\t\tvar opaquePorts strings.Builder\n\t\tfor _, pr := range util.ParseContainerOpaquePorts(override, namedPorts) {\n\t\t\tif opaquePorts.Len() > 0 {\n\t\t\t\topaquePorts.WriteRune(',')\n\t\t\t}\n\t\t\topaquePorts.WriteString(pr.ToString())\n\t\t}\n\n\t\tvalues.Proxy.OpaquePorts = opaquePorts.String()\n\t}\n\n\tif override, ok := annotations[k8s.DebugImageAnnotation]; ok {\n\t\tvalues.DebugContainer.Image.Name = override\n\t}\n\n\tif override, ok := annotations[k8s.DebugImageVersionAnnotation]; ok {\n\t\tvalues.DebugContainer.Image.Version = override\n\t}\n\n\tif override, ok := annotations[k8s.DebugImagePullPolicyAnnotation]; ok {\n\t\tvalues.DebugContainer.Image.PullPolicy = override\n\t}\n\n\tif override, ok := annotations[k8s.ProxyAwait]; ok {\n\t\tif override == k8s.Enabled || override == k8s.Disabled {\n\t\t\tvalues.Proxy.Await = override == k8s.Enabled\n\t\t} else {\n\t\t\tlog.Warnf(\"unrecognized value used for the %s annotation, valid values are: [%s, %s]\", k8s.ProxyAwait, k8s.Enabled, k8s.Disabled)\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxyDefaultInboundPolicyAnnotation]; ok {\n\t\tif override != k8s.AllUnauthenticated && override != k8s.AllAuthenticated && override != k8s.ClusterUnauthenticated && override != k8s.ClusterAuthenticated && override != k8s.Deny && override != k8s.Audit {\n\t\t\tlog.Warnf(\"unrecognized value used for the %s annotation, valid values are: [%s, %s, %s, %s, %s, %s]\", k8s.ProxyDefaultInboundPolicyAnnotation, k8s.AllUnauthenticated, k8s.AllAuthenticated, k8s.ClusterUnauthenticated, k8s.ClusterAuthenticated, k8s.Deny, k8s.Audit)\n\t\t} else {\n\t\t\tvalues.Proxy.DefaultInboundPolicy = override\n\t\t}\n\t}\n\n\tif override, ok := annotations[k8s.ProxySkipSubnetsAnnotation]; ok {\n\t\tvalues.ProxyInit.SkipSubnets = override\n\t}\n\n\tif override, ok := annotations[k8s.ProxyAccessLogAnnotation]; ok {\n\t\tvalues.Proxy.AccessLog = override\n\t}\n\n\tif values.Proxy.Tracing != nil {\n\t\tif values.Proxy.Tracing.Labels == nil {\n\t\t\tvalues.Proxy.Tracing.Labels = make(map[string]string)\n\t\t}\n\n\t\tif override, ok := labels[k8s.TracingInstanceLabel]; ok {\n\t\t\tvalues.Proxy.Tracing.Labels[k8s.TracingServiceName] = override\n\t\t} else if override, ok := labels[k8s.TracingNameLabel]; ok {\n\t\t\tvalues.Proxy.Tracing.Labels[k8s.TracingServiceName] = override\n\t\t}\n\n\t\tfor name, value := range annotations {\n\t\t\tafter, found := strings.CutPrefix(name, k8s.TracingSemanticConventionPrefix)\n\t\t\tif found {\n\t\t\t\tvalues.Proxy.Tracing.Labels[after] = value\n\t\t\t}\n\t\t}\n\t}\n}\n\n// NewResourceConfig creates and initializes a ResourceConfig\nfunc NewResourceConfig(values *l5dcharts.Values, origin Origin, ns string) *ResourceConfig {\n\tconfig := &ResourceConfig{\n\t\tnamespace:     ns,\n\t\tnsAnnotations: make(map[string]string),\n\t\tvalues:        values,\n\t\torigin:        origin,\n\t}\n\n\tconfig.workload.Meta = &metav1.ObjectMeta{}\n\tconfig.pod.meta = &metav1.ObjectMeta{}\n\n\tconfig.pod.labels = map[string]string{k8s.ControllerNSLabel: ns}\n\tconfig.pod.annotations = map[string]string{}\n\treturn config\n}\n\n// WithKind enriches ResourceConfig with the workload kind\nfunc (conf *ResourceConfig) WithKind(kind string) *ResourceConfig {\n\tconf.workload.metaType = metav1.TypeMeta{Kind: kind}\n\treturn conf\n}\n\n// WithNsAnnotations enriches ResourceConfig with the namespace annotations, that can\n// be used in shouldInject()\nfunc (conf *ResourceConfig) WithNsAnnotations(m map[string]string) *ResourceConfig {\n\tconf.nsAnnotations = m\n\treturn conf\n}\n\n// WithOwnerRetriever enriches ResourceConfig with a function that allows to retrieve\n// the kind and name of the workload's owner reference\nfunc (conf *ResourceConfig) WithOwnerRetriever(f OwnerRetrieverFunc) *ResourceConfig {\n\tconf.ownerRetriever = f\n\treturn conf\n}\n\n// GetOwnerRef returns a reference to the resource's owner resource, if any\nfunc (conf *ResourceConfig) GetOwnerRef() *metav1.OwnerReference {\n\treturn conf.workload.ownerRef\n}\n\nfunc (conf *ResourceConfig) GetOverrideAnnotations() map[string]string {\n\treturn conf.pod.annotations\n}\n\nfunc (conf *ResourceConfig) GetNsAnnotations() map[string]string {\n\treturn conf.nsAnnotations\n}\n\nfunc (conf *ResourceConfig) GetWorkloadAnnotations() map[string]string {\n\tif conf.IsPod() {\n\t\treturn conf.pod.meta.Annotations\n\t}\n\n\treturn conf.workload.Meta.Annotations\n}\n\n// AppendPodAnnotations appends the given annotations to the pod spec in conf\nfunc (conf *ResourceConfig) AppendPodAnnotations(annotations map[string]string) {\n\tfor annotation, value := range annotations {\n\t\tconf.pod.annotations[annotation] = value\n\t}\n}\n\n// AppendPodAnnotation appends the given single annotation to the pod spec in conf\nfunc (conf *ResourceConfig) AppendPodAnnotation(k, v string) {\n\tconf.pod.annotations[k] = v\n}\n\n// YamlMarshalObj returns the yaml for the workload in conf\nfunc (conf *ResourceConfig) YamlMarshalObj() ([]byte, error) {\n\tj, err := getFilteredJSON(conf.workload.obj)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn yaml.JSONToYAML(j)\n}\n\n// ParseMetaAndYAML extracts the workload metadata and pod specs from the given\n// input bytes. The results are stored in the conf's fields.\nfunc (conf *ResourceConfig) ParseMetaAndYAML(bytes []byte) (*Report, error) {\n\tif err := conf.parse(bytes); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn newReport(conf), nil\n}\n\n// FromObject extracts the workload metadata and pod specs from the given\n// runtime.Object instance. The results are stored in the conf's fields.\nfunc (conf *ResourceConfig) FromObject(v runtime.Object) (*Report, error) {\n\tif err := conf.populateMeta(v); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn newReport(conf), nil\n}\n\n// GetValues returns the values used for rendering patches.\nfunc (conf *ResourceConfig) GetValues() *l5dcharts.Values {\n\treturn conf.values\n}\n\nfunc (conf *ResourceConfig) GetNodeSelector() map[string]string {\n\tif conf.HasPodTemplate() {\n\t\treturn conf.pod.spec.NodeSelector\n\t}\n\n\treturn nil\n}\n\nfunc (conf *ResourceConfig) GetAnnotationOverrides() map[string]string {\n\toverrides := map[string]string{}\n\tfor k, v := range conf.pod.meta.Annotations {\n\t\toverrides[k] = v\n\t}\n\n\tif conf.origin != OriginCLI {\n\t\tfor k, v := range conf.pod.annotations {\n\t\t\toverrides[k] = v\n\t\t}\n\t}\n\treturn overrides\n}\n\nfunc (conf *ResourceConfig) GetLabelOverrides() map[string]string {\n\toverrides := map[string]string{}\n\tfor k, v := range conf.pod.meta.Labels {\n\t\toverrides[k] = v\n\t}\n\n\tif conf.origin != OriginCLI {\n\t\tfor k, v := range conf.pod.labels {\n\t\t\toverrides[k] = v\n\t\t}\n\t}\n\treturn overrides\n}\n\n// GetPodPatch returns the JSON patch containing the proxy and init containers specs, if any.\n// If injectProxy is false, only the config.linkerd.io annotations are set.\nfunc (conf *ResourceConfig) GetPodPatch(injectProxy bool, overrider ValueOverrider) ([]byte, error) {\n\tvalues, err := overrider(conf)\n\tvalues.Proxy.PodInboundPorts = getPodInboundPorts(conf.pod.spec)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not generate Overridden Values: %w\", err)\n\t}\n\n\tif values.ClusterNetworks != \"\" {\n\t\tfor _, network := range strings.Split(strings.Trim(values.ClusterNetworks, \",\"), \",\") {\n\t\t\tif _, _, err := net.ParseCIDR(network); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"cannot parse destination get networks: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tpatch := &podPatch{\n\t\tValues:      *values,\n\t\tAnnotations: map[string]string{},\n\t\tLabels:      map[string]string{},\n\t}\n\tswitch strings.ToLower(conf.workload.metaType.Kind) {\n\tcase k8s.Pod:\n\tcase k8s.CronJob:\n\t\tpatch.PathPrefix = \"/spec/jobTemplate/spec/template\"\n\tdefault:\n\t\tpatch.PathPrefix = \"/spec/template\"\n\t}\n\n\tif conf.pod.spec != nil {\n\t\tconf.injectPodAnnotations(patch)\n\t\tif injectProxy {\n\t\t\tconf.injectObjectMeta(patch)\n\t\t\tconf.injectPodSpec(patch)\n\t\t} else {\n\t\t\tpatch.Proxy = nil\n\t\t\tpatch.ProxyInit = nil\n\t\t}\n\t}\n\n\trawValues, err := yaml.Marshal(patch)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfiles := []*loader.BufferedFile{\n\t\t{Name: chartutil.ChartfileName},\n\t\t{Name: \"requirements.yaml\"},\n\t\t{Name: \"templates/patch.json\"},\n\t}\n\n\tchart := &chartspkg.Chart{\n\t\tName:      \"patch\",\n\t\tDir:       \"patch\",\n\t\tNamespace: conf.namespace,\n\t\tRawValues: rawValues,\n\t\tFiles:     files,\n\t\tFs:        charts.Templates,\n\t}\n\tbuf, err := chart.Render()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Get rid of invalid trailing commas\n\tres := rTrail.ReplaceAll(buf.Bytes(), []byte(\"}\\n]\"))\n\n\treturn res, nil\n}\n\n// GetConfigAnnotation returns two values. The first value is the annotation\n// value for a given key. The second is used to decide whether or not the caller\n// should add the annotation. The caller should not add the annotation if the\n// resource already has its own.\nfunc GetConfigOverride(annotationKey string, workloadAnn map[string]string, nsAnn map[string]string) (string, bool) {\n\t_, ok := workloadAnn[annotationKey]\n\tif ok {\n\t\tlog.Debugf(\"using workload %s annotation value\", annotationKey)\n\t\treturn \"\", false\n\t}\n\n\tannotation, ok := nsAnn[annotationKey]\n\tif ok {\n\t\tlog.Debugf(\"using namespace %s annotation value\", annotationKey)\n\t\treturn annotation, true\n\t}\n\treturn \"\", false\n}\n\n// CreateOpaquePortsPatch creates a patch that will add the default\n// list of opaque ports.\nfunc (conf *ResourceConfig) CreateOpaquePortsPatch() ([]byte, error) {\n\tif conf.HasWorkloadAnnotation(k8s.ProxyOpaquePortsAnnotation) {\n\t\t// The workload already has the opaque ports annotation so a patch\n\t\t// does not need to be created.\n\t\treturn nil, nil\n\t}\n\tworkloadAnn := conf.workload.Meta.Annotations\n\tif conf.IsPod() {\n\t\tworkloadAnn = conf.pod.meta.Annotations\n\t}\n\n\topaquePorts, ok := GetConfigOverride(k8s.ProxyOpaquePortsAnnotation, workloadAnn, conf.nsAnnotations)\n\tif ok {\n\t\t// The workload's namespace has the opaque ports annotation, so it\n\t\t// should inherit that value. A patch is created which adds that\n\t\t// list.\n\t\treturn conf.CreateAnnotationPatch(opaquePorts)\n\t}\n\n\t// Both the workload and the namespace do not have the annotation so a\n\t// patch is created which adds the default list.\n\tdefaultPorts := strings.Split(conf.GetValues().Proxy.OpaquePorts, \",\")\n\tvar filteredPorts []string\n\tif conf.IsPod() {\n\t\t// The workload is a pod so only add the default opaque ports that it\n\t\t// exposes as container ports.\n\t\tfilteredPorts = conf.FilterPodOpaquePorts(defaultPorts)\n\t} else if conf.IsService() {\n\t\t// The workload is a service so only add the default opaque ports that\n\t\t// are exposed as a service port, or targeted as a targetPort.\n\t\tservice := conf.workload.obj.(*corev1.Service)\n\t\tfor _, p := range service.Spec.Ports {\n\t\t\tport := strconv.Itoa(int(p.Port))\n\t\t\tif p.TargetPort.Type == 0 && p.TargetPort.IntVal == 0 {\n\t\t\t\t// The port's targetPort is not set, so add the port if is\n\t\t\t\t// opaque by default. Checking that targetPort is not set\n\t\t\t\t// avoids marking a port as opaque if it targets a port that\n\t\t\t\t// not opaque (e.g. port=3306 and targetPort=80; 3306 should\n\t\t\t\t// not be opaque)\n\t\t\t\tif util.ContainsString(port, defaultPorts) {\n\t\t\t\t\tfilteredPorts = append(filteredPorts, port)\n\t\t\t\t}\n\t\t\t} else if util.ContainsString(strconv.Itoa(int(p.TargetPort.IntVal)), defaultPorts) {\n\t\t\t\t// The port's targetPort is set; if it is opaque then port\n\t\t\t\t// should also be opaque.\n\t\t\t\tfilteredPorts = append(filteredPorts, port)\n\t\t\t}\n\t\t}\n\t}\n\tif len(filteredPorts) == 0 {\n\t\t// There are no default opaque ports to add so a patch does not need\n\t\t// to be created.\n\t\treturn nil, nil\n\t}\n\tports := strings.Join(filteredPorts, \",\")\n\treturn conf.CreateAnnotationPatch(ports)\n}\n\n// FilterPodOpaquePorts returns a list of opaque ports that a pod exposes that\n// are also in the given default opaque ports list.\nfunc (conf *ResourceConfig) FilterPodOpaquePorts(defaultPorts []string) []string {\n\tvar filteredPorts []string\n\tfor _, c := range append(conf.pod.spec.InitContainers, conf.pod.spec.Containers...) {\n\t\tfor _, p := range c.Ports {\n\t\t\tport := strconv.Itoa(int(p.ContainerPort))\n\t\t\tif util.ContainsString(port, defaultPorts) {\n\t\t\t\tfilteredPorts = append(filteredPorts, port)\n\t\t\t}\n\t\t}\n\t}\n\treturn filteredPorts\n}\n\n// HasWorkloadAnnotation returns true if the workload has the annotation set\n// by the resource config or its metadata.\nfunc (conf *ResourceConfig) HasWorkloadAnnotation(annotation string) bool {\n\tif _, ok := conf.pod.meta.Annotations[annotation]; ok {\n\t\treturn true\n\t}\n\tif _, ok := conf.workload.Meta.Annotations[annotation]; ok {\n\t\treturn true\n\t}\n\t_, ok := conf.pod.annotations[annotation]\n\treturn ok\n}\n\n// CreateAnnotationPatch returns a json patch which adds the opaque ports\n// annotation with the `opaquePorts` value.\nfunc (conf *ResourceConfig) CreateAnnotationPatch(opaquePorts string) ([]byte, error) {\n\taddRootAnnotations := false\n\tif conf.IsPod() {\n\t\taddRootAnnotations = len(conf.pod.meta.Annotations) == 0\n\t} else {\n\t\taddRootAnnotations = len(conf.workload.Meta.Annotations) == 0\n\t}\n\n\tpatch := &annotationPatch{\n\t\tAddRootAnnotations: addRootAnnotations,\n\t\tOpaquePorts:        opaquePorts,\n\t}\n\tt, err := template.New(\"tpl\").Parse(tpl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar patchJSON bytes.Buffer\n\tif err = t.Execute(&patchJSON, patch); err != nil {\n\t\treturn nil, err\n\t}\n\treturn patchJSON.Bytes(), nil\n}\n\n// Note this switch also defines what kinds are injectable\nfunc (conf *ResourceConfig) getFreshWorkloadObj() runtime.Object {\n\tswitch strings.ToLower(conf.workload.metaType.Kind) {\n\tcase k8s.Deployment:\n\t\treturn &appsv1.Deployment{}\n\tcase k8s.ReplicationController:\n\t\treturn &corev1.ReplicationController{}\n\tcase k8s.ReplicaSet:\n\t\treturn &appsv1.ReplicaSet{}\n\tcase k8s.Job:\n\t\treturn &batchv1.Job{}\n\tcase k8s.DaemonSet:\n\t\treturn &appsv1.DaemonSet{}\n\tcase k8s.StatefulSet:\n\t\treturn &appsv1.StatefulSet{}\n\tcase k8s.Pod:\n\t\treturn &corev1.Pod{}\n\tcase k8s.Namespace:\n\t\treturn &corev1.Namespace{}\n\tcase k8s.CronJob:\n\t\treturn &batchv1.CronJob{}\n\tcase k8s.Service:\n\t\treturn &corev1.Service{}\n\t}\n\n\treturn nil\n}\n\n// JSONToYAML is a replacement for the same function in sigs.k8s.io/yaml\n// that does conserve the field order as portrayed in k8s' api structs\nfunc (conf *ResourceConfig) JSONToYAML(bytes []byte) ([]byte, error) {\n\tobj := conf.getFreshWorkloadObj()\n\tif err := json.Unmarshal(bytes, obj); err != nil {\n\t\treturn nil, err\n\t}\n\n\tj, err := getFilteredJSON(obj)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn yaml.JSONToYAML(j)\n}\n\nfunc (conf *ResourceConfig) populateMeta(obj runtime.Object) error {\n\tswitch v := obj.(type) {\n\tcase *appsv1.Deployment:\n\t\tconf.workload.obj = v\n\t\tconf.workload.Meta = &v.ObjectMeta\n\t\tconf.pod.labels[k8s.ProxyDeploymentLabel] = v.Name\n\t\tconf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace\n\t\tconf.complete(&v.Spec.Template)\n\n\tcase *corev1.ReplicationController:\n\t\tconf.workload.obj = v\n\t\tconf.workload.Meta = &v.ObjectMeta\n\t\tconf.pod.labels[k8s.ProxyReplicationControllerLabel] = v.Name\n\t\tconf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace\n\t\tconf.complete(v.Spec.Template)\n\n\tcase *appsv1.ReplicaSet:\n\t\tconf.workload.obj = v\n\t\tconf.workload.Meta = &v.ObjectMeta\n\t\tconf.pod.labels[k8s.ProxyReplicaSetLabel] = v.Name\n\t\tconf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace\n\t\tconf.complete(&v.Spec.Template)\n\n\tcase *batchv1.Job:\n\t\tconf.workload.obj = v\n\t\tconf.workload.Meta = &v.ObjectMeta\n\t\tconf.pod.labels[k8s.ProxyJobLabel] = v.Name\n\t\tconf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace\n\t\tconf.complete(&v.Spec.Template)\n\n\tcase *appsv1.DaemonSet:\n\t\tconf.workload.obj = v\n\t\tconf.workload.Meta = &v.ObjectMeta\n\t\tconf.pod.labels[k8s.ProxyDaemonSetLabel] = v.Name\n\t\tconf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace\n\t\tconf.complete(&v.Spec.Template)\n\n\tcase *appsv1.StatefulSet:\n\t\tconf.workload.obj = v\n\t\tconf.workload.Meta = &v.ObjectMeta\n\t\tconf.pod.labels[k8s.ProxyStatefulSetLabel] = v.Name\n\t\tconf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace\n\t\tconf.complete(&v.Spec.Template)\n\n\tcase *corev1.Namespace:\n\t\tconf.workload.obj = v\n\t\tconf.workload.Meta = &v.ObjectMeta\n\t\tif conf.workload.Meta.Annotations == nil {\n\t\t\tconf.workload.Meta.Annotations = map[string]string{}\n\t\t}\n\n\tcase *batchv1.CronJob:\n\t\tconf.workload.obj = v\n\t\tconf.workload.Meta = &v.ObjectMeta\n\t\tconf.pod.labels[k8s.ProxyCronJobLabel] = v.Name\n\t\tconf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace\n\t\tconf.complete(&v.Spec.JobTemplate.Spec.Template)\n\n\tcase *corev1.Pod:\n\t\tconf.workload.obj = v\n\t\tconf.pod.spec = &v.Spec\n\t\tconf.pod.meta = &v.ObjectMeta\n\n\t\tif conf.ownerRetriever != nil {\n\t\t\tkind, name, err := conf.ownerRetriever(v)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tconf.workload.ownerRef = &metav1.OwnerReference{Kind: kind, Name: name}\n\t\t\tswitch kind {\n\t\t\tcase k8s.Deployment:\n\t\t\t\tconf.pod.labels[k8s.ProxyDeploymentLabel] = name\n\t\t\tcase k8s.ReplicationController:\n\t\t\t\tconf.pod.labels[k8s.ProxyReplicationControllerLabel] = name\n\t\t\tcase k8s.ReplicaSet:\n\t\t\t\tconf.pod.labels[k8s.ProxyReplicaSetLabel] = name\n\t\t\tcase k8s.Job:\n\t\t\t\tconf.pod.labels[k8s.ProxyJobLabel] = name\n\t\t\tcase k8s.DaemonSet:\n\t\t\t\tconf.pod.labels[k8s.ProxyDaemonSetLabel] = name\n\t\t\tcase k8s.StatefulSet:\n\t\t\t\tconf.pod.labels[k8s.ProxyStatefulSetLabel] = name\n\t\t\t}\n\t\t}\n\t\tconf.pod.labels[k8s.WorkloadNamespaceLabel] = v.Namespace\n\t\tif conf.pod.meta.Annotations == nil {\n\t\t\tconf.pod.meta.Annotations = map[string]string{}\n\t\t}\n\n\tcase *corev1.Service:\n\t\tconf.workload.obj = v\n\t\tconf.workload.Meta = &v.ObjectMeta\n\t\tif conf.workload.Meta.Annotations == nil {\n\t\t\tconf.workload.Meta.Annotations = map[string]string{}\n\t\t}\n\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported type %T\", v)\n\t}\n\n\treturn nil\n}\n\n// parse parses the bytes payload, filling the gaps in ResourceConfig\n// depending on the workload kind\nfunc (conf *ResourceConfig) parse(bytes []byte) error {\n\t// The Kubernetes API is versioned and each version has an API modeled\n\t// with its own distinct Go types. If we tell `yaml.Unmarshal()` which\n\t// version we support then it will provide a representation of that\n\t// object using the given type if possible. However, it only allows us\n\t// to supply one object (of one type), so first we have to determine\n\t// what kind of object `bytes` represents so we can pass an object of\n\t// the correct type to `yaml.Unmarshal()`.\n\t// ---------------------------------------\n\t// Note: bytes is expected to be YAML and will only modify it when a\n\t// supported type is found. Otherwise, conf is left unmodified.\n\n\t// When injecting the linkerd proxy into a linkerd controller pod. The linkerd proxy's\n\t// LINKERD2_PROXY_DESTINATION_SVC_ADDR variable must be set to localhost for\n\t// the following reasons:\n\t//\t1. According to https://github.com/kubernetes/minikube/issues/1568, minikube has an issue\n\t//     where pods are unable to connect to themselves through their associated service IP.\n\t//     Setting the LINKERD2_PROXY_DESTINATION_SVC_ADDR to localhost allows the\n\t//     proxy to bypass kube DNS name resolution as a workaround to this issue.\n\t//  2. We avoid the TLS overhead in encrypting and decrypting intra-pod traffic i.e. traffic\n\t//     between containers in the same pod.\n\t//  3. Using a Service IP instead of localhost would mean intra-pod traffic would be load-balanced\n\t//     across all controller pod replicas. This is undesirable as we would want all traffic between\n\t//\t   containers to be self contained.\n\t//  4. We skip recording telemetry for intra-pod traffic within the control plane.\n\n\tif err := yaml.Unmarshal(bytes, &conf.workload.metaType); err != nil {\n\t\treturn err\n\t}\n\tobj := conf.getFreshWorkloadObj()\n\n\tswitch v := obj.(type) {\n\tcase *appsv1.Deployment,\n\t\t*corev1.ReplicationController,\n\t\t*appsv1.ReplicaSet,\n\t\t*batchv1.Job,\n\t\t*appsv1.DaemonSet,\n\t\t*appsv1.StatefulSet,\n\t\t*corev1.Namespace,\n\t\t*batchv1.CronJob,\n\t\t*corev1.Pod,\n\t\t*corev1.Service:\n\t\tif err := yaml.Unmarshal(bytes, v); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := conf.populateMeta(v); err != nil {\n\t\t\treturn err\n\t\t}\n\n\tdefault:\n\t\t// unmarshal the metadata of other resource kinds like namespace, secret,\n\t\t// config map etc. to be used in the report struct\n\t\tif err := yaml.Unmarshal(bytes, &conf.workload); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (conf *ResourceConfig) complete(template *corev1.PodTemplateSpec) {\n\tconf.pod.spec = &template.Spec\n\tconf.pod.meta = &template.ObjectMeta\n\tif conf.pod.meta.Annotations == nil {\n\t\tconf.pod.meta.Annotations = map[string]string{}\n\t}\n}\n\n// injectPodSpec adds linkerd sidecars to the provided PodSpec.\nfunc (conf *ResourceConfig) injectPodSpec(values *podPatch) {\n\tsaVolumeMount := conf.serviceAccountVolumeMount()\n\n\t// use the primary container's capabilities to ensure psp compliance, if\n\t// enabled\n\tif len(conf.pod.spec.Containers) > 0 {\n\t\tif sc := conf.pod.spec.Containers[0].SecurityContext; sc != nil && sc.Capabilities != nil {\n\t\t\tvalues.Proxy.Capabilities = &l5dcharts.Capabilities{\n\t\t\t\tAdd:  []string{},\n\t\t\t\tDrop: []string{},\n\t\t\t}\n\t\t\tfor _, add := range sc.Capabilities.Add {\n\t\t\t\tvalues.Proxy.Capabilities.Add = append(values.Proxy.Capabilities.Add, string(add))\n\t\t\t}\n\t\t\tfor _, drop := range sc.Capabilities.Drop {\n\t\t\t\tvalues.Proxy.Capabilities.Drop = append(values.Proxy.Capabilities.Drop, string(drop))\n\t\t\t}\n\t\t}\n\t}\n\n\tif saVolumeMount != nil {\n\t\tvalues.Proxy.SAMountPath = &l5dcharts.VolumeMountPath{\n\t\t\tName:      saVolumeMount.Name,\n\t\t\tMountPath: saVolumeMount.MountPath,\n\t\t\tReadOnly:  saVolumeMount.ReadOnly,\n\t\t}\n\t}\n\n\tif v := conf.pod.meta.Annotations[k8s.ProxyEnableDebugAnnotation]; v != \"\" {\n\t\tdebug, err := strconv.ParseBool(v)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"unrecognized value used for the %s annotation: %s\", k8s.ProxyEnableDebugAnnotation, v)\n\t\t\tdebug = false\n\t\t}\n\n\t\tif debug {\n\t\t\tlog.Infof(\"inject debug container\")\n\t\t\tvalues.DebugContainer = &l5dcharts.DebugContainer{\n\t\t\t\tImage: &l5dcharts.Image{\n\t\t\t\t\tName:       values.Values.DebugContainer.Image.Name,\n\t\t\t\t\tVersion:    values.Values.DebugContainer.Image.Version,\n\t\t\t\t\tPullPolicy: values.Values.DebugContainer.Image.PullPolicy,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\t}\n\n\tconf.injectProxyInit(values)\n\tvalues.AddRootVolumes = len(conf.pod.spec.Volumes) == 0\n}\n\nfunc (conf *ResourceConfig) injectProxyInit(values *podPatch) {\n\n\t// Fill common fields from Proxy into ProxyInit\n\tif values.Proxy.Capabilities != nil {\n\t\tvalues.ProxyInit.Capabilities = &l5dcharts.Capabilities{}\n\t\tvalues.ProxyInit.Capabilities.Add = values.Proxy.Capabilities.Add\n\t\tvalues.ProxyInit.Capabilities.Drop = []string{}\n\t\tfor _, drop := range values.Proxy.Capabilities.Drop {\n\t\t\t// Skip NET_RAW and NET_ADMIN as the init container requires them to setup iptables.\n\t\t\tif drop == \"NET_RAW\" || drop == \"NET_ADMIN\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvalues.ProxyInit.Capabilities.Drop = append(values.ProxyInit.Capabilities.Drop, drop)\n\t\t}\n\t}\n\n\tvalues.ProxyInit.SAMountPath = values.Proxy.SAMountPath\n\n\tif v := conf.pod.meta.Annotations[k8s.CloseWaitTimeoutAnnotation]; v != \"\" {\n\t\tcloseWait, err := time.ParseDuration(v)\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"invalid duration value used for the %s annotation: %s\", k8s.CloseWaitTimeoutAnnotation, v)\n\t\t} else {\n\t\t\tvalues.ProxyInit.CloseWaitTimeoutSecs = int64(closeWait.Seconds())\n\t\t}\n\t}\n\n\tvalues.AddRootInitContainers = len(conf.pod.spec.InitContainers) == 0\n\n}\n\nfunc (conf *ResourceConfig) serviceAccountVolumeMount() *corev1.VolumeMount {\n\t// Probably always true, but want to be super-safe\n\tif containers := conf.pod.spec.Containers; len(containers) > 0 {\n\t\tfor _, vm := range containers[0].VolumeMounts {\n\t\t\tif vm.MountPath == k8s.MountPathServiceAccount {\n\t\t\t\tvm := vm // pin\n\t\t\t\treturn &vm\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// Given a ObjectMeta, update ObjectMeta in place with the new labels and\n// annotations.\nfunc (conf *ResourceConfig) injectObjectMeta(values *podPatch) {\n\n\t// Default proxy version to linkerd version\n\tif values.Proxy.Image.Version != \"\" {\n\t\tvalues.Annotations[k8s.ProxyVersionAnnotation] = values.Proxy.Image.Version\n\t} else {\n\t\tvalues.Annotations[k8s.ProxyVersionAnnotation] = values.LinkerdVersion\n\t}\n\n\t// Add the cert bundle's checksum to the workload's annotations.\n\tchecksumBytes := sha256.Sum256([]byte(values.IdentityTrustAnchorsPEM))\n\tchecksum := hex.EncodeToString(checksumBytes[:])\n\tvalues.Annotations[k8s.ProxyTrustRootSHA] = checksum\n\n\tif len(conf.pod.labels) > 0 {\n\t\tvalues.AddRootLabels = len(conf.pod.meta.Labels) == 0\n\t\tfor _, k := range sortedKeys(conf.pod.labels) {\n\t\t\tvalues.Labels[k] = conf.pod.labels[k]\n\t\t}\n\t}\n}\n\nfunc (conf *ResourceConfig) injectPodAnnotations(values *podPatch) {\n\t// ObjectMetaAnnotations.Annotations is nil for new empty structs, but we always initialize\n\t// it to an empty map in parse() above, so we follow suit here.\n\temptyMeta := &metav1.ObjectMeta{Annotations: map[string]string{}}\n\t// Cronjobs might have an empty `spec.jobTemplate.spec.template.metadata`\n\t// field so we make sure to create it if needed, before attempting adding annotations\n\tvalues.AddRootMetadata = reflect.DeepEqual(conf.pod.meta, emptyMeta)\n\tvalues.AddRootAnnotations = len(conf.pod.meta.Annotations) == 0\n\n\tfor _, k := range sortedKeys(conf.pod.annotations) {\n\t\tvalues.Annotations[k] = conf.pod.annotations[k]\n\n\t\t// append any additional pod annotations to the pod's meta.\n\t\t// for e.g., annotations that were converted from CLI inject options.\n\t\tconf.pod.meta.Annotations[k] = conf.pod.annotations[k]\n\t}\n}\n\n// GetOverriddenConfiguration returns a map of the overridden proxy annotations\nfunc (conf *ResourceConfig) GetOverriddenConfiguration() map[string]string {\n\tproxyOverrideConfig := map[string]string{}\n\tfor _, annotation := range ProxyAnnotations {\n\t\tproxyOverrideConfig[annotation] = conf.pod.meta.Annotations[annotation]\n\t}\n\n\treturn proxyOverrideConfig\n}\n\n// IsControlPlaneComponent returns true if the component is part of linkerd control plane\nfunc (conf *ResourceConfig) IsControlPlaneComponent() bool {\n\t_, b := conf.pod.meta.Labels[k8s.ControllerComponentLabel]\n\treturn b\n}\n\nfunc sortedKeys(m map[string]string) []string {\n\tkeys := []string{}\n\tfor k := range m {\n\t\tkeys = append(keys, k)\n\t}\n\n\tsort.Strings(keys)\n\n\treturn keys\n}\n\n// IsNamespace checks if a given config is a workload of Kind namespace\nfunc (conf *ResourceConfig) IsNamespace() bool {\n\treturn strings.ToLower(conf.workload.metaType.Kind) == k8s.Namespace\n}\n\n// IsService checks if a given config is a workload of Kind service\nfunc (conf *ResourceConfig) IsService() bool {\n\treturn strings.ToLower(conf.workload.metaType.Kind) == k8s.Service\n}\n\n// IsPod checks if a given config is a workload of Kind pod.\nfunc (conf *ResourceConfig) IsPod() bool {\n\treturn strings.ToLower(conf.workload.metaType.Kind) == k8s.Pod\n}\n\n// HasPodTemplate checks if a given config has a pod template spec.\nfunc (conf *ResourceConfig) HasPodTemplate() bool {\n\treturn conf.pod.meta != nil && conf.pod.spec != nil\n}\n\n// AnnotateNamespace annotates a namespace resource config with `annotations`.\nfunc (conf *ResourceConfig) AnnotateNamespace(annotations map[string]string) ([]byte, error) {\n\tns, ok := conf.workload.obj.(*corev1.Namespace)\n\tif !ok {\n\t\treturn nil, errors.New(\"can't inject namespace. Type assertion failed\")\n\t}\n\tns.Annotations[k8s.ProxyInjectAnnotation] = k8s.ProxyInjectEnabled\n\tif len(annotations) > 0 {\n\t\tfor annotation, value := range annotations {\n\t\t\tns.Annotations[annotation] = value\n\t\t}\n\t}\n\tj, err := getFilteredJSON(ns)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn yaml.JSONToYAML(j)\n}\n\n// AnnotateService annotates a service resource config with `annotations`.\nfunc (conf *ResourceConfig) AnnotateService(annotations map[string]string) ([]byte, error) {\n\tservice, ok := conf.workload.obj.(*corev1.Service)\n\tif !ok {\n\t\treturn nil, errors.New(\"can't inject service. Type assertion failed\")\n\t}\n\tif len(annotations) > 0 {\n\t\tfor annotation, value := range annotations {\n\t\t\tservice.Annotations[annotation] = value\n\t\t}\n\t}\n\tj, err := getFilteredJSON(service)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn yaml.JSONToYAML(j)\n}\n\n// getFilteredJSON method performs JSON marshaling such that zero values of\n// empty structs are respected by `omitempty` tags. We make use of a drop-in\n// replacement of the standard json/encoding library, without which empty struct values\n// present in workload objects would make it into the marshaled JSON.\nfunc getFilteredJSON(conf runtime.Object) ([]byte, error) {\n\treturn jsonfilter.Marshal(&conf)\n}\n\n// ToWholeCPUCores coerces a k8s resource value to a whole integer value, rounding up.\nfunc ToWholeCPUCores(q k8sResource.Quantity) (int64, error) {\n\tq.RoundUp(0)\n\tif n, ok := q.AsInt64(); ok {\n\t\treturn n, nil\n\t}\n\treturn 0, fmt.Errorf(\"Could not parse cores: %s\", q.String())\n}\n\n// getPodInboundPorts will return a string-formatted list of ports (in ascending\n// order) based on a PodSpec object. The function will check each container in\n// the pod and extract any defined ports. Additionally, it will also extract any\n// healthcheck target probes, provided the probe is an HTTP healthcheck\nfunc getPodInboundPorts(podSpec *corev1.PodSpec) string {\n\tports := make(map[int32]struct{})\n\tif podSpec != nil {\n\t\tfor _, container := range append(podSpec.InitContainers, podSpec.Containers...) {\n\t\t\tfor _, port := range container.Ports {\n\t\t\t\tports[port.ContainerPort] = struct{}{}\n\t\t\t}\n\n\t\t\tif readiness := container.ReadinessProbe; readiness != nil {\n\t\t\t\tif port, ok := getProbePort(readiness); ok {\n\t\t\t\t\tports[port] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif liveness := container.LivenessProbe; liveness != nil {\n\t\t\t\tif port, ok := getProbePort(liveness); ok {\n\t\t\t\t\tports[port] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tportList := make([]string, 0, len(ports))\n\tfor port := range ports {\n\t\tportList = append(portList, strconv.Itoa(int(port)))\n\t}\n\n\t// sort slice in ascending order\n\tsort.Strings(portList)\n\treturn strings.Join(portList, \",\")\n}\n\n// getProbePort takes the healthcheck probe spec of a container and returns the\n// target port if the probe is configured to do an HTTPGet. The function returns\n// the probe's target port and a success value (if successful)\nfunc getProbePort(probe *corev1.Probe) (int32, bool) {\n\tif probe.HTTPGet != nil {\n\t\t// HTTPGet probes use a named port, in this case, do not return it. A\n\t\t// named port must be declared in the container's own ports; if probe uses\n\t\t// a named port it is likely the port has been seen before.\n\t\tswitch probe.HTTPGet.Port.Type {\n\t\tcase intstr.Int:\n\t\t\treturn probe.HTTPGet.Port.IntVal, true\n\t\t}\n\t}\n\n\treturn 0, false\n}\n"
  },
  {
    "path": "pkg/inject/inject_fuzzer.go",
    "content": "package inject\n\nimport (\n\tl5dcharts \"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\n\tfuzz \"github.com/AdaLogics/go-fuzz-headers\"\n)\n\n// FuzzInject fuzzes Pod injection.\nfunc FuzzInject(data []byte) int {\n\tf := fuzz.NewConsumer(data)\n\tyamlBytes, err := f.GetBytes()\n\tif err != nil {\n\t\treturn 0\n\t}\n\n\tv := &l5dcharts.Values{}\n\terr = f.GenerateStruct(v)\n\tif err != nil {\n\t\treturn 0\n\t}\n\tconf := NewResourceConfig(v, OriginUnknown, \"\")\n\t_, _ = conf.ParseMetaAndYAML(yamlBytes)\n\tinjectProxy, err := f.GetBool()\n\tif err != nil {\n\t\treturn 0\n\t}\n\n\t_, _ = conf.GetPodPatch(injectProxy, GetOverriddenValues)\n\t_, _ = conf.CreateOpaquePortsPatch()\n\n\treport := &Report{}\n\terr = f.GenerateStruct(report)\n\tif err == nil {\n\t\t_, _ = conf.Uninject(report)\n\t}\n\treturn 1\n}\n"
  },
  {
    "path": "pkg/inject/inject_test.go",
    "content": "package inject\n\nimport (\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\tl5dcharts \"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tk8sResource \"k8s.io/apimachinery/pkg/api/resource\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nfunc TestGetOverriddenValues(t *testing.T) {\n\t// this test uses an annotated deployment and a expected Values object to verify\n\t// the GetOverriddenValues function.\n\n\tvar (\n\t\tproxyVersionOverride = \"proxy-version-override\"\n\t\tpullPolicy           = \"Always\"\n\t)\n\n\ttestConfig, err := l5dcharts.NewValues()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tvar testCases = []struct {\n\t\tid            string\n\t\tnsAnnotations map[string]string\n\t\tspec          appsv1.DeploymentSpec\n\t\texpected      func() *l5dcharts.Values\n\t}{\n\t\t{id: \"use overrides\",\n\t\t\tnsAnnotations: make(map[string]string),\n\t\t\tspec: appsv1.DeploymentSpec{\n\t\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\tk8s.ProxyImageAnnotation:                         \"cr.l5d.io/linkerd/proxy\",\n\t\t\t\t\t\t\tk8s.ProxyImagePullPolicyAnnotation:               pullPolicy,\n\t\t\t\t\t\t\tk8s.ProxyControlPortAnnotation:                   \"4000\",\n\t\t\t\t\t\t\tk8s.ProxyInboundPortAnnotation:                   \"5000\",\n\t\t\t\t\t\t\tk8s.ProxyAdminPortAnnotation:                     \"5001\",\n\t\t\t\t\t\t\tk8s.ProxyOutboundPortAnnotation:                  \"5002\",\n\t\t\t\t\t\t\tk8s.ProxyPodInboundPortsAnnotation:               \"1234,5678\",\n\t\t\t\t\t\t\tk8s.ProxyIgnoreInboundPortsAnnotation:            \"4222,6222\",\n\t\t\t\t\t\t\tk8s.ProxyIgnoreOutboundPortsAnnotation:           \"8079,8080\",\n\t\t\t\t\t\t\tk8s.ProxyCPURequestAnnotation:                    \"0.15\",\n\t\t\t\t\t\t\tk8s.ProxyMemoryRequestAnnotation:                 \"120\",\n\t\t\t\t\t\t\tk8s.ProxyEphemeralStorageRequestAnnotation:       \"10\",\n\t\t\t\t\t\t\tk8s.ProxyCPULimitAnnotation:                      \"1.5\",\n\t\t\t\t\t\t\tk8s.ProxyMemoryLimitAnnotation:                   \"256\",\n\t\t\t\t\t\t\tk8s.ProxyEphemeralStorageLimitAnnotation:         \"50\",\n\t\t\t\t\t\t\tk8s.ProxyUIDAnnotation:                           \"8500\",\n\t\t\t\t\t\t\tk8s.ProxyGIDAnnotation:                           \"8500\",\n\t\t\t\t\t\t\tk8s.ProxyLogLevelAnnotation:                      \"debug,linkerd=debug\",\n\t\t\t\t\t\t\tk8s.ProxyLogFormatAnnotation:                     \"json\",\n\t\t\t\t\t\t\tk8s.ProxyEnableExternalProfilesAnnotation:        \"false\",\n\t\t\t\t\t\t\tk8s.ProxyVersionOverrideAnnotation:               proxyVersionOverride,\n\t\t\t\t\t\t\tk8s.ProxyWaitBeforeExitSecondsAnnotation:         \"123\",\n\t\t\t\t\t\t\tk8s.ProxyRequireIdentityOnInboundPortsAnnotation: \"8888,9999\",\n\t\t\t\t\t\t\tk8s.ProxyOutboundConnectTimeout:                  \"6000ms\",\n\t\t\t\t\t\t\tk8s.ProxyInboundConnectTimeout:                   \"600ms\",\n\t\t\t\t\t\t\tk8s.ProxyOpaquePortsAnnotation:                   \"4320-4325,3306\",\n\t\t\t\t\t\t\tk8s.ProxyAwait:                                   \"enabled\",\n\t\t\t\t\t\t\tk8s.ProxySkipSubnetsAnnotation:                   \"172.17.0.0/16\",\n\t\t\t\t\t\t\tk8s.ProxyAccessLogAnnotation:                     \"apache\",\n\t\t\t\t\t\t\tk8s.ProxyShutdownGracePeriodAnnotation:           \"30s\",\n\t\t\t\t\t\t\tk8s.ProxyOutboundDiscoveryCacheUnusedTimeout:     \"50000ms\",\n\t\t\t\t\t\t\tk8s.ProxyInboundDiscoveryCacheUnusedTimeout:      \"900s\",\n\t\t\t\t\t\t\tk8s.ProxyDisableOutboundProtocolDetectTimeout:    \"true\",\n\t\t\t\t\t\t\tk8s.ProxyDisableInboundProtocolDetectTimeout:     \"true\",\n\t\t\t\t\t\t\tk8s.ProxyEnableNativeSidecarAnnotationBeta:       \"true\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tSpec: corev1.PodSpec{},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: func() *l5dcharts.Values {\n\t\t\t\tvalues, _ := l5dcharts.NewValues()\n\n\t\t\t\tvalues.Proxy.Runtime.Workers.Maximum = 2\n\t\t\t\tvalues.Proxy.Image.Name = \"cr.l5d.io/linkerd/proxy\"\n\t\t\t\tvalues.Proxy.Image.PullPolicy = pullPolicy\n\t\t\t\tvalues.Proxy.Image.Version = proxyVersionOverride\n\t\t\t\tvalues.Proxy.PodInboundPorts = \"1234,5678\"\n\t\t\t\tvalues.Proxy.Ports.Control = 4000\n\t\t\t\tvalues.Proxy.Ports.Inbound = 5000\n\t\t\t\tvalues.Proxy.Ports.Admin = 5001\n\t\t\t\tvalues.Proxy.Ports.Outbound = 5002\n\t\t\t\tvalues.Proxy.WaitBeforeExitSeconds = 123\n\t\t\t\tvalues.Proxy.LogLevel = \"debug,linkerd=debug\"\n\t\t\t\tvalues.Proxy.LogFormat = \"json\"\n\t\t\t\tvalues.Proxy.Resources = &l5dcharts.Resources{\n\t\t\t\t\tCPU: l5dcharts.Constraints{\n\t\t\t\t\t\tLimit:   \"1.5\",\n\t\t\t\t\t\tRequest: \"0.15\",\n\t\t\t\t\t},\n\t\t\t\t\tMemory: l5dcharts.Constraints{\n\t\t\t\t\t\tLimit:   \"256\",\n\t\t\t\t\t\tRequest: \"120\",\n\t\t\t\t\t},\n\t\t\t\t\tEphemeralStorage: l5dcharts.Constraints{\n\t\t\t\t\t\tLimit:   \"50\",\n\t\t\t\t\t\tRequest: \"10\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tvalues.Proxy.UID = 8500\n\t\t\t\tvalues.Proxy.GID = 8500\n\t\t\t\tvalues.ProxyInit.IgnoreInboundPorts = \"4222,6222\"\n\t\t\t\tvalues.ProxyInit.IgnoreOutboundPorts = \"8079,8080\"\n\t\t\t\tvalues.ProxyInit.SkipSubnets = \"172.17.0.0/16\"\n\t\t\t\tvalues.Proxy.RequireIdentityOnInboundPorts = \"8888,9999\"\n\t\t\t\tvalues.Proxy.OutboundConnectTimeout = \"6000ms\"\n\t\t\t\tvalues.Proxy.InboundConnectTimeout = \"600ms\"\n\t\t\t\tvalues.Proxy.OpaquePorts = \"4320-4325,3306\"\n\t\t\t\tvalues.Proxy.Await = true\n\t\t\t\tvalues.Proxy.AccessLog = \"apache\"\n\t\t\t\tvalues.Proxy.ShutdownGracePeriod = \"30000ms\"\n\t\t\t\tvalues.Proxy.OutboundDiscoveryCacheUnusedTimeout = \"50s\"\n\t\t\t\tvalues.Proxy.InboundDiscoveryCacheUnusedTimeout = \"900s\"\n\t\t\t\tvalues.Proxy.DisableOutboundProtocolDetectTimeout = true\n\t\t\t\tvalues.Proxy.DisableInboundProtocolDetectTimeout = true\n\t\t\t\tvalues.Proxy.NativeSidecar = true\n\t\t\t\treturn values\n\t\t\t},\n\t\t},\n\t\t{id: \"use defaults\",\n\t\t\tnsAnnotations: make(map[string]string),\n\t\t\tspec: appsv1.DeploymentSpec{\n\t\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{},\n\t\t\t\t\tSpec:       corev1.PodSpec{},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: func() *l5dcharts.Values {\n\t\t\t\tvalues, _ := l5dcharts.NewValues()\n\t\t\t\treturn values\n\t\t\t},\n\t\t},\n\t\t{id: \"use tracing annotations\",\n\t\t\tnsAnnotations: make(map[string]string),\n\t\t\tspec: appsv1.DeploymentSpec{\n\t\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\tk8s.TracingSemanticConventionPrefix + k8s.TracingServiceName: \"foo\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tSpec: corev1.PodSpec{},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: func() *l5dcharts.Values {\n\t\t\t\tvalues, _ := l5dcharts.NewValues()\n\n\t\t\t\tvalues.Proxy.Tracing.Labels[k8s.TracingServiceName] = \"foo\"\n\t\t\t\treturn values\n\t\t\t},\n\t\t},\n\t\t{id: \"use namespace overrides\",\n\t\t\tnsAnnotations: map[string]string{\n\t\t\t\tk8s.ProxyImageAnnotation:                      \"cr.l5d.io/linkerd/proxy\",\n\t\t\t\tk8s.ProxyImagePullPolicyAnnotation:            pullPolicy,\n\t\t\t\tk8s.ProxyControlPortAnnotation:                \"4000\",\n\t\t\t\tk8s.ProxyInboundPortAnnotation:                \"5000\",\n\t\t\t\tk8s.ProxyAdminPortAnnotation:                  \"5001\",\n\t\t\t\tk8s.ProxyOutboundPortAnnotation:               \"5002\",\n\t\t\t\tk8s.ProxyPodInboundPortsAnnotation:            \"1234,5678\",\n\t\t\t\tk8s.ProxyIgnoreInboundPortsAnnotation:         \"4222,6222\",\n\t\t\t\tk8s.ProxyIgnoreOutboundPortsAnnotation:        \"8079,8080\",\n\t\t\t\tk8s.ProxyCPURequestAnnotation:                 \"0.15\",\n\t\t\t\tk8s.ProxyMemoryRequestAnnotation:              \"120\",\n\t\t\t\tk8s.ProxyCPULimitAnnotation:                   \"1.5\",\n\t\t\t\tk8s.ProxyMemoryLimitAnnotation:                \"256\",\n\t\t\t\tk8s.ProxyUIDAnnotation:                        \"8500\",\n\t\t\t\tk8s.ProxyGIDAnnotation:                        \"8500\",\n\t\t\t\tk8s.ProxyLogLevelAnnotation:                   \"debug,linkerd=debug\",\n\t\t\t\tk8s.ProxyLogFormatAnnotation:                  \"json\",\n\t\t\t\tk8s.ProxyEnableExternalProfilesAnnotation:     \"false\",\n\t\t\t\tk8s.ProxyVersionOverrideAnnotation:            proxyVersionOverride,\n\t\t\t\tk8s.ProxyWaitBeforeExitSecondsAnnotation:      \"123\",\n\t\t\t\tk8s.ProxyOutboundConnectTimeout:               \"6000ms\",\n\t\t\t\tk8s.ProxyInboundConnectTimeout:                \"600ms\",\n\t\t\t\tk8s.ProxyOpaquePortsAnnotation:                \"4320-4325,3306\",\n\t\t\t\tk8s.ProxyAwait:                                \"enabled\",\n\t\t\t\tk8s.ProxyAccessLogAnnotation:                  \"apache\",\n\t\t\t\tk8s.ProxyInjectAnnotation:                     \"ingress\",\n\t\t\t\tk8s.ProxyOutboundDiscoveryCacheUnusedTimeout:  \"50s\",\n\t\t\t\tk8s.ProxyInboundDiscoveryCacheUnusedTimeout:   \"6000ms\",\n\t\t\t\tk8s.ProxyDisableOutboundProtocolDetectTimeout: \"true\",\n\t\t\t\tk8s.ProxyDisableInboundProtocolDetectTimeout:  \"false\",\n\t\t\t\tk8s.ProxyEnableNativeSidecarAnnotationBeta:    \"true\",\n\t\t\t},\n\t\t\tspec: appsv1.DeploymentSpec{\n\t\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\t\tSpec: corev1.PodSpec{},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: func() *l5dcharts.Values {\n\t\t\t\tvalues, _ := l5dcharts.NewValues()\n\n\t\t\t\tvalues.Proxy.Runtime.Workers.Maximum = 2\n\t\t\t\tvalues.Proxy.Image.Name = \"cr.l5d.io/linkerd/proxy\"\n\t\t\t\tvalues.Proxy.Image.PullPolicy = pullPolicy\n\t\t\t\tvalues.Proxy.Image.Version = proxyVersionOverride\n\t\t\t\tvalues.Proxy.PodInboundPorts = \"1234,5678\"\n\t\t\t\tvalues.Proxy.Ports.Control = 4000\n\t\t\t\tvalues.Proxy.Ports.Inbound = 5000\n\t\t\t\tvalues.Proxy.Ports.Admin = 5001\n\t\t\t\tvalues.Proxy.Ports.Outbound = 5002\n\t\t\t\tvalues.Proxy.WaitBeforeExitSeconds = 123\n\t\t\t\tvalues.Proxy.LogLevel = \"debug,linkerd=debug\"\n\t\t\t\tvalues.Proxy.LogFormat = \"json\"\n\t\t\t\tvalues.Proxy.Resources = &l5dcharts.Resources{\n\t\t\t\t\tCPU: l5dcharts.Constraints{\n\t\t\t\t\t\tLimit:   \"1.5\",\n\t\t\t\t\t\tRequest: \"0.15\",\n\t\t\t\t\t},\n\t\t\t\t\tMemory: l5dcharts.Constraints{\n\t\t\t\t\t\tLimit:   \"256\",\n\t\t\t\t\t\tRequest: \"120\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tvalues.Proxy.UID = 8500\n\t\t\t\tvalues.Proxy.GID = 8500\n\t\t\t\tvalues.ProxyInit.IgnoreInboundPorts = \"4222,6222\"\n\t\t\t\tvalues.ProxyInit.IgnoreOutboundPorts = \"8079,8080\"\n\t\t\t\tvalues.Proxy.OutboundConnectTimeout = \"6000ms\"\n\t\t\t\tvalues.Proxy.InboundConnectTimeout = \"600ms\"\n\t\t\t\tvalues.Proxy.OpaquePorts = \"4320-4325,3306\"\n\t\t\t\tvalues.Proxy.Await = true\n\t\t\t\tvalues.Proxy.AccessLog = \"apache\"\n\t\t\t\tvalues.Proxy.IsIngress = true\n\t\t\t\tvalues.Proxy.OutboundDiscoveryCacheUnusedTimeout = \"50s\"\n\t\t\t\tvalues.Proxy.InboundDiscoveryCacheUnusedTimeout = \"6s\"\n\t\t\t\tvalues.Proxy.DisableOutboundProtocolDetectTimeout = true\n\t\t\t\tvalues.Proxy.DisableInboundProtocolDetectTimeout = false\n\t\t\t\tvalues.Proxy.NativeSidecar = true\n\t\t\t\treturn values\n\t\t\t},\n\t\t},\n\t\t{id: \"use invalid duration for proxy timeouts\",\n\t\t\tnsAnnotations: map[string]string{\n\t\t\t\tk8s.ProxyOutboundConnectTimeout:               \"6000\",\n\t\t\t\tk8s.ProxyInboundConnectTimeout:                \"600\",\n\t\t\t\tk8s.ProxyOutboundDiscoveryCacheUnusedTimeout:  \"50\",\n\t\t\t\tk8s.ProxyInboundDiscoveryCacheUnusedTimeout:   \"5000\",\n\t\t\t\tk8s.ProxyDisableOutboundProtocolDetectTimeout: \"9000\",\n\t\t\t\tk8s.ProxyDisableInboundProtocolDetectTimeout:  \"9\",\n\t\t\t},\n\t\t\tspec: appsv1.DeploymentSpec{\n\t\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{},\n\t\t\t\t\tSpec:       corev1.PodSpec{},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: func() *l5dcharts.Values {\n\t\t\t\tvalues, _ := l5dcharts.NewValues()\n\t\t\t\treturn values\n\t\t\t},\n\t\t},\n\t\t{id: \"use valid duration for proxy timeouts\",\n\t\t\tnsAnnotations: map[string]string{\n\t\t\t\t// Validate we're converting time values into ms for the proxy to parse correctly.\n\t\t\t\tk8s.ProxyOutboundConnectTimeout:               \"6s5ms\",\n\t\t\t\tk8s.ProxyInboundConnectTimeout:                \"2s5ms\",\n\t\t\t\tk8s.ProxyOutboundDiscoveryCacheUnusedTimeout:  \"6s5000ms\",\n\t\t\t\tk8s.ProxyInboundDiscoveryCacheUnusedTimeout:   \"6s5000ms\",\n\t\t\t\tk8s.ProxyDisableOutboundProtocolDetectTimeout: \"false\",\n\t\t\t\tk8s.ProxyDisableInboundProtocolDetectTimeout:  \"true\",\n\t\t\t},\n\t\t\tspec: appsv1.DeploymentSpec{\n\t\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{},\n\t\t\t\t\tSpec:       corev1.PodSpec{},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: func() *l5dcharts.Values {\n\t\t\t\tvalues, _ := l5dcharts.NewValues()\n\t\t\t\tvalues.Proxy.OutboundConnectTimeout = \"6005ms\"\n\t\t\t\tvalues.Proxy.InboundConnectTimeout = \"2005ms\"\n\t\t\t\tvalues.Proxy.OutboundDiscoveryCacheUnusedTimeout = \"11s\"\n\t\t\t\tvalues.Proxy.InboundDiscoveryCacheUnusedTimeout = \"11s\"\n\t\t\t\tvalues.Proxy.DisableOutboundProtocolDetectTimeout = false\n\t\t\t\tvalues.Proxy.DisableInboundProtocolDetectTimeout = true\n\t\t\t\treturn values\n\t\t\t},\n\t\t},\n\t\t{id: \"use named port for opaque ports\",\n\t\t\tnsAnnotations: make(map[string]string),\n\t\t\tspec: appsv1.DeploymentSpec{\n\t\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\tk8s.ProxyOpaquePortsAnnotation: \"mysql\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tSpec: corev1.PodSpec{\n\t\t\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tPorts: []corev1.ContainerPort{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tName:          \"mysql\",\n\t\t\t\t\t\t\t\t\t\tContainerPort: 3306,\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\texpected: func() *l5dcharts.Values {\n\t\t\t\tvalues, _ := l5dcharts.NewValues()\n\t\t\t\tvalues.Proxy.OpaquePorts = \"3306\"\n\t\t\t\treturn values\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttestCase := tc\n\t\tt.Run(testCase.id, func(t *testing.T) {\n\t\t\tdata, err := yaml.Marshal(&appsv1.Deployment{Spec: testCase.spec})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tresourceConfig := NewResourceConfig(testConfig, OriginUnknown, \"linkerd\").\n\t\t\t\tWithKind(\"Deployment\").WithNsAnnotations(testCase.nsAnnotations)\n\t\t\tif err := resourceConfig.parse(data); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tAppendNamespaceAnnotations(resourceConfig.GetOverrideAnnotations(), resourceConfig.GetNsAnnotations(), resourceConfig.GetWorkloadAnnotations())\n\t\t\tactual, err := GetOverriddenValues(resourceConfig)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\texpected := testCase.expected()\n\t\t\tif diff := deep.Equal(actual, expected); diff != nil {\n\t\t\t\tt.Errorf(\"%+v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestApplyAnnotationOverridesMissingProxyTracing(t *testing.T) {\n\tvalues := &l5dcharts.Values{\n\t\tProxy: &l5dcharts.Proxy{\n\t\t\tImage:   &l5dcharts.Image{},\n\t\t\tPorts:   &l5dcharts.Ports{},\n\t\t\tMetrics: &l5dcharts.ProxyMetrics{},\n\t\t},\n\t\tProxyInit: &l5dcharts.ProxyInit{},\n\t}\n\n\tannotations := map[string]string{\n\t\tk8s.TracingSemanticConventionPrefix + \"http.method\": \"GET\",\n\t}\n\tlabels := map[string]string{\n\t\tk8s.TracingInstanceLabel: \"example\",\n\t}\n\n\tApplyAnnotationOverrides(values, annotations, labels, nil)\n\n\tif values.Proxy.Tracing != nil {\n\t\tt.Fatalf(\"expected tracing to remain nil, got %#v\", values.Proxy.Tracing)\n\t}\n}\n\nfunc TestApplyAnnotationOverridesInitializesTracingLabels(t *testing.T) {\n\tvalues := &l5dcharts.Values{\n\t\tProxy: &l5dcharts.Proxy{\n\t\t\tTracing: &l5dcharts.Tracing{},\n\t\t\tImage:   &l5dcharts.Image{},\n\t\t\tPorts:   &l5dcharts.Ports{},\n\t\t\tMetrics: &l5dcharts.ProxyMetrics{},\n\t\t},\n\t\tProxyInit: &l5dcharts.ProxyInit{},\n\t}\n\n\tannotations := map[string]string{\n\t\tk8s.TracingSemanticConventionPrefix + \"http.target\": \"/health\",\n\t}\n\tlabels := map[string]string{\n\t\tk8s.TracingNameLabel: \"workload\",\n\t}\n\n\tApplyAnnotationOverrides(values, annotations, labels, nil)\n\n\tif values.Proxy.Tracing == nil {\n\t\tt.Fatal(\"expected tracing to be initialized\")\n\t}\n\n\tif got := values.Proxy.Tracing.Labels[k8s.TracingServiceName]; got != \"workload\" {\n\t\tt.Fatalf(\"expected tracing service name to be set, got %q\", got)\n\t}\n\n\tif got := values.Proxy.Tracing.Labels[\"http.target\"]; got != \"/health\" {\n\t\tt.Fatalf(\"expected tracing semantic convention label to be set, got %q\", got)\n\t}\n}\n\nfunc TestWholeCPUCores(t *testing.T) {\n\tfor _, c := range []struct {\n\t\tv string\n\t\tn int\n\t}{\n\t\t{v: \"1\", n: 1},\n\t\t{v: \"1m\", n: 1},\n\t\t{v: \"1000m\", n: 1},\n\t\t{v: \"1001m\", n: 2},\n\t} {\n\t\tq, err := k8sResource.ParseQuantity(c.v)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tn, err := ToWholeCPUCores(q)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif n != int64(c.n) {\n\t\t\tt.Fatalf(\"Unexpected value: %v != %v\", n, c.n)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/inject/report.go",
    "content": "package inject\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tv1 \"k8s.io/api/core/v1\"\n)\n\nconst (\n\thostNetworkEnabled                   = \"host_network_enabled\"\n\tsidecarExists                        = \"sidecar_already_exists\"\n\tunsupportedResource                  = \"unsupported_resource\"\n\tinjectEnableAnnotationAbsent         = \"injection_enable_annotation_absent\"\n\tinjectDisableAnnotationPresent       = \"injection_disable_annotation_present\"\n\tannotationAtNamespace                = \"namespace\"\n\tannotationAtWorkload                 = \"workload\"\n\tinvalidInjectAnnotationWorkload      = \"invalid_inject_annotation_at_workload\"\n\tinvalidInjectAnnotationNamespace     = \"invalid_inject_annotation_at_ns\"\n\tdisabledAutomountServiceAccountToken = \"disabled_automount_service_account_token_account\"\n\tudpPortsEnabled                      = \"udp_ports_enabled\"\n)\n\nvar (\n\t// Reasons is a map of inject skip reasons with human readable sentences\n\tReasons = map[string]string{\n\t\thostNetworkEnabled:                   \"hostNetwork is enabled\",\n\t\tsidecarExists:                        \"pod has a sidecar injected already\",\n\t\tunsupportedResource:                  \"this resource kind is unsupported\",\n\t\tinjectEnableAnnotationAbsent:         fmt.Sprintf(\"neither the namespace nor the pod have the annotation \\\"%s:%s\\\"\", k8s.ProxyInjectAnnotation, k8s.ProxyInjectEnabled),\n\t\tinjectDisableAnnotationPresent:       fmt.Sprintf(\"pod has the annotation \\\"%s:%s\\\"\", k8s.ProxyInjectAnnotation, k8s.ProxyInjectDisabled),\n\t\tinvalidInjectAnnotationWorkload:      fmt.Sprintf(\"invalid value for annotation \\\"%s\\\" at workload\", k8s.ProxyInjectAnnotation),\n\t\tinvalidInjectAnnotationNamespace:     fmt.Sprintf(\"invalid value for annotation \\\"%s\\\" at namespace\", k8s.ProxyInjectAnnotation),\n\t\tdisabledAutomountServiceAccountToken: \"automountServiceAccountToken set to \\\"false\\\", with Values.identity.serviceAccountTokenProjection set to \\\"false\\\"\",\n\t\tudpPortsEnabled:                      \"UDP port(s) configured on pod spec\",\n\t}\n\n\t// Set of valid inject annotation values\n\tValidInjectAnnotationValues = map[string]struct{}{\n\t\tk8s.ProxyInjectEnabled:  {},\n\t\tk8s.ProxyInjectDisabled: {},\n\t\tk8s.ProxyInjectIngress:  {},\n\t}\n\n\t// Set of inject annotation values that indicate that injection is enabled\n\tInjectEnabledValues = map[string]struct{}{\n\t\tk8s.ProxyInjectEnabled: {},\n\t\tk8s.ProxyInjectIngress: {},\n\t}\n)\n\n// Report contains the Kind and Name for a given workload along with booleans\n// describing the result of the injection transformation\ntype Report struct {\n\tKind                         string\n\tName                         string\n\tHostNetwork                  bool\n\tSidecar                      bool\n\tUDP                          bool // true if any port in any container has `protocol: UDP`\n\tUnsupportedResource          bool // unsupported to inject\n\tInjectDisabled               bool\n\tInjectDisabledReason         string\n\tInjectAnnotationAt           string\n\tInjectAnnotationValue        string\n\tAnnotatable                  bool\n\tAnnotated                    bool\n\tAutomountServiceAccountToken bool\n\n\t// Uninjected consists of two boolean flags to indicate if a proxy and\n\t// proxy-init containers have been uninjected in this report\n\tUninjected struct {\n\t\t// Proxy is true if a proxy container has been uninjected\n\t\tProxy bool\n\n\t\t// ProxyInit is true if a proxy-init container has been uninjected\n\t\tProxyInit bool\n\t}\n}\n\n// newReport returns a new Report struct, initialized with the Kind and Name\n// from conf\nfunc newReport(conf *ResourceConfig) *Report {\n\tvar name string\n\tif conf.IsPod() {\n\t\tname = conf.pod.meta.Name\n\t\tif name == \"\" {\n\t\t\tname = conf.pod.meta.GenerateName\n\t\t}\n\t} else if m := conf.workload.Meta; m != nil {\n\t\tname = m.Name\n\t}\n\n\treport := &Report{\n\t\tKind:                         strings.ToLower(conf.workload.metaType.Kind),\n\t\tName:                         name,\n\t\tAutomountServiceAccountToken: true,\n\t}\n\n\tif conf.HasPodTemplate() {\n\t\treport.InjectDisabled, report.InjectDisabledReason, report.InjectAnnotationAt, report.InjectAnnotationValue = report.disabledByAnnotation(conf)\n\t\treport.HostNetwork = conf.pod.spec.HostNetwork\n\t\treport.Sidecar = healthcheck.HasExistingSidecars(conf.pod.spec)\n\t\treport.UDP = checkUDPPorts(conf.pod.spec)\n\t\tif conf.pod.spec.AutomountServiceAccountToken != nil &&\n\t\t\t(conf.values != nil && !conf.values.Identity.ServiceAccountTokenProjection) {\n\t\t\treport.AutomountServiceAccountToken = *conf.pod.spec.AutomountServiceAccountToken\n\t\t}\n\t\tif conf.origin == OriginWebhook {\n\t\t\tif vm := conf.serviceAccountVolumeMount(); vm == nil {\n\t\t\t\t// set to false only if it is not using the new linkerd-token volume projection\n\t\t\t\tif conf.values != nil && !conf.values.Identity.ServiceAccountTokenProjection {\n\t\t\t\t\treport.AutomountServiceAccountToken = false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\treport.UnsupportedResource = true\n\t}\n\n\tif conf.HasPodTemplate() || conf.IsService() || conf.IsNamespace() {\n\t\treport.Annotatable = true\n\t}\n\n\treturn report\n}\n\n// ResName returns a string \"Kind/Name\" for the workload referred in the report r\nfunc (r *Report) ResName() string {\n\treturn fmt.Sprintf(\"%s/%s\", r.Kind, r.Name)\n}\n\n// Injectable returns false if the report flags indicate that the workload is on a host network\n// or there is already a sidecar or the resource is not supported or inject is explicitly disabled.\n// If false, the second returned value describes the reason.\nfunc (r *Report) Injectable() (bool, []string) {\n\tvar reasons []string\n\tif r.HostNetwork {\n\t\treasons = append(reasons, hostNetworkEnabled)\n\t}\n\tif r.Sidecar {\n\t\treasons = append(reasons, sidecarExists)\n\t}\n\tif r.UnsupportedResource {\n\t\treasons = append(reasons, unsupportedResource)\n\t}\n\tif r.InjectDisabled {\n\t\treasons = append(reasons, r.InjectDisabledReason)\n\t}\n\n\tif !r.AutomountServiceAccountToken {\n\t\treasons = append(reasons, disabledAutomountServiceAccountToken)\n\t}\n\n\tif len(reasons) > 0 {\n\t\treturn false, reasons\n\t}\n\treturn true, nil\n}\n\n// IsAnnotatable returns true if the resource for a report can be annotated.\nfunc (r *Report) IsAnnotatable() bool {\n\treturn r.Annotatable\n}\n\nfunc checkUDPPorts(t *v1.PodSpec) bool {\n\t// Check for ports with `protocol: UDP`, which will not be routed by Linkerd\n\tfor _, container := range t.Containers {\n\t\tfor _, port := range container.Ports {\n\t\t\tif port.Protocol == v1.ProtocolUDP {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n// disabledByAnnotation checks the workload and namespace for the annotation\n// that disables injection. It returns if it is disabled, why it is disabled,\n// and the location where the annotation was present.\nfunc (r *Report) disabledByAnnotation(conf *ResourceConfig) (bool, string, string, string) {\n\t// truth table of the effects of the inject annotation:\n\t//\n\t// origin  | namespace | pod      | inject?  | return\n\t// ------- | --------- | -------- | -------- | ------\n\t// webhook | enabled   | enabled  | yes      | false\n\t// webhook | enabled   | \"\"       | yes      | false\n\t// webhook | enabled   | disabled | no       | true\n\t// webhook | disabled  | enabled  | yes      | false\n\t// webhook | \"\"        | enabled  | yes      | false\n\t// webhook | disabled  | disabled | no       | true\n\t// webhook | \"\"        | disabled | no       | true\n\t// webhook | disabled  | \"\"       | no       | true\n\t// webhook | \"\"        | \"\"       | no       | true\n\t// cli     | n/a       | enabled  | yes      | false\n\t// cli     | n/a       | \"\"       | yes      | false\n\t// cli     | n/a       | disabled | no       | true\n\n\tpodAnnotation := conf.pod.meta.Annotations[k8s.ProxyInjectAnnotation]\n\tnsAnnotation := conf.nsAnnotations[k8s.ProxyInjectAnnotation]\n\n\tif conf.origin == OriginCLI {\n\t\treturn podAnnotation == k8s.ProxyInjectDisabled, \"\", \"\", podAnnotation\n\t}\n\n\tif !isInjectAnnotationValid(nsAnnotation) {\n\t\treturn true, invalidInjectAnnotationNamespace, \"\", nsAnnotation\n\t}\n\n\tif !isInjectAnnotationValid(podAnnotation) {\n\t\treturn true, invalidInjectAnnotationWorkload, \"\", podAnnotation\n\t}\n\n\tif doesAnnotationEnableInject(nsAnnotation) {\n\t\tif podAnnotation == k8s.ProxyInjectDisabled {\n\t\t\treturn true, injectDisableAnnotationPresent, annotationAtWorkload, podAnnotation\n\t\t}\n\t\treturn false, \"\", annotationAtNamespace, nsAnnotation\n\t}\n\n\tif !doesAnnotationEnableInject(podAnnotation) {\n\t\treturn true, injectEnableAnnotationAbsent, \"\", podAnnotation\n\t}\n\n\treturn false, \"\", annotationAtWorkload, podAnnotation\n}\n\nfunc doesAnnotationEnableInject(annotation string) bool {\n\t_, isEnabledValue := InjectEnabledValues[annotation]\n\treturn isEnabledValue\n}\n\nfunc isInjectAnnotationValid(annotation string) bool {\n\t_, validValue := ValidInjectAnnotationValues[annotation]\n\tif annotation != \"\" && !validValue {\n\t\t// If the annotation is not empty, it must be a valid value\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// ThrowInjectError errors out `inject` when the report contains errors\n// related to automountServiceAccountToken, hostNetwork, existing sidecar,\n// or udp ports\n// See - https://github.com/linkerd/linkerd2/issues/4214\nfunc (r *Report) ThrowInjectError() []error {\n\n\terrs := []error{}\n\n\tif !r.AutomountServiceAccountToken {\n\t\terrs = append(errs, errors.New(Reasons[disabledAutomountServiceAccountToken]))\n\t}\n\n\tif r.HostNetwork {\n\t\terrs = append(errs, errors.New(Reasons[hostNetworkEnabled]))\n\t}\n\n\tif r.Sidecar {\n\t\terrs = append(errs, errors.New(Reasons[sidecarExists]))\n\t}\n\n\tif r.UDP {\n\t\terrs = append(errs, errors.New(Reasons[udpPortsEnabled]))\n\t}\n\n\treturn errs\n}\n"
  },
  {
    "path": "pkg/inject/report_test.go",
    "content": "package inject\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestInjectable(t *testing.T) {\n\tvar testCases = []struct {\n\t\tpodSpec             *corev1.PodSpec\n\t\tpodMeta             *metav1.ObjectMeta\n\t\tnsAnnotations       map[string]string\n\t\tunsupportedResource bool\n\t\tinjectable          bool\n\t\treasons             []string\n\t}{\n\t\t{\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tHostNetwork: false,\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tVolumeMounts: []corev1.VolumeMount{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMountPath: k8s.MountPathServiceAccount,\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\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,\n\t\t\t\t},\n\t\t\t},\n\t\t\tinjectable: true,\n\t\t},\n\t\t{\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tHostNetwork: true,\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tVolumeMounts: []corev1.VolumeMount{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMountPath: k8s.MountPathServiceAccount,\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\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,\n\t\t\t\t},\n\t\t\t},\n\t\t\tinjectable: false,\n\t\t\treasons:    []string{hostNetworkEnabled},\n\t\t},\n\t\t{\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\tImage: \"cr.l5d.io/linkerd/proxy:\",\n\t\t\t\t\t\tVolumeMounts: []corev1.VolumeMount{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMountPath: k8s.MountPathServiceAccount,\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\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,\n\t\t\t\t},\n\t\t\t},\n\t\t\tinjectable: false,\n\t\t\treasons:    []string{sidecarExists},\n\t\t},\n\t\t{\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tInitContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:  k8s.InitContainerName,\n\t\t\t\t\t\tImage: \"cr.l5d.io/linkerd/proxy-init:\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tVolumeMounts: []corev1.VolumeMount{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMountPath: k8s.MountPathServiceAccount,\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\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,\n\t\t\t\t},\n\t\t\t},\n\t\t\tinjectable: false,\n\t\t\treasons:    []string{sidecarExists},\n\t\t},\n\t\t{\n\t\t\tunsupportedResource: true,\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tVolumeMounts: []corev1.VolumeMount{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMountPath: k8s.MountPathServiceAccount,\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\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,\n\t\t\t\t},\n\t\t\t},\n\t\t\tinjectable: false,\n\t\t\treasons:    []string{unsupportedResource},\n\t\t},\n\t\t{\n\t\t\tunsupportedResource: true,\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tHostNetwork: true,\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tVolumeMounts: []corev1.VolumeMount{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMountPath: k8s.MountPathServiceAccount,\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\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,\n\t\t\t\t},\n\t\t\t},\n\n\t\t\tinjectable: false,\n\t\t\treasons:    []string{hostNetworkEnabled, unsupportedResource},\n\t\t},\n\t\t{\n\t\t\tnsAnnotations: map[string]string{\n\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,\n\t\t\t},\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tHostNetwork: true,\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tVolumeMounts: []corev1.VolumeMount{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMountPath: k8s.MountPathServiceAccount,\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\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,\n\t\t\t\t},\n\t\t\t},\n\n\t\t\tinjectable: false,\n\t\t\treasons:    []string{hostNetworkEnabled, injectDisableAnnotationPresent},\n\t\t},\n\t\t{\n\t\t\tnsAnnotations: map[string]string{\n\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,\n\t\t\t},\n\t\t\tunsupportedResource: true,\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tHostNetwork: true,\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tVolumeMounts: []corev1.VolumeMount{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMountPath: k8s.MountPathServiceAccount,\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\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,\n\t\t\t\t},\n\t\t\t},\n\n\t\t\tinjectable: false,\n\t\t\treasons:    []string{hostNetworkEnabled, unsupportedResource, injectDisableAnnotationPresent},\n\t\t},\n\t\t{\n\t\t\tunsupportedResource: true,\n\t\t\tpodSpec: &corev1.PodSpec{\n\t\t\t\tHostNetwork: true,\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tVolumeMounts: []corev1.VolumeMount{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMountPath: k8s.MountPathServiceAccount,\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\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\n\t\t\tinjectable: false,\n\t\t\treasons:    []string{hostNetworkEnabled, unsupportedResource, injectEnableAnnotationAbsent},\n\t\t},\n\t\t{\n\t\t\tpodSpec: &corev1.PodSpec{HostNetwork: true,\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\tImage: \"cr.l5d.io/linkerd/proxy:\",\n\t\t\t\t\t\tVolumeMounts: []corev1.VolumeMount{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMountPath: k8s.MountPathServiceAccount,\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\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\n\t\t\tinjectable: false,\n\t\t\treasons:    []string{hostNetworkEnabled, sidecarExists, injectEnableAnnotationAbsent},\n\t\t},\n\t}\n\n\tfor i, testCase := range testCases {\n\t\ttestCase := testCase\n\t\tt.Run(fmt.Sprintf(\"test case #%d\", i), func(t *testing.T) {\n\t\t\tresourceConfig := &ResourceConfig{}\n\t\t\tresourceConfig.WithNsAnnotations(testCase.nsAnnotations)\n\t\t\tresourceConfig.pod.spec = testCase.podSpec\n\t\t\tresourceConfig.origin = OriginWebhook\n\t\t\tresourceConfig.pod.meta = testCase.podMeta\n\n\t\t\treport := newReport(resourceConfig)\n\t\t\treport.UnsupportedResource = testCase.unsupportedResource\n\n\t\t\tactual, reasons := report.Injectable()\n\t\t\tif testCase.injectable != actual {\n\t\t\t\tt.Errorf(\"Expected %t. Actual %t\", testCase.injectable, actual)\n\t\t\t}\n\n\t\t\tif len(reasons) != len(testCase.reasons) {\n\t\t\t\tt.Errorf(\"Expected %d number of reasons. Actual %d\", len(testCase.reasons), len(reasons))\n\t\t\t}\n\n\t\t\tfor i := range reasons {\n\t\t\t\tif testCase.reasons[i] != reasons[i] {\n\t\t\t\t\tt.Errorf(\"Expected reason '%s'. Actual reason '%s'\", testCase.reasons[i], reasons[i])\n\t\t\t\t}\n\t\t\t}\n\n\t\t})\n\t}\n}\n\nfunc TestDisableByAnnotation(t *testing.T) {\n\tt.Run(\"webhook origin\", func(t *testing.T) {\n\t\tvar testCases = []struct {\n\t\t\tpodMeta       *metav1.ObjectMeta\n\t\t\tnsAnnotations map[string]string\n\t\t\texpected      bool\n\t\t}{\n\t\t\t{\n\t\t\t\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpected: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tnsAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,\n\t\t\t\t},\n\t\t\t\texpected: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tnsAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,\n\t\t\t\t},\n\t\t\t\texpected: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpodMeta: &metav1.ObjectMeta{},\n\t\t\t\tnsAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,\n\t\t\t\t},\n\t\t\t\texpected: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tnsAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,\n\t\t\t\t},\n\t\t\t\texpected: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tnsAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,\n\t\t\t\t},\n\t\t\t\texpected: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tnsAnnotations: map[string]string{},\n\t\t\t\texpected:      true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpodMeta: &metav1.ObjectMeta{},\n\t\t\t\tnsAnnotations: map[string]string{\n\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,\n\t\t\t\t},\n\t\t\t\texpected: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpodMeta:       &metav1.ObjectMeta{},\n\t\t\t\tnsAnnotations: map[string]string{},\n\t\t\t\texpected:      true,\n\t\t\t},\n\t\t}\n\n\t\tfor i, testCase := range testCases {\n\t\t\ttestCase := testCase\n\t\t\tt.Run(fmt.Sprintf(\"test case #%d\", i), func(t *testing.T) {\n\t\t\t\tresourceConfig := &ResourceConfig{origin: OriginWebhook}\n\t\t\t\tresourceConfig.WithNsAnnotations(testCase.nsAnnotations)\n\t\t\t\tresourceConfig.pod.meta = testCase.podMeta\n\t\t\t\tresourceConfig.pod.spec = &corev1.PodSpec{} // initialize empty spec to prevent test from failing\n\n\t\t\t\treport := newReport(resourceConfig)\n\t\t\t\tif actual, _, _, _ := report.disabledByAnnotation(resourceConfig); testCase.expected != actual {\n\t\t\t\t\tt.Errorf(\"Expected %t. Actual %t\", testCase.expected, actual)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n\n\tt.Run(\"CLI origin\", func(t *testing.T) {\n\t\tvar testCases = []struct {\n\t\t\tpodMeta  *metav1.ObjectMeta\n\t\t\texpected bool\n\t\t}{\n\t\t\t{\n\t\t\t\tpodMeta:  &metav1.ObjectMeta{},\n\t\t\t\texpected: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpected: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tpodMeta: &metav1.ObjectMeta{\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tk8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpected: true,\n\t\t\t},\n\t\t}\n\n\t\tfor i, testCase := range testCases {\n\t\t\ttestCase := testCase\n\t\t\tt.Run(fmt.Sprintf(\"test case #%d\", i), func(t *testing.T) {\n\t\t\t\tresourceConfig := &ResourceConfig{origin: OriginCLI}\n\t\t\t\tresourceConfig.pod.meta = testCase.podMeta\n\t\t\t\tresourceConfig.pod.spec = &corev1.PodSpec{} // initialize empty spec to prevent test from failing\n\n\t\t\t\treport := newReport(resourceConfig)\n\t\t\t\tif actual, _, _, _ := report.disabledByAnnotation(resourceConfig); testCase.expected != actual {\n\t\t\t\t\tt.Errorf(\"Expected %t. Actual %t\", testCase.expected, actual)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/inject/uninject.go",
    "content": "package inject\n\nimport (\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// Uninject removes from the workload in conf the init and proxy containers,\n// the TLS volumes and the extra annotations/labels that were added\nfunc (conf *ResourceConfig) Uninject(report *Report) ([]byte, error) {\n\tif conf.IsNamespace() || conf.IsService() {\n\t\tuninjectObjectMeta(conf.workload.Meta, report)\n\t\treturn conf.YamlMarshalObj()\n\t}\n\n\tif conf.pod.spec == nil {\n\t\treturn nil, nil\n\t}\n\n\tconf.uninjectPodSpec(report)\n\n\tif conf.workload.Meta != nil {\n\t\tuninjectObjectMeta(conf.workload.Meta, report)\n\t}\n\n\tuninjectObjectMeta(conf.pod.meta, report)\n\treturn conf.YamlMarshalObj()\n}\n\n// Given a PodSpec, update the PodSpec in place with the sidecar\n// and init-container uninjected\nfunc (conf *ResourceConfig) uninjectPodSpec(report *Report) {\n\tt := conf.pod.spec\n\tinitContainers := []v1.Container{}\n\tfor _, container := range t.InitContainers {\n\t\tswitch container.Name {\n\t\tcase k8s.InitContainerName:\n\t\t\treport.Uninjected.ProxyInit = true\n\t\tcase k8s.ProxyContainerName:\n\t\t\treport.Uninjected.Proxy = true\n\t\tdefault:\n\t\t\tinitContainers = append(initContainers, container)\n\t\t}\n\t}\n\tt.InitContainers = initContainers\n\n\tcontainers := []v1.Container{}\n\tfor _, container := range t.Containers {\n\t\tif container.Name != k8s.ProxyContainerName {\n\t\t\tcontainers = append(containers, container)\n\t\t} else {\n\t\t\treport.Uninjected.Proxy = true\n\t\t}\n\t}\n\tt.Containers = containers\n\n\tvolumes := []v1.Volume{}\n\tfor _, volume := range t.Volumes {\n\t\tif volume.Name != k8s.IdentityEndEntityVolumeName && volume.Name != k8s.InitXtablesLockVolumeMountName && volume.Name != k8s.LinkerdTokenVolumeMountName {\n\t\t\tvolumes = append(volumes, volume)\n\t\t}\n\t}\n\tt.Volumes = volumes\n}\n\nfunc uninjectObjectMeta(t *metav1.ObjectMeta, report *Report) {\n\t// We only uninject control plane components in the context\n\t// of doing an inject --manual. This is done as a way to update\n\t// something about the injection configuration - for example\n\t// adding a debug sidecar to the identity service.\n\t// With that in mind it is not really necessary to strip off\n\t// the linkerd.io/*  metadata from the pod during uninjection.\n\t// This is why we skip that part for control plane components.\n\t// Furthermore the latter will never have linkerd.io/inject as\n\t// they are always manually injected.\n\tif _, ok := t.Labels[k8s.ControllerComponentLabel]; !ok {\n\t\tnewAnnotations := make(map[string]string)\n\t\tfor key, val := range t.Annotations {\n\t\t\tif !strings.HasPrefix(key, k8s.Prefix) ||\n\t\t\t\t(key == k8s.ProxyInjectAnnotation && val == k8s.ProxyInjectDisabled) {\n\t\t\t\tnewAnnotations[key] = val\n\t\t\t} else {\n\t\t\t\treport.Uninjected.Proxy = true\n\t\t\t}\n\n\t\t}\n\t\tt.Annotations = newAnnotations\n\n\t\tlabels := make(map[string]string)\n\t\tfor key, val := range t.Labels {\n\t\t\tif !strings.HasPrefix(key, k8s.Prefix) {\n\t\t\t\tlabels[key] = val\n\t\t\t}\n\t\t}\n\t\tt.Labels = labels\n\t}\n}\n"
  },
  {
    "path": "pkg/issuercerts/issuercerts.go",
    "content": "package issuercerts\n\nimport (\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"k8s.io/client-go/kubernetes\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nconst keyMissingError = \"key %s containing the %s needs to exist in secret %s if --identity-external-issuer=%v\"\nconst expirationWarningThresholdInDays = 60\n\n// IssuerCertData holds the trust anchors cert data used by the CA\ntype IssuerCertData struct {\n\tTrustAnchors string\n\tIssuerCrt    string\n\tIssuerKey    string\n\tExpiry       *time.Time\n}\n\n// FetchIssuerData fetches the issuer data from the linkerd-identity-issuer secrets (used for linkerd.io/tls schemed secrets)\nfunc FetchIssuerData(ctx context.Context, api kubernetes.Interface, trustAnchors, controlPlaneNamespace string) (*IssuerCertData, error) {\n\tsecret, err := api.CoreV1().Secrets(controlPlaneNamespace).Get(ctx, k8s.IdentityIssuerSecretName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcrt, ok := secret.Data[k8s.IdentityIssuerCrtName]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(keyMissingError, k8s.IdentityIssuerCrtName, \"issuer certificate\", k8s.IdentityIssuerSecretName, false)\n\t}\n\n\tkey, ok := secret.Data[k8s.IdentityIssuerKeyName]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(keyMissingError, k8s.IdentityIssuerKeyName, \"issuer key\", k8s.IdentityIssuerSecretName, true)\n\t}\n\n\tcert, err := tls.DecodePEMCrt(string(crt))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not parse issuer certificate: %w\", err)\n\t}\n\n\treturn &IssuerCertData{trustAnchors, string(crt), string(key), &cert.Certificate.NotAfter}, nil\n}\n\n// FetchExternalIssuerData fetches the issuer data from the linkerd-identity-issuer secrets (used for kubernetes.io/tls schemed secrets)\nfunc FetchExternalIssuerData(ctx context.Context, api kubernetes.Interface, controlPlaneNamespace string) (*IssuerCertData, error) {\n\tsecret, err := api.CoreV1().Secrets(controlPlaneNamespace).Get(ctx, k8s.IdentityIssuerSecretName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tanchors, ok := secret.Data[k8s.IdentityIssuerTrustAnchorsNameExternal]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(keyMissingError, k8s.IdentityIssuerTrustAnchorsNameExternal, \"trust anchors\", k8s.IdentityIssuerSecretName, true)\n\t}\n\n\tcrt, ok := secret.Data[corev1.TLSCertKey]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(keyMissingError, corev1.TLSCertKey, \"issuer certificate\", k8s.IdentityIssuerSecretName, true)\n\t}\n\n\tkey, ok := secret.Data[corev1.TLSPrivateKeyKey]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(keyMissingError, corev1.TLSPrivateKeyKey, \"issuer key\", k8s.IdentityIssuerSecretName, true)\n\t}\n\n\tcert, err := tls.DecodePEMCrt(string(crt))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not parse issuer certificate: %w\", err)\n\t}\n\n\treturn &IssuerCertData{string(anchors), string(crt), string(key), &cert.Certificate.NotAfter}, nil\n}\n\n// LoadIssuerCrtAndKeyFromFiles loads the issuer certificate and key from files\nfunc LoadIssuerCrtAndKeyFromFiles(keyPEMFile, crtPEMFile string) (string, string, error) {\n\tkey, err := os.ReadFile(filepath.Clean(keyPEMFile))\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\tcrt, err := os.ReadFile(filepath.Clean(crtPEMFile))\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\treturn string(key), string(crt), nil\n}\n\n// LoadIssuerDataFromFiles loads the issuer data from file stored on disk\nfunc LoadIssuerDataFromFiles(keyPEMFile, crtPEMFile, trustPEMFile string) (*IssuerCertData, error) {\n\tkey, crt, err := LoadIssuerCrtAndKeyFromFiles(keyPEMFile, crtPEMFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tanchors, err := os.ReadFile(filepath.Clean(trustPEMFile))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &IssuerCertData{string(anchors), crt, key, nil}, nil\n}\n\n// CheckCertValidityPeriod ensures the certificate is valid time - wise\nfunc CheckCertValidityPeriod(cert *x509.Certificate) error {\n\tif cert.NotBefore.After(time.Now()) {\n\t\treturn fmt.Errorf(\"not valid before: %s\", cert.NotBefore.Format(time.RFC3339))\n\t}\n\n\tif cert.NotAfter.Before(time.Now()) {\n\t\treturn fmt.Errorf(\"not valid anymore. Expired on %s\", cert.NotAfter.Format(time.RFC3339))\n\t}\n\treturn nil\n}\n\n// CheckExpiringSoon returns an error if a certificate is expiring soon\nfunc CheckExpiringSoon(cert *x509.Certificate) error {\n\tif time.Now().AddDate(0, 0, expirationWarningThresholdInDays).After(cert.NotAfter) {\n\t\treturn fmt.Errorf(\"will expire on %s\", cert.NotAfter.Format(time.RFC3339))\n\t}\n\treturn nil\n}\n\n// CheckIssuerCertAlgoRequirements ensures the certificate respects with the constraints\n// we have posed on the public key and signature algorithms. Issuer certificates can only\n// be signed by an ECDSA certificate.\nfunc CheckIssuerCertAlgoRequirements(cert *x509.Certificate) error {\n\tif cert.PublicKeyAlgorithm == x509.ECDSA {\n\t\terr := checkECDSACertRequirements(cert)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\treturn fmt.Errorf(\"issuer certificate must use ECDSA for public key algorithm, instead %s was used\", cert.PublicKeyAlgorithm)\n\t}\n\n\treturn nil\n}\n\n// CheckTrustAnchorAlgoRequirements ensures the certificate respects with the constraints\n// we have posed on the public key and signature algorithms. Trust anchors can be signed by\n// an ECDSA or RSA certificate.\nfunc CheckTrustAnchorAlgoRequirements(cert *x509.Certificate) error {\n\tif cert.PublicKeyAlgorithm == x509.ECDSA {\n\t\terr := checkECDSACertRequirements(cert)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else if cert.PublicKeyAlgorithm == x509.RSA {\n\t\terr := checkRSACertRequirements(cert)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\treturn fmt.Errorf(\"trust anchor must use ECDSA or RSA for public key algorithm, instead %s was used\", cert.PublicKeyAlgorithm)\n\t}\n\n\treturn nil\n}\n\nfunc checkECDSACertRequirements(cert *x509.Certificate) error {\n\tk, ok := cert.PublicKey.(*ecdsa.PublicKey)\n\tif !ok {\n\t\treturn fmt.Errorf(\"expected ecdsa.PublicKey but got something %v\", cert.PublicKey)\n\t}\n\tif k.Params().BitSize != 256 {\n\t\treturn fmt.Errorf(\"must use P-256 curve for public key, instead P-%d was used\", k.Params().BitSize)\n\t}\n\tif cert.SignatureAlgorithm != x509.ECDSAWithSHA256 &&\n\t\tcert.SignatureAlgorithm != x509.SHA256WithRSA {\n\t\treturn fmt.Errorf(\"must be signed by an ECDSA P-256 key, instead %s was used\", cert.SignatureAlgorithm)\n\t}\n\n\treturn nil\n}\n\nfunc checkRSACertRequirements(cert *x509.Certificate) error {\n\tk, ok := cert.PublicKey.(*rsa.PublicKey)\n\tif !ok {\n\t\treturn fmt.Errorf(\"expected rsa.PublicKey but got something %v\", cert.PublicKey)\n\t}\n\tif k.N.BitLen() != 2048 && k.N.BitLen() != 4096 {\n\t\treturn fmt.Errorf(\"RSA must use at least 2084 bit public key, instead %d bit public key was used\", k.N.BitLen())\n\t}\n\tif cert.SignatureAlgorithm != x509.SHA256WithRSA {\n\t\treturn fmt.Errorf(\"must be signed by an RSA 2048/4096 bit key, instead %s was used\", cert.SignatureAlgorithm)\n\t}\n\n\treturn nil\n}\n\n// VerifyAndBuildCreds builds and validates the creds out of the data in IssuerCertData\nfunc (ic *IssuerCertData) VerifyAndBuildCreds() (*tls.Cred, error) {\n\tcreds, err := tls.ValidateAndCreateCreds(ic.IssuerCrt, ic.IssuerKey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read CA: %w\", err)\n\t}\n\n\t// we check the time validity of the issuer cert\n\tif err := CheckCertValidityPeriod(creds.Certificate); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// we check the algo requirements of the issuer cert\n\tif err := CheckIssuerCertAlgoRequirements(creds.Certificate); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !creds.Certificate.IsCA {\n\t\treturn nil, fmt.Errorf(\"issuer cert is not a CA\")\n\t}\n\n\tanchors, err := tls.DecodePEMCertPool(ic.TrustAnchors)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := creds.Verify(anchors, \"\", time.Time{}); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn creds, nil\n}\n"
  },
  {
    "path": "pkg/k8s/api.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tcrdclient \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\t\"github.com/linkerd/linkerd2/pkg/prometheus\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapiextensionsclient \"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/version\"\n\t\"k8s.io/client-go/dynamic\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n\tapiregistration \"k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset\"\n\n\t// Load all the auth plugins for the cloud providers.\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n)\n\nvar minAPIVersion = [3]int{1, 23, 0}\n\n// KubernetesAPI provides a client for accessing a Kubernetes cluster.\n// TODO: support ServiceProfile ClientSet. A prerequisite is moving the\n// ServiceProfile client code from `./controller` to `./pkg` (#2751). This will\n// also allow making `NewFakeClientSets` private, as KubernetesAPI will support\n// all relevant k8s resources.\ntype KubernetesAPI struct {\n\t*rest.Config\n\tkubernetes.Interface\n\tApiextensions   apiextensionsclient.Interface // for CRDs\n\tApiregistration apiregistration.Interface     // for access to APIService\n\tDynamicClient   dynamic.Interface\n\tL5dCrdClient    crdclient.Interface\n}\n\n// NewAPI validates a Kubernetes config and returns a client for accessing the\n// configured cluster.\nfunc NewAPI(configPath, kubeContext string, impersonate string, impersonateGroup []string, timeout time.Duration) (*KubernetesAPI, error) {\n\tconfig, err := GetConfig(configPath, kubeContext)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error configuring Kubernetes API client: %w\", err)\n\t}\n\treturn NewAPIForConfig(config, impersonate, impersonateGroup, timeout, 0, 0)\n}\n\n// NewAPIForConfig uses a Kubernetes config to construct a client for accessing\n// the configured cluster\nfunc NewAPIForConfig(\n\tconfig *rest.Config,\n\timpersonate string,\n\timpersonateGroup []string,\n\ttimeout time.Duration,\n\tqps float32,\n\tburst int,\n) (*KubernetesAPI, error) {\n\n\t// k8s' client-go doesn't support injecting context\n\t// https://github.com/kubernetes/kubernetes/issues/46503\n\t// but we can set the timeout manually\n\tconfig.Timeout = timeout\n\tif qps > 0 && burst > 0 {\n\t\tconfig.QPS = qps\n\t\tconfig.Burst = burst\n\t\tprometheus.SetClientQPS(\"k8s\", config.QPS)\n\t\tprometheus.SetClientBurst(\"k8s\", config.Burst)\n\t} else {\n\t\tprometheus.SetClientQPS(\"k8s\", rest.DefaultQPS)\n\t\tprometheus.SetClientBurst(\"k8s\", rest.DefaultBurst)\n\t}\n\twt, err := prometheus.ClientWithTelemetry(\"k8s\", config.WrapTransport)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error adding telemetry to client: %w\", err)\n\t}\n\tconfig.WrapTransport = wt\n\n\tif impersonate != \"\" {\n\t\tconfig.Impersonate = rest.ImpersonationConfig{\n\t\t\tUserName: impersonate,\n\t\t\tGroups:   impersonateGroup,\n\t\t}\n\t}\n\n\tclientset, err := kubernetes.NewForConfig(config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error configuring Kubernetes API clientset: %w\", err)\n\t}\n\tapiextensions, err := apiextensionsclient.NewForConfig(config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error configuring Kubernetes API Extensions clientset: %w\", err)\n\t}\n\taggregatorClient, err := apiregistration.NewForConfig(config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error configuring Kubernetes API server aggregator: %w\", err)\n\t}\n\tdynamicClient, err := dynamic.NewForConfig(config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error configuring Kubernetes Dynamic Client: %w\", err)\n\t}\n\n\tl5dCrdClient, err := crdclient.NewForConfig(config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error configuring Linkerd CRD clientset: %w\", err)\n\t}\n\n\treturn &KubernetesAPI{\n\t\tConfig:          config,\n\t\tInterface:       clientset,\n\t\tApiextensions:   apiextensions,\n\t\tApiregistration: aggregatorClient,\n\t\tDynamicClient:   dynamicClient,\n\t\tL5dCrdClient:    l5dCrdClient,\n\t}, nil\n}\n\n// NewClient returns an http.Client configured with a Transport to connect to\n// the Kubernetes cluster.\nfunc (kubeAPI *KubernetesAPI) NewClient() (*http.Client, error) {\n\tsecureTransport, err := rest.TransportFor(kubeAPI.Config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error instantiating Kubernetes API client: %w\", err)\n\t}\n\n\treturn &http.Client{\n\t\tTransport: secureTransport,\n\t}, nil\n}\n\n// GetVersionInfo returns version.Info for the Kubernetes cluster.\nfunc (kubeAPI *KubernetesAPI) GetVersionInfo() (*version.Info, error) {\n\treturn kubeAPI.Discovery().ServerVersion()\n}\n\n// CheckVersion validates whether the configured Kubernetes cluster's version is\n// running a minimum Kubernetes API version.\nfunc (kubeAPI *KubernetesAPI) CheckVersion(versionInfo *version.Info) error {\n\tapiVersion, err := getK8sVersion(versionInfo.String())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !isCompatibleVersion(minAPIVersion, apiVersion) {\n\t\treturn fmt.Errorf(\"Kubernetes is on version [%d.%d.%d], but version [%d.%d.%d] or more recent is required\",\n\t\t\tapiVersion[0], apiVersion[1], apiVersion[2],\n\t\t\tminAPIVersion[0], minAPIVersion[1], minAPIVersion[2])\n\t}\n\n\treturn nil\n}\n\n// NamespaceExists validates whether a given namespace exists.\nfunc (kubeAPI *KubernetesAPI) NamespaceExists(ctx context.Context, namespace string) (bool, error) {\n\tns, err := kubeAPI.GetNamespace(ctx, namespace)\n\tif kerrors.IsNotFound(err) {\n\t\treturn false, nil\n\t}\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treturn ns != nil, nil\n}\n\n// GetNamespace returns the namespace with a given name, if one exists.\nfunc (kubeAPI *KubernetesAPI) GetNamespace(ctx context.Context, namespace string) (*corev1.Namespace, error) {\n\treturn kubeAPI.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{})\n}\n\n// GetNodes returns all the nodes in a cluster.\nfunc (kubeAPI *KubernetesAPI) GetNodes(ctx context.Context) ([]corev1.Node, error) {\n\tnodes, err := kubeAPI.CoreV1().Nodes().List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn nodes.Items, nil\n}\n\n// GetPodsByNamespace returns all pods in a given namespace\nfunc (kubeAPI *KubernetesAPI) GetPodsByNamespace(ctx context.Context, namespace string) ([]corev1.Pod, error) {\n\tpodList, err := kubeAPI.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn podList.Items, nil\n}\n\n// GetReplicaSets returns all replicasets in a given namespace\nfunc (kubeAPI *KubernetesAPI) GetReplicaSets(ctx context.Context, namespace string) ([]appsv1.ReplicaSet, error) {\n\treplicaSetList, err := kubeAPI.AppsV1().ReplicaSets(namespace).List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn replicaSetList.Items, nil\n}\n\n// GetAllNamespacesWithExtensionLabel gets all namespaces with the linkerd.io/extension label key\nfunc (kubeAPI *KubernetesAPI) GetAllNamespacesWithExtensionLabel(ctx context.Context) ([]corev1.Namespace, error) {\n\tnamespaces, err := kubeAPI.CoreV1().Namespaces().List(ctx, metav1.ListOptions{LabelSelector: LinkerdExtensionLabel})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn namespaces.Items, nil\n}\n\n// GetNamespaceWithExtensionLabel gets the namespace with the LinkerdExtensionLabel label value of `value`\nfunc (kubeAPI *KubernetesAPI) GetNamespaceWithExtensionLabel(ctx context.Context, value string) (*corev1.Namespace, error) {\n\tnamespaces, err := kubeAPI.GetAllNamespacesWithExtensionLabel(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, ns := range namespaces {\n\t\tif ns.Labels[LinkerdExtensionLabel] == value {\n\t\t\treturn &ns, err\n\t\t}\n\t}\n\terrNotFound := kerrors.NewNotFound(corev1.Resource(\"namespace\"), value)\n\terrNotFound.ErrStatus.Message = fmt.Sprintf(\"namespace with label \\\"%s: %s\\\" not found\", LinkerdExtensionLabel, value)\n\treturn nil, errNotFound\n}\n\n// GetPodStatus receives a pod and returns the pod status, based on `kubectl` logic.\n// This logic is imported and adapted from the github.com/kubernetes/kubernetes project:\n// https://github.com/kubernetes/kubernetes/blob/v1.31.0-alpha.0/pkg/printers/internalversion/printers.go#L860\nfunc GetPodStatus(pod corev1.Pod) string {\n\treason := string(pod.Status.Phase)\n\tif pod.Status.Reason != \"\" {\n\t\treason = pod.Status.Reason\n\t}\n\n\tinitContainers := make(map[string]*corev1.Container)\n\tfor i := range pod.Spec.InitContainers {\n\t\tinitContainers[pod.Spec.InitContainers[i].Name] = &pod.Spec.InitContainers[i]\n\t}\n\n\tinitializing := false\n\tfor i := range pod.Status.InitContainerStatuses {\n\t\tcontainer := pod.Status.InitContainerStatuses[i]\n\t\tswitch {\n\t\tcase container.State.Terminated != nil && container.State.Terminated.ExitCode == 0 && container.State.Terminated.Signal == 0:\n\t\t\tcontinue\n\t\tcase isRestartableInitContainer(initContainers[container.Name]) &&\n\t\t\tcontainer.Started != nil && *container.Started && container.Ready:\n\t\t\tcontinue\n\t\tcase container.State.Terminated != nil:\n\t\t\t// initialization is failed\n\t\t\tif container.State.Terminated.Reason == \"\" {\n\t\t\t\tif container.State.Terminated.Signal != 0 {\n\t\t\t\t\treason = fmt.Sprintf(\"Init:Signal:%d\", container.State.Terminated.Signal)\n\t\t\t\t} else {\n\t\t\t\t\treason = fmt.Sprintf(\"Init:ExitCode:%d\", container.State.Terminated.ExitCode)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treason = \"Init:\" + container.State.Terminated.Reason\n\t\t\t}\n\t\t\tinitializing = true\n\t\tcase container.State.Waiting != nil && len(container.State.Waiting.Reason) > 0 && container.State.Waiting.Reason != \"PodInitializing\":\n\t\t\treason = \"Init:\" + container.State.Waiting.Reason\n\t\t\tinitializing = true\n\t\tdefault:\n\t\t\treason = fmt.Sprintf(\"Init:%d/%d\", i, len(pod.Spec.InitContainers))\n\t\t\tinitializing = true\n\t\t}\n\t\tbreak\n\t}\n\tif !initializing {\n\t\thasRunning := false\n\t\tfor i := len(pod.Status.ContainerStatuses) - 1; i >= 0; i-- {\n\t\t\tcontainer := pod.Status.ContainerStatuses[i]\n\n\t\t\tif container.State.Waiting != nil && container.State.Waiting.Reason != \"\" {\n\t\t\t\treason = container.State.Waiting.Reason\n\t\t\t} else if container.State.Terminated != nil && container.State.Terminated.Reason != \"\" {\n\t\t\t\treason = container.State.Terminated.Reason\n\t\t\t} else if container.State.Terminated != nil && container.State.Terminated.Reason == \"\" {\n\t\t\t\tif container.State.Terminated.Signal != 0 {\n\t\t\t\t\treason = fmt.Sprintf(\"Signal:%d\", container.State.Terminated.Signal)\n\t\t\t\t} else {\n\t\t\t\t\treason = fmt.Sprintf(\"ExitCode:%d\", container.State.Terminated.ExitCode)\n\t\t\t\t}\n\t\t\t} else if container.Ready && container.State.Running != nil {\n\t\t\t\thasRunning = true\n\t\t\t}\n\t\t}\n\n\t\t// change pod status back to \"Running\" if there is at least one container still reporting as \"Running\" status\n\t\tif reason == \"Completed\" && hasRunning {\n\t\t\treason = \"Running\"\n\t\t}\n\t}\n\n\treturn reason\n}\n\n// Borrowed from\n// https://github.com/kubernetes/kubernetes/blob/v1.31.0-alpha.0/pkg/printers/internalversion/printers.go#L3209\nfunc isRestartableInitContainer(initContainer *corev1.Container) bool {\n\tif initContainer.RestartPolicy == nil {\n\t\treturn false\n\t}\n\n\treturn *initContainer.RestartPolicy == corev1.ContainerRestartPolicyAlways\n}\n\n// GetProxyReady returns true if the pod contains a proxy that is ready\nfunc GetProxyReady(pod corev1.Pod) bool {\n\tstatuses := append(pod.Status.InitContainerStatuses, pod.Status.ContainerStatuses...)\n\tfor _, container := range statuses {\n\t\tif container.Name == ProxyContainerName {\n\t\t\treturn container.Ready\n\t\t}\n\t}\n\treturn false\n}\n\n// GetProxyVersion returns the container proxy's version, if any\nfunc GetProxyVersion(pod corev1.Pod) string {\n\tcontainers := append(pod.Spec.InitContainers, pod.Spec.Containers...)\n\tfor _, container := range containers {\n\t\tif container.Name == ProxyContainerName {\n\t\t\tif strings.Contains(container.Image, \"@\") {\n\t\t\t\t// Proxy container image is specified with digest instead of\n\t\t\t\t// tag. We are unable to determine version.\n\t\t\t\treturn \"\"\n\t\t\t}\n\t\t\tparts := strings.Split(container.Image, \":\")\n\t\t\treturn parts[len(parts)-1]\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// GetPodsFor takes a resource string, queries the Kubernetes API, and returns a\n// list of pods belonging to that resource.\nfunc GetPodsFor(ctx context.Context, clientset kubernetes.Interface, namespace string, resource string) ([]corev1.Pod, error) {\n\telems := strings.Split(resource, \"/\")\n\n\tif len(elems) == 1 {\n\t\treturn nil, errors.New(\"no resource name provided\")\n\t}\n\n\tif len(elems) != 2 {\n\t\treturn nil, fmt.Errorf(\"invalid resource string: %s\", resource)\n\t}\n\n\ttyp, err := CanonicalResourceNameFromFriendlyName(elems[0])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tname := elems[1]\n\n\t// special case if a single pod was specified\n\tif typ == Pod {\n\t\tpod, err := clientset.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []corev1.Pod{*pod}, nil\n\t}\n\n\tvar matchLabels map[string]string\n\tvar ownerUID types.UID\n\tswitch typ {\n\tcase CronJob:\n\t\tjobs, err := clientset.BatchV1().Jobs(namespace).List(ctx, metav1.ListOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar pods []corev1.Pod\n\t\tfor _, job := range jobs.Items {\n\t\t\tif isOwner(job.GetUID(), job.GetOwnerReferences()) {\n\t\t\t\tjobPods, err := GetPodsFor(ctx, clientset, namespace, fmt.Sprintf(\"%s/%s\", Job, job.GetName()))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tpods = append(pods, jobPods...)\n\t\t\t}\n\t\t}\n\t\treturn pods, nil\n\n\tcase DaemonSet:\n\t\tds, err := clientset.AppsV1().DaemonSets(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmatchLabels = ds.Spec.Selector.MatchLabels\n\t\townerUID = ds.GetUID()\n\n\tcase Deployment:\n\t\tdeployment, err := clientset.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmatchLabels = deployment.Spec.Selector.MatchLabels\n\t\townerUID = deployment.GetUID()\n\n\t\treplicaSets, err := clientset.AppsV1().ReplicaSets(namespace).List(\n\t\t\tctx,\n\t\t\tmetav1.ListOptions{\n\t\t\t\tLabelSelector: labels.Set(matchLabels).AsSelector().String(),\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar pods []corev1.Pod\n\t\tfor _, rs := range replicaSets.Items {\n\t\t\tif isOwner(ownerUID, rs.GetOwnerReferences()) {\n\t\t\t\tpodsRS, err := GetPodsFor(ctx, clientset, namespace, fmt.Sprintf(\"%s/%s\", ReplicaSet, rs.GetName()))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tpods = append(pods, podsRS...)\n\t\t\t}\n\t\t}\n\t\treturn pods, nil\n\n\tcase Job:\n\t\tjob, err := clientset.BatchV1().Jobs(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmatchLabels = job.Spec.Selector.MatchLabels\n\t\townerUID = job.GetUID()\n\n\tcase ReplicaSet:\n\t\trs, err := clientset.AppsV1().ReplicaSets(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmatchLabels = rs.Spec.Selector.MatchLabels\n\t\townerUID = rs.GetUID()\n\n\tcase ReplicationController:\n\t\trc, err := clientset.CoreV1().ReplicationControllers(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmatchLabels = rc.Spec.Selector\n\t\townerUID = rc.GetUID()\n\n\tcase StatefulSet:\n\t\tss, err := clientset.AppsV1().StatefulSets(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmatchLabels = ss.Spec.Selector.MatchLabels\n\t\townerUID = ss.GetUID()\n\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported resource type: %s\", name)\n\t}\n\n\tpodList, err := clientset.\n\t\tCoreV1().\n\t\tPods(namespace).\n\t\tList(\n\t\t\tctx,\n\t\t\tmetav1.ListOptions{\n\t\t\t\tLabelSelector: labels.Set(matchLabels).AsSelector().String(),\n\t\t\t},\n\t\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif ownerUID == \"\" {\n\t\treturn podList.Items, nil\n\t}\n\n\tpods := []corev1.Pod{}\n\tfor _, pod := range podList.Items {\n\t\tif isOwner(ownerUID, pod.GetOwnerReferences()) {\n\t\t\tpods = append(pods, pod)\n\t\t}\n\t}\n\n\treturn pods, nil\n}\n\nfunc isOwner(u types.UID, ownerRefs []metav1.OwnerReference) bool {\n\tfor _, or := range ownerRefs {\n\t\tif u == or.UID {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/k8s/api_test.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\nfunc TestGetPodStatus(t *testing.T) {\n\tscenarios := []struct {\n\t\tdesc     string\n\t\tpod      string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tdesc:     \"Pod is running\",\n\t\t\texpected: \"Running\",\n\t\t\tpod: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`,\n\t\t},\n\t\t{\n\t\t\tdesc:     \"Pod with proxy native sidecar is running\",\n\t\t\texpected: \"Running\",\n\t\t\tpod: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nspec:\n    initContainers:\n    - name: linkerd-proxy\n      restartPolicy: Always\nstatus:\n  phase: Running\n  initContainerStatuses:\n  - name: linkerd-proxy\n    ready: true\n    started: true\n`,\n\t\t},\n\t\t{\n\t\t\tdesc:     \"Pod's reason is filled\",\n\t\t\texpected: \"podReason\",\n\t\t\tpod: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Pending\n  reason: podReason\n`,\n\t\t},\n\t\t{\n\t\t\tdesc:     \"Pod waiting is filled\",\n\t\t\texpected: \"CrashLoopBackOff\",\n\t\t\tpod: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Pending\n  reason: podReason\n  containerStatuses:\n  - state:\n      waiting:\n        reason: CrashLoopBackOff\n`,\n\t\t},\n\t\t{\n\t\t\tdesc:     \"Pod is terminated\",\n\t\t\texpected: \"podTerminated\",\n\t\t\tpod: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Pending\n  reason: podReason\n  containerStatuses:\n  - state:\n      terminated:\n        reason: podTerminated\n        exitCode: 2\n`,\n\t\t},\n\t\t{\n\t\t\tdesc:     \"Pod terminated with signal\",\n\t\t\texpected: \"Signal:9\",\n\t\t\tpod: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Pending\n  reason: podReason\n  containerStatuses:\n  - state:\n      terminated:\n        signal: 9\n`,\n\t\t},\n\t\t{\n\t\t\tdesc:     \"Pod terminated with exti code\",\n\t\t\texpected: \"ExitCode:2\",\n\t\t\tpod: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Pending\n  reason: podReason\n  containerStatuses:\n  - state:\n      terminated:\n        exitCode: 2\n`,\n\t\t},\n\t\t{\n\t\t\tdesc:     \"Pod has a running container\",\n\t\t\texpected: \"Running\",\n\t\t\tpod: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n  reason: Completed\n  containerStatuses:\n  - ready: true\n    state:\n      running:\n        startedAt: 1995-02-10T00:42:42Z\n`,\n\t\t},\n\t\t{\n\t\t\tdesc:     \"Pod init container terminated with exit code 0\",\n\t\t\texpected: \"Running\",\n\t\t\tpod: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nspec:\n    initContainers:\n    - name: foo\nstatus:\n  phase: Running\n  containerStatuses:\n  - state:\n      running:\n        startedAt: 1995-02-10T00:42:42Z\n  initContainerStatuses:\n  - name: foo\n    state:\n      terminated:\n        exitCode: 0\n        reason: Completed\n`,\n\t\t},\n\t\t{\n\t\t\tdesc:     \"Pod init container terminated with exit code 2\",\n\t\t\texpected: \"Init:ExitCode:2\",\n\t\t\tpod: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nspec:\n    initContainers:\n    - name: foo\n    containers:\n    - name: bar\nstatus:\n  phase: Running\n  containerStatuses:\n  - name: bar\n    state:\n      running:\n        startedAt: 1995-02-10T00:42:42Z\n  initContainerStatuses:\n  - name: foo\n    state:\n      terminated:\n        exitCode: 2\n`,\n\t\t},\n\t\t{\n\t\t\tdesc:     \"Pod init container terminated with signal\",\n\t\t\texpected: \"Init:Signal:9\",\n\t\t\tpod: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nspec:\n    initContainers:\n    - name: foo\n    containers:\n    - name: bar\nstatus:\n  phase: Running\n  containerStatuses:\n  - name: bar\n    state:\n      running:\n        startedAt: 1995-02-10T00:42:42Z\n  initContainerStatuses:\n  - name: foo\n    state:\n      terminated:\n        signal: 9\n`,\n\t\t},\n\t\t{\n\t\t\tdesc:     \"Pod init container in CrashLooBackOff\",\n\t\t\texpected: \"Init:CrashLoopBackOff\",\n\t\t\tpod: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nspec:\n    initContainers:\n    - name: foo\nstatus:\n  phase: Pending\n  initContainerStatuses:\n  - name: foo\n    state:\n      terminated:\n        exitCode: 2\n        reason: CrashLoopBackOff\n`,\n\t\t},\n\t\t{\n\t\t\tdesc:     \"Pod init container waiting for someReason\",\n\t\t\texpected: \"Init:someReason\",\n\t\t\tpod: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nspec:\n    initContainers:\n    - name: foo\nstatus:\n  phase: Pending\n  initContainerStatuses:\n  - name: foo\n    state:\n      waiting:\n        reason: someReason\n`,\n\t\t},\n\t\t{\n\t\t\tdesc:     \"Pod init container is waiting on PodInitializing\",\n\t\t\texpected: \"Init:0/1\",\n\t\t\tpod: `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nspec:\n    initContainers:\n    - name: foo\nstatus:\n  phase: Pending\n  initContainerStatuses:\n  - name: foo\n    state:\n      waiting:\n        reason: PodInitializing\n`,\n\t\t},\n\t}\n\tfor _, s := range scenarios {\n\t\tobj, err := ToRuntimeObject(s.pod)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"could not decode yml: %s\", err)\n\t\t}\n\t\tpod, ok := obj.(*corev1.Pod)\n\t\tif !ok {\n\t\t\tt.Fatalf(\"could not convert returned object to pod\")\n\t\t}\n\n\t\tgot := GetPodStatus(*pod)\n\t\tif s.expected != got {\n\t\t\tt.Fatalf(\"Wrong pod status on '%s'. Expected '%s', got '%s'\", s.desc, s.expected, got)\n\t\t}\n\t}\n}\n\nfunc TestGetPodsFor(t *testing.T) {\n\n\tconfigs := []string{\n\t\t// pod-1\n\t\t`apiVersion: v1\nkind: Pod\nmetadata:\n  name: pod-1\n  namespace: ns\n  uid: pod-1\n  labels:\n    app: foo\n  ownerReferences:\n  - apiVersion: apps/v1\n    controller: true\n    kind: ReplicaSet\n    name: rs-1\n    uid: rs-1\n`,\n\t\t// rs-1\n\t\t`apiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  name: rs-1\n  namespace: ns\n  uid: rs-1\n  labels:\n    app: foo\n  ownerReferences:\n  - apiVersion: apps/v1\n    controller: true\n    kind: Deployment\n    name: deploy-1\n    uid: deploy-1\nspec:\n  selector:\n    matchLabels:\n      app: foo\n`,\n\t\t// deploy-1\n\t\t`apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: deploy-1\n  namespace: ns\n  uid: deploy-1\nspec:\n  selector:\n    matchLabels:\n      app: foo\n`,\n\t\t// pod-2\n\t\t`apiVersion: v1\nkind: Pod\nmetadata:\n  name: pod-2\n  namespace: ns\n  uid: pod-2\n  labels:\n    app: foo\n  ownerReferences:\n  - apiVersion: apps/v1\n    controller: true\n    kind: ReplicaSet\n    name: rs-2\n    uid: rs-2\n`,\n\t\t// rs-2\n\t\t`apiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  name: rs-2\n  namespace: ns\n  uid: rs-2\n  labels:\n    app: foo\n  ownerReferences:\n  - apiVersion: apps/v1\n    controller: true\n    kind: Deployment\n    name: deploy-2\n    uid: deploy-2\nspec:\n  selector:\n    matchLabels:\n     app: foo\n`,\n\t\t// deploy-2\n\t\t`apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: deploy-2\n  namespace: ns\n  uid: deploy-2\nspec:\n  selector:\n    matchLabels:\n      app: foo\n`}\n\n\tk8sClient, err := NewFakeAPI(configs...)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error %s\", err)\n\t}\n\n\t// Both pod-1 and pod-2 have labels which match deploy-1's selector.\n\t// However, only pod-1 is owned by deploy-1 according to the owner references.\n\t// Owner references should be considered authoritative to resolve ambiguity\n\t// when deployments have overlapping seletors.\n\tpods, err := GetPodsFor(context.Background(), k8sClient, \"ns\", \"deploy/deploy-1\")\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error %s\", err)\n\t}\n\n\tif len(pods) != 1 {\n\t\tfor _, p := range pods {\n\t\t\tt.Logf(\"%s/%s\", p.Namespace, p.Name)\n\t\t}\n\t\tt.Fatalf(\"Expected 1 pod, got %d\", len(pods))\n\t}\n}\n"
  },
  {
    "path": "pkg/k8s/authz.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\tauthV1 \"k8s.io/api/authorization/v1\"\n\tdiscovery \"k8s.io/api/discovery/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\n// ResourceAuthz checks whether a given Kubernetes client is authorized to\n// perform a given action.\nfunc ResourceAuthz(\n\tctx context.Context,\n\tk8sClient kubernetes.Interface,\n\tnamespace, verb, group, version, resource, name string,\n) error {\n\tssar := &authV1.SelfSubjectAccessReview{\n\t\tSpec: authV1.SelfSubjectAccessReviewSpec{\n\t\t\tResourceAttributes: &authV1.ResourceAttributes{\n\t\t\t\tNamespace: namespace,\n\t\t\t\tVerb:      verb,\n\t\t\t\tGroup:     group,\n\t\t\t\tVersion:   version,\n\t\t\t\tResource:  resource,\n\t\t\t\tName:      name,\n\t\t\t},\n\t\t},\n\t}\n\n\tresult, err := k8sClient.\n\t\tAuthorizationV1().\n\t\tSelfSubjectAccessReviews().\n\t\tCreate(ctx, ssar, metav1.CreateOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn evaluateAccessReviewStatus(group, resource, result.Status)\n}\n\n// ResourceAuthzForUser checks whether a given user is authorized to perform a\n// given action.\nfunc ResourceAuthzForUser(\n\tctx context.Context,\n\tclient kubernetes.Interface,\n\tnamespace, verb, group, version, resource, subresource, name, user string,\n\tuserGroups []string,\n\textra map[string]authV1.ExtraValue,\n) error {\n\tsar := &authV1.SubjectAccessReview{\n\t\tSpec: authV1.SubjectAccessReviewSpec{\n\t\t\tUser:   user,\n\t\t\tGroups: userGroups,\n\t\t\tExtra:  extra,\n\t\t\tResourceAttributes: &authV1.ResourceAttributes{\n\t\t\t\tNamespace:   namespace,\n\t\t\t\tVerb:        verb,\n\t\t\t\tGroup:       group,\n\t\t\t\tVersion:     version,\n\t\t\t\tResource:    resource,\n\t\t\t\tSubresource: subresource,\n\t\t\t\tName:        name,\n\t\t\t},\n\t\t},\n\t}\n\n\tresult, err := client.\n\t\tAuthorizationV1().\n\t\tSubjectAccessReviews().\n\t\tCreate(ctx, sar, metav1.CreateOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn evaluateAccessReviewStatus(group, resource, result.Status)\n}\n\nfunc evaluateAccessReviewStatus(group, resource string, status authV1.SubjectAccessReviewStatus) error {\n\tif status.Allowed {\n\t\treturn nil\n\t}\n\n\tgk := schema.GroupKind{\n\t\tGroup: group,\n\t\tKind:  resource,\n\t}\n\tif len(status.Reason) > 0 {\n\t\treturn fmt.Errorf(\"not authorized to access %s: %s\", gk, status.Reason)\n\t}\n\treturn fmt.Errorf(\"not authorized to access %s\", gk)\n}\n\n// ServiceProfilesAccess checks whether the ServiceProfile CRD is installed\n// on the cluster and the client is authorized to access ServiceProfiles.\nfunc ServiceProfilesAccess(ctx context.Context, k8sClient kubernetes.Interface) error {\n\tres, err := k8sClient.Discovery().ServerResourcesForGroupVersion(ServiceProfileAPIVersion)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif res.GroupVersion == ServiceProfileAPIVersion {\n\t\tfor _, apiRes := range res.APIResources {\n\t\t\tif apiRes.Kind == ServiceProfileKind {\n\t\t\t\treturn ResourceAuthz(ctx, k8sClient, \"\", \"list\", \"linkerd.io\", \"\", \"serviceprofiles\", \"\")\n\t\t\t}\n\t\t}\n\t}\n\n\treturn errors.New(\"ServiceProfile CRD not found\")\n}\n\n// ServersAccess checks whether the Server CRD is installed on the cluster\n// and the client is authorized to access Servers.\nfunc ServersAccess(ctx context.Context, k8sClient kubernetes.Interface) error {\n\tgroupVersion := fmt.Sprintf(\"%s/%s\", PolicyAPIGroup, PolicyServerCRDVersion)\n\tres, err := k8sClient.Discovery().ServerResourcesForGroupVersion(groupVersion)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif res.GroupVersion == groupVersion {\n\t\tfor _, apiRes := range res.APIResources {\n\t\t\tif apiRes.Kind == ServerKind {\n\t\t\t\treturn ResourceAuthz(ctx, k8sClient, \"\", \"list\", PolicyAPIGroup, \"\", \"servers\", \"\")\n\t\t\t}\n\t\t}\n\t}\n\treturn fmt.Errorf(\"server CRD (%s) not found\", groupVersion)\n}\n\n// ExtWorkloadAccess checks whether the ExternalWorkload CRD is installed on the\n// cluster and the client is authorized to access ExternalWorkloads\nfunc ExtWorkloadAccess(ctx context.Context, k8sClient kubernetes.Interface) error {\n\tgroupVersion := fmt.Sprintf(\"%s/%s\", WorkloadAPIGroup, WorkloadAPIVersion)\n\tres, err := k8sClient.Discovery().ServerResourcesForGroupVersion(groupVersion)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif res.GroupVersion == groupVersion {\n\t\tfor _, apiRes := range res.APIResources {\n\t\t\tif apiRes.Kind == ExtWorkloadKind {\n\t\t\t\treturn ResourceAuthz(ctx, k8sClient, \"\", \"list\", WorkloadAPIGroup, \"\", \"externalworkloads\", \"\")\n\t\t\t}\n\t\t}\n\t}\n\treturn errors.New(\"ExternalWorkload CRD not found\")\n}\n\n// LinksAccess checks whether the Links CRD is installed on the\n// cluster and the client is authorized to access Links\nfunc LinksAccess(ctx context.Context, k8sClient kubernetes.Interface) error {\n\tgroupVersion := fmt.Sprintf(\"%s/%s\", LinkAPIGroup, LinkAPIVersion)\n\tres, err := k8sClient.Discovery().ServerResourcesForGroupVersion(groupVersion)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif res.GroupVersion == groupVersion {\n\t\tfor _, apiRes := range res.APIResources {\n\t\t\tif apiRes.Kind == LinkKind {\n\t\t\t\treturn ResourceAuthz(ctx, k8sClient, \"\", \"list\", LinkAPIGroup, \"\", \"links\", \"\")\n\t\t\t}\n\t\t}\n\t}\n\treturn errors.New(\"Links CRD not found\")\n}\n\n// EndpointSliceAccess verifies whether the K8s cluster has\n// access to EndpointSlice resources.\nfunc EndpointSliceAccess(ctx context.Context, k8sClient kubernetes.Interface) error {\n\tgv := discovery.SchemeGroupVersion.String()\n\tres, err := k8sClient.Discovery().ServerResourcesForGroupVersion(gv)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif res.GroupVersion == gv {\n\t\tfor _, apiRes := range res.APIResources {\n\t\t\tif apiRes.Kind == \"EndpointSlice\" {\n\t\t\t\treturn checkEndpointSlicesExist(ctx, k8sClient)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn errors.New(\"EndpointSlice resource not found\")\n}\n\nfunc checkEndpointSlicesExist(ctx context.Context, k8sClient kubernetes.Interface) error {\n\tsliceList, err := k8sClient.DiscoveryV1().EndpointSlices(\"\").List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(sliceList.Items) > 0 {\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"no EndpointSlice resources exist in the cluster\")\n}\n\n// ClusterAccess verifies whether k8sClient is authorized to access all pods in\n// all namespaces in the cluster.\nfunc ClusterAccess(ctx context.Context, k8sClient kubernetes.Interface) error {\n\treturn ResourceAuthz(ctx, k8sClient, \"\", \"list\", \"\", \"\", \"pods\", \"\")\n}\n"
  },
  {
    "path": "pkg/k8s/authz_test.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n)\n\nfunc TestResourceAuthz(t *testing.T) {\n\ttests := []struct {\n\t\tk8sConfigs []string\n\t\terr        error\n\t}{\n\t\t{\n\t\t\t// TODO: determine the objects that will affect ResourceAuthz\n\t\t\t[]string{`\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: cr-test\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"deployments\"]\n  verbs: [\"list\"]`,\n\t\t\t\t`\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: crb-test\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cr-test\nsubjects:\n- kind: Group\n  name: system:unauthenticated\n  apiGroup: rbac.authorization.k8s.io`,\n\t\t\t},\n\t\t\terrors.New(\"not authorized to access deployments.apps\"),\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tfor i, test := range tests {\n\t\ttest := test // pin\n\t\tt.Run(fmt.Sprintf(\"%d: returns expected authorization\", i), func(t *testing.T) {\n\t\t\tk8sClient, err := NewFakeAPI(test.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\t\t\terr = ResourceAuthz(ctx, k8sClient, \"\", \"list\", \"apps\", \"v1\", \"deployments\", \"\")\n\t\t\tif err != nil || test.err != nil {\n\t\t\t\tif (err == nil && test.err != nil) ||\n\t\t\t\t\t(err != nil && test.err == nil) ||\n\t\t\t\t\t(err.Error() != test.err.Error()) {\n\t\t\t\t\tt.Fatalf(\"Unexpected error (Expected: %s, Got: %s)\", test.err, err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestServiceProfilesAccess(t *testing.T) {\n\tfakeResources := []string{`\nkind: APIResourceList\napiVersion: v1\ngroupVersion: linkerd.io/v1alpha2\nresources:\n- name: serviceprofiles\n  singularName: serviceprofile\n  namespaced: true\n  kind: ServiceProfile\n  verbs:\n  - delete\n  - deletecollection\n  - get\n  - list\n  - patch\n  - create\n  - update\n  - watch\n  shortNames:\n  - sp`}\n\n\tapi, err := NewFakeAPI(fakeResources...)\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeAPI error: %s\", err)\n\t}\n\n\terr = ServiceProfilesAccess(context.Background(), api)\n\t// RBAC SSAR request failed, but the Discovery lookup succeeded\n\tif diff := deep.Equal(err, errors.New(\"not authorized to access serviceprofiles.linkerd.io\")); diff != nil {\n\t\tt.Errorf(\"%+v\", diff)\n\t}\n}\n\nfunc TestServersAccess(t *testing.T) {\n\ttestCases := []struct {\n\t\tname          string\n\t\tresources     []string\n\t\texpectedError error\n\t}{\n\t\t{\n\t\t\tname: \"supports version but not authorized\",\n\t\t\tresources: []string{\n\t\t\t\t`\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: servers.policy.linkerd.io\nspec:\n  conversion:\n    strategy: None\n  group: policy.linkerd.io\n  names:\n    kind: Server\n    listKind: ServerList\n    plural: servers\n    shortNames:\n    - srv\n    singular: server\n  scope: Namespaced\n`, `\nkind: APIResourceList\napiVersion: v1beta1\ngroupVersion: policy.linkerd.io/v1beta3\nresources:\n- name: servers\n  singularName: server\n  namespaced: true\n  kind: Server\n  verbs:\n  - delete\n  - deletecollection\n  - get\n  - list\n  - patch\n  - create\n  - update\n  - watch\n  shortNames:\n  - srv\n`},\n\t\t\texpectedError: errors.New(\"not authorized to access servers.policy.linkerd.io\"),\n\t\t},\n\t\t{\n\t\t\tname: \"does not support version\",\n\t\t\tresources: []string{\n\t\t\t\t`\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: servers.policy.linkerd.io\nspec:\n  conversion:\n    strategy: None\n  group: policy.linkerd.io\n  names:\n    kind: Server\n    listKind: ServerList\n    plural: servers\n    shortNames:\n    - srv\n    singular: server\n  scope: Namespaced\n`, `\nkind: APIResourceList\napiVersion: v1beta1\ngroupVersion: policy.linkerd.io/v1beta1\nresources:\n- name: servers\n  singularName: server\n  namespaced: true\n  kind: Server\n  verbs:\n  - delete\n  - deletecollection\n  - get\n  - list\n  - patch\n  - create\n  - update\n  - watch\n  shortNames:\n  - srv\n`},\n\t\t\texpectedError: errors.New(`the server could not find the requested resource, GroupVersion \"policy.linkerd.io/v1beta3\" not found`),\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tapi, err := NewFakeAPI(tc.resources...)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\terr = ServersAccess(context.Background(), api.Interface)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatal(\"Expected error, but got success\")\n\t\t\t}\n\t\t\tif err.Error() != tc.expectedError.Error() {\n\t\t\t\tt.Fatalf(\"Unexpected error (Expected: %s, Got: %s)\", tc.expectedError, err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/k8s/completion.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\n// CommandCompletion generates CLI suggestions from resources in a given cluster\n// It uses a list of arguments and a substring from the CLI to filter suggestions.\ntype CommandCompletion struct {\n\tk8sAPI    *KubernetesAPI\n\tnamespace string\n}\n\n// NewCommandCompletion creates a command completion module\nfunc NewCommandCompletion(\n\tk8sAPI *KubernetesAPI,\n\tnamespace string,\n) *CommandCompletion {\n\treturn &CommandCompletion{\n\t\tk8sAPI:    k8sAPI,\n\t\tnamespace: namespace,\n\t}\n}\n\n// Complete accepts a list of arguments and a substring to generate CLI suggestions.\n// `args` represent a list of arguments a user has already entered in the CLI. These\n// arguments are used for determining what resource type we'd like to receive\n// suggestions for as well as a list of resources names that have already provided.\n// `toComplete` represents the string prefix of a resource name that we'd like to\n// use to search for suggestions\n//\n// If `args` is empty, send back a list of all resource types support by the CLI.\n// If `args` has at least one or more items, assume that the first item in `args`\n// is the resource type we are trying to get suggestions for e.g. Deployment, StatefulSets.\n//\n// Complete is generic enough so that it can find suggestions for any type of resource\n// in a Kubernetes cluster. It does this by first querying what GroupVersion a resource\n// belongs to and then does a dynamic `List` query to get resources under that GroupVersion\nfunc (c *CommandCompletion) Complete(args []string, toComplete string) ([]string, error) {\n\tctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancelFn()\n\n\tsuggestions := []string{}\n\tif len(args) == 0 && toComplete == \"\" {\n\t\treturn CompletionResourceTypes, nil\n\t}\n\n\tif len(args) == 0 && toComplete != \"\" {\n\t\tfor _, t := range CompletionResourceTypes {\n\t\t\tif strings.HasPrefix(t, toComplete) {\n\t\t\t\tsuggestions = append(suggestions, t)\n\t\t\t}\n\t\t}\n\t\treturn suggestions, nil\n\t}\n\n\t// Similar to kubectl, we don't provide resource completion\n\t// when the resource provided is in format <kind>/<resourceName>\n\tif strings.Contains(args[0], \"/\") {\n\t\treturn []string{}, nil\n\t}\n\n\tresType, err := CanonicalResourceNameFromFriendlyName(args[0])\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%s is not a valid resource name\", args)\n\t}\n\n\t// if we are looking for namespace suggestions clear namespace selector\n\tif resType == \"namespace\" {\n\t\tc.namespace = \"\"\n\t}\n\n\tgvr, err := c.getGroupVersionKindForResource(resType)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tuList, err := c.k8sAPI.DynamicClient.\n\t\tResource(*gvr).\n\t\tNamespace(c.namespace).\n\t\tList(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\totherResources := args[1:]\n\tfor _, u := range uList.Items {\n\t\tname := u.GetName()\n\n\t\t// Filter out the list of resource items returned from k8s API to\n\t\t// only include items that have the prefix `toComplete` and items\n\t\t// that aren't in the list of resources already provided in the\n\t\t// list of arguments.\n\t\t//\n\t\t// This is useful so that we avoid duplicate suggestions if they\n\t\t// are already in the list of args\n\t\tif strings.HasPrefix(name, toComplete) &&\n\t\t\t!containsResource(name, otherResources) {\n\t\t\tsuggestions = append(suggestions, name)\n\t\t}\n\t}\n\n\treturn suggestions, nil\n}\n\nfunc (c *CommandCompletion) getGroupVersionKindForResource(resourceName string) (*schema.GroupVersionResource, error) {\n\t_, apiResourceList, err := c.k8sAPI.Discovery().ServerGroupsAndResources()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// find the plural name to ensure the resource we are searching for is not a subresource\n\t// i.e. deployment/scale\n\tpluralResourceName, err := PluralResourceNameFromFriendlyName(resourceName)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%s not a valid resource name\", resourceName)\n\t}\n\n\tgvr, err := findGroupVersionResource(resourceName, pluralResourceName, apiResourceList)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not find GroupVersionResource for %s\", resourceName)\n\t}\n\n\treturn gvr, nil\n}\n\nfunc findGroupVersionResource(singularName string, pluralName string, apiResourceList []*metav1.APIResourceList) (*schema.GroupVersionResource, error) {\n\terr := fmt.Errorf(\"could not find the requested resource\")\n\tfor _, res := range apiResourceList {\n\t\tfor _, r := range res.APIResources {\n\n\t\t\t// Make sure we get a resource type where its Kind matches the\n\t\t\t// singularName passed into this function and its Name (which is always\n\t\t\t// the pluralName of an api resource) matches the pluralName passed\n\t\t\t// into this function. Skip further processing of this APIResource\n\t\t\t// if this is not the case.\n\t\t\tif strings.ToLower(r.Kind) != singularName || r.Name != pluralName {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tgv := strings.Split(res.GroupVersion, \"/\")\n\n\t\t\tif len(gv) == 1 && gv[0] == \"v1\" {\n\t\t\t\treturn &schema.GroupVersionResource{\n\t\t\t\t\tVersion:  gv[0],\n\t\t\t\t\tResource: r.Name,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\tif len(gv) != 2 {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treturn &schema.GroupVersionResource{\n\t\t\t\tGroup:    gv[0],\n\t\t\t\tVersion:  gv[1],\n\t\t\t\tResource: r.Name,\n\t\t\t}, nil\n\t\t}\n\t}\n\n\treturn nil, err\n}\n\nfunc containsResource(resource string, otherResources []string) bool {\n\tfor _, r := range otherResources {\n\t\tif r == resource {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "pkg/k8s/fake.go",
    "content": "package k8s\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\tspclient \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned\"\n\tspfake \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/fake\"\n\n\tspscheme \"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tdiscovery \"k8s.io/api/discovery/v1\"\n\tapiextensionsv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\tapiextensionsv1beta1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1\"\n\tapiextensionsclient \"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset\"\n\tapiextensionsfake \"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/util/rand\"\n\tyamlDecoder \"k8s.io/apimachinery/pkg/util/yaml\"\n\tdiscoveryfake \"k8s.io/client-go/discovery/fake\"\n\t\"k8s.io/client-go/dynamic\"\n\tdynamicFake \"k8s.io/client-go/dynamic/fake\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/testing\"\n\tapiregistrationv1 \"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1\"\n\tapiregistrationclient \"k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset\"\n\tapiregistrationfake \"k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake\"\n\n\t\"sigs.k8s.io/yaml\"\n)\n\nfunc init() {\n\tapiextensionsv1beta1.AddToScheme(scheme.Scheme)\n\tapiextensionsv1.AddToScheme(scheme.Scheme)\n\tapiregistrationv1.AddToScheme(scheme.Scheme)\n\tspscheme.AddToScheme(scheme.Scheme)\n}\n\n// NewFakeAPI provides a mock KubernetesAPI backed by hard-coded resources\nfunc NewFakeAPI(configs ...string) (*KubernetesAPI, error) {\n\tclient, apiextClient, apiregClient, _, _, err := NewFakeClientSets(configs...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &KubernetesAPI{\n\t\tConfig:          &rest.Config{},\n\t\tInterface:       client,\n\t\tApiextensions:   apiextClient,\n\t\tApiregistration: apiregClient,\n\t}, nil\n}\n\n// NewFakeAPIFromManifests reads from a slice of readers, each representing a\n// manifest or collection of manifests, and returns a mock KubernetesAPI.\nfunc NewFakeAPIFromManifests(readers []io.Reader) (*KubernetesAPI, error) {\n\tclient, apiextClient, apiregClient, _, _, err := newFakeClientSetsFromManifests(readers)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &KubernetesAPI{\n\t\tInterface:       client,\n\t\tApiextensions:   apiextClient,\n\t\tApiregistration: apiregClient,\n\t}, nil\n}\n\n// NewFakeClientSets provides mock Kubernetes ClientSets.\n// TODO: make this private once KubernetesAPI (and NewFakeAPI) supports spClient\nfunc NewFakeClientSets(configs ...string) (\n\t*fake.Clientset,\n\tapiextensionsclient.Interface,\n\tapiregistrationclient.Interface,\n\tspclient.Interface,\n\tdynamic.Interface,\n\terror,\n) {\n\tobjs := []runtime.Object{}\n\tapiextObjs := []runtime.Object{}\n\tapiRegObjs := []runtime.Object{}\n\tdiscoveryObjs := []runtime.Object{}\n\tspObjs := []runtime.Object{}\n\tfor _, config := range configs {\n\t\tobj, err := ToRuntimeObject(config)\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, nil, nil, err\n\t\t}\n\t\tswitch strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind) {\n\t\tcase \"customresourcedefinition\":\n\t\t\tapiextObjs = append(apiextObjs, obj)\n\t\tcase \"apiservice\":\n\t\t\tapiRegObjs = append(apiRegObjs, obj)\n\t\tcase \"apiresourcelist\":\n\t\t\tdiscoveryObjs = append(discoveryObjs, obj)\n\t\tcase ServiceProfile:\n\t\t\tspObjs = append(spObjs, obj)\n\t\tcase Server:\n\t\t\tspObjs = append(spObjs, obj)\n\t\tcase ExtWorkload:\n\t\t\tspObjs = append(spObjs, obj)\n\t\tdefault:\n\t\t\tobjs = append(objs, obj)\n\t\t}\n\t}\n\n\tendpointslice, err := ToRuntimeObject(`apiVersion: discovery.k8s.io/v1\nkind: EndpointSlice\nmetadata:\n  name: kubernetes\n  namespace: default`)\n\tif err != nil {\n\t\treturn nil, nil, nil, nil, nil, err\n\t}\n\tobjs = append(objs, endpointslice)\n\n\tcs := fake.NewSimpleClientset(objs...)\n\tfakeDiscoveryClient := cs.Discovery().(*discoveryfake.FakeDiscovery)\n\tfor _, obj := range discoveryObjs {\n\t\tapiResList := obj.(*metav1.APIResourceList)\n\t\tfakeDiscoveryClient.Resources = append(fakeDiscoveryClient.Resources, apiResList)\n\t}\n\tfakeDiscoveryClient.Resources = append(fakeDiscoveryClient.Resources, &metav1.APIResourceList{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"APIResourceList\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tGroupVersion: discovery.SchemeGroupVersion.String(),\n\t\tAPIResources: []metav1.APIResource{\n\t\t\t{\n\t\t\t\tName:         \"endpointslices\",\n\t\t\t\tKind:         \"EndpointSlice\",\n\t\t\t\tSingularName: \"endpointslice\",\n\t\t\t},\n\t\t},\n\t})\n\n\t// Add helpers to work with endpoint slice objects.\n\tcs.PrependReactor(\"create\", \"endpointslices\", testing.ReactionFunc(func(action testing.Action) (bool, runtime.Object, error) {\n\t\tes := action.(testing.CreateAction).GetObject().(*discovery.EndpointSlice)\n\n\t\t// The API Server cannot generate a name when we use mocks, intercept\n\t\t// the object and change its name\n\t\tif es.GenerateName != \"\" {\n\t\t\tes.Name = fmt.Sprintf(\"%s-%s\", es.GenerateName, rand.String(8))\n\t\t\tes.GenerateName = \"\"\n\t\t}\n\t\tes.Generation = 1\n\n\t\treturn false, es, nil\n\t}))\n\n\tcs.PrependReactor(\"update\", \"endpointslices\", testing.ReactionFunc(func(action testing.Action) (bool, runtime.Object, error) {\n\t\t// An update won't increase the generation since the API Server is\n\t\t// mocked, so do a typecast and increment it here.\n\t\tes := action.(testing.CreateAction).GetObject().(*discovery.EndpointSlice)\n\t\tes.Generation++\n\t\treturn false, es, nil\n\t}))\n\n\treturn cs,\n\t\tapiextensionsfake.NewSimpleClientset(apiextObjs...),\n\t\tapiregistrationfake.NewSimpleClientset(apiRegObjs...),\n\t\tspfake.NewSimpleClientset(spObjs...),\n\t\tdynamicFake.NewSimpleDynamicClient(scheme.Scheme, objs...),\n\t\tnil\n}\n\n// newFakeClientSetsFromManifests reads from a slice of readers, each\n// representing a manifest or collection of manifests, and returns a mock\n// Kubernetes ClientSet.\n//\n//nolint:unparam\nfunc newFakeClientSetsFromManifests(readers []io.Reader) (\n\tkubernetes.Interface,\n\tapiextensionsclient.Interface,\n\tapiregistrationclient.Interface,\n\tspclient.Interface,\n\tdynamic.Interface,\n\terror,\n) {\n\tconfigs := []string{}\n\n\tfor _, reader := range readers {\n\t\tr := yamlDecoder.NewYAMLReader(bufio.NewReaderSize(reader, 4096))\n\n\t\t// Iterate over all YAML objects in the input\n\t\tfor {\n\t\t\t// Read a single YAML object\n\t\t\tbytes, err := r.Read()\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\treturn nil, nil, nil, nil, nil, err\n\t\t\t}\n\n\t\t\t// check for kind\n\t\t\tvar typeMeta metav1.TypeMeta\n\t\t\tif err := yaml.Unmarshal(bytes, &typeMeta); err != nil {\n\t\t\t\treturn nil, nil, nil, nil, nil, err\n\t\t\t}\n\n\t\t\tswitch typeMeta.Kind {\n\t\t\tcase \"\":\n\t\t\t\t// Kind missing from YAML, skipping\n\n\t\t\tcase \"List\":\n\t\t\t\tvar sourceList corev1.List\n\t\t\t\tif err := yaml.Unmarshal(bytes, &sourceList); err != nil {\n\t\t\t\t\treturn nil, nil, nil, nil, nil, err\n\t\t\t\t}\n\t\t\t\tfor _, item := range sourceList.Items {\n\t\t\t\t\tconfigs = append(configs, string(item.Raw))\n\t\t\t\t}\n\n\t\t\tdefault:\n\t\t\t\tconfigs = append(configs, string(bytes))\n\t\t\t}\n\t\t}\n\t}\n\n\treturn NewFakeClientSets(configs...)\n}\n\n// ToRuntimeObject deserializes Kubernetes YAML into a Runtime Object\nfunc ToRuntimeObject(config string) (runtime.Object, error) {\n\tdecode := scheme.Codecs.UniversalDeserializer().Decode\n\tobj, _, err := decode([]byte(config), nil, nil)\n\treturn obj, err\n}\n\n// ObjectKinds wraps client-go's scheme.Scheme.ObjectKinds()\n// It returns all possible group,version,kind of the go object, true if the\n// object is considered unversioned, or an error if it's not a pointer or is\n// unregistered.\nfunc ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {\n\tapiextensionsv1beta1.AddToScheme(scheme.Scheme)\n\tapiextensionsv1.AddToScheme(scheme.Scheme)\n\tapiregistrationv1.AddToScheme(scheme.Scheme)\n\tspscheme.AddToScheme(scheme.Scheme)\n\treturn scheme.Scheme.ObjectKinds(obj)\n}\n"
  },
  {
    "path": "pkg/k8s/fake_test.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\nfunc TestNewFakeAPI(t *testing.T) {\n\n\tk8sConfigs := []string{\n\t\t`\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: dep-name\n  namespace: dep-ns\n`, `\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: fakecrd.linkerd.io\nspec:\n  group: my-group.io\n  version: v1alpha1\n  scope: Namespaced\n  names:\n    plural: fakecrds\n    singular: fakecrd\n    kind: FakeCRD\n    shortNames:\n    - fc\n`, `\napiVersion: apiregistration.k8s.io/v1\nkind: APIService\nmetadata:\n  name: v1alpha1.tap.linkerd.io\n  labels:\n    linkerd.io/control-plane-component: tap\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: tap.linkerd.io\n  version: v1alpha1\n  groupPriorityMinimum: 1000\n  versionPriority: 100\n  service:\n    name: linkerd-tap\n    namespace: linkerd\n  caBundle: dGFwIGNydA==`,\n\t}\n\n\tapi, err := NewFakeAPI(k8sConfigs...)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\n\tctx := context.Background()\n\tdeploy, err := api.AppsV1().Deployments(\"dep-ns\").Get(ctx, \"dep-name\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\tgvk := schema.GroupVersionKind{\n\t\tGroup:   \"apps\",\n\t\tVersion: \"v1\",\n\t\tKind:    \"Deployment\",\n\t}\n\tif diff := deep.Equal(deploy.GroupVersionKind(), gvk); diff != nil {\n\t\tt.Errorf(\"%+v\", diff)\n\t}\n\n\tcrd, err := api.Apiextensions.ApiextensionsV1beta1().CustomResourceDefinitions().Get(ctx, \"fakecrd.linkerd.io\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\tgvk = schema.GroupVersionKind{\n\t\tGroup:   \"apiextensions.k8s.io\",\n\t\tVersion: \"v1beta1\",\n\t\tKind:    \"CustomResourceDefinition\",\n\t}\n\tif diff := deep.Equal(crd.GroupVersionKind(), gvk); diff != nil {\n\t\tt.Errorf(\"%+v\", diff)\n\t}\n}\n\nfunc TestNewFakeAPIFromManifests(t *testing.T) {\n\tk8sConfigs := []string{\n\t\t`\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: dep-name\n  namespace: dep-ns\n`, `\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: fakecrd.linkerd.io\nspec:\n  group: my-group.io\n  version: v1alpha1\n  scope: Namespaced\n  names:\n    plural: fakecrds\n    singular: fakecrd\n    kind: FakeCRD\n    shortNames:\n    - fc\n`, `\napiVersion: apiregistration.k8s.io/v1\nkind: APIService\nmetadata:\n  name: v1alpha1.tap.linkerd.io\n  labels:\n    linkerd.io/control-plane-component: tap\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: tap.linkerd.io\n  version: v1alpha1\n  groupPriorityMinimum: 1000\n  versionPriority: 100\n  service:\n    name: linkerd-tap\n    namespace: linkerd\n  caBundle: dGFwIGNydA==`,\n\t}\n\n\treaders := []io.Reader{}\n\tfor _, m := range k8sConfigs {\n\t\treaders = append(readers, strings.NewReader(m))\n\t}\n\n\tapi, err := NewFakeAPIFromManifests(readers)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\n\tctx := context.Background()\n\tdeploy, err := api.AppsV1().Deployments(\"dep-ns\").Get(ctx, \"dep-name\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\tgvk := schema.GroupVersionKind{\n\t\tGroup:   \"apps\",\n\t\tVersion: \"v1\",\n\t\tKind:    \"Deployment\",\n\t}\n\tif diff := deep.Equal(deploy.GroupVersionKind(), gvk); diff != nil {\n\t\tt.Errorf(\"%+v\", diff)\n\t}\n\n\tcrd, err := api.Apiextensions.ApiextensionsV1beta1().CustomResourceDefinitions().Get(ctx, \"fakecrd.linkerd.io\", metav1.GetOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t}\n\tgvk = schema.GroupVersionKind{\n\t\tGroup:   \"apiextensions.k8s.io\",\n\t\tVersion: \"v1beta1\",\n\t\tKind:    \"CustomResourceDefinition\",\n\t}\n\tif diff := deep.Equal(crd.GroupVersionKind(), gvk); diff != nil {\n\t\tt.Errorf(\"%+v\", diff)\n\t}\n}\n\nfunc TestNewFakeClientSets(t *testing.T) {\n\ttestCases := []struct {\n\t\tk8sConfigs []string\n\t\terr        error\n\t}{\n\t\t{\n\t\t\t[]string{\n\t\t\t\t`kind: Secret\napiVersion: v1\nmetadata:\n  name: fake-secret\n  namespace: ns\ndata:\n  foo: YmFyCg==`,\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t[]string{`\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: foobar.ns.svc.cluster.local\n  namespace: linkerd\nspec:\n  routes:\n  - condition:\n      pathRegex: \"/x/y/z\"`,\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t[]string{`\napiVersion: apiregistration.k8s.io/v1\nkind: APIService\nmetadata:\n  name: v1alpha1.tap.linkerd.io\n  labels:\n    linkerd.io/control-plane-component: tap\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: tap.linkerd.io\n  version: v1alpha1\n  groupPriorityMinimum: 1000\n  versionPriority: 100\n  service:\n    name: linkerd-tap\n    namespace: linkerd\n  caBundle: dGFwIGNydA==`,\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t[]string{\"\"},\n\t\t\truntime.NewMissingKindErr(\"\"),\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\t_, _, _, _, _, err := NewFakeClientSets(tc.k8sConfigs...)\n\t\t\tif diff := deep.Equal(err, tc.err); diff != nil {\n\t\t\t\tt.Errorf(\"%+v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNewFakeClientSetsFromManifests(t *testing.T) {\n\ttestCases := []struct {\n\t\tmanifests []string\n\t\terr       error\n\t}{\n\t\t{\n\t\t\t[]string{\n\t\t\t\t`kind: Secret\napiVersion: v1\nmetadata:\n  name: fake-secret\n  namespace: ns\ndata:\n  foo: YmFyCg==`,\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t[]string{`\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: foobar.ns.svc.cluster.local\n  namespace: linkerd\nspec:\n  routes:\n  - condition:\n      pathRegex: \"/x/y/z\"`,\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t[]string{`\nkind: List\napiVersion: v1\nitems:\n- kind: Secret\n  apiVersion: v1\n  metadata:\n    name: fake-secret\n    namespace: ns\n  data:\n    foo: YmFyCg==\n- apiVersion: linkerd.io/v1alpha2\n  kind: ServiceProfile\n  metadata:\n    name: foobar.ns.svc.cluster.local\n    namespace: linkerd\n  spec:\n    routes:\n    - condition:\n        pathRegex: \"/x/y/z\"`,\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t[]string{`\napiVersion: apiregistration.k8s.io/v1\nkind: APIService\nmetadata:\n  name: v1alpha1.tap.linkerd.io\n  labels:\n    linkerd.io/control-plane-component: tap\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: tap.linkerd.io\n  version: v1alpha1\n  groupPriorityMinimum: 1000\n  versionPriority: 100\n  service:\n    name: linkerd-tap\n    namespace: linkerd\n  caBundle: dGFwIGNydA==`,\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t[]string{\"---\"},\n\t\t\tnil,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\treaders := []io.Reader{}\n\t\t\tfor _, m := range tc.manifests {\n\t\t\t\treaders = append(readers, strings.NewReader(m))\n\t\t\t}\n\n\t\t\t_, _, _, _, _, err := newFakeClientSetsFromManifests(readers)\n\t\t\tif diff := deep.Equal(err, tc.err); diff != nil {\n\t\t\t\tt.Errorf(\"%+v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestToRuntimeObject(t *testing.T) {\n\ttestCases := []struct {\n\t\tconfig string\n\t\terr    error\n\t}{\n\t\t{\n\t\t\t`kind: ConfigMap\napiVersion: v1\nmetadata:\n  name: fake-cm\n  namespace: ns\ndata:\n  foo: bar`,\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t`kind: Secret\napiVersion: v1\nmetadata:\n  name: fake-secret\n  namespace: ns\ndata:\n  foo: YmFyCg==`,\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t`\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: foobar.ns.svc.cluster.local\n  namespace: linkerd\nspec:\n  routes:\n  - condition:\n      pathRegex: \"/x/y/z\"`,\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"\",\n\t\t\truntime.NewMissingKindErr(\"\"),\n\t\t},\n\t\t{\n\t\t\t\"---\",\n\t\t\truntime.NewMissingKindErr(\"---\"),\n\t\t},\n\t\t{\n\t\t\t`\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: fakecrd.linkerd.io\nspec:\n  group: my-group.io\n  version: v1alpha1\n  scope: Namespaced\n  names:\n    plural: fakecrds\n    singular: fakecrd\n    kind: FakeCRD\n    shortNames:\n    - fc`,\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t`\napiVersion: apiregistration.k8s.io/v1\nkind: APIService\nmetadata:\n  name: v1alpha1.tap.linkerd.io\n  labels:\n    linkerd.io/control-plane-component: tap\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  group: tap.linkerd.io\n  version: v1alpha1\n  groupPriorityMinimum: 1000\n  versionPriority: 100\n  service:\n    name: linkerd-tap\n    namespace: linkerd\n  caBundle: dGFwIGNydA==`,\n\t\t\tnil,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\t_, err := ToRuntimeObject(tc.config)\n\t\t\tif diff := deep.Equal(err, tc.err); diff != nil {\n\t\t\t\tt.Errorf(\"%+v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/k8s/k8s.go",
    "content": "package k8s\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n)\n\n// These constants are string representations of Kubernetes resource types.\nconst (\n\tAll                   = \"all\"\n\tConfigMap             = \"configmap\"\n\tCronJob               = \"cronjob\"\n\tDaemonSet             = \"daemonset\"\n\tDeployment            = \"deployment\"\n\tEndpoints             = \"endpoints\"\n\tEndpointSlices        = \"endpointslices\"\n\tExtWorkload           = \"externalworkload\"\n\tJob                   = \"job\"\n\tLink                  = \"link\"\n\tMeshTLSAuthentication = \"meshtlsauthentication\"\n\tMutatingWebhookConfig = \"mutatingwebhookconfig\"\n\tNamespace             = \"namespace\"\n\tNetworkAuthentication = \"networkauthentication\"\n\tPod                   = \"pod\"\n\tReplicationController = \"replicationcontroller\"\n\tReplicaSet            = \"replicaset\"\n\tSecret                = \"secret\"\n\tService               = \"service\"\n\tServiceProfile        = \"serviceprofile\"\n\tStatefulSet           = \"statefulset\"\n\tNode                  = \"node\"\n\tServer                = \"server\"\n\tServerAuthorization   = \"serverauthorization\"\n\tAuthorizationPolicy   = \"authorizationpolicy\"\n\tHTTPRoute             = \"httproute\"\n\n\tPolicyAPIGroup         = \"policy.linkerd.io\"\n\tPolicyServerCRDVersion = \"v1beta3\"\n\n\tServiceProfileAPIVersion = \"linkerd.io/v1alpha2\"\n\tServiceProfileKind       = \"ServiceProfile\"\n\n\tLinkAPIGroup        = \"multicluster.linkerd.io\"\n\tLinkAPIVersion      = \"v1alpha3\"\n\tLinkAPIGroupVersion = \"multicluster.linkerd.io/v1alpha3\"\n\tLinkKind            = \"Link\"\n\n\tK8sCoreAPIGroup = \"core\"\n\n\tNamespaceKind   = \"Namespace\"\n\tServerKind      = \"Server\"\n\tHTTPRouteKind   = \"HTTPRoute\"\n\tExtWorkloadKind = \"ExternalWorkload\"\n\tPodKind         = \"Pod\"\n\n\tWorkloadAPIGroup   = \"workload.linkerd.io\"\n\tWorkloadAPIVersion = \"v1alpha1\"\n\n\t// special case k8s job label, to not conflict with Prometheus' job label\n\tl5dJob = \"k8s_job\"\n)\n\ntype resourceName struct {\n\tshort  string\n\tfull   string\n\tplural string\n}\n\n// AllResources is a sorted list of all resources defined as constants above.\nvar AllResources = []string{\n\tAuthorizationPolicy,\n\tCronJob,\n\tDaemonSet,\n\tDeployment,\n\tHTTPRoute,\n\tJob,\n\tNamespace,\n\tPod,\n\tReplicaSet,\n\tReplicationController,\n\tServer,\n\tServerAuthorization,\n\tService,\n\tServiceProfile,\n\tStatefulSet,\n}\n\n// StatAllResourceTypes represents the resources to query in StatSummary when Resource.Type is \"all\"\nvar StatAllResourceTypes = []string{\n\tDaemonSet,\n\tStatefulSet,\n\tJob,\n\tDeployment,\n\tReplicationController,\n\tPod,\n\tService,\n\tCronJob,\n\tReplicaSet,\n}\n\n// CompletionResourceTypes represents resources the CLI's uses for autocompleting resource type names\nvar CompletionResourceTypes = []string{\n\tNamespace,\n\tDaemonSet,\n\tStatefulSet,\n\tJob,\n\tDeployment,\n\tReplicationController,\n\tPod,\n\tService,\n\tCronJob,\n\tReplicaSet,\n}\n\nvar resourceNames = []resourceName{\n\t{\"cj\", \"cronjob\", \"cronjobs\"},\n\t{\"ds\", \"daemonset\", \"daemonsets\"},\n\t{\"deploy\", \"deployment\", \"deployments\"},\n\t{\"job\", \"job\", \"jobs\"},\n\t{\"meshtlsauthn\", \"meshtlsauthentication\", \"meshtlsauthentications\"},\n\t{\"ns\", \"namespace\", \"namespaces\"},\n\t{\"netauthn\", \"networkauthentication\", \"networkauthentications\"},\n\t{\"networkauthn\", \"networkauthentication\", \"networkauthentications\"},\n\t{\"po\", \"pod\", \"pods\"},\n\t{\"rc\", \"replicationcontroller\", \"replicationcontrollers\"},\n\t{\"rs\", \"replicaset\", \"replicasets\"},\n\t{\"svc\", \"service\", \"services\"},\n\t{\"sp\", \"serviceprofile\", \"serviceprofiles\"},\n\t{\"saz\", \"serverauthorization\", \"serverauthorizations\"},\n\t{\"serverauthz\", \"serverauthorization\", \"serverauthorizations\"},\n\t{\"srvauthz\", \"serverauthorization\", \"serverauthorizations\"},\n\t{\"srv\", \"server\", \"servers\"},\n\t{\"ap\", \"authorizationpolicy\", \"authorizationpolicies\"},\n\t{\"httproute\", \"httproute\", \"httproutes\"},\n\t{\"authzpolicy\", \"authorizationpolicy\", \"authorizationpolicies\"},\n\t{\"sts\", \"statefulset\", \"statefulsets\"},\n\t{\"ln\", \"link\", \"links\"},\n\t{\"all\", \"all\", \"all\"},\n}\n\n// GetConfig returns kubernetes config based on the current environment.\n// If fpath is provided, loads configuration from that file. Otherwise,\n// GetConfig uses default strategy to load configuration from $KUBECONFIG,\n// .kube/config, or just returns in-cluster config.\nfunc GetConfig(fpath, kubeContext string) (*rest.Config, error) {\n\trules := clientcmd.NewDefaultClientConfigLoadingRules()\n\tif fpath != \"\" {\n\t\trules.ExplicitPath = fpath\n\t}\n\toverrides := &clientcmd.ConfigOverrides{CurrentContext: kubeContext}\n\treturn clientcmd.\n\t\tNewNonInteractiveDeferredLoadingClientConfig(rules, overrides).\n\t\tClientConfig()\n}\n\n// CanonicalResourceNameFromFriendlyName returns a canonical name from common shorthands used in command line tools.\n// This works based on https://github.com/kubernetes/kubernetes/blob/63ffb1995b292be0a1e9ebde6216b83fc79dd988/pkg/kubectl/kubectl.go#L39\n// This also works for non-k8s resources, e.g. authorities\nfunc CanonicalResourceNameFromFriendlyName(friendlyName string) (string, error) {\n\tfor _, name := range resourceNames {\n\t\tif friendlyName == name.short || friendlyName == name.full || friendlyName == name.plural {\n\t\t\treturn name.full, nil\n\t\t}\n\t}\n\treturn \"\", fmt.Errorf(\"cannot find Kubernetes canonical name from friendly name [%s]\", friendlyName)\n}\n\n// PluralResourceNameFromFriendlyName returns a pluralized canonical name from common shorthands used in command line tools.\n// This works based on https://github.com/kubernetes/kubernetes/blob/63ffb1995b292be0a1e9ebde6216b83fc79dd988/pkg/kubectl/kubectl.go#L39\n// This also works for non-k8s resources, e.g. authorities\nfunc PluralResourceNameFromFriendlyName(friendlyName string) (string, error) {\n\tfor _, name := range resourceNames {\n\t\tif friendlyName == name.short || friendlyName == name.full || friendlyName == name.plural {\n\t\t\treturn name.plural, nil\n\t\t}\n\t}\n\treturn \"\", fmt.Errorf(\"cannot find Kubernetes canonical name from friendly name [%s]\", friendlyName)\n}\n\n// ShortNameFromCanonicalResourceName returns the shortest name for a k8s canonical name.\n// Essentially the reverse of CanonicalResourceNameFromFriendlyName\nfunc ShortNameFromCanonicalResourceName(canonicalName string) string {\n\tswitch canonicalName {\n\tcase CronJob:\n\t\treturn \"cj\"\n\tcase DaemonSet:\n\t\treturn \"ds\"\n\tcase Deployment:\n\t\treturn \"deploy\"\n\tcase Job:\n\t\treturn \"job\"\n\tcase Namespace:\n\t\treturn \"ns\"\n\tcase Pod:\n\t\treturn \"po\"\n\tcase ReplicationController:\n\t\treturn \"rc\"\n\tcase ReplicaSet:\n\t\treturn \"rs\"\n\tcase Service:\n\t\treturn \"svc\"\n\tcase ServiceProfile:\n\t\treturn \"sp\"\n\tcase StatefulSet:\n\t\treturn \"sts\"\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\n// KindToL5DLabel converts a Kubernetes `kind` to a Linkerd label.\n// For example:\n//\n//\t`pod` -> `pod`\n//\t`job` -> `k8s_job`\nfunc KindToL5DLabel(k8sKind string) string {\n\tif k8sKind == Job {\n\t\treturn l5dJob\n\t}\n\treturn k8sKind\n}\n\n// PodIdentity returns the mesh TLS identity name of this pod, as constructed\n// from the pod's service account name and other metadata.\nfunc PodIdentity(pod *corev1.Pod) (string, error) {\n\tif pod.Status.Phase != corev1.PodRunning {\n\t\treturn \"\", fmt.Errorf(\"pod not running: %s\", pod.GetName())\n\t}\n\n\tpodsa := pod.Spec.ServiceAccountName\n\tpodns := pod.ObjectMeta.Namespace\n\tcontainers := append(pod.Spec.InitContainers, pod.Spec.Containers...)\n\tfor _, c := range containers {\n\t\tif c.Name == ProxyContainerName {\n\t\t\tfor _, env := range c.Env {\n\t\t\t\tif env.Name == \"LINKERD2_PROXY_IDENTITY_LOCAL_NAME\" {\n\t\t\t\t\treturn strings.ReplaceAll(env.Value, \"$(_pod_sa).$(_pod_ns)\", fmt.Sprintf(\"%s.%s\", podsa, podns)), nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\", nil\n}\n"
  },
  {
    "path": "pkg/k8s/k8s_test.go",
    "content": "package k8s\n\nimport (\n\t\"testing\"\n)\n\nfunc TestGetConfig(t *testing.T) {\n\tt.Run(\"Gets host correctly form existing file\", func(t *testing.T) {\n\t\tconfig, err := GetConfig(\"testdata/config.test\", \"\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\texpectedHost := \"https://55.197.171.239\"\n\t\tif config.Host != expectedHost {\n\t\t\tt.Fatalf(\"Expected host to be [%s] got [%s]\", expectedHost, config.Host)\n\t\t}\n\t})\n\n\tt.Run(\"Returns error if configuration cannot be found\", func(t *testing.T) {\n\t\t_, err := GetConfig(\"/this/doest./not/exist.config\", \"\")\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"Expecting error when config file does not exist, got nothing\")\n\t\t}\n\t})\n}\n\nfunc TestCanonicalResourceNameFromFriendlyName(t *testing.T) {\n\tt.Run(\"Returns canonical name for all known variants\", func(t *testing.T) {\n\t\texpectations := map[string]string{\n\t\t\t\"po\":           Pod,\n\t\t\t\"pod\":          Pod,\n\t\t\t\"deployment\":   Deployment,\n\t\t\t\"deployments\":  Deployment,\n\t\t\t\"cj\":           CronJob,\n\t\t\t\"cronjob\":      CronJob,\n\t\t\t\"serverauthz\":  ServerAuthorization,\n\t\t\t\"srvauthz\":     ServerAuthorization,\n\t\t\t\"authzpolicy\":  AuthorizationPolicy,\n\t\t\t\"meshtlsauthn\": MeshTLSAuthentication,\n\t\t\t\"networkauthn\": NetworkAuthentication,\n\t\t\t\"netauthn\":     NetworkAuthentication,\n\t\t}\n\n\t\tfor input, expectedName := range expectations {\n\t\t\tactualName, err := CanonicalResourceNameFromFriendlyName(input)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\tif actualName != expectedName {\n\t\t\t\tt.Fatalf(\"Expected friendly name [%s] to resolve to [%s], but got [%s]\", input, expectedName, actualName)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Returns error if input isn't a supported name\", func(t *testing.T) {\n\t\tunsupportedNames := []string{\n\t\t\t\"pdo\", \"dop\", \"paths\", \"path\", \"\", \"mesh\",\n\t\t}\n\n\t\tfor _, n := range unsupportedNames {\n\t\t\tout, err := CanonicalResourceNameFromFriendlyName(n)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatalf(\"Expecting error when resolving [%s], but it did resolve to [%s]\", n, out)\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/k8s/labels.go",
    "content": "/*\nKubernetes labels and annotations used in Linkerd's control plane and data plane\nKubernetes configs.\n*/\n\npackage k8s\n\nimport (\n\t\"fmt\"\n\n\tewv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/externalworkload/v1beta1\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\nconst (\n\t/*\n\t * Labels\n\t */\n\n\t// Prefix is the prefix common to all labels and annotations injected by Linkerd\n\tPrefix = \"linkerd.io\"\n\n\t// LinkerdExtensionLabel is a label that helps identifying the namespace\n\t// that contain a Linkerd Extension\n\tLinkerdExtensionLabel = Prefix + \"/extension\"\n\n\t// ControllerComponentLabel identifies this object as a component of Linkerd's\n\t// control plane (e.g. web, controller).\n\tControllerComponentLabel = Prefix + \"/control-plane-component\"\n\n\t// ExtensionAPIServerAuthenticationConfigMapName is the name of the ConfigMap where\n\t// authentication data for extension API servers is placed.\n\tExtensionAPIServerAuthenticationConfigMapName = \"extension-apiserver-authentication\"\n\n\t// ExtensionAPIServerAuthenticationRequestHeaderClientCAFileKey is the key that\n\t// contains the value of the \"--requestheader-client-ca-file\" flag.\n\tExtensionAPIServerAuthenticationRequestHeaderClientCAFileKey = \"requestheader-client-ca-file\"\n\n\t// RequireIDHeader signals to the proxy that a certain identity should be expected\n\t// of the remote peer\n\tRequireIDHeader = \"l5d-require-id\"\n\n\t// ControllerNSLabel is injected into mesh-enabled apps, identifying the\n\t// namespace of the Linkerd control plane.\n\tControllerNSLabel = Prefix + \"/control-plane-ns\"\n\n\t// ProxyDeploymentLabel is injected into mesh-enabled apps, identifying the\n\t// deployment that this proxy belongs to.\n\tProxyDeploymentLabel = Prefix + \"/proxy-deployment\"\n\n\t// ProxyReplicationControllerLabel is injected into mesh-enabled apps,\n\t// identifying the ReplicationController that this proxy belongs to.\n\tProxyReplicationControllerLabel = Prefix + \"/proxy-replicationcontroller\"\n\n\t// ProxyReplicaSetLabel is injected into mesh-enabled apps, identifying the\n\t// ReplicaSet that this proxy belongs to.\n\tProxyReplicaSetLabel = Prefix + \"/proxy-replicaset\"\n\n\t// ProxyJobLabel is injected into mesh-enabled apps, identifying the Job that\n\t// this proxy belongs to.\n\tProxyJobLabel = Prefix + \"/proxy-job\"\n\n\t// ProxyDaemonSetLabel is injected into mesh-enabled apps, identifying the\n\t// DaemonSet that this proxy belongs to.\n\tProxyDaemonSetLabel = Prefix + \"/proxy-daemonset\"\n\n\t// ProxyStatefulSetLabel is injected into mesh-enabled apps, identifying the\n\t// StatefulSet that this proxy belongs to.\n\tProxyStatefulSetLabel = Prefix + \"/proxy-statefulset\"\n\n\t// ProxyCronJobLabel is injected into mesh-enabled apps, identifying the\n\t// CronJob that this proxy belongs to.\n\tProxyCronJobLabel = Prefix + \"/proxy-cronjob\"\n\n\t// WorkloadNamespaceLabel is injected into mesh-enabled apps, identifying the\n\t// Namespace that this proxy belongs to.\n\tWorkloadNamespaceLabel = Prefix + \"/workload-ns\"\n\n\t// Enabled is used by annotations whose valid values include \"enabled\".\n\tEnabled = \"enabled\"\n\n\t// Disabled is used by annotations whose valid values include \"disabled\".\n\tDisabled = \"disabled\"\n\n\t/*\n\t * Annotations\n\t */\n\n\t// CreatedByAnnotation indicates the source of the injected data plane\n\t// (e.g. linkerd/cli v2.0.0).\n\tCreatedByAnnotation = Prefix + \"/created-by\"\n\n\t// ProxyVersionAnnotation indicates the version of the injected data plane\n\t// (e.g. v0.1.3).\n\tProxyVersionAnnotation = Prefix + \"/proxy-version\"\n\n\t// ProxyInjectAnnotation controls whether or not a pod should be injected\n\t// when set on a pod spec. When set on a namespace spec, it applies to all\n\t// pods in the namespace. Supported values are Enabled or Disabled\n\tProxyInjectAnnotation = Prefix + \"/inject\"\n\n\t// ProxyInjectEnabled is assigned to the ProxyInjectAnnotation annotation to\n\t// enable injection for a pod or namespace.\n\tProxyInjectEnabled = Enabled\n\n\t// ProxyInjectIngress is assigned to the ProxyInjectAnnotation annotation to\n\t// enable injection in ingress mode for a pod.\n\tProxyInjectIngress = \"ingress\"\n\n\t// ProxyInjectDisabled is assigned to the ProxyInjectAnnotation annotation to\n\t// disable injection for a pod or namespace.\n\tProxyInjectDisabled = Disabled\n\n\t// ProxyTrustRootSHA indicates the cert bundle configured on the injected\n\t// workload.\n\tProxyTrustRootSHA = Prefix + \"/trust-root-sha256\"\n\n\t/*\n\t * Proxy config annotations\n\t */\n\n\t// ProxyConfigAnnotationsPrefix is the prefix of all config-related annotations\n\tProxyConfigAnnotationsPrefix = \"config.linkerd.io\"\n\n\t// ProxyConfigAnnotationsPrefixAlpha is the prefix of newly released config-related annotations\n\tProxyConfigAnnotationsPrefixAlpha = \"config.alpha.linkerd.io\"\n\n\t// ProxyConfigAnnotationsPrefixBeta is the prefix for config-related annotations more mature than alpha but not yet stable\n\tProxyConfigAnnotationsPrefixBeta = \"config.beta.linkerd.io\"\n\n\t// ProxyImageAnnotation can be used to override the proxyImage config.\n\tProxyImageAnnotation = ProxyConfigAnnotationsPrefix + \"/proxy-image\"\n\n\t// ProxyImagePullPolicyAnnotation can be used to override the\n\t// proxyImagePullPolicy and proxyInitImagePullPolicy configs.\n\tProxyImagePullPolicyAnnotation = ProxyConfigAnnotationsPrefix + \"/image-pull-policy\"\n\n\t// DebugImageAnnotation can be used to override the debugImage config.\n\tDebugImageAnnotation = ProxyConfigAnnotationsPrefix + \"/debug-image\"\n\n\t// DebugImageVersionAnnotation can be used to override the debugImageVersion config.\n\tDebugImageVersionAnnotation = ProxyConfigAnnotationsPrefix + \"/debug-image-version\"\n\n\t// DebugImagePullPolicyAnnotation can be used to override the debugImagePullPolicy config.\n\tDebugImagePullPolicyAnnotation = ProxyConfigAnnotationsPrefix + \"/debug-image-pull-policy\"\n\n\t// ProxyControlPortAnnotation can be used to override the controlPort config.\n\tProxyControlPortAnnotation = ProxyConfigAnnotationsPrefix + \"/control-port\"\n\n\t// ProxyIgnoreInboundPortsAnnotation can be used to override the\n\t// ignoreInboundPorts config.\n\tProxyIgnoreInboundPortsAnnotation = ProxyConfigAnnotationsPrefix + \"/skip-inbound-ports\"\n\n\t// ProxyOpaquePortsAnnotation can be used to override the opaquePorts\n\t// config.\n\tProxyOpaquePortsAnnotation = ProxyConfigAnnotationsPrefix + \"/opaque-ports\"\n\n\t// ProxyIgnoreOutboundPortsAnnotation can be used to override the\n\t// ignoreOutboundPorts config.\n\tProxyIgnoreOutboundPortsAnnotation = ProxyConfigAnnotationsPrefix + \"/skip-outbound-ports\"\n\n\t// ProxySkipSubnetsAnnotation can be used to override the skipSubnets config\n\tProxySkipSubnetsAnnotation = ProxyConfigAnnotationsPrefix + \"/skip-subnets\"\n\n\t// ProxyInboundPortAnnotation can be used to override the inboundPort config.\n\tProxyInboundPortAnnotation = ProxyConfigAnnotationsPrefix + \"/inbound-port\"\n\n\t// ProxyAdminPortAnnotation can be used to override the adminPort config.\n\tProxyAdminPortAnnotation = ProxyConfigAnnotationsPrefix + \"/admin-port\"\n\n\t// ProxyOutboundPortAnnotation can be used to override the outboundPort\n\t// config.\n\tProxyOutboundPortAnnotation = ProxyConfigAnnotationsPrefix + \"/outbound-port\"\n\n\t// ProxyPodInboundPortsAnnotation can be used to set a comma-separated\n\t// list of (non-proxy) container ports exposed by the pod spec. Useful\n\t// when other mutating webhooks inject sidecar containers after the\n\t// proxy injector has run.\n\tProxyPodInboundPortsAnnotation = ProxyConfigAnnotationsPrefix + \"/pod-inbound-ports\"\n\n\t// ProxyCPURequestAnnotation can be used to override the requestCPU config.\n\tProxyCPURequestAnnotation = ProxyConfigAnnotationsPrefix + \"/proxy-cpu-request\"\n\n\t// ProxyMemoryRequestAnnotation can be used to override the\n\t// requestMemoryConfig.\n\tProxyMemoryRequestAnnotation = ProxyConfigAnnotationsPrefix + \"/proxy-memory-request\"\n\n\t// ProxyEphemeralStorageRequestAnnotation can be used to override the requestEphemeralStorage config.\n\tProxyEphemeralStorageRequestAnnotation = ProxyConfigAnnotationsPrefix + \"/proxy-ephemeral-storage-request\"\n\n\t// ProxyCPULimitAnnotation can be used to override the limitCPU config.\n\tProxyCPULimitAnnotation = ProxyConfigAnnotationsPrefix + \"/proxy-cpu-limit\"\n\n\t// ProxyCPURatioLimitAnnotation can be used to configure\n\t// proxy worker threads as a proportion of the node's available CPUs.\n\tProxyCPURatioLimitAnnotation = ProxyConfigAnnotationsPrefix + \"/proxy-cpu-ratio-limit\"\n\n\t// ProxyMemoryLimitAnnotation can be used to override the limitMemory config.\n\tProxyMemoryLimitAnnotation = ProxyConfigAnnotationsPrefix + \"/proxy-memory-limit\"\n\n\t// ProxyEphemeralStorageLimitAnnotation can be used to override the limitEphemeralStorage config.\n\tProxyEphemeralStorageLimitAnnotation = ProxyConfigAnnotationsPrefix + \"/proxy-ephemeral-storage-limit\"\n\n\t// ProxyUIDAnnotation can be used to override the UID config.\n\tProxyUIDAnnotation = ProxyConfigAnnotationsPrefix + \"/proxy-uid\"\n\n\t// ProxyGIDAnnotation can be used to override the GID config.\n\tProxyGIDAnnotation = ProxyConfigAnnotationsPrefix + \"/proxy-gid\"\n\n\t// ProxyAdminShutdownAnnotation can be used to override the\n\t// LINKERD2_PROXY_SHUTDOWN_ENDPOINT_ENABLED config.\n\tProxyAdminShutdownAnnotation = ProxyConfigAnnotationsPrefix + \"/proxy-admin-shutdown\"\n\n\t// ProxyLogLevelAnnotation can be used to override the log level config.\n\tProxyLogLevelAnnotation = ProxyConfigAnnotationsPrefix + \"/proxy-log-level\"\n\n\t// ProxyLogHTTPHeaders can be used to override if the proxy is permitted to log HTTP headers.\n\tProxyLogHTTPHeaders = ProxyConfigAnnotationsPrefix + \"/proxy-log-http-headers\"\n\n\t// ProxyLogFormatAnnotation can be used to override the log format config.\n\tProxyLogFormatAnnotation = ProxyConfigAnnotationsPrefix + \"/proxy-log-format\"\n\n\t// ProxyEnableExternalProfilesAnnotation can be used to override the\n\t// disableExternalProfilesAnnotation config.\n\tProxyEnableExternalProfilesAnnotation = ProxyConfigAnnotationsPrefix + \"/enable-external-profiles\"\n\n\t// ProxyVersionOverrideAnnotation can be used to override the proxy version config.\n\tProxyVersionOverrideAnnotation = ProxyConfigAnnotationsPrefix + \"/proxy-version\"\n\n\t// ProxyRequireIdentityOnInboundPortsAnnotation can be used to configure the proxy\n\t// to always require identity on inbound ports\n\tProxyRequireIdentityOnInboundPortsAnnotation = ProxyConfigAnnotationsPrefix + \"/proxy-require-identity-inbound-ports\"\n\n\t// ProxyOutboundConnectTimeout can be used to configure the outbound TCP connection\n\t// timeout in the proxy\n\tProxyEnableHostnameLabels = ProxyConfigAnnotationsPrefix + \"/proxy-metrics-hostname-labels\"\n\n\t// ProxyOutboundConnectTimeout can be used to configure the outbound TCP connection\n\t// timeout in the proxy\n\tProxyOutboundConnectTimeout = ProxyConfigAnnotationsPrefix + \"/proxy-outbound-connect-timeout\"\n\n\t// ProxyInboundConnectTimeout can be used to configure the inbound TCP connection\n\t// timeout in the proxy\n\tProxyInboundConnectTimeout = ProxyConfigAnnotationsPrefix + \"/proxy-inbound-connect-timeout\"\n\n\t// ProxyOutboundDiscoveryCacheTimeout can be used to configure the timeout\n\t// that will evict unused outbound discovery results\n\tProxyOutboundDiscoveryCacheUnusedTimeout = ProxyConfigAnnotationsPrefix + \"/proxy-outbound-discovery-cache-unused-timeout\"\n\n\t// ProxyInboundDiscoveryCacheUnusedTimeout can be used to configure the timeout\n\t// that will evict unused inbound discovery results\n\tProxyInboundDiscoveryCacheUnusedTimeout = ProxyConfigAnnotationsPrefix + \"/proxy-inbound-discovery-cache-unused-timeout\"\n\n\t// ProxyDisableOutboundProtocolDetectTimeout can be used to disable protocol\n\t// detection timeouts for outbound connections by setting them to a very\n\t// high value.\n\tProxyDisableOutboundProtocolDetectTimeout = ProxyConfigAnnotationsPrefix + \"/proxy-disable-outbound-protocol-detect-timeout\"\n\n\t// ProxyDisableInboundProtocolDetectTimeout can be used to disable protocol\n\t// detection timeouts for inbound connections by setting them to a very\n\t// high value.\n\tProxyDisableInboundProtocolDetectTimeout = ProxyConfigAnnotationsPrefix + \"/proxy-disable-inbound-protocol-detect-timeout\"\n\n\t// ProxyEnableGatewayAnnotation can be used to configure the proxy\n\t// to operate as a gateway, routing requests that target the inbound router.\n\tProxyEnableGatewayAnnotation = ProxyConfigAnnotationsPrefix + \"/enable-gateway\"\n\n\t// ProxyEnableDebugAnnotation is set to true if the debug container is\n\t// injected.\n\tProxyEnableDebugAnnotation = ProxyConfigAnnotationsPrefix + \"/enable-debug-sidecar\"\n\n\t// CloseWaitTimeoutAnnotation configures nf_conntrack_tcp_timeout_close_wait.\n\tCloseWaitTimeoutAnnotation = ProxyConfigAnnotationsPrefix + \"/close-wait-timeout\"\n\n\t// ProxyWaitBeforeExitSecondsAnnotation makes the proxy container to wait for the given period before exiting\n\t// after the Pod entered the Terminating state. Must be smaller than terminationGracePeriodSeconds\n\t// configured for the Pod\n\tProxyWaitBeforeExitSecondsAnnotation = ProxyConfigAnnotationsPrefixAlpha + \"/proxy-wait-before-exit-seconds\"\n\n\t// ProxyEnableNativeSidecarAnnotationAlpha enables the new native initContainer sidecar.\n\t// Deprecated: use ProxyEnableNativeSidecarAnnotationBeta instead.\n\tProxyEnableNativeSidecarAnnotationAlpha = ProxyConfigAnnotationsPrefixAlpha + \"/proxy-enable-native-sidecar\"\n\n\t// ProxyEnableNativeSidecarAnnotationBeta enables the new native initContainer sidecar\n\tProxyEnableNativeSidecarAnnotationBeta = ProxyConfigAnnotationsPrefixBeta + \"/proxy-enable-native-sidecar\"\n\n\t// ProxyAwait can be used to force the application to wait for the proxy\n\t// to be ready.\n\tProxyAwait = ProxyConfigAnnotationsPrefix + \"/proxy-await\"\n\n\t// ProxyDefaultInboundPolicyAnnotation is used to configure the default\n\t// inbound policy of the proxy\n\tProxyDefaultInboundPolicyAnnotation = ProxyConfigAnnotationsPrefix + \"/default-inbound-policy\"\n\n\t// ProxyAccessLogAnnotation configures whether HTTP access logging is\n\t// enabled, and what access log format is used.\n\tProxyAccessLogAnnotation = ProxyConfigAnnotationsPrefix + \"/access-log\"\n\n\t// AllUnauthenticated allows all unathenticated connections.\n\tAllUnauthenticated = \"all-unauthenticated\"\n\n\t// AllAuthenticated allows all authenticated connections.\n\tAllAuthenticated = \"all-authenticated\"\n\n\t// ClusterUnauthenticated allows all unauthenticated connections from\n\t// within the cluster.\n\tClusterUnauthenticated = \"cluster-unauthenticated\"\n\n\t// ClusterAuthenticated allows all authenticated connections from within\n\t// the cluster.\n\tClusterAuthenticated = \"cluster-authenticated\"\n\n\t// Deny denies all connections.\n\tDeny = \"deny\"\n\n\t// Audit allows all connections, but logs and emits audit metrics whenever\n\t// the default policy is enacted\n\tAudit = \"audit\"\n\n\t// ProxyShutdownGracePeriodAnnotation configures the grace period for\n\t// graceful shutdowns in the proxy.\n\tProxyShutdownGracePeriodAnnotation = ProxyConfigAnnotationsPrefix + \"/shutdown-grace-period\"\n\n\t/*\n\t * Component Names\n\t */\n\n\t// ConfigConfigMapName is the name of the ConfigMap containing the linkerd controller configuration.\n\tConfigConfigMapName = \"linkerd-config\"\n\n\t// DebugContainerName is the name of the default linkerd debug container\n\tDebugContainerName = \"linkerd-debug\"\n\n\t// DebugSidecarImage is the image name of the default linkerd debug container\n\tDebugSidecarImage = \"cr.l5d.io/linkerd/debug\"\n\n\t// InitContainerName is the name assigned to the injected init container.\n\tInitContainerName = \"linkerd-init\"\n\n\t// InitXtablesLockVolumeMountName is the name of the volumeMount used by proxy-init\n\t// to handle iptables-legacy\n\tInitXtablesLockVolumeMountName = \"linkerd-proxy-init-xtables-lock\"\n\n\t// LinkerdTokenVolumeMountName is the name of the volumeMount used for\n\t// the serviceAccount token\n\tLinkerdTokenVolumeMountName = \"linkerd-identity-token\"\n\n\t// ProxyContainerName is the name assigned to the injected proxy container.\n\tProxyContainerName = \"linkerd-proxy\"\n\n\t// IdentityEndEntityVolumeName is the name assigned the temporary end-entity\n\t// volume mounted into each proxy to store identity credentials.\n\tIdentityEndEntityVolumeName = \"linkerd-identity-end-entity\"\n\n\t// IdentityIssuerSecretName is the name of the Secret that stores issuer credentials.\n\tIdentityIssuerSecretName = \"linkerd-identity-issuer\"\n\n\t// IdentityIssuerSchemeLinkerd is the issuer secret scheme used by linkerd\n\tIdentityIssuerSchemeLinkerd = \"linkerd.io/tls\"\n\n\t// IdentityIssuerKeyName is the issuer's private key file.\n\tIdentityIssuerKeyName = \"key.pem\"\n\n\t// IdentityIssuerCrtName is the issuer's certificate file.\n\tIdentityIssuerCrtName = \"crt.pem\"\n\n\t// IdentityIssuerTrustAnchorsNameExternal is the issuer's certificate file (when using cert-manager).\n\tIdentityIssuerTrustAnchorsNameExternal = \"ca.crt\"\n\n\t// ProxyPortName is the name of the Linkerd Proxy's proxy port.\n\tProxyPortName = \"linkerd-proxy\"\n\n\t// ProxyAdminPortName is the name of the Linkerd Proxy's metrics port.\n\tProxyAdminPortName = \"linkerd-admin\"\n\n\t// ProxyInjectorWebhookServiceName is the name of the mutating webhook service\n\tProxyInjectorWebhookServiceName = \"linkerd-proxy-injector\"\n\n\t// ProxyInjectorWebhookConfigName is the name of the mutating webhook configuration\n\tProxyInjectorWebhookConfigName = ProxyInjectorWebhookServiceName + \"-webhook-config\"\n\n\t// SPValidatorWebhookServiceName is the name of the validating webhook service\n\tSPValidatorWebhookServiceName = \"linkerd-sp-validator\"\n\n\t// SPValidatorWebhookConfigName is the name of the validating webhook configuration\n\tSPValidatorWebhookConfigName = SPValidatorWebhookServiceName + \"-webhook-config\"\n\n\t// PolicyValidatorWebhookConfigName is the name of the validating webhook configuration\n\tPolicyValidatorWebhookConfigName = \"linkerd-policy-validator-webhook-config\"\n\n\t/*\n\t * Mount paths\n\t */\n\n\t// MountPathBase is the base directory of the mount path.\n\tMountPathBase = \"/var/run/linkerd\"\n\n\t// MountPathTrustRootsBase is the base directory of the trust roots.\n\tMountPathTrustRootsBase = MountPathBase + \"/identity/trust-roots\"\n\n\t// MountPathTrustRootsPEM is the path at which the trust bundle is mounted.\n\tMountPathTrustRootsPEM = MountPathTrustRootsBase + \"/ca-bundle.crt\"\n\n\t// MountPathServiceAccount is the default path where Kubernetes stores\n\t// the service account token\n\tMountPathServiceAccount = \"/var/run/secrets/kubernetes.io/serviceaccount\"\n\n\t// MountPathValuesConfig is the path at which the values config file is mounted.\n\tMountPathValuesConfig = MountPathBase + \"/config/values\"\n\n\t// MountPathTLSBase is the path at which the TLS cert and key PEM files are mounted\n\tMountPathTLSBase = MountPathBase + \"/tls\"\n\n\t// MountPathTLSKeyPEM is the path at which the TLS key PEM file is mounted.\n\tMountPathTLSKeyPEM = MountPathTLSBase + \"/tls.key\"\n\n\t// MountPathTLSCrtPEM is the path at which the TLS cert PEM file is mounted.\n\tMountPathTLSCrtPEM = MountPathTLSBase + \"/tls.crt\"\n\n\t/*\n\t * Service mirror constants\n\t */\n\n\t// SvcMirrorPrefix is the prefix common to all labels and annotations\n\t// and types used by the service mirror component\n\tSvcMirrorPrefix = \"mirror.linkerd.io\"\n\n\t// MulticlusterPrefix is the prefix common to all labels and annotations\n\t// used for multicluster services.\n\tMulticlusterPrefix = \"multicluster.linkerd.io\"\n\n\t// MirrorSecretType is the type of secret that is supposed to contain\n\t// the access information for remote clusters.\n\tMirrorSecretType = SvcMirrorPrefix + \"/remote-kubeconfig\"\n\n\t// DefaultExportedServiceSelector is the default label selector for exported\n\t// services.\n\tDefaultExportedServiceSelector = SvcMirrorPrefix + \"/exported\"\n\n\t// DefaultFederatedServiceSelector is the default label selector for\n\t// federated services.\n\tDefaultFederatedServiceSelector = SvcMirrorPrefix + \"/federated\"\n\n\t// MirroredResourceLabel indicates that this resource is the result\n\t// of a mirroring operation (can be a namespace or a service)\n\tMirroredResourceLabel = SvcMirrorPrefix + \"/mirrored-service\"\n\n\t// MirroredGatewayLabel indicates that this is a mirrored gateway\n\tMirroredGatewayLabel = SvcMirrorPrefix + \"/mirrored-gateway\"\n\n\t// MirroredHeadlessSvcNameLabel indicates the root headless service for\n\t// mirrored headless hosts.\n\tMirroredHeadlessSvcNameLabel = SvcMirrorPrefix + \"/headless-mirror-svc-name\"\n\n\t// RemoteClusterNameLabel put on a local mirrored service, it\n\t// allows us to associate a mirrored service with a remote cluster\n\tRemoteClusterNameLabel = SvcMirrorPrefix + \"/cluster-name\"\n\n\t// RemoteDiscoveryLabel indicates that this service is a remote discovery\n\t// service and the value of this label is the name of the remote cluster.\n\tRemoteDiscoveryLabel = MulticlusterPrefix + \"/remote-discovery\"\n\n\t// RemoteServiceLabel is the name of the service in the remote cluster.\n\tRemoteServiceLabel = MulticlusterPrefix + \"/remote-service\"\n\n\t// RemoteDiscoveryAnnotation indicates that this service is a remote discovery\n\t// service and the value of this label is a comma-separated list of remote\n\t// discovery targets of the form <service>@<cluster>. This can be used in\n\t// conjunction with LocalDiscoveryAnnotation and the endpoints will be\n\t// unioned.\n\tRemoteDiscoveryAnnotation = MulticlusterPrefix + \"/remote-discovery\"\n\n\t// LocalDiscoveryAnnotation indicates that service discovery information for\n\t// this service should be fetched from another service in the local cluster\n\t// instead of the target service itself. This can be used in conjunction\n\t// with RemoteDiscoveryAnnotation and the endpoints will be unioned.\n\tLocalDiscoveryAnnotation = MulticlusterPrefix + \"/local-discovery\"\n\n\t// RemoteResourceVersionAnnotation is the last observed remote resource\n\t// version of a mirrored resource. Useful when doing updates\n\tRemoteResourceVersionAnnotation = SvcMirrorPrefix + \"/remote-resource-version\"\n\n\t// RemoteServiceFqName is the fully qualified name of the mirrored service\n\t// on the remote cluster\n\tRemoteServiceFqName = SvcMirrorPrefix + \"/remote-svc-fq-name\"\n\n\t// RemoteGatewayIdentity follows the same kind of logic as RemoteGatewayNameLabel\n\tRemoteGatewayIdentity = SvcMirrorPrefix + \"/remote-gateway-identity\"\n\n\t// GatewayIdentity can be found on the remote gateway service\n\tGatewayIdentity = SvcMirrorPrefix + \"/gateway-identity\"\n\n\t// GatewayProbeFailureThreshold is the minimum consecutive failures for the probe to be considered failed\n\tGatewayProbeFailureThreshold = SvcMirrorPrefix + \"/probe-failure-threshold\"\n\n\t// GatewayProbePeriod the interval at which the health of the gateway should be probed\n\tGatewayProbePeriod = SvcMirrorPrefix + \"/probe-period\"\n\n\t// GatewayProbePath the path at which the health of the gateway should be probed\n\tGatewayProbePath = SvcMirrorPrefix + \"/probe-path\"\n\n\t// GatewayProbeTimeout is the probe request timeout\n\tGatewayProbeTimeout = SvcMirrorPrefix + \"/probe-timeout\"\n\n\t// ConfigKeyName is the key in the secret that stores the kubeconfig needed to connect\n\t// to a remote cluster\n\tConfigKeyName = \"kubeconfig\"\n\n\t// GatewayPortName is the name of the incoming port of the gateway\n\tGatewayPortName = \"mc-gateway\"\n\n\t// ProbePortName is the name of the probe port of the gateway\n\tProbePortName = \"mc-probe\"\n\n\t// TracingNameLabel is a pod label that the trace service name can be populated from\n\tTracingNameLabel = \"app.kubernetes.io/name\"\n\t// TracingInstanceLabel is a pod label that the trace service name can be populated from\n\tTracingInstanceLabel = \"app.kubernetes.io/instance\"\n\t// TracingSemanticConventionPrefix is the prefix for pod annotations that specify custom trace labels\n\tTracingSemanticConventionPrefix = \"resource.opentelemetry.io/\"\n\t// TracingServiceName is the label for the trace service name\n\tTracingServiceName = \"service.name\"\n)\n\n// CreatedByAnnotationValue returns the value associated with\n// CreatedByAnnotation.\nfunc CreatedByAnnotationValue() string {\n\treturn fmt.Sprintf(\"linkerd/cli %s\", version.Version)\n}\n\n// GetServiceAccountAndNS returns the pod's serviceaccount and namespace.\nfunc GetServiceAccountAndNS(pod *corev1.Pod) (sa string, ns string) {\n\tsa = pod.Spec.ServiceAccountName\n\tif sa == \"\" {\n\t\tsa = \"default\"\n\t}\n\n\tns = pod.GetNamespace()\n\tif ns == \"\" {\n\t\tns = \"default\"\n\t}\n\n\treturn\n}\n\n// GetPodLabels returns the set of prometheus owner labels for a given pod\nfunc GetPodLabels(ownerKind, ownerName string, pod *corev1.Pod) map[string]string {\n\tlabels := map[string]string{\"pod\": pod.Name}\n\n\tl5dLabel := KindToL5DLabel(ownerKind)\n\tlabels[l5dLabel] = ownerName\n\n\tlabels[\"serviceaccount\"], _ = GetServiceAccountAndNS(pod)\n\n\tif controllerNS := pod.Labels[ControllerNSLabel]; controllerNS != \"\" {\n\t\tlabels[\"control_plane_ns\"] = controllerNS\n\t}\n\n\tif pth := pod.Labels[appsv1.DefaultDeploymentUniqueLabelKey]; pth != \"\" {\n\t\tlabels[\"pod_template_hash\"] = pth\n\t}\n\n\treturn labels\n}\n\n// GetExternalWorkloadLabels returns the set of prometheus owner labels for a given ExternalWorkload\nfunc GetExternalWorkloadLabels(ownerKind, ownerName string, ew *ewv1beta1.ExternalWorkload) map[string]string {\n\tlabels := map[string]string{\"external_workload\": ew.Name}\n\n\tif ownerKind != \"\" && ownerName != \"\" {\n\t\tlabels[ownerKind] = ownerName\n\t}\n\treturn labels\n}\n\n// IsMeshed returns whether a given Pod is in a given controller's service mesh.\nfunc IsMeshed(pod *corev1.Pod, controllerNS string) bool {\n\treturn pod.Labels[ControllerNSLabel] == controllerNS\n}\n"
  },
  {
    "path": "pkg/k8s/labels_test.go",
    "content": "package k8s\n\nimport (\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestGetPodLabels(t *testing.T) {\n\tt.Run(\"Maps proxy labels to prometheus labels\", func(t *testing.T) {\n\t\tpod := &corev1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"test-pod\",\n\t\t\t\tNamespace: \"test-ns\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\tControllerNSLabel:                      \"linkerd-namespace\",\n\t\t\t\t\tappsv1.DefaultDeploymentUniqueLabelKey: \"test-pth\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: corev1.PodSpec{\n\t\t\t\tServiceAccountName: \"test-sa\",\n\t\t\t},\n\t\t}\n\n\t\townerKind := \"deployment\"\n\t\townerName := \"test-deployment\"\n\n\t\texpectedLabels := map[string]string{\n\t\t\t\"control_plane_ns\":  \"linkerd-namespace\",\n\t\t\t\"deployment\":        \"test-deployment\",\n\t\t\t\"pod\":               \"test-pod\",\n\t\t\t\"pod_template_hash\": \"test-pth\",\n\t\t\t\"serviceaccount\":    \"test-sa\",\n\t\t}\n\n\t\tpodLabels := GetPodLabels(ownerKind, ownerName, pod)\n\n\t\tif diff := deep.Equal(podLabels, expectedLabels); diff != nil {\n\t\t\tt.Errorf(\"labels %+v\", diff)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/k8s/metrics.go",
    "content": "package k8s\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// AdminHTTPPortNameSuffix is the suffix for ports used by admin HTTP servers.\n// Ports may be named <container>-admin to avoid conflicts across multiple\n// containers.\nconst AdminHTTPPortNameSuffix string = \"-admin\"\n\n// GetContainerMetrics returns the metrics exposed by a container on the passed in portName\nfunc GetContainerMetrics(\n\tk8sAPI *KubernetesAPI,\n\tpod corev1.Pod,\n\tcontainer corev1.Container,\n\temitLogs bool,\n\tportName string,\n) ([]byte, error) {\n\tportForward, err := NewContainerMetricsForward(k8sAPI, pod, container, emitLogs, portName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdefer portForward.Stop()\n\tif err = portForward.Init(); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Error running port-forward: %s\\n\", err)\n\t\treturn nil, err\n\t}\n\n\tmetricsURL := portForward.URLFor(\"/metrics\")\n\treturn getResponse(metricsURL)\n}\n\n// getResponse makes a http Get request to the passed url and returns the response/error\nfunc getResponse(url string) ([]byte, error) {\n\t// url has been constructed by k8s.newPortForward and is not passed in by\n\t// the user.\n\t//nolint:gosec\n\tresp, err := http.Get(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\treturn io.ReadAll(resp.Body)\n}\n"
  },
  {
    "path": "pkg/k8s/policy.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"k8s.io/apimachinery/pkg/labels\"\n\n\tpolicyv1 \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tserverv1beta3 \"github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta3\"\n\tserverauthorizationv1beta1 \"github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\n// Authorization holds the names of the resources involved in an authorization.\ntype Authorization struct {\n\tRoute               string\n\tServer              string\n\tServerAuthorization string\n\tAuthorizationPolicy string\n}\n\n// AuthorizationPolicyGVR is the GroupVersionResource for the AuthorizationPolicy resource.\nvar AuthorizationPolicyGVR = policyv1.SchemeGroupVersion.WithResource(\"authorizationpolicies\")\n\n// HTTPRouteGVR is the GroupVersionResource for the HTTPRoute resource.\nvar HTTPRouteGVR = policyv1.SchemeGroupVersion.WithResource(\"httproutes\")\n\n// SazGVR is the GroupVersionResource for the ServerAuthorization resource.\nvar SazGVR = serverauthorizationv1beta1.SchemeGroupVersion.WithResource(\"serverauthorizations\")\n\n// ServerGVR is the GroupVersionResource for the Server resource.\nvar ServerGVR = serverv1beta3.SchemeGroupVersion.WithResource(\"servers\")\n\n// AuthorizationsForResource returns a list of ServerAuthorizations and\n// AuthorizationPolicies which apply to any Server or HttpRoute which select\n// pods belonging to the given resource.\nfunc AuthorizationsForResource(ctx context.Context, k8sAPI *KubernetesAPI, namespace string, resource string) ([]Authorization, error) {\n\tpods, err := getPodsForResourceOrKind(ctx, k8sAPI, namespace, resource, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresults := make([]Authorization, 0)\n\n\tsazs, err := k8sAPI.L5dCrdClient.ServerauthorizationV1beta1().ServerAuthorizations(namespace).List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Failed to get serverauthorization resources: %s\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\tfor _, saz := range sazs.Items {\n\t\tvar servers []serverv1beta3.Server\n\n\t\tif saz.Spec.Server.Name != \"\" {\n\t\t\tserver, err := k8sAPI.L5dCrdClient.ServerV1beta3().Servers(saz.GetNamespace()).Get(ctx, saz.Spec.Server.Name, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"ServerAuthorization/%s targets Server/%s but we failed to get it: %s\\n\", saz.Name, saz.Spec.Server.Name, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tservers = []serverv1beta3.Server{*server}\n\t\t} else if saz.Spec.Server.Selector != nil {\n\t\t\tselector, err := metav1.LabelSelectorAsSelector(saz.Spec.Server.Selector)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"Failed to parse Server selector for ServerAuthorization/%s: %s\\n\", saz.Name, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tserverList, err := k8sAPI.L5dCrdClient.ServerV1beta3().Servers(saz.GetNamespace()).List(ctx, metav1.ListOptions{LabelSelector: selector.String()})\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"Failed to get Servers for ServerAuthorization/%s: %s\\n\", saz.Name, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tservers = serverList.Items\n\t\t}\n\n\t\tfor _, server := range servers {\n\t\t\tif serverIncludesPod(server, pods) {\n\t\t\t\tresults = append(results, Authorization{\n\t\t\t\t\tRoute:               \"\",\n\t\t\t\t\tServer:              server.GetName(),\n\t\t\t\t\tServerAuthorization: saz.GetName(),\n\t\t\t\t\tAuthorizationPolicy: \"\",\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tpolicies, err := k8sAPI.L5dCrdClient.PolicyV1alpha1().AuthorizationPolicies(namespace).List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Failed to get AuthorizationPolicy resources: %s\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\tallServersInNamespace := map[string]*serverv1beta3.ServerList{}\n\n\tfor _, p := range policies.Items {\n\t\ttarget := p.Spec.TargetRef\n\t\tif target.Kind == NamespaceKind && target.Group == K8sCoreAPIGroup {\n\t\t\tserverList, ok := allServersInNamespace[p.Namespace]\n\t\t\tif !ok {\n\t\t\t\tserverList, err = k8sAPI.L5dCrdClient.ServerV1beta3().Servers(p.Namespace).List(ctx, metav1.ListOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"Failed to get Servers for Namespace/%s: %s\\n\", p.Namespace, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tallServersInNamespace[p.Namespace] = serverList\n\t\t\t}\n\n\t\t\tfor _, server := range serverList.Items {\n\t\t\t\tif serverIncludesPod(server, pods) {\n\t\t\t\t\tresults = append(results, Authorization{\n\t\t\t\t\t\tRoute:               \"\",\n\t\t\t\t\t\tServer:              server.GetName(),\n\t\t\t\t\t\tServerAuthorization: \"\",\n\t\t\t\t\t\tAuthorizationPolicy: p.GetName(),\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if target.Kind == ServerKind && target.Group == PolicyAPIGroup {\n\t\t\tserver, err := k8sAPI.L5dCrdClient.ServerV1beta3().Servers(p.Namespace).Get(ctx, string(target.Name), metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"AuthorizationPolicy/%s targets Server/%s but we failed to get it: %s\\n\", p.Name, target.Name, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif serverIncludesPod(*server, pods) {\n\t\t\t\tresults = append(results, Authorization{\n\t\t\t\t\tRoute:               \"\",\n\t\t\t\t\tServer:              server.GetName(),\n\t\t\t\t\tServerAuthorization: \"\",\n\t\t\t\t\tAuthorizationPolicy: p.GetName(),\n\t\t\t\t})\n\t\t\t}\n\t\t} else if target.Kind == HTTPRouteKind && target.Group == PolicyAPIGroup {\n\t\t\troute, err := k8sAPI.L5dCrdClient.PolicyV1alpha1().HTTPRoutes(p.Namespace).Get(ctx, string(target.Name), metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"AuthorizationPolicy/%s targets HTTPRoute/%s but we failed to get it: %s\\n\", p.Name, target.Name, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor _, parent := range route.Spec.ParentRefs {\n\t\t\t\tif parent.Kind != nil && *parent.Kind == ServerKind &&\n\t\t\t\t\tparent.Group != nil && *parent.Group == PolicyAPIGroup {\n\t\t\t\t\tserver, err := k8sAPI.L5dCrdClient.ServerV1beta3().Servers(p.Namespace).Get(ctx, string(parent.Name), metav1.GetOptions{})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Fprintf(os.Stderr, \"HTTPRoute/%s belongs to Server/%s but we failed to get it: %s\\n\", target.Name, parent.Name, err)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif serverIncludesPod(*server, pods) {\n\t\t\t\t\t\tresults = append(results, Authorization{\n\t\t\t\t\t\t\tRoute:               route.GetName(),\n\t\t\t\t\t\t\tServer:              server.GetName(),\n\t\t\t\t\t\t\tServerAuthorization: \"\",\n\t\t\t\t\t\t\tAuthorizationPolicy: p.GetName(),\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\treturn results, nil\n}\n\n// ServersForResource returns a list of Server names of Servers which select pods\n// belonging to the given resource.\nfunc ServersForResource(ctx context.Context, k8sAPI *KubernetesAPI, namespace string, resource string, labelSelector string) ([]string, error) {\n\tpods, err := getPodsForResourceOrKind(ctx, k8sAPI, namespace, resource, labelSelector)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresults := make([]string, 0)\n\n\tservers, err := k8sAPI.L5dCrdClient.ServerV1beta3().Servers(namespace).List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Failed to get serverauthorization resources: %s\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\tfor _, server := range servers.Items {\n\t\tif serverIncludesPod(server, pods) {\n\t\t\tresults = append(results, server.GetName())\n\t\t}\n\t}\n\treturn results, nil\n}\n\n// ServerAuthorizationsForServer returns a list of ServerAuthorization names of\n// ServerAuthorizations which select the given Server.\nfunc ServerAuthorizationsForServer(ctx context.Context, k8sAPI *KubernetesAPI, namespace string, server string) ([]string, error) {\n\tresults := make([]string, 0)\n\n\tsazs, err := k8sAPI.L5dCrdClient.ServerauthorizationV1beta1().ServerAuthorizations(namespace).List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Failed to get serverauthorization resources: %s\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\tfor _, saz := range sazs.Items {\n\t\tif saz.Spec.Server.Name != \"\" {\n\t\t\ts, err := k8sAPI.DynamicClient.Resource(ServerGVR).Namespace(saz.GetNamespace()).Get(ctx, saz.Spec.Server.Name, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"Failed to get server %s: %s\\n\", saz.Spec.Server.Name, err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tif s.GetName() == server {\n\t\t\t\tresults = append(results, saz.GetName())\n\t\t\t}\n\t\t} else if saz.Spec.Server.Selector != nil {\n\t\t\tselector, err := metav1.LabelSelectorAsSelector(saz.Spec.Server.Selector)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"Failed to get servers: %s\\n\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tserverList, err := k8sAPI.L5dCrdClient.ServerV1beta3().Servers(saz.GetNamespace()).List(ctx, metav1.ListOptions{LabelSelector: selector.String()})\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"Failed to get servers: %s\\n\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tfor _, s := range serverList.Items {\n\t\t\t\tif s.GetName() == server {\n\t\t\t\t\tresults = append(results, saz.GetName())\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn results, nil\n}\n\n// serverIncludesPod returns true the given server selects any of the given pods\n// and that pod uses the server's port.\nfunc serverIncludesPod(server serverv1beta3.Server, pods []corev1.Pod) bool {\n\tif server.Spec.PodSelector == nil {\n\t\treturn false\n\t}\n\n\tselector, err := metav1.LabelSelectorAsSelector(server.Spec.PodSelector)\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Failed to parse PodSelector of Server/%s: %s\\n\", server.Name, err)\n\t\treturn false\n\t}\n\n\tfor _, pod := range pods {\n\t\tif selector.Matches(labels.Set(pod.Labels)) {\n\t\t\tfor _, container := range append(pod.Spec.InitContainers, pod.Spec.Containers...) {\n\t\t\t\tfor _, p := range container.Ports {\n\t\t\t\t\tif server.Spec.Port.IntVal == p.ContainerPort || server.Spec.Port.StrVal == p.Name {\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n// getPodsForResourceOrKind is similar to getPodsForResource, but also supports\n// querying for all resources of a given kind (i.e. when resource name is unspecified).\nfunc getPodsForResourceOrKind(ctx context.Context, k8sAPI kubernetes.Interface, namespace string, resource string, labelSelector string) ([]corev1.Pod, error) {\n\n\telems := strings.Split(resource, \"/\")\n\tif len(elems) > 2 {\n\t\treturn nil, fmt.Errorf(\"invalid resource: %s\", resource)\n\t}\n\tif len(elems) == 2 {\n\t\tpods, err := GetPodsFor(ctx, k8sAPI, namespace, resource)\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"failed to get pods: %s\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\treturn pods, nil\n\t}\n\tpods := []corev1.Pod{}\n\n\ttyp, err := CanonicalResourceNameFromFriendlyName(elems[0])\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid resource: %s\", resource)\n\t}\n\n\tselector := metav1.ListOptions{\n\t\tLabelSelector: labelSelector,\n\t}\n\n\tswitch typ {\n\tcase Pod:\n\t\tps, err := k8sAPI.CoreV1().Pods(namespace).List(ctx, selector)\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"failed to get pods: %s\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tpods = append(pods, ps.Items...)\n\n\tcase CronJob:\n\t\tjobs, err := k8sAPI.BatchV1().CronJobs(namespace).List(ctx, selector)\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"failed to get cronjobs: %s\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tfor _, job := range jobs.Items {\n\t\t\tps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf(\"%s/%s\", CronJob, job.Name))\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"failed to get pods: %s\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tpods = append(pods, ps...)\n\t\t}\n\n\tcase DaemonSet:\n\t\tdss, err := k8sAPI.AppsV1().DaemonSets(namespace).List(ctx, selector)\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"failed to get demonsets: %s\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tfor _, ds := range dss.Items {\n\t\t\tps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf(\"%s/%s\", DaemonSet, ds.Name))\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"failed to get pods: %s\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tpods = append(pods, ps...)\n\t\t}\n\n\tcase Deployment:\n\t\tdeploys, err := k8sAPI.AppsV1().Deployments(namespace).List(ctx, selector)\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"failed to get deployments: %s\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tfor _, deploy := range deploys.Items {\n\t\t\tps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf(\"%s/%s\", Deployment, deploy.Name))\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"failed to get pods: %s\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tpods = append(pods, ps...)\n\t\t}\n\n\tcase Job:\n\t\tjobs, err := k8sAPI.BatchV1().Jobs(namespace).List(ctx, selector)\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"failed to get jobs: %s\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tfor _, job := range jobs.Items {\n\t\t\tps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf(\"%s/%s\", Job, job.Name))\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"failed to get pods: %s\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tpods = append(pods, ps...)\n\t\t}\n\n\tcase ReplicaSet:\n\t\trss, err := k8sAPI.AppsV1().ReplicaSets(namespace).List(ctx, selector)\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"failed to get replicasets: %s\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tfor _, rs := range rss.Items {\n\t\t\tps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf(\"%s/%s\", ReplicaSet, rs.Name))\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"failed to get pods: %s\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tpods = append(pods, ps...)\n\t\t}\n\n\tcase ReplicationController:\n\t\trcs, err := k8sAPI.CoreV1().ReplicationControllers(namespace).List(ctx, selector)\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"failed to get replicationcontrollers: %s\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tfor _, rc := range rcs.Items {\n\t\t\tps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf(\"%s/%s\", ReplicationController, rc.Name))\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"failed to get pods: %s\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tpods = append(pods, ps...)\n\t\t}\n\n\tcase StatefulSet:\n\t\tsss, err := k8sAPI.AppsV1().StatefulSets(namespace).List(ctx, selector)\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"failed to get statefulsets: %s\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tfor _, ss := range sss.Items {\n\t\t\tps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf(\"%s/%s\", StatefulSet, ss.Name))\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"failed to get pods: %s\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tpods = append(pods, ps...)\n\t\t}\n\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported resource type: %s\", typ)\n\t}\n\treturn pods, nil\n}\n"
  },
  {
    "path": "pkg/k8s/portforward.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"strconv\"\n\n\tlog \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\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// Load all the auth plugins for the cloud providers.\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n)\n\n// PortForward provides a port-forward connection into a Kubernetes cluster.\ntype PortForward struct {\n\tmethod     string\n\turl        *url.URL\n\thost       string\n\tnamespace  string\n\tpodName    string\n\tlocalPort  int\n\tremotePort int\n\temitLogs   bool\n\tstopCh     chan struct{}\n\treadyCh    chan struct{}\n\tconfig     *rest.Config\n}\n\n// NewContainerMetricsForward returns an instance of the PortForward struct that can\n// be used to establish a port-forward connection to a containers metrics\n// endpoint, specified by namespace, pod, container and portName.\nfunc NewContainerMetricsForward(\n\tk8sAPI *KubernetesAPI,\n\tpod corev1.Pod,\n\tcontainer corev1.Container,\n\temitLogs bool,\n\tportName string,\n) (*PortForward, error) {\n\tvar port corev1.ContainerPort\n\tfor _, p := range container.Ports {\n\t\tif p.Name == portName {\n\t\t\tport = p\n\t\t\tbreak\n\t\t}\n\t}\n\tif port.Name != portName {\n\t\treturn nil, fmt.Errorf(\"no %s port found for container %s/%s\", portName, pod.GetName(), container.Name)\n\t}\n\n\treturn NewPodPortForward(k8sAPI, pod.GetNamespace(), pod.GetName(), \"localhost\", 0, int(port.ContainerPort), emitLogs)\n}\n\n// NewPortForward returns an instance of the PortForward struct that can be used\n// to establish a port-forward connection to a pod in the deployment that's\n// specified by namespace and deployName. If localPort is 0, it will use a\n// random ephemeral port.\n// Note that the connection remains open for the life of the process, as this\n// function is typically called by the CLI. Care should be taken if called from\n// control plane code.\nfunc NewPortForward(\n\tctx context.Context,\n\tk8sAPI *KubernetesAPI,\n\tnamespace, deployName string,\n\thost string, localPort, remotePort int,\n\temitLogs bool,\n) (*PortForward, error) {\n\ttimeoutSeconds := int64(30)\n\tpodList, err := k8sAPI.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{TimeoutSeconds: &timeoutSeconds})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpodName := \"\"\n\tfor _, pod := range podList.Items {\n\t\tif pod.Status.Phase == corev1.PodRunning {\n\t\t\tgrandparent, err := getDeploymentForPod(ctx, k8sAPI, pod)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warnf(\"Failed to get deploy for pod [%s]: %s\", pod.Name, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif grandparent == deployName {\n\t\t\t\tpodName = pod.Name\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif podName == \"\" {\n\t\treturn nil, fmt.Errorf(\"no running pods found for %s\", deployName)\n\t}\n\n\treturn NewPodPortForward(k8sAPI, namespace, podName, host, localPort, remotePort, emitLogs)\n}\n\nfunc getDeploymentForPod(ctx context.Context, k8sAPI *KubernetesAPI, pod corev1.Pod) (string, error) {\n\tparents := pod.GetOwnerReferences()\n\tif len(parents) != 1 {\n\t\treturn \"\", nil\n\t}\n\trs, err := k8sAPI.AppsV1().ReplicaSets(pod.Namespace).Get(ctx, parents[0].Name, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tgrandparents := rs.GetOwnerReferences()\n\tif len(grandparents) != 1 {\n\t\treturn \"\", nil\n\t}\n\treturn grandparents[0].Name, nil\n}\n\n// NewPodPortForward returns an instance of the PortForward struct that can be\n// used to establish a port-forward connection to a specific Pod.\nfunc NewPodPortForward(\n\tk8sAPI *KubernetesAPI,\n\tnamespace, podName string,\n\thost string, localPort, remotePort int,\n\temitLogs bool,\n) (*PortForward, error) {\n\n\trestClient := k8sAPI.CoreV1().RESTClient()\n\t// This early return is for testing purposes. If the k8sAPI is a fake\n\t// client, attempting to create a request will result in a nil-pointer\n\t// panic. Instead, we return with no port-forward and no error.\n\tif fakeRest, ok := restClient.(*rest.RESTClient); ok {\n\t\tif fakeRest == nil {\n\t\t\treturn nil, nil\n\t\t}\n\t}\n\n\treq := restClient.Post().\n\t\tResource(\"pods\").\n\t\tNamespace(namespace).\n\t\tName(podName).\n\t\tSubResource(\"portforward\")\n\n\tvar err error\n\tif localPort == 0 {\n\t\tif host != \"localhost\" {\n\t\t\treturn nil, fmt.Errorf(\"local port must be specified when host is not localhost\")\n\t\t}\n\n\t\tlocalPort, err = getEphemeralPort()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn &PortForward{\n\t\tmethod:     \"POST\",\n\t\turl:        req.URL(),\n\t\thost:       host,\n\t\tnamespace:  namespace,\n\t\tpodName:    podName,\n\t\tlocalPort:  localPort,\n\t\tremotePort: remotePort,\n\t\temitLogs:   emitLogs,\n\t\tstopCh:     make(chan struct{}, 1),\n\t\treadyCh:    make(chan struct{}),\n\t\tconfig:     k8sAPI.Config,\n\t}, nil\n}\n\n// run creates and runs the port-forward connection.\n// When the connection is established it blocks until Stop() is called.\nfunc (pf *PortForward) run() error {\n\ttransport, upgrader, err := spdy.RoundTripperFor(pf.config)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tout := io.Discard\n\terrOut := io.Discard\n\tif pf.emitLogs {\n\t\tout = os.Stdout\n\t\terrOut = os.Stderr\n\t}\n\n\tports := []string{fmt.Sprintf(\"%d:%d\", pf.localPort, pf.remotePort)}\n\tdialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, pf.method, pf.url)\n\n\tfw, err := portforward.NewOnAddresses(dialer, []string{pf.host}, ports, pf.stopCh, pf.readyCh, out, errOut)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = fw.ForwardPorts()\n\tif err != nil {\n\t\terr = fmt.Errorf(\"%w for %s/%s\", err, pf.namespace, pf.podName)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// Init creates and runs a port-forward connection.\n// This function blocks until the connection is established, in which case it returns nil.\n// It's the caller's responsibility to call Stop() to finish the connection.\nfunc (pf *PortForward) Init() error {\n\tlog.Debugf(\"Starting port forward to %s %d:%d\", pf.url, pf.localPort, pf.remotePort)\n\n\tfailure := make(chan error, 1)\n\n\tgo func() {\n\t\tif err := pf.run(); err != nil {\n\t\t\tfailure <- err\n\t\t}\n\t}()\n\n\t// The `select` statement below depends on one of two outcomes from `pf.run()`:\n\t// 1) Succeed and block, causing a receive on `<-pf.readyCh`\n\t// 2) Return an err, causing a receive `<-failure`\n\tselect {\n\tcase <-pf.readyCh:\n\t\tlog.Debug(\"Port forward initialised\")\n\tcase err := <-failure:\n\t\tlog.Debugf(\"Port forward failed: %v\", err)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Stop terminates the port-forward connection.\n// It is the caller's responsibility to call Stop even in case of errors\nfunc (pf *PortForward) Stop() {\n\tclose(pf.stopCh)\n}\n\n// GetStop returns the stopCh.\n// Receiving on stopCh will block until the port forwarding stops.\nfunc (pf *PortForward) GetStop() <-chan struct{} {\n\treturn pf.stopCh\n}\n\n// URLFor returns the URL for the port-forward connection.\nfunc (pf *PortForward) URLFor(path string) string {\n\tstrPort := strconv.Itoa(pf.localPort)\n\turlAddress := net.JoinHostPort(pf.host, strPort)\n\treturn fmt.Sprintf(\"http://%s%s\", urlAddress, path)\n}\n\n// AddressAndPort returns the address and port for the port-forward connection.\nfunc (pf *PortForward) AddressAndPort() string {\n\tstrPort := strconv.Itoa(pf.localPort)\n\treturn net.JoinHostPort(pf.host, strPort)\n}\n\n// getEphemeralPort selects a port for the port-forwarding. It binds to a free\n// ephemeral port and returns the port number.\nfunc getEphemeralPort() (int, error) {\n\tln, err := net.Listen(\"tcp\", \"localhost:0\")\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer ln.Close()\n\t// get port\n\ttcpAddr, ok := ln.Addr().(*net.TCPAddr)\n\tif !ok {\n\t\treturn 0, fmt.Errorf(\"invalid listen address: %s\", ln.Addr())\n\t}\n\n\treturn tcpAddr.Port, nil\n}\n"
  },
  {
    "path": "pkg/k8s/portforward_test.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestNewContainerMetricsForward(t *testing.T) {\n\t// TODO: test successful cases by mocking out `clientset.CoreV1().RESTClient()`\n\ttests := []struct {\n\t\tns         string\n\t\tname       string\n\t\tk8sConfigs []string\n\t\terr        error\n\t}{\n\t\t{\n\t\t\t\"pod-ns\",\n\t\t\t\"pod-name\",\n\t\t\t[]string{`apiVersion: v1\nkind: Pod\nmetadata:\n  name: pod-name\n  namespace: pod-ns\nstatus:\n  phase: Running\nspec:\n  containers:\n  - name: linkerd-proxy\n    ports:\n    - name: bad-port\n      port: 123`,\n\t\t\t},\n\t\t\terrors.New(\"no linkerd-admin port found for container pod-name/linkerd-proxy\"),\n\t\t},\n\t}\n\n\tfor i, test := range tests {\n\t\ttest := test // pin\n\t\tt.Run(fmt.Sprintf(\"%d: NewContainerMetricsForward returns expected result\", i), func(t *testing.T) {\n\t\t\tk8sClient, err := NewFakeAPI(test.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error %s\", err)\n\t\t\t}\n\t\t\tpod, err := k8sClient.CoreV1().Pods(test.ns).Get(context.Background(), test.name, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error %s\", err)\n\t\t\t}\n\t\t\tvar container corev1.Container\n\t\t\tfor _, c := range pod.Spec.Containers {\n\t\t\t\tcontainer = c\n\t\t\t\tbreak\n\t\t\t}\n\t\t\t_, err = NewContainerMetricsForward(&KubernetesAPI{Interface: k8sClient}, *pod, container, false, ProxyAdminPortName)\n\t\t\tif err != nil || test.err != nil {\n\t\t\t\tif (err == nil && test.err != nil) ||\n\t\t\t\t\t(err != nil && test.err == nil) ||\n\t\t\t\t\t(err.Error() != test.err.Error()) {\n\t\t\t\t\tt.Fatalf(\"Unexpected error (Expected: %s, Got: %s)\", test.err, err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNewPortForward(t *testing.T) {\n\ttests := []struct {\n\t\tdescription string\n\t\tns          string\n\t\tdeployName  string\n\t\tk8sConfigs  []string\n\t\terr         error\n\t}{\n\t\t{\n\t\t\t\"Pod is owned by the specified deployment\",\n\t\t\t\"ns\",\n\t\t\t\"deploy\",\n\t\t\t[]string{`apiVersion: v1\nkind: Pod\nmetadata:\n  name: pod\n  namespace: ns\n  uid: pod\n  labels:\n    app: foo\n  ownerReferences:\n  - apiVersion: apps/v1\n    controller: true\n    kind: Deployment\n    name: rs\n    uid: rs\nstatus:\n  phase: Running`,\n\t\t\t\t`apiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  name: rs\n  namespace: ns\n  uid: rs\n  labels:\n    app: foo\n  ownerReferences:\n  - apiVersion: apps/v1\n    controller: true\n    kind: Deployment\n    name: deploy\n    uid: deploy\n  spec:\n    selector:\n      matchLabels:\n        app: foo\n`,\n\t\t\t\t`apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: deploy\n  namespace: ns\n  uid: deploy\nspec:\n  selector:\n    matchLabels:\n      app: foo\n`},\n\t\t\tnil,\n\t\t},\n\t\t// In the case of overlapping deployments, a pod may match the label\n\t\t// selector of more than one deployment but will still be owned by\n\t\t// exactly one.\n\t\t{\n\t\t\t\"Pod's labels match, but is not owned by the deployment\",\n\t\t\t\"ns\",\n\t\t\t\"deploy\",\n\t\t\t[]string{`apiVersion: v1\nkind: Pod\nmetadata:\n  name: pod\n  namespace: ns\n  uid: pod\n  labels:\n    app: foo\n  ownerReferences:\n  - apiVersion: apps/v1\n    controller: true\n    kind: ReplicaSet\n    name: rs\n    uid: SOME-OTHER-UID\n  status:\n    phase: Running`,\n\t\t\t\t`apiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  name: rs\n  namespace: ns\n  uid: rs\n  labels:\n    app: foo\n  ownerReferences:\n  - apiVersion: apps/v1\n    controller: true\n    kind: Deployment\n    name: deploy\n    uid: deploy\n  spec:\n    selector:\n      matchLabels:\n        app: foo\n`,\n\t\t\t\t`apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: deploy\n  namespace: ns\n  uid: deploy\nspec:\n  selector:\n    matchLabels:\n      app: foo\n`},\n\t\t\terrors.New(\"no running pods found for deploy\"),\n\t\t},\n\t\t{\n\t\t\t\"Pod is owned by the specified deployment but is not running\",\n\t\t\t\"ns\",\n\t\t\t\"deploy\",\n\t\t\t[]string{`apiVersion: v1\nkind: Pod\nmetadata:\n  name: pod\n  namespace: ns\n  uid: pod\n  labels:\n    app: foo\n  ownerReferences:\n  - apiVersion: apps/v1\n    controller: true\n    kind: ReplicaSet\n    name: rs\n    uid: rs\n  status:\n    phase: Stopped`,\n\t\t\t\t`apiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  name: rs\n  namespace: ns\n  uid: rs\n  labels:\n    app: foo\n  ownerReferences:\n  - apiVersion: apps/v1\n    controller: true\n    kind: Deployment\n    name: deploy\n    uid: deploy\nspec:\n  selector:\n    matchLabels:\n      app: foo\n`,\n\t\t\t\t`apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: deploy\n  namespace: ns\n  uid: deploy\nspec:\n  selector:\n    matchLabels:\n      app: foo\n`},\n\t\t\terrors.New(\"no running pods found for deploy\"),\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\ttest := test // pin\n\t\tt.Run(test.description, func(t *testing.T) {\n\t\t\tk8sClient, err := NewFakeAPI(test.k8sConfigs...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error %s\", err)\n\t\t\t}\n\t\t\t_, err = NewPortForward(context.Background(), &KubernetesAPI{Interface: k8sClient}, test.ns, test.deployName, \"localhost\", 0, 0, false)\n\t\t\tif err != nil || test.err != nil {\n\t\t\t\tif (err == nil && test.err != nil) ||\n\t\t\t\t\t(err != nil && test.err == nil) ||\n\t\t\t\t\t(err.Error() != test.err.Error()) {\n\t\t\t\t\tt.Fatalf(\"Unexpected error (Expected: %s, Got: %s)\", test.err, err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/k8s/resource/resource.go",
    "content": "package resource\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\n\tlink \"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha1\"\n\tpolicy \"github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1\"\n\tprofile \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tlog \"github.com/sirupsen/logrus\"\n\tadmissionRegistration \"k8s.io/api/admissionregistration/v1\"\n\tapps \"k8s.io/api/apps/v1\"\n\tbatch \"k8s.io/api/batch/v1\"\n\tcore \"k8s.io/api/core/v1\"\n\tk8sPolicy \"k8s.io/api/policy/v1\"\n\trbac \"k8s.io/api/rbac/v1\"\n\tapiextension \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tapiRegistration \"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1\"\n\tapiregistrationv1client \"k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nconst (\n\tyamlSep = \"---\\n\"\n)\n\n// Kubernetes is a parent object used to generalize all k8s types\ntype Kubernetes struct {\n\truntime.TypeMeta\n\tmetav1.ObjectMeta `json:\"metadata\"`\n}\n\nvar prunableNamespaceResources []schema.GroupVersionResource = []schema.GroupVersionResource{\n\tcore.SchemeGroupVersion.WithResource(\"configmaps\"),\n\tbatch.SchemeGroupVersion.WithResource(\"cronjobs\"),\n\tapps.SchemeGroupVersion.WithResource(\"daemonsets\"),\n\tapps.SchemeGroupVersion.WithResource(\"deployments\"),\n\tbatch.SchemeGroupVersion.WithResource(\"jobs\"),\n\tpolicy.SchemeGroupVersion.WithResource(\"meshtlsauthentications\"),\n\tpolicy.SchemeGroupVersion.WithResource(\"networkauthentications\"),\n\tcore.SchemeGroupVersion.WithResource(\"replicationcontrollers\"),\n\tcore.SchemeGroupVersion.WithResource(\"secrets\"),\n\tcore.SchemeGroupVersion.WithResource(\"services\"),\n\tprofile.SchemeGroupVersion.WithResource(\"serviceprofiles\"),\n\tapps.SchemeGroupVersion.WithResource(\"statefulsets\"),\n\trbac.SchemeGroupVersion.WithResource(\"roles\"),\n\trbac.SchemeGroupVersion.WithResource(\"rolebindings\"),\n\tcore.SchemeGroupVersion.WithResource(\"serviceaccounts\"),\n\tk8sPolicy.SchemeGroupVersion.WithResource(\"poddisruptionbudgets\"),\n\tk8s.ServerGVR,\n\tk8s.SazGVR,\n\tk8s.AuthorizationPolicyGVR,\n\tlink.SchemeGroupVersion.WithResource(\"links\"),\n\tk8s.HTTPRouteGVR,\n}\n\nvar prunableClusterResources []schema.GroupVersionResource = []schema.GroupVersionResource{\n\trbac.SchemeGroupVersion.WithResource(\"clusterroles\"),\n\trbac.SchemeGroupVersion.WithResource(\"clusterrolebindings\"),\n\tapiRegistration.SchemeGroupVersion.WithResource(\"apiservices\"),\n\tadmissionRegistration.SchemeGroupVersion.WithResource(\"mutatingwebhookconfigurations\"),\n\tadmissionRegistration.SchemeGroupVersion.WithResource(\"validatingwebhookconfigurations\"),\n\tapiextension.SchemeGroupVersion.WithResource(\"customresourcedefinitions\"),\n}\n\n// New returns a kubernetes resource with the given data\nfunc New(apiVersion, kind, name string) Kubernetes {\n\treturn Kubernetes{\n\t\truntime.TypeMeta{\n\t\t\tAPIVersion: apiVersion,\n\t\t\tKind:       kind,\n\t\t},\n\t\tmetav1.ObjectMeta{\n\t\t\tName: name,\n\t\t},\n\t}\n}\n\n// NewNamespaced returns a namespace scoped kubernetes resource with the given data\nfunc NewNamespaced(apiVersion, kind, name, namespace string) Kubernetes {\n\treturn Kubernetes{\n\t\truntime.TypeMeta{\n\t\t\tAPIVersion: apiVersion,\n\t\t\tKind:       kind,\n\t\t},\n\t\tmetav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: namespace,\n\t\t},\n\t}\n}\n\n// RenderResource renders a kubernetes object as a yaml object\nfunc (r Kubernetes) RenderResource(w io.Writer) error {\n\tb, err := yaml.Marshal(r)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = w.Write(b)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = w.Write([]byte(yamlSep))\n\treturn err\n}\n\n// RenderResource renders a kubernetes object as a json object\nfunc (r Kubernetes) RenderResourceJSON(w io.Writer) error {\n\tb, err := json.Marshal(r)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = w.Write(b)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = w.Write([]byte(\"\\n\"))\n\treturn err\n}\n\n// FetchKubernetesResources returns a slice of all cluster scoped kubernetes\n// resources which match the given ListOptions.\nfunc FetchKubernetesResources(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {\n\n\tresources := make([]Kubernetes, 0)\n\n\tclusterRoles, err := fetchClusterRoles(ctx, k, options)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not fetch ClusterRole resources: %w\", err)\n\t}\n\tresources = append(resources, clusterRoles...)\n\n\tclusterRoleBindings, err := fetchClusterRoleBindings(ctx, k, options)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not fetch ClusterRoleBinding resources: %w\", err)\n\t}\n\tresources = append(resources, clusterRoleBindings...)\n\n\troles, err := fetchRoles(ctx, k, options)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not fetch Roles: %w\", err)\n\t}\n\tresources = append(resources, roles...)\n\n\troleBindings, err := fetchRoleBindings(ctx, k, options)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not fetch RoleBindings: %w\", err)\n\t}\n\tresources = append(resources, roleBindings...)\n\n\tcrds, err := fetchCustomResourceDefinitions(ctx, k, options)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not fetch CustomResourceDefinition resources: %w\", err)\n\t}\n\tresources = append(resources, crds...)\n\n\tapiCRDs, err := fetchAPIRegistrationResources(ctx, k, options)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not fetch APIService CRDs: %w\", err)\n\t}\n\tresources = append(resources, apiCRDs...)\n\n\tmutatinghooks, err := fetchMutatingWebhooksConfiguration(ctx, k, options)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not fetch MutatingWebhookConfigurations: %w\", err)\n\t}\n\tresources = append(resources, mutatinghooks...)\n\n\tvalidationhooks, err := fetchValidatingWebhooksConfiguration(ctx, k, options)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not fetch ValidatingWebhookConfiguration: %w\", err)\n\t}\n\tresources = append(resources, validationhooks...)\n\n\tnamespaces, err := fetchNamespace(ctx, k, options)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not fetch Namespace: %w\", err)\n\t}\n\tresources = append(resources, namespaces...)\n\n\treturn resources, nil\n}\n\nfunc fetchClusterRoles(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {\n\tlist, err := k.RbacV1().ClusterRoles().List(ctx, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresources := make([]Kubernetes, len(list.Items))\n\tfor i, item := range list.Items {\n\t\tresources[i] = New(rbac.SchemeGroupVersion.String(), \"ClusterRole\", item.Name)\n\t}\n\n\treturn resources, nil\n}\n\nfunc fetchClusterRoleBindings(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {\n\tlist, err := k.RbacV1().ClusterRoleBindings().List(ctx, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresources := make([]Kubernetes, len(list.Items))\n\tfor i, item := range list.Items {\n\t\tresources[i] = New(rbac.SchemeGroupVersion.String(), \"ClusterRoleBinding\", item.Name)\n\t}\n\n\treturn resources, nil\n}\n\nfunc fetchRoles(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {\n\tlist, err := k.RbacV1().Roles(\"\").List(ctx, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresources := make([]Kubernetes, len(list.Items))\n\tfor i, item := range list.Items {\n\t\tr := New(rbac.SchemeGroupVersion.String(), \"Role\", item.Name)\n\t\tr.Namespace = item.Namespace\n\t\tresources[i] = r\n\t}\n\treturn resources, nil\n}\n\nfunc fetchRoleBindings(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {\n\tlist, err := k.RbacV1().RoleBindings(\"\").List(ctx, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresources := make([]Kubernetes, len(list.Items))\n\tfor i, item := range list.Items {\n\t\tr := New(rbac.SchemeGroupVersion.String(), \"RoleBinding\", item.Name)\n\t\tr.Namespace = item.Namespace\n\t\tresources[i] = r\n\t}\n\treturn resources, nil\n}\n\nfunc fetchCustomResourceDefinitions(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {\n\tlist, err := k.Apiextensions.ApiextensionsV1().CustomResourceDefinitions().List(ctx, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresources := make([]Kubernetes, len(list.Items))\n\tfor i, item := range list.Items {\n\t\tresources[i] = New(apiextension.SchemeGroupVersion.String(), \"CustomResourceDefinition\", item.Name)\n\t}\n\n\treturn resources, nil\n}\n\nfunc fetchNamespace(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {\n\tlist, err := k.CoreV1().Namespaces().List(ctx, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresources := make([]Kubernetes, len(list.Items))\n\tfor i, item := range list.Items {\n\t\tr := New(core.SchemeGroupVersion.String(), \"Namespace\", item.Name)\n\t\tr.Namespace = item.Namespace\n\t\tresources[i] = r\n\t}\n\treturn resources, nil\n}\n\nfunc fetchValidatingWebhooksConfiguration(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {\n\tlist, err := k.AdmissionregistrationV1().ValidatingWebhookConfigurations().List(ctx, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresources := make([]Kubernetes, len(list.Items))\n\tfor i, item := range list.Items {\n\t\tresources[i] = New(admissionRegistration.SchemeGroupVersion.String(), \"ValidatingWebhookConfiguration\", item.Name)\n\t}\n\n\treturn resources, nil\n}\n\nfunc fetchMutatingWebhooksConfiguration(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {\n\tlist, err := k.AdmissionregistrationV1().MutatingWebhookConfigurations().List(ctx, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresources := make([]Kubernetes, len(list.Items))\n\tfor i, item := range list.Items {\n\t\tresources[i] = New(admissionRegistration.SchemeGroupVersion.String(), \"MutatingWebhookConfiguration\", item.Name)\n\t}\n\n\treturn resources, nil\n}\nfunc fetchAPIRegistrationResources(ctx context.Context, k *k8s.KubernetesAPI, options metav1.ListOptions) ([]Kubernetes, error) {\n\tapiClient, err := apiregistrationv1client.NewForConfig(k.Config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlist, err := apiClient.APIServices().List(ctx, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresources := make([]Kubernetes, len(list.Items))\n\tfor i, item := range list.Items {\n\t\tresources[i] = New(apiRegistration.SchemeGroupVersion.String(), \"APIService\", item.Name)\n\t}\n\n\treturn resources, nil\n}\n\nfunc FetchPrunableResources(ctx context.Context, k *k8s.KubernetesAPI, namespace string, options metav1.ListOptions) ([]Kubernetes, error) {\n\tresources := []Kubernetes{}\n\n\tfor _, gvr := range prunableNamespaceResources {\n\t\titems, err := k.DynamicClient.Resource(gvr).Namespace(namespace).List(ctx, options)\n\t\tif err != nil {\n\t\t\tif !kerrors.IsNotFound(err) {\n\t\t\t\tlog.Debugf(\"failed to list resources of type %s\", gvr)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tfor _, item := range items.Items {\n\t\t\tresources = append(resources, NewNamespaced(item.GetAPIVersion(), item.GetKind(), item.GetName(), item.GetNamespace()))\n\t\t}\n\t}\n\n\tfor _, gvr := range prunableClusterResources {\n\t\titems, err := k.DynamicClient.Resource(gvr).List(ctx, options)\n\t\tif err != nil {\n\t\t\tlog.Debugf(\"failed to list resources of type %s\", gvr)\n\t\t\tcontinue\n\t\t}\n\t\tfor _, item := range items.Items {\n\t\t\tresources = append(resources, New(item.GetAPIVersion(), item.GetKind(), item.GetName()))\n\t\t}\n\t}\n\treturn resources, nil\n}\n"
  },
  {
    "path": "pkg/k8s/resource/resource_test.go",
    "content": "package resource\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestRenderRBACResource(t *testing.T) {\n\t// Given\n\t// RBAC object in the cluster\n\tk8sCfg := []string{\n\t\t`apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  annotations:\n    kubectl.kubernetes.io/last-applied-configuration: |\n      {\"apiVersion\":\"rbac.authorization.k8s.io/v1\",\"kind\":\"ClusterRoleBinding\",\"metadata\":{\"annotations\":{},\"labels\":{\"linkerd.io/control-plane-component\":\"web\",\"linkerd.io/control-plane-ns\":\"linkerd\"},\"name\":\"linkerd-linkerd-web-admin\"},\"roleRef\":{\"apiGroup\":\"rbac.authorization.k8s.io\",\"kind\":\"ClusterRole\",\"name\":\"linkerd-linkerd-tap-admin\"},\"subjects\":[{\"kind\":\"ServiceAccount\",\"name\":\"linkerd-web\",\"namespace\":\"linkerd\"}]}\n  creationTimestamp: \"2020-03-28T20:33:00Z\"\n  labels:\n    linkerd.io/control-plane-component: web\n    linkerd.io/control-plane-ns: linkerd\n  name: linkerd-linkerd-web-admin\n  resourceVersion: \"5512995\"`,\n\t}\n\t// A clientset to get the resource from the cluster\n\tfakeK8sAPI, err := k8s.NewFakeAPI(k8sCfg...)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error creating fake k8s clientset:%v\", err)\n\t}\n\n\t// When we fetch the resources using our fake client\n\tresources, err := fetchClusterRoleBindings(context.Background(), fakeK8sAPI, metav1.ListOptions{LabelSelector: k8s.ControllerNSLabel})\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error fetching resources from mock client:%v\", err)\n\t}\n\n\t// Then\n\texpResources := 1\n\tif len(resources) != expResources {\n\t\tt.Errorf(\"mismatch in resource slice size: expected %d and got %d\", expResources, len(resources))\n\t}\n\n\trbacResource := resources[0]\n\texpKind := \"ClusterRoleBinding\"\n\tif rbacResource.Kind != expKind {\n\t\tt.Errorf(\"mismatch in resource kind: expected %s and got %s\", expKind, rbacResource.Kind)\n\t}\n\n\texpVersion := \"rbac.authorization.k8s.io/v1\"\n\tif rbacResource.APIVersion != expVersion {\n\t\tt.Errorf(\"mismatch in resource apiVersion: expected %s and got %s\", expVersion, rbacResource.APIVersion)\n\t}\n\n\texpName := \"linkerd-linkerd-web-admin\"\n\tif rbacResource.Name != expName {\n\t\tt.Errorf(\"mismatch in resource name: expected %s and got %s\", expName, rbacResource.Name)\n\t}\n}\n"
  },
  {
    "path": "pkg/k8s/testdata/config.test",
    "content": "apiVersion: v1\nclusters:\n- cluster:\n    certificate-authority-data: cXVlIHBhcmFkYSBhdHJhc2FkYQ==\n    server: https://55.197.171.239\n  name: cluster1\n- cluster:\n    certificate-authority-data: cXVlIHBhcmFkYSBhdHJhc2FkYQ==\n    server: https://30.88.172.234\n  name: cluster2\n- cluster:\n    certificate-authority-data: cXVlIHBhcmFkYSBhdHJhc2FkYQ==\n    server: https://13.184.231.31\n  name: cluster3\n- cluster:\n    certificate-authority-data: cXVlIHBhcmFkYSBhdHJhc2FkYQ==\n    server: https://162.128.50.10\n  name: cluster4\n- cluster:\n    certificate-authority-data: cXVlIHBhcmFkYSBhdHJhc2FkYQ==\n    server: https://162.128.50.11/\n  name: clusterTrailingSlash\n- cluster:\n    certificate-authority-data: cXVlIHBhcmFkYSBhdHJhc2FkYQ==\n    server: https://162.128.50.12/k8s/clusters/c-fhjws\n  name: clusterWithPath\ncontexts:\n- context:\n    cluster: cluster3\n    namespace: bobo-lab\n    user: cluster3\n  name: dev\n- context:\n    cluster: cluster1\n    user: cluster1\n  name: cluster1\n- context:\n    cluster: cluster2\n    user: cluster2\n  name: cluster2\n- context:\n    cluster: cluster3\n    user: cluster3\n  name: cluster3\n- context:\n    cluster: cluster4\n    user: cluster4\n  name: cluster4\n- context:\n    cluster: clusterTrailingSlash\n    user: clusterTrailingSlash\n  name: clusterTrailingSlash\n- context:\n    cluster: clusterWithPath\n    user: clusterWithPath\n  name: clusterWithPath\ncurrent-context: cluster1\nkind: Config\npreferences: {}\nusers:\n- name: cluster1\n  user:\n    auth-provider:\n      config:\n        access-token: 4cc3sspassatempo\n        cmd-args: config config-helper --format=json\n        cmd-path: /Users/bobojones/bin/google-cloud-sdk/bin/gcloud\n        expiry: 2017-10-11T06:30:02Z\n        expiry-key: '{.credential.token_expiry}'\n        token-key: '{.credential.access_token}'\n      name: gcp\n- name: cluster2\n  user:\n    auth-provider:\n      config:\n        access-token: 4cc3sspassatempo\n        cmd-args: config config-helper --format=json\n        cmd-path: /Users/bobojones/bin/google-cloud-sdk/bin/gcloud\n        expiry: 2017-12-14 06:30:02\n        expiry-key: '{.credential.token_expiry}'\n        token-key: '{.credential.access_token}'\n      name: gcp\n- name: cluster3\n  user:\n    auth-provider:\n      config:\n        access-token: 4cc3sspassatempo\n        cmd-args: config config-helper --format=json\n        cmd-path: /Users/bobojones/bin/google-cloud-sdk/bin/gcloud\n        expiry: 2017-10-17 18:40:01\n        expiry-key: '{.credential.token_expiry}'\n        token-key: '{.credential.access_token}'\n      name: gcp\n- name: cluster4\n  user:\n    auth-provider:\n      config:\n        access-token: 4cc3sspassatempoq\n        cmd-args: config config-helper --format=json\n        cmd-path: /Users/bobojones/bin/google-cloud-sdk/bin/gcloud\n        expiry: 2017-11-22 22:13:05\n        expiry-key: '{.credential.token_expiry}'\n        token-key: '{.credential.access_token}'\n      name: gcp\n- name: clusterTrailingSlash\n  user:\n    auth-provider:\n      config:\n        access-token: 4cc3sspassatempoq\n        cmd-args: config config-helper --format=json\n        cmd-path: /Users/bobojones/bin/google-cloud-sdk/bin/gcloud\n        expiry: 2017-11-22 22:13:05\n        expiry-key: '{.credential.token_expiry}'\n        token-key: '{.credential.access_token}'\n      name: gcp\n- name: clusterWithPath\n  user:\n    auth-provider:\n      config:\n        access-token: 4cc3sspassatempoq\n        cmd-args: config config-helper --format=json\n        cmd-path: /Users/bobojones/bin/google-cloud-sdk/bin/gcloud\n        expiry: 2017-11-22 22:13:05\n        expiry-key: '{.credential.token_expiry}'\n        token-key: '{.credential.access_token}'\n      name: gcp\n"
  },
  {
    "path": "pkg/k8s/testdata/kubectl_cluster-info.output",
    "content": "Kubernetes master is running at https://192.168.99.10:8443\n\nTo further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\n"
  },
  {
    "path": "pkg/k8s/testdata/kubectl_config.output",
    "content": "Modify kubeconfig files using subcommands like \"kubectl config set current-context my-context\"\n\nThe loading order follows these rules:\n\n  1. If the --kubeconfig flag is set, then only that file is loaded.  The flag may only be set once and no merging takes\nplace.\n  2. If $KUBECONFIG environment variable is set, then it is used a list of paths (normal path delimitting rules for your\nsystem).  These paths are merged.  When a value is modified, it is modified in the file that defines the stanza.  When a\nvalue is created, it is created in the first file that exists.  If no files in the chain exist, then it creates the last\nfile in the list.\n  3. Otherwise, ${HOME}/.kube/config is used and no merging takes place.\n\nAvailable Commands:\n  current-context Displays the current-context\n  delete-cluster  Delete the specified cluster from the kubeconfig\n  delete-context  Delete the specified context from the kubeconfig\n  get-clusters    Display clusters defined in the kubeconfig\n  get-contexts    Describe one or many contexts\n  rename-context  Renames a context from the kubeconfig file.\n  set             Sets an individual value in a kubeconfig file\n  set-cluster     Sets a cluster entry in kubeconfig\n  set-context     Sets a context entry in kubeconfig\n  set-credentials Sets a user entry in kubeconfig\n  unset           Unsets an individual value in a kubeconfig file\n  use-context     Sets the current-context in a kubeconfig file\n  view            Display merged kubeconfig settings or a specified kubeconfig file\n\nUsage:\n  kubectl config SUBCOMMAND [options]\n\nUse \"kubectl <command> --help\" for more information about a given command.\nUse \"kubectl options\" for a list of global command-line options (applies to all commands).\n"
  },
  {
    "path": "pkg/k8s/version.go",
    "content": "package k8s\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nvar revisionSeparator = regexp.MustCompile(\"[^0-9.]\")\n\nfunc getK8sVersion(versionString string) ([3]int, error) {\n\tvar version [3]int\n\tjustTheVersionString := strings.TrimPrefix(versionString, \"v\")\n\tjustTheMajorMinorRevisionNumbers := revisionSeparator.Split(justTheVersionString, -1)[0]\n\tsplit := strings.Split(justTheMajorMinorRevisionNumbers, \".\")\n\n\tif len(split) < 3 {\n\t\treturn version, fmt.Errorf(\"unknown version string format [%s]\", versionString)\n\t}\n\n\tfor i, segment := range split {\n\t\tv, err := strconv.Atoi(strings.TrimSpace(segment))\n\t\tif err != nil {\n\t\t\treturn version, fmt.Errorf(\"unknown version string format [%s]\", versionString)\n\t\t}\n\t\tversion[i] = v\n\t}\n\n\treturn version, nil\n}\n\nfunc isCompatibleVersion(minimalRequirementVersion [3]int, actualVersion [3]int) bool {\n\tif minimalRequirementVersion[0] < actualVersion[0] {\n\t\treturn true\n\t}\n\n\tif (minimalRequirementVersion[0] == actualVersion[0]) && minimalRequirementVersion[1] < actualVersion[1] {\n\t\treturn true\n\t}\n\n\tif (minimalRequirementVersion[0] == actualVersion[0]) && (minimalRequirementVersion[1] == actualVersion[1]) && (minimalRequirementVersion[2] <= actualVersion[2]) {\n\t\treturn true\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "pkg/k8s/version_test.go",
    "content": "package k8s\n\nimport (\n\t\"testing\"\n)\n\nfunc TestGetK8sVersion(t *testing.T) {\n\tt.Run(\"Correctly parses a Version string\", func(t *testing.T) {\n\t\tversions := map[string][3]int{\n\t\t\t\"v1.8.4\":               {1, 8, 4},\n\t\t\t\"v2.7.1\":               {2, 7, 1},\n\t\t\t\"v2.0.1\":               {2, 0, 1},\n\t\t\t\"v1.9.0-beta.2\":        {1, 9, 0},\n\t\t\t\"v1.7.9+7f63532e4ff4f\": {1, 7, 9},\n\t\t}\n\n\t\tfor k, expectedVersion := range versions {\n\t\t\tactualVersion, err := getK8sVersion(k)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Error parsing string: %v\", err)\n\t\t\t}\n\n\t\t\tif actualVersion != expectedVersion {\n\t\t\t\tt.Fatalf(\"Expecting %s to be parsed into %v but got %v\", k, expectedVersion, actualVersion)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Returns error if Version string looks broken\", func(t *testing.T) {\n\t\tversions := []string{\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t\t\"1.8.\",\n\t\t\t\"1.9-beta.2\",\n\t\t\t\"v1.7+7f63532e4ff4f\",\n\t\t\t\"Client Version: v1.8.4\",\n\t\t\t\"Version.Info{Major:\\\"1\\\", Minor:\\\"8\\\", GitVersion:\\\"v1.8.4\\\", GitCommit:\\\"9befc2b8928a9426501d3bf62f72849d5cbcd5a3\\\", GitTreeState:\\\"clean\\\", BuildDate:\\\"2017-11-20T05:28:34Z\\\", GoVersion:\\\"go1.8.3\\\", Compiler:\\\"gc\\\", Platform:\\\"darwin/amd64\\\"}\",\n\t\t}\n\n\t\tfor _, invalidVersion := range versions {\n\t\t\t_, err := getK8sVersion(invalidVersion)\n\n\t\t\tif err == nil {\n\t\t\t\tt.Fatalf(\"Expected error parsing string: %s\", invalidVersion)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestIsCompatibleVersion(t *testing.T) {\n\tt.Run(\"Success when compatible versions\", func(t *testing.T) {\n\t\tcompatibleVersions := map[[3]int][3]int{\n\t\t\t{1, 8, 4}: {1, 8, 4},\n\t\t\t{1, 9, 2}: {1, 9, 4},\n\t\t\t{1, 1, 1}: {1, 1, 1},\n\t\t\t{1, 1, 1}: {2, 1, 2},\n\t\t\t{1, 1, 1}: {1, 2, 1},\n\t\t\t{1, 1, 1}: {1, 1, 2},\n\t\t\t{1, 1, 1}: {100, 1, 2},\n\t\t}\n\n\t\tfor e, a := range compatibleVersions {\n\t\t\tif !isCompatibleVersion(e, a) {\n\t\t\t\tt.Fatalf(\"Expected required version [%v] to be compatible with [%v] but it wasn't\", e, a)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Fail when incompatible versions\", func(t *testing.T) {\n\t\tinCompatibleVersions := map[[3]int][3]int{\n\t\t\t{1, 8, 4}:    {1, 7, 1},\n\t\t\t{1, 9, 2}:    {1, 9, 0},\n\t\t\t{10, 10, 10}: {9, 10, 10},\n\t\t\t{10, 10, 10}: {10, 9, 10},\n\t\t\t{10, 10, 10}: {10, 10, 9},\n\t\t\t{10, 10, 10}: {0, 10, 9},\n\t\t}\n\t\tfor e, a := range inCompatibleVersions {\n\t\t\tif isCompatibleVersion(e, a) {\n\t\t\t\tt.Fatalf(\"Expected required version [%v] to  NOT be compatible with [%v] but it was'\", e, a)\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/profiles/openapi.go",
    "content": "package profiles\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/go-openapi/spec\"\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nconst (\n\txLinkerdRetryable = \"x-linkerd-retryable\"\n\txLinkerdTimeout   = \"x-linkerd-timeout\"\n)\n\n// RenderOpenAPI reads an OpenAPI spec file and renders the corresponding\n// ServiceProfile to a buffer, given a namespace, service, and control plane\n// namespace.\nfunc RenderOpenAPI(fileName, namespace, name, clusterDomain string) (*sp.ServiceProfile, error) {\n\n\tinput, err := readFile(fileName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbytes, err := io.ReadAll(input)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Error reading file: %w\", err)\n\t}\n\tjson, err := yaml.YAMLToJSON(bytes)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Error parsing yaml: %w\", err)\n\t}\n\n\tswagger := spec.Swagger{}\n\terr = swagger.UnmarshalJSON(json)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Error parsing OpenAPI spec: %w\", err)\n\t}\n\n\tprofile := swaggerToServiceProfile(swagger, namespace, name, clusterDomain)\n\n\treturn &profile, nil\n}\n\nfunc swaggerToServiceProfile(swagger spec.Swagger, namespace, name, clusterDomain string) sp.ServiceProfile {\n\tprofile := sp.ServiceProfile{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      fmt.Sprintf(\"%s.%s.svc.%s\", name, namespace, clusterDomain),\n\t\t\tNamespace: namespace,\n\t\t},\n\t\tTypeMeta: ServiceProfileMeta,\n\t}\n\n\troutes := make([]*sp.RouteSpec, 0)\n\n\tpaths := make([]string, 0)\n\tif swagger.Paths != nil {\n\t\tfor path := range swagger.Paths.Paths {\n\t\t\tpaths = append(paths, path)\n\t\t}\n\t\tsort.Strings(paths)\n\t}\n\n\tbase := strings.TrimRight(swagger.BasePath, \"/\")\n\tfor _, relPath := range paths {\n\t\titem := swagger.Paths.Paths[relPath]\n\t\tpath := base + \"/\" + strings.TrimLeft(relPath, \"/\")\n\t\tpathRegex := PathToRegex(path)\n\t\tif item.Delete != nil {\n\t\t\tspec := MkRouteSpec(path, pathRegex, http.MethodDelete, item.Delete)\n\t\t\troutes = append(routes, spec)\n\t\t}\n\t\tif item.Get != nil {\n\t\t\tspec := MkRouteSpec(path, pathRegex, http.MethodGet, item.Get)\n\t\t\troutes = append(routes, spec)\n\t\t}\n\t\tif item.Head != nil {\n\t\t\tspec := MkRouteSpec(path, pathRegex, http.MethodHead, item.Head)\n\t\t\troutes = append(routes, spec)\n\t\t}\n\t\tif item.Options != nil {\n\t\t\tspec := MkRouteSpec(path, pathRegex, http.MethodOptions, item.Options)\n\t\t\troutes = append(routes, spec)\n\t\t}\n\t\tif item.Patch != nil {\n\t\t\tspec := MkRouteSpec(path, pathRegex, http.MethodPatch, item.Patch)\n\t\t\troutes = append(routes, spec)\n\t\t}\n\t\tif item.Post != nil {\n\t\t\tspec := MkRouteSpec(path, pathRegex, http.MethodPost, item.Post)\n\t\t\troutes = append(routes, spec)\n\t\t}\n\t\tif item.Put != nil {\n\t\t\tspec := MkRouteSpec(path, pathRegex, http.MethodPut, item.Put)\n\t\t\troutes = append(routes, spec)\n\t\t}\n\t}\n\n\tprofile.Spec.Routes = routes\n\treturn profile\n}\n\n// MkRouteSpec makes a service profile route from an OpenAPI operation.\nfunc MkRouteSpec(path, pathRegex string, method string, operation *spec.Operation) *sp.RouteSpec {\n\tretryable := false\n\ttimeout := \"\"\n\tvar responses *spec.Responses\n\tif operation != nil {\n\t\tretryable, _ = operation.VendorExtensible.Extensions.GetBool(xLinkerdRetryable)\n\t\ttimeout, _ = operation.VendorExtensible.Extensions.GetString(xLinkerdTimeout)\n\t\tresponses = operation.Responses\n\t}\n\treturn &sp.RouteSpec{\n\t\tName:            fmt.Sprintf(\"%s %s\", method, path),\n\t\tCondition:       toReqMatch(pathRegex, method),\n\t\tResponseClasses: toRspClasses(responses),\n\t\tIsRetryable:     retryable,\n\t\tTimeout:         timeout,\n\t}\n}\n\nfunc toReqMatch(path string, method string) *sp.RequestMatch {\n\treturn &sp.RequestMatch{\n\t\tPathRegex: path,\n\t\tMethod:    method,\n\t}\n}\n\nfunc toRspClasses(responses *spec.Responses) []*sp.ResponseClass {\n\tif responses == nil {\n\t\treturn nil\n\t}\n\tclasses := make([]*sp.ResponseClass, 0)\n\n\tstatuses := make([]int, 0)\n\tfor status := range responses.StatusCodeResponses {\n\t\tstatuses = append(statuses, status)\n\t}\n\tsort.Ints(statuses)\n\n\tfor _, status := range statuses {\n\t\tcond := &sp.ResponseMatch{\n\t\t\tStatus: &sp.Range{\n\t\t\t\tMin: uint32(status),\n\t\t\t\tMax: uint32(status),\n\t\t\t},\n\t\t}\n\t\tclasses = append(classes, &sp.ResponseClass{\n\t\t\tCondition: cond,\n\t\t\tIsFailure: status >= 500,\n\t\t})\n\t}\n\treturn classes\n}\n"
  },
  {
    "path": "pkg/profiles/openapi_test.go",
    "content": "package profiles\n\nimport (\n\t\"testing\"\n\n\t\"github.com/go-openapi/spec\"\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestSwaggerToServiceProfile(t *testing.T) {\n\tnamespace := \"myns\"\n\tname := \"mysvc\"\n\tclusterDomain := \"mycluster.local\"\n\n\tswagger := spec.Swagger{\n\t\tSwaggerProps: spec.SwaggerProps{\n\t\t\tPaths: &spec.Paths{\n\t\t\t\tPaths: map[string]spec.PathItem{\n\t\t\t\t\t\"/authors/{id}\": {\n\t\t\t\t\t\tPathItemProps: spec.PathItemProps{\n\t\t\t\t\t\t\tPost: &spec.Operation{\n\t\t\t\t\t\t\t\tOperationProps: spec.OperationProps{\n\t\t\t\t\t\t\t\t\tResponses: &spec.Responses{\n\t\t\t\t\t\t\t\t\t\tResponsesProps: spec.ResponsesProps{\n\t\t\t\t\t\t\t\t\t\t\tStatusCodeResponses: map[int]spec.Response{\n\t\t\t\t\t\t\t\t\t\t\t\t500: {},\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\tVendorExtensible: spec.VendorExtensible{\n\t\t\t\t\t\t\t\t\tExtensions: spec.Extensions{xLinkerdRetryable: true, xLinkerdTimeout: \"60s\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"/path/with/trailing/slash/\": {\n\t\t\t\t\t\tPathItemProps: spec.PathItemProps{\n\t\t\t\t\t\t\tGet: &spec.Operation{},\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\texpectedServiceProfile := sp.ServiceProfile{\n\t\tTypeMeta: ServiceProfileMeta,\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name + \".\" + namespace + \".svc.\" + clusterDomain,\n\t\t\tNamespace: namespace,\n\t\t},\n\t\tSpec: sp.ServiceProfileSpec{\n\t\t\tRoutes: []*sp.RouteSpec{\n\t\t\t\t{\n\t\t\t\t\tName: \"POST /authors/{id}\",\n\t\t\t\t\tCondition: &sp.RequestMatch{\n\t\t\t\t\t\tPathRegex: \"/authors/[^/]*\",\n\t\t\t\t\t\tMethod:    \"POST\",\n\t\t\t\t\t},\n\t\t\t\t\tResponseClasses: []*sp.ResponseClass{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tCondition: &sp.ResponseMatch{\n\t\t\t\t\t\t\t\tStatus: &sp.Range{\n\t\t\t\t\t\t\t\t\tMin: 500,\n\t\t\t\t\t\t\t\t\tMax: 500,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tIsFailure: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tIsRetryable: true,\n\t\t\t\t\tTimeout:     \"60s\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"GET /path/with/trailing/slash/\",\n\t\t\t\t\tCondition: &sp.RequestMatch{\n\t\t\t\t\t\tPathRegex: \"/path/with/trailing/slash/\",\n\t\t\t\t\t\tMethod:    \"GET\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tactualServiceProfile := swaggerToServiceProfile(swagger, namespace, name, clusterDomain)\n\n\terr := ServiceProfileYamlEquals(actualServiceProfile, expectedServiceProfile)\n\tif err != nil {\n\t\tt.Fatalf(\"ServiceProfiles are not equal: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "pkg/profiles/profiles.go",
    "content": "package profiles\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"text/template\"\n\t\"time\"\n\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\" // TODO: pkg/profiles should not depend on controller/gen\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n\tyamlDecoder \"k8s.io/apimachinery/pkg/util/yaml\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nvar pathParamRegex = regexp.MustCompile(`\\\\{[^\\}]*\\\\}`)\n\ntype profileTemplateConfig struct {\n\tServiceNamespace string\n\tServiceName      string\n\tClusterDomain    string\n}\n\nvar (\n\t// ServiceProfileMeta is the TypeMeta for the ServiceProfile custom resource.\n\tServiceProfileMeta = metav1.TypeMeta{\n\t\tAPIVersion: k8s.ServiceProfileAPIVersion,\n\t\tKind:       k8s.ServiceProfileKind,\n\t}\n\n\tminStatus uint32 = 100\n\tmaxStatus uint32 = 599\n\n\terrRequestMatchField  = errors.New(\"A request match must have a field set\")\n\terrResponseMatchField = errors.New(\"A response match must have a field set\")\n)\n\n// Validate validates the structure of a ServiceProfile. This code is a superset\n// of the validation provided by the `openAPIV3Schema`, defined in the\n// ServiceProfile CRD.\n// openAPIV3Schema validates:\n// - types of non-recursive fields\n// - presence of required fields\n// This function validates:\n// - types of all fields\n// - presence of required fields\n// - presence of unknown fields\n// - recursive fields\nfunc Validate(data []byte) error {\n\tvar serviceProfile sp.ServiceProfile\n\terr := yaml.UnmarshalStrict(data, &serviceProfile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to validate ServiceProfile: %w\", err)\n\t}\n\n\terrs := validation.IsDNS1123Subdomain(serviceProfile.Name)\n\tif len(errs) > 0 {\n\t\treturn fmt.Errorf(\"ServiceProfile %q has invalid name: %s\", serviceProfile.Name, errs[0])\n\t}\n\n\tfor _, route := range serviceProfile.Spec.Routes {\n\t\tif route.Name == \"\" {\n\t\t\treturn fmt.Errorf(\"ServiceProfile %q has a route with no name\", serviceProfile.Name)\n\t\t}\n\t\tif route.Timeout != \"\" {\n\t\t\t_, err := time.ParseDuration(route.Timeout)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"ServiceProfile %q has a route with an invalid timeout: %w\", serviceProfile.Name, err)\n\t\t\t}\n\t\t}\n\t\tif route.Condition == nil {\n\t\t\treturn fmt.Errorf(\"ServiceProfile %q has a route with no condition\", serviceProfile.Name)\n\t\t}\n\t\terr := ValidateRequestMatch(route.Condition)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"ServiceProfile %q has a route with an invalid condition: %w\", serviceProfile.Name, err)\n\t\t}\n\t\tfor _, rc := range route.ResponseClasses {\n\t\t\tif rc.Condition == nil {\n\t\t\t\treturn fmt.Errorf(\"ServiceProfile %q has a response class with no condition\", serviceProfile.Name)\n\t\t\t}\n\t\t\terr = ValidateResponseMatch(rc.Condition)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"ServiceProfile %q has a response class with an invalid condition: %w\", serviceProfile.Name, err)\n\t\t\t}\n\t\t}\n\t}\n\n\trb := serviceProfile.Spec.RetryBudget\n\tif rb != nil {\n\t\tif rb.RetryRatio < 0 {\n\t\t\treturn fmt.Errorf(\"ServiceProfile %q RetryBudget RetryRatio must be non-negative: %f\", serviceProfile.Name, rb.RetryRatio)\n\t\t}\n\n\t\tif rb.TTL == \"\" {\n\t\t\treturn fmt.Errorf(\"ServiceProfile %q RetryBudget missing TTL field\", serviceProfile.Name)\n\t\t}\n\n\t\t_, err := time.ParseDuration(rb.TTL)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"ServiceProfile %q RetryBudget: %w\", serviceProfile.Name, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ValidateRequestMatch validates whether a ServiceProfile RequestMatch has at\n// least one field set.\nfunc ValidateRequestMatch(reqMatch *sp.RequestMatch) error {\n\tmatchKindSet := false\n\tif reqMatch.All != nil {\n\t\tmatchKindSet = true\n\t\tfor _, child := range reqMatch.All {\n\t\t\terr := ValidateRequestMatch(child)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tif reqMatch.Any != nil {\n\t\tmatchKindSet = true\n\t\tfor _, child := range reqMatch.Any {\n\t\t\terr := ValidateRequestMatch(child)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tif reqMatch.Method != \"\" {\n\t\tmatchKindSet = true\n\t}\n\tif reqMatch.Not != nil {\n\t\tmatchKindSet = true\n\t\terr := ValidateRequestMatch(reqMatch.Not)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif reqMatch.PathRegex != \"\" {\n\t\tmatchKindSet = true\n\t}\n\n\tif !matchKindSet {\n\t\treturn errRequestMatchField\n\t}\n\n\treturn nil\n}\n\n// ValidateResponseMatch validates whether a ServiceProfile ResponseMatch has at\n// least one field set, and sanity checks the Status Range.\nfunc ValidateResponseMatch(rspMatch *sp.ResponseMatch) error {\n\tmatchKindSet := false\n\tif rspMatch.All != nil {\n\t\tmatchKindSet = true\n\t\tfor _, child := range rspMatch.All {\n\t\t\terr := ValidateResponseMatch(child)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tif rspMatch.Any != nil {\n\t\tmatchKindSet = true\n\t\tfor _, child := range rspMatch.Any {\n\t\t\terr := ValidateResponseMatch(child)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tif rspMatch.Status != nil {\n\t\tif rspMatch.Status.Min != 0 && (rspMatch.Status.Min < minStatus || rspMatch.Status.Min > maxStatus) {\n\t\t\treturn fmt.Errorf(\"Range minimum must be between %d and %d, inclusive\", minStatus, maxStatus)\n\t\t} else if rspMatch.Status.Max != 0 && (rspMatch.Status.Max < minStatus || rspMatch.Status.Max > maxStatus) {\n\t\t\treturn fmt.Errorf(\"Range maximum must be between %d and %d, inclusive\", minStatus, maxStatus)\n\t\t} else if rspMatch.Status.Max != 0 && rspMatch.Status.Min != 0 && rspMatch.Status.Max < rspMatch.Status.Min {\n\t\t\treturn errors.New(\"Range maximum cannot be smaller than minimum\")\n\t\t}\n\t\tmatchKindSet = true\n\t}\n\tif rspMatch.Not != nil {\n\t\tmatchKindSet = true\n\t\terr := ValidateResponseMatch(rspMatch.Not)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif !matchKindSet {\n\t\treturn errResponseMatchField\n\t}\n\n\treturn nil\n}\n\nfunc buildConfig(namespace, service, clusterDomain string) *profileTemplateConfig {\n\treturn &profileTemplateConfig{\n\t\tServiceNamespace: namespace,\n\t\tServiceName:      service,\n\t\tClusterDomain:    clusterDomain,\n\t}\n}\n\n// RenderProfileTemplate renders a ServiceProfile template to a buffer, given a\n// namespace, service, and control plane namespace.\nfunc RenderProfileTemplate(namespace, service, clusterDomain string, w io.Writer, format string) error {\n\tconfig := buildConfig(namespace, service, clusterDomain)\n\ttemplate, err := template.New(\"profile\").Parse(Template)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuf := &bytes.Buffer{}\n\terr = template.Execute(buf, config)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif format == pkgcmd.JsonOutput {\n\t\tbytes, err := yamlDecoder.ToJSON(buf.Bytes())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = w.Write(append(bytes, '\\n'))\n\t\treturn err\n\t}\n\tif format == pkgcmd.YamlOutput {\n\t\t_, err = w.Write(buf.Bytes())\n\t\treturn err\n\t}\n\n\treturn fmt.Errorf(\"unknown output format: %s\", format)\n}\n\nfunc readFile(fileName string) (io.Reader, error) {\n\tif fileName == \"-\" {\n\t\treturn os.Stdin, nil\n\t}\n\treturn os.Open(filepath.Clean(fileName))\n}\n\n// PathToRegex converts a path into a regex.\nfunc PathToRegex(path string) string {\n\tescaped := regexp.QuoteMeta(path)\n\treturn pathParamRegex.ReplaceAllLiteralString(escaped, \"[^/]*\")\n}\n"
  },
  {
    "path": "pkg/profiles/profiles_fuzzer.go",
    "content": "package profiles\n\nimport (\n\t\"os\"\n\n\tfuzz \"github.com/AdaLogics/go-fuzz-headers\"\n)\n\n// FuzzProfilesValidate fuzzes the ProfilesValidate function.\nfunc FuzzProfilesValidate(data []byte) int {\n\t_ = Validate(data)\n\treturn 1\n}\n\n// FuzzRenderProto fuzzes the RenderProto function.\nfunc FuzzRenderProto(data []byte) int {\n\tf := fuzz.NewConsumer(data)\n\tprotodata, err := f.GetBytes()\n\tif err != nil {\n\t\treturn 0\n\t}\n\tnamespace, err := f.GetString()\n\tif err != nil {\n\t\treturn 0\n\t}\n\tname, err := f.GetString()\n\tif err != nil {\n\t\treturn 0\n\t}\n\tclusterDomain, err := f.GetString()\n\tif err != nil {\n\t\treturn 0\n\t}\n\tprotofile, err := os.Create(\"protofile\")\n\tif err != nil {\n\t\treturn 0\n\t}\n\tdefer protofile.Close()\n\tdefer os.Remove(protofile.Name())\n\n\t_, err = protofile.Write(protodata)\n\tif err != nil {\n\t\treturn 0\n\t}\n\t_, err = RenderProto(protofile.Name(), namespace, name, clusterDomain)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn 1\n}\n"
  },
  {
    "path": "pkg/profiles/profiles_test.go",
    "content": "package profiles\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n)\n\ntype spExp struct {\n\terr error\n\tsp  string\n}\n\nfunc TestValidate(t *testing.T) {\n\texpectations := []spExp{\n\t\t{\n\t\t\terr: nil,\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  routes:\n  - name: name-1\n    condition:\n      method: GET\n      pathRegex: /route-1`,\n\t\t},\n\t\t{\n\t\t\terr: nil,\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  retryBudget:\n    minRetriesPerSecond: 5\n    retryRatio: 0.2\n    ttl: 10ms\n  routes:\n  - name: name-1\n    condition:\n      method: GET\n      pathRegex: /route-1\n      any:\n      - all:\n        - method: POST\n        - pathRegex: '/authors/\\d+'\n      - all:\n        - not:\n            method: DELETE\n        - pathRegex: /info.txt\n    responseClasses:\n    - condition:\n        status:\n          min: 500\n          max: 599\n        all:\n        - status:\n            min: 500\n            max: 599\n        - not:\n            status:\n              min: 503`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"ServiceProfile \\\"^.^\\\" has invalid name: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: ^.^\n  namespace: linkerd-ns\nspec:\n  routes:\n  - name: name-1\n    condition:\n      method: GET\n      pathRegex: /route-1`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: unknown field \\\"foo\\\"\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  foo: bar\n  routes:\n  - name: name-1\n    condition:\n      method: GET\n      pathRegex: /route-1`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: unknown field \\\"foo\\\"\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  routes:\n  - name: name-1\n    foo: bar\n    condition:\n      method: GET\n      pathRegex: /route-1`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: unknown field \\\"foo\\\"\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  routes:\n  - name: name-1\n    condition:\n      foo: bar\n      method: GET\n      pathRegex: /route-1`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"ServiceProfile \\\"name.ns.svc.cluster.local\\\" has a route with no condition\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  routes:\n  - name: name-1`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"ServiceProfile \\\"name.ns.svc.cluster.local\\\" has a route with no name\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  routes:\n  - condition:\n      method: GET\n      pathRegex: /route-1`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: unknown field \\\"foo\\\"\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  routes:\n  - name: name-1\n    condition:\n      foo: bar\n      method: GET\n      pathRegex: /route-1\n      not:\n        method: GET`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"ServiceProfile \\\"name.ns.svc.cluster.local\\\" has a route with no condition\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  routes:\n  - name: name-1\n    condition:`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"ServiceProfile \\\"name.ns.svc.cluster.local\\\" has a route with an invalid condition: A request match must have a field set\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  routes:\n  - name: name-1\n    condition:\n      method:`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"ServiceProfile \\\"name.ns.svc.cluster.local\\\" has a response class with no condition\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  routes:\n  - name: name-1\n    condition:\n      method: GET\n      pathRegex: /route-1\n    responseClasses:\n    - condition:`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"ServiceProfile \\\"name.ns.svc.cluster.local\\\" has a response class with an invalid condition: A response match must have a field set\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  routes:\n  - name: name-1\n    condition:\n      method: GET\n      pathRegex: /route-1\n    responseClasses:\n    - condition:\n        status:\n          min: 500\n          max: 599\n        all:\n        - status:`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"ServiceProfile \\\"name.ns.svc.cluster.local\\\" has a response class with an invalid condition: Range maximum must be between 100 and 599, inclusive\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  routes:\n  - name: name-1\n    condition:\n      method: GET\n      pathRegex: /route-1\n    responseClasses:\n    - condition:\n        status:\n          min: 500\n          max: 600`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"ServiceProfile \\\"name.ns.svc.cluster.local\\\" has a response class with an invalid condition: Range maximum cannot be smaller than minimum\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  routes:\n  - name: name-1\n    condition:\n      method: GET\n      pathRegex: /route-1\n    responseClasses:\n    - condition:\n        status:\n          min: 500\n          max: 599\n        all:\n        - status:\n            min: 500\n            max: 599\n        - not:\n            status:\n              min: 300\n              max: 200`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"ServiceProfile \\\"name.ns.svc.cluster.local\\\" has a response class with an invalid condition: Range minimum must be between 100 and 599, inclusive\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  routes:\n  - name: name-1\n    condition:\n      method: GET\n      pathRegex: /route-1\n    responseClasses:\n    - condition:\n        status:\n          min: 500\n          max: 599\n        all:\n        - status:\n            min: 500\n            max: 599\n        - not:\n            status:\n              min: 1`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal bool into Go struct field Range.spec.routes.responseClasses.condition.all.not.status.min of type uint32\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  routes:\n  - name: name-1\n    condition:\n      method: GET\n      pathRegex: /route-1\n    responseClasses:\n    - condition:\n        status:\n          min: 500\n          max: 599\n        all:\n        - status:\n            min: 500\n            max: 599\n        - not:\n            status:\n              min: false`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"ServiceProfile \\\"name.ns.svc.cluster.local\\\" RetryBudget missing TTL field\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  retryBudget:\n    minRetriesPerSecond: 5\n    retryRatio: 0.2\n  routes:\n  - name: name-1\n    condition:\n      method: GET\n      pathRegex: /route-1`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"ServiceProfile \\\"name.ns.svc.cluster.local\\\" RetryBudget: time: invalid duration \\\"foo\\\"\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  retryBudget:\n    minRetriesPerSecond: 5\n    retryRatio: 0.2\n    ttl: foo\n  routes:\n  - name: name-1\n    condition:\n      method: GET\n      pathRegex: /route-1`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"ServiceProfile \\\"name.ns.svc.cluster.local\\\" RetryBudget RetryRatio must be non-negative: -0.200000\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  retryBudget:\n    minRetriesPerSecond: 5\n    retryRatio: -0.2\n    ttl: 10s\n  routes:\n  - name: name-1\n    condition:\n      method: GET\n      pathRegex: /route-1`,\n\t\t},\n\t\t{\n\t\t\terr: errors.New(\"failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal number -5 into Go struct field RetryBudget.spec.retryBudget.minRetriesPerSecond of type uint32\"),\n\t\t\tsp: `apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: name.ns.svc.cluster.local\n  namespace: linkerd-ns\nspec:\n  retryBudget:\n    minRetriesPerSecond: -5\n    retryRatio: 0.2\n    ttl: 10s\n  routes:\n  - name: name-1\n    condition:\n      method: GET\n      pathRegex: /route-1`,\n\t\t},\n\t}\n\n\tfor id, exp := range expectations {\n\t\texp := exp // pin\n\t\tt.Run(fmt.Sprintf(\"%d\", id), func(t *testing.T) {\n\t\t\terr := Validate([]byte(exp.sp))\n\t\t\tif err != nil || exp.err != nil {\n\t\t\t\tif (err == nil && exp.err != nil) ||\n\t\t\t\t\t(err != nil && exp.err == nil) ||\n\t\t\t\t\t(err.Error() != exp.err.Error()) {\n\t\t\t\t\tt.Fatalf(\"Unexpected error (Expected: %s, Got: %s)\", exp.err, err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/profiles/proto.go",
    "content": "package profiles\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/emicklei/proto\"\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// RenderProto reads a protobuf definition file and renders the corresponding\n// ServiceProfile to a buffer, given a namespace, service, and control plane\n// namespace.\nfunc RenderProto(fileName, namespace, name, clusterDomain string) (*sp.ServiceProfile, error) {\n\tinput, err := readFile(fileName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tparser := proto.NewParser(input)\n\n\treturn protoToServiceProfile(parser, namespace, name, clusterDomain)\n}\n\nfunc protoToServiceProfile(parser *proto.Parser, namespace, name, clusterDomain string) (*sp.ServiceProfile, error) {\n\tdefinition, err := parser.Parse()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\troutes := make([]*sp.RouteSpec, 0)\n\tpkg := \"\"\n\n\thandle := func(visitee proto.Visitee) {\n\t\tswitch typed := visitee.(type) {\n\t\tcase *proto.Package:\n\t\t\tpkg = typed.Name\n\t\tcase *proto.RPC:\n\t\t\tif service, ok := typed.Parent.(*proto.Service); ok {\n\t\t\t\tvar path string\n\t\t\t\tswitch pkg {\n\t\t\t\tcase \"\":\n\t\t\t\t\tpath = fmt.Sprintf(\"/%s/%s\", service.Name, typed.Name)\n\t\t\t\tdefault:\n\t\t\t\t\tpath = fmt.Sprintf(\"/%s.%s/%s\", pkg, service.Name, typed.Name)\n\t\t\t\t}\n\n\t\t\t\troute := &sp.RouteSpec{\n\t\t\t\t\tName: typed.Name,\n\t\t\t\t\tCondition: &sp.RequestMatch{\n\t\t\t\t\t\tMethod:    http.MethodPost,\n\t\t\t\t\t\tPathRegex: regexp.QuoteMeta(path),\n\t\t\t\t\t},\n\t\t\t\t\tIsRetryable: isMethodRetryable(typed),\n\t\t\t\t}\n\t\t\t\troutes = append(routes, route)\n\t\t\t}\n\t\t}\n\t}\n\n\tproto.Walk(definition, handle)\n\n\treturn &sp.ServiceProfile{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      fmt.Sprintf(\"%s.%s.svc.%s\", name, namespace, clusterDomain),\n\t\t\tNamespace: namespace,\n\t\t},\n\t\tTypeMeta: ServiceProfileMeta,\n\t\tSpec: sp.ServiceProfileSpec{\n\t\t\tRoutes: routes,\n\t\t},\n\t}, nil\n}\n\nfunc isMethodRetryable(rpc *proto.RPC) bool {\n\tfor _, e := range rpc.Elements {\n\t\toption, ok := e.(*proto.Option)\n\t\tif !ok {\n\t\t\t// Not an option\n\t\t\tcontinue\n\t\t}\n\n\t\t// method options can be wrapped in parentheses so we trim them away\n\t\tif strings.Trim(option.Name, \"()\") != \"idempotency_level\" {\n\t\t\t// Not an idempotency option\n\t\t\tcontinue\n\t\t}\n\n\t\treturn option.Constant.Source == \"IDEMPOTENT\" ||\n\t\t\toption.Constant.Source == \"NO_SIDE_EFFECTS\"\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "pkg/profiles/proto_test.go",
    "content": "package profiles\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/emicklei/proto\"\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestProtoToServiceProfile(t *testing.T) {\n\tvar (\n\t\tnamespace     = \"myns\"\n\t\tname          = \"mysvc\"\n\t\tclusterDomain = \"mycluster.local\"\n\t)\n\n\tt.Run(\"rpc\", func(t *testing.T) {\n\t\tprotobuf := `syntax = \"proto3\";\n\t\n\tpackage emojivoto.v1;\n\t\n\tmessage VoteRequest {\n\t}\n\t\n\tmessage VotingResult {\n\t\tstring Shortcode = 1;\n\t\tint32 Votes = 2;\n\t}\n\t\n\tservice VotingService {\n\t\trpc VotePoop (VoteRequest) returns (VoteResponse);\n\t}`\n\n\t\tparser := proto.NewParser(strings.NewReader(protobuf))\n\n\t\texpectedServiceProfile := sp.ServiceProfile{\n\t\t\tTypeMeta: ServiceProfileMeta,\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      name + \".\" + namespace + \".svc.\" + clusterDomain,\n\t\t\t\tNamespace: namespace,\n\t\t\t},\n\t\t\tSpec: sp.ServiceProfileSpec{\n\t\t\t\tRoutes: []*sp.RouteSpec{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"VotePoop\",\n\t\t\t\t\t\tCondition: &sp.RequestMatch{\n\t\t\t\t\t\t\tPathRegex: `/emojivoto\\.v1\\.VotingService/VotePoop`,\n\t\t\t\t\t\t\tMethod:    \"POST\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tIsRetryable: false,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tactualServiceProfile, err := protoToServiceProfile(parser, namespace, name, clusterDomain)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to create ServiceProfile: %v\", err)\n\t\t}\n\n\t\terr = ServiceProfileYamlEquals(*actualServiceProfile, expectedServiceProfile)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"ServiceProfiles are not equal: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"rpc unknown idempotency level\", func(t *testing.T) {\n\t\tprotobuf := `syntax = \"proto3\";\n\t\n\tpackage emojivoto.v1;\n\t\n\tmessage VoteRequest {\n\t}\n\t\n\tmessage VotingResult {\n\t\tstring Shortcode = 1;\n\t\tint32 Votes = 2;\n\t}\n\t\n\tservice VotingService {\n\t\trpc VotePoop (VoteRequest) returns (VoteResponse){\n\t\t\toption idempotency_level = \"UNKNOWN\";\n\t\t};\n\t}`\n\n\t\tparser := proto.NewParser(strings.NewReader(protobuf))\n\n\t\texpectedServiceProfile := sp.ServiceProfile{\n\t\t\tTypeMeta: ServiceProfileMeta,\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      name + \".\" + namespace + \".svc.\" + clusterDomain,\n\t\t\t\tNamespace: namespace,\n\t\t\t},\n\t\t\tSpec: sp.ServiceProfileSpec{\n\t\t\t\tRoutes: []*sp.RouteSpec{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"VotePoop\",\n\t\t\t\t\t\tCondition: &sp.RequestMatch{\n\t\t\t\t\t\t\tPathRegex: `/emojivoto\\.v1\\.VotingService/VotePoop`,\n\t\t\t\t\t\t\tMethod:    \"POST\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tIsRetryable: false,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tactualServiceProfile, err := protoToServiceProfile(parser, namespace, name, clusterDomain)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to create ServiceProfile: %v\", err)\n\t\t}\n\n\t\terr = ServiceProfileYamlEquals(*actualServiceProfile, expectedServiceProfile)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"ServiceProfiles are not equal: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"rpc idempotent\", func(t *testing.T) {\n\t\tprotobuf := `syntax = \"proto3\";\n\t\n\tpackage emojivoto.v1;\n\t\n\tmessage VoteRequest {\n\t}\n\t\n\tmessage VotingResult {\n\t\tstring Shortcode = 1;\n\t\tint32 Votes = 2;\n\t}\n\t\n\tservice VotingService {\n\t\trpc VotePoop (VoteRequest) returns (VoteResponse){\n\t\t\toption idempotency_level = \"IDEMPOTENT\";\n\t\t};\n\t}`\n\n\t\tparser := proto.NewParser(strings.NewReader(protobuf))\n\n\t\texpectedServiceProfile := sp.ServiceProfile{\n\t\t\tTypeMeta: ServiceProfileMeta,\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      name + \".\" + namespace + \".svc.\" + clusterDomain,\n\t\t\t\tNamespace: namespace,\n\t\t\t},\n\t\t\tSpec: sp.ServiceProfileSpec{\n\t\t\t\tRoutes: []*sp.RouteSpec{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"VotePoop\",\n\t\t\t\t\t\tCondition: &sp.RequestMatch{\n\t\t\t\t\t\t\tPathRegex: `/emojivoto\\.v1\\.VotingService/VotePoop`,\n\t\t\t\t\t\t\tMethod:    \"POST\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tIsRetryable: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tactualServiceProfile, err := protoToServiceProfile(parser, namespace, name, clusterDomain)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to create ServiceProfile: %v\", err)\n\t\t}\n\n\t\terr = ServiceProfileYamlEquals(*actualServiceProfile, expectedServiceProfile)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"ServiceProfiles are not equal: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"rpc no side effects\", func(t *testing.T) {\n\t\tprotobuf := `syntax = \"proto3\";\n\t\n\tpackage emojivoto.v1;\n\t\n\tmessage VoteRequest {\n\t}\n\t\n\tmessage VotingResult {\n\t\tstring Shortcode = 1;\n\t\tint32 Votes = 2;\n\t}\n\t\n\tservice VotingService {\n\t\trpc VotePoop (VoteRequest) returns (VoteResponse){\n\t\t\toption (idempotency_level) = \"NO_SIDE_EFFECTS\";\n\t\t};\n\t}`\n\n\t\tparser := proto.NewParser(strings.NewReader(protobuf))\n\n\t\texpectedServiceProfile := sp.ServiceProfile{\n\t\t\tTypeMeta: ServiceProfileMeta,\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      name + \".\" + namespace + \".svc.\" + clusterDomain,\n\t\t\t\tNamespace: namespace,\n\t\t\t},\n\t\t\tSpec: sp.ServiceProfileSpec{\n\t\t\t\tRoutes: []*sp.RouteSpec{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"VotePoop\",\n\t\t\t\t\t\tCondition: &sp.RequestMatch{\n\t\t\t\t\t\t\tPathRegex: `/emojivoto\\.v1\\.VotingService/VotePoop`,\n\t\t\t\t\t\t\tMethod:    \"POST\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tIsRetryable: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tactualServiceProfile, err := protoToServiceProfile(parser, namespace, name, clusterDomain)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to create ServiceProfile: %v\", err)\n\t\t}\n\n\t\terr = ServiceProfileYamlEquals(*actualServiceProfile, expectedServiceProfile)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"ServiceProfiles are not equal: %v\", err)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/profiles/template.go",
    "content": "package profiles\n\n// Template provides the base template for the `linkerd profile --template` command.\nconst Template = `### ServiceProfile for {{.ServiceName}}.{{.ServiceNamespace}} ###\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: {{.ServiceName}}.{{.ServiceNamespace}}.svc.{{.ClusterDomain}}\n  namespace: {{.ServiceNamespace}}\nspec:\n  # A service profile defines a list of routes.  Linkerd can aggregate metrics\n  # like request volume, latency, and success rate by route.\n  routes:\n  - name: '/authors/{id}'\n\n    # Each route must define a condition.  All requests that match the\n    # condition will be counted as belonging to that route.  If a request\n    # matches more than one route, the first match wins.\n    condition:\n      # The simplest condition is a path regular expression.\n      pathRegex: '/authors/\\d+'\n\n      # This is a condition that checks the request method.\n      method: POST\n\n      # If more than one condition field is set, all of them must be satisfied.\n      # This is equivalent to using the 'all' condition:\n      # all:\n      # - pathRegex: '/authors/\\d+'\n      # - method: POST\n\n      # Conditions can be combined using 'all', 'any', and 'not'.\n      # any:\n      # - all:\n      #   - method: POST\n      #   - pathRegex: '/authors/\\d+'\n      # - all:\n      #   - not:\n      #       method: DELETE\n      #   - pathRegex: /info.txt\n\n    # A route may be marked as retryable.  This indicates that requests to this\n    # route are always safe to retry and will cause the proxy to retry failed\n    # requests on this route whenever possible.\n    # isRetryable: true\n\n    # A route may optionally define a list of response classes which describe\n    # how responses from this route will be classified.\n    responseClasses:\n\n    # Each response class must define a condition.  All responses from this\n    # route that match the condition will be classified as this response class.\n    - condition:\n        # The simplest condition is a HTTP status code range.\n        status:\n          min: 500\n          max: 599\n\n        # Specifying only one of min or max matches just that one status code.\n        # status:\n        #   min: 404 # This matches 404s only.\n\n        # Conditions can be combined using 'all', 'any', and 'not'.\n        # all:\n        # - status:\n        #     min: 500\n        #     max: 599\n        # - not:\n        #     status:\n        #       min: 503\n\n      # The response class defines whether responses should be counted as\n      # successes or failures.\n      isFailure: true\n\n    # A route can define a request timeout.  Any requests to this route that\n    # exceed the timeout will be canceled.  If unspecified, the default timeout\n    # is '10s' (ten seconds).\n    # timeout: 250ms\n\n  # A service profile can also define a retry budget.  This specifies the\n  # maximum total number of retries that should be sent to this service as a\n  # ratio of the original request volume.\n  # retryBudget:\n  #   The retryRatio is the maximum ratio of retries requests to original\n  #   requests.  A retryRatio of 0.2 means that retries may add at most an\n  #   additional 20% to the request load.\n  #   retryRatio: 0.2\n\n  #   This is an allowance of retries per second in addition to those allowed\n  #   by the retryRatio.  This allows retries to be performed, when the request\n  #   rate is very low.\n  #   minRetriesPerSecond: 10\n\n  #   This duration indicates for how long requests should be considered for the\n  #   purposes of calculating the retryRatio.  A higher value considers a larger\n  #   window and therefore allows burstier retries.\n  #   ttl: 10s\n`\n"
  },
  {
    "path": "pkg/profiles/test_helper.go",
    "content": "package profiles\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// GenServiceProfile generates a mock ServiceProfile.\nfunc GenServiceProfile(service, namespace, clusterDomain string) v1alpha2.ServiceProfile {\n\treturn v1alpha2.ServiceProfile{\n\t\tTypeMeta: ServiceProfileMeta,\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      service + \".\" + namespace + \".svc.\" + clusterDomain,\n\t\t\tNamespace: namespace,\n\t\t},\n\t\tSpec: v1alpha2.ServiceProfileSpec{\n\t\t\tRoutes: []*v1alpha2.RouteSpec{\n\t\t\t\t{\n\t\t\t\t\tName: \"/authors/{id}\",\n\t\t\t\t\tCondition: &v1alpha2.RequestMatch{\n\t\t\t\t\t\tPathRegex: \"/authors/\\\\d+\",\n\t\t\t\t\t\tMethod:    \"POST\",\n\t\t\t\t\t},\n\t\t\t\t\tResponseClasses: []*v1alpha2.ResponseClass{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tCondition: &v1alpha2.ResponseMatch{\n\t\t\t\t\t\t\t\tStatus: &v1alpha2.Range{\n\t\t\t\t\t\t\t\t\tMin: 500,\n\t\t\t\t\t\t\t\t\tMax: 599,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tIsFailure: true,\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\n// ServiceProfileYamlEquals validates whether two ServiceProfiles are equal.\nfunc ServiceProfileYamlEquals(actual, expected v1alpha2.ServiceProfile) error {\n\tif diff := deep.Equal(actual, expected); diff != nil {\n\t\treturn fmt.Errorf(\"ServiceProfile mismatch: %+v\", diff)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/prometheus/prometheus.go",
    "content": "package prometheus\n\nimport (\n\t\"net/http\"\n\n\tgrpc_prometheus \"github.com/grpc-ecosystem/go-grpc-prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"go.opencensus.io/plugin/ocgrpc\"\n\t\"go.opencensus.io/plugin/ochttp\"\n\t\"google.golang.org/grpc\"\n)\n\nvar (\n\t// RequestLatencyBucketsSeconds represents latency buckets to record (seconds)\n\tRequestLatencyBucketsSeconds = append(append(append(\n\t\tprometheus.LinearBuckets(0.01, 0.01, 5),\n\t\tprometheus.LinearBuckets(0.1, 0.1, 5)...),\n\t\tprometheus.LinearBuckets(1, 1, 5)...),\n\t\tprometheus.LinearBuckets(10, 10, 5)...)\n\n\t// ResponseSizeBuckets represents response size buckets (bytes)\n\tResponseSizeBuckets = append(append(append(\n\t\tprometheus.LinearBuckets(100, 100, 5),\n\t\tprometheus.LinearBuckets(1000, 1000, 5)...),\n\t\tprometheus.LinearBuckets(10000, 10000, 5)...),\n\t\tprometheus.LinearBuckets(1000000, 1000000, 5)...)\n\n\t// server metrics\n\tserverCounter = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"http_server_requests_total\",\n\t\t\tHelp: \"A counter for requests to the wrapped handler.\",\n\t\t},\n\t\t[]string{\"code\", \"method\"},\n\t)\n\n\tserverLatency = prometheus.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tName:    \"http_server_request_latency_seconds\",\n\t\t\tHelp:    \"A histogram of latencies for requests in seconds.\",\n\t\t\tBuckets: RequestLatencyBucketsSeconds,\n\t\t},\n\t\t[]string{\"code\", \"method\"},\n\t)\n\n\tserverResponseSize = prometheus.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tName:    \"http_server_response_size_bytes\",\n\t\t\tHelp:    \"A histogram of response sizes for requests.\",\n\t\t\tBuckets: ResponseSizeBuckets,\n\t\t},\n\t\t[]string{\"code\", \"method\"},\n\t)\n\n\t// client metrics\n\tclientCounter = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"http_client_requests_total\",\n\t\t\tHelp: \"A counter for requests from the wrapped client.\",\n\t\t},\n\t\t[]string{\"client\", \"code\", \"method\"},\n\t)\n\n\tclientErrorCounter = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tName: \"http_client_errors_total\",\n\t\t\tHelp: \"A counter for errors from the wrapped client.\",\n\t\t},\n\t\t[]string{\"client\", \"method\"},\n\t)\n\n\tclientLatency = prometheus.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tName:    \"http_client_request_latency_seconds\",\n\t\t\tHelp:    \"A histogram of request latencies.\",\n\t\t\tBuckets: RequestLatencyBucketsSeconds,\n\t\t},\n\t\t[]string{\"client\", \"code\", \"method\"},\n\t)\n\n\tclientInFlight = prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tName: \"http_client_in_flight_requests\",\n\t\t\tHelp: \"A gauge of in-flight requests for the wrapped client.\",\n\t\t},\n\t\t[]string{\"client\"},\n\t)\n\tclientQPS = prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tName: \"http_client_qps\",\n\t\t\tHelp: \"Max QPS used for the client config.\",\n\t\t},\n\t\t[]string{\"client\"},\n\t)\n\tclientBurst = prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tName: \"http_client_burst\",\n\t\t\tHelp: \"Burst used for the client config.\",\n\t\t},\n\t\t[]string{\"client\"},\n\t)\n)\n\nfunc init() {\n\tprometheus.MustRegister(\n\t\tserverCounter, serverLatency, serverResponseSize, clientCounter,\n\t\tclientLatency, clientInFlight, clientQPS, clientBurst, clientErrorCounter,\n\t)\n}\n\n// NewGrpcServer returns a grpc server pre-configured with prometheus interceptors and oc-grpc handler\nfunc NewGrpcServer(opt ...grpc.ServerOption) *grpc.Server {\n\tserver := grpc.NewServer(\n\t\tappend([]grpc.ServerOption{\n\t\t\tgrpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),\n\t\t\tgrpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),\n\t\t\tgrpc.StatsHandler(&ocgrpc.ServerHandler{}),\n\t\t}, opt...)...,\n\t)\n\n\t// Use custom buckets tuned for long-lived streaming RPCs. This configuration\n\t// should be kept in sync with policy-controller/grpc's metrics.\n\tgrpc_prometheus.EnableHandlingTimeHistogram(\n\t\tgrpc_prometheus.WithHistogramBuckets([]float64{0.1, 1.0, 300.0, 3600.0}),\n\t)\n\tgrpc_prometheus.Register(server)\n\treturn server\n}\n\n// WithTelemetry instruments the HTTP server with prometheus and oc-http handler\nfunc WithTelemetry(handler http.Handler) http.Handler {\n\treturn &ochttp.Handler{\n\t\tHandler: promhttp.InstrumentHandlerDuration(serverLatency,\n\t\t\tpromhttp.InstrumentHandlerResponseSize(serverResponseSize,\n\t\t\t\tpromhttp.InstrumentHandlerCounter(serverCounter, handler))),\n\t}\n}\n\n// ClientWithTelemetry instruments the HTTP client with prometheus\nfunc ClientWithTelemetry(name string, wt func(http.RoundTripper) http.RoundTripper) (func(http.RoundTripper) http.RoundTripper, error) {\n\tlatency, err := clientLatency.CurryWith(prometheus.Labels{\"client\": name})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcounter, err := clientCounter.CurryWith(prometheus.Labels{\"client\": name})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinFlight, err := clientInFlight.GetMetricWith(prometheus.Labels{\"client\": name})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terrors, err := clientErrorCounter.CurryWith(prometheus.Labels{\"client\": name})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn func(rt http.RoundTripper) http.RoundTripper {\n\t\tif wt != nil {\n\t\t\trt = wt(rt)\n\t\t}\n\n\t\treturn InstrumentErrorCounter(errors,\n\t\t\tpromhttp.InstrumentRoundTripperInFlight(inFlight,\n\t\t\t\tpromhttp.InstrumentRoundTripperCounter(counter,\n\t\t\t\t\tpromhttp.InstrumentRoundTripperDuration(latency, rt),\n\t\t\t\t),\n\t\t\t),\n\t\t)\n\t}, nil\n}\n\nfunc InstrumentErrorCounter(counter *prometheus.CounterVec, next http.RoundTripper) promhttp.RoundTripperFunc {\n\treturn func(r *http.Request) (*http.Response, error) {\n\t\tresp, err := next.RoundTrip(r)\n\t\tif err != nil {\n\t\t\tcounter, err := counter.GetMetricWith(prometheus.Labels{\"method\": r.Method})\n\t\t\tif err != nil {\n\t\t\t\tlog.Errorf(\"failed to get client error counter: %q\", err)\n\t\t\t} else {\n\t\t\t\tcounter.Inc()\n\t\t\t}\n\t\t}\n\t\treturn resp, err\n\t}\n}\n\nfunc SetClientQPS(name string, qps float32) {\n\tgauge, err := clientQPS.GetMetricWith(prometheus.Labels{\"client\": name})\n\tif err != nil {\n\t\tlog.Errorf(\"failed to get client QPS metric: %q\", err)\n\t} else {\n\t\tgauge.Set(float64(qps))\n\t}\n}\n\nfunc SetClientBurst(name string, burst int) {\n\tgauge, err := clientBurst.GetMetricWith(prometheus.Labels{\"client\": name})\n\tif err != nil {\n\t\tlog.Errorf(\"failed to get client burst metric: %q\", err)\n\t} else {\n\t\tgauge.Set(float64(burst))\n\t}\n}\n"
  },
  {
    "path": "pkg/prometheus/test_helper.go",
    "content": "package prometheus\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"time\"\n\n\tpromv1 \"github.com/prometheus/client_golang/api/prometheus/v1\"\n\t\"github.com/prometheus/common/model\"\n)\n\n//\n// Prometheus client\n//\n\n// MockProm satisfies the promv1.API interface for testing.\n// TODO: move this into something shared under /controller, or into /pkg\ntype MockProm struct {\n\tRes             model.Value\n\tQueriesExecuted []string // expose the queries our Mock Prometheus receives, to test query generation\n\trwLock          sync.Mutex\n}\n\n// Query performs a query for the given time.\nfunc (m *MockProm) Query(ctx context.Context, query string, ts time.Time, opts ...promv1.Option) (model.Value, promv1.Warnings, error) {\n\tm.rwLock.Lock()\n\tdefer m.rwLock.Unlock()\n\tm.QueriesExecuted = append(m.QueriesExecuted, query)\n\treturn m.Res, nil, nil\n}\n\n// QueryRange performs a query for the given range.\nfunc (m *MockProm) QueryRange(ctx context.Context, query string, r promv1.Range, opts ...promv1.Option) (model.Value, promv1.Warnings, error) {\n\tm.rwLock.Lock()\n\tdefer m.rwLock.Unlock()\n\tm.QueriesExecuted = append(m.QueriesExecuted, query)\n\treturn m.Res, nil, nil\n}\n\n// AlertManagers returns an overview of the current state of the Prometheus alert\n// manager discovery.\nfunc (m *MockProm) AlertManagers(ctx context.Context) (promv1.AlertManagersResult, error) {\n\treturn promv1.AlertManagersResult{}, nil\n}\n\n// Alerts returns a list of all active alerts.\nfunc (m *MockProm) Alerts(ctx context.Context) (promv1.AlertsResult, error) {\n\treturn promv1.AlertsResult{}, nil\n}\n\n// CleanTombstones removes the deleted data from disk and cleans up the existing\n// tombstones.\nfunc (m *MockProm) CleanTombstones(ctx context.Context) error {\n\treturn nil\n}\n\n// Config returns the current Prometheus configuration.\nfunc (m *MockProm) Config(ctx context.Context) (promv1.ConfigResult, error) {\n\treturn promv1.ConfigResult{}, nil\n}\n\n// DeleteSeries deletes data for a selection of series in a time range.\nfunc (m *MockProm) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error {\n\treturn nil\n}\n\n// Flags returns the flag values that Prometheus was launched with.\nfunc (m *MockProm) Flags(ctx context.Context) (promv1.FlagsResult, error) {\n\treturn promv1.FlagsResult{}, nil\n}\n\n// LabelValues performs a query for the values of the given label, time range and matchers.\nfunc (m *MockProm) LabelValues(ctx context.Context, label string, matches []string, startTime time.Time, endTime time.Time, opts ...promv1.Option) (model.LabelValues, promv1.Warnings, error) {\n\treturn nil, nil, nil\n}\n\n// Series finds series by label matchers.\nfunc (m *MockProm) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time, opts ...promv1.Option) ([]model.LabelSet, promv1.Warnings, error) {\n\treturn nil, nil, nil\n}\n\n// Snapshot creates a snapshot of all current data into\n// snapshots/<datetime>-<rand> under the TSDB's data directory and returns the\n// directory as response.\nfunc (m *MockProm) Snapshot(ctx context.Context, skipHead bool) (promv1.SnapshotResult, error) {\n\treturn promv1.SnapshotResult{}, nil\n}\n\n// Targets returns an overview of the current state of the Prometheus target\n// discovery.\nfunc (m *MockProm) Targets(ctx context.Context) (promv1.TargetsResult, error) {\n\treturn promv1.TargetsResult{}, nil\n}\n\n// LabelNames returns the unique label names present in the block in sorted order by given time range and matchers.\nfunc (m *MockProm) LabelNames(ctx context.Context, matches []string, startTime time.Time, endTime time.Time, opts ...promv1.Option) ([]string, promv1.Warnings, error) {\n\treturn []string{}, nil, nil\n}\n\n// Runtimeinfo returns the runtime info about Prometheus\nfunc (m *MockProm) Runtimeinfo(ctx context.Context) (promv1.RuntimeinfoResult, error) {\n\treturn promv1.RuntimeinfoResult{}, nil\n}\n\n// Metadata returns the metadata of the specified metric\nfunc (m *MockProm) Metadata(ctx context.Context, metric string, limit string) (map[string][]promv1.Metadata, error) {\n\treturn nil, nil\n}\n\n// Rules returns a list of alerting and recording rules that are currently loaded.\nfunc (m *MockProm) Rules(ctx context.Context) (promv1.RulesResult, error) {\n\treturn promv1.RulesResult{}, nil\n}\n\n// TargetsMetadata returns metadata about metrics currently scraped by the target.\nfunc (m *MockProm) TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]promv1.MetricMetadata, error) {\n\treturn []promv1.MetricMetadata{}, nil\n}\n\n// Buildinfo returns various build information properties about the Prometheus server\nfunc (m *MockProm) Buildinfo(ctx context.Context) (promv1.BuildinfoResult, error) {\n\treturn promv1.BuildinfoResult{}, nil\n}\n\n// QueryExemplars performs a query for exemplars by the given query and time range.\nfunc (m *MockProm) QueryExemplars(ctx context.Context, query string, startTime time.Time, endTime time.Time) ([]promv1.ExemplarQueryResult, error) {\n\treturn []promv1.ExemplarQueryResult{}, nil\n}\n\n// TSDB returns the cardinality statistics.\nfunc (m *MockProm) TSDB(ctx context.Context, opts ...promv1.Option) (promv1.TSDBResult, error) {\n\treturn promv1.TSDBResult{}, nil\n}\n\n// WalReplay returns the current replay status of the wal.\nfunc (m *MockProm) WalReplay(ctx context.Context) (promv1.WalReplayStatus, error) {\n\treturn promv1.WalReplayStatus{}, nil\n}\n"
  },
  {
    "path": "pkg/protohttp/protohttp.go",
    "content": "package protohttp\n\nimport (\n\t\"bufio\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/util\"\n\tmetricsPb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/proto\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n)\n\nconst (\n\terrorHeader                = \"linkerd-error\"\n\tdefaultHTTPErrorStatusCode = http.StatusInternalServerError\n\tcontentTypeHeader          = \"Content-Type\"\n\tprotobufContentType        = \"application/octet-stream\"\n\tnumBytesForMessageLength   = 4\n)\n\n// HTTPError is an error which indicates the HTTP response contained an error\ntype HTTPError struct {\n\tCode         int\n\tWrappedError error\n}\n\n// FlushableResponseWriter wraps a ResponseWriter for use in streaming\n// responses\ntype FlushableResponseWriter interface {\n\thttp.ResponseWriter\n\thttp.Flusher\n}\n\n// Error satisfies the error interface for HTTPError.\nfunc (e HTTPError) Error() string {\n\treturn fmt.Sprintf(\"HTTP error, status Code [%d] (%v)\", e.Code, e.WrappedError)\n}\n\n// HTTPRequestToProto converts an HTTP Request to a protobuf request.\nfunc HTTPRequestToProto(req *http.Request, protoRequestOut proto.Message) error {\n\tbytes, err := util.ReadAllLimit(req.Body, 100*util.MB)\n\tif err != nil {\n\t\treturn HTTPError{\n\t\t\tCode:         http.StatusBadRequest,\n\t\t\tWrappedError: err,\n\t\t}\n\t}\n\n\terr = proto.Unmarshal(bytes, protoRequestOut)\n\tif err != nil {\n\t\treturn HTTPError{\n\t\t\tCode:         http.StatusBadRequest,\n\t\t\tWrappedError: err,\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// WriteErrorToHTTPResponse writes a protobuf-encoded error to an HTTP Response.\nfunc WriteErrorToHTTPResponse(w http.ResponseWriter, errorObtained error) {\n\tstatusCode := defaultHTTPErrorStatusCode\n\terrorToReturn := errorObtained\n\n\tvar he HTTPError\n\tif errors.As(errorObtained, &he) {\n\t\tstatusCode = he.Code\n\t\terrorToReturn = he.WrappedError\n\t}\n\n\tw.Header().Set(errorHeader, http.StatusText(statusCode))\n\n\terrorMessageToReturn := errorToReturn.Error()\n\tif grpcError, ok := status.FromError(errorObtained); ok {\n\t\terrorMessageToReturn = grpcError.Message()\n\t}\n\n\terrorAsProto := &metricsPb.ApiError{Error: errorMessageToReturn}\n\n\terr := WriteProtoToHTTPResponse(w, errorAsProto)\n\tif err != nil {\n\t\tlog.Errorf(\"Error writing error to http response: %v\", err)\n\t\tw.Header().Set(errorHeader, err.Error())\n\t}\n}\n\n// WriteProtoToHTTPResponse writes a protobuf-encoded message to an HTTP\n// Response.\nfunc WriteProtoToHTTPResponse(w http.ResponseWriter, msg proto.Message) error {\n\tw.Header().Set(contentTypeHeader, protobufContentType)\n\tmarshalledProtobufMessage, err := proto.Marshal(msg)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfullPayload := SerializeAsPayload(marshalledProtobufMessage)\n\t_, err = w.Write(fullPayload)\n\treturn err\n}\n\n// NewStreamingWriter takes a ResponseWriter and returns it wrapped in a\n// FlushableResponseWriter.\nfunc NewStreamingWriter(w http.ResponseWriter) (FlushableResponseWriter, error) {\n\tflushableWriter, ok := w.(FlushableResponseWriter)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"streaming not supported by this writer\")\n\t}\n\n\tflushableWriter.Header().Set(\"Connection\", \"keep-alive\")\n\tflushableWriter.Header().Set(\"Transfer-Encoding\", \"chunked\")\n\treturn flushableWriter, nil\n}\n\n// SerializeAsPayload appends a 4-byte length in front of a byte slice.\nfunc SerializeAsPayload(messageContentsInBytes []byte) []byte {\n\tlengthOfThePayload := uint32(len(messageContentsInBytes))\n\n\tmessageLengthInBytes := make([]byte, numBytesForMessageLength)\n\tbinary.LittleEndian.PutUint32(messageLengthInBytes, lengthOfThePayload)\n\n\treturn append(messageLengthInBytes, messageContentsInBytes...)\n}\n\nfunc deserializePayloadFromReader(reader *bufio.Reader) ([]byte, error) {\n\tmessageLengthAsBytes := make([]byte, numBytesForMessageLength)\n\t_, err := io.ReadFull(reader, messageLengthAsBytes)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error while reading message length: %w\", err)\n\t}\n\tmessageLength := int(binary.LittleEndian.Uint32(messageLengthAsBytes))\n\n\tmessageContentsAsBytes := make([]byte, messageLength)\n\t_, err = io.ReadFull(reader, messageContentsAsBytes)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error while reading bytes from message: %w\", err)\n\t}\n\n\treturn messageContentsAsBytes, nil\n}\n\n// CheckIfResponseHasError checks an HTTP Response for errors and returns error\n// information with the following precedence:\n// 1. \"linkerd-error\" header, with protobuf-encoded apiError\n// 2. non-200 Status Code, with Kubernetes StatusError\n// 3. non-200 Status Code\nfunc CheckIfResponseHasError(rsp *http.Response) error {\n\t// check for protobuf-encoded error\n\terrorMsg := rsp.Header.Get(errorHeader)\n\tif errorMsg != \"\" {\n\t\treader := bufio.NewReader(rsp.Body)\n\t\tvar apiError metricsPb.ApiError\n\n\t\terr := FromByteStreamToProtocolBuffers(reader, &apiError)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"response has %s header [%s], but response body didn't contain protobuf error: %w\", errorHeader, errorMsg, err)\n\t\t}\n\n\t\treturn errors.New(apiError.Error)\n\t}\n\n\t// check for JSON-encoded error\n\tif rsp.StatusCode != http.StatusOK {\n\t\tif rsp.Body != nil {\n\t\t\tbytes, err := util.ReadAllLimit(rsp.Body, 100*util.MB)\n\t\t\tif err == nil && len(bytes) > 0 {\n\t\t\t\tbody := string(bytes)\n\t\t\t\tobj, err := k8s.ToRuntimeObject(body)\n\t\t\t\tif err == nil {\n\t\t\t\t\treturn HTTPError{Code: rsp.StatusCode, WrappedError: kerrors.FromObject(obj)}\n\t\t\t\t}\n\n\t\t\t\tbody = fmt.Sprintf(\"unexpected API response: %s\", body)\n\t\t\t\treturn HTTPError{Code: rsp.StatusCode, WrappedError: errors.New(body)}\n\t\t\t}\n\t\t}\n\n\t\treturn HTTPError{Code: rsp.StatusCode, WrappedError: errors.New(\"unexpected API response\")}\n\t}\n\n\treturn nil\n}\n\n// FromByteStreamToProtocolBuffers converts a byte stream to a protobuf message.\nfunc FromByteStreamToProtocolBuffers(byteStreamContainingMessage *bufio.Reader, out proto.Message) error {\n\tmessageAsBytes, err := deserializePayloadFromReader(byteStreamContainingMessage)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading byte stream header: %w\", err)\n\t}\n\n\terr = proto.Unmarshal(messageAsBytes, out)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error unmarshalling array of [%d] bytes error: %w\", len(messageAsBytes), err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/protohttp/protohttp_test.go",
    "content": "package protohttp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\tmetricsPb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/proto\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\ntype stubResponseWriter struct {\n\tbody    *bytes.Buffer\n\theaders http.Header\n}\n\nfunc (w *stubResponseWriter) Header() http.Header {\n\treturn w.headers\n}\n\nfunc (w *stubResponseWriter) Write(p []byte) (int, error) {\n\tn, err := w.body.Write(p)\n\treturn n, err\n}\n\nfunc (w *stubResponseWriter) WriteHeader(int) {}\n\nfunc (w *stubResponseWriter) Flush() {}\n\ntype nonStreamingResponseWriter struct {\n}\n\nfunc (w *nonStreamingResponseWriter) Header() http.Header { return nil }\n\nfunc (w *nonStreamingResponseWriter) Write(p []byte) (int, error) { return -1, nil }\n\nfunc (w *nonStreamingResponseWriter) WriteHeader(int) {}\n\nfunc newStubResponseWriter() *stubResponseWriter {\n\treturn &stubResponseWriter{\n\t\theaders: make(http.Header),\n\t\tbody:    bytes.NewBufferString(\"\"),\n\t}\n}\n\nfunc TestHttpRequestToProto(t *testing.T) {\n\tsomeURL := \"https://www.example.org/something\"\n\tsomeMethod := http.MethodPost\n\n\tt.Run(\"Given a valid request, serializes its contents into protobuf object\", func(t *testing.T) {\n\t\texpectedProtoMessage := metricsPb.Pod{\n\t\t\tName:                \"some-name\",\n\t\t\tPodIP:               \"some-name\",\n\t\t\tOwner:               &metricsPb.Pod_Deployment{Deployment: \"some-name\"},\n\t\t\tStatus:              \"some-name\",\n\t\t\tAdded:               false,\n\t\t\tControllerNamespace: \"some-name\",\n\t\t\tControlPlane:        false,\n\t\t}\n\t\tpayload, err := proto.Marshal(&expectedProtoMessage)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\treq, err := http.NewRequest(someMethod, someURL, bytes.NewReader(payload))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\tvar actualProtoMessage metricsPb.Pod\n\t\terr = HTTPRequestToProto(req, &actualProtoMessage)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\tif !proto.Equal(&actualProtoMessage, &expectedProtoMessage) {\n\t\t\tt.Fatalf(\"Expected request to be [%s], but got [%s]\", expectedProtoMessage.String(), actualProtoMessage.String())\n\t\t}\n\t})\n\n\tt.Run(\"Given a broken request, returns http error\", func(t *testing.T) {\n\t\tvar actualProtoMessage metricsPb.Pod\n\n\t\treq, err := http.NewRequest(someMethod, someURL, strings.NewReader(\"not really protobuf\"))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\terr = HTTPRequestToProto(req, &actualProtoMessage)\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"Expecting error, got nothing\")\n\t\t}\n\n\t\tvar he HTTPError\n\t\tif errors.As(err, &he) {\n\t\t\texpectedStatusCode := http.StatusBadRequest\n\t\t\tif he.Code != expectedStatusCode || he.WrappedError == nil {\n\t\t\t\tt.Fatalf(\"Expected error status to be [%d] and contain wrapper error, got status [%d] and error [%s]\", expectedStatusCode, he.Code, he.WrappedError)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Fatalf(\"Expected error to be httpError, got: %v\", err)\n\t\t}\n\t})\n}\n\nfunc TestWriteErrorToHttpResponse(t *testing.T) {\n\tt.Run(\"Writes generic error correctly to response\", func(t *testing.T) {\n\t\texpectedErrorStatusCode := defaultHTTPErrorStatusCode\n\n\t\tresponseWriter := newStubResponseWriter()\n\t\tgenericError := errors.New(\"expected generic error\")\n\n\t\tWriteErrorToHTTPResponse(responseWriter, genericError)\n\n\t\tassertResponseHasProtobufContentType(t, responseWriter)\n\n\t\tactualErrorStatusCode := responseWriter.headers.Get(errorHeader)\n\t\tif actualErrorStatusCode != http.StatusText(expectedErrorStatusCode) {\n\t\t\tt.Fatalf(\"Expecting response to have status code [%d], got [%s]\", expectedErrorStatusCode, actualErrorStatusCode)\n\t\t}\n\n\t\tpayloadRead, err := deserializePayloadFromReader(bufio.NewReader(bytes.NewReader(responseWriter.body.Bytes())))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\texpectedErrorPayload := metricsPb.ApiError{Error: genericError.Error()}\n\t\tvar actualErrorPayload metricsPb.ApiError\n\t\terr = proto.Unmarshal(payloadRead, &actualErrorPayload)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\tif !proto.Equal(&actualErrorPayload, &expectedErrorPayload) {\n\t\t\tt.Fatalf(\"Expecting error to be serialized as [%s], but got [%s]\", expectedErrorPayload.String(), actualErrorPayload.String())\n\t\t}\n\t})\n\n\tt.Run(\"Writes http specific error correctly to response\", func(t *testing.T) {\n\t\texpectedErrorStatusCode := http.StatusBadGateway\n\t\tresponseWriter := newStubResponseWriter()\n\t\thttpError := HTTPError{\n\t\t\tWrappedError: errors.New(\"expected to be wrapped\"),\n\t\t\tCode:         http.StatusBadGateway,\n\t\t}\n\n\t\tWriteErrorToHTTPResponse(responseWriter, httpError)\n\n\t\tassertResponseHasProtobufContentType(t, responseWriter)\n\n\t\tactualErrorStatusCode := responseWriter.headers.Get(errorHeader)\n\t\tif actualErrorStatusCode != http.StatusText(expectedErrorStatusCode) {\n\t\t\tt.Fatalf(\"Expecting response to have status code [%d], got [%s]\", expectedErrorStatusCode, actualErrorStatusCode)\n\t\t}\n\n\t\tpayloadRead, err := deserializePayloadFromReader(bufio.NewReader(bytes.NewReader(responseWriter.body.Bytes())))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\texpectedErrorPayload := metricsPb.ApiError{Error: httpError.WrappedError.Error()}\n\t\tvar actualErrorPayload metricsPb.ApiError\n\t\terr = proto.Unmarshal(payloadRead, &actualErrorPayload)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\tif !proto.Equal(&actualErrorPayload, &expectedErrorPayload) {\n\t\t\tt.Fatalf(\"Expecting error to be serialized as [%s], but got [%s]\", expectedErrorPayload.String(), actualErrorPayload.String())\n\t\t}\n\t})\n\n\tt.Run(\"Writes gRPC specific error correctly to response\", func(t *testing.T) {\n\t\texpectedErrorStatusCode := defaultHTTPErrorStatusCode\n\n\t\tresponseWriter := newStubResponseWriter()\n\t\texpectedErrorMessage := \"error message\"\n\t\tgrpcError := status.Error(codes.AlreadyExists, expectedErrorMessage)\n\n\t\tWriteErrorToHTTPResponse(responseWriter, grpcError)\n\n\t\tassertResponseHasProtobufContentType(t, responseWriter)\n\n\t\tactualErrorStatusCode := responseWriter.headers.Get(errorHeader)\n\t\tif actualErrorStatusCode != http.StatusText(expectedErrorStatusCode) {\n\t\t\tt.Fatalf(\"Expecting response to have status code [%d], got [%s]\", expectedErrorStatusCode, actualErrorStatusCode)\n\t\t}\n\n\t\tpayloadRead, err := deserializePayloadFromReader(bufio.NewReader(bytes.NewReader(responseWriter.body.Bytes())))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\texpectedErrorPayload := metricsPb.ApiError{Error: expectedErrorMessage}\n\t\tvar actualErrorPayload metricsPb.ApiError\n\t\terr = proto.Unmarshal(payloadRead, &actualErrorPayload)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\tif actualErrorPayload.String() != expectedErrorPayload.String() {\n\t\t\tt.Fatalf(\"Expecting error to be serialized as [%s], but got [%s]\", expectedErrorPayload.String(), actualErrorPayload.String())\n\t\t}\n\t})\n}\n\nfunc TestDeserializePayloadFromReader(t *testing.T) {\n\tt.Run(\"Can read message correctly based on payload size correct payload size to message\", func(t *testing.T) {\n\t\texpectedMessage := \"this is the message\"\n\n\t\tmessageWithSize := SerializeAsPayload([]byte(expectedMessage))\n\t\tmessageWithSomeNoise := append(messageWithSize, []byte(\"this is noise and should not be read\")...)\n\n\t\tactualMessage, err := deserializePayloadFromReader(bufio.NewReader(bytes.NewReader(messageWithSomeNoise)))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\tif string(actualMessage) != expectedMessage {\n\t\t\tt.Fatalf(\"Expecting payload to contain message [%s], but it had [%s]\", expectedMessage, actualMessage)\n\t\t}\n\t})\n\n\tt.Run(\"Can multiple messages in the same stream\", func(t *testing.T) {\n\t\texpectedMessage1 := \"Hit the road, Jack and don't you come back\\n\"\n\t\tfor i := 0; i < 450; i++ {\n\t\t\texpectedMessage1 += fmt.Sprintf(\"no more (%d), \", i)\n\t\t}\n\n\t\texpectedMessage2 := \"back street back, alright\\n\"\n\t\tfor i := 0; i < 450; i++ {\n\t\t\texpectedMessage2 += fmt.Sprintf(\"tum (%d), \", i)\n\t\t}\n\n\t\tmessageWithSize1 := SerializeAsPayload([]byte(expectedMessage1))\n\t\tmessageWithSize2 := SerializeAsPayload([]byte(expectedMessage2))\n\n\t\tstreamWithManyMessages := append(messageWithSize1, messageWithSize2...)\n\t\treader := bufio.NewReader(bytes.NewReader(streamWithManyMessages))\n\n\t\tactualMessage1, err := deserializePayloadFromReader(reader)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\tactualMessage2, err := deserializePayloadFromReader(reader)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\tif string(actualMessage1) != expectedMessage1 {\n\t\t\tt.Fatalf(\"Expecting payload to contain message:\\n%s\\nbut it had\\n%s\", expectedMessage1, actualMessage1)\n\t\t}\n\n\t\tif string(actualMessage2) != expectedMessage2 {\n\t\t\tt.Fatalf(\"Expecting payload to contain message:\\n%s\\nbut it had\\n%s\", expectedMessage2, actualMessage2)\n\t\t}\n\t})\n\n\tt.Run(\"Can read byte streams larger than Go's default buffer chunk size\", func(t *testing.T) {\n\t\tgoDefaultChunkSize := 4000\n\t\texpectedMessage := \"Hit the road, Jack and don't you come back\\n\"\n\t\tfor i := 0; i < 450; i++ {\n\t\t\texpectedMessage += fmt.Sprintf(\"no more (%d), \", i)\n\t\t}\n\n\t\texpectedMessageAsBytes := []byte(expectedMessage)\n\t\tlengthOfInputData := len(expectedMessageAsBytes)\n\n\t\tif lengthOfInputData < goDefaultChunkSize {\n\t\t\tt.Fatalf(\"Test needs data larger than [%d] bytes, currently only [%d] bytes\", goDefaultChunkSize, lengthOfInputData)\n\t\t}\n\n\t\tpayload := SerializeAsPayload(expectedMessageAsBytes)\n\t\tactualMessage, err := deserializePayloadFromReader(bufio.NewReader(bytes.NewReader(payload)))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\tif string(actualMessage) != expectedMessage {\n\t\t\tt.Fatalf(\"Expecting payload to contain message:\\n%s\\n, but it had\\n%s\", expectedMessageAsBytes, actualMessage)\n\t\t}\n\t})\n\n\tt.Run(\"Returns error when message has fewer bytes than declared message size\", func(t *testing.T) {\n\t\texpectedMessage := \"this is the message\"\n\n\t\tmessageWithSize := SerializeAsPayload([]byte(expectedMessage))\n\t\tmessageMissingOneCharacter := messageWithSize[:len(expectedMessage)-1]\n\t\t_, err := deserializePayloadFromReader(bufio.NewReader(bytes.NewReader(messageMissingOneCharacter)))\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"Expecting error, got nothing\")\n\t\t}\n\t})\n}\n\nfunc TestNewStreamingWriter(t *testing.T) {\n\tt.Run(\"Returns a streaming writer if the ResponseWriter is compatible with streaming\", func(t *testing.T) {\n\t\trawWriter := newStubResponseWriter()\n\t\tflushableWriter, err := NewStreamingWriter(rawWriter)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\tif flushableWriter != rawWriter {\n\t\t\tt.Fatalf(\"Expected to return same instance of writer\")\n\t\t}\n\n\t\theader := \"Connection\"\n\t\texpectedValue := \"keep-alive\"\n\t\tactualValue := rawWriter.Header().Get(header)\n\t\tif actualValue != expectedValue {\n\t\t\tt.Fatalf(\"Expected header [%s] to be set to [%s], but was [%s]\", header, expectedValue, actualValue)\n\t\t}\n\n\t\theader = \"Transfer-Encoding\"\n\t\texpectedValue = \"chunked\"\n\t\tactualValue = rawWriter.Header().Get(header)\n\t\tif actualValue != expectedValue {\n\t\t\tt.Fatalf(\"Expected header [%s] to be set to [%s], but was [%s]\", header, expectedValue, actualValue)\n\t\t}\n\t})\n\n\tt.Run(\"Returns an error if writer does not support streaming\", func(t *testing.T) {\n\t\t_, err := NewStreamingWriter(&nonStreamingResponseWriter{})\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"Expecting error, got nothing\")\n\t\t}\n\t})\n}\n\nfunc TestCheckIfResponseHasError(t *testing.T) {\n\tt.Run(\"returns nil if response doesn't contain linkerd-error header and is 200\", func(t *testing.T) {\n\t\tresponse := &http.Response{\n\t\t\tHeader:     make(http.Header),\n\t\t\tStatusCode: http.StatusOK,\n\t\t}\n\t\terr := CheckIfResponseHasError(response)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"returns error in body if response contains linkerd-error header\", func(t *testing.T) {\n\t\texpectedErrorMessage := \"expected error message\"\n\t\tprotoInBytes, err := proto.Marshal(&metricsPb.ApiError{Error: expectedErrorMessage})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\tmessage := SerializeAsPayload(protoInBytes)\n\t\tresponse := &http.Response{\n\t\t\tHeader:     make(http.Header),\n\t\t\tBody:       io.NopCloser(bytes.NewReader(message)),\n\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t}\n\t\tresponse.Header.Set(errorHeader, \"error\")\n\n\t\terr = CheckIfResponseHasError(response)\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"Expecting error, got nothing\")\n\t\t}\n\n\t\tactualErrorMessage := err.Error()\n\t\tif actualErrorMessage != expectedErrorMessage {\n\t\t\tt.Fatalf(\"Expected error message to be [%s], but it was [%s]\", expectedErrorMessage, actualErrorMessage)\n\t\t}\n\t})\n\n\tt.Run(\"returns Kubernetes StatusError if present\", func(t *testing.T) {\n\t\tstatusError := kerrors.NewForbidden(\n\t\t\tschema.GroupResource{Group: \"group\", Resource: \"res\"},\n\t\t\t\"name\", errors.New(\"test-err\"),\n\t\t)\n\t\tstatusError.ErrStatus.Kind = \"Status\"\n\t\tstatusError.ErrStatus.APIVersion = \"v1\"\n\t\tj, err := json.Marshal(statusError.ErrStatus)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Failed to marshal JSON: %+v\", statusError)\n\t\t}\n\t\tfmt.Printf(\"J: %+v\\n\", string(j))\n\n\t\tresponse := &http.Response{\n\t\t\tHeader:     make(http.Header),\n\t\t\tBody:       io.NopCloser(bytes.NewReader(j)),\n\t\t\tStatusCode: http.StatusForbidden,\n\t\t\tStatus:     \"403 Forbidden\",\n\t\t}\n\n\t\terr = CheckIfResponseHasError(response)\n\t\texpectedErr := HTTPError{Code: http.StatusForbidden, WrappedError: statusError}\n\n\t\tif diff := deep.Equal(err, expectedErr); diff != nil {\n\t\t\tt.Fatalf(\"%v\", diff)\n\t\t}\n\t})\n\n\tt.Run(\"returns error if response is not a 200\", func(t *testing.T) {\n\t\tresponse := &http.Response{\n\t\t\tStatusCode: http.StatusServiceUnavailable,\n\t\t\tStatus:     \"503 Service Unavailable\",\n\t\t}\n\n\t\terr := CheckIfResponseHasError(response)\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"Expecting error, got nothing\")\n\t\t}\n\n\t\texpectedErrorMessage := \"HTTP error, status Code [503] (unexpected API response)\"\n\t\tactualErrorMessage := err.Error()\n\t\tif actualErrorMessage != expectedErrorMessage {\n\t\t\tt.Fatalf(\"Expected error message to be [%s], but it was [%s]\", expectedErrorMessage, actualErrorMessage)\n\t\t}\n\t})\n}\n\nfunc assertResponseHasProtobufContentType(t *testing.T, responseWriter *stubResponseWriter) {\n\tactualContentType := responseWriter.headers.Get(contentTypeHeader)\n\texpectedContentType := protobufContentType\n\tif actualContentType != expectedContentType {\n\t\tt.Fatalf(\"Expected content-type to be [%s], but got [%s]\", expectedContentType, actualContentType)\n\t}\n}\n"
  },
  {
    "path": "pkg/servicemirror/util.go",
    "content": "package servicemirror\n\nimport (\n\t\"fmt\"\n\n\tconsts \"github.com/linkerd/linkerd2/pkg/k8s\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// ParseRemoteClusterSecret extracts the credentials used to access the remote cluster\nfunc ParseRemoteClusterSecret(secret *corev1.Secret) ([]byte, error) {\n\tconfig, hasConfig := secret.Data[consts.ConfigKeyName]\n\n\tif !hasConfig {\n\t\treturn nil, fmt.Errorf(\"secret should contain target cluster name as annotation %s\", consts.RemoteClusterNameLabel)\n\t}\n\n\treturn config, nil\n}\n"
  },
  {
    "path": "pkg/tls/ca.go",
    "content": "package tls\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"fmt\"\n\t\"math/big\"\n\t\"time\"\n)\n\ntype (\n\t// CA provides a certificate authority for TLS-enabled installs.\n\t// Issuing certificates concurrently is not supported.\n\tCA struct {\n\t\t// Cred contains the CA's credentials.\n\t\tCred Cred\n\n\t\t// Validity configures the NotBefore and NotAfter parameters for certificates\n\t\t// issued by this CA.\n\t\t//\n\t\t// Currently this is used for the CA's validity too, but nothing should\n\t\t// assume that the CA's validity period is the same as issued certificates'\n\t\t// validity.\n\t\tValidity Validity\n\n\t\t// nextSerialNumber is the serial number of the next certificate to issue.\n\t\t// Serial numbers must not be reused.\n\t\t//\n\t\t// It is assumed there is only one instance of CA and it is assumed that a\n\t\t// given CA object isn't requested to issue certificates concurrently.\n\t\t//\n\t\t// For now we do not attempt to meet CABForum requirements (e.g. regarding\n\t\t// randomness).\n\t\tnextSerialNumber uint64\n\n\t\t// firstCrtExpiration is the time when the first expiration of a certificate\n\t\t// in the trust chain occurs\n\t\tfirstCrtExpiration time.Time\n\t}\n\n\t// Validity configures the expiry times of issued certificates.\n\tValidity struct {\n\t\t// Validity is the duration for which issued certificates are valid. This\n\t\t// is approximately cert.NotAfter - cert.NotBefore with some additional\n\t\t// allowance for clock skew.\n\t\t//\n\t\t// Currently this is used for the CA's validity too, but nothing should\n\t\t// assume that the CA's validity period is the same as issued certificates'\n\t\t// validity.\n\t\tLifetime time.Duration\n\n\t\t// ClockSkewAllowance is the maximum supported clock skew. Everything that\n\t\t// processes the certificates must have a system clock that is off by no\n\t\t// more than this allowance in either direction.\n\t\tClockSkewAllowance time.Duration\n\n\t\t// ValidFrom is the point in time from which the certificate is valid.\n\t\t// This is cert.NotBefore with some clock skew allowance.\n\t\tValidFrom *time.Time\n\t}\n\n\t// Issuer implementors signs certificate requests.\n\tIssuer interface {\n\t\tIssueEndEntityCrt(*x509.CertificateRequest) (Crt, error)\n\t}\n)\n\nconst (\n\t// DefaultLifetime configures certificate validity.\n\t//\n\t// Initially all certificates will be valid for one year.\n\t//\n\t// TODO: Shorten the validity duration of CA and end-entity certificates downward.\n\tDefaultLifetime = (24 * 365) * time.Hour\n\n\t// DefaultClockSkewAllowance indicates the maximum allowed difference in clocks\n\t// in the network.\n\t//\n\t// TODO: make it tunable.\n\t//\n\t// TODO: Reconsider how this interacts with the similar logic in the webpki\n\t// verifier; since both are trying to account for clock skew, there is\n\t// somewhat of an over-correction.\n\tDefaultClockSkewAllowance = 10 * time.Second\n)\n\n// Finds the time at which the first certificate\n// from the chain will expire\nfunc findFirstExpiration(cred *Cred) time.Time {\n\tfirstExpiration := cred.Certificate.NotAfter\n\tfor _, c := range cred.TrustChain {\n\t\tif c.NotAfter.Before(firstExpiration) {\n\t\t\tfirstExpiration = c.NotAfter\n\t\t}\n\t}\n\treturn firstExpiration\n}\n\n// NewCA initializes a new CA with default settings.\nfunc NewCA(cred Cred, validity Validity) *CA {\n\treturn &CA{cred, validity, uint64(1), findFirstExpiration(&cred)}\n}\n\nfunc init() {\n\t// Assert that the struct implements the interface.\n\tvar _ Issuer = &CA{}\n}\n\n// CreateRootCA configures a new root CA with the given settings\nfunc CreateRootCA(\n\tname string,\n\tkey *ecdsa.PrivateKey,\n\tvalidity Validity,\n) (*CA, error) {\n\t// Configure the root certificate.\n\tt := createTemplate(1, &key.PublicKey, validity)\n\tt.Subject = pkix.Name{CommonName: name}\n\tt.IsCA = true\n\tt.MaxPathLen = -1\n\tt.BasicConstraintsValid = true\n\tt.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign\n\n\t// Self-sign the root certificate.\n\tcrtb, err := x509.CreateCertificate(rand.Reader, t, t, key.Public(), key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tc, err := x509.ParseCertificate(crtb)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// The Crt has an empty TrustChain because it's at the root.\n\tcred := validCredOrPanic(key, Crt{Certificate: c})\n\tca := NewCA(cred, validity)\n\tca.nextSerialNumber++ // Because we've already created the root cert.\n\treturn ca, nil\n}\n\n// GenerateKey creates a new P-256 ECDSA private key from the default random\n// source.\nfunc GenerateKey() (*ecdsa.PrivateKey, error) {\n\treturn ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n}\n\n// GenerateRootCAWithDefaults generates a new root CA with default settings.\nfunc GenerateRootCAWithDefaults(name string) (*CA, error) {\n\t// Generate a new root key.\n\tkey, err := GenerateKey()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn CreateRootCA(name, key, Validity{})\n}\n\n// GenerateCA generates a new intermediate CA.\nfunc (ca *CA) GenerateCA(name string, maxPathLen int) (*CA, error) {\n\tkey, err := GenerateKey()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tt := ca.createTemplate(&key.PublicKey)\n\tt.Subject = pkix.Name{CommonName: name}\n\tt.IsCA = true\n\tt.MaxPathLen = maxPathLen\n\tt.MaxPathLenZero = true // 0-values are actually 0\n\tt.BasicConstraintsValid = true\n\tt.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign\n\tcrt, err := ca.Cred.SignCrt(t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn NewCA(validCredOrPanic(key, crt), ca.Validity), nil\n}\n\n// GenerateEndEntityCred creates a new certificate that is valid for the\n// given DNS name, generating a new keypair for it.\nfunc (ca *CA) GenerateEndEntityCred(dnsName string) (*Cred, error) {\n\tkey, err := GenerateKey()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcsr := x509.CertificateRequest{\n\t\tSubject:   pkix.Name{CommonName: dnsName},\n\t\tDNSNames:  []string{dnsName},\n\t\tPublicKey: &key.PublicKey,\n\t}\n\tcrt, err := ca.IssueEndEntityCrt(&csr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc := validCredOrPanic(key, crt)\n\treturn &c, nil\n}\n\n// IssueEndEntityCrt creates a new certificate that is valid for the\n// given DNS name, generating a new keypair for it.\nfunc (ca *CA) IssueEndEntityCrt(csr *x509.CertificateRequest) (Crt, error) {\n\tpubkey, ok := csr.PublicKey.(*ecdsa.PublicKey)\n\tif !ok {\n\t\treturn Crt{}, fmt.Errorf(\"CSR must contain an ECDSA public key: %+v\", csr.PublicKey)\n\t}\n\n\tt := ca.createTemplate(pubkey)\n\tt.Issuer = ca.Cred.Crt.Certificate.Subject\n\tt.Subject = csr.Subject\n\tt.Extensions = csr.Extensions\n\tt.ExtraExtensions = csr.ExtraExtensions\n\tt.DNSNames = csr.DNSNames\n\tt.EmailAddresses = csr.EmailAddresses\n\tt.IPAddresses = csr.IPAddresses\n\tt.URIs = csr.URIs\n\n\treturn ca.Cred.SignCrt(t)\n}\n\n// createTemplate returns a certificate t for a non-CA certificate with\n// no subject name, no subjectAltNames. The t can then be modified into\n// a (root) CA t or an end-entity t by the caller.\nfunc (ca *CA) createTemplate(pubkey *ecdsa.PublicKey) *x509.Certificate {\n\tc := createTemplate(ca.nextSerialNumber, pubkey, ca.Validity)\n\tca.nextSerialNumber++\n\t// if our trust chain contains a certificate that expires\n\t// sooner than the one we intend to issue, we clamp the\n\t// NotAfter time of our newly issued certificate. That ensures\n\t// the proxy will request a new cert before any of the\n\t// certs in the chain are expired.\n\tif ca.firstCrtExpiration.Before(c.NotAfter) {\n\t\tc.NotAfter = ca.firstCrtExpiration\n\t}\n\treturn c\n}\n\n// createTemplate returns a certificate t for a non-CA certificate with\n// no subject name, no subjectAltNames. The t can then be modified into\n// a (root) CA t or an end-entity t by the caller.\nfunc createTemplate(\n\tserialNumber uint64,\n\tk *ecdsa.PublicKey,\n\tv Validity,\n) *x509.Certificate {\n\t// ECDSA is used instead of RSA because ECDSA key generation is\n\t// straightforward and fast whereas RSA key generation is extremely slow\n\t// and error-prone.\n\t//\n\t// CA certificates are signed with the same algorithm as end-entity\n\t// certificates because they are relatively short-lived, because using one\n\t// algorithm minimizes exposure to implementation flaws, and to speed up\n\t// signature verification time.\n\t//\n\t// SHA-256 is used because any larger digest would be truncated to 256 bits\n\t// anyway since a P-256 scalar is only 256 bits long.\n\tconst SignatureAlgorithm = x509.ECDSAWithSHA256\n\n\tif v.ValidFrom == nil {\n\t\tnow := time.Now()\n\t\tv.ValidFrom = &now\n\t}\n\tnotBefore, notAfter := v.Window(*v.ValidFrom)\n\n\treturn &x509.Certificate{\n\t\tSerialNumber:       big.NewInt(int64(serialNumber)),\n\t\tSignatureAlgorithm: SignatureAlgorithm,\n\t\tNotBefore:          notBefore,\n\t\tNotAfter:           notAfter,\n\t\tPublicKey:          k,\n\t\tKeyUsage:           x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage: []x509.ExtKeyUsage{\n\t\t\tx509.ExtKeyUsageServerAuth,\n\t\t\tx509.ExtKeyUsageClientAuth,\n\t\t},\n\t}\n}\n\n// Window returns the time window for which a certificate should be valid.\nfunc (v *Validity) Window(t time.Time) (time.Time, time.Time) {\n\tlife := v.Lifetime\n\tif life == 0 {\n\t\tlife = DefaultLifetime\n\t}\n\tskew := v.ClockSkewAllowance\n\tif skew == 0 {\n\t\tskew = DefaultClockSkewAllowance\n\t}\n\tstart := t.Add(-skew)\n\tend := t.Add(life).Add(skew)\n\treturn start, end\n}\n"
  },
  {
    "path": "pkg/tls/ca_test.go",
    "content": "package tls\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc getCa(validFrom time.Time, issuerCertLifetime time.Duration, endCertLifetime time.Duration) (*CA, error) {\n\tkey, err := GenerateKey()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tca, err := CreateRootCA(\"fake-name\", key, Validity{ValidFrom: &validFrom, Lifetime: issuerCertLifetime})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn NewCA(ca.Cred, Validity{ValidFrom: &validFrom, Lifetime: endCertLifetime}), nil\n}\n\nfunc TestCaIssuesCertsWithCorrectExpiration(t *testing.T) {\n\n\tvalidFrom := time.Now().UTC().Round(time.Second)\n\n\ttestCases := []struct {\n\t\tdesc                   string\n\t\tvalidFrom              time.Time\n\t\tissuerLifeTime         time.Duration\n\t\tendCertLifetime        time.Duration\n\t\texpectedCertExpiration time.Time\n\t}{\n\t\t{\n\t\t\tdesc:                   \"issuer cert expires after end cert\",\n\t\t\tvalidFrom:              validFrom,\n\t\t\tissuerLifeTime:         time.Hour * 48,\n\t\t\tendCertLifetime:        time.Hour * 24,\n\t\t\texpectedCertExpiration: validFrom.Add(time.Hour * 24).Add(DefaultClockSkewAllowance),\n\t\t},\n\t\t{\n\t\t\tdesc:                   \"issuer cert expires before end cert\",\n\t\t\tvalidFrom:              validFrom,\n\t\t\tissuerLifeTime:         time.Hour * 10,\n\t\t\tendCertLifetime:        time.Hour * 24,\n\t\t\texpectedCertExpiration: validFrom.Add(time.Hour * 10).Add(DefaultClockSkewAllowance),\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\n\t\t\tca, err := getCa(tc.validFrom, tc.issuerLifeTime, tc.endCertLifetime)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\t\t\tcrt, err := ca.GenerateEndEntityCred(\"fake-name\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif crt.Certificate.NotAfter != tc.expectedCertExpiration {\n\t\t\t\tt.Fatalf(\"Expected cert expiration %v but got %v\", tc.expectedCertExpiration, crt.Certificate.NotAfter)\n\t\t\t}\n\t\t})\n\t}\n\n}\n"
  },
  {
    "path": "pkg/tls/codec.go",
    "content": "package tls\n\nimport (\n\t\"bytes\"\n\t\"crypto/ecdsa\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n)\n\n// === ENCODE ===\n\n// EncodeCertificatesPEM encodes the collection of provided certificates as\n// a text blob of PEM-encoded certificates.\nfunc EncodeCertificatesPEM(crts ...*x509.Certificate) string {\n\tbuf := bytes.Buffer{}\n\tfor _, c := range crts {\n\t\tencode(&buf, &pem.Block{Type: \"CERTIFICATE\", Bytes: c.Raw})\n\t}\n\treturn buf.String()\n}\n\n// EncodePrivateKeyPEM encodes the provided key as PEM-encoded text\nfunc EncodePrivateKeyPEM(k *ecdsa.PrivateKey) ([]byte, error) {\n\tder, err := x509.MarshalECPrivateKey(k)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pem.EncodeToMemory(&pem.Block{Type: \"EC PRIVATE KEY\", Bytes: der}), nil\n}\n\n// EncodePrivateKeyP8 encodes the provided key as PEM-encoded text\nfunc EncodePrivateKeyP8(k *ecdsa.PrivateKey) []byte {\n\tp8, err := x509.MarshalPKCS8PrivateKey(k)\n\tif err != nil {\n\t\tpanic(\"ECDSA keys must be encodeable as PKCS8\")\n\t}\n\treturn p8\n}\n\nfunc encode(buf *bytes.Buffer, blk *pem.Block) {\n\tif err := pem.Encode(buf, blk); err != nil {\n\t\tpanic(\"encoding to memory must not fail\")\n\t}\n}\n\n// === DECODE ===\n\n// DecodePEMKey parses a PEM-encoded private key from the named path.\nfunc DecodePEMKey(txt string) (GenericPrivateKey, error) {\n\tblock, _ := pem.Decode([]byte(txt))\n\tif block == nil {\n\t\treturn nil, errors.New(\"not PEM-encoded\")\n\t}\n\tswitch block.Type {\n\tcase \"EC PRIVATE KEY\":\n\t\tk, err := x509.ParseECPrivateKey(block.Bytes)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn privateKeyEC{k}, nil\n\tcase \"RSA PRIVATE KEY\":\n\t\tk, err := x509.ParsePKCS1PrivateKey(block.Bytes)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn privateKeyRSA{k}, nil\n\tcase \"PRIVATE KEY\":\n\t\tk, err := x509.ParsePKCS8PrivateKey(block.Bytes)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif ec, ok := k.(*ecdsa.PrivateKey); ok {\n\t\t\treturn privateKeyEC{ec}, nil\n\t\t}\n\t\tif rsa, ok := k.(*rsa.PrivateKey); ok {\n\t\t\treturn privateKeyRSA{rsa}, nil\n\t\t}\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"unsupported PKCS#8 encoded private key type: '%s', linkerd2 only supports ECDSA and RSA private keys\",\n\t\t\treflect.TypeOf(k))\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported block type: '%s'\", block.Type)\n\t}\n}\n\n// DecodePEMCertificates parses a string containing PEM-encoded certificates.\nfunc DecodePEMCertificates(txt string) (certs []*x509.Certificate, err error) {\n\tbuf := []byte(txt)\n\tfor len(buf) > 0 {\n\t\tvar c *x509.Certificate\n\t\tc, buf, err = decodeCertificatePEM(buf)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif c == nil {\n\t\t\tcontinue // not a CERTIFICATE, skip\n\t\t}\n\t\tcerts = append(certs, c)\n\t}\n\treturn\n}\n\n// CertificatesToPool converts a slice of certificates into a cert pool\nfunc CertificatesToPool(certs []*x509.Certificate) *x509.CertPool {\n\tpool := x509.NewCertPool()\n\tfor _, c := range certs {\n\t\tpool.AddCert(c)\n\t}\n\treturn pool\n}\n\n// DecodePEMCertPool parses a string containing PE-encoded certificates into a CertPool.\nfunc DecodePEMCertPool(txt string) (*x509.CertPool, error) {\n\tcerts, err := DecodePEMCertificates(txt)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(certs) == 0 {\n\t\treturn nil, errors.New(\"no certificates found\")\n\t}\n\n\treturn CertificatesToPool(certs), nil\n}\n\nfunc decodeCertificatePEM(crtb []byte) (*x509.Certificate, []byte, error) {\n\tblock, crtb := pem.Decode(crtb)\n\tif block == nil {\n\t\treturn nil, crtb, errors.New(\"not a PEM certificate\")\n\t}\n\tif block.Type != \"CERTIFICATE\" {\n\t\treturn nil, nil, nil\n\t}\n\tc, err := x509.ParseCertificate(block.Bytes)\n\treturn c, crtb, err\n}\n"
  },
  {
    "path": "pkg/tls/codec_test.go",
    "content": "package tls\n\nimport (\n\t\"testing\"\n)\n\nfunc TestPrivateKeyParsing(t *testing.T) {\n\tif _, err := DecodePEMKey(\"\"); err == nil {\n\t\tt.Fatalf(\"Empty private key should fail to parse\")\n\t}\n\tif _, err := DecodePEMKey(\"BEGIN EC PRIVATE KEY\\nafjlakjflaksdjf\\nEND EC PRIVATE KEY\"); err == nil {\n\t\tt.Fatalf(\"Invalid PKCS#1 ECDSA private key should fail to parse\")\n\t}\n\tif _, err := DecodePEMKey(\"BEGIN RSA PRIVATE KEY\\nafjlakjflaksdjf\\nEND RSA PRIVATE KEY\"); err == nil {\n\t\tt.Fatalf(\"Invalid PKCS#1 RSA private key should fail to parse\")\n\t}\n\tif _, err := DecodePEMKey(\"BEGIN PRIVATE KEY\\nafjlakjflaksdjf\\nEND PRIVATE KEY\"); err == nil {\n\t\tt.Fatalf(\"Invalid PKCS#8 private key should fail to parse\")\n\t}\n\tecPkcs8 := \"-----BEGIN PRIVATE KEY-----\\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgDZUgDvKixfLi8cK8\\n/TFLY97TDmQV3J2ygPpvuI8jSdihRANCAARRN3xgbPIR83dr27UuDaf2OJezpEJx\\nUC3v06+FD8MUNcRAboqt4akehaNNSh7MMZI+HdnsM4RXN2y8NePUQsPL\\n-----END PRIVATE KEY-----\"\n\tif _, err := DecodePEMKey(ecPkcs8); err != nil {\n\t\tt.Fatalf(\"Failed to parse PKCS#8 encoded ECDSA private key: %s\", err)\n\t}\n\trsaPkcs8 := \"-----BEGIN PRIVATE KEY-----\\nMIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEAq7BFUpkGp3+LQmlQ\\nYx2eqzDV+xeG8kx/sQFV18S5JhzGeIJNA72wSeukEPojtqUyX2J0CciPBh7eqclQ\\n2zpAswIDAQABAkAgisq4+zRdrzkwH1ITV1vpytnkO/NiHcnePQiOW0VUybPyHoGM\\n/jf75C5xET7ZQpBe5kx5VHsPZj0CBb3b+wSRAiEA2mPWCBytosIU/ODRfq6EiV04\\nlt6waE7I2uSPqIC20LcCIQDJQYIHQII+3YaPqyhGgqMexuuuGx+lDKD6/Fu/JwPb\\n5QIhAKthiYcYKlL9h8bjDsQhZDUACPasjzdsDEdq8inDyLOFAiEAmCr/tZwA3qeA\\nZoBzI10DGPIuoKXBd3nk/eBxPkaxlEECIQCNymjsoI7GldtujVnr1qT+3yedLfHK\\nsrDVjIT3LsvTqw==\\n-----END PRIVATE KEY-----\"\n\tif _, err := DecodePEMKey(rsaPkcs8); err != nil {\n\t\tt.Fatalf(\"Failed to parse PKCS#8 encoded RSA private key: %s\", err)\n\t}\n}\n"
  },
  {
    "path": "pkg/tls/cred.go",
    "content": "package tls\n\nimport (\n\t\"bytes\"\n\t\"crypto/ecdsa\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n)\n\ntype (\n\t// PrivateKeyEC wraps an EC private key\n\tprivateKeyEC struct {\n\t\t*ecdsa.PrivateKey\n\t}\n\n\t// PrivateKeyRSA wraps an RSA private key\n\tprivateKeyRSA struct {\n\t\t*rsa.PrivateKey\n\t}\n\n\t// GenericPrivateKey represents either an EC or an RSA private key\n\tGenericPrivateKey interface {\n\t\tmatchesCertificate(*x509.Certificate) bool\n\t\tmarshal() ([]byte, error)\n\t}\n\n\t// Cred is a container for a certificate, trust chain, and private key.\n\tCred struct {\n\t\tPrivateKey GenericPrivateKey\n\t\tCrt\n\t}\n\n\t// Crt is a container for a certificate and trust chain.\n\t//\n\t// The trust chain stores all issuer certificates from the root at the head to\n\t// the direct issuer at the tail.\n\tCrt struct {\n\t\tCertificate *x509.Certificate\n\t\tTrustChain  []*x509.Certificate\n\t}\n)\n\nfunc (k privateKeyEC) matchesCertificate(c *x509.Certificate) bool {\n\tpub, ok := c.PublicKey.(*ecdsa.PublicKey)\n\treturn ok && pub.X.Cmp(k.X) == 0 && pub.Y.Cmp(k.Y) == 0\n}\n\nfunc (k privateKeyEC) marshal() ([]byte, error) {\n\treturn x509.MarshalECPrivateKey(k.PrivateKey)\n}\n\nfunc (k privateKeyRSA) matchesCertificate(c *x509.Certificate) bool {\n\tpub, ok := c.PublicKey.(*rsa.PublicKey)\n\treturn ok && pub.N.Cmp(k.N) == 0 && pub.E == k.E\n}\n\nfunc (k privateKeyRSA) marshal() ([]byte, error) {\n\treturn x509.MarshalPKCS1PrivateKey(k.PrivateKey), nil\n}\n\n// validCredOrPanic creates a  Cred, panicking if the key does not match the certificate.\nfunc validCredOrPanic(ecKey *ecdsa.PrivateKey, crt Crt) Cred {\n\tk := privateKeyEC{ecKey}\n\tif !k.matchesCertificate(crt.Certificate) {\n\t\tpanic(\"Cert's public key does not match private key\")\n\t}\n\treturn Cred{Crt: crt, PrivateKey: k}\n}\n\n// CertPool returns a CertPool containing this Crt.\nfunc (crt *Crt) CertPool() *x509.CertPool {\n\tp := x509.NewCertPool()\n\tp.AddCert(crt.Certificate)\n\tfor _, c := range crt.TrustChain {\n\t\tp.AddCert(c)\n\t}\n\treturn p\n}\n\n// Verify the validity of the provided certificate\nfunc (crt *Crt) Verify(roots *x509.CertPool, name string, currentTime time.Time) error {\n\ti := x509.NewCertPool()\n\tfor _, c := range crt.TrustChain {\n\t\ti.AddCert(c)\n\t}\n\tvo := x509.VerifyOptions{Roots: roots, Intermediates: i, DNSName: name, CurrentTime: currentTime}\n\t_, err := crt.Certificate.Verify(vo)\n\n\tif currentTime.IsZero() {\n\t\tcurrentTime = time.Now()\n\t}\n\n\tif crtExpiryError(err) {\n\t\treturn fmt.Errorf(\"%w - Current Time : %s - Invalid before %s - Invalid After %s\", err, currentTime, crt.Certificate.NotBefore, crt.Certificate.NotAfter)\n\t}\n\treturn err\n}\n\n// ExtractRaw extracts the DER-encoded certificates in the Crt from leaf to root.\nfunc (crt *Crt) ExtractRaw() [][]byte {\n\tchain := make([][]byte, len(crt.TrustChain)+1)\n\tchain[0] = crt.Certificate.Raw\n\tfor i, c := range crt.TrustChain {\n\t\tchain[len(crt.TrustChain)-i] = c.Raw\n\t}\n\treturn chain\n}\n\n// EncodePEM emits a certificate and trust chain as a\n// series of PEM-encoded certificates from leaf to root.\nfunc (crt *Crt) EncodePEM() string {\n\tbuf := bytes.Buffer{}\n\tencode(&buf, &pem.Block{Type: \"CERTIFICATE\", Bytes: crt.Certificate.Raw})\n\n\t// Serialize certificates from leaf to root.\n\tn := len(crt.TrustChain)\n\tfor i := n - 1; i >= 0; i-- {\n\t\tencode(&buf, &pem.Block{Type: \"CERTIFICATE\", Bytes: crt.TrustChain[i].Raw})\n\t}\n\n\treturn buf.String()\n}\n\n// EncodeCertificatePEM emits the Crt's leaf certificate as PEM-encoded text.\nfunc (crt *Crt) EncodeCertificatePEM() string {\n\treturn EncodeCertificatesPEM(crt.Certificate)\n}\n\n// EncodePrivateKeyPEM emits the private key as PEM-encoded text.\nfunc (cred *Cred) EncodePrivateKeyPEM() string {\n\tb, err := cred.PrivateKey.marshal()\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"Invalid private key: %s\", err))\n\t}\n\n\treturn string(pem.EncodeToMemory(&pem.Block{Type: \"EC PRIVATE KEY\", Bytes: b}))\n}\n\n// EncodePrivateKeyP8 encodes the provided key to the PKCS#8 binary form.\nfunc (cred *Cred) EncodePrivateKeyP8() ([]byte, error) {\n\treturn x509.MarshalPKCS8PrivateKey(cred.PrivateKey)\n}\n\n// SignCrt uses this Cred to sign a new certificate.\n//\n// This may fail if the Cred contains an end-entity certificate.\nfunc (cred *Cred) SignCrt(template *x509.Certificate) (Crt, error) {\n\tcrtb, err := x509.CreateCertificate(\n\t\trand.Reader,\n\t\ttemplate,\n\t\tcred.Crt.Certificate,\n\t\ttemplate.PublicKey,\n\t\tcred.PrivateKey,\n\t)\n\tif err != nil {\n\t\treturn Crt{}, err\n\t}\n\n\tc, err := x509.ParseCertificate(crtb)\n\tif err != nil {\n\t\treturn Crt{}, err\n\t}\n\n\tcrt := Crt{\n\t\tCertificate: c,\n\t\tTrustChain:  append(cred.Crt.TrustChain, cred.Crt.Certificate),\n\t}\n\treturn crt, nil\n}\n\n// ValidateAndCreateCreds reads PEM-encoded credentials from strings and validates them\nfunc ValidateAndCreateCreds(crt, key string) (*Cred, error) {\n\tk, err := DecodePEMKey(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc, err := DecodePEMCrt(crt)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !k.matchesCertificate(c.Certificate) {\n\t\treturn nil, errors.New(\"tls: Public and private key do not match\")\n\t}\n\treturn &Cred{PrivateKey: k, Crt: *c}, nil\n}\n\n// ReadPEMCreds reads PEM-encoded credentials from the named files.\nfunc ReadPEMCreds(keyPath, crtPath string) (*Cred, error) {\n\tkeyb, err := os.ReadFile(filepath.Clean(keyPath))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcrtb, err := os.ReadFile(filepath.Clean(crtPath))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ValidateAndCreateCreds(string(crtb), string(keyb))\n}\n\n// DecodePEMCrt decodes PEM-encoded certificates from leaf to root.\nfunc DecodePEMCrt(txt string) (*Crt, error) {\n\tcerts, err := DecodePEMCertificates(txt)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(certs) == 0 {\n\t\treturn nil, errors.New(\"No certificates found\")\n\t}\n\n\tcrt := Crt{\n\t\tCertificate: certs[0],\n\t\tTrustChain:  make([]*x509.Certificate, len(certs)-1),\n\t}\n\n\t// The chain is read from Leaf to Root, but we store it from Root to Leaf.\n\tcerts = certs[1:]\n\tfor i, c := range certs {\n\t\tcrt.TrustChain[len(certs)-i-1] = c\n\t}\n\n\treturn &crt, nil\n}\n\nfunc crtExpiryError(err error) bool {\n\tvar cie x509.CertificateInvalidError\n\tif errors.As(err, &cie) {\n\t\treturn cie.Reason == x509.Expired\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/tls/cred_test.go",
    "content": "package tls\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc newRoot(t *testing.T) CA {\n\troot, err := GenerateRootCAWithDefaults(t.Name())\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create CA: %s\", err)\n\t}\n\treturn *root\n}\n\nfunc TestCrtRoundtrip(t *testing.T) {\n\troot := newRoot(t)\n\trootTrust := root.Cred.Crt.CertPool()\n\n\tcred, err := root.GenerateEndEntityCred(\"endentity.test\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create end entity cred: %s\", err)\n\t}\n\n\tcrt, err := DecodePEMCrt(cred.Crt.EncodePEM())\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to decode PEM Crt: %s\", err)\n\t}\n\n\tif err := crt.Verify(rootTrust, \"\", time.Time{}); err != nil {\n\t\tt.Fatal(\"Failed to verify round-tripped certificate\")\n\t}\n}\n\nfunc TestCredEncodeCertificateAndTrustChain(t *testing.T) {\n\troot, err := GenerateRootCAWithDefaults(\"Test Root CA\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create CA: %s\", err)\n\t}\n\n\tcred, err := root.GenerateEndEntityCred(\"test end entity\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create end entity cred\")\n\t}\n\n\texpected := EncodeCertificatesPEM(cred.Crt.Certificate, root.Cred.Crt.Certificate)\n\tif cred.EncodePEM() != expected {\n\t\tt.Errorf(\"Encoded Certificate And TrustChain does not match expected output\")\n\t}\n}\n\nfunc TestCrtExpiry(t *testing.T) {\n\troot := newRoot(t)\n\trootTrust := root.Cred.Crt.CertPool()\n\n\tcred, err := root.GenerateEndEntityCred(\"expired.test\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create end entity cred: %s\", err)\n\t}\n\n\tcrt, err := DecodePEMCrt(cred.Crt.EncodePEM())\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to decode PEM Crt: %s\", err)\n\t}\n\n\t// need to remove seconds and nanoseconds for testing returned error\n\tnow := time.Now()\n\n\ttestCases := []struct {\n\t\tcurrentTime time.Time\n\t\tnotBefore   time.Time\n\t\tnotAfter    time.Time\n\t\tvalid       bool\n\t}{\n\t\t// cert not valid yet\n\t\t{\n\t\t\tcurrentTime: now,\n\t\t\tnotAfter:    now.AddDate(0, 0, 20),\n\t\t\tnotBefore:   now.AddDate(0, 0, 10),\n\t\t\tvalid:       false,\n\t\t},\n\t\t// cert has expired\n\t\t{\n\t\t\tcurrentTime: now,\n\t\t\tnotAfter:    now.AddDate(0, 0, -10),\n\t\t\tnotBefore:   now.AddDate(0, 0, -20),\n\t\t\tvalid:       false,\n\t\t},\n\t\t// cert is valid\n\t\t{\n\t\t\tcurrentTime: time.Time{},\n\t\t\tnotAfter:    crt.Certificate.NotAfter,\n\t\t\tnotBefore:   crt.Certificate.NotBefore,\n\t\t\tvalid:       true,\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d\", i), func(t *testing.T) {\n\t\t\t// explicitly kill the certificate\n\t\t\tcrt.Certificate.NotBefore = tc.notBefore\n\t\t\tcrt.Certificate.NotAfter = tc.notAfter\n\n\t\t\terr := crt.Verify(rootTrust, \"\", tc.currentTime)\n\t\t\tif tc.valid && err != nil {\n\t\t\t\tt.Fatalf(\"expected certificate to be valid but was invalid: %s\", err.Error())\n\t\t\t}\n\t\t\tif !tc.valid && err == nil {\n\t\t\t\tt.Fatal(\"expected certificate to be invalid, but was valid\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/tls/creds_watcher.go",
    "content": "package tls\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"sync/atomic\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nconst dataDirectoryLnName = \"..data\"\n\n// FsCredsWatcher is used to monitor tls credentials on the filesystem\ntype FsCredsWatcher struct {\n\tcertRootPath string\n\tcertFilePath string\n\tkeyFilePath  string\n\tEventChan    chan<- struct{}\n\tErrorChan    chan<- error\n}\n\n// NewFsCredsWatcher constructs a FsCredsWatcher instance\nfunc NewFsCredsWatcher(certRootPath string, updateEvent chan<- struct{}, errEvent chan<- error) *FsCredsWatcher {\n\treturn &FsCredsWatcher{certRootPath, \"\", \"\", updateEvent, errEvent}\n}\n\n// WithFilePaths completes the FsCredsWatcher instance with the cert and key files locations\nfunc (fscw *FsCredsWatcher) WithFilePaths(certFilePath, keyFilePath string) *FsCredsWatcher {\n\tfscw.certFilePath = certFilePath\n\tfscw.keyFilePath = keyFilePath\n\treturn fscw\n}\n\n// StartWatching starts watching the filesystem for cert updates\nfunc (fscw *FsCredsWatcher) StartWatching(ctx context.Context) error {\n\twatcher, err := fsnotify.NewWatcher()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer watcher.Close()\n\n\t// no point of proceeding if we fail to watch this\n\tif err := watcher.Add(fscw.certRootPath); err != nil {\n\t\treturn err\n\t}\n\nLOOP:\n\tfor {\n\t\tselect {\n\t\tcase event := <-watcher.Events:\n\t\t\tlog.Debugf(\"Received event: %v\", event)\n\t\t\t// Watching the folder for create events as this indicates\n\t\t\t// that the secret has been updated.\n\t\t\tif event.Op&fsnotify.Create == fsnotify.Create &&\n\t\t\t\tevent.Name == filepath.Join(fscw.certRootPath, dataDirectoryLnName) {\n\t\t\t\tfscw.EventChan <- struct{}{}\n\t\t\t}\n\t\tcase err := <-watcher.Errors:\n\t\t\tfscw.ErrorChan <- err\n\t\t\tlog.Warnf(\"Error while watching %s: %s\", fscw.certRootPath, err)\n\t\t\tbreak LOOP\n\t\tcase <-ctx.Done():\n\t\t\tif err := ctx.Err(); err != nil {\n\t\t\t\tfscw.ErrorChan <- err\n\t\t\t}\n\t\t\tbreak LOOP\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// UpdateCert reads the cert and key files and stores the key pair in certVal\nfunc (fscw *FsCredsWatcher) UpdateCert(certVal *atomic.Value) error {\n\tcreds, err := ReadPEMCreds(fscw.keyFilePath, fscw.certFilePath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read cert from disk: %w\", err)\n\t}\n\n\tcertPEM := creds.EncodePEM()\n\tkeyPEM := creds.EncodePrivateKeyPEM()\n\tcert, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM))\n\tif err != nil {\n\t\treturn err\n\t}\n\tcertVal.Store(&cert)\n\treturn nil\n}\n\n// ProcessEvents reads from the update and error channels and reloads the certs when necessary\nfunc (fscw *FsCredsWatcher) ProcessEvents(\n\tlog *log.Entry,\n\tcertVal *atomic.Value,\n\tupdateEvent <-chan struct{},\n\terrEvent <-chan error,\n) {\n\tfor {\n\t\tselect {\n\t\tcase <-updateEvent:\n\t\t\tif err := fscw.UpdateCert(certVal); err != nil {\n\t\t\t\tlog.Warnf(\"Skipping update as cert could not be read from disk: %s\", err)\n\t\t\t} else {\n\t\t\t\tlog.Infof(\"Updated certificate\")\n\t\t\t}\n\t\tcase err := <-errEvent:\n\t\t\tlog.Warnf(\"Received error from fs watcher: %s\", err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/trace/trace.go",
    "content": "package trace\n\nimport (\n\t\"contrib.go.opencensus.io/exporter/ocagent\"\n\t\"go.opencensus.io/trace\"\n)\n\n// InitializeTracing initiates trace, exporter and the sampler\nfunc InitializeTracing(serviceName string, address string) error {\n\toce, err := ocagent.NewExporter(\n\t\tocagent.WithInsecure(),\n\t\tocagent.WithAddress(address),\n\t\tocagent.WithServiceName(serviceName))\n\tif err != nil {\n\t\treturn err\n\t}\n\ttrace.RegisterExporter(oce)\n\ttrace.ApplyConfig(trace.Config{\n\t\tDefaultSampler: trace.AlwaysSample(),\n\t})\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/tree/tree.go",
    "content": "package tree\n\nimport (\n\t\"fmt\"\n\n\t\"sigs.k8s.io/yaml\"\n)\n\n// Tree is a structured representation of a string keyed tree document such as\n// yaml or json.\ntype Tree map[string]interface{}\n\n// ToYAML returns a yaml serialization of the Tree.\nfunc (t Tree) ToYAML() (string, error) {\n\tbytes, err := yaml.Marshal(t)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(bytes), nil\n}\n\n// String returns a yaml representation of the Tree or an error string if\n// serialization fails.\nfunc (t Tree) String() string {\n\ts, err := t.ToYAML()\n\tif err != nil {\n\t\treturn err.Error()\n\t}\n\treturn s\n}\n\n// GetString returns the string value at the given path\nfunc (t Tree) GetString(path ...string) (string, error) {\n\tif len(path) == 1 {\n\t\t// check if exists\n\t\tif val, ok := t[path[0]]; ok {\n\t\t\t// check if string\n\t\t\tif s, ok := val.(string); ok {\n\t\t\t\treturn s, nil\n\t\t\t}\n\t\t\treturn \"\", fmt.Errorf(\"expected string at node %s but found a different type\", path[0])\n\t\t}\n\t\treturn \"\", fmt.Errorf(\"could not find node %s\", path[0])\n\t}\n\n\t// check if exists\n\tif val, ok := t[path[0]]; ok {\n\t\t// Check if its a Tree\n\t\tif valTree, ok := val.(Tree); ok {\n\t\t\treturn valTree.GetString(path[1:]...)\n\t\t}\n\t\treturn \"\", fmt.Errorf(\"expected Tree at node %s but found a different type\", path[0])\n\t}\n\treturn \"\", fmt.Errorf(\"could not find node %s\", path[0])\n}\n\n// Diff returns the subset of other where its values differ from t.\nfunc (t Tree) Diff(other Tree) (Tree, error) {\n\tdiff := make(Tree)\n\tfor k, v := range other {\n\t\ttv, ok := t[k]\n\t\tif ok {\n\t\t\ttvt, tvIsTree := tv.(Tree)\n\t\t\tvt, vIsTree := v.(Tree)\n\t\t\tif tvIsTree && vIsTree {\n\t\t\t\tsubdiff, err := tvt.Diff(vt)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tdiff[k] = subdiff\n\t\t\t} else if !tvIsTree && !vIsTree {\n\t\t\t\tif !equal(v, tv) {\n\t\t\t\t\tdiff[k] = v\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff[k] = v\n\t\t\t}\n\t\t} else {\n\t\t\tdiff[k] = v\n\t\t}\n\t}\n\tdiff.Prune()\n\treturn diff, nil\n}\n\nfunc equal(x interface{}, y interface{}) bool {\n\txt, xIsTree := x.(Tree)\n\tyt, yIsTree := y.(Tree)\n\tif xIsTree && yIsTree {\n\t\tif len(xt) != len(yt) {\n\t\t\treturn false\n\t\t}\n\t\tfor k := range xt {\n\t\t\tif !equal(xt[k], yt[k]) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\tif xIsTree || yIsTree {\n\t\treturn false\n\t}\n\txs, xIsSlice := x.([]interface{})\n\tys, yIsSlice := x.([]interface{})\n\tif xIsSlice && yIsSlice {\n\t\tif len(xs) != len(ys) {\n\t\t\treturn false\n\t\t}\n\t\tfor i := range xs {\n\t\t\tif !equal(xs[i], ys[i]) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\tif xIsSlice || yIsSlice {\n\t\treturn false\n\t}\n\treturn x == y\n}\n\n// Prune removes all empty subtrees.  A subtree is considered empty if it does\n// not contain any leaf values.\nfunc (t Tree) Prune() {\n\tfor k, v := range t {\n\t\tchild, isTree := v.(Tree)\n\t\tif isTree {\n\t\t\tif child.Empty() {\n\t\t\t\tdelete(t, k)\n\t\t\t} else {\n\t\t\t\tchild.Prune()\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Empty returns true iff the Tree contains no leaf values.\nfunc (t Tree) Empty() bool {\n\tfor _, v := range t {\n\t\tchild, isTree := v.(Tree)\n\t\tif !isTree {\n\t\t\treturn false\n\t\t}\n\t\tif !child.Empty() {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// MarshalToTree marshals obj to yaml and then parses the resulting yaml as\n// a Tree.\nfunc MarshalToTree(obj interface{}) (Tree, error) {\n\tbytes, err := yaml.Marshal(obj)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn BytesToTree(bytes)\n}\n\n// BytesToTree converts given bytes into a tree by Unmarshaling\nfunc BytesToTree(bytes []byte) (Tree, error) {\n\ttree := make(Tree)\n\terr := yaml.Unmarshal(bytes, &tree)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttree.coerceToTree()\n\treturn tree, nil\n}\n\n// Diff marshals two objects into their yaml representations and then performs\n// a diff on those Trees.  It returns a Tree which represents all of the fields\n// in y which differ from x.\nfunc Diff(x interface{}, y interface{}) (Tree, error) {\n\txTree, err := MarshalToTree(x)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tyTree, err := MarshalToTree(y)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn xTree.Diff(yTree)\n}\n\n// coerceTreeValue accepts a value and returns a value where all child values\n// have been coerced to a Tree where such a coercion is possible\nfunc coerceTreeValue(v interface{}) interface{} {\n\tif vt, ok := v.(Tree); ok {\n\t\tvt.coerceToTree()\n\t} else if vm, ok := v.(map[string]interface{}); ok {\n\t\ttree := Tree(vm)\n\t\ttree.coerceToTree()\n\t\treturn tree\n\t} else if va, ok := v.([]interface{}); ok {\n\t\tfor i, v := range va {\n\t\t\tva[i] = coerceTreeValue(v)\n\t\t}\n\t}\n\treturn v\n}\n\n// coerceToTree recursively casts all instances of map[string]interface{} into\n// Tree within this Tree.  When a tree document is unmarshaled, the subtrees\n// will typically be unmarshaled as map[string]interface{} values.  We cast\n// each of these into the Tree newtype so that the Tree type is used uniformly\n// throughout the tree. Will additionally recurse through arrays\nfunc (t Tree) coerceToTree() {\n\tfor k, v := range t {\n\t\tt[k] = coerceTreeValue(v)\n\t}\n}\n"
  },
  {
    "path": "pkg/tree/tree_test.go",
    "content": "package tree\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\tl5dcharts \"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n)\n\nfunc TestTreeGetString(t *testing.T) {\n\t// Build Tree and check the return values\n\tvals, err := l5dcharts.NewValues()\n\tif err != nil {\n\t\tt.Fatalf(\"expected no error; got %s\", err)\n\t}\n\n\tvalues, err := MarshalToTree(vals)\n\tif err != nil {\n\t\tt.Fatalf(\"expected no error; got %s\", err)\n\t}\n\n\ttestCases := []struct {\n\t\ttree  Tree\n\t\tpath  []string\n\t\tvalue string\n\t\terr   error\n\t}{\n\t\t{\n\t\t\tvalues,\n\t\t\t[]string{\"global\", \"namespace\"},\n\t\t\t\"\",\n\t\t\tfmt.Errorf(\"could not find node global\"),\n\t\t},\n\t\t{\n\t\t\tvalues,\n\t\t\t[]string{\"global\"},\n\t\t\t\"\",\n\t\t\tfmt.Errorf(\"could not find node global\"),\n\t\t},\n\t\t{\n\t\t\tvalues,\n\t\t\t[]string{\"proxy\", \"image\"},\n\t\t\t\"\",\n\t\t\tfmt.Errorf(\"expected string at node image but found a different type\"),\n\t\t},\n\t\t{\n\t\t\tvalues,\n\t\t\t[]string{\"proxy\", \"logFormat\"},\n\t\t\t\"plain\",\n\t\t\tnil,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%s: %s, %v\", strings.Join(tc.path, \"/\"), tc.value, tc.err), func(t *testing.T) {\n\t\t\tfinalValue, err := tc.tree.GetString(tc.path...)\n\t\t\tif err != nil {\n\t\t\t\tif tc.err != nil {\n\t\t\t\t\tassert(t, err.Error(), tc.err.Error())\n\t\t\t\t} else {\n\t\t\t\t\tt.Fatalf(\"expected no error; got %s\", err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tassert(t, finalValue, tc.value)\n\t\t})\n\t}\n}\n\nfunc assert(t *testing.T, received, expected string) {\n\tif expected != received {\n\t\tt.Fatalf(\"Expected %v, got %v\", expected, received)\n\t}\n}\n"
  },
  {
    "path": "pkg/util/http.go",
    "content": "package util\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\thttpPb \"github.com/linkerd/linkerd2-proxy-api/go/http_types\"\n)\n\n// KB = Kilobyte\nconst KB = 1024\n\n// MB = Megabyte\nconst MB = KB * 1024\n\n// ParseScheme converts a scheme string to protobuf\n// TODO: validate scheme\nfunc ParseScheme(scheme string) *httpPb.Scheme {\n\tvalue, ok := httpPb.Scheme_Registered_value[strings.ToUpper(scheme)]\n\tif ok {\n\t\treturn &httpPb.Scheme{\n\t\t\tType: &httpPb.Scheme_Registered_{\n\t\t\t\tRegistered: httpPb.Scheme_Registered(value),\n\t\t\t},\n\t\t}\n\t}\n\treturn &httpPb.Scheme{\n\t\tType: &httpPb.Scheme_Unregistered{\n\t\t\tUnregistered: strings.ToUpper(scheme),\n\t\t},\n\t}\n}\n\n// ParseMethod converts a method string to protobuf\n// TODO: validate method\nfunc ParseMethod(method string) *httpPb.HttpMethod {\n\tvalue, ok := httpPb.HttpMethod_Registered_value[strings.ToUpper(method)]\n\tif ok {\n\t\treturn &httpPb.HttpMethod{\n\t\t\tType: &httpPb.HttpMethod_Registered_{\n\t\t\t\tRegistered: httpPb.HttpMethod_Registered(value),\n\t\t\t},\n\t\t}\n\t}\n\treturn &httpPb.HttpMethod{\n\t\tType: &httpPb.HttpMethod_Unregistered{\n\t\t\tUnregistered: strings.ToUpper(method),\n\t\t},\n\t}\n}\n\n// ReadAllLimit reads from r until EOF or until limit bytes are read. If EOF is\n// reached, the full bytes are returned. If the limit is reached, an error is\n// returned.\nfunc ReadAllLimit(r io.Reader, limit int) ([]byte, error) {\n\tbytes, err := io.ReadAll(io.LimitReader(r, int64(limit)))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(bytes) == limit {\n\t\treturn nil, fmt.Errorf(\"limit reached while reading: %d\", limit)\n\t}\n\treturn bytes, nil\n}\n"
  },
  {
    "path": "pkg/util/http_test.go",
    "content": "package util\n\nimport (\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\thttpPb \"github.com/linkerd/linkerd2-proxy-api/go/http_types\"\n)\n\nfunc TestParseScheme(t *testing.T) {\n\tcases := []struct {\n\t\tscheme   string\n\t\texpected *httpPb.Scheme\n\t}{\n\t\t{\n\t\t\tscheme: \"http\",\n\t\t\texpected: &httpPb.Scheme{\n\t\t\t\tType: &httpPb.Scheme_Registered_{\n\t\t\t\t\tRegistered: 0,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tscheme: \"https\",\n\t\t\texpected: &httpPb.Scheme{\n\t\t\t\tType: &httpPb.Scheme_Registered_{\n\t\t\t\t\tRegistered: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tscheme: \"unknown\",\n\t\t\texpected: &httpPb.Scheme{\n\t\t\t\tType: &httpPb.Scheme_Unregistered{\n\t\t\t\t\tUnregistered: \"UNKNOWN\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tc := c\n\t\tt.Run(c.scheme, func(t *testing.T) {\n\t\t\tgot := ParseScheme(c.scheme)\n\t\t\tif diff := deep.Equal(c.expected, got); diff != nil {\n\t\t\t\tt.Errorf(\"%v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseMethod(t *testing.T) {\n\tcases := []struct {\n\t\tmethod   string\n\t\texpected *httpPb.HttpMethod\n\t}{\n\t\t{\n\t\t\tmethod: \"get\",\n\t\t\texpected: &httpPb.HttpMethod{\n\t\t\t\tType: &httpPb.HttpMethod_Registered_{\n\t\t\t\t\tRegistered: 0,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmethod: \"post\",\n\t\t\texpected: &httpPb.HttpMethod{\n\t\t\t\tType: &httpPb.HttpMethod_Registered_{\n\t\t\t\t\tRegistered: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmethod: \"put\",\n\t\t\texpected: &httpPb.HttpMethod{\n\t\t\t\tType: &httpPb.HttpMethod_Registered_{\n\t\t\t\t\tRegistered: 2,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmethod: \"delete\",\n\t\t\texpected: &httpPb.HttpMethod{\n\t\t\t\tType: &httpPb.HttpMethod_Registered_{\n\t\t\t\t\tRegistered: 3,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmethod: \"patch\",\n\t\t\texpected: &httpPb.HttpMethod{\n\t\t\t\tType: &httpPb.HttpMethod_Registered_{\n\t\t\t\t\tRegistered: 4,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmethod: \"options\",\n\t\t\texpected: &httpPb.HttpMethod{\n\t\t\t\tType: &httpPb.HttpMethod_Registered_{\n\t\t\t\t\tRegistered: 5,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmethod: \"connect\",\n\t\t\texpected: &httpPb.HttpMethod{\n\t\t\t\tType: &httpPb.HttpMethod_Registered_{\n\t\t\t\t\tRegistered: 6,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmethod: \"head\",\n\t\t\texpected: &httpPb.HttpMethod{\n\t\t\t\tType: &httpPb.HttpMethod_Registered_{\n\t\t\t\t\tRegistered: 7,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmethod: \"trace\",\n\t\t\texpected: &httpPb.HttpMethod{\n\t\t\t\tType: &httpPb.HttpMethod_Registered_{\n\t\t\t\t\tRegistered: 8,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmethod: \"unknown\",\n\t\t\texpected: &httpPb.HttpMethod{\n\t\t\t\tType: &httpPb.HttpMethod_Unregistered{\n\t\t\t\t\tUnregistered: \"UNKNOWN\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tc := c\n\t\tt.Run(c.method, func(t *testing.T) {\n\t\t\tgot := ParseMethod(c.method)\n\t\t\tif diff := deep.Equal(c.expected, got); diff != nil {\n\t\t\t\tt.Errorf(\"%v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/util/parsing.go",
    "content": "package util\n\nimport (\n\t\"strings\"\n\n\tlog \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// ParsePorts parses the given ports string into a map of ports;\n// this includes converting port ranges into separate ports\nfunc ParsePorts(portsString string) map[uint32]struct{} {\n\topaquePorts := make(map[uint32]struct{})\n\tif portsString != \"\" {\n\t\tportRanges := GetPortRanges(portsString)\n\t\tfor _, pr := range portRanges {\n\t\t\tportsRange, err := ParsePortRange(pr)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warnf(\"Invalid port range [%v]: %s\", pr, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor i := portsRange.LowerBound; i <= portsRange.UpperBound; i++ {\n\t\t\t\topaquePorts[uint32(i)] = struct{}{}\n\t\t\t}\n\n\t\t}\n\t}\n\treturn opaquePorts\n}\n\n// ParseContainerOpaquePorts parses the opaque ports annotation into a list of\n// port ranges, including validating port ranges and converting named ports\n// into their port number equivalents.\nfunc ParseContainerOpaquePorts(override string, namedPorts map[string]int32) []PortRange {\n\tportRanges := GetPortRanges(override)\n\tvar values []PortRange\n\tfor _, pr := range portRanges {\n\t\tport, named := namedPorts[pr]\n\t\tif named {\n\t\t\tvalues = append(values, PortRange{UpperBound: int(port), LowerBound: int(port)})\n\t\t} else {\n\t\t\tpr, err := ParsePortRange(pr)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warnf(\"Invalid port range [%v]: %s\", pr, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvalues = append(values, pr)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc GetNamedPorts(containers []corev1.Container) map[string]int32 {\n\tnamedPorts := make(map[string]int32)\n\tfor _, container := range containers {\n\t\tfor _, p := range container.Ports {\n\t\t\tif p.Name != \"\" {\n\t\t\t\tnamedPorts[p.Name] = p.ContainerPort\n\t\t\t}\n\t\t}\n\t}\n\n\treturn namedPorts\n}\n\n// GetPortRanges gets port ranges from an override annotation\nfunc GetPortRanges(override string) []string {\n\tvar ports []string\n\tfor _, port := range strings.Split(strings.TrimSuffix(override, \",\"), \",\") {\n\t\tports = append(ports, strings.TrimSpace(port))\n\t}\n\n\treturn ports\n}\n\n// ContainsString checks if a string collections contains the given string.\nfunc ContainsString(str string, collection []string) bool {\n\tfor _, e := range collection {\n\t\tif str == e {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/util/parsing_test.go",
    "content": "package util\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n)\n\nfunc TestParsePorts(t *testing.T) {\n\ttestCases := []struct {\n\t\tports  string\n\t\tresult map[uint32]struct{}\n\t}{\n\t\t{\n\t\t\t\"25,443,587,3306,5432,11211\",\n\t\t\tmap[uint32]struct{}{\n\t\t\t\t25:    {},\n\t\t\t\t443:   {},\n\t\t\t\t587:   {},\n\t\t\t\t3306:  {},\n\t\t\t\t5432:  {},\n\t\t\t\t11211: {},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"25,443-447,3306,5432-5435,11211\",\n\t\t\tmap[uint32]struct{}{\n\t\t\t\t25:    {},\n\t\t\t\t443:   {},\n\t\t\t\t444:   {},\n\t\t\t\t445:   {},\n\t\t\t\t446:   {},\n\t\t\t\t447:   {},\n\t\t\t\t3306:  {},\n\t\t\t\t5432:  {},\n\t\t\t\t5433:  {},\n\t\t\t\t5434:  {},\n\t\t\t\t5435:  {},\n\t\t\t\t11211: {},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"test %s\", tc.ports), func(t *testing.T) {\n\t\t\tports := ParsePorts(tc.ports)\n\t\t\tif diff := deep.Equal(ports, tc.result); diff != nil {\n\t\t\t\tt.Errorf(\"%v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/util/portrange.go",
    "content": "package util\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// PortRange defines the upper- and lower-bounds for a range of ports.\ntype PortRange struct {\n\tLowerBound int\n\tUpperBound int\n}\n\n// ParsePort parses and verifies the validity of the port candidate.\nfunc ParsePort(port string) (int, error) {\n\ti, err := strconv.Atoi(port)\n\tif err != nil || !isPort(i) {\n\t\treturn -1, fmt.Errorf(\"\\\"%s\\\" is not a valid TCP port\", port)\n\t}\n\treturn i, nil\n}\n\n// ParsePortRange parses and checks the provided range candidate to ensure it is\n// a valid TCP port range.\nfunc ParsePortRange(portRange string) (PortRange, error) {\n\tbounds := strings.Split(portRange, \"-\")\n\tif len(bounds) > 2 {\n\t\treturn PortRange{}, fmt.Errorf(\"ranges expected as <lower>-<upper>\")\n\t}\n\tif len(bounds) == 1 {\n\t\t// If only provided a single value, treat as both lower- and upper-bounds\n\t\tbounds = append(bounds, bounds[0])\n\t}\n\tlower, err := strconv.Atoi(bounds[0])\n\tif err != nil || !isPort(lower) {\n\t\treturn PortRange{}, fmt.Errorf(\"\\\"%s\\\" is not a valid lower-bound\", bounds[0])\n\t}\n\tupper, err := strconv.Atoi(bounds[1])\n\tif err != nil || !isPort(upper) {\n\t\treturn PortRange{}, fmt.Errorf(\"\\\"%s\\\" is not a valid upper-bound\", bounds[1])\n\t}\n\tif upper < lower {\n\t\treturn PortRange{}, fmt.Errorf(\"\\\"%s\\\": upper-bound must be greater than or equal to lower-bound\", portRange)\n\t}\n\treturn PortRange{LowerBound: lower, UpperBound: upper}, nil\n}\n\n// isPort checks the provided to determine whether or not the port\n// candidate is a valid TCP port number. Valid TCP ports range from 0 to 65535.\nfunc isPort(port int) bool {\n\treturn 0 <= port && port <= 65535\n}\n\n// Ports returns an array of all the ports contained by this range.\nfunc (pr PortRange) Ports() []uint16 {\n\tvar ports []uint16\n\tfor i := pr.LowerBound; i <= pr.UpperBound; i++ {\n\t\tports = append(ports, uint16(i))\n\t}\n\treturn ports\n}\n\nfunc (pr PortRange) ToString() string {\n\tif pr.LowerBound == pr.UpperBound {\n\t\treturn strconv.Itoa(pr.LowerBound)\n\t}\n\n\treturn fmt.Sprintf(\"%d-%d\", pr.LowerBound, pr.UpperBound)\n}\n"
  },
  {
    "path": "pkg/util/portrange_test.go",
    "content": "package util\n\nimport (\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestParsePort(t *testing.T) {\n\ttests := []struct {\n\t\tinput  string\n\t\texpect int\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"8080\", 8080},\n\t\t{\"65535\", 65535},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input, func(t *testing.T) {\n\t\t\tif check, _ := ParsePort(tt.input); check != tt.expect {\n\t\t\t\tt.Fatalf(\"expected %d but received %d\", tt.expect, check)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParsePort_Errors(t *testing.T) {\n\ttests := []string{\"-1\", \"65536\"}\n\tfor _, tt := range tests {\n\t\tt.Run(tt, func(t *testing.T) {\n\t\t\tif r, err := ParsePort(tt); err == nil {\n\t\t\t\tt.Fatalf(\"expected error but received %d\", r)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParsePortRange(t *testing.T) {\n\ttests := []struct {\n\t\tinput    string\n\t\texpected PortRange\n\t}{\n\t\t{\"23-23\", PortRange{LowerBound: 23, UpperBound: 23}},\n\t\t{\"25-27\", PortRange{LowerBound: 25, UpperBound: 27}},\n\t\t{\"0-65535\", PortRange{LowerBound: 0, UpperBound: 65535}},\n\t\t{\"33\", PortRange{LowerBound: 33, UpperBound: 33}},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input, func(t *testing.T) {\n\t\t\tcheck, _ := ParsePortRange(tt.input)\n\t\t\treflect.DeepEqual(tt.expected, check)\n\t\t})\n\t}\n}\n\nfunc TestParsePortRange_Errors(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\tcheck string\n\t}{\n\t\t{\"\", \"not a valid lower-bound\"},\n\t\t{\"notanumber\", \"not a valid lower-bound\"},\n\t\t{\"not-number\", \"not a valid lower-bound\"},\n\t\t{\"-23-25\", \"ranges expected as\"},\n\t\t{\"-23\", \"not a valid lower-bound\"},\n\t\t{\"25-23\", \"upper-bound must be greater than or equal to\"},\n\t\t{\"65536-65539\", \"not a valid lower-bound\"},\n\t\t{\"23-notanumber\", \"not a valid upper-bound\"},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input, func(t *testing.T) {\n\t\t\t_, err := ParsePortRange(tt.input)\n\t\t\tassertError(t, err, tt.check)\n\t\t})\n\t}\n}\n\n// assertError confirms that the provided is an error having the provided message.\nfunc assertError(t *testing.T, err error, containing string) {\n\tif err == nil {\n\t\tt.Fatal(\"expected error; got nothing\")\n\t}\n\tif !strings.Contains(err.Error(), containing) {\n\t\tt.Fatalf(\"expected error to contain '%s' but received '%s'\", containing, err.Error())\n\t}\n}\n"
  },
  {
    "path": "pkg/version/channels.go",
    "content": "package version\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\n\t\"github.com/linkerd/linkerd2/pkg/util\"\n)\n\n// Channels provides an interface to interact with a set of release channels.\n// This module is also responsible for online retrieval of the latest release\n// versions.\ntype Channels struct {\n\tarray []channelVersion\n}\n\nvar (\n\t// CheckURL provides an online endpoint for Linkerd's version checks\n\tCheckURL = \"https://versioncheck.linkerd.io/version.json\"\n)\n\n// NewChannels is used primarily for testing, it returns a Channels struct that\n// mimic a GetLatestVersions response.\nfunc NewChannels(channel string) (Channels, error) {\n\tcv, err := parseChannelVersion(channel)\n\tif err != nil {\n\t\treturn Channels{}, err\n\t}\n\n\treturn Channels{\n\t\tarray: []channelVersion{cv},\n\t}, nil\n}\n\n// Match validates whether the given version string:\n// 1) is a well-formed channel-version string, for example: \"edge-19.1.2\"\n// 2) references a known channel\n// 3) matches the version in the known channel\nfunc (c Channels) Match(actualVersion string) error {\n\tif actualVersion == \"\" {\n\t\treturn errors.New(\"actual version is empty\")\n\t}\n\n\tif c.Empty() {\n\t\treturn errors.New(\"unable to determine version channel\")\n\t}\n\n\tactual, err := parseChannelVersion(actualVersion)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse actual version: %w\", err)\n\t}\n\n\tfor _, cv := range c.array {\n\t\tif cv.updateChannel() == actual.updateChannel() {\n\t\t\treturn match(cv.String(), actualVersion)\n\t\t}\n\t}\n\n\treturn fmt.Errorf(\"unsupported version channel: %s\", actualVersion)\n}\n\n// Determines whether there are any release channels stored in the struct.\nfunc (c Channels) Empty() bool {\n\treturn len(c.array) == 0\n}\n\n// GetLatestVersions performs an online request to check for the latest Linkerd\n// release channels.\nfunc GetLatestVersions(ctx context.Context, uuid string, source string) (Channels, error) {\n\turl := fmt.Sprintf(\"%s?version=%s&uuid=%s&source=%s\", CheckURL, Version, uuid, source)\n\treturn getLatestVersions(ctx, http.DefaultClient, url)\n}\n\nfunc getLatestVersions(ctx context.Context, client *http.Client, url string) (Channels, error) {\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn Channels{}, err\n\t}\n\n\trsp, err := client.Do(req.WithContext(ctx))\n\tif err != nil {\n\t\tvar dnsError *net.DNSError\n\t\tif errors.As(err, &dnsError) {\n\t\t\treturn Channels{}, fmt.Errorf(\"failed to resolve version check server: %s\", url)\n\t\t}\n\t\treturn Channels{}, err\n\t}\n\tdefer rsp.Body.Close()\n\n\tif rsp.StatusCode != 200 {\n\t\treturn Channels{}, fmt.Errorf(\"unexpected versioncheck response: %s\", rsp.Status)\n\t}\n\n\tbytes, err := util.ReadAllLimit(rsp.Body, util.MB)\n\tif err != nil {\n\t\treturn Channels{}, err\n\t}\n\n\tvar versionRsp map[string]string\n\terr = json.Unmarshal(bytes, &versionRsp)\n\tif err != nil {\n\t\treturn Channels{}, err\n\t}\n\n\tchannels := Channels{}\n\tfor c, v := range versionRsp {\n\t\tcv, err := parseChannelVersion(v)\n\t\tif err != nil {\n\t\t\treturn Channels{}, fmt.Errorf(\"unexpected versioncheck response: %w\", err)\n\t\t}\n\n\t\tif c != cv.updateChannel() {\n\t\t\treturn Channels{}, fmt.Errorf(\"unexpected versioncheck response: channel in %s does not match %s\", cv, c)\n\t\t}\n\n\t\tchannels.array = append(channels.array, cv)\n\t}\n\n\treturn channels, nil\n}\n"
  },
  {
    "path": "pkg/version/channels_test.go",
    "content": "package version\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestGetLatestVersions(t *testing.T) {\n\tfour := int64(4)\n\ttestCases := []struct {\n\t\tname   string\n\t\tresp   interface{}\n\t\terr    error\n\t\tlatest Channels\n\t}{\n\t\t{\n\t\t\t\"valid response\",\n\t\t\tmap[string]string{\n\t\t\t\t\"foo\":         \"foo-1.2.3\",\n\t\t\t\t\"fooHotpatch\": \"foo-1.2.3-4\",\n\t\t\t\t\"stable\":      \"stable-2.1.0\",\n\t\t\t\t\"edge\":        \"edge-2.1.0\",\n\t\t\t},\n\t\t\tnil,\n\t\t\tChannels{\n\t\t\t\t[]channelVersion{\n\t\t\t\t\t{\"foo\", \"1.2.3\", nil, \"foo-1.2.3\"},\n\t\t\t\t\t{\"foo\", \"1.2.3\", &four, \"foo-1.2.3-4\"},\n\t\t\t\t\t{\"stable\", \"2.1.0\", nil, \"stable-2.1.0\"},\n\t\t\t\t\t{\"edge\", \"2.1.0\", nil, \"edge-2.1.0\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"channel version mismatch\",\n\t\t\tmap[string]string{\n\t\t\t\t\"foo\":        \"foo-1.2.3\",\n\t\t\t\t\"stable\":     \"stable-2.1.0\",\n\t\t\t\t\"badchannel\": \"edge-2.1.0\",\n\t\t\t},\n\t\t\tfmt.Errorf(\"unexpected versioncheck response: channel in edge-2.1.0 does not match badchannel\"),\n\t\t\tChannels{},\n\t\t},\n\t\t{\n\t\t\t\"invalid version\",\n\t\t\tmap[string]string{\n\t\t\t\t\"foo\":    \"foo-1.2.3\",\n\t\t\t\t\"stable\": \"badchannelversion\",\n\t\t\t},\n\t\t\tfmt.Errorf(\"unexpected versioncheck response: unsupported version format: badchannelversion\"),\n\t\t\tChannels{},\n\t\t},\n\t\t{\n\t\t\t\"invalid JSON\",\n\t\t\t\"bad response\",\n\t\t\tfmt.Errorf(\"json: cannot unmarshal string into Go value of type map[string]string\"),\n\t\t\tChannels{},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tj, err := json.Marshal(tc.resp)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"JSON marshal failed with: %s\", err)\n\t\t\t}\n\n\t\t\tts := httptest.NewServer(http.HandlerFunc(\n\t\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tw.Write(j)\n\t\t\t\t}),\n\t\t\t)\n\t\t\tdefer ts.Close()\n\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\t\tdefer cancel()\n\t\t\tlatest, err := getLatestVersions(ctx, ts.Client(), ts.URL)\n\t\t\tif (err == nil && tc.err != nil) ||\n\t\t\t\t(err != nil && tc.err == nil) ||\n\t\t\t\t((err != nil && tc.err != nil) && (err.Error() != tc.err.Error())) {\n\t\t\t\tt.Fatalf(\"Expected \\\"%s\\\", got \\\"%s\\\"\", tc.err, err)\n\t\t\t}\n\n\t\t\tif !channelsEqual(latest, tc.latest) {\n\t\t\t\tt.Fatalf(\"Expected latest versions \\\"%s\\\", got \\\"%s\\\"\", tc.latest, latest)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc channelsEqual(c1, c2 Channels) bool {\n\tif len(c1.array) != len(c2.array) {\n\t\treturn false\n\t}\n\n\tfor _, cv1 := range c1.array {\n\t\tfound := false\n\t\tfor _, cv2 := range c2.array {\n\t\t\tif cv1.channel == cv2.channel && cv1.version == cv2.version && cv1.hotpatchEqual(cv2) {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc TestChannelsMatch(t *testing.T) {\n\tfour := int64(4)\n\tchannels := Channels{\n\t\t[]channelVersion{\n\t\t\t{\"stable\", \"2.1.0\", nil, \"stable-2.1.0\"},\n\t\t\t{\"foo\", \"1.2.3\", nil, \"foo-1.2.3\"},\n\t\t\t{\"foo\", \"1.2.3\", &four, \"foo-1.2.3-4\"},\n\t\t\t{\"version\", \"3.2.1\", nil, \"version-3.2.1\"},\n\t\t},\n\t}\n\n\ttestCases := []struct {\n\t\tactualVersion string\n\t\terr           error\n\t}{\n\t\t{\"stable-2.1.0\", nil},\n\t\t{\"stable-2.1.0-buildinfo\", nil},\n\t\t{\"foo-1.2.3\", nil},\n\t\t{\"foo-1.2.3-4\", nil},\n\t\t{\"foo-1.2.3-4-buildinfo\", nil},\n\t\t{\"version-3.2.1\", nil},\n\t\t{\n\t\t\t\"foo-1.2.2\",\n\t\t\tfmt.Errorf(\"is running version 1.2.2 but the latest foo version is 1.2.3\"),\n\t\t},\n\t\t{\n\t\t\t\"foo-1.2.3-3\",\n\t\t\tfmt.Errorf(\"is running version 1.2.3-3 but the latest foo version is 1.2.3-4\"),\n\t\t},\n\t\t{\n\t\t\t\"unsupportedChannel-1.2.3\",\n\t\t\tfmt.Errorf(\"unsupported version channel: unsupportedChannel-1.2.3\"),\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"test %d ChannelsMatch(%s, %s)\", i, tc.actualVersion, tc.err), func(t *testing.T) {\n\t\t\terr := channels.Match(tc.actualVersion)\n\t\t\tif (err == nil && tc.err != nil) ||\n\t\t\t\t(err != nil && tc.err == nil) ||\n\t\t\t\t((err != nil && tc.err != nil) && (err.Error() != tc.err.Error())) {\n\t\t\t\tt.Fatalf(\"Expected \\\"%s\\\", got \\\"%s\\\"\", tc.err, err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/version/channelversion.go",
    "content": "package version\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// channelVersion is a low-level struct for handling release channels in a\n// structured way. It has no dependencies on the rest of the version package.\ntype channelVersion struct {\n\tchannel  string\n\tversion  string\n\thotpatch *int64\n\toriginal string\n}\n\n// hotpatchSuffix is the suffix applied to channel names to indicate that the\n// version string includes a hotpatch number (e.g. dev-0.1.2-3)\nconst hotpatchSuffix = \"Hotpatch\"\n\nfunc (cv channelVersion) String() string {\n\treturn cv.original\n}\n\n// updateChannel returns the channel name to check for updates. if there's no\n// hotpatch number set, then it returns the channel name itself. otherwise it\n// returns the channel name suffixed with \"Hotpatch\" to indicate that a separate\n// update channel should be used.\nfunc (cv channelVersion) updateChannel() string {\n\tif cv.hotpatch != nil {\n\t\treturn cv.channel + hotpatchSuffix\n\t}\n\treturn cv.channel\n}\n\n// versionWithHotpatch returns the version string, suffixed with the hotpatch\n// number if it exists.\nfunc (cv channelVersion) versionWithHotpatch() string {\n\tif cv.hotpatch == nil {\n\t\treturn cv.version\n\t}\n\treturn fmt.Sprintf(\"%s-%d\", cv.version, *cv.hotpatch)\n}\n\nfunc (cv channelVersion) hotpatchEqual(other channelVersion) bool {\n\tif cv.hotpatch == nil && other.hotpatch == nil {\n\t\treturn true\n\t}\n\tif cv.hotpatch == nil || other.hotpatch == nil {\n\t\treturn false\n\t}\n\treturn *cv.hotpatch == *other.hotpatch\n}\n\n// parseChannelVersion parses a build string into a channelVersion struct. it\n// expects the channel and version to be separated by a hyphen (e.g. dev-0.1.2).\n// the version may additionally include a hotpatch number, which is separated\n// from the base version by another hyphen (e.g. dev-0.1.2-3). if the version is\n// suffixed with any other non-numeric build info strings (e.g. dev-0.1.2-foo),\n// those strings are ignored.\nfunc parseChannelVersion(cv string) (channelVersion, error) {\n\tparts := strings.Split(cv, \"-\")\n\tif len(parts) < 2 {\n\t\treturn channelVersion{}, fmt.Errorf(\"unsupported version format: %s\", cv)\n\t}\n\n\tchannel := parts[0]\n\tversion := parts[1]\n\tvar hotpatch *int64\n\n\tfor _, part := range parts[2:] {\n\t\tif i, err := strconv.ParseInt(part, 10, 64); err == nil {\n\t\t\thotpatch = &i\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn channelVersion{channel, version, hotpatch, cv}, nil\n}\n\n// IsReleaseChannel returns true if the channel of the version is \"edge\" or\n// \"stable\".\nfunc IsReleaseChannel(version string) (bool, error) {\n\tcv, err := parseChannelVersion(version)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn cv.channel == \"edge\" || cv.channel == \"stable\", nil\n}\n"
  },
  {
    "path": "pkg/version/channelversion_test.go",
    "content": "package version\n\nimport (\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n)\n\nfunc TestIsReleaseChannel(t *testing.T) {\n\tcases := []struct {\n\t\tversion       string\n\t\texpected      bool\n\t\texpectedError bool\n\t}{\n\t\t{\n\t\t\tversion:  \"edge-1.0\",\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tversion:  \"stable-1.0\",\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tversion:  \"edge-\",\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tversion:  \"stable-\",\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tversion:       \"edge\",\n\t\t\texpected:      false,\n\t\t\texpectedError: true,\n\t\t},\n\t\t{\n\t\t\tversion:       \"stable\",\n\t\t\texpected:      false,\n\t\t\texpectedError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tc := c\n\t\tt.Run(c.version, func(t *testing.T) {\n\t\t\tgot, err := IsReleaseChannel(c.version)\n\t\t\tif (err != nil) != c.expectedError {\n\t\t\t\tt.Errorf(\"got unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif diff := deep.Equal(c.expected, got); diff != nil {\n\t\t\t\tt.Errorf(\"%v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/version/version.go",
    "content": "package version\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n)\n\n// Version is updated automatically as part of the build process, and is the\n// ground source of truth for the current process's build version.\n//\n// DO NOT EDIT\nvar Version = undefinedVersion\n\nvar LinkerdCNIVersion = \"v1.6.6\"\n\nconst (\n\t// undefinedVersion should take the form `channel-version` to conform to\n\t// channelVersion functions.\n\tundefinedVersion = \"dev-undefined\"\n)\n\nfunc init() {\n\t// Use `$LINKERD_CONTAINER_VERSION_OVERRIDE` as the version only if the\n\t// version wasn't set at link time to minimize the chance of using it\n\t// unintentionally. This mechanism allows the version to be bound at\n\t// container build time instead of at executable link time to improve\n\t// incremental rebuild efficiency.\n\tif Version == undefinedVersion {\n\t\toverride := os.Getenv(\"LINKERD_CONTAINER_VERSION_OVERRIDE\")\n\t\tif override != \"\" {\n\t\t\tVersion = override\n\t\t}\n\t}\n}\n\n// match compares two versions and returns success if they match, or an error\n// with a contextual message if they do not.\nfunc match(expectedVersion, actualVersion string) error {\n\tif expectedVersion == \"\" {\n\t\treturn errors.New(\"expected version is empty\")\n\t} else if actualVersion == \"\" {\n\t\treturn errors.New(\"actual version is empty\")\n\t}\n\n\tactual, err := parseChannelVersion(actualVersion)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse actual version: %w\", err)\n\t}\n\texpected, err := parseChannelVersion(expectedVersion)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse expected version: %w\", err)\n\t}\n\n\tif actual.channel != expected.channel {\n\t\treturn fmt.Errorf(\"mismatched channels: running %s but retrieved %s\",\n\t\t\tactual, expected)\n\t}\n\n\tif actual.version != expected.version || !actual.hotpatchEqual(expected) {\n\t\treturn fmt.Errorf(\"is running version %s but the latest %s version is %s\",\n\t\t\tactual.versionWithHotpatch(), actual.channel, expected.versionWithHotpatch())\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/version/version_test.go",
    "content": "package version\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nfunc TestMatch(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\texpected string\n\t\tactual   string\n\t\terr      error\n\t}{\n\t\t{\n\t\t\tname:     \"up-to-date\",\n\t\t\texpected: \"dev-0.1.2\",\n\t\t\tactual:   \"dev-0.1.2\",\n\t\t},\n\t\t{\n\t\t\tname:     \"up-to-date with same build info\",\n\t\t\texpected: \"dev-0.1.2-bar\",\n\t\t\tactual:   \"dev-0.1.2-bar\",\n\t\t},\n\t\t{\n\t\t\tname:     \"up-to-date with different build info\",\n\t\t\texpected: \"dev-0.1.2-bar\",\n\t\t\tactual:   \"dev-0.1.2-baz\",\n\t\t},\n\t\t{\n\t\t\tname:     \"up-to-date with hotpatch\",\n\t\t\texpected: \"dev-0.1.2-3\",\n\t\t\tactual:   \"dev-0.1.2-3\",\n\t\t},\n\t\t{\n\t\t\tname:     \"up-to-date with hotpatch and different build info\",\n\t\t\texpected: \"dev-0.1.2-3-bar\",\n\t\t\tactual:   \"dev-0.1.2-3-baz\",\n\t\t},\n\t\t{\n\t\t\tname:     \"not up-to-date\",\n\t\t\texpected: \"dev-0.1.2\",\n\t\t\tactual:   \"dev-0.1.1\",\n\t\t\terr:      errors.New(\"is running version 0.1.1 but the latest dev version is 0.1.2\"),\n\t\t},\n\t\t{\n\t\t\tname:     \"not up-to-date but with same build info\",\n\t\t\texpected: \"dev-0.1.2-bar\",\n\t\t\tactual:   \"dev-0.1.1-bar\",\n\t\t\terr:      errors.New(\"is running version 0.1.1 but the latest dev version is 0.1.2\"),\n\t\t},\n\t\t{\n\t\t\tname:     \"not up-to-date with hotpatch\",\n\t\t\texpected: \"dev-0.1.2-3\",\n\t\t\tactual:   \"dev-0.1.2-2\",\n\t\t\terr:      errors.New(\"is running version 0.1.2-2 but the latest dev version is 0.1.2-3\"),\n\t\t},\n\t\t{\n\t\t\tname:     \"mismatched channels\",\n\t\t\texpected: \"dev-0.1.2\",\n\t\t\tactual:   \"git-cb21f1bc\",\n\t\t\terr:      errors.New(\"mismatched channels: running git-cb21f1bc but retrieved dev-0.1.2\"),\n\t\t},\n\t\t{\n\t\t\tname:     \"expected version malformed\",\n\t\t\texpected: \"badformat\",\n\t\t\tactual:   \"dev-0.1.2\",\n\t\t\terr:      errors.New(\"failed to parse expected version: unsupported version format: badformat\"),\n\t\t},\n\t\t{\n\t\t\tname:     \"actual version malformed\",\n\t\t\texpected: \"dev-0.1.2\",\n\t\t\tactual:   \"badformat\",\n\t\t\terr:      errors.New(\"failed to parse actual version: unsupported version format: badformat\"),\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\terr := match(tc.expected, tc.actual)\n\t\t\tif (err == nil && tc.err != nil) ||\n\t\t\t\t(err != nil && tc.err == nil) ||\n\t\t\t\t((err != nil && tc.err != nil) && (err.Error() != tc.err.Error())) {\n\t\t\t\tt.Fatalf(\"Expected \\\"%s\\\", got \\\"%s\\\"\", tc.err, err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "policy-controller/.dockerignore",
    "content": "target\n"
  },
  {
    "path": "policy-controller/Cargo.toml",
    "content": "[package]\nname = \"linkerd-policy-controller\"\nversion = \"0.1.0\"\nedition = \"2021\"\nlicense = \"Apache-2.0\"\npublish = false\n\n[dependencies]\nanyhow = \"1\"\ntokio = { version = \"1\", features = [\"macros\", \"rt\", \"rt-multi-thread\"] }\nrustls = { version = \"0.23.37\", default-features = false, features = [\"aws-lc-rs\"] }\n\n[dependencies.linkerd-policy-controller-runtime]\nworkspace = true\n\n[target.x86_64-unknown-linux-gnu.dependencies]\njemallocator = \"0.5\"\n"
  },
  {
    "path": "policy-controller/core/Cargo.toml",
    "content": "[package]\nname = \"linkerd-policy-controller-core\"\nversion = \"0.1.0\"\nedition = \"2021\"\nlicense = \"Apache-2.0\"\npublish = false\n\n[dependencies]\nahash = \"0.8\"\nanyhow = \"1\"\nasync-trait = \"0.1\"\nchrono = { version = \"0.4.44\", default-features = false }\nfutures = { version = \"0.3\", default-features = false, features = [\"std\"] }\nhttp = { workspace = true }\nipnet = \"2\"\nregex = \"1\"\n"
  },
  {
    "path": "policy-controller/core/src/identity_match.rs",
    "content": "use std::{convert::Infallible, fmt, str::FromStr};\n\n/// Matches a client's mesh identity.\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub enum IdentityMatch {\n    /// An exact match.\n    Exact(String),\n\n    /// A suffix match.\n    Suffix(Vec<String>),\n}\n\n// === impl IdentityMatch ===\n\nimpl FromStr for IdentityMatch {\n    type Err = Infallible;\n\n    fn from_str(s: &str) -> Result<Self, Infallible> {\n        if s == \"*\" {\n            return Ok(IdentityMatch::Suffix(vec![]));\n        }\n\n        if s.starts_with(\"*.\") {\n            return Ok(IdentityMatch::Suffix(\n                s.split('.').skip(1).map(|s| s.to_string()).collect(),\n            ));\n        }\n\n        Ok(IdentityMatch::Exact(s.to_string()))\n    }\n}\n\nimpl fmt::Display for IdentityMatch {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        use std::fmt::Write;\n        match self {\n            Self::Exact(name) => name.fmt(f),\n            Self::Suffix(suffix) => {\n                f.write_char('*')?;\n                for part in suffix {\n                    write!(f, \".{part}\")?;\n                }\n                Ok(())\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn parse_star() {\n        assert_eq!(\"*\".parse(), Ok(IdentityMatch::Suffix(vec![])));\n\n        assert_eq!(\n            \"*.example.com\".parse(),\n            Ok(IdentityMatch::Suffix(vec![\n                \"example\".to_string(),\n                \"com\".to_string()\n            ]))\n        );\n        assert_eq!(\n            \"*.*.example.com\".parse(),\n            Ok(IdentityMatch::Suffix(vec![\n                \"*\".to_string(),\n                \"example\".to_string(),\n                \"com\".to_string()\n            ]))\n        );\n        assert_eq!(\n            \"x.example.com\".parse(),\n            Ok(IdentityMatch::Exact(\"x.example.com\".to_string()))\n        );\n\n        assert_eq!(\n            \"**.example.com\".parse(),\n            Ok(IdentityMatch::Exact(\"**.example.com\".to_string()))\n        );\n\n        assert_eq!(\n            \"foo.*.example.com\".parse(),\n            Ok(IdentityMatch::Exact(\"foo.*.example.com\".to_string()))\n        );\n    }\n}\n"
  },
  {
    "path": "policy-controller/core/src/inbound.rs",
    "content": "use crate::{\n    identity_match::IdentityMatch,\n    network_match::NetworkMatch,\n    routes::{\n        FailureInjectorFilter, GroupKindName, GrpcMethodMatch, GrpcRouteMatch,\n        HeaderModifierFilter, HostMatch, HttpRouteMatch, PathMatch, RequestRedirectFilter,\n    },\n};\nuse ahash::AHashMap as HashMap;\nuse anyhow::Result;\nuse chrono::{offset::Utc, DateTime};\nuse futures::prelude::*;\nuse std::{pin::Pin, time::Duration};\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum ServerRef {\n    Default(&'static str),\n    Server(String),\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub enum AuthorizationRef {\n    Default(&'static str),\n    ServerAuthorization(String),\n    AuthorizationPolicy(String),\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub enum RouteRef {\n    Default(&'static str),\n    Resource(GroupKindName),\n}\n\n/// Describes how a proxy should handle inbound connections.\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub enum ProxyProtocol {\n    /// Indicates that the protocol should be discovered dynamically.\n    Detect {\n        timeout: Duration,\n    },\n\n    Http1,\n    Http2,\n    Grpc,\n\n    /// Indicates that connections should be handled opaquely.\n    Opaque,\n\n    /// Indicates that connections should be handled as application-terminated TLS.\n    Tls,\n}\n\n/// Describes a class of authorized clients.\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct ClientAuthorization {\n    /// Limits which source networks this authorization applies to.\n    pub networks: Vec<NetworkMatch>,\n\n    /// Describes the client's authentication requirements.\n    pub authentication: ClientAuthentication,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum ClientAuthentication {\n    /// Indicates that clients need not be authenticated.\n    Unauthenticated,\n\n    /// Indicates that clients must use TLS but need not provide a client identity.\n    TlsUnauthenticated,\n\n    /// Indicates that clients must use mutually-authenticated TLS.\n    TlsAuthenticated(Vec<IdentityMatch>),\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct RateLimit {\n    pub name: String,\n    pub total: Option<Limit>,\n    pub identity: Option<Limit>,\n    pub overrides: Vec<Override>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct Limit {\n    pub requests_per_second: u32,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct Override {\n    pub requests_per_second: u32,\n    pub client_identities: Vec<String>,\n}\n\n/// Models inbound server configuration discovery.\n#[async_trait::async_trait]\npub trait DiscoverInboundServer<T> {\n    async fn get_inbound_server(&self, target: T) -> Result<Option<InboundServer>>;\n\n    async fn watch_inbound_server(&self, target: T) -> Result<Option<InboundServerStream>>;\n}\n\npub type InboundServerStream = Pin<Box<dyn Stream<Item = InboundServer> + Send + Sync + 'static>>;\n\n/// Inbound server configuration.\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct InboundServer {\n    pub reference: ServerRef,\n\n    pub protocol: ProxyProtocol,\n    pub authorizations: HashMap<AuthorizationRef, ClientAuthorization>,\n    pub ratelimit: Option<RateLimit>,\n    pub http_routes: HashMap<RouteRef, InboundRoute<HttpRouteMatch>>,\n    pub grpc_routes: HashMap<RouteRef, InboundRoute<GrpcRouteMatch>>,\n}\n\npub type HttpRoute = InboundRoute<HttpRouteMatch>;\npub type GrpcRoute = InboundRoute<GrpcRouteMatch>;\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct InboundRoute<M> {\n    pub hostnames: Vec<HostMatch>,\n    pub rules: Vec<InboundRouteRule<M>>,\n    pub authorizations: HashMap<AuthorizationRef, ClientAuthorization>,\n\n    /// This is required for ordering returned `HttpRoute`s by their creation\n    /// timestamp.\n    pub creation_timestamp: Option<DateTime<Utc>>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct InboundRouteRule<M> {\n    pub matches: Vec<M>,\n    pub filters: Vec<Filter>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum Filter {\n    RequestHeaderModifier(HeaderModifierFilter),\n    ResponseHeaderModifier(HeaderModifierFilter),\n    RequestRedirect(RequestRedirectFilter),\n    FailureInjector(FailureInjectorFilter),\n}\n\n// === impl InboundRoute ===\n\n/// The default `InboundRoute` used for any `InboundServer` that\n/// does not have routes.\nimpl Default for InboundRoute<HttpRouteMatch> {\n    fn default() -> Self {\n        Self {\n            hostnames: vec![],\n            rules: vec![InboundRouteRule {\n                matches: vec![HttpRouteMatch {\n                    path: Some(PathMatch::Prefix(\"/\".to_string())),\n                    headers: vec![],\n                    query_params: vec![],\n                    method: None,\n                }],\n                filters: vec![],\n            }],\n            // Default routes do not have authorizations; the default policy's\n            // authzs will be configured by the default `InboundServer`, not by\n            // the route.\n            authorizations: HashMap::new(),\n            creation_timestamp: None,\n        }\n    }\n}\n\n/// The default `InboundRoute` used for any `InboundServer` that\n/// does not have routes.\nimpl Default for InboundRoute<GrpcRouteMatch> {\n    fn default() -> Self {\n        Self {\n            hostnames: vec![],\n            rules: vec![InboundRouteRule {\n                matches: vec![GrpcRouteMatch {\n                    headers: vec![],\n                    method: Some(GrpcMethodMatch {\n                        method: None,\n                        service: None,\n                    }),\n                }],\n                filters: vec![],\n            }],\n            // Default routes do not have authorizations; the default policy's\n            // authzs will be configured by the default `InboundServer`, not by\n            // the route.\n            authorizations: HashMap::new(),\n            creation_timestamp: None,\n        }\n    }\n}\n\n// === impl InboundHttpRouteRef ===\n\nimpl Ord for RouteRef {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        match (self, other) {\n            (Self::Default(a), Self::Default(b)) => a.cmp(b),\n            (Self::Resource(a), Self::Resource(b)) => a.cmp(b),\n            // Route resources are always preferred over default resources, so they should sort\n            // first in a list.\n            (Self::Resource(_), Self::Default(_)) => std::cmp::Ordering::Less,\n            (Self::Default(_), Self::Resource(_)) => std::cmp::Ordering::Greater,\n        }\n    }\n}\n\nimpl PartialOrd for RouteRef {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n"
  },
  {
    "path": "policy-controller/core/src/lib.rs",
    "content": "#![deny(warnings, rust_2018_idioms)]\n#![forbid(unsafe_code)]\n\nmod identity_match;\npub mod inbound;\nmod network_match;\npub mod outbound;\npub mod routes;\n\npub use self::{identity_match::IdentityMatch, network_match::NetworkMatch};\npub use ipnet::{IpNet, Ipv4Net, Ipv6Net};\n\npub const POLICY_CONTROLLER_NAME: &str = \"linkerd.io/policy-controller\";\n"
  },
  {
    "path": "policy-controller/core/src/network_match.rs",
    "content": "use ipnet::{IpNet, Ipv4Net, Ipv6Net};\nuse std::net::IpAddr;\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub struct NetworkMatch {\n    /// A network to match against.\n    pub net: IpNet,\n\n    /// Networks to exclude from the match.\n    pub except: Vec<IpNet>,\n}\n\n// === impl NetworkMatch ===\n\nimpl From<IpAddr> for NetworkMatch {\n    fn from(net: IpAddr) -> Self {\n        IpNet::from(net).into()\n    }\n}\n\nimpl From<IpNet> for NetworkMatch {\n    fn from(net: IpNet) -> Self {\n        Self {\n            net,\n            except: vec![],\n        }\n    }\n}\n\nimpl From<Ipv4Net> for NetworkMatch {\n    fn from(net: Ipv4Net) -> Self {\n        IpNet::from(net).into()\n    }\n}\n\nimpl From<Ipv6Net> for NetworkMatch {\n    fn from(net: Ipv6Net) -> Self {\n        IpNet::from(net).into()\n    }\n}\n"
  },
  {
    "path": "policy-controller/core/src/outbound/policy.rs",
    "content": "use super::{\n    AppProtocol, FailureAccrual, GrpcRetryCondition, GrpcRoute, HttpRetryCondition, HttpRoute,\n    RouteRetry, RouteSet, RouteTimeouts, TcpRoute, TlsRoute, TrafficPolicy,\n};\n\nuse std::num::NonZeroU16;\n\n// ParentInfo carries resource-specific information about\n// the parent to which outbound policy is associated.\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub enum ParentInfo {\n    Service {\n        name: String,\n        namespace: String,\n        authority: String,\n    },\n    EgressNetwork {\n        name: String,\n        namespace: String,\n        traffic_policy: TrafficPolicy,\n    },\n}\n\n#[derive(Clone, Debug, PartialEq)]\npub struct OutboundPolicy {\n    pub parent_info: ParentInfo,\n    pub http_routes: RouteSet<HttpRoute>,\n    pub grpc_routes: RouteSet<GrpcRoute>,\n    pub tls_routes: RouteSet<TlsRoute>,\n    pub tcp_routes: RouteSet<TcpRoute>,\n    pub port: NonZeroU16,\n    pub app_protocol: Option<AppProtocol>,\n    pub accrual: Option<FailureAccrual>,\n    pub http_retry: Option<RouteRetry<HttpRetryCondition>>,\n    pub grpc_retry: Option<RouteRetry<GrpcRetryCondition>>,\n    pub timeouts: RouteTimeouts,\n}\n\nimpl ParentInfo {\n    pub fn name(&self) -> &str {\n        match self {\n            Self::EgressNetwork { name, .. } => name,\n            Self::Service { name, .. } => name,\n        }\n    }\n\n    pub fn namespace(&self) -> &str {\n        match self {\n            Self::EgressNetwork { namespace, .. } => namespace,\n            Self::Service { namespace, .. } => namespace,\n        }\n    }\n}\n\nimpl OutboundPolicy {\n    pub fn parent_name(&self) -> &str {\n        self.parent_info.name()\n    }\n\n    pub fn parent_namespace(&self) -> &str {\n        self.parent_info.namespace()\n    }\n}\n"
  },
  {
    "path": "policy-controller/core/src/outbound/target.rs",
    "content": "use std::{net::SocketAddr, num::NonZeroU16};\n\n/// OutboundDiscoverTarget allows us to express the fact that\n/// a policy resolution can be fulfilled by either a resource\n/// we know about (a specific EgressNetwork or a Service) or\n/// by our fallback mechanism.\n#[derive(Clone, Debug)]\npub enum OutboundDiscoverTarget {\n    Resource(ResourceTarget),\n    External(SocketAddr),\n    // UndefinedPort indicates that the target is a Service on a port which is\n    // not defined in the Service's spec.\n    UndefinedPort(ResourceTarget),\n}\n\n#[derive(Clone, Debug)]\npub struct ResourceTarget {\n    pub name: String,\n    pub namespace: String,\n    pub port: NonZeroU16,\n    pub source_namespace: String,\n    pub kind: Kind,\n}\n\n#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]\npub enum Kind {\n    EgressNetwork(SocketAddr),\n    Service,\n}\n\nimpl ResourceTarget {\n    pub fn original_dst(&self) -> Option<SocketAddr> {\n        match self.kind {\n            Kind::EgressNetwork(original_dst) => Some(original_dst),\n            Kind::Service => None,\n        }\n    }\n}\n\nimpl Kind {\n    pub fn group(&self) -> &'static str {\n        match self {\n            Kind::EgressNetwork(_) => \"policy.linkerd.io\",\n            Kind::Service => \"core\",\n        }\n    }\n    pub fn kind(&self) -> &'static str {\n        match self {\n            Kind::EgressNetwork(_) => \"EgressNetwork\",\n            Kind::Service => \"Service\",\n        }\n    }\n}\n"
  },
  {
    "path": "policy-controller/core/src/outbound.rs",
    "content": "use crate::routes::{\n    FailureInjectorFilter, GroupKindNamespaceName, GrpcRouteMatch, HeaderModifierFilter, HostMatch,\n    HttpRouteMatch, RequestRedirectFilter,\n};\nuse ahash::AHashMap as HashMap;\nuse anyhow::Result;\nuse chrono::{offset::Utc, DateTime};\nuse futures::prelude::*;\nuse std::{net::IpAddr, num::NonZeroU16, pin::Pin, str::FromStr, sync::Arc, time};\n\nmod policy;\nmod target;\n\ntype FallbackPolicy = ();\n\npub use self::{\n    policy::{OutboundPolicy, ParentInfo},\n    target::{Kind, OutboundDiscoverTarget, ResourceTarget},\n};\n\npub trait Route {\n    fn creation_timestamp(&self) -> Option<DateTime<Utc>>;\n}\n\n/// Models outbound policy discovery.\n#[async_trait::async_trait]\npub trait DiscoverOutboundPolicy<R, T> {\n    async fn get_outbound_policy(&self, target: R) -> Result<Option<OutboundPolicy>>;\n\n    async fn watch_outbound_policy(&self, target: R) -> Result<Option<OutboundPolicyStream>>;\n\n    async fn watch_external_policy(&self) -> ExternalPolicyStream;\n\n    fn lookup_ip(&self, addr: IpAddr, port: NonZeroU16, source_namespace: String) -> Option<T>;\n}\n\npub type OutboundPolicyStream = Pin<Box<dyn Stream<Item = OutboundPolicy> + Send + Sync + 'static>>;\npub type ExternalPolicyStream = Pin<Box<dyn Stream<Item = FallbackPolicy> + Send + Sync + 'static>>;\n\npub type HttpRoute = OutboundRoute<HttpRouteMatch, HttpRetryCondition>;\npub type GrpcRoute = OutboundRoute<GrpcRouteMatch, GrpcRetryCondition>;\n\npub type RouteSet<T> = HashMap<GroupKindNamespaceName, T>;\n\n#[derive(Debug, Clone, PartialEq)]\npub enum AppProtocol {\n    Http1,\n    Http2,\n    Opaque,\n    Unknown(Arc<str>),\n}\n\nimpl FromStr for AppProtocol {\n    type Err = std::convert::Infallible;\n\n    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {\n        let protocol = match s.to_ascii_lowercase().as_str() {\n            \"http\" => AppProtocol::Http1,\n            \"kubernetes.io/h2c\" => AppProtocol::Http2,\n            \"linkerd.io/tcp\" | \"linkerd.io/opaque\" => AppProtocol::Opaque,\n            protocol => AppProtocol::Unknown(Arc::from(protocol)),\n        };\n        Ok(protocol)\n    }\n}\n\n#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]\npub enum TrafficPolicy {\n    Allow,\n    Deny,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct OutboundRoute<M, R> {\n    pub hostnames: Vec<HostMatch>,\n    pub rules: Vec<OutboundRouteRule<M, R>>,\n\n    /// This is required for ordering returned routes\n    /// by their creation timestamp.\n    pub creation_timestamp: Option<DateTime<Utc>>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct TlsRoute {\n    pub hostnames: Vec<HostMatch>,\n    pub rule: TcpRouteRule,\n    /// This is required for ordering returned routes\n    /// by their creation timestamp.\n    pub creation_timestamp: Option<DateTime<Utc>>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct TcpRoute {\n    pub rule: TcpRouteRule,\n\n    /// This is required for ordering returned routes\n    /// by their creation timestamp.\n    pub creation_timestamp: Option<DateTime<Utc>>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct OutboundRouteRule<M, R> {\n    pub matches: Vec<M>,\n    pub backends: Vec<Backend>,\n    pub retry: Option<RouteRetry<R>>,\n    pub timeouts: RouteTimeouts,\n    pub filters: Vec<Filter>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct TcpRouteRule {\n    pub backends: Vec<Backend>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum Backend {\n    Addr(WeightedAddr),\n    Service(WeightedService),\n    EgressNetwork(WeightedEgressNetwork),\n    Invalid { weight: u32, message: String },\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct WeightedAddr {\n    pub weight: u32,\n    pub addr: IpAddr,\n    pub port: NonZeroU16,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct WeightedService {\n    pub weight: u32,\n    pub authority: String,\n    pub name: String,\n    pub namespace: String,\n    pub port: NonZeroU16,\n    pub filters: Vec<Filter>,\n    pub exists: bool,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct WeightedEgressNetwork {\n    pub weight: u32,\n    pub name: String,\n    pub namespace: String,\n    pub port: Option<NonZeroU16>,\n    pub filters: Vec<Filter>,\n    pub exists: bool,\n}\n\n#[derive(Copy, Clone, Debug, PartialEq)]\npub enum FailureAccrual {\n    Consecutive { max_failures: u32, backoff: Backoff },\n}\n\n#[derive(Copy, Clone, Debug, PartialEq)]\npub struct Backoff {\n    pub min_penalty: time::Duration,\n    pub max_penalty: time::Duration,\n    pub jitter: f32,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum Filter {\n    RequestHeaderModifier(HeaderModifierFilter),\n    ResponseHeaderModifier(HeaderModifierFilter),\n    RequestRedirect(RequestRedirectFilter),\n    FailureInjector(FailureInjectorFilter),\n}\n\n#[derive(Clone, Debug, Default, PartialEq, Eq)]\npub struct RouteTimeouts {\n    pub response: Option<time::Duration>,\n    pub request: Option<time::Duration>,\n    pub idle: Option<time::Duration>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct RouteRetry<R> {\n    pub limit: u16,\n    pub timeout: Option<time::Duration>,\n    pub conditions: Option<Vec<R>>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct HttpRetryCondition {\n    pub status_min: u32,\n    pub status_max: u32,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum GrpcRetryCondition {\n    Cancelled,\n    DeadlineExceeded,\n    ResourceExhausted,\n    Internal,\n    Unavailable,\n}\n\nimpl<M, R> Route for OutboundRoute<M, R> {\n    fn creation_timestamp(&self) -> Option<DateTime<Utc>> {\n        self.creation_timestamp\n    }\n}\n\nimpl Route for TcpRoute {\n    fn creation_timestamp(&self) -> Option<DateTime<Utc>> {\n        self.creation_timestamp\n    }\n}\n\nimpl Route for TlsRoute {\n    fn creation_timestamp(&self) -> Option<DateTime<Utc>> {\n        self.creation_timestamp\n    }\n}\n"
  },
  {
    "path": "policy-controller/core/src/routes.rs",
    "content": "use anyhow::Result;\n\npub use http::{\n    header::{HeaderName, HeaderValue},\n    uri::Scheme,\n    Method, StatusCode,\n};\nuse regex::Regex;\nuse std::{borrow::Cow, num::NonZeroU16};\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct GroupKindName {\n    pub group: Cow<'static, str>,\n    pub kind: Cow<'static, str>,\n    pub name: Cow<'static, str>,\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct GroupKindNamespaceName {\n    pub group: Cow<'static, str>,\n    pub kind: Cow<'static, str>,\n    pub namespace: Cow<'static, str>,\n    pub name: Cow<'static, str>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum HostMatch {\n    Exact(String),\n    Suffix { reverse_labels: Vec<String> },\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct HeaderModifierFilter {\n    pub add: Vec<(HeaderName, HeaderValue)>,\n    pub set: Vec<(HeaderName, HeaderValue)>,\n    pub remove: Vec<HeaderName>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct RequestRedirectFilter {\n    pub scheme: Option<Scheme>,\n    pub host: Option<String>,\n    pub path: Option<PathModifier>,\n    pub port: Option<NonZeroU16>,\n    pub status: Option<StatusCode>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct FailureInjectorFilter {\n    pub status: StatusCode,\n    pub message: String,\n    pub ratio: Ratio,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum PathModifier {\n    Full(String),\n    Prefix(String),\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct Ratio {\n    pub numerator: u32,\n    pub denominator: u32,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct HttpRouteMatch {\n    pub path: Option<PathMatch>,\n    pub headers: Vec<HeaderMatch>,\n    pub query_params: Vec<QueryParamMatch>,\n    pub method: Option<Method>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct GrpcRouteMatch {\n    pub headers: Vec<HeaderMatch>,\n    pub method: Option<GrpcMethodMatch>,\n}\n\n#[derive(Clone, Debug)]\npub enum PathMatch {\n    Exact(String),\n    Prefix(String),\n    Regex(Regex),\n}\n\n#[derive(Clone, Debug)]\npub enum HeaderMatch {\n    Exact(HeaderName, HeaderValue),\n    Regex(HeaderName, Regex),\n}\n\n#[derive(Clone, Debug)]\npub enum QueryParamMatch {\n    Exact(String, String),\n    Regex(String, Regex),\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct GrpcMethodMatch {\n    pub method: Option<String>,\n    pub service: Option<String>,\n}\n\n// === impl GroupKindName ===\n\nimpl Ord for GroupKindName {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        self.name.cmp(&other.name).then(\n            self.group\n                .cmp(&other.group)\n                .then(self.kind.cmp(&other.kind)),\n        )\n    }\n}\n\nimpl PartialOrd for GroupKindName {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl GroupKindName {\n    pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {\n        self.group.eq_ignore_ascii_case(&other.group)\n            && self.kind.eq_ignore_ascii_case(&other.kind)\n            && self.name.eq_ignore_ascii_case(&other.name)\n    }\n\n    pub fn namespaced(self, namespace: String) -> GroupKindNamespaceName {\n        GroupKindNamespaceName {\n            group: self.group,\n            kind: self.kind,\n            namespace: namespace.into(),\n            name: self.name,\n        }\n    }\n}\n\n// === impl HttpRouteMatch ===\n\nimpl Default for HttpRouteMatch {\n    fn default() -> Self {\n        Self {\n            method: None,\n            headers: vec![],\n            query_params: vec![],\n            path: Some(PathMatch::Prefix(\"/\".to_string())),\n        }\n    }\n}\n\n// === impl PathMatch ===\n\nimpl PartialEq for PathMatch {\n    fn eq(&self, other: &Self) -> bool {\n        match (self, other) {\n            (Self::Exact(l0), Self::Exact(r0)) => l0 == r0,\n            (Self::Prefix(l0), Self::Prefix(r0)) => l0 == r0,\n            (Self::Regex(l0), Self::Regex(r0)) => l0.as_str() == r0.as_str(),\n            _ => false,\n        }\n    }\n}\n\nimpl Eq for PathMatch {}\n\nimpl PathMatch {\n    pub fn regex(s: &str) -> Result<Self> {\n        Ok(Self::Regex(Regex::new(s)?))\n    }\n}\n\n// === impl HeaderMatch ===\n\nimpl PartialEq for HeaderMatch {\n    fn eq(&self, other: &Self) -> bool {\n        match (self, other) {\n            (Self::Exact(n0, v0), Self::Exact(n1, v1)) => n0 == n1 && v0 == v1,\n            (Self::Regex(n0, r0), Self::Regex(n1, r1)) => n0 == n1 && r0.as_str() == r1.as_str(),\n            _ => false,\n        }\n    }\n}\n\nimpl Eq for HeaderMatch {}\n\n// === impl QueryParamMatch ===\n\nimpl PartialEq for QueryParamMatch {\n    fn eq(&self, other: &Self) -> bool {\n        match (self, other) {\n            (Self::Exact(n0, v0), Self::Exact(n1, v1)) => n0 == n1 && v0 == v1,\n            (Self::Regex(n0, r0), Self::Regex(n1, r1)) => n0 == n1 && r0.as_str() == r1.as_str(),\n            _ => false,\n        }\n    }\n}\n\nimpl Eq for QueryParamMatch {}\n"
  },
  {
    "path": "policy-controller/grpc/Cargo.toml",
    "content": "[package]\nname = \"linkerd-policy-controller-grpc\"\nversion = \"0.1.0\"\nedition = \"2021\"\nlicense = \"Apache-2.0\"\npublish = false\n\n[dependencies]\nasync-stream = \"0.3\"\nasync-trait = \"0.1\"\ndrain = \"0.2\"\nfutures = { version = \"0.3\", default-features = false }\nhttp = { workspace = true }\nmaplit = \"1\"\nprometheus-client = { workspace = true }\nprost-types = \"0.14\"\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\ntokio = { version = \"1\", features = [\"macros\", \"time\"] }\ntonic = { workspace = true }\ntracing = \"0.1\"\n\nlinkerd-policy-controller-core = { workspace = true }\n\n[dependencies.linkerd2-proxy-api]\nworkspace = true\nfeatures = [\"inbound\", \"outbound\"]\n"
  },
  {
    "path": "policy-controller/grpc/src/inbound/grpc.rs",
    "content": "use linkerd2_proxy_api::{inbound, meta};\nuse linkerd_policy_controller_core::{\n    inbound::{Filter, GrpcRoute, InboundRouteRule, RouteRef},\n    IpNet,\n};\n\nuse crate::routes;\n\nuse super::to_authz;\n\npub(crate) fn to_route_list<'r>(\n    routes: impl IntoIterator<Item = (&'r RouteRef, &'r GrpcRoute)>,\n    cluster_networks: &[IpNet],\n) -> Vec<inbound::GrpcRoute> {\n    // Per the Gateway API spec:\n    //\n    // > If ties still exist across multiple Routes, matching precedence MUST be\n    // > determined in order of the following criteria, continuing on ties:\n    // >\n    // >    The oldest Route based on creation timestamp.\n    // >    The Route appearing first in alphabetical order by\n    // >   \"{namespace}/{name}\".\n    //\n    // Note that we don't need to include the route's namespace in this\n    // comparison, because all these routes will exist in the same\n    // namespace.\n    let mut route_list = routes.into_iter().collect::<Vec<_>>();\n    route_list.sort_by(|(a_ref, a), (b_ref, b)| {\n        let by_ts = match (&a.creation_timestamp, &b.creation_timestamp) {\n            (Some(a_ts), Some(b_ts)) => a_ts.cmp(b_ts),\n            (None, None) => std::cmp::Ordering::Equal,\n            // Routes with timestamps are preferred over routes without.\n            (Some(_), None) => return std::cmp::Ordering::Less,\n            (None, Some(_)) => return std::cmp::Ordering::Greater,\n        };\n        by_ts.then_with(|| a_ref.cmp(b_ref))\n    });\n\n    route_list\n        .into_iter()\n        .map(|(route_ref, route)| to_grpc_route(route_ref, route.clone(), cluster_networks))\n        .collect()\n}\n\nfn to_grpc_route(\n    reference: &RouteRef,\n    GrpcRoute {\n        hostnames,\n        rules,\n        authorizations,\n        creation_timestamp: _,\n    }: GrpcRoute,\n    cluster_networks: &[IpNet],\n) -> inbound::GrpcRoute {\n    let metadata = match reference {\n        RouteRef::Resource(gkn) => meta::Metadata {\n            kind: Some(meta::metadata::Kind::Resource(meta::Resource {\n                group: gkn.group.to_string(),\n                kind: gkn.kind.to_string(),\n                name: gkn.name.to_string(),\n                ..Default::default()\n            })),\n        },\n        RouteRef::Default(name) => meta::Metadata {\n            kind: Some(meta::metadata::Kind::Default(name.to_string())),\n        },\n    };\n\n    let hosts = hostnames\n        .into_iter()\n        .map(routes::convert_host_match)\n        .collect();\n\n    let rules = rules\n        .into_iter()\n        .map(\n            |InboundRouteRule { matches, filters }| inbound::grpc_route::Rule {\n                matches: matches\n                    .into_iter()\n                    .map(routes::grpc::convert_match)\n                    .collect(),\n                filters: filters\n                    .into_iter()\n                    .filter_map(convert_grpc_filter)\n                    .collect(),\n            },\n        )\n        .collect();\n\n    let authorizations = authorizations\n        .iter()\n        .map(|(n, c)| to_authz(n, c, cluster_networks))\n        .collect();\n\n    inbound::GrpcRoute {\n        metadata: Some(metadata),\n        hosts,\n        rules,\n        authorizations,\n    }\n}\n\nfn convert_grpc_filter(filter: Filter) -> Option<inbound::grpc_route::Filter> {\n    use inbound::grpc_route::filter::Kind;\n\n    let kind = match filter {\n        Filter::FailureInjector(_) => None,\n        Filter::RequestHeaderModifier(f) => Some(Kind::RequestHeaderModifier(\n            routes::convert_request_header_modifier_filter(f),\n        )),\n        Filter::ResponseHeaderModifier(_) => None,\n        Filter::RequestRedirect(_) => None,\n    };\n\n    kind.map(|kind| inbound::grpc_route::Filter { kind: Some(kind) })\n}\n"
  },
  {
    "path": "policy-controller/grpc/src/inbound/http.rs",
    "content": "use super::to_authz;\nuse crate::routes;\nuse linkerd2_proxy_api::{inbound, meta};\nuse linkerd_policy_controller_core::{\n    inbound::{Filter, HttpRoute, InboundRouteRule, RouteRef},\n    IpNet,\n};\n\npub(crate) fn to_route_list<'r>(\n    routes: impl IntoIterator<Item = (&'r RouteRef, &'r HttpRoute)>,\n    cluster_networks: &[IpNet],\n) -> Vec<inbound::HttpRoute> {\n    // Per the Gateway API spec:\n    //\n    // > If ties still exist across multiple Routes, matching precedence MUST be\n    // > determined in order of the following criteria, continuing on ties:\n    // >\n    // >    The oldest Route based on creation timestamp.\n    // >    The Route appearing first in alphabetical order by\n    // >   \"{namespace}/{name}\".\n    //\n    // Note that we don't need to include the route's namespace in this\n    // comparison, because all these routes will exist in the same\n    // namespace.\n    let mut route_list = routes.into_iter().collect::<Vec<_>>();\n    route_list.sort_by(|(a_ref, a), (b_ref, b)| {\n        let by_ts = match (&a.creation_timestamp, &b.creation_timestamp) {\n            (Some(a_ts), Some(b_ts)) => a_ts.cmp(b_ts),\n            (None, None) => std::cmp::Ordering::Equal,\n            // Routes with timestamps are preferred over routes without.\n            (Some(_), None) => return std::cmp::Ordering::Less,\n            (None, Some(_)) => return std::cmp::Ordering::Greater,\n        };\n        by_ts.then_with(|| a_ref.cmp(b_ref))\n    });\n\n    route_list\n        .into_iter()\n        .map(|(route_ref, route)| to_http_route(route_ref, route.clone(), cluster_networks))\n        .collect()\n}\n\nfn to_http_route(\n    reference: &RouteRef,\n    HttpRoute {\n        hostnames,\n        rules,\n        authorizations,\n        creation_timestamp: _,\n    }: HttpRoute,\n    cluster_networks: &[IpNet],\n) -> inbound::HttpRoute {\n    let metadata = meta::Metadata {\n        kind: Some(match reference {\n            RouteRef::Default(name) => meta::metadata::Kind::Default(name.to_string()),\n            RouteRef::Resource(gkn) => meta::metadata::Kind::Resource(meta::Resource {\n                group: gkn.group.to_string(),\n                kind: gkn.kind.to_string(),\n                name: gkn.name.to_string(),\n                ..Default::default()\n            }),\n        }),\n    };\n\n    let hosts = hostnames\n        .into_iter()\n        .map(routes::convert_host_match)\n        .collect();\n\n    let rules = rules\n        .into_iter()\n        .map(\n            |InboundRouteRule { matches, filters }| inbound::http_route::Rule {\n                matches: matches\n                    .into_iter()\n                    .map(routes::http::convert_match)\n                    .collect(),\n                filters: filters\n                    .into_iter()\n                    .filter_map(convert_http_filter)\n                    .collect(),\n            },\n        )\n        .collect();\n\n    let authorizations = authorizations\n        .iter()\n        .map(|(n, c)| to_authz(n, c, cluster_networks))\n        .collect();\n\n    inbound::HttpRoute {\n        metadata: Some(metadata),\n        hosts,\n        rules,\n        authorizations,\n    }\n}\n\nfn convert_http_filter(filter: Filter) -> Option<inbound::http_route::Filter> {\n    use inbound::http_route::filter::Kind;\n\n    let kind = match filter {\n        Filter::FailureInjector(f) => Some(Kind::FailureInjector(\n            routes::http::convert_failure_injector_filter(f),\n        )),\n        Filter::RequestHeaderModifier(f) => Some(Kind::RequestHeaderModifier(\n            routes::convert_request_header_modifier_filter(f),\n        )),\n        Filter::ResponseHeaderModifier(_) => None,\n        Filter::RequestRedirect(f) => Some(Kind::Redirect(routes::convert_redirect_filter(f))),\n    };\n\n    kind.map(|kind| inbound::http_route::Filter { kind: Some(kind) })\n}\n"
  },
  {
    "path": "policy-controller/grpc/src/inbound.rs",
    "content": "use crate::{\n    metrics::{self, GrpcServerMetricsFamily, GrpcServerRPCMetrics},\n    workload::Workload,\n};\nuse futures::prelude::*;\nuse linkerd2_proxy_api::{\n    self as api,\n    inbound::{\n        self as proto,\n        inbound_server_policies_server::{InboundServerPolicies, InboundServerPoliciesServer},\n    },\n    meta::{metadata, Metadata},\n};\nuse linkerd_policy_controller_core::{\n    inbound::{\n        AuthorizationRef, ClientAuthentication, ClientAuthorization, DiscoverInboundServer,\n        InboundServer, InboundServerStream, ProxyProtocol, RateLimit, ServerRef,\n    },\n    IdentityMatch, IpNet, NetworkMatch,\n};\nuse maplit::*;\nuse std::{num::NonZeroU16, str::FromStr, sync::Arc};\nuse tracing::trace;\n\nmod grpc;\nmod http;\n\n#[derive(Clone, Debug)]\npub struct InboundPolicyServer<T> {\n    discover: T,\n    drain: drain::Watch,\n    cluster_networks: Arc<[IpNet]>,\n    get_metrics: GrpcServerRPCMetrics,\n    watch_metrics: GrpcServerRPCMetrics,\n}\n\n// === impl InboundPolicyServer ===\n\nimpl<T> InboundPolicyServer<T>\nwhere\n    T: DiscoverInboundServer<(Workload, NonZeroU16)> + Send + Sync + 'static,\n{\n    pub fn new(\n        discover: T,\n        cluster_networks: Vec<IpNet>,\n        drain: drain::Watch,\n        metrics: GrpcServerMetricsFamily,\n    ) -> Self {\n        const SERVICE: &str = \"io.linkerd.proxy.inbound.InboundServerPolicies\";\n        let get_metrics = metrics.unary_rpc(SERVICE, \"GetPort\");\n        let watch_metrics = metrics.server_stream_rpc(SERVICE, \"WatchPort\");\n\n        Self {\n            discover,\n            drain,\n            cluster_networks: cluster_networks.into(),\n            get_metrics,\n            watch_metrics,\n        }\n    }\n\n    pub fn svc(self) -> InboundServerPoliciesServer<Self> {\n        InboundServerPoliciesServer::new(self)\n    }\n\n    fn check_target(\n        &self,\n        proto::PortSpec { workload, port }: proto::PortSpec,\n    ) -> Result<(Workload, NonZeroU16), tonic::Status> {\n        let workload = Workload::from_str(&workload)?;\n        // Ensure that the port is in the valid range.\n        let port = u16::try_from(port)\n            .and_then(NonZeroU16::try_from)\n            .map_err(|_| tonic::Status::invalid_argument(format!(\"Invalid port: {port}\")))?;\n\n        Ok((workload, port))\n    }\n}\n\n#[async_trait::async_trait]\nimpl<T> InboundServerPolicies for InboundPolicyServer<T>\nwhere\n    T: DiscoverInboundServer<(Workload, NonZeroU16)> + Send + Sync + 'static,\n{\n    async fn get_port(\n        &self,\n        req: tonic::Request<proto::PortSpec>,\n    ) -> Result<tonic::Response<proto::Server>, tonic::Status> {\n        let metrics = self.get_metrics.start();\n        let target = match self.check_target(req.into_inner()) {\n            Ok(target) => target,\n            Err(status) => {\n                metrics.end(status.code());\n                return Err(status);\n            }\n        };\n\n        match self.discover.get_inbound_server(target).await {\n            Ok(Some(server)) => Ok(tonic::Response::new(to_server(\n                &server,\n                &self.cluster_networks,\n            ))),\n            Ok(None) => {\n                let status = tonic::Status::not_found(\"unknown server\");\n                metrics.end(status.code());\n                Err(status)\n            }\n            Err(error) => {\n                let status = tonic::Status::internal(format!(\"lookup failed: {error}\"));\n                metrics.end(status.code());\n                Err(status)\n            }\n        }\n    }\n\n    type WatchPortStream = BoxWatchStream;\n\n    async fn watch_port(\n        &self,\n        req: tonic::Request<proto::PortSpec>,\n    ) -> Result<tonic::Response<BoxWatchStream>, tonic::Status> {\n        let metrics = self.watch_metrics.start();\n        let target = match self.check_target(req.into_inner()) {\n            Ok(target) => target,\n            Err(status) => {\n                metrics.end(status.code());\n                return Err(status);\n            }\n        };\n\n        let drain = self.drain.clone();\n        match self.discover.watch_inbound_server(target).await {\n            Ok(Some(rx)) => {\n                let stream = response_stream(drain, rx, self.cluster_networks.clone(), metrics);\n                Ok(tonic::Response::new(stream))\n            }\n            Ok(None) => {\n                let status = tonic::Status::not_found(\"unknown server\");\n                metrics.end(status.code());\n                Err(status)\n            }\n            Err(error) => {\n                let status = tonic::Status::internal(format!(\"lookup failed: {error}\"));\n                metrics.end(status.code());\n                Err(status)\n            }\n        }\n    }\n}\n\ntype BoxWatchStream =\n    std::pin::Pin<Box<dyn Stream<Item = Result<proto::Server, tonic::Status>> + Send + Sync>>;\n\nfn response_stream(\n    drain: drain::Watch,\n    mut rx: InboundServerStream,\n    cluster_networks: Arc<[IpNet]>,\n    metrics: metrics::ResponseObserver,\n) -> BoxWatchStream {\n    Box::pin(async_stream::try_stream! {\n        tokio::pin! {\n            let shutdown = drain.signaled();\n        }\n\n        loop {\n            tokio::select! {\n                // When the port is updated with a new server, update the server watch.\n                res = rx.next() => match res {\n                    Some(s) => {\n                        metrics.msg_sent();\n                        yield to_server(&s, &cluster_networks);\n                    }\n                    None => break,\n                },\n\n                // If the server starts shutting down, close the stream so that it doesn't hold the\n                // server open.\n                _ = (&mut shutdown) => break,\n            }\n        }\n\n        metrics.end(tonic::Code::Ok);\n    })\n}\n\nfn to_server(srv: &InboundServer, cluster_networks: &[IpNet]) -> proto::Server {\n    // Convert the protocol object into a protobuf response.\n    let protocol = proto::ProxyProtocol {\n        kind: match srv.protocol {\n            ProxyProtocol::Detect { timeout } => Some(proto::proxy_protocol::Kind::Detect(\n                proto::proxy_protocol::Detect {\n                    timeout: timeout.try_into().map_err(|error| tracing::warn!(%error, \"Failed to convert protocol detect timeout to protobuf\")).ok(),\n                    http_routes: http::to_route_list(&srv.http_routes, cluster_networks),\n                    http_local_rate_limit: srv.ratelimit.as_ref().map(to_rate_limit),\n                },\n            )),\n            ProxyProtocol::Http1 => Some(proto::proxy_protocol::Kind::Http1(\n                proto::proxy_protocol::Http1 {\n                    routes: http::to_route_list(&srv.http_routes, cluster_networks),\n                    local_rate_limit: srv.ratelimit.as_ref().map(to_rate_limit),\n                },\n            )),\n            ProxyProtocol::Http2 => Some(proto::proxy_protocol::Kind::Http2(\n                proto::proxy_protocol::Http2 {\n                    routes: http::to_route_list(&srv.http_routes, cluster_networks),\n                    local_rate_limit: srv.ratelimit.as_ref().map(to_rate_limit),\n                },\n            )),\n            ProxyProtocol::Grpc => Some(proto::proxy_protocol::Kind::Grpc(\n                proto::proxy_protocol::Grpc {\n                    routes: grpc::to_route_list(&srv.grpc_routes, cluster_networks),\n                },\n            )),\n            ProxyProtocol::Opaque => Some(proto::proxy_protocol::Kind::Opaque(\n                proto::proxy_protocol::Opaque {},\n            )),\n            ProxyProtocol::Tls => Some(proto::proxy_protocol::Kind::Tls(\n                proto::proxy_protocol::Tls {},\n            )),\n        },\n    };\n    trace!(?protocol);\n\n    let authorizations = srv\n        .authorizations\n        .iter()\n        .map(|(n, c)| to_authz(n, c, cluster_networks))\n        .collect();\n    trace!(?authorizations);\n\n    let labels = match &srv.reference {\n        ServerRef::Default(name) => convert_args!(hashmap!(\n            \"group\" => \"\",\n            \"kind\" => \"default\",\n            \"name\" => *name,\n        )),\n        ServerRef::Server(name) => convert_args!(hashmap!(\n            \"group\" => \"policy.linkerd.io\",\n            \"kind\" => \"server\",\n            \"name\" => name,\n        )),\n    };\n    trace!(?labels);\n\n    proto::Server {\n        protocol: Some(protocol),\n        authorizations,\n        labels,\n        ..Default::default()\n    }\n}\n\nfn to_authz(\n    reference: &AuthorizationRef,\n    ClientAuthorization {\n        networks,\n        authentication,\n    }: &ClientAuthorization,\n    cluster_networks: &[IpNet],\n) -> proto::Authz {\n    let meta = Metadata {\n        kind: Some(match reference {\n            AuthorizationRef::Default(name) => metadata::Kind::Default(name.to_string()),\n            AuthorizationRef::AuthorizationPolicy(name) => {\n                metadata::Kind::Resource(api::meta::Resource {\n                    group: \"policy.linkerd.io\".to_string(),\n                    kind: \"authorizationpolicy\".to_string(),\n                    name: name.to_string(),\n                    ..Default::default()\n                })\n            }\n            AuthorizationRef::ServerAuthorization(name) => {\n                metadata::Kind::Resource(api::meta::Resource {\n                    group: \"policy.linkerd.io\".to_string(),\n                    kind: \"serverauthorization\".to_string(),\n                    name: name.clone(),\n                    ..Default::default()\n                })\n            }\n        }),\n    };\n\n    // TODO labels are deprecated, but we want to continue to support them for older proxies. This\n    // can be removed in 2.13.\n    let labels = match reference {\n        AuthorizationRef::Default(name) => convert_args!(hashmap!(\n            \"group\" => \"\",\n            \"kind\" => \"default\",\n            \"name\" => *name,\n        )),\n        AuthorizationRef::ServerAuthorization(name) => convert_args!(hashmap!(\n            \"group\" => \"policy.linkerd.io\",\n            \"kind\" => \"serverauthorization\",\n            \"name\" => name,\n        )),\n        AuthorizationRef::AuthorizationPolicy(name) => convert_args!(hashmap!(\n            \"group\" => \"policy.linkerd.io\",\n            \"kind\" => \"authorizationpolicy\",\n            \"name\" => name,\n        )),\n    };\n\n    let networks = if networks.is_empty() {\n        cluster_networks\n            .iter()\n            .map(|n| proto::Network {\n                net: Some((*n).into()),\n                except: vec![],\n            })\n            .collect::<Vec<_>>()\n    } else {\n        networks\n            .iter()\n            .map(|NetworkMatch { net, except }| proto::Network {\n                net: Some((*net).into()),\n                except: except.iter().cloned().map(Into::into).collect(),\n            })\n            .collect()\n    };\n\n    let authn = match authentication {\n        ClientAuthentication::Unauthenticated => proto::Authn {\n            permit: Some(proto::authn::Permit::Unauthenticated(\n                proto::authn::PermitUnauthenticated {},\n            )),\n        },\n\n        ClientAuthentication::TlsUnauthenticated => proto::Authn {\n            permit: Some(proto::authn::Permit::MeshTls(proto::authn::PermitMeshTls {\n                clients: Some(proto::authn::permit_mesh_tls::Clients::Unauthenticated(\n                    proto::authn::PermitUnauthenticated {},\n                )),\n            })),\n        },\n\n        // Authenticated connections must have TLS and apply to all\n        // networks.\n        ClientAuthentication::TlsAuthenticated(identities) => {\n            let suffixes = identities\n                .iter()\n                .filter_map(|i| match i {\n                    IdentityMatch::Suffix(s) => Some(proto::IdentitySuffix { parts: s.to_vec() }),\n                    _ => None,\n                })\n                .collect();\n\n            let identities = identities\n                .iter()\n                .filter_map(|i| match i {\n                    IdentityMatch::Exact(n) => Some(proto::Identity {\n                        name: n.to_string(),\n                    }),\n                    _ => None,\n                })\n                .collect();\n\n            proto::Authn {\n                permit: Some(proto::authn::Permit::MeshTls(proto::authn::PermitMeshTls {\n                    clients: Some(proto::authn::permit_mesh_tls::Clients::Identities(\n                        proto::authn::permit_mesh_tls::PermitClientIdentities {\n                            identities,\n                            suffixes,\n                        },\n                    )),\n                })),\n            }\n        }\n    };\n\n    proto::Authz {\n        metadata: Some(meta),\n        labels,\n        networks,\n        authentication: Some(authn),\n    }\n}\n\nfn to_rate_limit(rl: &RateLimit) -> proto::HttpLocalRateLimit {\n    let meta = Metadata {\n        kind: Some(metadata::Kind::Resource(api::meta::Resource {\n            group: \"policy.linkerd.io\".to_string(),\n            kind: \"HTTPLocalRateLimitPolicy\".to_string(),\n            name: rl.name.to_string(),\n            ..Default::default()\n        })),\n    };\n\n    proto::HttpLocalRateLimit {\n        metadata: Some(meta),\n        total: rl\n            .total\n            .as_ref()\n            .map(|lim| proto::http_local_rate_limit::Limit {\n                requests_per_second: lim.requests_per_second,\n            }),\n        identity: rl\n            .identity\n            .as_ref()\n            .map(|lim| proto::http_local_rate_limit::Limit {\n                requests_per_second: lim.requests_per_second,\n            }),\n        overrides: rl\n            .overrides\n            .iter()\n            .map(|ovr| proto::http_local_rate_limit::Override {\n                limit: Some(proto::http_local_rate_limit::Limit {\n                    requests_per_second: ovr.requests_per_second,\n                }),\n                clients: Some(proto::http_local_rate_limit::r#override::ClientIdentities {\n                    identities: ovr\n                        .client_identities\n                        .iter()\n                        .map(|id| proto::Identity {\n                            name: id.to_string(),\n                        })\n                        .collect(),\n                }),\n            })\n            .collect(),\n    }\n}\n"
  },
  {
    "path": "policy-controller/grpc/src/lib.rs",
    "content": "#![deny(warnings, rust_2018_idioms)]\n#![allow(clippy::result_large_err)]\n#![forbid(unsafe_code)]\n\nmod routes;\n\npub mod inbound;\npub mod metrics;\npub mod outbound;\npub mod workload;\n"
  },
  {
    "path": "policy-controller/grpc/src/metrics.rs",
    "content": "use prometheus_client::encoding::EncodeLabelSet;\nuse prometheus_client::metrics::histogram::Histogram;\nuse prometheus_client::{\n    metrics::{counter::Counter, family::Family},\n    registry::Registry,\n};\nuse tokio::time;\n\n#[derive(Clone, Debug)]\npub struct GrpcServerMetricsFamily {\n    started: Family<Labels, Counter>,\n    handling: Family<Labels, Histogram>,\n    handled: Family<CodeLabels, Counter>,\n    msg_received: Family<Labels, Counter>,\n    msg_sent: Family<Labels, Counter>,\n}\n\n#[derive(Clone, Debug)]\npub(crate) struct GrpcServerRPCMetrics {\n    started: Counter,\n    msg_received: Counter,\n    msg_sent: Counter,\n    handling: Histogram,\n    handled: Family<CodeLabels, Counter>,\n    labels: Labels,\n}\n\npub(crate) struct ResponseObserver {\n    msg_sent: Counter,\n    handled: Option<ResponseHandle>,\n}\n\nstruct ResponseHandle {\n    start: time::Instant,\n    durations: Histogram,\n    codes: Family<CodeLabels, Counter>,\n    labels: Labels,\n}\n\n#[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug)]\nstruct Labels {\n    grpc_service: &'static str,\n    grpc_method: &'static str,\n    grpc_type: &'static str,\n}\n\n#[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug)]\nstruct CodeLabels {\n    grpc_service: &'static str,\n    grpc_method: &'static str,\n    grpc_type: &'static str,\n    grpc_code: &'static str,\n}\n\n// === GrpcServerMetricsFamily ===\n\nimpl GrpcServerMetricsFamily {\n    pub fn register(reg: &mut Registry) -> Self {\n        let started = Family::<Labels, Counter>::default();\n        reg.register(\n            \"started\",\n            \"Total number of RPCs started on the server\",\n            started.clone(),\n        );\n\n        let msg_received = Family::<Labels, Counter>::default();\n        reg.register(\n            \"msg_received\",\n            \"Total number of RPC stream messages received on the server\",\n            msg_received.clone(),\n        );\n\n        let msg_sent = Family::<Labels, Counter>::default();\n        reg.register(\n            \"msg_sent\",\n            \"Total number of gRPC stream messages sent by the server\",\n            msg_sent.clone(),\n        );\n\n        let handled = Family::<CodeLabels, Counter>::default();\n        reg.register(\n            \"handled\",\n            \"Total number of RPCs completed on the server, regardless of success or failure\",\n            handled.clone(),\n        );\n\n        let handling = Family::<Labels, Histogram>::new_with_constructor(|| {\n            // Our default client configuration has a 5m idle timeout and a 1h\n            // max lifetime.\n            Histogram::new([0.1, 1.0, 300.0, 3600.0])\n        });\n        reg.register_with_unit(\n            \"handling\",\n            \"Histogram of response latency (seconds) of gRPC that had been application-level handled by the server\",\n            prometheus_client::registry::Unit::Seconds,\n            handling.clone(),\n        );\n\n        Self {\n            started,\n            msg_received,\n            msg_sent,\n            handled,\n            handling,\n        }\n    }\n\n    pub(crate) fn unary_rpc(\n        &self,\n        svc: &'static str,\n        method: &'static str,\n    ) -> GrpcServerRPCMetrics {\n        self.rpc(svc, method, \"unary\")\n    }\n\n    pub(crate) fn server_stream_rpc(\n        &self,\n        svc: &'static str,\n        method: &'static str,\n    ) -> GrpcServerRPCMetrics {\n        self.rpc(svc, method, \"server_stream\")\n    }\n\n    fn rpc(\n        &self,\n        grpc_service: &'static str,\n        grpc_method: &'static str,\n        grpc_type: &'static str,\n    ) -> GrpcServerRPCMetrics {\n        let labels = Labels {\n            grpc_service,\n            grpc_method,\n            grpc_type,\n        };\n        GrpcServerRPCMetrics {\n            started: self.started.get_or_create(&labels).clone(),\n            msg_received: self.msg_received.get_or_create(&labels).clone(),\n            msg_sent: self.msg_sent.get_or_create(&labels).clone(),\n            handled: self.handled.clone(),\n            handling: self.handling.get_or_create(&labels).clone(),\n            labels,\n        }\n    }\n}\n\n// === GrpcServerRPCMetrics ===\n\nimpl GrpcServerRPCMetrics {\n    pub(crate) fn start(&self) -> ResponseObserver {\n        self.started.inc();\n\n        // All of our interfaces are unary or server-streaming, so we can\n        // assume that if we receive a request, we received a single message.\n        self.msg_received.inc();\n\n        let handled = {\n            // Pre-register OK\n            let _ = self.handled.get_or_create(&CodeLabels {\n                grpc_service: self.labels.grpc_service,\n                grpc_method: self.labels.grpc_method,\n                grpc_type: self.labels.grpc_type,\n                grpc_code: code_str(tonic::Code::Ok),\n            });\n\n            Some(ResponseHandle {\n                start: time::Instant::now(),\n                durations: self.handling.clone(),\n                codes: self.handled.clone(),\n                labels: self.labels.clone(),\n            })\n        };\n\n        ResponseObserver {\n            msg_sent: self.msg_sent.clone(),\n            handled,\n        }\n    }\n}\n\n// === ResponseObserver ===\n\nimpl ResponseObserver {\n    pub(crate) fn msg_sent(&self) {\n        self.msg_sent.inc();\n    }\n\n    pub(crate) fn end(mut self, code: tonic::Code) {\n        self.handled\n            .take()\n            .expect(\"handle must be set\")\n            .inc_end(code);\n    }\n}\n\nimpl Drop for ResponseObserver {\n    fn drop(&mut self) {\n        if let Some(inner) = self.handled.take() {\n            inner.inc_end(tonic::Code::Ok);\n        }\n    }\n}\n\n// === ResponseHandle ===\n\nimpl ResponseHandle {\n    #[inline]\n    fn inc_end(self, code: tonic::Code) {\n        let Self {\n            start,\n            durations,\n            codes,\n            labels,\n        } = self;\n        durations.observe(start.elapsed().as_secs_f64());\n        codes\n            .get_or_create(&CodeLabels {\n                grpc_service: labels.grpc_service,\n                grpc_method: labels.grpc_method,\n                grpc_type: labels.grpc_type,\n                grpc_code: code_str(code),\n            })\n            .inc();\n    }\n}\n\nfn code_str(code: tonic::Code) -> &'static str {\n    use tonic::Code::*;\n    match code {\n        Ok => \"OK\",\n        Cancelled => \"CANCELLED\",\n        Unknown => \"UNKNOWN\",\n        InvalidArgument => \"INVALID_ARGUMENT\",\n        DeadlineExceeded => \"DEADLINE_EXCEEDED\",\n        NotFound => \"NOT_FOUND\",\n        AlreadyExists => \"ALREADY_EXISTS\",\n        PermissionDenied => \"PERMISSION_DENIED\",\n        ResourceExhausted => \"RESOURCE_EXHAUSTED\",\n        FailedPrecondition => \"FAILED_PRECONDITION\",\n        Aborted => \"ABORTED\",\n        OutOfRange => \"OUT_OF_RANGE\",\n        Unimplemented => \"UNIMPLEMENTED\",\n        Internal => \"INTERNAL\",\n        Unavailable => \"UNAVAILABLE\",\n        DataLoss => \"DATA_LOSS\",\n        Unauthenticated => \"UNAUTHENTICATED\",\n    }\n}\n"
  },
  {
    "path": "policy-controller/grpc/src/outbound/grpc.rs",
    "content": "use super::{convert_duration, default_balancer_config, default_queue_config};\nuse crate::routes::{\n    convert_host_match, convert_request_header_modifier_filter, grpc::convert_match,\n};\nuse linkerd2_proxy_api::{\n    destination, grpc_route, http_route,\n    meta::{self},\n    outbound,\n};\nuse linkerd_policy_controller_core::{\n    outbound::{\n        Backend, Filter, GrpcRetryCondition, GrpcRoute, OutboundRoute, OutboundRouteRule,\n        ParentInfo, RouteRetry, RouteTimeouts, TrafficPolicy,\n    },\n    routes::{FailureInjectorFilter, GroupKindNamespaceName},\n};\nuse std::{net::SocketAddr, time};\n\n#[allow(clippy::too_many_arguments)]\npub(crate) fn protocol(\n    default_backend: outbound::Backend,\n    routes: impl Iterator<Item = (GroupKindNamespaceName, GrpcRoute)>,\n    failure_accrual: Option<outbound::FailureAccrual>,\n    service_retry: Option<RouteRetry<GrpcRetryCondition>>,\n    service_timeouts: RouteTimeouts,\n    allow_l5d_request_headers: bool,\n    parent_info: &ParentInfo,\n    original_dst: Option<SocketAddr>,\n) -> outbound::proxy_protocol::Kind {\n    let mut routes = routes\n        .map(|(gknn, route)| {\n            convert_outbound_route(\n                gknn,\n                route,\n                default_backend.clone(),\n                service_retry.clone(),\n                service_timeouts.clone(),\n                allow_l5d_request_headers,\n                parent_info,\n                original_dst,\n            )\n        })\n        .collect::<Vec<_>>();\n\n    if let ParentInfo::EgressNetwork { traffic_policy, .. } = parent_info {\n        routes.push(default_outbound_egress_route(\n            default_backend,\n            service_retry,\n            service_timeouts,\n            traffic_policy,\n        ));\n    }\n\n    outbound::proxy_protocol::Kind::Grpc(outbound::proxy_protocol::Grpc {\n        routes,\n        failure_accrual,\n    })\n}\n\n#[allow(clippy::too_many_arguments)]\nfn convert_outbound_route(\n    gknn: GroupKindNamespaceName,\n    OutboundRoute {\n        hostnames,\n        rules,\n        creation_timestamp: _,\n    }: GrpcRoute,\n    backend: outbound::Backend,\n    service_retry: Option<RouteRetry<GrpcRetryCondition>>,\n    service_timeouts: RouteTimeouts,\n    allow_l5d_request_headers: bool,\n    parent_info: &ParentInfo,\n    original_dst: Option<SocketAddr>,\n) -> outbound::GrpcRoute {\n    let metadata = Some(meta::Metadata {\n        kind: Some(meta::metadata::Kind::Resource(meta::Resource {\n            group: gknn.group.to_string(),\n            kind: gknn.kind.to_string(),\n            namespace: gknn.namespace.to_string(),\n            name: gknn.name.to_string(),\n            ..Default::default()\n        })),\n    });\n\n    let hosts = hostnames.into_iter().map(convert_host_match).collect();\n\n    let rules = rules\n        .into_iter()\n        .map(\n            |OutboundRouteRule {\n                 matches,\n                 backends,\n                 mut retry,\n                 mut timeouts,\n                 filters,\n             }| {\n                let backends = backends\n                    .into_iter()\n                    .map(|b| convert_backend(b, parent_info, original_dst))\n                    .collect::<Vec<_>>();\n                let dist = if backends.is_empty() {\n                    outbound::grpc_route::distribution::Kind::FirstAvailable(\n                        outbound::grpc_route::distribution::FirstAvailable {\n                            backends: vec![outbound::grpc_route::RouteBackend {\n                                backend: Some(backend.clone()),\n                                filters: vec![],\n                                ..Default::default()\n                            }],\n                        },\n                    )\n                } else {\n                    outbound::grpc_route::distribution::Kind::RandomAvailable(\n                        outbound::grpc_route::distribution::RandomAvailable { backends },\n                    )\n                };\n                if timeouts == Default::default() {\n                    timeouts = service_timeouts.clone();\n                }\n                if retry.is_none() {\n                    retry = service_retry.clone();\n                }\n                #[allow(deprecated)]\n                outbound::grpc_route::Rule {\n                    matches: matches.into_iter().map(convert_match).collect(),\n                    backends: Some(outbound::grpc_route::Distribution { kind: Some(dist) }),\n                    filters: filters.into_iter().map(convert_to_filter).collect(),\n                    request_timeout: timeouts\n                        .request\n                        .and_then(|d| convert_duration(\"request timeout\", d)),\n                    timeouts: Some(http_route::Timeouts {\n                        request: timeouts\n                            .request\n                            .and_then(|d| convert_duration(\"stream timeout\", d)),\n                        idle: timeouts\n                            .idle\n                            .and_then(|d| convert_duration(\"idle timeout\", d)),\n                        response: timeouts\n                            .response\n                            .and_then(|d| convert_duration(\"response timeout\", d)),\n                    }),\n                    retry: retry.map(|r| outbound::grpc_route::Retry {\n                        max_retries: r.limit.into(),\n                        max_request_bytes: 64 * 1024,\n                        backoff: Some(outbound::ExponentialBackoff {\n                            min_backoff: Some(time::Duration::from_millis(25).try_into().unwrap()),\n                            max_backoff: Some(time::Duration::from_millis(250).try_into().unwrap()),\n                            jitter_ratio: 1.0,\n                        }),\n                        conditions: Some(r.conditions.iter().flatten().fold(\n                            outbound::grpc_route::retry::Conditions::default(),\n                            |mut cond, c| {\n                                match c {\n                                    GrpcRetryCondition::Cancelled => cond.cancelled = true,\n                                    GrpcRetryCondition::DeadlineExceeded => {\n                                        cond.deadine_exceeded = true\n                                    }\n                                    GrpcRetryCondition::Internal => cond.internal = true,\n                                    GrpcRetryCondition::ResourceExhausted => {\n                                        cond.resource_exhausted = true\n                                    }\n                                    GrpcRetryCondition::Unavailable => cond.unavailable = true,\n                                };\n                                cond\n                            },\n                        )),\n                        timeout: r.timeout.and_then(|d| convert_duration(\"retry timeout\", d)),\n                    }),\n                    allow_l5d_request_headers,\n                }\n            },\n        )\n        .collect();\n\n    outbound::GrpcRoute {\n        metadata,\n        hosts,\n        rules,\n    }\n}\n\nfn convert_backend(\n    backend: Backend,\n    parent_info: &ParentInfo,\n    original_dst: Option<SocketAddr>,\n) -> outbound::grpc_route::WeightedRouteBackend {\n    let original_dst_port = original_dst.map(|o| o.port());\n\n    match backend {\n        Backend::Addr(addr) => {\n            let socket_addr = SocketAddr::new(addr.addr, addr.port.get());\n            outbound::grpc_route::WeightedRouteBackend {\n                weight: addr.weight,\n                backend: Some(outbound::grpc_route::RouteBackend {\n                    backend: Some(outbound::Backend {\n                        metadata: None,\n                        queue: Some(default_queue_config()),\n                        kind: Some(outbound::backend::Kind::Forward(\n                            destination::WeightedAddr {\n                                addr: Some(socket_addr.into()),\n                                weight: addr.weight,\n                                ..Default::default()\n                            },\n                        )),\n                    }),\n                    filters: Default::default(),\n                    ..Default::default()\n                }),\n            }\n        }\n        Backend::Service(svc) if svc.exists => {\n            let filters = svc\n                .filters\n                .clone()\n                .into_iter()\n                .map(convert_to_filter)\n                .collect();\n            outbound::grpc_route::WeightedRouteBackend {\n                weight: svc.weight,\n                backend: Some(outbound::grpc_route::RouteBackend {\n                    backend: Some(outbound::Backend {\n                        metadata: Some(super::service_meta(svc.clone())),\n                        queue: Some(default_queue_config()),\n                        kind: Some(outbound::backend::Kind::Balancer(\n                            outbound::backend::BalanceP2c {\n                                discovery: Some(outbound::backend::EndpointDiscovery {\n                                    kind: Some(outbound::backend::endpoint_discovery::Kind::Dst(\n                                        outbound::backend::endpoint_discovery::DestinationGet {\n                                            path: svc.authority,\n                                        },\n                                    )),\n                                }),\n                                load: Some(default_balancer_config()),\n                            },\n                        )),\n                    }),\n                    filters,\n                    ..Default::default()\n                }),\n            }\n        }\n        Backend::Service(svc) => invalid_backend(\n            svc.weight,\n            format!(\"Service not found {}\", svc.name),\n            super::service_meta(svc),\n        ),\n        Backend::EgressNetwork(egress_net) if egress_net.exists => {\n            match (parent_info, original_dst) {\n                (\n                    ParentInfo::EgressNetwork {\n                        name, namespace, ..\n                    },\n                    Some(original_dst),\n                ) => {\n                    if *name == egress_net.name && *namespace == egress_net.namespace {\n                        let filters = egress_net\n                            .filters\n                            .clone()\n                            .into_iter()\n                            .map(convert_to_filter)\n                            .collect();\n\n                        outbound::grpc_route::WeightedRouteBackend {\n                            weight: egress_net.weight,\n                            backend: Some(outbound::grpc_route::RouteBackend {\n                                backend: Some(outbound::Backend {\n                                    metadata: Some(super::egress_net_meta(\n                                        egress_net.clone(),\n                                        original_dst_port,\n                                    )),\n                                    queue: Some(default_queue_config()),\n                                    kind: Some(outbound::backend::Kind::Forward(\n                                        destination::WeightedAddr {\n                                            addr: Some(original_dst.into()),\n                                            weight: egress_net.weight,\n                                            ..Default::default()\n                                        },\n                                    )),\n                                }),\n                                filters,\n                                ..Default::default()\n                            }),\n                        }\n                    } else {\n                        let weight = egress_net.weight;\n                        let message =  \"Route with EgressNetwork backend needs to have the same EgressNetwork as a parent\".to_string();\n                        invalid_backend(\n                            weight,\n                            message,\n                            super::egress_net_meta(egress_net, original_dst_port),\n                        )\n                    }\n                }\n                (ParentInfo::EgressNetwork { .. }, None) => invalid_backend(\n                    egress_net.weight,\n                    \"EgressNetwork can be resolved from an ip:port combo only\".to_string(),\n                    super::egress_net_meta(egress_net, original_dst_port),\n                ),\n                (ParentInfo::Service { .. }, _) => invalid_backend(\n                    egress_net.weight,\n                    \"EgressNetwork backends attach to EgressNetwork parents only\".to_string(),\n                    super::egress_net_meta(egress_net, original_dst_port),\n                ),\n            }\n        }\n        Backend::EgressNetwork(egress_net) => invalid_backend(\n            egress_net.weight,\n            format!(\"EgressNetwork not found {}\", egress_net.name),\n            super::egress_net_meta(egress_net, original_dst_port),\n        ),\n        Backend::Invalid { weight, message } => invalid_backend(\n            weight,\n            message,\n            meta::Metadata {\n                kind: Some(meta::metadata::Kind::Default(\"invalid\".to_string())),\n            },\n        ),\n    }\n}\n\nfn invalid_backend(\n    weight: u32,\n    message: String,\n    meta: meta::Metadata,\n) -> outbound::grpc_route::WeightedRouteBackend {\n    outbound::grpc_route::WeightedRouteBackend {\n        weight,\n        backend: Some(outbound::grpc_route::RouteBackend {\n            backend: Some(outbound::Backend {\n                metadata: Some(meta),\n                queue: Some(default_queue_config()),\n                kind: None,\n            }),\n            filters: vec![outbound::grpc_route::Filter {\n                kind: Some(outbound::grpc_route::filter::Kind::FailureInjector(\n                    grpc_route::GrpcFailureInjector {\n                        code: 500,\n                        message,\n                        ratio: None,\n                    },\n                )),\n            }],\n            ..Default::default()\n        }),\n    }\n}\n\npub(crate) fn default_outbound_egress_route(\n    backend: outbound::Backend,\n    service_retry: Option<RouteRetry<GrpcRetryCondition>>,\n    service_timeouts: RouteTimeouts,\n    traffic_policy: &TrafficPolicy,\n) -> outbound::GrpcRoute {\n    #![allow(deprecated)]\n    let (filters, name) = match traffic_policy {\n        TrafficPolicy::Allow => (Vec::default(), \"grpc-egress-allow\"),\n        TrafficPolicy::Deny => (\n            vec![outbound::grpc_route::Filter {\n                kind: Some(outbound::grpc_route::filter::Kind::FailureInjector(\n                    grpc_route::GrpcFailureInjector {\n                        code: 7,\n                        message: \"traffic not allowed\".to_string(),\n                        ratio: None,\n                    },\n                )),\n            }],\n            \"grpc-egress-deny\",\n        ),\n    };\n\n    // This encoder sets deprecated timeouts for older proxies.\n    let metadata = Some(meta::Metadata {\n        kind: Some(meta::metadata::Kind::Default(name.to_string())),\n    });\n    let rules = vec![outbound::grpc_route::Rule {\n        backends: Some(outbound::grpc_route::Distribution {\n            kind: Some(outbound::grpc_route::distribution::Kind::FirstAvailable(\n                outbound::grpc_route::distribution::FirstAvailable {\n                    backends: vec![outbound::grpc_route::RouteBackend {\n                        backend: Some(backend),\n                        ..Default::default()\n                    }],\n                },\n            )),\n        }),\n        request_timeout: service_timeouts\n            .request\n            .and_then(|d| convert_duration(\"request timeout\", d)),\n        timeouts: Some(http_route::Timeouts {\n            request: service_timeouts\n                .request\n                .and_then(|d| convert_duration(\"stream timeout\", d)),\n            idle: service_timeouts\n                .idle\n                .and_then(|d| convert_duration(\"idle timeout\", d)),\n            response: service_timeouts\n                .response\n                .and_then(|d| convert_duration(\"response timeout\", d)),\n        }),\n        retry: service_retry.map(|r| outbound::grpc_route::Retry {\n            max_retries: r.limit.into(),\n            max_request_bytes: 64 * 1024,\n            backoff: Some(outbound::ExponentialBackoff {\n                min_backoff: Some(time::Duration::from_millis(25).try_into().unwrap()),\n                max_backoff: Some(time::Duration::from_millis(250).try_into().unwrap()),\n                jitter_ratio: 1.0,\n            }),\n            conditions: Some(r.conditions.iter().flatten().fold(\n                outbound::grpc_route::retry::Conditions::default(),\n                |mut cond, c| {\n                    match c {\n                        GrpcRetryCondition::Cancelled => cond.cancelled = true,\n                        GrpcRetryCondition::DeadlineExceeded => cond.deadine_exceeded = true,\n                        GrpcRetryCondition::Internal => cond.internal = true,\n                        GrpcRetryCondition::ResourceExhausted => cond.resource_exhausted = true,\n                        GrpcRetryCondition::Unavailable => cond.unavailable = true,\n                    };\n                    cond\n                },\n            )),\n            timeout: r.timeout.and_then(|d| convert_duration(\"retry timeout\", d)),\n        }),\n        filters,\n        ..Default::default()\n    }];\n    outbound::GrpcRoute {\n        metadata,\n        rules,\n        ..Default::default()\n    }\n}\n\nfn convert_to_filter(filter: Filter) -> outbound::grpc_route::Filter {\n    use outbound::grpc_route::filter::Kind as GrpcFilterKind;\n\n    outbound::grpc_route::Filter {\n        kind: match filter {\n            Filter::FailureInjector(FailureInjectorFilter {\n                status,\n                message,\n                ratio,\n            }) => Some(GrpcFilterKind::FailureInjector(\n                grpc_route::GrpcFailureInjector {\n                    code: u32::from(status.as_u16()),\n                    message,\n                    ratio: Some(http_route::Ratio {\n                        numerator: ratio.numerator,\n                        denominator: ratio.denominator,\n                    }),\n                },\n            )),\n            Filter::RequestHeaderModifier(filter) => Some(GrpcFilterKind::RequestHeaderModifier(\n                convert_request_header_modifier_filter(filter),\n            )),\n            Filter::RequestRedirect(filter) => {\n                tracing::warn!(filter = ?filter, \"Declining to convert invalid filter type for GrpcRoute\");\n                None\n            }\n            Filter::ResponseHeaderModifier(filter) => {\n                tracing::warn!(filter = ?filter, \"Declining to convert invalid filter type for GrpcRoute\");\n                None\n            }\n        },\n    }\n}\n"
  },
  {
    "path": "policy-controller/grpc/src/outbound/http.rs",
    "content": "use super::{\n    convert_duration, default_balancer_config, default_outbound_opaq_route, default_queue_config,\n};\nuse crate::routes::{\n    convert_host_match, convert_redirect_filter, convert_request_header_modifier_filter,\n    convert_response_header_modifier_filter,\n    http::{convert_failure_injector_filter, convert_match},\n};\nuse linkerd2_proxy_api::{destination, http_route, meta, outbound};\nuse linkerd_policy_controller_core::{\n    outbound::{\n        Backend, Filter, HttpRetryCondition, HttpRoute, OutboundRouteRule, ParentInfo, RouteRetry,\n        RouteTimeouts, TrafficPolicy,\n    },\n    routes::GroupKindNamespaceName,\n};\nuse std::{net::SocketAddr, time};\n\n#[allow(clippy::too_many_arguments)]\npub(crate) fn protocol(\n    default_backend: outbound::Backend,\n    routes: impl Iterator<Item = (GroupKindNamespaceName, HttpRoute)>,\n    accrual: Option<outbound::FailureAccrual>,\n    service_retry: Option<RouteRetry<HttpRetryCondition>>,\n    service_timeouts: RouteTimeouts,\n    allow_l5d_request_headers: bool,\n    parent_info: &ParentInfo,\n    original_dst: Option<SocketAddr>,\n) -> outbound::proxy_protocol::Kind {\n    let opaque_route = default_outbound_opaq_route(default_backend.clone(), parent_info);\n    let mut routes = routes\n        .map(|(gknn, route)| {\n            convert_outbound_route(\n                gknn,\n                route,\n                default_backend.clone(),\n                service_retry.clone(),\n                service_timeouts.clone(),\n                allow_l5d_request_headers,\n                parent_info,\n                original_dst,\n            )\n        })\n        .collect::<Vec<_>>();\n\n    match parent_info {\n        ParentInfo::Service { .. } => {\n            if routes.is_empty() {\n                routes.push(default_outbound_service_route(\n                    default_backend,\n                    service_retry.clone(),\n                    service_timeouts.clone(),\n                ));\n            }\n        }\n        ParentInfo::EgressNetwork { traffic_policy, .. } => {\n            routes.push(default_outbound_egress_route(\n                default_backend,\n                service_retry.clone(),\n                service_timeouts.clone(),\n                traffic_policy,\n            ));\n        }\n    }\n\n    outbound::proxy_protocol::Kind::Detect(outbound::proxy_protocol::Detect {\n        timeout: Some(\n            time::Duration::from_secs(10)\n                .try_into()\n                .expect(\"failed to convert detect timeout to protobuf\"),\n        ),\n\n        opaque: Some(outbound::proxy_protocol::Opaque {\n            routes: vec![opaque_route],\n        }),\n        http1: Some(outbound::proxy_protocol::Http1 {\n            routes: routes.clone(),\n            failure_accrual: accrual,\n        }),\n        http2: Some(outbound::proxy_protocol::Http2 {\n            routes,\n            failure_accrual: accrual,\n        }),\n    })\n}\n\n#[allow(clippy::too_many_arguments)]\npub(crate) fn http1_only_protocol(\n    default_backend: outbound::Backend,\n    routes: impl Iterator<Item = (GroupKindNamespaceName, HttpRoute)>,\n    accrual: Option<outbound::FailureAccrual>,\n    service_retry: Option<RouteRetry<HttpRetryCondition>>,\n    service_timeouts: RouteTimeouts,\n    allow_l5d_request_headers: bool,\n    parent_info: &ParentInfo,\n    original_dst: Option<SocketAddr>,\n) -> outbound::proxy_protocol::Kind {\n    outbound::proxy_protocol::Kind::Http1(outbound::proxy_protocol::Http1 {\n        routes: base_http_routes(\n            default_backend,\n            routes,\n            service_retry,\n            service_timeouts,\n            allow_l5d_request_headers,\n            parent_info,\n            original_dst,\n        ),\n        failure_accrual: accrual,\n    })\n}\n\n#[allow(clippy::too_many_arguments)]\npub(crate) fn http2_only_protocol(\n    default_backend: outbound::Backend,\n    routes: impl Iterator<Item = (GroupKindNamespaceName, HttpRoute)>,\n    accrual: Option<outbound::FailureAccrual>,\n    service_retry: Option<RouteRetry<HttpRetryCondition>>,\n    service_timeouts: RouteTimeouts,\n    allow_l5d_request_headers: bool,\n    parent_info: &ParentInfo,\n    original_dst: Option<SocketAddr>,\n) -> outbound::proxy_protocol::Kind {\n    outbound::proxy_protocol::Kind::Http2(outbound::proxy_protocol::Http2 {\n        routes: base_http_routes(\n            default_backend,\n            routes,\n            service_retry,\n            service_timeouts,\n            allow_l5d_request_headers,\n            parent_info,\n            original_dst,\n        ),\n        failure_accrual: accrual,\n    })\n}\n\nfn base_http_routes(\n    default_backend: outbound::Backend,\n    routes: impl Iterator<Item = (GroupKindNamespaceName, HttpRoute)>,\n    service_retry: Option<RouteRetry<HttpRetryCondition>>,\n    service_timeouts: RouteTimeouts,\n    allow_l5d_request_headers: bool,\n    parent_info: &ParentInfo,\n    original_dst: Option<SocketAddr>,\n) -> Vec<outbound::HttpRoute> {\n    let mut routes = routes\n        .map(|(gknn, route)| {\n            convert_outbound_route(\n                gknn,\n                route,\n                default_backend.clone(),\n                service_retry.clone(),\n                service_timeouts.clone(),\n                allow_l5d_request_headers,\n                parent_info,\n                original_dst,\n            )\n        })\n        .collect::<Vec<_>>();\n\n    match parent_info {\n        ParentInfo::Service { .. } => {\n            if routes.is_empty() {\n                routes.push(default_outbound_service_route(\n                    default_backend,\n                    service_retry.clone(),\n                    service_timeouts.clone(),\n                ));\n            }\n        }\n        ParentInfo::EgressNetwork { traffic_policy, .. } => {\n            routes.push(default_outbound_egress_route(\n                default_backend,\n                service_retry.clone(),\n                service_timeouts.clone(),\n                traffic_policy,\n            ));\n        }\n    }\n\n    routes\n}\n\n#[allow(clippy::too_many_arguments)]\nfn convert_outbound_route(\n    gknn: GroupKindNamespaceName,\n    HttpRoute {\n        hostnames,\n        rules,\n        creation_timestamp: _,\n    }: HttpRoute,\n    backend: outbound::Backend,\n    service_retry: Option<RouteRetry<HttpRetryCondition>>,\n    service_timeouts: RouteTimeouts,\n    allow_l5d_request_headers: bool,\n    parent_info: &ParentInfo,\n    original_dst: Option<SocketAddr>,\n) -> outbound::HttpRoute {\n    let metadata = Some(meta::Metadata {\n        kind: Some(meta::metadata::Kind::Resource(meta::Resource {\n            group: gknn.group.to_string(),\n            kind: gknn.kind.to_string(),\n            namespace: gknn.namespace.to_string(),\n            name: gknn.name.to_string(),\n            ..Default::default()\n        })),\n    });\n\n    let hosts = hostnames.into_iter().map(convert_host_match).collect();\n\n    let rules = rules\n        .into_iter()\n        .map(\n            |OutboundRouteRule {\n                 matches,\n                 backends,\n                 mut retry,\n                 mut timeouts,\n                 filters,\n             }| {\n                let backends = backends\n                    .into_iter()\n                    .map(|b| convert_backend(b, parent_info, original_dst))\n                    .collect::<Vec<_>>();\n                let dist = if backends.is_empty() {\n                    outbound::http_route::distribution::Kind::FirstAvailable(\n                        outbound::http_route::distribution::FirstAvailable {\n                            backends: vec![outbound::http_route::RouteBackend {\n                                backend: Some(backend.clone()),\n                                filters: vec![],\n                                ..Default::default()\n                            }],\n                        },\n                    )\n                } else {\n                    outbound::http_route::distribution::Kind::RandomAvailable(\n                        outbound::http_route::distribution::RandomAvailable { backends },\n                    )\n                };\n                if timeouts == Default::default() {\n                    timeouts = service_timeouts.clone();\n                }\n                if retry.is_none() {\n                    retry = service_retry.clone();\n                }\n                // This encoder sets deprecated timeouts for older proxies.\n                #[allow(deprecated)]\n                outbound::http_route::Rule {\n                    matches: matches.into_iter().map(convert_match).collect(),\n                    backends: Some(outbound::http_route::Distribution { kind: Some(dist) }),\n                    filters: filters.into_iter().map(convert_to_filter).collect(),\n                    request_timeout: timeouts\n                        .request\n                        .and_then(|d| convert_duration(\"request timeout\", d)),\n                    timeouts: Some(convert_timeouts(timeouts)),\n                    retry: retry.map(convert_retry),\n                    allow_l5d_request_headers,\n                }\n            },\n        )\n        .collect();\n\n    outbound::HttpRoute {\n        metadata,\n        hosts,\n        rules,\n    }\n}\n\nfn convert_backend(\n    backend: Backend,\n    parent_info: &ParentInfo,\n    original_dst: Option<SocketAddr>,\n) -> outbound::http_route::WeightedRouteBackend {\n    let original_dst_port = original_dst.map(|o| o.port());\n    match backend {\n        Backend::Addr(addr) => {\n            let socket_addr = SocketAddr::new(addr.addr, addr.port.get());\n            outbound::http_route::WeightedRouteBackend {\n                weight: addr.weight,\n                backend: Some(outbound::http_route::RouteBackend {\n                    backend: Some(outbound::Backend {\n                        metadata: None,\n                        queue: Some(default_queue_config()),\n                        kind: Some(outbound::backend::Kind::Forward(\n                            destination::WeightedAddr {\n                                addr: Some(socket_addr.into()),\n                                weight: addr.weight,\n                                ..Default::default()\n                            },\n                        )),\n                    }),\n                    filters: Default::default(),\n                    ..Default::default()\n                }),\n            }\n        }\n        Backend::Service(svc) if svc.exists => {\n            let filters = svc\n                .filters\n                .clone()\n                .into_iter()\n                .map(convert_to_filter)\n                .collect();\n            outbound::http_route::WeightedRouteBackend {\n                weight: svc.weight,\n                backend: Some(outbound::http_route::RouteBackend {\n                    backend: Some(outbound::Backend {\n                        metadata: Some(super::service_meta(svc.clone())),\n                        queue: Some(default_queue_config()),\n                        kind: Some(outbound::backend::Kind::Balancer(\n                            outbound::backend::BalanceP2c {\n                                discovery: Some(outbound::backend::EndpointDiscovery {\n                                    kind: Some(outbound::backend::endpoint_discovery::Kind::Dst(\n                                        outbound::backend::endpoint_discovery::DestinationGet {\n                                            path: svc.authority,\n                                        },\n                                    )),\n                                }),\n                                load: Some(default_balancer_config()),\n                            },\n                        )),\n                    }),\n                    filters,\n                    ..Default::default()\n                }),\n            }\n        }\n        Backend::Service(svc) => invalid_backend(\n            svc.weight,\n            format!(\"Service not found {}\", svc.name),\n            super::service_meta(svc),\n        ),\n        Backend::EgressNetwork(egress_net) if egress_net.exists => {\n            match (parent_info, original_dst) {\n                (\n                    ParentInfo::EgressNetwork {\n                        name, namespace, ..\n                    },\n                    Some(original_dst),\n                ) => {\n                    if *name == egress_net.name && *namespace == egress_net.namespace {\n                        let filters = egress_net\n                            .filters\n                            .clone()\n                            .into_iter()\n                            .map(convert_to_filter)\n                            .collect();\n\n                        outbound::http_route::WeightedRouteBackend {\n                            weight: egress_net.weight,\n                            backend: Some(outbound::http_route::RouteBackend {\n                                backend: Some(outbound::Backend {\n                                    metadata: Some(super::egress_net_meta(\n                                        egress_net.clone(),\n                                        original_dst_port,\n                                    )),\n                                    queue: Some(default_queue_config()),\n                                    kind: Some(outbound::backend::Kind::Forward(\n                                        destination::WeightedAddr {\n                                            addr: Some(original_dst.into()),\n                                            weight: egress_net.weight,\n                                            ..Default::default()\n                                        },\n                                    )),\n                                }),\n                                filters,\n                                ..Default::default()\n                            }),\n                        }\n                    } else {\n                        let weight = egress_net.weight;\n                        let message =  \"Route with EgressNetwork backend needs to have the same EgressNetwork as a parent\".to_string();\n                        invalid_backend(\n                            weight,\n                            message,\n                            super::egress_net_meta(egress_net, original_dst_port),\n                        )\n                    }\n                }\n                (ParentInfo::EgressNetwork { .. }, None) => invalid_backend(\n                    egress_net.weight,\n                    \"EgressNetwork can be resolved from an ip:port combo only\".to_string(),\n                    super::egress_net_meta(egress_net, original_dst_port),\n                ),\n                (ParentInfo::Service { .. }, _) => invalid_backend(\n                    egress_net.weight,\n                    \"EgressNetwork backends attach to EgressNetwork parents only\".to_string(),\n                    super::egress_net_meta(egress_net, original_dst_port),\n                ),\n            }\n        }\n        Backend::EgressNetwork(egress_net) => invalid_backend(\n            egress_net.weight,\n            format!(\"EgressNetwork not found {}\", egress_net.name),\n            super::egress_net_meta(egress_net, original_dst_port),\n        ),\n        Backend::Invalid { weight, message } => invalid_backend(\n            weight,\n            message,\n            meta::Metadata {\n                kind: Some(meta::metadata::Kind::Default(\"invalid\".to_string())),\n            },\n        ),\n    }\n}\n\nfn invalid_backend(\n    weight: u32,\n    message: String,\n    meta: meta::Metadata,\n) -> outbound::http_route::WeightedRouteBackend {\n    outbound::http_route::WeightedRouteBackend {\n        weight,\n        backend: Some(outbound::http_route::RouteBackend {\n            backend: Some(outbound::Backend {\n                metadata: Some(meta),\n                queue: Some(default_queue_config()),\n                kind: None,\n            }),\n            filters: vec![outbound::http_route::Filter {\n                kind: Some(outbound::http_route::filter::Kind::FailureInjector(\n                    http_route::HttpFailureInjector {\n                        status: 500,\n                        message,\n                        ratio: None,\n                    },\n                )),\n            }],\n            ..Default::default()\n        }),\n    }\n}\n\npub(crate) fn default_outbound_service_route(\n    backend: outbound::Backend,\n    service_retry: Option<RouteRetry<HttpRetryCondition>>,\n    service_timeouts: RouteTimeouts,\n) -> outbound::HttpRoute {\n    // This encoder sets deprecated timeouts for older proxies.\n    #![allow(deprecated)]\n    let metadata = Some(meta::Metadata {\n        kind: Some(meta::metadata::Kind::Default(\"http\".to_string())),\n    });\n    let rules = vec![outbound::http_route::Rule {\n        matches: vec![http_route::HttpRouteMatch {\n            path: Some(http_route::PathMatch {\n                kind: Some(http_route::path_match::Kind::Prefix(\"/\".to_string())),\n            }),\n            ..Default::default()\n        }],\n        backends: Some(outbound::http_route::Distribution {\n            kind: Some(outbound::http_route::distribution::Kind::FirstAvailable(\n                outbound::http_route::distribution::FirstAvailable {\n                    backends: vec![outbound::http_route::RouteBackend {\n                        backend: Some(backend),\n                        filters: vec![],\n                        ..Default::default()\n                    }],\n                },\n            )),\n        }),\n        retry: service_retry.map(convert_retry),\n        request_timeout: service_timeouts\n            .request\n            .and_then(|d| convert_duration(\"request timeout\", d)),\n        timeouts: Some(convert_timeouts(service_timeouts)),\n        ..Default::default()\n    }];\n    outbound::HttpRoute {\n        metadata,\n        rules,\n        ..Default::default()\n    }\n}\n\npub(crate) fn default_outbound_egress_route(\n    backend: outbound::Backend,\n    service_retry: Option<RouteRetry<HttpRetryCondition>>,\n    service_timeouts: RouteTimeouts,\n    traffic_policy: &TrafficPolicy,\n) -> outbound::HttpRoute {\n    #![allow(deprecated)]\n    let (filters, name) = match traffic_policy {\n        TrafficPolicy::Allow => (Vec::default(), \"http-egress-allow\"),\n        TrafficPolicy::Deny => (\n            vec![outbound::http_route::Filter {\n                kind: Some(outbound::http_route::filter::Kind::FailureInjector(\n                    http_route::HttpFailureInjector {\n                        status: 403,\n                        message: \"traffic not allowed\".to_string(),\n                        ratio: None,\n                    },\n                )),\n            }],\n            \"http-egress-deny\",\n        ),\n    };\n\n    // This encoder sets deprecated timeouts for older proxies.\n    let metadata = Some(meta::Metadata {\n        kind: Some(meta::metadata::Kind::Default(name.to_string())),\n    });\n    let rules = vec![outbound::http_route::Rule {\n        matches: vec![http_route::HttpRouteMatch {\n            path: Some(http_route::PathMatch {\n                kind: Some(http_route::path_match::Kind::Prefix(\"/\".to_string())),\n            }),\n            ..Default::default()\n        }],\n        backends: Some(outbound::http_route::Distribution {\n            kind: Some(outbound::http_route::distribution::Kind::FirstAvailable(\n                outbound::http_route::distribution::FirstAvailable {\n                    backends: vec![outbound::http_route::RouteBackend {\n                        backend: Some(backend),\n                        ..Default::default()\n                    }],\n                },\n            )),\n        }),\n        retry: service_retry.map(convert_retry),\n        request_timeout: service_timeouts\n            .request\n            .and_then(|d| convert_duration(\"request timeout\", d)),\n        timeouts: Some(convert_timeouts(service_timeouts)),\n        filters,\n        ..Default::default()\n    }];\n    outbound::HttpRoute {\n        metadata,\n        rules,\n        ..Default::default()\n    }\n}\n\nfn convert_to_filter(filter: Filter) -> outbound::http_route::Filter {\n    use outbound::http_route::filter::Kind;\n\n    outbound::http_route::Filter {\n        kind: Some(match filter {\n            Filter::RequestHeaderModifier(f) => {\n                Kind::RequestHeaderModifier(convert_request_header_modifier_filter(f))\n            }\n            Filter::ResponseHeaderModifier(f) => {\n                Kind::ResponseHeaderModifier(convert_response_header_modifier_filter(f))\n            }\n            Filter::RequestRedirect(f) => Kind::Redirect(convert_redirect_filter(f)),\n            Filter::FailureInjector(f) => Kind::FailureInjector(convert_failure_injector_filter(f)),\n        }),\n    }\n}\n\nfn convert_retry(r: RouteRetry<HttpRetryCondition>) -> outbound::http_route::Retry {\n    outbound::http_route::Retry {\n        max_retries: r.limit.into(),\n        max_request_bytes: 64 * 1024,\n        backoff: Some(outbound::ExponentialBackoff {\n            min_backoff: Some(time::Duration::from_millis(25).try_into().unwrap()),\n            max_backoff: Some(time::Duration::from_millis(250).try_into().unwrap()),\n            jitter_ratio: 1.0,\n        }),\n        conditions: Some(r.conditions.iter().flatten().fold(\n            outbound::http_route::retry::Conditions::default(),\n            |mut cond, c| {\n                cond.status_ranges\n                    .push(outbound::http_route::retry::conditions::StatusRange {\n                        start: c.status_min,\n                        end: c.status_max,\n                    });\n                cond\n            },\n        )),\n        timeout: r.timeout.and_then(|d| convert_duration(\"retry timeout\", d)),\n    }\n}\n\nfn convert_timeouts(timeouts: RouteTimeouts) -> http_route::Timeouts {\n    http_route::Timeouts {\n        request: timeouts\n            .request\n            .and_then(|d| convert_duration(\"request timeout\", d)),\n        idle: timeouts\n            .idle\n            .and_then(|d| convert_duration(\"idle timeout\", d)),\n        response: timeouts\n            .response\n            .and_then(|d| convert_duration(\"response timeout\", d)),\n    }\n}\n"
  },
  {
    "path": "policy-controller/grpc/src/outbound/tcp.rs",
    "content": "use super::{default_balancer_config, default_queue_config};\nuse linkerd2_proxy_api::{self, destination, meta, outbound};\nuse linkerd_policy_controller_core::{\n    outbound::{Backend, ParentInfo, TcpRoute, TrafficPolicy},\n    routes::GroupKindNamespaceName,\n};\nuse std::net::SocketAddr;\n\n// Since there is no way to do real selection on a TCPRoute, we only allow 1\nconst MAXIMUM_ALLOWED_TCP_ROUTES: usize = 1;\n\npub(crate) fn protocol(\n    default_backend: outbound::Backend,\n    routes: impl Iterator<Item = (GroupKindNamespaceName, TcpRoute)>,\n    parent_info: &ParentInfo,\n    original_dst: Option<SocketAddr>,\n) -> outbound::proxy_protocol::Kind {\n    let mut routes = routes\n        .take(MAXIMUM_ALLOWED_TCP_ROUTES)\n        .map(|(gknn, route)| {\n            convert_outbound_route(\n                gknn,\n                route,\n                default_backend.clone(),\n                parent_info,\n                original_dst,\n            )\n        })\n        .collect::<Vec<_>>();\n\n    if routes.is_empty() {\n        if let ParentInfo::EgressNetwork { traffic_policy, .. } = parent_info {\n            routes.push(default_outbound_egress_route(\n                default_backend,\n                traffic_policy,\n            ));\n        }\n    }\n\n    outbound::proxy_protocol::Kind::Opaque(outbound::proxy_protocol::Opaque { routes })\n}\n\nfn convert_outbound_route(\n    gknn: GroupKindNamespaceName,\n    TcpRoute {\n        rule,\n        creation_timestamp: _,\n    }: TcpRoute,\n    backend: outbound::Backend,\n    parent_info: &ParentInfo,\n    original_dst: Option<SocketAddr>,\n) -> outbound::OpaqueRoute {\n    let metadata = Some(meta::Metadata {\n        kind: Some(meta::metadata::Kind::Resource(meta::Resource {\n            group: gknn.group.to_string(),\n            kind: gknn.kind.to_string(),\n            namespace: gknn.namespace.to_string(),\n            name: gknn.name.to_string(),\n            ..Default::default()\n        })),\n    });\n\n    let backends = rule\n        .backends\n        .into_iter()\n        .map(|b| convert_backend(b, parent_info, original_dst))\n        .collect::<Vec<_>>();\n\n    let dist = if backends.is_empty() {\n        outbound::opaque_route::distribution::Kind::FirstAvailable(\n            outbound::opaque_route::distribution::FirstAvailable {\n                backends: vec![outbound::opaque_route::RouteBackend {\n                    backend: Some(backend.clone()),\n                    filters: Vec::new(),\n                }],\n            },\n        )\n    } else {\n        outbound::opaque_route::distribution::Kind::RandomAvailable(\n            outbound::opaque_route::distribution::RandomAvailable { backends },\n        )\n    };\n\n    let rules = vec![outbound::opaque_route::Rule {\n        backends: Some(outbound::opaque_route::Distribution { kind: Some(dist) }),\n        filters: Vec::new(),\n    }];\n\n    outbound::OpaqueRoute { metadata, rules }\n}\n\nfn convert_backend(\n    backend: Backend,\n    parent_info: &ParentInfo,\n    original_dst: Option<SocketAddr>,\n) -> outbound::opaque_route::WeightedRouteBackend {\n    let original_dst_port = original_dst.map(|o| o.port());\n\n    match backend {\n        Backend::Addr(addr) => {\n            let socket_addr = SocketAddr::new(addr.addr, addr.port.get());\n            outbound::opaque_route::WeightedRouteBackend {\n                weight: addr.weight,\n                backend: Some(outbound::opaque_route::RouteBackend {\n                    backend: Some(outbound::Backend {\n                        metadata: None,\n                        queue: Some(default_queue_config()),\n                        kind: Some(outbound::backend::Kind::Forward(\n                            destination::WeightedAddr {\n                                addr: Some(socket_addr.into()),\n                                weight: addr.weight,\n                                ..Default::default()\n                            },\n                        )),\n                    }),\n                    filters: Vec::new(),\n                }),\n            }\n        }\n        Backend::Service(svc) if svc.exists => outbound::opaque_route::WeightedRouteBackend {\n            weight: svc.weight,\n            backend: Some(outbound::opaque_route::RouteBackend {\n                backend: Some(outbound::Backend {\n                    metadata: Some(super::service_meta(svc.clone())),\n                    queue: Some(default_queue_config()),\n                    kind: Some(outbound::backend::Kind::Balancer(\n                        outbound::backend::BalanceP2c {\n                            discovery: Some(outbound::backend::EndpointDiscovery {\n                                kind: Some(outbound::backend::endpoint_discovery::Kind::Dst(\n                                    outbound::backend::endpoint_discovery::DestinationGet {\n                                        path: svc.authority,\n                                    },\n                                )),\n                            }),\n                            load: Some(default_balancer_config()),\n                        },\n                    )),\n                }),\n                filters: Vec::new(),\n            }),\n        },\n        Backend::Service(svc) => invalid_backend(\n            svc.weight,\n            format!(\"Service not found {}\", svc.name),\n            super::service_meta(svc),\n        ),\n        Backend::EgressNetwork(egress_net) if egress_net.exists => {\n            match (parent_info, original_dst) {\n                (\n                    ParentInfo::EgressNetwork {\n                        name, namespace, ..\n                    },\n                    Some(original_dst),\n                ) => {\n                    if *name == egress_net.name && *namespace == egress_net.namespace {\n                        outbound::opaque_route::WeightedRouteBackend {\n                            weight: egress_net.weight,\n                            backend: Some(outbound::opaque_route::RouteBackend {\n                                backend: Some(outbound::Backend {\n                                    metadata: Some(super::egress_net_meta(\n                                        egress_net.clone(),\n                                        original_dst_port,\n                                    )),\n                                    queue: Some(default_queue_config()),\n                                    kind: Some(outbound::backend::Kind::Forward(\n                                        destination::WeightedAddr {\n                                            addr: Some(original_dst.into()),\n                                            weight: egress_net.weight,\n                                            ..Default::default()\n                                        },\n                                    )),\n                                }),\n                                filters: Vec::new(),\n                            }),\n                        }\n                    } else {\n                        let weight = egress_net.weight;\n                        let message =  \"Route with EgressNetwork backend needs to have the same EgressNetwork as a parent\".to_string();\n                        invalid_backend(\n                            weight,\n                            message,\n                            super::egress_net_meta(egress_net, original_dst_port),\n                        )\n                    }\n                }\n                (ParentInfo::EgressNetwork { .. }, None) => invalid_backend(\n                    egress_net.weight,\n                    \"EgressNetwork can be resolved from an ip:port combo only\".to_string(),\n                    super::egress_net_meta(egress_net, original_dst_port),\n                ),\n                (ParentInfo::Service { .. }, _) => invalid_backend(\n                    egress_net.weight,\n                    \"EgressNetwork backends attach to EgressNetwork parents only\".to_string(),\n                    super::egress_net_meta(egress_net, original_dst_port),\n                ),\n            }\n        }\n        Backend::EgressNetwork(egress_net) => invalid_backend(\n            egress_net.weight,\n            format!(\"EgressNetwork not found {}\", egress_net.name),\n            super::egress_net_meta(egress_net, original_dst_port),\n        ),\n        Backend::Invalid { weight, message } => invalid_backend(\n            weight,\n            message,\n            meta::Metadata {\n                kind: Some(meta::metadata::Kind::Default(\"invalid\".to_string())),\n            },\n        ),\n    }\n}\n\nfn invalid_backend(\n    weight: u32,\n    message: String,\n    meta: meta::Metadata,\n) -> outbound::opaque_route::WeightedRouteBackend {\n    outbound::opaque_route::WeightedRouteBackend {\n        weight,\n        backend: Some(outbound::opaque_route::RouteBackend {\n            backend: Some(outbound::Backend {\n                metadata: Some(meta),\n                queue: Some(default_queue_config()),\n                kind: None,\n            }),\n\n            filters: vec![outbound::opaque_route::Filter {\n                kind: Some(outbound::opaque_route::filter::Kind::Invalid(\n                    linkerd2_proxy_api::opaque_route::Invalid { message },\n                )),\n            }],\n        }),\n    }\n}\n\npub(crate) fn default_outbound_egress_route(\n    backend: outbound::Backend,\n    traffic_policy: &TrafficPolicy,\n) -> outbound::OpaqueRoute {\n    let (filters, name) = match traffic_policy {\n        TrafficPolicy::Allow => (Vec::default(), \"tcp-egress-allow\"),\n        TrafficPolicy::Deny => (\n            vec![outbound::opaque_route::Filter {\n                kind: Some(outbound::opaque_route::filter::Kind::Forbidden(\n                    linkerd2_proxy_api::opaque_route::Forbidden {},\n                )),\n            }],\n            \"tcp-egress-deny\",\n        ),\n    };\n\n    let metadata = Some(meta::Metadata {\n        kind: Some(meta::metadata::Kind::Default(name.to_string())),\n    });\n    let rules = vec![outbound::opaque_route::Rule {\n        backends: Some(outbound::opaque_route::Distribution {\n            kind: Some(outbound::opaque_route::distribution::Kind::FirstAvailable(\n                outbound::opaque_route::distribution::FirstAvailable {\n                    backends: vec![outbound::opaque_route::RouteBackend {\n                        backend: Some(backend),\n                        filters: Vec::new(),\n                    }],\n                },\n            )),\n        }),\n        filters,\n    }];\n    outbound::OpaqueRoute { metadata, rules }\n}\n"
  },
  {
    "path": "policy-controller/grpc/src/outbound/tls.rs",
    "content": "use super::{default_balancer_config, default_queue_config};\nuse crate::routes::convert_sni_match;\nuse linkerd2_proxy_api::{destination, meta, outbound};\nuse linkerd_policy_controller_core::{\n    outbound::{Backend, ParentInfo, TlsRoute, TrafficPolicy},\n    routes::GroupKindNamespaceName,\n};\nuse std::net::SocketAddr;\n\npub(crate) fn protocol(\n    default_backend: outbound::Backend,\n    routes: impl Iterator<Item = (GroupKindNamespaceName, TlsRoute)>,\n    parent_info: &ParentInfo,\n    original_dst: Option<SocketAddr>,\n) -> outbound::proxy_protocol::Kind {\n    let mut routes = routes\n        .map(|(gknn, route)| {\n            convert_outbound_route(\n                gknn,\n                route,\n                default_backend.clone(),\n                parent_info,\n                original_dst,\n            )\n        })\n        .collect::<Vec<_>>();\n\n    if let ParentInfo::EgressNetwork { traffic_policy, .. } = parent_info {\n        routes.push(default_outbound_egress_route(\n            default_backend,\n            traffic_policy,\n        ));\n    }\n\n    outbound::proxy_protocol::Kind::Tls(outbound::proxy_protocol::Tls { routes })\n}\n\nfn convert_outbound_route(\n    gknn: GroupKindNamespaceName,\n    TlsRoute {\n        hostnames,\n        rule,\n        creation_timestamp: _,\n    }: TlsRoute,\n    backend: outbound::Backend,\n    parent_info: &ParentInfo,\n    original_dst: Option<SocketAddr>,\n) -> outbound::TlsRoute {\n    let metadata = Some(meta::Metadata {\n        kind: Some(meta::metadata::Kind::Resource(meta::Resource {\n            group: gknn.group.to_string(),\n            kind: gknn.kind.to_string(),\n            namespace: gknn.namespace.to_string(),\n            name: gknn.name.to_string(),\n            ..Default::default()\n        })),\n    });\n\n    let snis = hostnames.into_iter().map(convert_sni_match).collect();\n\n    let backends = rule\n        .backends\n        .into_iter()\n        .map(|b| convert_backend(b, parent_info, original_dst))\n        .collect::<Vec<_>>();\n\n    let dist = if backends.is_empty() {\n        outbound::tls_route::distribution::Kind::FirstAvailable(\n            outbound::tls_route::distribution::FirstAvailable {\n                backends: vec![outbound::tls_route::RouteBackend {\n                    backend: Some(backend.clone()),\n                    filters: Vec::new(),\n                }],\n            },\n        )\n    } else {\n        outbound::tls_route::distribution::Kind::RandomAvailable(\n            outbound::tls_route::distribution::RandomAvailable { backends },\n        )\n    };\n\n    let rules = vec![outbound::tls_route::Rule {\n        backends: Some(outbound::tls_route::Distribution { kind: Some(dist) }),\n        filters: Vec::new(),\n    }];\n\n    outbound::TlsRoute {\n        metadata,\n        snis,\n        rules,\n    }\n}\n\nfn convert_backend(\n    backend: Backend,\n    parent_info: &ParentInfo,\n    original_dst: Option<SocketAddr>,\n) -> outbound::tls_route::WeightedRouteBackend {\n    let original_dst_port = original_dst.map(|o| o.port());\n\n    match backend {\n        Backend::Addr(addr) => {\n            let socket_addr = SocketAddr::new(addr.addr, addr.port.get());\n            outbound::tls_route::WeightedRouteBackend {\n                weight: addr.weight,\n                backend: Some(outbound::tls_route::RouteBackend {\n                    backend: Some(outbound::Backend {\n                        metadata: None,\n                        queue: Some(default_queue_config()),\n                        kind: Some(outbound::backend::Kind::Forward(\n                            destination::WeightedAddr {\n                                addr: Some(socket_addr.into()),\n                                weight: addr.weight,\n                                ..Default::default()\n                            },\n                        )),\n                    }),\n                    filters: Vec::new(),\n                }),\n            }\n        }\n        Backend::Service(svc) if svc.exists => outbound::tls_route::WeightedRouteBackend {\n            weight: svc.weight,\n            backend: Some(outbound::tls_route::RouteBackend {\n                backend: Some(outbound::Backend {\n                    metadata: Some(super::service_meta(svc.clone())),\n                    queue: Some(default_queue_config()),\n                    kind: Some(outbound::backend::Kind::Balancer(\n                        outbound::backend::BalanceP2c {\n                            discovery: Some(outbound::backend::EndpointDiscovery {\n                                kind: Some(outbound::backend::endpoint_discovery::Kind::Dst(\n                                    outbound::backend::endpoint_discovery::DestinationGet {\n                                        path: svc.authority,\n                                    },\n                                )),\n                            }),\n                            load: Some(default_balancer_config()),\n                        },\n                    )),\n                }),\n                filters: Vec::new(),\n            }),\n        },\n        Backend::Service(svc) => invalid_backend(\n            svc.weight,\n            format!(\"Service not found {}\", svc.name),\n            super::service_meta(svc),\n        ),\n        Backend::EgressNetwork(egress_net) if egress_net.exists => {\n            match (parent_info, original_dst) {\n                (\n                    ParentInfo::EgressNetwork {\n                        name, namespace, ..\n                    },\n                    Some(original_dst),\n                ) => {\n                    if *name == egress_net.name && *namespace == egress_net.namespace {\n                        outbound::tls_route::WeightedRouteBackend {\n                            weight: egress_net.weight,\n                            backend: Some(outbound::tls_route::RouteBackend {\n                                backend: Some(outbound::Backend {\n                                    metadata: Some(super::egress_net_meta(\n                                        egress_net.clone(),\n                                        original_dst_port,\n                                    )),\n                                    queue: Some(default_queue_config()),\n                                    kind: Some(outbound::backend::Kind::Forward(\n                                        destination::WeightedAddr {\n                                            addr: Some(original_dst.into()),\n                                            weight: egress_net.weight,\n                                            ..Default::default()\n                                        },\n                                    )),\n                                }),\n                                filters: Vec::new(),\n                            }),\n                        }\n                    } else {\n                        let weight = egress_net.weight;\n                        let message =  \"Route with EgressNetwork backend needs to have the same EgressNetwork as a parent\".to_string();\n                        invalid_backend(\n                            weight,\n                            message,\n                            super::egress_net_meta(egress_net, original_dst_port),\n                        )\n                    }\n                }\n                (ParentInfo::EgressNetwork { .. }, None) => invalid_backend(\n                    egress_net.weight,\n                    \"EgressNetwork can be resolved from an ip:port combo only\".to_string(),\n                    super::egress_net_meta(egress_net, original_dst_port),\n                ),\n                (ParentInfo::Service { .. }, _) => invalid_backend(\n                    egress_net.weight,\n                    \"EgressNetwork backends attach to EgressNetwork parents only\".to_string(),\n                    super::egress_net_meta(egress_net, original_dst_port),\n                ),\n            }\n        }\n        Backend::EgressNetwork(egress_net) => invalid_backend(\n            egress_net.weight,\n            format!(\"EgressNetwork not found {}\", egress_net.name),\n            super::egress_net_meta(egress_net, original_dst_port),\n        ),\n        Backend::Invalid { weight, message } => invalid_backend(\n            weight,\n            message,\n            meta::Metadata {\n                kind: Some(meta::metadata::Kind::Default(\"invalid\".to_string())),\n            },\n        ),\n    }\n}\n\nfn invalid_backend(\n    weight: u32,\n    message: String,\n    meta: meta::Metadata,\n) -> outbound::tls_route::WeightedRouteBackend {\n    outbound::tls_route::WeightedRouteBackend {\n        weight,\n        backend: Some(outbound::tls_route::RouteBackend {\n            backend: Some(outbound::Backend {\n                metadata: Some(meta),\n                queue: Some(default_queue_config()),\n                kind: None,\n            }),\n            filters: vec![outbound::tls_route::Filter {\n                kind: Some(outbound::tls_route::filter::Kind::Invalid(\n                    linkerd2_proxy_api::opaque_route::Invalid { message },\n                )),\n            }],\n        }),\n    }\n}\n\npub(crate) fn default_outbound_egress_route(\n    backend: outbound::Backend,\n    traffic_policy: &TrafficPolicy,\n) -> outbound::TlsRoute {\n    let (filters, name) = match traffic_policy {\n        TrafficPolicy::Allow => (Vec::default(), \"tls-egress-allow\"),\n        TrafficPolicy::Deny => (\n            vec![outbound::tls_route::Filter {\n                kind: Some(outbound::tls_route::filter::Kind::Forbidden(\n                    linkerd2_proxy_api::opaque_route::Forbidden {},\n                )),\n            }],\n            \"tls-egress-deny\",\n        ),\n    };\n\n    let metadata = Some(meta::Metadata {\n        kind: Some(meta::metadata::Kind::Default(name.to_string())),\n    });\n    let rules = vec![outbound::tls_route::Rule {\n        backends: Some(outbound::tls_route::Distribution {\n            kind: Some(outbound::tls_route::distribution::Kind::FirstAvailable(\n                outbound::tls_route::distribution::FirstAvailable {\n                    backends: vec![outbound::tls_route::RouteBackend {\n                        backend: Some(backend),\n                        filters: Vec::new(),\n                    }],\n                },\n            )),\n        }),\n        filters,\n    }];\n    outbound::TlsRoute {\n        metadata,\n        rules,\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "policy-controller/grpc/src/outbound.rs",
    "content": "use crate::metrics::{self, GrpcServerMetricsFamily, GrpcServerRPCMetrics};\nuse crate::workload;\nuse ::http::uri::Authority;\nuse futures::{prelude::*, StreamExt};\nuse linkerd2_proxy_api::{\n    self as api, destination,\n    meta::{metadata, Metadata, Resource},\n    outbound::{\n        self,\n        outbound_policies_server::{OutboundPolicies, OutboundPoliciesServer},\n    },\n};\nuse linkerd_policy_controller_core::{\n    outbound::{\n        AppProtocol, DiscoverOutboundPolicy, ExternalPolicyStream, Kind, OutboundDiscoverTarget,\n        OutboundPolicy, OutboundPolicyStream, ParentInfo, ResourceTarget, Route,\n        WeightedEgressNetwork, WeightedService,\n    },\n    routes::GroupKindNamespaceName,\n};\nuse std::{net::SocketAddr, num::NonZeroU16, str::FromStr, sync::Arc, time};\n\nmod grpc;\nmod http;\nmod tcp;\nmod tls;\n\n#[derive(Clone, Debug)]\npub struct OutboundPolicyServer<T> {\n    index: T,\n    // Used to parse named addresses into <svc>.<ns>.svc.<cluster-domain>.\n    cluster_domain: Arc<str>,\n    allow_l5d_request_headers: bool,\n    drain: drain::Watch,\n    get_metrics: GrpcServerRPCMetrics,\n    watch_metrics: GrpcServerRPCMetrics,\n}\n\nimpl<T> OutboundPolicyServer<T>\nwhere\n    T: DiscoverOutboundPolicy<ResourceTarget, OutboundDiscoverTarget> + Send + Sync + 'static,\n{\n    pub fn new(\n        discover: T,\n        cluster_domain: impl Into<Arc<str>>,\n        allow_l5d_request_headers: bool,\n        drain: drain::Watch,\n        metrics: GrpcServerMetricsFamily,\n    ) -> Self {\n        const SERVICE: &str = \"io.linkerd.proxy.outbound.OutboundPolicies\";\n        let get_metrics = metrics.unary_rpc(SERVICE, \"Get\");\n        let watch_metrics = metrics.server_stream_rpc(SERVICE, \"Watch\");\n\n        Self {\n            index: discover,\n            cluster_domain: cluster_domain.into(),\n            allow_l5d_request_headers,\n            drain,\n            get_metrics,\n            watch_metrics,\n        }\n    }\n\n    pub fn svc(self) -> OutboundPoliciesServer<Self> {\n        OutboundPoliciesServer::new(self)\n    }\n\n    fn lookup(&self, spec: outbound::TrafficSpec) -> Result<OutboundDiscoverTarget, tonic::Status> {\n        let target = spec\n            .target\n            .ok_or_else(|| tonic::Status::invalid_argument(\"target is required\"))?;\n        let source_namespace = workload::Workload::from_str(&spec.source_workload)?.namespace;\n        let target = match target {\n            outbound::traffic_spec::Target::Addr(target) => target,\n            outbound::traffic_spec::Target::Authority(auth) => {\n                return self.lookup_authority(&auth).map(|(namespace, name, port)| {\n                    OutboundDiscoverTarget::Resource(ResourceTarget {\n                        kind: Kind::Service,\n                        name,\n                        namespace,\n                        port,\n                        source_namespace,\n                    })\n                })\n            }\n        };\n\n        let port = target\n            .port\n            .try_into()\n            .map_err(|_| tonic::Status::invalid_argument(\"port outside valid range\"))?;\n        let port = NonZeroU16::new(port)\n            .ok_or_else(|| tonic::Status::invalid_argument(\"port cannot be zero\"))?;\n\n        let addr = target\n            .ip\n            .ok_or_else(|| tonic::Status::invalid_argument(\"traffic target must have an IP\"))?\n            .try_into()\n            .map_err(|error| {\n                tonic::Status::invalid_argument(format!(\"failed to parse target addr: {error}\"))\n            })?;\n\n        self.index\n            .lookup_ip(addr, port, source_namespace)\n            .ok_or_else(|| tonic::Status::not_found(\"No such service\"))\n    }\n\n    fn lookup_authority(\n        &self,\n        authority: &str,\n    ) -> Result<(String, String, NonZeroU16), tonic::Status> {\n        let auth = authority\n            .parse::<Authority>()\n            .map_err(|_| tonic::Status::invalid_argument(\"invalid authority\"))?;\n\n        let mut host = auth.host();\n        if host.is_empty() {\n            return Err(tonic::Status::invalid_argument(\n                \"authority must have a host\",\n            ));\n        }\n\n        host = host\n            .trim_end_matches('.')\n            .trim_end_matches(&*self.cluster_domain);\n\n        let mut parts = host.split('.');\n        let invalid = {\n            let domain = &self.cluster_domain;\n            move || {\n                tonic::Status::not_found(format!(\n                    \"authority must be of the form <name>.<namespace>.svc.{domain}\",\n                ))\n            }\n        };\n        let name = parts.next().ok_or_else(invalid)?;\n        let namespace = parts.next().ok_or_else(invalid)?;\n        if parts.next() != Some(\"svc\") {\n            return Err(invalid());\n        };\n\n        let port = auth\n            .port_u16()\n            .and_then(|p| NonZeroU16::try_from(p).ok())\n            .unwrap_or_else(|| 80.try_into().unwrap());\n\n        Ok((namespace.to_string(), name.to_string(), port))\n    }\n}\n\n#[async_trait::async_trait]\nimpl<T> OutboundPolicies for OutboundPolicyServer<T>\nwhere\n    T: DiscoverOutboundPolicy<ResourceTarget, OutboundDiscoverTarget> + Send + Sync + 'static,\n{\n    async fn get(\n        &self,\n        req: tonic::Request<outbound::TrafficSpec>,\n    ) -> Result<tonic::Response<outbound::OutboundPolicy>, tonic::Status> {\n        let metrics = self.get_metrics.start();\n        let target = match self.lookup(req.into_inner()) {\n            Ok(target) => target,\n            Err(status) => {\n                metrics.end(status.code());\n                return Err(status);\n            }\n        };\n\n        match target {\n            OutboundDiscoverTarget::Resource(resource) => {\n                let original_dst = resource.original_dst();\n                match self.index.get_outbound_policy(resource).await {\n                    Ok(Some(policy)) => Ok(tonic::Response::new(to_proto(\n                        policy,\n                        self.allow_l5d_request_headers,\n                        original_dst,\n                    ))),\n                    Ok(None) => {\n                        let status = tonic::Status::not_found(\"unknown target\");\n                        metrics.end(status.code());\n                        return Err(status);\n                    }\n                    Err(error) => {\n                        let status = tonic::Status::internal(format!(\n                            \"failed to get outbound policy: {error}\"\n                        ));\n                        metrics.end(status.code());\n                        return Err(status);\n                    }\n                }\n            }\n\n            OutboundDiscoverTarget::External(original_dst) => {\n                Ok(tonic::Response::new(fallback(original_dst)))\n            }\n\n            OutboundDiscoverTarget::UndefinedPort(resource) => {\n                Ok(tonic::Response::new(undefined_port(resource)))\n            }\n        }\n    }\n\n    type WatchStream = BoxWatchStream;\n\n    async fn watch(\n        &self,\n        req: tonic::Request<outbound::TrafficSpec>,\n    ) -> Result<tonic::Response<BoxWatchStream>, tonic::Status> {\n        let metrics = self.watch_metrics.start();\n        tracing::debug!(?req, \"watching outbound policy\");\n        let target = match self.lookup(req.into_inner()) {\n            Ok(target) => target,\n            Err(status) => {\n                metrics.end(status.code());\n                return Err(status);\n            }\n        };\n\n        let drain = self.drain.clone();\n        match target {\n            OutboundDiscoverTarget::Resource(resource) => {\n                let original_dst = resource.original_dst();\n                let rx = match self.index.watch_outbound_policy(resource).await {\n                    Ok(Some(rx)) => rx,\n                    Ok(None) => {\n                        let status = tonic::Status::not_found(\"unknown target\");\n                        metrics.end(status.code());\n                        return Err(status);\n                    }\n                    Err(error) => {\n                        let status = tonic::Status::internal(format!(\n                            \"failed to get outbound policy: {error}\"\n                        ));\n                        metrics.end(status.code());\n                        return Err(status);\n                    }\n                };\n\n                Ok(tonic::Response::new(response_stream(\n                    drain,\n                    rx,\n                    self.allow_l5d_request_headers,\n                    original_dst,\n                    metrics,\n                )))\n            }\n\n            OutboundDiscoverTarget::External(original_dst) => {\n                let rx = self.index.watch_external_policy().await;\n                Ok(tonic::Response::new(external_stream(\n                    drain,\n                    rx,\n                    original_dst,\n                    metrics,\n                )))\n            }\n            OutboundDiscoverTarget::UndefinedPort(resource) => {\n                Ok(tonic::Response::new(undefined_port_stream(drain, resource)))\n            }\n        }\n    }\n}\n\ntype BoxWatchStream = std::pin::Pin<\n    Box<dyn Stream<Item = Result<outbound::OutboundPolicy, tonic::Status>> + Send + Sync>,\n>;\n\nfn response_stream(\n    drain: drain::Watch,\n    mut rx: OutboundPolicyStream,\n    allow_l5d_request_headers: bool,\n    original_dst: Option<SocketAddr>,\n    metrics: metrics::ResponseObserver,\n) -> BoxWatchStream {\n    Box::pin(async_stream::try_stream! {\n        tokio::pin! {\n            let shutdown = drain.signaled();\n        }\n\n        loop {\n            tokio::select! {\n                // When the port is updated with a new server, update the server watch.\n                res = rx.next() => match res {\n                    Some(policy) => {\n                        metrics.msg_sent();\n                        yield to_proto(policy, allow_l5d_request_headers, original_dst);\n                    }\n                    None => break,\n                },\n\n                // If the server starts shutting down, close the stream so that it doesn't hold the\n                // server open.\n                _ = &mut shutdown => break,\n            }\n        }\n\n        metrics.end(tonic::Code::Ok);\n    })\n}\n\nfn external_stream(\n    drain: drain::Watch,\n    mut rx: ExternalPolicyStream,\n    original_dst: SocketAddr,\n    metrics: metrics::ResponseObserver,\n) -> BoxWatchStream {\n    Box::pin(async_stream::try_stream! {\n        tokio::pin! {\n            let shutdown = drain.signaled();\n        }\n\n        loop {\n            tokio::select! {\n                res = rx.next() => match res {\n                    Some(_) => {\n                        metrics.msg_sent();\n                        yield fallback(original_dst);\n                    }\n                    None => break,\n                },\n\n                // If the server starts shutting down, close the stream so that it doesn't hold the\n                // server open.\n                _ = &mut shutdown => break,\n            }\n        }\n\n        metrics.end(tonic::Code::Ok);\n    })\n}\n\nfn fallback(original_dst: SocketAddr) -> outbound::OutboundPolicy {\n    // This encoder sets deprecated timeouts for older proxies.\n    let metadata = Some(Metadata {\n        kind: Some(metadata::Kind::Default(\"egress-fallback\".to_string())),\n    });\n\n    let backend = outbound::Backend {\n        metadata: metadata.clone(),\n        queue: Some(default_queue_config()),\n        kind: Some(outbound::backend::Kind::Forward(\n            destination::WeightedAddr {\n                addr: Some(original_dst.into()),\n                weight: 1,\n                ..Default::default()\n            },\n        )),\n    };\n\n    let opaque = outbound::proxy_protocol::Opaque {\n        routes: vec![outbound::OpaqueRoute {\n            metadata: Some(Metadata {\n                kind: Some(metadata::Kind::Default(\"egress-fallback\".to_string())),\n            }),\n            rules: vec![outbound::opaque_route::Rule {\n                backends: Some(outbound::opaque_route::Distribution {\n                    kind: Some(outbound::opaque_route::distribution::Kind::FirstAvailable(\n                        outbound::opaque_route::distribution::FirstAvailable {\n                            backends: vec![outbound::opaque_route::RouteBackend {\n                                backend: Some(backend.clone()),\n                                filters: Vec::new(),\n                            }],\n                        },\n                    )),\n                }),\n                filters: Vec::new(),\n            }],\n        }],\n    };\n\n    let http_routes = vec![outbound::HttpRoute {\n        hosts: Vec::default(),\n        metadata: metadata.clone(),\n        rules: vec![outbound::http_route::Rule {\n            backends: Some(outbound::http_route::Distribution {\n                kind: Some(outbound::http_route::distribution::Kind::FirstAvailable(\n                    outbound::http_route::distribution::FirstAvailable {\n                        backends: vec![outbound::http_route::RouteBackend {\n                            backend: Some(backend),\n                            ..Default::default()\n                        }],\n                    },\n                )),\n            }),\n            matches: vec![api::http_route::HttpRouteMatch::default()],\n            filters: Vec::default(),\n            ..Default::default()\n        }],\n    }];\n\n    outbound::OutboundPolicy {\n        metadata,\n        protocol: Some(outbound::ProxyProtocol {\n            kind: Some(outbound::proxy_protocol::Kind::Detect(\n                outbound::proxy_protocol::Detect {\n                    timeout: Some(\n                        time::Duration::from_secs(10)\n                            .try_into()\n                            .expect(\"failed to convert detect timeout to protobuf\"),\n                    ),\n                    opaque: Some(opaque),\n                    http1: Some(outbound::proxy_protocol::Http1 {\n                        routes: http_routes.clone(),\n                        failure_accrual: None,\n                    }),\n                    http2: Some(outbound::proxy_protocol::Http2 {\n                        routes: http_routes,\n                        failure_accrual: None,\n                    }),\n                },\n            )),\n        }),\n    }\n}\n\nfn undefined_port(target: ResourceTarget) -> outbound::OutboundPolicy {\n    let metadata = Some(Metadata {\n        kind: Some(metadata::Kind::Resource(Resource {\n            group: target.kind.group().to_string(),\n            kind: target.kind.kind().to_string(),\n            name: target.name,\n            namespace: target.namespace,\n            section: String::default(),\n            port: target.port.get() as u32,\n        })),\n    });\n\n    let opaque = outbound::proxy_protocol::Opaque {\n        routes: vec![outbound::OpaqueRoute {\n            metadata: Some(Metadata {\n                kind: Some(metadata::Kind::Default(\"undefined-port\".to_string())),\n            }),\n            rules: vec![outbound::opaque_route::Rule {\n                backends: Some(outbound::opaque_route::Distribution {\n                    kind: Some(outbound::opaque_route::distribution::Kind::Empty(\n                        outbound::opaque_route::distribution::Empty {},\n                    )),\n                }),\n                filters: vec![outbound::opaque_route::Filter {\n                    kind: Some(outbound::opaque_route::filter::Kind::Forbidden(\n                        linkerd2_proxy_api::opaque_route::Forbidden {},\n                    )),\n                }],\n            }],\n        }],\n    };\n\n    outbound::OutboundPolicy {\n        metadata,\n        protocol: Some(outbound::ProxyProtocol {\n            kind: Some(outbound::proxy_protocol::Kind::Opaque(opaque)),\n        }),\n    }\n}\n\nfn undefined_port_stream(drain: drain::Watch, target: ResourceTarget) -> BoxWatchStream {\n    Box::pin(async_stream::try_stream! {\n        tokio::pin! {\n            let shutdown = drain.signaled();\n        }\n\n        yield undefined_port(target.clone());\n\n        let _ = shutdown.await;\n    })\n}\n\nfn to_proto(\n    policy: OutboundPolicy,\n    allow_l5d_request_headers: bool,\n    original_dst: Option<SocketAddr>,\n) -> outbound::OutboundPolicy {\n    let backend: outbound::Backend = default_backend(&policy, original_dst);\n\n    let accrual = policy.accrual.map(|accrual| outbound::FailureAccrual {\n        kind: Some(match accrual {\n            linkerd_policy_controller_core::outbound::FailureAccrual::Consecutive {\n                max_failures,\n                backoff,\n            } => outbound::failure_accrual::Kind::ConsecutiveFailures(\n                outbound::failure_accrual::ConsecutiveFailures {\n                    max_failures,\n                    backoff: Some(outbound::ExponentialBackoff {\n                        min_backoff: convert_duration(\"min_backoff\", backoff.min_penalty),\n                        max_backoff: convert_duration(\"max_backoff\", backoff.max_penalty),\n                        jitter_ratio: backoff.jitter,\n                    }),\n                },\n            ),\n        }),\n    });\n\n    let mut http_routes = policy.http_routes.clone().into_iter().collect::<Vec<_>>();\n\n    let kind = match &policy.app_protocol {\n        Some(AppProtocol::Http1) => {\n            http_routes.sort_by(timestamp_then_name);\n            http::http1_only_protocol(\n                backend,\n                http_routes.into_iter(),\n                accrual,\n                policy.http_retry.clone(),\n                policy.timeouts.clone(),\n                allow_l5d_request_headers,\n                &policy.parent_info,\n                original_dst,\n            )\n        }\n        Some(AppProtocol::Http2) => {\n            let mut grpc_routes = policy.grpc_routes.clone().into_iter().collect::<Vec<_>>();\n\n            if !grpc_routes.is_empty() {\n                grpc_routes.sort_by(timestamp_then_name);\n                grpc::protocol(\n                    backend,\n                    grpc_routes.into_iter(),\n                    accrual,\n                    policy.grpc_retry.clone(),\n                    policy.timeouts.clone(),\n                    allow_l5d_request_headers,\n                    &policy.parent_info,\n                    original_dst,\n                )\n            } else {\n                http_routes.sort_by(timestamp_then_name);\n                http::http2_only_protocol(\n                    backend,\n                    http_routes.into_iter(),\n                    accrual,\n                    policy.http_retry.clone(),\n                    policy.timeouts.clone(),\n                    allow_l5d_request_headers,\n                    &policy.parent_info,\n                    original_dst,\n                )\n            }\n        }\n        Some(AppProtocol::Opaque) | Some(AppProtocol::Unknown(_)) => {\n            if let Some(AppProtocol::Unknown(protocol)) = &policy.app_protocol {\n                tracing::debug!(resource = ?policy.parent_info, port = policy.port.get(), \"Unknown appProtocol \\\"{protocol}\\\"\");\n            }\n\n            let mut tcp_routes = policy.tcp_routes.clone().into_iter().collect::<Vec<_>>();\n\n            if !tcp_routes.is_empty() {\n                tcp_routes.sort_by(timestamp_then_name);\n                tcp::protocol(\n                    backend,\n                    tcp_routes.into_iter(),\n                    &policy.parent_info,\n                    original_dst,\n                )\n            } else {\n                outbound::proxy_protocol::Kind::Opaque(outbound::proxy_protocol::Opaque {\n                    routes: vec![default_outbound_opaq_route(backend, &policy.parent_info)],\n                })\n            }\n        }\n        None => {\n            let mut grpc_routes = policy.grpc_routes.clone().into_iter().collect::<Vec<_>>();\n            let mut tls_routes = policy.tls_routes.clone().into_iter().collect::<Vec<_>>();\n            let mut tcp_routes = policy.tcp_routes.clone().into_iter().collect::<Vec<_>>();\n\n            if !grpc_routes.is_empty() {\n                grpc_routes.sort_by(timestamp_then_name);\n                grpc::protocol(\n                    backend,\n                    grpc_routes.into_iter(),\n                    accrual,\n                    policy.grpc_retry.clone(),\n                    policy.timeouts.clone(),\n                    allow_l5d_request_headers,\n                    &policy.parent_info,\n                    original_dst,\n                )\n            } else if !http_routes.is_empty() {\n                http_routes.sort_by(timestamp_then_name);\n                http::protocol(\n                    backend,\n                    http_routes.into_iter(),\n                    accrual,\n                    policy.http_retry.clone(),\n                    policy.timeouts.clone(),\n                    allow_l5d_request_headers,\n                    &policy.parent_info,\n                    original_dst,\n                )\n            } else if !tls_routes.is_empty() {\n                tls_routes.sort_by(timestamp_then_name);\n                tls::protocol(\n                    backend,\n                    tls_routes.into_iter(),\n                    &policy.parent_info,\n                    original_dst,\n                )\n            } else if !tcp_routes.is_empty() {\n                tcp_routes.sort_by(timestamp_then_name);\n                tcp::protocol(\n                    backend,\n                    tcp_routes.into_iter(),\n                    &policy.parent_info,\n                    original_dst,\n                )\n            } else {\n                http_routes.sort_by(timestamp_then_name);\n                http::protocol(\n                    backend,\n                    http_routes.into_iter(),\n                    accrual,\n                    policy.http_retry.clone(),\n                    policy.timeouts.clone(),\n                    allow_l5d_request_headers,\n                    &policy.parent_info,\n                    original_dst,\n                )\n            }\n        }\n    };\n\n    let (parent_group, parent_kind, namespace, name) = match policy.parent_info {\n        ParentInfo::EgressNetwork {\n            namespace, name, ..\n        } => (\"policy.linkerd.io\", \"EgressNetwork\", namespace, name),\n        ParentInfo::Service {\n            name, namespace, ..\n        } => (\"core\", \"Service\", namespace, name),\n    };\n\n    let metadata = Metadata {\n        kind: Some(metadata::Kind::Resource(api::meta::Resource {\n            group: parent_group.into(),\n            kind: parent_kind.into(),\n            namespace,\n            name,\n            port: u16::from(policy.port).into(),\n            ..Default::default()\n        })),\n    };\n\n    outbound::OutboundPolicy {\n        metadata: Some(metadata),\n        protocol: Some(outbound::ProxyProtocol { kind: Some(kind) }),\n    }\n}\n\nfn timestamp_then_name<R: Route>(\n    (left_id, left_route): &(GroupKindNamespaceName, R),\n    (right_id, right_route): &(GroupKindNamespaceName, R),\n) -> std::cmp::Ordering {\n    let by_ts = match (\n        &left_route.creation_timestamp(),\n        &right_route.creation_timestamp(),\n    ) {\n        (Some(left_ts), Some(right_ts)) => left_ts.cmp(right_ts),\n        (None, None) => std::cmp::Ordering::Equal,\n        // Routes with timestamps are preferred over routes without.\n        (Some(_), None) => return std::cmp::Ordering::Less,\n        (None, Some(_)) => return std::cmp::Ordering::Greater,\n    };\n\n    by_ts.then_with(|| left_id.name.cmp(&right_id.name))\n}\n\nfn default_backend(policy: &OutboundPolicy, original_dst: Option<SocketAddr>) -> outbound::Backend {\n    match policy.parent_info.clone() {\n        ParentInfo::Service {\n            authority,\n            namespace,\n            name,\n            ..\n        } => outbound::Backend {\n            metadata: Some(Metadata {\n                kind: Some(metadata::Kind::Resource(api::meta::Resource {\n                    group: \"core\".to_string(),\n                    kind: \"Service\".to_string(),\n                    name,\n                    namespace,\n                    section: Default::default(),\n                    port: u16::from(policy.port).into(),\n                })),\n            }),\n            queue: Some(default_queue_config()),\n            kind: Some(outbound::backend::Kind::Balancer(\n                outbound::backend::BalanceP2c {\n                    discovery: Some(outbound::backend::EndpointDiscovery {\n                        kind: Some(outbound::backend::endpoint_discovery::Kind::Dst(\n                            outbound::backend::endpoint_discovery::DestinationGet {\n                                path: authority.clone(),\n                            },\n                        )),\n                    }),\n                    load: Some(default_balancer_config()),\n                },\n            )),\n        },\n\n        ParentInfo::EgressNetwork {\n            namespace, name, ..\n        } => {\n            debug_assert!(\n                original_dst.is_some(),\n                \"Must not serve EgressNetwork for named lookups; IP:PORT required\"\n            );\n            let metadata = Some(Metadata {\n                kind: Some(metadata::Kind::Resource(api::meta::Resource {\n                    group: \"policy.linkerd.io\".to_string(),\n                    kind: \"EgressNetwork\".to_string(),\n                    name,\n                    namespace,\n                    section: Default::default(),\n                    port: u16::from(policy.port).into(),\n                })),\n            });\n\n            let Some(addr) = original_dst else {\n                tracing::error!(\n                    ?metadata,\n                    \"Unexpected state: EgressNetworks should only be returned when lookup is by IP:PORT; synthesizing invalid backend\"\n                );\n                return outbound::Backend {\n                    metadata,\n                    queue: None,\n                    kind: None,\n                };\n            };\n\n            outbound::Backend {\n                metadata,\n                queue: Some(default_queue_config()),\n                kind: Some(outbound::backend::Kind::Forward(\n                    destination::WeightedAddr {\n                        addr: Some(addr.into()),\n                        weight: 1,\n                        ..Default::default()\n                    },\n                )),\n            }\n        }\n    }\n}\n\nfn default_outbound_opaq_route(\n    backend: outbound::Backend,\n    parent_info: &ParentInfo,\n) -> outbound::OpaqueRoute {\n    match parent_info {\n        ParentInfo::EgressNetwork { traffic_policy, .. } => {\n            tcp::default_outbound_egress_route(backend, traffic_policy)\n        }\n        ParentInfo::Service { .. } => {\n            let metadata = Some(Metadata {\n                kind: Some(metadata::Kind::Default(\"opaq\".to_string())),\n            });\n            let rules = vec![outbound::opaque_route::Rule {\n                backends: Some(outbound::opaque_route::Distribution {\n                    kind: Some(outbound::opaque_route::distribution::Kind::FirstAvailable(\n                        outbound::opaque_route::distribution::FirstAvailable {\n                            backends: vec![outbound::opaque_route::RouteBackend {\n                                backend: Some(backend),\n                                filters: Vec::new(),\n                            }],\n                        },\n                    )),\n                }),\n                filters: Vec::new(),\n            }];\n\n            outbound::OpaqueRoute { metadata, rules }\n        }\n    }\n}\n\nfn default_balancer_config() -> outbound::backend::balance_p2c::Load {\n    outbound::backend::balance_p2c::Load::PeakEwma(outbound::backend::balance_p2c::PeakEwma {\n        default_rtt: Some(\n            time::Duration::from_millis(30)\n                .try_into()\n                .expect(\"failed to convert ewma default_rtt to protobuf\"),\n        ),\n        decay: Some(\n            time::Duration::from_secs(10)\n                .try_into()\n                .expect(\"failed to convert ewma decay to protobuf\"),\n        ),\n    })\n}\n\nfn default_queue_config() -> outbound::Queue {\n    outbound::Queue {\n        capacity: 100,\n        failfast_timeout: Some(\n            time::Duration::from_secs(3)\n                .try_into()\n                .expect(\"failed to convert failfast_timeout to protobuf\"),\n        ),\n    }\n}\n\npub(crate) fn convert_duration(\n    name: &'static str,\n    duration: time::Duration,\n) -> Option<prost_types::Duration> {\n    duration\n        .try_into()\n        .map_err(|error| {\n            tracing::warn!(%error, \"Invalid {name} duration\");\n        })\n        .ok()\n}\n\npub(crate) fn service_meta(svc: WeightedService) -> Metadata {\n    Metadata {\n        kind: Some(metadata::Kind::Resource(Resource {\n            group: \"core\".to_string(),\n            kind: \"Service\".to_string(),\n            name: svc.name,\n            namespace: svc.namespace,\n            section: Default::default(),\n            port: u16::from(svc.port).into(),\n        })),\n    }\n}\n\npub(crate) fn egress_net_meta(\n    egress_net: WeightedEgressNetwork,\n    original_dst_port: Option<u16>,\n) -> Metadata {\n    let port = egress_net\n        .port\n        .map(NonZeroU16::get)\n        .or(original_dst_port)\n        .unwrap_or_default();\n\n    Metadata {\n        kind: Some(metadata::Kind::Resource(Resource {\n            group: \"policy.linkerd.io\".to_string(),\n            kind: \"EgressNetwork\".to_string(),\n            name: egress_net.name,\n            namespace: egress_net.namespace,\n            section: Default::default(),\n            port: port.into(),\n        })),\n    }\n}\n"
  },
  {
    "path": "policy-controller/grpc/src/routes/grpc.rs",
    "content": "use linkerd2_proxy_api::{grpc_route, http_route};\nuse linkerd_policy_controller_core::routes::{GrpcRouteMatch, HeaderMatch};\n\npub(crate) fn convert_match(\n    GrpcRouteMatch { headers, method }: GrpcRouteMatch,\n) -> grpc_route::GrpcRouteMatch {\n    let headers = headers\n        .into_iter()\n        .map(|rule| match rule {\n            HeaderMatch::Exact(name, value) => http_route::HeaderMatch {\n                name: name.to_string(),\n                value: Some(http_route::header_match::Value::Exact(\n                    value.as_bytes().to_vec(),\n                )),\n            },\n            HeaderMatch::Regex(name, re) => http_route::HeaderMatch {\n                name: name.to_string(),\n                value: Some(http_route::header_match::Value::Regex(re.to_string())),\n            },\n        })\n        .collect();\n\n    let rpc = method.map(|value| grpc_route::GrpcRpcMatch {\n        method: value.method.unwrap_or_default(),\n        service: value.service.unwrap_or_default(),\n    });\n\n    grpc_route::GrpcRouteMatch { rpc, headers }\n}\n"
  },
  {
    "path": "policy-controller/grpc/src/routes/http.rs",
    "content": "use linkerd2_proxy_api::http_route;\nuse linkerd_policy_controller_core::routes::{\n    FailureInjectorFilter, HeaderMatch, HttpRouteMatch, PathMatch, QueryParamMatch,\n};\n\npub(crate) fn convert_match(\n    HttpRouteMatch {\n        headers,\n        path,\n        query_params,\n        method,\n    }: HttpRouteMatch,\n) -> http_route::HttpRouteMatch {\n    let headers = headers\n        .into_iter()\n        .map(|hm| match hm {\n            HeaderMatch::Exact(name, value) => http_route::HeaderMatch {\n                name: name.to_string(),\n                value: Some(http_route::header_match::Value::Exact(\n                    value.as_bytes().to_vec(),\n                )),\n            },\n            HeaderMatch::Regex(name, re) => http_route::HeaderMatch {\n                name: name.to_string(),\n                value: Some(http_route::header_match::Value::Regex(re.to_string())),\n            },\n        })\n        .collect();\n\n    let path = path.map(|path| http_route::PathMatch {\n        kind: Some(match path {\n            PathMatch::Exact(path) => http_route::path_match::Kind::Exact(path),\n            PathMatch::Prefix(prefix) => http_route::path_match::Kind::Prefix(prefix),\n            PathMatch::Regex(regex) => http_route::path_match::Kind::Regex(regex.to_string()),\n        }),\n    });\n\n    let query_params = query_params\n        .into_iter()\n        .map(|qpm| match qpm {\n            QueryParamMatch::Exact(name, value) => http_route::QueryParamMatch {\n                name,\n                value: Some(http_route::query_param_match::Value::Exact(value)),\n            },\n            QueryParamMatch::Regex(name, re) => http_route::QueryParamMatch {\n                name,\n                value: Some(http_route::query_param_match::Value::Regex(re.to_string())),\n            },\n        })\n        .collect();\n\n    http_route::HttpRouteMatch {\n        headers,\n        path,\n        query_params,\n        method: method.map(Into::into),\n    }\n}\n\npub(crate) fn convert_failure_injector_filter(\n    FailureInjectorFilter {\n        status,\n        message,\n        ratio,\n    }: FailureInjectorFilter,\n) -> http_route::HttpFailureInjector {\n    http_route::HttpFailureInjector {\n        status: u32::from(status.as_u16()),\n        message,\n        ratio: Some(http_route::Ratio {\n            numerator: ratio.numerator,\n            denominator: ratio.denominator,\n        }),\n    }\n}\n"
  },
  {
    "path": "policy-controller/grpc/src/routes.rs",
    "content": "use linkerd2_proxy_api::{http_route as proto, http_types, tls_route as tls_proto};\nuse linkerd_policy_controller_core::routes::{\n    HeaderModifierFilter, HostMatch, PathModifier, RequestRedirectFilter,\n};\n\npub(crate) mod grpc;\npub(crate) mod http;\n\npub(crate) fn convert_host_match(h: HostMatch) -> proto::HostMatch {\n    proto::HostMatch {\n        r#match: Some(match h {\n            HostMatch::Exact(host) => proto::host_match::Match::Exact(host),\n            HostMatch::Suffix { reverse_labels } => {\n                proto::host_match::Match::Suffix(proto::host_match::Suffix {\n                    reverse_labels: reverse_labels.to_vec(),\n                })\n            }\n        }),\n    }\n}\n\npub(crate) fn convert_sni_match(h: HostMatch) -> tls_proto::SniMatch {\n    tls_proto::SniMatch {\n        r#match: Some(match h {\n            HostMatch::Exact(host) => tls_proto::sni_match::Match::Exact(host),\n            HostMatch::Suffix { reverse_labels } => {\n                tls_proto::sni_match::Match::Suffix(tls_proto::sni_match::Suffix {\n                    reverse_labels: reverse_labels.to_vec(),\n                })\n            }\n        }),\n    }\n}\n\npub(crate) fn convert_request_header_modifier_filter(\n    HeaderModifierFilter { add, set, remove }: HeaderModifierFilter,\n) -> proto::RequestHeaderModifier {\n    proto::RequestHeaderModifier {\n        add: Some(http_types::Headers {\n            headers: add\n                .into_iter()\n                .map(|(n, v)| http_types::headers::Header {\n                    name: n.to_string(),\n                    value: v.as_bytes().to_owned(),\n                })\n                .collect(),\n        }),\n        set: Some(http_types::Headers {\n            headers: set\n                .into_iter()\n                .map(|(n, v)| http_types::headers::Header {\n                    name: n.to_string(),\n                    value: v.as_bytes().to_owned(),\n                })\n                .collect(),\n        }),\n        remove: remove.into_iter().map(|n| n.to_string()).collect(),\n    }\n}\n\npub(crate) fn convert_response_header_modifier_filter(\n    HeaderModifierFilter { add, set, remove }: HeaderModifierFilter,\n) -> proto::ResponseHeaderModifier {\n    proto::ResponseHeaderModifier {\n        add: Some(http_types::Headers {\n            headers: add\n                .into_iter()\n                .map(|(n, v)| http_types::headers::Header {\n                    name: n.to_string(),\n                    value: v.as_bytes().to_owned(),\n                })\n                .collect(),\n        }),\n        set: Some(http_types::Headers {\n            headers: set\n                .into_iter()\n                .map(|(n, v)| http_types::headers::Header {\n                    name: n.to_string(),\n                    value: v.as_bytes().to_owned(),\n                })\n                .collect(),\n        }),\n        remove: remove.into_iter().map(|n| n.to_string()).collect(),\n    }\n}\n\npub(crate) fn convert_redirect_filter(\n    RequestRedirectFilter {\n        scheme,\n        host,\n        path,\n        port,\n        status,\n    }: RequestRedirectFilter,\n) -> proto::RequestRedirect {\n    proto::RequestRedirect {\n        scheme: scheme.map(|ref s| s.into()),\n        host: host.unwrap_or_default(),\n        path: path.map(|pm| proto::PathModifier {\n            replace: Some(match pm {\n                PathModifier::Full(p) => proto::path_modifier::Replace::Full(p),\n                PathModifier::Prefix(p) => proto::path_modifier::Replace::Prefix(p),\n            }),\n        }),\n        port: port.map(u16::from).map(u32::from).unwrap_or_default(),\n        status: u32::from(status.unwrap_or_default().as_u16()),\n    }\n}\n"
  },
  {
    "path": "policy-controller/grpc/src/workload.rs",
    "content": "use serde::{Deserialize, Serialize};\nuse std::str::FromStr;\n\n#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]\npub enum Kind {\n    #[serde(rename = \"external_workload\")]\n    External(String),\n    #[serde(rename = \"pod\")]\n    Pod(String),\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]\npub struct Workload {\n    #[serde(flatten)]\n    pub kind: Kind,\n    #[serde(rename = \"ns\")]\n    pub namespace: String,\n}\n\nimpl FromStr for Workload {\n    type Err = tonic::Status;\n\n    fn from_str(s: &str) -> Result<Self, tonic::Status> {\n        if s.starts_with('{') {\n            return serde_json::from_str(s).map_err(|error| {\n                tracing::warn!(%error, \"Invalid {s} workload string\");\n                tonic::Status::invalid_argument(format!(\"Invalid workload: {s}\"))\n            });\n        }\n\n        match s.split_once(':') {\n            None => Err(tonic::Status::invalid_argument(format!(\n                \"Invalid workload: {s}\"\n            ))),\n            Some((ns, pod)) if ns.is_empty() || pod.is_empty() => Err(\n                tonic::Status::invalid_argument(format!(\"Invalid workload: {s}\")),\n            ),\n            Some((ns, pod)) => Ok(Workload {\n                namespace: ns.to_string(),\n                kind: Kind::Pod(pod.to_string()),\n            }),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn parse_old_format() {\n        let input = \"my-namespace:my-pod\";\n        let expected: Workload = Workload {\n            namespace: \"my-namespace\".to_string(),\n            kind: Kind::Pod(\"my-pod\".to_string()),\n        };\n        assert_eq!(expected, Workload::from_str(input).expect(\"should parse\"));\n    }\n\n    #[test]\n    fn parse_new_format_pod() {\n        let input = r#\"{\"ns\":\"my-namespace\", \"pod\":\"my-pod\"}\"#;\n        let expected: Workload = Workload {\n            namespace: \"my-namespace\".to_string(),\n            kind: Kind::Pod(\"my-pod\".to_string()),\n        };\n        assert_eq!(expected, Workload::from_str(input).expect(\"should parse\"));\n    }\n\n    #[test]\n    fn parse_new_format_external() {\n        let input = r#\"{\"ns\":\"my-namespace\", \"external_workload\":\"my-external\"}\"#;\n        let expected: Workload = Workload {\n            namespace: \"my-namespace\".to_string(),\n            kind: Kind::External(\"my-external\".to_string()),\n        };\n        assert_eq!(expected, Workload::from_str(input).expect(\"should parse\"));\n    }\n\n    #[test]\n    fn errors_invalid_new_format() {\n        let input = r#\"{\"ns\":\"my-namespace\", \"nonsense\":\"my-external\"}\"#;\n        assert!(Workload::from_str(input).is_err());\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/Cargo.toml",
    "content": "[package]\nname = \"linkerd-policy-controller-k8s-api\"\nversion = \"0.1.0\"\nedition = \"2021\"\nlicense = \"Apache-2.0\"\npublish = false\n\n[dependencies]\ngateway-api = { workspace = true }\nipnet = { version = \"2.12\", features = [\"json\"] }\nk8s-openapi = { workspace = true }\nschemars = \"0.8\"\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\nserde_yaml = \"0.9\"\nthiserror = \"2\"\ntokio = { version = \"1\", features = [\"time\"] }\ntracing = \"0.1\"\n\n[dependencies.kube]\nworkspace = true\ndefault-features = false\nfeatures = [\n    \"client\",\n    \"derive\",\n    \"runtime\",\n]\n"
  },
  {
    "path": "policy-controller/k8s/api/src/duration.rs",
    "content": "use serde::{de, Deserialize, Deserializer, Serialize, Serializer};\nuse std::{fmt, str::FromStr, time::Duration};\n\n#[derive(Copy, Clone, PartialEq, Eq)]\npub struct K8sDuration {\n    duration: Duration,\n    is_negative: bool,\n}\n\n#[derive(Debug, thiserror::Error, Eq, PartialEq)]\n#[non_exhaustive]\npub enum ParseError {\n    #[error(\"invalid unit: {}\", EXPECTED_UNITS)]\n    InvalidUnit,\n\n    #[error(\"missing a unit: {}\", EXPECTED_UNITS)]\n    NoUnit,\n\n    #[error(\"invalid floating-point number: {}\", .0)]\n    NotANumber(#[from] std::num::ParseFloatError),\n}\n\nconst EXPECTED_UNITS: &str = \"expected one of 'ns', 'us', '\\u{00b5}s', 'ms', 's', 'm', or 'h'\";\n\nimpl From<Duration> for K8sDuration {\n    fn from(duration: Duration) -> Self {\n        Self {\n            duration,\n            is_negative: false,\n        }\n    }\n}\n\nimpl From<K8sDuration> for Duration {\n    fn from(K8sDuration { duration, .. }: K8sDuration) -> Self {\n        duration\n    }\n}\n\nimpl K8sDuration {\n    #[inline]\n    #[must_use]\n    pub fn is_negative(&self) -> bool {\n        self.is_negative\n    }\n}\n\nimpl fmt::Debug for K8sDuration {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        use std::fmt::Write;\n        if self.is_negative {\n            f.write_char('-')?;\n        }\n        fmt::Debug::fmt(&self.duration, f)\n    }\n}\n\nimpl fmt::Display for K8sDuration {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        use std::fmt::Write;\n        if self.is_negative {\n            f.write_char('-')?;\n        }\n        fmt::Debug::fmt(&self.duration, f)\n    }\n}\n\nimpl FromStr for K8sDuration {\n    type Err = ParseError;\n\n    fn from_str(mut s: &str) -> Result<Self, Self::Err> {\n        // implements the same format as\n        // https://cs.opensource.google/go/go/+/refs/tags/go1.20.4:src/time/format.go;l=1589\n\n        fn duration_from_units(val: f64, unit: &str) -> Result<Duration, ParseError> {\n            const MINUTE: Duration = Duration::from_secs(60);\n            // https://cs.opensource.google/go/go/+/refs/tags/go1.20.4:src/time/format.go;l=1573\n            let base = match unit {\n                \"ns\" => Duration::from_nanos(1),\n                // U+00B5 is the \"micro sign\" while U+03BC is \"Greek letter mu\"\n                \"us\" | \"\\u{00b5}s\" | \"\\u{03bc}s\" => Duration::from_micros(1),\n                \"ms\" => Duration::from_millis(1),\n                \"s\" => Duration::from_secs(1),\n                \"m\" => MINUTE,\n                \"h\" => MINUTE * 60,\n                _ => return Err(ParseError::InvalidUnit),\n            };\n            Ok(base.mul_f64(val))\n        }\n\n        // Go durations are signed. Rust durations aren't. So we need to ignore\n        // this for now.\n        let is_negative = s.starts_with('-');\n        s = s.trim_start_matches('+').trim_start_matches('-');\n\n        let mut total = Duration::from_secs(0);\n        while !s.is_empty() {\n            if let Some(unit_start) = s.find(|c: char| c.is_alphabetic()) {\n                let (val, rest) = s.split_at(unit_start);\n                let val = val.parse::<f64>()?;\n                let unit = if let Some(next_numeric_start) = rest.find(|c: char| !c.is_alphabetic())\n                {\n                    let (unit, rest) = rest.split_at(next_numeric_start);\n                    s = rest;\n                    unit\n                } else {\n                    s = \"\";\n                    rest\n                };\n                total += duration_from_units(val, unit)?;\n            } else if s == \"0\" {\n                return Ok(K8sDuration {\n                    duration: Duration::from_secs(0),\n                    is_negative,\n                });\n            } else {\n                return Err(ParseError::NoUnit);\n            }\n        }\n\n        Ok(K8sDuration {\n            duration: total,\n            is_negative,\n        })\n    }\n}\n\nimpl Serialize for K8sDuration {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        serializer.collect_str(self)\n    }\n}\n\nimpl<'de> Deserialize<'de> for K8sDuration {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        struct Visitor;\n        impl de::Visitor<'_> for Visitor {\n            type Value = K8sDuration;\n\n            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n                f.write_str(\"a string in Go `time.Duration.String()` format\")\n            }\n\n            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>\n            where\n                E: de::Error,\n            {\n                let val = value.parse::<K8sDuration>().map_err(de::Error::custom)?;\n                Ok(val)\n            }\n        }\n        deserializer.deserialize_str(Visitor)\n    }\n}\n\nimpl schemars::JsonSchema for K8sDuration {\n    // see\n    // https://github.com/kubernetes/apimachinery/blob/756e2227bf3a486098f504af1a0ffb736ad16f4c/pkg/apis/meta/v1/duration.go#L61\n    fn schema_name() -> String {\n        \"K8sDuration\".to_owned()\n    }\n\n    fn is_referenceable() -> bool {\n        false\n    }\n\n    fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {\n        schemars::schema::SchemaObject {\n            instance_type: Some(schemars::schema::InstanceType::String.into()),\n            // the format should *not* be \"duration\", because \"duration\" means\n            // the duration is formatted in ISO 8601, as described here:\n            // https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-02#section-7.3.1\n            format: None,\n            ..Default::default()\n        }\n        .into()\n    }\n}\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn parses_the_same_as_go() {\n        const MINUTE: Duration = Duration::from_secs(60);\n        const HOUR: Duration = Duration::from_secs(60 * 60);\n        // from Go:\n        // https://cs.opensource.google/go/go/+/refs/tags/go1.20.4:src/time/time_test.go;l=891-951\n        // ```\n        // var parseDurationTests = []struct {\n        // \tin   string\n        // \twant Duration\n        // }{\n        let cases: &[(&str, K8sDuration)] = &[\n            // \t// simple\n            // \t{\"0\", 0},\n            (\"0\", Duration::from_secs(0).into()),\n            // \t{\"5s\", 5 * Second},\n            (\"5s\", Duration::from_secs(5).into()),\n            // \t{\"30s\", 30 * Second},\n            (\"30s\", Duration::from_secs(30).into()),\n            // \t{\"1478s\", 1478 * Second},\n            (\"1478s\", Duration::from_secs(1478).into()),\n            // \t// sign\n            // \t{\"-5s\", -5 * Second},\n            (\n                \"-5s\",\n                K8sDuration {\n                    duration: Duration::from_secs(5),\n                    is_negative: true,\n                },\n            ),\n            // \t{\"+5s\", 5 * Second},\n            (\"+5s\", Duration::from_secs(5).into()),\n            // \t{\"-0\", 0},\n            (\n                \"-0\",\n                K8sDuration {\n                    duration: Duration::from_secs(0),\n                    is_negative: true,\n                },\n            ),\n            // \t{\"+0\", 0},\n            (\"+0\", Duration::from_secs(0).into()),\n            // \t// decimal\n            // \t{\"5.0s\", 5 * Second},\n            (\"5s\", Duration::from_secs(5).into()),\n            // \t{\"5.6s\", 5*Second + 600*Millisecond},\n            (\n                \"5.6s\",\n                (Duration::from_secs(5) + Duration::from_millis(600)).into(),\n            ),\n            // \t{\"5.s\", 5 * Second},\n            (\"5.s\", Duration::from_secs(5).into()),\n            // \t{\".5s\", 500 * Millisecond},\n            (\".5s\", Duration::from_millis(500).into()),\n            // \t{\"1.0s\", 1 * Second},\n            (\"1.0s\", Duration::from_secs(1).into()),\n            // \t{\"1.00s\", 1 * Second},\n            (\"1.00s\", Duration::from_secs(1).into()),\n            // \t{\"1.004s\", 1*Second + 4*Millisecond},\n            (\n                \"1.004s\",\n                (Duration::from_secs(1) + Duration::from_millis(4)).into(),\n            ),\n            // \t{\"1.0040s\", 1*Second + 4*Millisecond},\n            (\n                \"1.0040s\",\n                (Duration::from_secs(1) + Duration::from_millis(4)).into(),\n            ),\n            // \t{\"100.00100s\", 100*Second + 1*Millisecond},\n            (\n                \"100.00100s\",\n                (Duration::from_secs(100) + Duration::from_millis(1)).into(),\n            ),\n            // \t// different units\n            // \t{\"10ns\", 10 * Nanosecond},\n            (\"10ns\", Duration::from_nanos(10).into()),\n            // \t{\"11us\", 11 * Microsecond},\n            (\"11us\", Duration::from_micros(11).into()),\n            // \t{\"12µs\", 12 * Microsecond}, // U+00B5\n            (\"12µs\", Duration::from_micros(12).into()),\n            // \t{\"12μs\", 12 * Microsecond}, // U+03BC\n            (\"12μs\", Duration::from_micros(12).into()),\n            // \t{\"13ms\", 13 * Millisecond},\n            (\"13ms\", Duration::from_millis(13).into()),\n            // \t{\"14s\", 14 * Second},\n            (\"14s\", Duration::from_secs(14).into()),\n            // \t{\"15m\", 15 * Minute},\n            (\"15m\", (15 * MINUTE).into()),\n            // \t{\"16h\", 16 * Hour},\n            (\"16h\", (16 * HOUR).into()),\n            // \t// composite durations\n            // \t{\"3h30m\", 3*Hour + 30*Minute},\n            (\"3h30m\", (3 * HOUR + 30 * MINUTE).into()),\n            // \t{\"10.5s4m\", 4*Minute + 10*Second + 500*Millisecond},\n            (\n                \"10.5s4m\",\n                (4 * MINUTE + Duration::from_secs(10) + Duration::from_millis(500)).into(),\n            ),\n            // \t{\"-2m3.4s\", -(2*Minute + 3*Second + 400*Millisecond)},\n            (\n                \"-2m3.4s\",\n                K8sDuration {\n                    duration: 2 * MINUTE + Duration::from_secs(3) + Duration::from_millis(400),\n                    is_negative: true,\n                },\n            ),\n            // \t{\"1h2m3s4ms5us6ns\", 1*Hour + 2*Minute + 3*Second + 4*Millisecond + 5*Microsecond + 6*Nanosecond},\n            (\n                \"1h2m3s4ms5us6ns\",\n                (1 * HOUR\n                    + 2 * MINUTE\n                    + Duration::from_secs(3)\n                    + Duration::from_millis(4)\n                    + Duration::from_micros(5)\n                    + Duration::from_nanos(6))\n                .into(),\n            ),\n            // \t{\"39h9m14.425s\", 39*Hour + 9*Minute + 14*Second + 425*Millisecond},\n            (\n                \"39h9m14.425s\",\n                (39 * HOUR + 9 * MINUTE + Duration::from_secs(14) + Duration::from_millis(425))\n                    .into(),\n            ),\n            // \t// large value\n            // \t{\"52763797000ns\", 52763797000 * Nanosecond},\n            (\"52763797000ns\", Duration::from_nanos(52763797000).into()),\n            // \t// more than 9 digits after decimal point, see https://golang.org/issue/6617\n            // \t{\"0.3333333333333333333h\", 20 * Minute},\n            (\"0.3333333333333333333h\", (20 * MINUTE).into()),\n            // \t// 9007199254740993 = 1<<53+1 cannot be stored precisely in a float64\n            // \t{\"9007199254740993ns\", (1<<53 + 1) * Nanosecond},\n            (\n                \"9007199254740993ns\",\n                Duration::from_nanos((1 << 53) + 1).into(),\n            ),\n            // Rust Durations can handle larger durations than Go's\n            // representation, so skip these tests for their precision limits\n\n            // \t// largest duration that can be represented by int64 in nanoseconds\n            // \t{\"9223372036854775807ns\", (1<<63 - 1) * Nanosecond},\n            // (\"9223372036854775807ns\", Duration::from_nanos((1 << 63) - 1).into()),\n            // \t{\"9223372036854775.807us\", (1<<63 - 1) * Nanosecond},\n            // (\"9223372036854775.807us\", Duration::from_nanos((1 << 63) - 1).into()),\n            // \t{\"9223372036s854ms775us807ns\", (1<<63 - 1) * Nanosecond},\n            // \t{\"-9223372036854775808ns\", -1 << 63 * Nanosecond},\n            // \t{\"-9223372036854775.808us\", -1 << 63 * Nanosecond},\n            // \t{\"-9223372036s854ms775us808ns\", -1 << 63 * Nanosecond},\n            // \t// largest negative value\n            // \t{\"-9223372036854775808ns\", -1 << 63 * Nanosecond},\n            // \t// largest negative round trip value, see https://golang.org/issue/48629\n            // \t{\"-2562047h47m16.854775808s\", -1 << 63 * Nanosecond},\n\n            // \t// huge string; issue 15011.\n            // \t{\"0.100000000000000000000h\", 6 * Minute},\n            (\"0.100000000000000000000h\", (6 * MINUTE).into()), // \t// This value tests the first overflow check in leadingFraction.\n                                                               // \t{\"0.830103483285477580700h\", 49*Minute + 48*Second + 372539827*Nanosecond},\n                                                               // }\n                                                               // ```\n        ];\n\n        for (input, expected) in cases {\n            let parsed = dbg!(input).parse::<K8sDuration>().unwrap();\n            assert_eq!(&dbg!(parsed), expected);\n        }\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/external_workload.rs",
    "content": "use kube::CustomResource;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\n/// ExternalWorkload describes a single workload (i.e. a deployable unit,\n/// conceptually similar to a Kubernetes Pod) that is running outside of a\n/// Kubernetes cluster. An ExternalWorkload should be enrolled in the mesh and\n/// typically represents a virtual machine.\n#[derive(Clone, Debug, PartialEq, Eq, CustomResource, Deserialize, Serialize, JsonSchema)]\n#[kube(\n    group = \"workload.linkerd.io\",\n    version = \"v1beta1\",\n    kind = \"ExternalWorkload\",\n    status = \"ExternalWorkloadStatus\",\n    namespaced\n)]\npub struct ExternalWorkloadSpec {\n    /// MeshTls describes TLS settings associated with an external workload\n    #[serde(rename = \"meshTLS\")]\n    pub mesh_tls: MeshTls,\n    /// Ports describes a set of ports exposed by the workload\n    pub ports: Option<Vec<PortSpec>>,\n    /// List of IP addresses that can be used to send traffic to an external\n    /// workload\n    #[serde(rename = \"workloadIPs\")]\n    pub workload_ips: Option<Vec<WorkloadIP>>,\n}\n\n/// MeshTls describes TLS settings associated with an external workload\n#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]\npub struct MeshTls {\n    /// Identity associated with the workload. Used by peers to perform\n    /// verification in the mTLS handshake\n    pub identity: String,\n    /// ServerName is the DNS formatted name associated with the workload. Used\n    /// to terminate TLS using the SNI extension.\n    #[serde(rename = \"serverName\")]\n    pub server_name: String,\n}\n\n/// PortSpec represents a network port in a single workload.\n#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]\npub struct PortSpec {\n    /// If specified, must be an IANA_SVC_NAME and unique within the exposed\n    /// ports set. Each named port must have a unique name. The name may be\n    /// referred to by services\n    pub name: Option<String>,\n    /// Number of port exposed on the workload's IP address.\n    /// Must be a valid port number, i.e. 0 < x < 65536.\n    pub port: std::num::NonZeroU16,\n    /// Protocol defines network protocols supported. One of UDP, TCP, or SCTP.\n    /// Should coincide with Service selecting the workload.\n    /// Defaults to \"TCP\" if unspecified.\n    pub protocol: Option<String>,\n}\n\n/// WorkloadIPs contains a list of IP addresses exposed by an ExternalWorkload\n#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]\npub struct WorkloadIP {\n    pub ip: String,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]\npub struct ExternalWorkloadStatus {\n    pub conditions: Vec<Condition>,\n}\n\n/// WorkloadCondition represents the service state of an ExternalWorkload\n#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]\n#[serde(rename_all = \"camelCase\")]\npub struct Condition {\n    /// Type of the condition\n    // see: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions\n    #[serde(rename = \"type\")]\n    typ: String,\n    /// Status of the condition.\n    /// Can be True, False, Unknown\n    status: ConditionStatus,\n    /// Last time a condition transitioned from one status to another.\n    last_transition_time: Option<crate::apimachinery::pkg::apis::meta::v1::Time>,\n    /// Last time an ExternalWorkload was probed for a condition.\n    last_probe_time: Option<crate::apimachinery::pkg::apis::meta::v1::Time>,\n    /// Unique one word reason in CamelCase that describes the reason for a\n    /// transition.\n    reason: Option<String>,\n    /// Human readable message that describes details about last transition.\n    message: Option<String>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]\npub enum ConditionStatus {\n    True,\n    False,\n    Unknown,\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/labels.rs",
    "content": "use schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse std::{\n    collections::{BTreeMap, BTreeSet},\n    sync::Arc,\n};\n\n#[derive(Clone, Debug, Default)]\npub struct Labels(Arc<Map>);\n\npub type Map = BTreeMap<String, String>;\n\npub type Expressions = Vec<Expression>;\n\n#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]\npub struct Expression {\n    key: String,\n    operator: Operator,\n    values: Option<BTreeSet<String>>,\n}\n\n#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]\npub enum Operator {\n    In,\n    NotIn,\n    Exists,\n    DoesNotExist,\n}\n\n/// Selects a set of pods that expose a server. The result of `match_labels` and\n/// `match_expressions` are ANDed.\n#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Serialize, JsonSchema)]\n#[serde(rename_all = \"camelCase\")]\npub struct Selector {\n    match_labels: Option<Map>,\n    match_expressions: Option<Expressions>,\n}\n\n// === Selector ===\n\nimpl Selector {\n    #[cfg(test)]\n    fn new(labels: Map, exprs: Expressions) -> Self {\n        Self {\n            match_labels: Some(labels),\n            match_expressions: Some(exprs),\n        }\n    }\n\n    fn from_expressions(exprs: Expressions) -> Self {\n        Self {\n            match_labels: None,\n            match_expressions: Some(exprs),\n        }\n    }\n\n    fn from_map(map: Map) -> Self {\n        Self {\n            match_labels: Some(map),\n            match_expressions: None,\n        }\n    }\n\n    /// Indicates whether this label selector matches all pods\n    pub fn selects_all(&self) -> bool {\n        match (self.match_labels.as_ref(), self.match_expressions.as_ref()) {\n            (None, None) => true,\n            (Some(l), None) => l.is_empty(),\n            (None, Some(e)) => e.is_empty(),\n            (Some(l), Some(e)) => l.is_empty() && e.is_empty(),\n        }\n    }\n\n    pub fn matches(&self, labels: &Labels) -> bool {\n        for expr in self.match_expressions.iter().flatten() {\n            if !expr.matches(labels.as_ref()) {\n                return false;\n            }\n        }\n\n        if let Some(match_labels) = self.match_labels.as_ref() {\n            for (k, v) in match_labels {\n                if labels.0.get(k) != Some(v) {\n                    return false;\n                }\n            }\n        }\n\n        true\n    }\n}\n\nimpl std::iter::FromIterator<(String, String)> for Selector {\n    fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {\n        Self::from_map(iter.into_iter().collect())\n    }\n}\n\nimpl std::iter::FromIterator<(&'static str, &'static str)> for Selector {\n    fn from_iter<T: IntoIterator<Item = (&'static str, &'static str)>>(iter: T) -> Self {\n        Self::from_map(\n            iter.into_iter()\n                .map(|(k, v)| (k.to_string(), v.to_string()))\n                .collect(),\n        )\n    }\n}\n\nimpl std::iter::FromIterator<Expression> for Selector {\n    fn from_iter<T: IntoIterator<Item = Expression>>(iter: T) -> Self {\n        Self::from_expressions(iter.into_iter().collect())\n    }\n}\n\n// === Labels ===\n\nimpl From<Option<Map>> for Labels {\n    #[inline]\n    fn from(labels: Option<Map>) -> Self {\n        labels.unwrap_or_default().into()\n    }\n}\n\nimpl From<Map> for Labels {\n    #[inline]\n    fn from(labels: Map) -> Self {\n        Self(Arc::new(labels))\n    }\n}\n\nimpl AsRef<Map> for Labels {\n    #[inline]\n    fn as_ref(&self) -> &Map {\n        self.0.as_ref()\n    }\n}\n\nimpl std::cmp::PartialEq<Self> for Labels {\n    #[inline]\n    fn eq(&self, t: &Self) -> bool {\n        self.0.as_ref().eq(t.as_ref())\n    }\n}\n\nimpl std::cmp::PartialEq<Option<Map>> for Labels {\n    #[inline]\n    fn eq(&self, t: &Option<Map>) -> bool {\n        match t {\n            None => self.0.is_empty(),\n            Some(t) => t.eq(self.0.as_ref()),\n        }\n    }\n}\n\nimpl std::iter::FromIterator<(String, String)> for Labels {\n    fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {\n        Self(Arc::new(iter.into_iter().collect()))\n    }\n}\n\nimpl std::iter::FromIterator<(&'static str, &'static str)> for Labels {\n    fn from_iter<T: IntoIterator<Item = (&'static str, &'static str)>>(iter: T) -> Self {\n        iter.into_iter()\n            .map(|(k, v)| (k.to_string(), v.to_string()))\n            .collect()\n    }\n}\n\n// === Expression ===\n\nimpl Expression {\n    fn matches(&self, labels: &Map) -> bool {\n        match (self.operator, &self.key, self.values.as_ref()) {\n            (Operator::In, key, Some(values)) => match labels.get(key) {\n                Some(v) => values.contains(v),\n                None => false,\n            },\n            (Operator::NotIn, key, Some(values)) => match labels.get(key) {\n                Some(v) => !values.contains(v),\n                None => true,\n            },\n            (Operator::Exists, key, None) => labels.contains_key(key),\n            (Operator::DoesNotExist, key, None) => !labels.contains_key(key),\n            (operator, key, values) => {\n                tracing::warn!(?operator, %key, ?values, \"Illegal match expression\");\n                false\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use std::iter::FromIterator;\n\n    #[test]\n    fn test_matches() {\n        for (selector, labels, matches, msg) in &[\n            (Selector::default(), Labels::default(), true, \"empty match\"),\n            (\n                Selector::from_iter(Some((\"foo\", \"bar\"))),\n                Labels::from_iter(Some((\"foo\", \"bar\"))),\n                true,\n                \"exact label match\",\n            ),\n            (\n                Selector::from_iter(Some((\"foo\", \"bar\"))),\n                Labels::from_iter(vec![(\"foo\", \"bar\"), (\"bah\", \"baz\")]),\n                true,\n                \"sufficient label match\",\n            ),\n            (\n                Selector::from_iter(Some(Expression {\n                    key: \"foo\".into(),\n                    operator: Operator::In,\n                    values: Some(Some(\"bar\".to_string()).into_iter().collect()),\n                })),\n                Labels::from_iter(vec![(\"foo\", \"bar\"), (\"bah\", \"baz\")]),\n                true,\n                \"In expression match\",\n            ),\n            (\n                Selector::from_iter(Some(Expression {\n                    key: \"foo\".into(),\n                    operator: Operator::NotIn,\n                    values: Some(Some(\"quux\".to_string()).into_iter().collect()),\n                })),\n                Labels::from_iter(vec![(\"foo\", \"bar\"), (\"bah\", \"baz\")]),\n                true,\n                \"NotIn expression match\",\n            ),\n            (\n                Selector::from_iter(Some(Expression {\n                    key: \"foo\".into(),\n                    operator: Operator::NotIn,\n                    values: Some(Some(\"bar\".to_string()).into_iter().collect()),\n                })),\n                Labels::from_iter(vec![(\"foo\", \"bar\"), (\"bah\", \"baz\")]),\n                false,\n                \"NotIn expression non-match\",\n            ),\n            (\n                Selector::new(\n                    Map::from([(\"foo\".to_string(), \"bar\".to_string())]),\n                    vec![Expression {\n                        key: \"bah\".into(),\n                        operator: Operator::In,\n                        values: Some(Some(\"bar\".to_string()).into_iter().collect()),\n                    }],\n                ),\n                Labels::from_iter(vec![(\"foo\", \"bar\"), (\"bah\", \"baz\")]),\n                false,\n                \"matches labels but not expressions\",\n            ),\n            (\n                Selector::new(\n                    Map::from([(\"foo\".to_string(), \"bar\".to_string())]),\n                    vec![Expression {\n                        key: \"bah\".into(),\n                        operator: Operator::In,\n                        values: Some(Some(\"bar\".to_string()).into_iter().collect()),\n                    }],\n                ),\n                Labels::from_iter(vec![(\"foo\", \"bar\"), (\"bah\", \"bar\")]),\n                true,\n                \"matches both labels and expressions\",\n            ),\n        ] {\n            assert_eq!(selector.matches(labels), *matches, \"{msg}\");\n        }\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/lib.rs",
    "content": "#![deny(warnings, rust_2018_idioms)]\n#![forbid(unsafe_code)]\n\npub mod duration;\npub mod external_workload;\npub mod labels;\npub mod policy;\n\npub use self::labels::Labels;\npub use k8s_openapi::{\n    api::{\n        self,\n        coordination::v1::Lease,\n        core::v1::{\n            Container, ContainerPort, Endpoints, HTTPGetAction, Namespace, Node, NodeSpec, Pod,\n            PodSpec, PodStatus, Probe, Service, ServiceAccount, ServicePort, ServiceSpec,\n        },\n    },\n    apimachinery::{\n        self,\n        pkg::{\n            apis::meta::v1::{Condition, Time},\n            util::intstr::IntOrString,\n        },\n    },\n    NamespaceResourceScope,\n};\npub use kube::{\n    api::{Api, ListParams, ObjectMeta, Patch, PatchParams, Resource, ResourceExt},\n    error::ErrorResponse,\n    runtime::watcher::Event as WatchEvent,\n    Client, Error,\n};\n\npub mod gateway {\n    pub use gateway_api::apis::experimental::grpcroutes::*;\n    pub use gateway_api::apis::experimental::httproutes::*;\n    pub use gateway_api::apis::experimental::tcproutes::*;\n    pub use gateway_api::apis::experimental::tlsroutes::*;\n\n    pub mod http_method {\n        use gateway_api::apis::experimental::httproutes::HTTPRouteRulesMatchesMethod;\n\n        pub const GET: HTTPRouteRulesMatchesMethod = HTTPRouteRulesMatchesMethod::Get;\n        pub const POST: HTTPRouteRulesMatchesMethod = HTTPRouteRulesMatchesMethod::Post;\n        pub const PUT: HTTPRouteRulesMatchesMethod = HTTPRouteRulesMatchesMethod::Put;\n        pub const DELETE: HTTPRouteRulesMatchesMethod = HTTPRouteRulesMatchesMethod::Delete;\n        pub const PATCH: HTTPRouteRulesMatchesMethod = HTTPRouteRulesMatchesMethod::Patch;\n        pub const HEAD: HTTPRouteRulesMatchesMethod = HTTPRouteRulesMatchesMethod::Head;\n        pub const OPTIONS: HTTPRouteRulesMatchesMethod = HTTPRouteRulesMatchesMethod::Options;\n        pub const CONNECT: HTTPRouteRulesMatchesMethod = HTTPRouteRulesMatchesMethod::Connect;\n        pub const TRACE: HTTPRouteRulesMatchesMethod = HTTPRouteRulesMatchesMethod::Trace;\n    }\n\n    pub mod http_scheme {\n        use gateway_api::apis::experimental::httproutes::HTTPRouteRulesFiltersRequestRedirectScheme;\n\n        pub const HTTP: HTTPRouteRulesFiltersRequestRedirectScheme =\n            HTTPRouteRulesFiltersRequestRedirectScheme::Http;\n        pub const HTTPS: HTTPRouteRulesFiltersRequestRedirectScheme =\n            HTTPRouteRulesFiltersRequestRedirectScheme::Https;\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/policy/authorization_policy.rs",
    "content": "use super::{LocalTargetRef, NamespacedTargetRef};\n\n#[derive(\n    Clone, Debug, kube::CustomResource, serde::Deserialize, serde::Serialize, schemars::JsonSchema,\n)]\n#[kube(\n    group = \"policy.linkerd.io\",\n    version = \"v1alpha1\",\n    kind = \"AuthorizationPolicy\",\n    namespaced\n)]\n#[serde(rename_all = \"camelCase\")]\npub struct AuthorizationPolicySpec {\n    pub target_ref: LocalTargetRef,\n    pub required_authentication_refs: Vec<NamespacedTargetRef>,\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/policy/egress_network.rs",
    "content": "use super::network::Network;\nuse k8s_openapi::apimachinery::pkg::apis::meta::v1::Condition;\nuse kube::CustomResource;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Clone, Debug, PartialEq, Eq, CustomResource, Deserialize, Serialize, JsonSchema)]\n#[kube(\n    group = \"policy.linkerd.io\",\n    version = \"v1alpha1\",\n    kind = \"EgressNetwork\",\n    status = \"EgressNetworkStatus\",\n    namespaced\n)]\n#[serde(rename_all = \"camelCase\")]\npub struct EgressNetworkSpec {\n    pub networks: Option<Vec<Network>>,\n    pub traffic_policy: TrafficPolicy,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]\npub enum TrafficPolicy {\n    Allow,\n    Deny,\n}\n\n#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema)]\npub struct EgressNetworkStatus {\n    pub conditions: Vec<Condition>,\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/policy/grpcroute.rs",
    "content": "use crate::gateway;\n\npub fn parent_ref_targets_kind<T>(parent_ref: &gateway::GRPCRouteParentRefs) -> bool\nwhere\n    T: kube::Resource,\n    T::DynamicType: Default,\n{\n    let kind = match parent_ref.kind {\n        Some(ref kind) => kind,\n        None => return false,\n    };\n\n    super::targets_kind::<T>(parent_ref.group.as_deref(), kind)\n}\n\npub fn backend_ref_targets_kind<T>(backend_ref: &gateway::GRPCRouteRulesBackendRefs) -> bool\nwhere\n    T: kube::Resource,\n    T::DynamicType: Default,\n{\n    // Default kind is assumed to be service for backend ref objects\n    super::targets_kind::<T>(\n        backend_ref.group.as_deref(),\n        backend_ref.kind.as_deref().unwrap_or(\"Service\"),\n    )\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/policy/httproute.rs",
    "content": "use crate::gateway::{self, HTTPRouteStatus};\n\n/// HTTPRoute provides a way to route HTTP requests. This includes the\n/// capability to match requests by hostname, path, header, or query param.\n/// Filters can be used to specify additional processing steps. Backends specify\n/// where matching requests should be routed.\n#[derive(\n    Clone,\n    Debug,\n    Default,\n    kube::CustomResource,\n    serde::Deserialize,\n    serde::Serialize,\n    schemars::JsonSchema,\n)]\n#[kube(\n    group = \"policy.linkerd.io\",\n    version = \"v1beta3\",\n    kind = \"HTTPRoute\",\n    root = \"HttpRoute\",\n    status = \"HTTPRouteStatus\",\n    namespaced\n)]\npub struct HttpRouteSpec {\n    /// Common route information.\n    #[serde(\n        default,\n        skip_serializing_if = \"Option::is_none\",\n        rename = \"parentRefs\"\n    )]\n    pub parent_refs: Option<Vec<gateway::HTTPRouteParentRefs>>,\n\n    /// Hostnames defines a set of hostname that should match against the HTTP\n    /// Host header to select a HTTPRoute to process the request. This matches\n    /// the RFC 1123 definition of a hostname with 2 notable exceptions:\n    ///\n    /// 1. IPs are not allowed.\n    /// 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n    ///    label must appear by itself as the first label.\n    pub hostnames: Option<Vec<String>>,\n\n    /// Rules are a list of HTTP matchers, filters and actions.\n    pub rules: Option<Vec<HttpRouteRule>>,\n}\n\n/// HTTPRouteRule defines semantics for matching an HTTP request based on\n/// conditions (matches), processing it (filters), and forwarding the request to\n/// an API object (backendRefs).\n#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]\n#[serde(rename_all = \"camelCase\")]\npub struct HttpRouteRule {\n    /// Matches define conditions used for matching the rule against incoming\n    /// HTTP requests. Each match is independent, i.e. this rule will be matched\n    /// if **any** one of the matches is satisfied.\n    ///\n    /// For example, take the following matches configuration:\n    ///\n    /// ```yaml\n    /// matches:\n    /// - path:\n    ///     value: \"/foo\"\n    ///   headers:\n    ///   - name: \"version\"\n    ///     value: \"v2\"\n    /// - path:\n    ///     value: \"/v2/foo\"\n    /// ```\n    ///\n    /// For a request to match against this rule, a request must satisfy\n    /// EITHER of the two conditions:\n    ///\n    /// - path prefixed with `/foo` AND contains the header `version: v2`\n    /// - path prefix of `/v2/foo`\n    ///\n    /// See the documentation for HTTPRouteMatch on how to specify multiple\n    /// match conditions that should be ANDed together.\n    ///\n    /// If no matches are specified, the default is a prefix\n    /// path match on \"/\", which has the effect of matching every\n    /// HTTP request.\n    ///\n    /// Proxy or Load Balancer routing configuration generated from HTTPRoutes\n    /// MUST prioritize rules based on the following criteria, continuing on\n    /// ties. Precedence must be given to the Rule with the largest number of:\n    ///\n    /// * Characters in a matching non-wildcard hostname.\n    /// * Characters in a matching hostname.\n    /// * Characters in a matching path.\n    /// * Header matches.\n    /// * Query param matches.\n    ///\n    /// If ties still exist across multiple Routes, matching precedence MUST be\n    /// determined in order of the following criteria, continuing on ties:\n    ///\n    /// * The oldest Route based on creation timestamp.\n    /// * The Route appearing first in alphabetical order by\n    ///   \"{namespace}/{name}\".\n    ///\n    /// If ties still exist within the Route that has been given precedence,\n    /// matching precedence MUST be granted to the first matching rule meeting\n    /// the above criteria.\n    ///\n    /// When no rules matching a request have been successfully attached to the\n    /// parent a request is coming from, a HTTP 404 status code MUST be returned.\n    pub matches: Option<Vec<gateway::HTTPRouteRulesMatches>>,\n\n    /// Filters define the filters that are applied to requests that match this\n    /// rule.\n    ///\n    /// The effects of ordering of multiple behaviors are currently unspecified.\n    /// This can change in the future based on feedback during the alpha stage.\n    ///\n    /// Conformance-levels at this level are defined based on the type of\n    /// filter:\n    ///\n    /// - ALL core filters MUST be supported by all implementations.\n    /// - Implementers are encouraged to support extended filters.\n    /// - Implementation-specific custom filters have no API guarantees across\n    ///   implementations.\n    ///\n    /// Specifying a core filter multiple times has unspecified or custom\n    /// conformance.\n    ///\n    /// Support: Core\n    pub filters: Option<Vec<HttpRouteFilter>>,\n\n    /// BackendRefs defines the backend(s) where matching requests should be\n    /// sent.\n    ///\n    /// A 500 status code MUST be returned if there are no BackendRefs or\n    /// filters specified that would result in a response being sent.\n    ///\n    /// A BackendRef is considered invalid when it refers to:\n    ///\n    /// * an unknown or unsupported kind of resource\n    /// * a resource that does not exist\n    /// * a resource in another namespace when the reference has not been\n    ///   explicitly allowed by a ReferencePolicy (or equivalent concept).\n    ///\n    /// When a BackendRef is invalid, 500 status codes MUST be returned for\n    /// requests that would have otherwise been routed to an invalid backend. If\n    /// multiple backends are specified, and some are invalid, the proportion of\n    /// requests that would otherwise have been routed to an invalid backend\n    /// MUST receive a 500 status code.\n    ///\n    /// When a BackendRef refers to a Service that has no ready endpoints, it is\n    /// recommended to return a 503 status code.\n    ///\n    /// Support: Core for Kubernetes Service\n    /// Support: Custom for any other resource\n    ///\n    /// Support for weight: Core\n    pub backend_refs: Option<Vec<gateway::HTTPRouteRulesBackendRefs>>,\n\n    /// Timeouts defines the timeouts that can be configured for an HTTP request.\n    ///\n    /// Support: Core\n    pub timeouts: Option<HttpRouteTimeouts>,\n}\n\n/// HTTPRouteFilter defines processing steps that must be completed during the\n/// request or response lifecycle. HTTPRouteFilters are meant as an extension\n/// point to express processing that may be done in Gateway implementations.\n/// Some examples include request or response modification, implementing\n/// authentication strategies, rate-limiting, and traffic shaping. API\n/// guarantee/conformance is defined based on the type of the filter.\n#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]\n#[serde(tag = \"type\", rename_all = \"PascalCase\")]\npub enum HttpRouteFilter {\n    /// RequestHeaderModifier defines a schema for a filter that modifies request\n    /// headers.\n    ///\n    /// Support: Core\n    #[serde(rename_all = \"camelCase\")]\n    RequestHeaderModifier {\n        request_header_modifier: gateway::HTTPRouteRulesFiltersRequestHeaderModifier,\n    },\n\n    /// ResponseHeaderModifier defines a schema for a filter that modifies response\n    /// headers.\n    ///\n    /// Support: Extended\n    #[serde(rename_all = \"camelCase\")]\n    ResponseHeaderModifier {\n        response_header_modifier: gateway::HTTPRouteRulesFiltersResponseHeaderModifier,\n    },\n\n    /// RequestRedirect defines a schema for a filter that responds to the\n    /// request with an HTTP redirection.\n    ///\n    /// Support: Core\n    #[serde(rename_all = \"camelCase\")]\n    RequestRedirect {\n        request_redirect: gateway::HTTPRouteRulesFiltersRequestRedirect,\n    },\n}\n\n/// HTTPRouteTimeouts defines timeouts that can be configured for an HTTPRoute.\n/// Timeout values are formatted like 1h/1m/1s/1ms as parsed by Golang time.ParseDuration\n/// and MUST BE >= 1ms.\n#[derive(\n    Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize, schemars::JsonSchema,\n)]\n#[serde(rename_all = \"camelCase\")]\npub struct HttpRouteTimeouts {\n    /// Request specifies a timeout for the Gateway to send a response to a client HTTP request.\n    /// Whether the gateway starts the timeout before or after the entire client request stream\n    /// has been received, is implementation dependent.\n    ///\n    /// For example, setting the `rules.timeouts.request` field to the value `10s` in an\n    /// `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds\n    /// to complete.\n    ///\n    /// Request timeouts are disabled by default.\n    ///\n    /// Support: Core\n    pub request: Option<crate::duration::K8sDuration>,\n    /// BackendRequest specifies a timeout for an individual request from the gateway\n    /// to a backend service. Typically used in conjunction with retry configuration,\n    /// if supported by an implementation.\n    ///\n    /// The value of BackendRequest defaults to and must be <= the value of Request timeout.\n    ///\n    /// Support: Extended\n    pub backend_request: Option<crate::duration::K8sDuration>,\n}\n\npub fn parent_ref_targets_kind<T>(parent_ref: &gateway::HTTPRouteParentRefs) -> bool\nwhere\n    T: kube::Resource,\n    T::DynamicType: Default,\n{\n    let kind = match parent_ref.kind {\n        Some(ref kind) => kind,\n        None => return false,\n    };\n\n    super::targets_kind::<T>(parent_ref.group.as_deref(), kind)\n}\n\npub fn backend_ref_targets_kind<T>(backend_ref: &gateway::HTTPRouteRulesBackendRefs) -> bool\nwhere\n    T: kube::Resource,\n    T::DynamicType: Default,\n{\n    // Default kind is assumed to be service for backend ref objects\n    super::targets_kind::<T>(\n        backend_ref.group.as_deref(),\n        backend_ref.kind.as_deref().unwrap_or(\"Service\"),\n    )\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/policy/meshtls_authentication.rs",
    "content": "use super::NamespacedTargetRef;\n\n#[derive(\n    Clone,\n    Debug,\n    Default,\n    Eq,\n    PartialEq,\n    kube::CustomResource,\n    serde::Deserialize,\n    serde::Serialize,\n    schemars::JsonSchema,\n)]\n#[kube(\n    group = \"policy.linkerd.io\",\n    version = \"v1alpha1\",\n    kind = \"MeshTLSAuthentication\",\n    namespaced\n)]\n#[serde(rename_all = \"camelCase\")]\npub struct MeshTLSAuthenticationSpec {\n    pub identities: Option<Vec<String>>,\n    pub identity_refs: Option<Vec<NamespacedTargetRef>>,\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/policy/network.rs",
    "content": "use std::net::IpAddr;\n\nuse ipnet::IpNet;\n\n#[derive(\n    Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema,\n)]\n#[serde(rename_all = \"camelCase\")]\npub struct Network {\n    pub cidr: Cidr,\n    pub except: Option<Vec<Cidr>>,\n}\n\n// === impl Network ===\n\nimpl Network {\n    #[inline]\n    pub fn intersect(&self, cidr: &Cidr) -> bool {\n        let cidr_is_exception = self.except.iter().flatten().any(|ex| ex.contains(cidr));\n        let intersect = cidr.contains(&self.cidr) || self.cidr.contains(cidr);\n\n        intersect && !cidr_is_exception\n    }\n\n    #[inline]\n    pub fn contains(&self, addr: IpAddr) -> bool {\n        let addr = Cidr::Addr(addr);\n        let addr_is_exception = self.except.iter().flatten().any(|ex| ex.contains(&addr));\n        if addr_is_exception {\n            return false;\n        }\n\n        self.cidr.contains(&addr)\n    }\n\n    /// Returns the size of this Network. The size is the\n    /// cidr size - the sum of the exception sizes. We assume\n    /// that exceptions do not overlap.\n    #[inline]\n    pub fn block_size(&self) -> usize {\n        let except_size: usize = self.except.iter().flatten().map(|c| c.block_size()).sum();\n        self.cidr.block_size() - except_size\n    }\n}\n\n#[derive(\n    Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema,\n)]\n#[serde(untagged)]\npub enum Cidr {\n    Addr(std::net::IpAddr),\n    Net(ipnet::IpNet),\n}\n\n#[derive(Debug, thiserror::Error)]\n#[error(\"not a valid CIDR or IP address: {0}\")]\npub struct CidrParseError(String);\n\n// === impl Cidr ===\n\nimpl Cidr {\n    #[inline]\n    pub fn contains(&self, other: &Self) -> bool {\n        match (self, other) {\n            (Self::Net(this), Self::Net(other)) => this.contains(other),\n            (Self::Net(this), Self::Addr(other)) => this.contains(other),\n            (Self::Addr(this), Self::Net(other)) => ipnet::IpNet::from(*this).contains(other),\n            (Self::Addr(this), Self::Addr(other)) => this == other,\n        }\n    }\n\n    #[inline]\n    pub fn is_ipv6(&self) -> bool {\n        match self {\n            Self::Addr(addr) => addr.is_ipv6(),\n            Self::Net(IpNet::V4(_)) => false,\n            Self::Net(IpNet::V6(_)) => true,\n        }\n    }\n\n    /// Returns the size of this CIDR block.\n    ///\n    /// Returns `1` if this represents a single address.\n    #[inline]\n    pub fn block_size(&self) -> usize {\n        match self {\n            Cidr::Net(net) => net.hosts().count(),\n            Cidr::Addr(_) => 1,\n        }\n    }\n}\n\nimpl std::str::FromStr for Cidr {\n    type Err = CidrParseError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        if let Ok(net) = s.parse() {\n            return Ok(Self::Net(net));\n        }\n\n        if let Ok(addr) = s.parse() {\n            return Ok(Self::Addr(addr));\n        }\n\n        Err(CidrParseError(s.to_string()))\n    }\n}\n\nimpl From<Cidr> for ipnet::IpNet {\n    fn from(cidr: Cidr) -> ipnet::IpNet {\n        match cidr {\n            Cidr::Net(net) => net,\n            Cidr::Addr(addr) => ipnet::IpNet::from(addr),\n        }\n    }\n}\n\nimpl From<ipnet::IpNet> for Cidr {\n    fn from(net: ipnet::IpNet) -> Self {\n        Self::Net(net)\n    }\n}\n\nimpl From<ipnet::Ipv4Net> for Cidr {\n    fn from(net: ipnet::Ipv4Net) -> Self {\n        Self::Net(net.into())\n    }\n}\n\nimpl From<ipnet::Ipv6Net> for Cidr {\n    fn from(net: ipnet::Ipv6Net) -> Self {\n        Self::Net(net.into())\n    }\n}\n\nimpl From<std::net::IpAddr> for Cidr {\n    fn from(net: std::net::IpAddr) -> Self {\n        Self::Addr(net)\n    }\n}\n\nimpl From<std::net::Ipv4Addr> for Cidr {\n    fn from(addr: std::net::Ipv4Addr) -> Self {\n        Self::Addr(addr.into())\n    }\n}\n\nimpl From<std::net::Ipv6Addr> for Cidr {\n    fn from(addr: std::net::Ipv6Addr) -> Self {\n        Self::Addr(addr.into())\n    }\n}\n\nimpl std::fmt::Display for Cidr {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Addr(addr) => addr.fmt(f),\n            Self::Net(net) => net.fmt(f),\n        }\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/policy/network_authentication.rs",
    "content": "pub use super::Network;\n\n#[derive(\n    Clone,\n    Debug,\n    Default,\n    Eq,\n    PartialEq,\n    kube::CustomResource,\n    serde::Deserialize,\n    serde::Serialize,\n    schemars::JsonSchema,\n)]\n#[kube(\n    group = \"policy.linkerd.io\",\n    version = \"v1alpha1\",\n    kind = \"NetworkAuthentication\",\n    namespaced\n)]\n#[serde(rename_all = \"camelCase\")]\npub struct NetworkAuthenticationSpec {\n    pub networks: Vec<Network>,\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/policy/ratelimit_policy.rs",
    "content": "use super::{LocalTargetRef, NamespacedTargetRef};\nuse k8s_openapi::apimachinery::pkg::apis::meta::v1::Condition;\n\n#[derive(\n    Clone, Debug, kube::CustomResource, serde::Deserialize, serde::Serialize, schemars::JsonSchema,\n)]\n#[kube(\n    group = \"policy.linkerd.io\",\n    version = \"v1alpha1\",\n    kind = \"HTTPLocalRateLimitPolicy\",\n    root = \"HttpLocalRateLimitPolicy\",\n    status = \"HttpLocalRateLimitPolicyStatus\",\n    namespaced\n)]\n#[serde(rename_all = \"camelCase\")]\npub struct RateLimitPolicySpec {\n    pub target_ref: LocalTargetRef,\n    pub total: Option<Limit>,\n    pub identity: Option<Limit>,\n    pub overrides: Option<Vec<Override>>,\n}\n\n#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]\n#[serde(rename_all = \"camelCase\")]\npub struct HttpLocalRateLimitPolicyStatus {\n    pub conditions: Vec<Condition>,\n    pub target_ref: LocalTargetRef,\n}\n\n#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]\n#[serde(rename_all = \"camelCase\")]\npub struct Limit {\n    pub requests_per_second: u32,\n}\n\n#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]\n#[serde(rename_all = \"camelCase\")]\npub struct Override {\n    pub requests_per_second: u32,\n    pub client_refs: Vec<NamespacedTargetRef>,\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/policy/server.rs",
    "content": "use super::super::labels;\nuse kube::CustomResource;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\nuse std::{fmt, num::NonZeroU16};\n\n/// Describes a server interface exposed by a set of pods.\n#[derive(Clone, Debug, PartialEq, Eq, CustomResource, Deserialize, Serialize, JsonSchema)]\n#[kube(\n    group = \"policy.linkerd.io\",\n    version = \"v1beta3\",\n    kind = \"Server\",\n    namespaced\n)]\n#[serde(rename_all = \"camelCase\")]\npub struct ServerSpec {\n    #[serde(flatten)]\n    pub selector: Selector,\n    pub port: Port,\n    pub proxy_protocol: Option<ProxyProtocol>,\n    pub access_policy: Option<String>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]\npub enum Selector {\n    #[serde(rename = \"podSelector\")]\n    Pod(labels::Selector),\n    #[serde(rename = \"externalWorkloadSelector\")]\n    ExternalWorkload(labels::Selector),\n}\n\n/// References a pod spec's port by name or number.\n#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema)]\n#[serde(untagged)]\npub enum Port {\n    Number(NonZeroU16),\n    Name(String),\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]\npub enum ProxyProtocol {\n    #[serde(rename = \"unknown\")]\n    Unknown,\n    #[serde(rename = \"HTTP/1\")]\n    Http1,\n    #[serde(rename = \"HTTP/2\")]\n    Http2,\n    #[serde(rename = \"gRPC\")]\n    Grpc,\n    #[serde(rename = \"opaque\")]\n    Opaque,\n    #[serde(rename = \"TLS\")]\n    Tls,\n}\n\nimpl fmt::Display for Port {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Port::Number(n) => fmt::Display::fmt(n, f),\n            Port::Name(n) => fmt::Display::fmt(n, f),\n        }\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/policy/server_authorization.rs",
    "content": "pub use super::Network;\nuse crate::labels;\nuse kube::CustomResource;\nuse schemars::JsonSchema;\nuse serde::{Deserialize, Serialize};\n\n/// Authorizes clients to connect to a Server.\n#[derive(CustomResource, Default, Deserialize, Serialize, Clone, Debug, JsonSchema)]\n#[kube(\n    group = \"policy.linkerd.io\",\n    version = \"v1beta1\",\n    kind = \"ServerAuthorization\",\n    namespaced\n)]\n#[serde(rename_all = \"camelCase\")]\npub struct ServerAuthorizationSpec {\n    pub server: Server,\n    pub client: Client,\n}\n\n#[derive(Default, Deserialize, Serialize, Clone, Debug, JsonSchema)]\npub struct Server {\n    pub name: Option<String>,\n    pub selector: Option<labels::Selector>,\n}\n\n/// Describes an authenticated client.\n///\n/// Exactly one of `identities` and `service_accounts` should be set.\n#[derive(Default, Deserialize, Serialize, Clone, Debug, JsonSchema)]\n#[serde(rename_all = \"camelCase\")]\npub struct Client {\n    pub networks: Option<Vec<Network>>,\n\n    #[serde(default)]\n    pub unauthenticated: bool,\n\n    #[serde(rename = \"meshTLS\")]\n    pub mesh_tls: Option<MeshTls>,\n}\n\n/// Describes an authenticated client.\n///\n/// Exactly one of `identities` and `service_accounts` should be set.\n#[derive(Default, Deserialize, Serialize, Clone, Debug, JsonSchema)]\n#[serde(rename_all = \"camelCase\")]\npub struct MeshTls {\n    #[serde(rename = \"unauthenticatedTLS\", default)]\n    pub unauthenticated_tls: bool,\n\n    /// Indicates a Linkerd identity that is authorized to access a server.\n    pub identities: Option<Vec<String>>,\n\n    /// Identifies a `ServiceAccount` authorized to access a server.\n    pub service_accounts: Option<Vec<ServiceAccountRef>>,\n}\n\n/// References a Kubernetes `ServiceAccount` instance.\n///\n/// If no namespace is specified, the `Authorization`'s namespace is used.\n#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema)]\npub struct ServiceAccountRef {\n    pub namespace: Option<String>,\n    pub name: String,\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/policy/target_ref.rs",
    "content": "use super::targets_kind;\n\n#[derive(\n    Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema,\n)]\npub struct ClusterTargetRef {\n    pub group: Option<String>,\n    pub kind: String,\n    pub name: String,\n}\n\n#[derive(\n    Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema,\n)]\npub struct LocalTargetRef {\n    pub group: Option<String>,\n    pub kind: String,\n    pub name: String,\n}\n\n#[derive(\n    Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema,\n)]\npub struct NamespacedTargetRef {\n    pub group: Option<String>,\n    pub kind: String,\n    pub name: String,\n    pub namespace: Option<String>,\n}\n\nimpl ClusterTargetRef {\n    pub fn from_resource<T>(resource: &T) -> Self\n    where\n        T: kube::Resource,\n        T::DynamicType: Default,\n    {\n        let (group, kind, name) = group_kind_name(resource);\n        Self { group, kind, name }\n    }\n\n    /// Returns the target ref kind, qualified by its group, if necessary.\n    pub fn canonical_kind(&self) -> String {\n        canonical_kind(self.group.as_deref(), &self.kind)\n    }\n\n    /// Checks whether the target references the given resource type\n    pub fn targets_kind<T>(&self) -> bool\n    where\n        T: kube::Resource,\n        T::DynamicType: Default,\n    {\n        targets_kind::<T>(self.group.as_deref(), &self.kind)\n    }\n\n    /// Checks whether the target references the given cluster-level resource\n    pub fn targets<T>(&self, resource: &T) -> bool\n    where\n        T: kube::Resource,\n        T::DynamicType: Default,\n    {\n        if !self.targets_kind::<T>() {\n            return false;\n        }\n\n        if resource.meta().namespace.is_some() {\n            // If the reference or the resource has a namespace, that's a deal-breaker.\n            return false;\n        }\n\n        match resource.meta().name.as_deref() {\n            None => return false,\n            Some(rname) => {\n                if !self.name.eq_ignore_ascii_case(rname) {\n                    return false;\n                }\n            }\n        }\n\n        true\n    }\n}\n\nimpl LocalTargetRef {\n    pub fn from_resource<T>(resource: &T) -> Self\n    where\n        T: kube::Resource,\n        T::DynamicType: Default,\n    {\n        let (group, kind, name) = group_kind_name(resource);\n        Self { group, kind, name }\n    }\n\n    /// Returns the target ref kind, qualified by its group, if necessary.\n    pub fn canonical_kind(&self) -> String {\n        canonical_kind(self.group.as_deref(), &self.kind)\n    }\n\n    /// Checks whether the target references the given resource type\n    pub fn targets_kind<T>(&self) -> bool\n    where\n        T: kube::Resource,\n        T::DynamicType: Default,\n    {\n        targets_kind::<T>(self.group.as_deref(), &self.kind)\n    }\n\n    /// Checks whether the target references the given namespaced resource\n    pub fn targets<T>(&self, resource: &T, local_ns: &str) -> bool\n    where\n        T: kube::Resource,\n        T::DynamicType: Default,\n    {\n        if !self.targets_kind::<T>() {\n            return false;\n        }\n\n        // If the resource specifies a namespace other than the target or the\n        // default namespace, that's a deal-breaker.\n        match resource.meta().namespace.as_deref() {\n            Some(rns) if rns.eq_ignore_ascii_case(local_ns) => {}\n            _ => return false,\n        };\n\n        match resource.meta().name.as_deref() {\n            Some(rname) => rname.eq_ignore_ascii_case(&self.name),\n            _ => false,\n        }\n    }\n}\n\nimpl NamespacedTargetRef {\n    pub fn from_resource<T>(resource: &T) -> Self\n    where\n        T: kube::Resource,\n        T::DynamicType: Default,\n    {\n        let (group, kind, name) = group_kind_name(resource);\n        let namespace = resource.meta().namespace.clone();\n        Self {\n            group,\n            kind,\n            name,\n            namespace,\n        }\n    }\n\n    /// Returns the target ref kind, qualified by its group, if necessary.\n    pub fn canonical_kind(&self) -> String {\n        canonical_kind(self.group.as_deref(), &self.kind)\n    }\n\n    /// Checks whether the target references the given resource type\n    pub fn targets_kind<T>(&self) -> bool\n    where\n        T: kube::Resource,\n        T::DynamicType: Default,\n    {\n        targets_kind::<T>(self.group.as_deref(), &self.kind)\n    }\n\n    /// Checks whether the target references the given namespaced resource\n    pub fn targets<T>(&self, resource: &T, local_ns: &str) -> bool\n    where\n        T: kube::Resource,\n        T::DynamicType: Default,\n    {\n        if !self.targets_kind::<T>() {\n            return false;\n        }\n\n        // If the resource specifies a namespace other than the target or the\n        // default namespace, that's a deal-breaker.\n        let tns = self.namespace.as_deref().unwrap_or(local_ns);\n        match resource.meta().namespace.as_deref() {\n            Some(rns) if rns.eq_ignore_ascii_case(tns) => {}\n            _ => return false,\n        };\n\n        match resource.meta().name.as_deref() {\n            None => return false,\n            Some(rname) => {\n                if !self.name.eq_ignore_ascii_case(rname) {\n                    return false;\n                }\n            }\n        }\n\n        true\n    }\n}\n\nfn canonical_kind(group: Option<&str>, kind: &str) -> String {\n    if let Some(group) = group {\n        format!(\"{kind}.{group}\")\n    } else {\n        kind.to_string()\n    }\n}\n\nfn group_kind_name<T>(resource: &T) -> (Option<String>, String, String)\nwhere\n    T: kube::Resource,\n    T::DynamicType: Default,\n{\n    let dt = Default::default();\n\n    let group = match T::group(&dt) {\n        g if (*g).is_empty() => None,\n        g => Some(g.to_string()),\n    };\n\n    let kind = T::kind(&dt).to_string();\n\n    let name = resource\n        .meta()\n        .name\n        .clone()\n        .expect(\"resource must have a name\");\n\n    (group, kind, name)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::{policy::Server, Namespace, ObjectMeta, ServiceAccount};\n\n    #[test]\n    fn cluster_targets_namespace() {\n        let t = ClusterTargetRef {\n            group: None,\n            kind: \"Namespace\".to_string(),\n            name: \"appns\".to_string(),\n        };\n        assert!(t.targets_kind::<Namespace>());\n        assert!(t.targets(&Namespace {\n            metadata: ObjectMeta {\n                name: Some(\"appns\".to_string()),\n                ..ObjectMeta::default()\n            },\n            ..Namespace::default()\n        }));\n    }\n\n    #[test]\n    fn namespaced_targets_service_account() {\n        for tgt in &[\n            NamespacedTargetRef {\n                group: None,\n                kind: \"ServiceAccount\".to_string(),\n                name: \"default\".to_string(),\n                namespace: Some(\"appns\".to_string()),\n            },\n            NamespacedTargetRef {\n                group: Some(\"core\".to_string()),\n                kind: \"ServiceAccount\".to_string(),\n                name: \"default\".to_string(),\n                namespace: Some(\"appns\".to_string()),\n            },\n            NamespacedTargetRef {\n                group: Some(\"CORE\".to_string()),\n                kind: \"SERVICEACCOUNT\".to_string(),\n                name: \"DEFAULT\".to_string(),\n                namespace: Some(\"APPNS\".to_string()),\n            },\n            NamespacedTargetRef {\n                group: None,\n                kind: \"ServiceAccount\".to_string(),\n                name: \"default\".to_string(),\n                namespace: None,\n            },\n        ] {\n            assert!(tgt.targets_kind::<ServiceAccount>());\n\n            assert!(!tgt.targets_kind::<Namespace>());\n\n            let sa = ServiceAccount {\n                metadata: ObjectMeta {\n                    namespace: Some(\"appns\".to_string()),\n                    name: Some(\"default\".to_string()),\n                    ..ObjectMeta::default()\n                },\n                ..ServiceAccount::default()\n            };\n            assert!(\n                tgt.targets(&sa, \"appns\"),\n                \"ServiceAccounts are targeted by name: {tgt:#?}\"\n            );\n\n            let sa = ServiceAccount {\n                metadata: ObjectMeta {\n                    namespace: Some(\"otherns\".to_string()),\n                    name: Some(\"default\".to_string()),\n                    ..ObjectMeta::default()\n                },\n                ..ServiceAccount::default()\n            };\n            assert!(\n                !tgt.targets(&sa, \"appns\"),\n                \"ServiceAccounts in other namespaces should not be targeted: {tgt:#?}\"\n            );\n        }\n\n        let tgt = NamespacedTargetRef {\n            group: None,\n            kind: \"ServiceAccount\".to_string(),\n            name: \"default\".to_string(),\n            namespace: None,\n        };\n        assert!(\n            {\n                let sa = ServiceAccount {\n                    metadata: ObjectMeta {\n                        namespace: Some(\"appns\".to_string()),\n                        name: Some(\"special\".to_string()),\n                        ..ObjectMeta::default()\n                    },\n                    ..ServiceAccount::default()\n                };\n                !tgt.targets(&sa, \"appns\")\n            },\n            \"resource comparison uses \"\n        );\n    }\n\n    #[test]\n    fn namespaced_targets_server() {\n        let tgt = NamespacedTargetRef {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: \"Server\".to_string(),\n            name: \"http\".to_string(),\n            namespace: Some(\"appns\".to_string()),\n        };\n\n        assert!(tgt.targets_kind::<Server>());\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/policy/tcproute.rs",
    "content": "use crate::gateway;\n\npub fn parent_ref_targets_kind<T>(parent_ref: &gateway::TCPRouteParentRefs) -> bool\nwhere\n    T: kube::Resource,\n    T::DynamicType: Default,\n{\n    let kind = match parent_ref.kind {\n        Some(ref kind) => kind,\n        None => return false,\n    };\n\n    super::targets_kind::<T>(parent_ref.group.as_deref(), kind)\n}\n\npub fn backend_ref_targets_kind<T>(backend_ref: &gateway::TCPRouteRulesBackendRefs) -> bool\nwhere\n    T: kube::Resource,\n    T::DynamicType: Default,\n{\n    // Default kind is assumed to be service for backend ref objects\n    super::targets_kind::<T>(\n        backend_ref.group.as_deref(),\n        backend_ref.kind.as_deref().unwrap_or(\"Service\"),\n    )\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/policy/tlsroute.rs",
    "content": "use crate::gateway;\n\npub fn parent_ref_targets_kind<T>(parent_ref: &gateway::TLSRouteParentRefs) -> bool\nwhere\n    T: kube::Resource,\n    T::DynamicType: Default,\n{\n    let kind = match parent_ref.kind {\n        Some(ref kind) => kind,\n        None => return false,\n    };\n\n    super::targets_kind::<T>(parent_ref.group.as_deref(), kind)\n}\n\npub fn backend_ref_targets_kind<T>(backend_ref: &gateway::TLSRouteRulesBackendRefs) -> bool\nwhere\n    T: kube::Resource,\n    T::DynamicType: Default,\n{\n    // Default kind is assumed to be service for backend ref objects\n    super::targets_kind::<T>(\n        backend_ref.group.as_deref(),\n        backend_ref.kind.as_deref().unwrap_or(\"Service\"),\n    )\n}\n"
  },
  {
    "path": "policy-controller/k8s/api/src/policy.rs",
    "content": "pub mod authorization_policy;\npub mod egress_network;\npub mod grpcroute;\npub mod httproute;\npub mod meshtls_authentication;\nmod network;\npub mod network_authentication;\npub mod ratelimit_policy;\npub mod server;\npub mod server_authorization;\npub mod target_ref;\npub mod tcproute;\npub mod tlsroute;\n\npub use self::{\n    authorization_policy::{AuthorizationPolicy, AuthorizationPolicySpec},\n    egress_network::{EgressNetwork, EgressNetworkSpec, EgressNetworkStatus, TrafficPolicy},\n    httproute::{HttpRoute, HttpRouteSpec},\n    meshtls_authentication::{MeshTLSAuthentication, MeshTLSAuthenticationSpec},\n    network::{Cidr, Network},\n    network_authentication::{NetworkAuthentication, NetworkAuthenticationSpec},\n    ratelimit_policy::{\n        HttpLocalRateLimitPolicy, HttpLocalRateLimitPolicyStatus, Limit, Override,\n        RateLimitPolicySpec,\n    },\n    server::{Server, ServerSpec},\n    server_authorization::{ServerAuthorization, ServerAuthorizationSpec},\n    target_ref::{ClusterTargetRef, LocalTargetRef, NamespacedTargetRef},\n};\n\nfn targets_kind<T>(group: Option<&str>, kind: &str) -> bool\nwhere\n    T: kube::Resource,\n    T::DynamicType: Default,\n{\n    let dt = Default::default();\n\n    let mut t_group = &*T::group(&dt);\n    if t_group.is_empty() {\n        t_group = \"core\";\n    }\n\n    group\n        .filter(|s| !s.is_empty())\n        .unwrap_or(\"core\")\n        .eq_ignore_ascii_case(t_group)\n        && kind.eq_ignore_ascii_case(&T::kind(&dt))\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/Cargo.toml",
    "content": "[package]\nname = \"linkerd-policy-controller-k8s-index\"\nversion = \"0.1.0\"\nedition = \"2021\"\nlicense = \"Apache-2.0\"\npublish = false\n\n[dependencies]\nahash = \"0.8\"\nanyhow = \"1\"\nchrono = { version = \"0.4.44\", default-features = false }\nfutures = { version = \"0.3\", default-features = false }\nhttp = { workspace = true }\nkubert = { workspace = true, default-features = false, features = [\"index\"] }\nparking_lot = \"0.12\"\nprometheus-client = { workspace = true, default-features = false }\nthiserror = \"2\"\ntokio = { version = \"1\", features = [\"macros\", \"rt\", \"sync\"] }\ntracing = \"0.1\"\n\nlinkerd-policy-controller-core = { workspace = true }\nlinkerd-policy-controller-k8s-api = { workspace = true }\n\n[dependencies.kube]\nworkspace = true\ndefault-features = false\nfeatures = [\n    \"client\",\n    \"derive\",\n    \"runtime\",\n]\n\n[dev-dependencies]\nchrono = { version = \"0.4\", default-features = false }\nk8s-openapi = { workspace = true, features = [\"schemars\"] }\nmaplit = \"1\"\ntokio-stream = \"0.1\"\ntokio-test = \"0.4\"\ntracing-subscriber = \"0.3\"\n"
  },
  {
    "path": "policy-controller/k8s/index/src/cluster_info.rs",
    "content": "use std::{num::NonZeroU16, sync::Arc};\n\nuse crate::{ports::PortSet, DefaultPolicy};\nuse linkerd_policy_controller_core::IpNet;\nuse tokio::time;\n\n/// Holds cluster metadata.\n#[derive(Clone, Debug)]\npub struct ClusterInfo {\n    /// Networks including PodIPs in this cluster.\n    ///\n    /// Unfortunately, there's no way to discover this at runtime.\n    pub networks: Vec<IpNet>,\n\n    /// The namespace where the linkerd control plane is deployed\n    pub control_plane_ns: String,\n\n    /// E.g. \"cluster.local\"\n    pub dns_domain: String,\n\n    /// The cluster's mesh identity trust domain.\n    pub identity_domain: String,\n\n    /// The cluster-wide default policy.\n    pub default_policy: DefaultPolicy,\n\n    /// The cluster-wide default protocol detection timeout.\n    pub default_detect_timeout: time::Duration,\n\n    /// The default set of ports to be marked opaque.\n    pub default_opaque_ports: PortSet,\n\n    /// The networks that probes are expected to be from.\n    pub probe_networks: Vec<IpNet>,\n\n    /// The namespace that is designated for egress configuration\n    /// affecting all workloads across the cluster\n    pub global_egress_network_namespace: Arc<String>,\n}\n\nimpl ClusterInfo {\n    pub(crate) fn service_account_identity(&self, ns: &str, sa: &str) -> String {\n        format!(\n            \"{}.{}.serviceaccount.identity.{}.{}\",\n            sa, ns, self.control_plane_ns, self.identity_domain\n        )\n    }\n\n    pub(crate) fn namespace_identity(&self, ns: &str) -> String {\n        format!(\n            \"*.{}.serviceaccount.identity.{}.{}\",\n            ns, self.control_plane_ns, self.identity_domain\n        )\n    }\n\n    pub(crate) fn service_dns_authority(&self, ns: &str, svc: &str, port: NonZeroU16) -> String {\n        format!(\"{}.{}.svc.{}:{port}\", svc, ns, self.dns_domain)\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/defaults.rs",
    "content": "use ahash::AHashMap as HashMap;\nuse anyhow::{anyhow, Error, Result};\nuse linkerd_policy_controller_core::{\n    inbound::{AuthorizationRef, ClientAuthentication, ClientAuthorization},\n    IdentityMatch, IpNet,\n};\nuse std::hash::Hash;\n\nuse crate::ClusterInfo;\n\n/// Indicates the default behavior to apply when no Server is found for a port.\n#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]\npub enum DefaultPolicy {\n    Allow {\n        /// Indicates that, by default, all traffic must be authenticated.\n        authenticated_only: bool,\n\n        /// Indicates that all traffic must, by default, be from an IP address within the cluster.\n        cluster_only: bool,\n    },\n\n    /// Indicates that all traffic is denied unless explicitly permitted by an authorization policy.\n    Deny,\n\n    /// Indicates that all traffic is let through, but gets audited\n    Audit,\n}\n\n// === impl DefaultPolicy ===\n\nimpl std::str::FromStr for DefaultPolicy {\n    type Err = Error;\n\n    fn from_str(s: &str) -> Result<Self> {\n        match s {\n            \"all-authenticated\" => Ok(Self::Allow {\n                authenticated_only: true,\n                cluster_only: false,\n            }),\n            \"all-unauthenticated\" => Ok(Self::Allow {\n                authenticated_only: false,\n                cluster_only: false,\n            }),\n            \"cluster-authenticated\" => Ok(Self::Allow {\n                authenticated_only: true,\n                cluster_only: true,\n            }),\n            \"cluster-unauthenticated\" => Ok(Self::Allow {\n                authenticated_only: false,\n                cluster_only: true,\n            }),\n            \"deny\" => Ok(Self::Deny),\n            \"audit\" => Ok(Self::Audit),\n            s => Err(anyhow!(\"invalid mode: {s:?}\")),\n        }\n    }\n}\n\nimpl DefaultPolicy {\n    pub(crate) fn as_str(&self) -> &'static str {\n        match self {\n            Self::Allow {\n                authenticated_only: true,\n                cluster_only: false,\n            } => \"all-authenticated\",\n            Self::Allow {\n                authenticated_only: false,\n                cluster_only: false,\n            } => \"all-unauthenticated\",\n            Self::Allow {\n                authenticated_only: true,\n                cluster_only: true,\n            } => \"cluster-authenticated\",\n            Self::Allow {\n                authenticated_only: false,\n                cluster_only: true,\n            } => \"cluster-unauthenticated\",\n            Self::Deny => \"deny\",\n            Self::Audit => \"audit\",\n        }\n    }\n\n    pub(crate) fn default_authzs(\n        self,\n        config: &ClusterInfo,\n    ) -> HashMap<AuthorizationRef, ClientAuthorization> {\n        let mut authzs = HashMap::default();\n        let auth_ref = AuthorizationRef::Default(self.as_str());\n\n        if let DefaultPolicy::Allow {\n            authenticated_only,\n            cluster_only,\n        } = self\n        {\n            authzs.insert(\n                auth_ref,\n                Self::default_client_authz(config, authenticated_only, cluster_only),\n            );\n        } else if let DefaultPolicy::Audit = self {\n            authzs.insert(auth_ref, Self::default_client_authz(config, false, false));\n        }\n\n        authzs\n    }\n\n    fn default_client_authz(\n        config: &ClusterInfo,\n        authenticated_only: bool,\n        cluster_only: bool,\n    ) -> ClientAuthorization {\n        let authentication = if authenticated_only {\n            ClientAuthentication::TlsAuthenticated(vec![IdentityMatch::Suffix(vec![])])\n        } else {\n            ClientAuthentication::Unauthenticated\n        };\n        let networks = if cluster_only {\n            config.networks.iter().copied().map(Into::into).collect()\n        } else {\n            vec![\n                \"0.0.0.0/0\".parse::<IpNet>().unwrap().into(),\n                \"::/0\".parse::<IpNet>().unwrap().into(),\n            ]\n        };\n\n        ClientAuthorization {\n            authentication,\n            networks,\n        }\n    }\n}\n\nimpl std::fmt::Display for DefaultPolicy {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        self.as_str().fmt(f)\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_parse_displayed() {\n        for default in [\n            DefaultPolicy::Deny,\n            DefaultPolicy::Allow {\n                authenticated_only: true,\n                cluster_only: false,\n            },\n            DefaultPolicy::Allow {\n                authenticated_only: false,\n                cluster_only: false,\n            },\n            DefaultPolicy::Allow {\n                authenticated_only: false,\n                cluster_only: true,\n            },\n            DefaultPolicy::Allow {\n                authenticated_only: false,\n                cluster_only: true,\n            },\n            DefaultPolicy::Audit,\n        ] {\n            assert_eq!(\n                default.to_string().parse::<DefaultPolicy>().unwrap(),\n                default,\n                \"failed to parse displayed {default:?}\"\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/authorization_policy.rs",
    "content": "use anyhow::Result;\nuse linkerd_policy_controller_core::routes::GroupKindName;\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s, gateway,\n    policy::{LocalTargetRef, NamespacedTargetRef},\n    ServiceAccount,\n};\n\n#[derive(Debug, PartialEq)]\npub(crate) struct Spec {\n    pub target: Target,\n    pub authentications: Vec<AuthenticationTarget>,\n}\n\n#[derive(Debug, PartialEq)]\npub(crate) enum Target {\n    HttpRoute(GroupKindName),\n    GrpcRoute(GroupKindName),\n    Server(String),\n    Namespace,\n}\n\n#[derive(Debug, PartialEq)]\npub(crate) enum AuthenticationTarget {\n    MeshTLS {\n        namespace: Option<String>,\n        name: String,\n    },\n    Network {\n        namespace: Option<String>,\n        name: String,\n    },\n    ServiceAccount {\n        namespace: Option<String>,\n        name: String,\n    },\n}\n\n#[inline]\npub fn validate(ap: k8s::policy::AuthorizationPolicySpec) -> Result<()> {\n    Spec::try_from(ap)?;\n    Ok(())\n}\n\nimpl TryFrom<k8s::policy::AuthorizationPolicySpec> for Spec {\n    type Error = anyhow::Error;\n\n    fn try_from(ap: k8s::policy::AuthorizationPolicySpec) -> Result<Self> {\n        let target = target(ap.target_ref)?;\n\n        let authentications = ap\n            .required_authentication_refs\n            .into_iter()\n            .map(authentication_ref)\n            .collect::<Result<Vec<_>>>()?;\n\n        Ok(Self {\n            target,\n            authentications,\n        })\n    }\n}\n\nfn target(t: LocalTargetRef) -> Result<Target> {\n    match t {\n        t if t.targets_kind::<k8s::policy::Server>() => Ok(Target::Server(t.name)),\n        t if t.targets_kind::<k8s::Namespace>() => Ok(Target::Namespace),\n        t if t.targets_kind::<k8s::policy::HttpRoute>()\n            || t.targets_kind::<gateway::HTTPRoute>() =>\n        {\n            Ok(Target::HttpRoute(GroupKindName {\n                group: t.group.unwrap_or_default().into(),\n                kind: t.kind.into(),\n                name: t.name.into(),\n            }))\n        }\n        t if t.targets_kind::<gateway::GRPCRoute>() => Ok(Target::GrpcRoute(GroupKindName {\n            group: t.group.unwrap_or_default().into(),\n            kind: t.kind.into(),\n            name: t.name.into(),\n        })),\n        _ => anyhow::bail!(\n            \"unsupported authorization target type: {}\",\n            t.canonical_kind()\n        ),\n    }\n}\n\nfn authentication_ref(t: NamespacedTargetRef) -> Result<AuthenticationTarget> {\n    if t.targets_kind::<k8s::policy::MeshTLSAuthentication>() {\n        Ok(AuthenticationTarget::MeshTLS {\n            namespace: t.namespace,\n            name: t.name,\n        })\n    } else if t.targets_kind::<k8s::policy::NetworkAuthentication>() {\n        Ok(AuthenticationTarget::Network {\n            namespace: t.namespace,\n            name: t.name,\n        })\n    } else if t.targets_kind::<ServiceAccount>() {\n        Ok(AuthenticationTarget::ServiceAccount {\n            namespace: t.namespace,\n            name: t.name,\n        })\n    } else {\n        anyhow::bail!(\"unsupported authentication target: {}\", t.canonical_kind());\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/index/grpc.rs",
    "content": "use crate::inbound::routes::{ParentRef, RouteBinding, Status};\nuse crate::routes::grpc::try_match;\nuse ahash::AHashMap as HashMap;\nuse anyhow::{bail, Error, Result};\nuse linkerd_policy_controller_core::{\n    inbound::{Filter, GrpcRoute, InboundRoute, InboundRouteRule},\n    routes::GrpcRouteMatch,\n};\nuse linkerd_policy_controller_k8s_api::{self as k8s, gateway};\n\nimpl TryFrom<gateway::GRPCRoute> for RouteBinding<GrpcRoute> {\n    type Error = Error;\n\n    fn try_from(route: gateway::GRPCRoute) -> Result<Self, Self::Error> {\n        let route_ns = route.metadata.namespace.as_deref();\n        let creation_timestamp = route.metadata.creation_timestamp.map(|k8s::Time(t)| t);\n        let parents = ParentRef::collect_from_grpc(route_ns, route.spec.parent_refs)?;\n        let hostnames = route\n            .spec\n            .hostnames\n            .into_iter()\n            .flatten()\n            .map(crate::routes::host_match)\n            .collect();\n\n        let rules = route\n            .spec\n            .rules\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::GRPCRouteRules {\n                     matches, filters, ..\n                 }| { try_grpc_rule(matches, filters, try_grpc_filter) },\n            )\n            .collect::<Result<_>>()?;\n\n        let statuses = route\n            .status\n            .map_or_else(Vec::new, Status::collect_from_grpc);\n\n        Ok(RouteBinding {\n            parents,\n            route: InboundRoute {\n                hostnames,\n                rules,\n                authorizations: HashMap::default(),\n                creation_timestamp,\n            },\n            statuses,\n        })\n    }\n}\n\nfn try_grpc_rule<F>(\n    matches: Option<Vec<gateway::GRPCRouteRulesMatches>>,\n    filters: Option<Vec<F>>,\n    try_filter: impl Fn(F) -> Result<Filter>,\n) -> Result<InboundRouteRule<GrpcRouteMatch>> {\n    let matches = matches\n        .into_iter()\n        .flatten()\n        .map(try_match)\n        .collect::<Result<_>>()?;\n\n    let filters = filters\n        .into_iter()\n        .flatten()\n        .map(try_filter)\n        .collect::<Result<_>>()?;\n\n    Ok(InboundRouteRule { matches, filters })\n}\n\nfn try_grpc_filter(filter: gateway::GRPCRouteRulesFilters) -> Result<Filter> {\n    if let Some(request_header_modifier) = filter.request_header_modifier {\n        let filter = crate::routes::grpc::request_header_modifier(request_header_modifier)?;\n        return Ok(Filter::RequestHeaderModifier(filter));\n    }\n\n    if let Some(response_header_modifier) = filter.response_header_modifier {\n        let filter = crate::routes::grpc::response_header_modifier(response_header_modifier)?;\n        return Ok(Filter::ResponseHeaderModifier(filter));\n    }\n\n    if let Some(_request_mirror) = filter.request_mirror {\n        bail!(\"RequestMirror filter is not supported\")\n    }\n\n    if let Some(_extension_ref) = filter.extension_ref {\n        bail!(\"ExtensionRef filter is not supported\")\n    }\n\n    bail!(\"No filter specified\");\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/index/http.rs",
    "content": "use crate::inbound::routes::{ParentRef, RouteBinding, Status};\nuse crate::routes::http::try_match;\nuse ahash::AHashMap as HashMap;\nuse anyhow::{bail, Error, Result};\nuse linkerd_policy_controller_core::{\n    inbound::{Filter, HttpRoute, InboundRoute, InboundRouteRule},\n    routes::HttpRouteMatch,\n};\nuse linkerd_policy_controller_k8s_api::{self as k8s, gateway, policy};\n\nimpl TryFrom<gateway::HTTPRoute> for RouteBinding<HttpRoute> {\n    type Error = Error;\n\n    fn try_from(route: gateway::HTTPRoute) -> Result<Self, Self::Error> {\n        let route_ns = route.metadata.namespace.as_deref();\n        let creation_timestamp = route.metadata.creation_timestamp.map(|k8s::Time(t)| t);\n        let parents = ParentRef::collect_from_http(route_ns, route.spec.parent_refs)?;\n        let hostnames = route\n            .spec\n            .hostnames\n            .into_iter()\n            .flatten()\n            .map(crate::routes::host_match)\n            .collect();\n\n        let rules = route\n            .spec\n            .rules\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::HTTPRouteRules {\n                     matches, filters, ..\n                 }| try_http_rule(matches, filters, try_gateway_filter),\n            )\n            .collect::<Result<_>>()?;\n\n        let statuses = route\n            .status\n            .map_or_else(Vec::new, Status::collect_from_http);\n\n        Ok(RouteBinding {\n            parents,\n            route: InboundRoute {\n                hostnames,\n                rules,\n                authorizations: HashMap::default(),\n                creation_timestamp,\n            },\n            statuses,\n        })\n    }\n}\n\nimpl TryFrom<policy::HttpRoute> for RouteBinding<HttpRoute> {\n    type Error = Error;\n\n    fn try_from(route: policy::HttpRoute) -> Result<Self, Self::Error> {\n        let route_ns = route.metadata.namespace.as_deref();\n        let creation_timestamp = route.metadata.creation_timestamp.map(|k8s::Time(t)| t);\n        let parents = ParentRef::collect_from_http(route_ns, route.spec.parent_refs)?;\n        let hostnames = route\n            .spec\n            .hostnames\n            .into_iter()\n            .flatten()\n            .map(crate::routes::host_match)\n            .collect();\n\n        let rules = route\n            .spec\n            .rules\n            .into_iter()\n            .flatten()\n            .map(\n                |policy::httproute::HttpRouteRule {\n                     matches, filters, ..\n                 }| { try_http_rule(matches, filters, try_policy_filter) },\n            )\n            .collect::<Result<_>>()?;\n\n        let statuses = route\n            .status\n            .map_or_else(Vec::new, Status::collect_from_http);\n\n        Ok(RouteBinding {\n            parents,\n            route: InboundRoute {\n                hostnames,\n                rules,\n                authorizations: HashMap::default(),\n                creation_timestamp,\n            },\n            statuses,\n        })\n    }\n}\n\nfn try_http_rule<F>(\n    matches: Option<Vec<gateway::HTTPRouteRulesMatches>>,\n    filters: Option<Vec<F>>,\n    try_filter: impl Fn(F) -> Result<Filter>,\n) -> Result<InboundRouteRule<HttpRouteMatch>> {\n    let matches = matches\n        .into_iter()\n        .flatten()\n        .map(try_match)\n        .collect::<Result<_>>()?;\n\n    let filters = filters\n        .into_iter()\n        .flatten()\n        .map(try_filter)\n        .collect::<Result<_>>()?;\n\n    Ok(InboundRouteRule { matches, filters })\n}\n\nfn try_gateway_filter(filter: gateway::HTTPRouteRulesFilters) -> Result<Filter> {\n    if let Some(request_header_modifier) = filter.request_header_modifier {\n        let filter = crate::routes::http::request_header_modifier(request_header_modifier)?;\n        return Ok(Filter::RequestHeaderModifier(filter));\n    }\n    if let Some(response_header_modifier) = filter.response_header_modifier {\n        let filter = crate::routes::http::response_header_modifier(response_header_modifier)?;\n        return Ok(Filter::ResponseHeaderModifier(filter));\n    }\n    if let Some(request_redirect) = filter.request_redirect {\n        let filter = crate::routes::http::req_redirect(request_redirect)?;\n        return Ok(Filter::RequestRedirect(filter));\n    }\n    if let Some(_request_mirror) = filter.request_mirror {\n        bail!(\"RequestMirror filter is not supported\")\n    }\n    if let Some(_url_rewrite) = filter.url_rewrite {\n        bail!(\"URLRewrite filter is not supported\")\n    }\n    if let Some(_extension_ref) = filter.extension_ref {\n        bail!(\"ExtensionRef filter is not supported\")\n    }\n    bail!(\"No filter specified\");\n}\n\nfn try_policy_filter(filter: policy::httproute::HttpRouteFilter) -> Result<Filter> {\n    let filter = match filter {\n        policy::httproute::HttpRouteFilter::RequestHeaderModifier {\n            request_header_modifier,\n        } => {\n            let filter = crate::routes::http::request_header_modifier(request_header_modifier)?;\n            Filter::RequestHeaderModifier(filter)\n        }\n\n        policy::httproute::HttpRouteFilter::ResponseHeaderModifier {\n            response_header_modifier,\n        } => {\n            let filter = crate::routes::http::response_header_modifier(response_header_modifier)?;\n            Filter::ResponseHeaderModifier(filter)\n        }\n\n        policy::httproute::HttpRouteFilter::RequestRedirect { request_redirect } => {\n            let filter = crate::routes::http::req_redirect(request_redirect)?;\n            Filter::RequestRedirect(filter)\n        }\n    };\n    Ok(filter)\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/index/metrics.rs",
    "content": "use prometheus_client::{\n    collector::Collector,\n    encoding::{DescriptorEncoder, EncodeMetric},\n    metrics::{gauge::ConstGauge, MetricType},\n    registry::Registry,\n};\n\nuse super::SharedIndex;\n\n#[derive(Debug)]\nstruct Instrumented(SharedIndex);\n\npub fn register(reg: &mut Registry, index: SharedIndex) {\n    reg.register_collector(Box::new(Instrumented(index)));\n}\n\nimpl Collector for Instrumented {\n    fn encode(\n        &self,\n        mut encoder: DescriptorEncoder<'_>,\n    ) -> std::prelude::v1::Result<(), std::fmt::Error> {\n        let this = self.0.read();\n\n        let mut meshtls_authn_encoder = encoder.encode_descriptor(\n            \"meshtls_authentication_index_size\",\n            \"The number of MeshTLS authentications in index\",\n            None,\n            MetricType::Gauge,\n        )?;\n        for (ns, auth) in &this.authentications.by_ns {\n            let labels = [(\"namespace\", ns.as_str())];\n            let meshtls_authn = ConstGauge::new(auth.meshtls.len() as u32);\n            let meshtls_authn_encoder = meshtls_authn_encoder.encode_family(&labels)?;\n            meshtls_authn.encode(meshtls_authn_encoder)?;\n        }\n\n        let mut network_authn_encoder = encoder.encode_descriptor(\n            \"network_authentication_index_size\",\n            \"The number of Network authentications in index\",\n            None,\n            MetricType::Gauge,\n        )?;\n        for (ns, auth) in &this.authentications.by_ns {\n            let labels = [(\"namespace\", ns.as_str())];\n            let network_authn = ConstGauge::new(auth.network.len() as u32);\n            let network_authn_encoder = network_authn_encoder.encode_family(&labels)?;\n            network_authn.encode(network_authn_encoder)?;\n        }\n\n        let mut pods_encoder = encoder.encode_descriptor(\n            \"pod_index_size\",\n            \"The number of pods in index\",\n            None,\n            MetricType::Gauge,\n        )?;\n        for (ns, index) in &this.namespaces.by_ns {\n            let labels = [(\"namespace\", ns.as_str())];\n            let pods = ConstGauge::new(index.pods.by_name.len() as u32);\n            let pods_encoder = pods_encoder.encode_family(&labels)?;\n            pods.encode(pods_encoder)?;\n        }\n\n        let mut external_workloads_encoder = encoder.encode_descriptor(\n            \"external_workload_index_size\",\n            \"The number of external workloads in index\",\n            None,\n            MetricType::Gauge,\n        )?;\n        for (ns, index) in &this.namespaces.by_ns {\n            let labels = [(\"namespace\", ns.as_str())];\n            let external_workloads = ConstGauge::new(index.external_workloads.by_name.len() as u32);\n            let external_workloads_encoder = external_workloads_encoder.encode_family(&labels)?;\n            external_workloads.encode(external_workloads_encoder)?;\n        }\n\n        let mut servers_encoder = encoder.encode_descriptor(\n            \"server_index_size\",\n            \"The number of servers in index\",\n            None,\n            MetricType::Gauge,\n        )?;\n        for (ns, index) in &this.namespaces.by_ns {\n            let labels = [(\"namespace\", ns.as_str())];\n            let servers = ConstGauge::new(index.policy.servers.len() as u32);\n            let servers_encoder = servers_encoder.encode_family(&labels)?;\n            servers.encode(servers_encoder)?;\n        }\n\n        let mut server_authz_encoder = encoder.encode_descriptor(\n            \"server_authorization_index_size\",\n            \"The number of server authorizations in index\",\n            None,\n            MetricType::Gauge,\n        )?;\n        for (ns, index) in &this.namespaces.by_ns {\n            let labels = [(\"namespace\", ns.as_str())];\n            let server_authz = ConstGauge::new(index.policy.server_authorizations.len() as u32);\n            let server_authz_encoder = server_authz_encoder.encode_family(&labels)?;\n            server_authz.encode(server_authz_encoder)?;\n        }\n\n        let mut authz_policies_encoder = encoder.encode_descriptor(\n            \"authorization_policy_index_size\",\n            \"The number of authorization policies in index\",\n            None,\n            MetricType::Gauge,\n        )?;\n        for (ns, index) in &this.namespaces.by_ns {\n            let labels = [(\"namespace\", ns.as_str())];\n            let authz_policies = ConstGauge::new(index.policy.authorization_policies.len() as u32);\n            let authz_policies_encoder = authz_policies_encoder.encode_family(&labels)?;\n            authz_policies.encode(authz_policies_encoder)?;\n        }\n\n        let mut http_routes_encoder = encoder.encode_descriptor(\n            \"http_route_index_size\",\n            \"The number of HTTP routes in index\",\n            None,\n            MetricType::Gauge,\n        )?;\n        for (ns, index) in &this.namespaces.by_ns {\n            let labels = [(\"namespace\", ns.as_str())];\n            let http_routes = ConstGauge::new(index.policy.http_routes.len() as u32);\n            let http_routes_encoder = http_routes_encoder.encode_family(&labels)?;\n            http_routes.encode(http_routes_encoder)?;\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/index.rs",
    "content": "//! Keeps track of `Pod`, `Server`, and `ServerAuthorization` resources to\n//! provide a dynamic server configuration for all known ports on all pods.\n//!\n//! The `Index` type exposes a single public method: `Index::pod_server_rx`,\n//! which is used to lookup pod/ports (i.e. by the gRPC API). Otherwise, it\n//! implements `kubert::index::IndexNamespacedResource` for the indexed\n//! kubernetes resources.\n\nuse super::{\n    authorization_policy, meshtls_authentication, network_authentication, ratelimit_policy,\n    routes::RouteBinding, server, server_authorization, workload,\n};\nuse crate::{\n    ports::{PortHasher, PortMap, PortSet},\n    routes::{ExplicitGKN, ImpliedGKN},\n    ClusterInfo, DefaultPolicy,\n};\nuse ahash::{AHashMap as HashMap, AHashSet as HashSet};\nuse anyhow::{anyhow, bail, Result};\nuse linkerd_policy_controller_core::{\n    inbound::{\n        AuthorizationRef, ClientAuthentication, ClientAuthorization, GrpcRoute, HttpRoute,\n        InboundRouteRule, InboundServer, Limit, Override, ProxyProtocol, RateLimit, RouteRef,\n        ServerRef,\n    },\n    routes::{GroupKindName, HttpRouteMatch, Method, PathMatch},\n    IdentityMatch, Ipv4Net, Ipv6Net, NetworkMatch,\n};\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s, gateway,\n    policy::server::{Port, Selector},\n    ResourceExt,\n};\nuse parking_lot::RwLock;\nuse std::{\n    collections::{hash_map::Entry, BTreeSet},\n    num::NonZeroU16,\n    sync::Arc,\n};\nuse tokio::sync::watch;\nuse tracing::info_span;\n\nmod grpc;\nmod http;\npub mod metrics;\n\npub type SharedIndex = Arc<RwLock<Index>>;\n\n/// Holds all indexing state. Owned and updated by a single task that processes\n/// watch events, publishing results to the shared lookup map for quick lookups\n/// in the API server.\n#[derive(Debug)]\npub struct Index {\n    cluster_info: Arc<ClusterInfo>,\n    namespaces: NamespaceIndex,\n    authentications: AuthenticationNsIndex,\n}\n\n/// Holds all `Pod`, `Server`, and `ServerAuthorization` indices by-namespace.\n#[derive(Debug)]\nstruct NamespaceIndex {\n    cluster_info: Arc<ClusterInfo>,\n    by_ns: HashMap<String, Namespace>,\n}\n\n/// Holds all `NetworkAuthentication` and `MeshTLSAuthentication` indices by-namespace.\n///\n/// This is separate from `NamespaceIndex` because authorization policies may reference\n/// authentication resources across namespaces.\n#[derive(Debug, Default)]\nstruct AuthenticationNsIndex {\n    by_ns: HashMap<String, AuthenticationIndex>,\n}\n\n/// Holds `Pod`, `ExternalWorkload`, `Server`, and `ServerAuthorization` indices for a single namespace.\n#[derive(Debug)]\nstruct Namespace {\n    pods: PodIndex,\n    external_workloads: ExternalWorkloadIndex,\n    policy: PolicyIndex,\n}\n\n/// Holds all pod data for a single namespace.\n#[derive(Debug)]\nstruct PodIndex {\n    namespace: String,\n    by_name: HashMap<String, Pod>,\n}\n\n/// Holds a single pod's data with the server watches for all known ports.\n///\n/// The set of ports/servers is updated as clients discover server configuration\n/// or as `Server` resources select a port.\n#[derive(Debug)]\nstruct Pod {\n    meta: workload::Meta,\n\n    /// The pod's named container ports. Used by `Server` port selectors.\n    ///\n    /// A pod may have multiple ports with the same name. E.g., each container\n    /// may have its own `admin` port.\n    port_names: HashMap<String, PortSet>,\n\n    /// All known TCP server ports. This may be updated by\n    /// `Namespace::reindex`--when a port is selected by a `Server`--or by\n    /// `Namespace::get_pod_server` when a client discovers a port that has no\n    /// configured server (and i.e. uses the default policy).\n    port_servers: PortMap<WorkloadPortServer>,\n\n    /// The pod's probe ports and their respective paths.\n    ///\n    /// In order for the policy controller to authorize probes, it must be\n    /// aware of the probe ports and the expected paths on which probes are\n    /// expected.\n    probes: PortMap<BTreeSet<String>>,\n}\n\n/// Holds the state of a single port on a workload (e.g. a pod or an external\n/// workload).\n#[derive(Debug)]\nstruct WorkloadPortServer {\n    /// The name of the server resource that matches this port. Unset when no\n    /// server resources match this pod/port (and, i.e., the default policy is\n    /// used).\n    name: Option<String>,\n\n    /// A sender used to broadcast workload port server updates.\n    watch: watch::Sender<InboundServer>,\n}\n\n/// Holds all external workload data for a single namespace\n#[derive(Debug)]\nstruct ExternalWorkloadIndex {\n    namespace: String,\n    by_name: HashMap<String, ExternalWorkload>,\n}\n\n/// Holds data for a single external workload, with server watches for all known\n/// ports.\n///\n/// The set of ports / servers is updated as clients discover server\n/// configuration or as `Server` resources select a port.\n#[derive(Debug)]\nstruct ExternalWorkload {\n    meta: workload::Meta,\n\n    // The workload's named container ports. Used by `Server` port selectors.\n    //\n    // A workload will not have multiple ports with the same name, e.g. two\n    // `admin` ports pointing to different numerical values.\n    port_names: HashMap<String, NonZeroU16>,\n\n    /// All known TCP server ports.\n    port_servers: PortMap<WorkloadPortServer>,\n}\n\n/// Holds the state of policy resources for a single namespace.\n#[derive(Debug)]\nstruct PolicyIndex {\n    namespace: String,\n    cluster_info: Arc<ClusterInfo>,\n\n    servers: HashMap<String, server::Server>,\n    server_authorizations: HashMap<String, server_authorization::ServerAuthz>,\n\n    authorization_policies: HashMap<String, authorization_policy::Spec>,\n    ratelimit_policies: HashMap<String, ratelimit_policy::Spec>,\n    http_routes: HashMap<GroupKindName, RouteBinding<HttpRoute>>,\n    grpc_routes: HashMap<GroupKindName, RouteBinding<GrpcRoute>>,\n}\n\n#[derive(Debug, Default)]\nstruct AuthenticationIndex {\n    meshtls: HashMap<String, meshtls_authentication::Spec>,\n    network: HashMap<String, network_authentication::Spec>,\n}\n\nstruct NsUpdate<K, T> {\n    added: Vec<(K, T)>,\n    removed: HashSet<K>,\n}\n\n// === impl Index ===\n\nimpl Index {\n    pub fn shared(cluster_info: impl Into<Arc<ClusterInfo>>) -> SharedIndex {\n        let cluster_info = cluster_info.into();\n        Arc::new(RwLock::new(Self {\n            cluster_info: cluster_info.clone(),\n            namespaces: NamespaceIndex {\n                cluster_info,\n                by_ns: HashMap::default(),\n            },\n            authentications: AuthenticationNsIndex::default(),\n        }))\n    }\n\n    /// Obtains a pod:port's server receiver.\n    ///\n    /// An error is returned if the pod is not found. If the port is not found,\n    /// a default is server is created.\n    pub fn pod_server_rx(\n        &mut self,\n        namespace: &str,\n        pod: &str,\n        port: NonZeroU16,\n    ) -> Result<watch::Receiver<InboundServer>> {\n        let ns = self\n            .namespaces\n            .by_ns\n            .get_mut(namespace)\n            .ok_or_else(|| anyhow::anyhow!(\"namespace not found: {namespace}\"))?;\n        let pod = ns\n            .pods\n            .by_name\n            .get_mut(pod)\n            .ok_or_else(|| anyhow::anyhow!(\"pod {pod}.{namespace} not found\"))?;\n        Ok(pod\n            .port_server_or_default(port, &self.cluster_info)\n            .watch\n            .subscribe())\n    }\n\n    /// Obtains an external_workload:port's server receiver.\n    ///\n    /// An error is returned if the external workload is not found. If the port\n    /// is not found, a default server is created.\n    pub fn external_workload_server_rx(\n        &mut self,\n        namespace: &str,\n        workload: &str,\n        port: NonZeroU16,\n    ) -> Result<watch::Receiver<InboundServer>> {\n        let ns = self\n            .namespaces\n            .by_ns\n            .get_mut(namespace)\n            .ok_or_else(|| anyhow::anyhow!(\"namespace not found: {namespace}\"))?;\n        let external_workload = ns\n            .external_workloads\n            .by_name\n            .get_mut(workload)\n            .ok_or_else(|| anyhow::anyhow!(\"external workload {workload}.{namespace} not found\"))?;\n        Ok(external_workload\n            .port_server_or_default(port, &self.cluster_info)\n            .watch\n            .subscribe())\n    }\n\n    fn ns_with_reindex(&mut self, namespace: String, f: impl FnOnce(&mut Namespace) -> bool) {\n        self.namespaces\n            .get_with_reindex(namespace, &self.authentications, f)\n    }\n\n    fn ns_or_default_with_reindex(\n        &mut self,\n        namespace: String,\n        f: impl FnOnce(&mut Namespace) -> bool,\n    ) {\n        self.namespaces\n            .get_or_default_with_reindex(namespace, &self.authentications, f)\n    }\n\n    fn reindex_all(&mut self) {\n        tracing::debug!(\"Reindexing all namespaces\");\n        for ns in self.namespaces.by_ns.values_mut() {\n            ns.reindex(&self.authentications);\n        }\n    }\n\n    fn apply_http_route<R>(&mut self, route: R)\n    where\n        R: ResourceExt<DynamicType = ()>,\n        RouteBinding<HttpRoute>: TryFrom<R>,\n        <RouteBinding<HttpRoute> as TryFrom<R>>::Error: std::fmt::Display,\n    {\n        let ns = route.namespace().expect(\"HttpRoute must have a namespace\");\n        let name = route.name_unchecked();\n        let gkn = route.gkn();\n        let _span = info_span!(\"apply httproute\", %ns, %name).entered();\n\n        let route_binding = match route.try_into() {\n            Ok(binding) => binding,\n            Err(error) => {\n                tracing::info!(%ns, %name, %error, \"Ignoring HTTPRoute\");\n                return;\n            }\n        };\n\n        self.ns_or_default_with_reindex(ns, |ns| ns.policy.update_http_route(gkn, route_binding))\n    }\n\n    fn reset_http_route<R>(&mut self, routes: Vec<R>, deleted: HashMap<String, HashSet<String>>)\n    where\n        R: ResourceExt<DynamicType = ()>,\n        RouteBinding<HttpRoute>: TryFrom<R>,\n        <RouteBinding<HttpRoute> as TryFrom<R>>::Error: std::fmt::Display,\n    {\n        let _span = info_span!(\"httproute reset\").entered();\n\n        // Aggregate all of the updates by namespace so that we only reindex\n        // once per namespace.\n        type Ns = NsUpdate<GroupKindName, RouteBinding<HttpRoute>>;\n        let mut updates_by_ns = HashMap::<String, Ns>::default();\n        for route in routes.into_iter() {\n            let namespace = route.namespace().expect(\"HttpRoute must be namespaced\");\n            let name = route.name_unchecked();\n            let gkn = route.gkn();\n            let route_binding = match route.try_into() {\n                Ok(binding) => binding,\n                Err(error) => {\n                    tracing::info!(ns = %namespace, %name, %error, \"Ignoring HTTPRoute\");\n                    continue;\n                }\n            };\n            updates_by_ns\n                .entry(namespace)\n                .or_default()\n                .added\n                .push((gkn, route_binding));\n        }\n        for (ns, names) in deleted.into_iter() {\n            let removed = names\n                .into_iter()\n                .map(|name| GroupKindName {\n                    group: R::group(&()),\n                    kind: R::kind(&()),\n                    name: name.into(),\n                })\n                .collect();\n            updates_by_ns.entry(ns).or_default().removed = removed;\n        }\n\n        for (namespace, Ns { added, removed }) in updates_by_ns.into_iter() {\n            if added.is_empty() {\n                // If there are no live resources in the namespace, we do not\n                // want to create a default namespace instance, we just want to\n                // clear out all resources for the namespace (and then drop the\n                // whole namespace, if necessary).\n                self.ns_with_reindex(namespace, |ns| {\n                    ns.policy.http_routes.clear();\n                    true\n                });\n            } else {\n                // Otherwise, we take greater care to reindex only when the\n                // state actually changed. The vast majority of resets will see\n                // no actual data change.\n                self.ns_or_default_with_reindex(namespace, |ns| {\n                    let mut changed = !removed.is_empty();\n                    for gkn in removed.into_iter() {\n                        ns.policy.http_routes.remove(&gkn);\n                    }\n                    for (gkn, route_binding) in added.into_iter() {\n                        changed = ns.policy.update_http_route(gkn, route_binding) || changed;\n                    }\n                    changed\n                });\n            }\n        }\n    }\n\n    fn delete_http_route(&mut self, ns: String, gkn: GroupKindName) {\n        let _span = info_span!(\"delete httproute\", %ns, route = ?gkn).entered();\n        self.ns_with_reindex(ns, |ns| ns.policy.http_routes.remove(&gkn).is_some())\n    }\n\n    fn apply_grpc_route(&mut self, route: gateway::GRPCRoute) {\n        let ns = route.namespace().expect(\"GrpcRoute must have a namespace\");\n        let name = route.name_unchecked();\n        let gkn = route.gkn();\n        let _span = info_span!(\"apply grpcroute\", %ns, %name).entered();\n\n        let route_binding = match route.try_into() {\n            Ok(binding) => binding,\n            Err(error) => {\n                tracing::info!(%ns, %name, %error, \"Ignoring GrpcRoute\");\n                return;\n            }\n        };\n\n        self.ns_or_default_with_reindex(ns, |ns| ns.policy.update_grpc_route(gkn, route_binding))\n    }\n\n    #[tracing::instrument(skip_all)]\n    fn reset_grpc_route(\n        &mut self,\n        routes: Vec<gateway::GRPCRoute>,\n        deleted: HashMap<String, HashSet<String>>,\n    ) {\n        // Aggregate all of the updates by namespace so that we only reindex\n        // once per namespace.\n        type Ns = NsUpdate<GroupKindName, RouteBinding<GrpcRoute>>;\n        let mut updates_by_ns = HashMap::<String, Ns>::default();\n        for route in routes.into_iter() {\n            let namespace = route.namespace().expect(\"GrpcRoute must be namespaced\");\n            let name = route.name_unchecked();\n            let gkn = route.gkn();\n            let route_binding = match route.try_into() {\n                Ok(binding) => binding,\n                Err(error) => {\n                    tracing::info!(ns = %namespace, %name, %error, \"Ignoring GrpcRoute\");\n                    continue;\n                }\n            };\n            updates_by_ns\n                .entry(namespace)\n                .or_default()\n                .added\n                .push((gkn, route_binding));\n        }\n        for (ns, names) in deleted.into_iter() {\n            let removed = names\n                .into_iter()\n                .map(|name| name.gkn::<gateway::GRPCRoute>())\n                .collect();\n            updates_by_ns.entry(ns).or_default().removed = removed;\n        }\n\n        for (namespace, Ns { added, removed }) in updates_by_ns.into_iter() {\n            if added.is_empty() {\n                // If there are no live resources in the namespace, we do not\n                // want to create a default namespace instance, we just want to\n                // clear out all resources for the namespace (and then drop the\n                // whole namespace, if necessary).\n                self.ns_with_reindex(namespace, |ns| {\n                    ns.policy.grpc_routes.clear();\n                    true\n                });\n            } else {\n                // Otherwise, we take greater care to reindex only when the\n                // state actually changed. The vast majority of resets will see\n                // no actual data change.\n                self.ns_or_default_with_reindex(namespace, |ns| {\n                    let mut changed = !removed.is_empty();\n                    for gkn in removed.into_iter() {\n                        ns.policy.grpc_routes.remove(&gkn);\n                    }\n                    for (gkn, route_binding) in added.into_iter() {\n                        changed = ns.policy.update_grpc_route(gkn, route_binding) || changed;\n                    }\n                    changed\n                });\n            }\n        }\n    }\n\n    fn delete_grpc_route(&mut self, ns: String, gkn: GroupKindName) {\n        let _span = info_span!(\"delete grpcroute\", %ns, route = ?gkn).entered();\n        self.ns_with_reindex(ns, |ns| ns.policy.grpc_routes.remove(&gkn).is_some())\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<k8s::Pod> for Index {\n    fn apply(&mut self, pod: k8s::Pod) {\n        let namespace = pod.namespace().unwrap();\n        let name = pod.name_unchecked();\n        let _span = info_span!(\"apply\", ns = %namespace, %name).entered();\n\n        let port_names = pod\n            .spec\n            .as_ref()\n            .map(workload::pod_tcp_ports_by_name)\n            .unwrap_or_default();\n        let probes = pod\n            .spec\n            .as_ref()\n            .map(workload::pod_http_probes)\n            .unwrap_or_default();\n\n        let meta = workload::Meta::from_metadata(pod.metadata);\n\n        // Add or update the pod. If the pod was not already present in the\n        // index with the same metadata, index it against the policy resources,\n        // updating its watches.\n        let ns = self.namespaces.get_or_default(namespace);\n        match ns.pods.update(name, meta, port_names, probes) {\n            Ok(None) => {}\n            Ok(Some(pod)) => pod.reindex_servers(&ns.policy, &self.authentications),\n            Err(error) => {\n                tracing::warn!(%error, \"Illegal pod update\");\n            }\n        }\n    }\n\n    fn delete(&mut self, ns: String, name: String) {\n        tracing::debug!(%ns, %name, \"delete\");\n        if let Entry::Occupied(mut ns) = self.namespaces.by_ns.entry(ns) {\n            // Once the pod is removed, there's nothing else to update. Any open\n            // watches will complete.  No other parts of the index need to be\n            // updated.\n            if ns.get_mut().pods.by_name.remove(&name).is_some() && ns.get().is_empty() {\n                tracing::debug!(namespace = ns.key(), \"Removing empty namespace index\");\n                ns.remove();\n            }\n        }\n    }\n\n    // Since apply only reindexes a single pod at a time, there's no need to\n    // handle resets specially.\n}\n\nimpl kubert::index::IndexNamespacedResource<k8s::external_workload::ExternalWorkload> for Index {\n    fn apply(&mut self, ext_workload: k8s::external_workload::ExternalWorkload) {\n        let ns = ext_workload.namespace().unwrap();\n        let name = ext_workload.name_unchecked();\n        let _span = info_span!(\"apply\", %ns, %name).entered();\n\n        // Extract ports and settings.\n        // Note: external workloads do not have any probe paths to synthesise\n        // default policies for.\n        let port_names = workload::external_tcp_ports_by_name(&ext_workload.spec);\n        let meta = workload::Meta::from_metadata(ext_workload.metadata);\n\n        // Add or update the workload.\n        //\n        // If the resource is present in the index, but its metadata has\n        // changed, then it means the watches need to get an update.\n        let ns = self.namespaces.get_or_default(ns);\n        match ns.external_workloads.update(name, meta, port_names) {\n            // No update\n            Ok(None) => {}\n            // Update, so re-index\n            Ok(Some(workload)) => workload.reindex_servers(&ns.policy, &self.authentications),\n            Err(error) => {\n                tracing::warn!(%error, \"Illegal external workload update\");\n            }\n        }\n    }\n\n    fn delete(&mut self, ns: String, name: String) {\n        tracing::debug!(%ns, %name, \"delete\");\n        if let Entry::Occupied(mut ns) = self.namespaces.by_ns.entry(ns) {\n            // Once the external workload is removed, there's nothing else to\n            // update. Any open watches will complete. No other parts of the\n            // index need to be updated.\n            if ns\n                .get_mut()\n                .external_workloads\n                .by_name\n                .remove(&name)\n                .is_some()\n                && ns.get().is_empty()\n            {\n                ns.remove();\n            }\n        }\n    }\n\n    // Since apply only reindexes a single external workload at a time, there's no need to\n    // handle resets specially.\n}\n\nimpl kubert::index::IndexNamespacedResource<k8s::policy::Server> for Index {\n    fn apply(&mut self, srv: k8s::policy::Server) {\n        let ns = srv.namespace().expect(\"server must be namespaced\");\n        let name = srv.name_unchecked();\n        let _span = info_span!(\"apply\", %ns, %name).entered();\n\n        let server = server::Server::from_resource(srv, &self.cluster_info);\n        self.ns_or_default_with_reindex(ns, |ns| ns.policy.update_server(name, server))\n    }\n\n    fn delete(&mut self, ns: String, name: String) {\n        let _span = info_span!(\"delete\", %ns, %name).entered();\n        self.ns_with_reindex(ns, |ns| ns.policy.servers.remove(&name).is_some())\n    }\n\n    fn reset(&mut self, srvs: Vec<k8s::policy::Server>, deleted: HashMap<String, HashSet<String>>) {\n        let _span = info_span!(\"reset\").entered();\n\n        // Aggregate all of the updates by namespace so that we only reindex\n        // once per namespace.\n        type Ns = NsUpdate<String, server::Server>;\n        let mut updates_by_ns = HashMap::<String, Ns>::default();\n        for srv in srvs.into_iter() {\n            let namespace = srv.namespace().expect(\"server must be namespaced\");\n            let name = srv.name_unchecked();\n            let server = server::Server::from_resource(srv, &self.cluster_info);\n            updates_by_ns\n                .entry(namespace)\n                .or_default()\n                .added\n                .push((name, server));\n        }\n        for (ns, names) in deleted.into_iter() {\n            updates_by_ns.entry(ns).or_default().removed = names;\n        }\n\n        for (namespace, Ns { added, removed }) in updates_by_ns.into_iter() {\n            if added.is_empty() {\n                // If there are no live resources in the namespace, we do not\n                // want to create a default namespace instance, we just want to\n                // clear out all resources for the namespace (and then drop the\n                // whole namespace, if necessary).\n                self.ns_with_reindex(namespace, |ns| {\n                    ns.policy.servers.clear();\n                    true\n                });\n            } else {\n                // Otherwise, we take greater care to reindex only when the\n                // state actually changed. The vast majority of resets will see\n                // no actual data change.\n                self.ns_or_default_with_reindex(namespace, |ns| {\n                    let mut changed = !removed.is_empty();\n                    for name in removed.into_iter() {\n                        ns.policy.servers.remove(&name);\n                    }\n                    for (name, server) in added.into_iter() {\n                        changed = ns.policy.update_server(name, server) || changed;\n                    }\n                    changed\n                });\n            }\n        }\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<k8s::policy::ServerAuthorization> for Index {\n    fn apply(&mut self, saz: k8s::policy::ServerAuthorization) {\n        let ns = saz.namespace().unwrap();\n        let name = saz.name_unchecked();\n        let _span = info_span!(\"apply\", %ns, %name).entered();\n\n        match server_authorization::ServerAuthz::from_resource(saz, &self.cluster_info) {\n            Ok(meta) => self.ns_or_default_with_reindex(ns, move |ns| {\n                ns.policy.update_server_authz(name, meta)\n            }),\n            Err(error) => tracing::warn!(%error, \"Illegal server authorization update\"),\n        }\n    }\n\n    fn delete(&mut self, ns: String, name: String) {\n        let _span = info_span!(\"delete\", %ns, %name).entered();\n        self.ns_with_reindex(ns, |ns| {\n            ns.policy.server_authorizations.remove(&name).is_some()\n        })\n    }\n\n    fn reset(\n        &mut self,\n        sazs: Vec<k8s::policy::ServerAuthorization>,\n        deleted: HashMap<String, HashSet<String>>,\n    ) {\n        let _span = info_span!(\"reset\");\n\n        // Aggregate all of the updates by namespace so that we only reindex\n        // once per namespace.\n        type Ns = NsUpdate<String, server_authorization::ServerAuthz>;\n        let mut updates_by_ns = HashMap::<String, Ns>::default();\n        for saz in sazs.into_iter() {\n            let namespace = saz\n                .namespace()\n                .expect(\"serverauthorization must be namespaced\");\n            let name = saz.name_unchecked();\n            match server_authorization::ServerAuthz::from_resource(saz, &self.cluster_info) {\n                Ok(saz) => updates_by_ns\n                    .entry(namespace)\n                    .or_default()\n                    .added\n                    .push((name, saz)),\n                Err(error) => {\n                    tracing::warn!(ns = %namespace, %name, %error, \"Illegal server authorization update\")\n                }\n            }\n        }\n        for (ns, names) in deleted.into_iter() {\n            updates_by_ns.entry(ns).or_default().removed = names;\n        }\n\n        for (namespace, Ns { added, removed }) in updates_by_ns.into_iter() {\n            if added.is_empty() {\n                // If there are no live resources in the namespace, we do not\n                // want to create a default namespace instance, we just want to\n                // clear out all resources for the namespace (and then drop the\n                // whole namespace, if necessary).\n                self.ns_with_reindex(namespace, |ns| {\n                    ns.policy.server_authorizations.clear();\n                    true\n                });\n            } else {\n                // Otherwise, we take greater care to reindex only when the\n                // state actually changed. The vast majority of resets will see\n                // no actual data change.\n                self.ns_or_default_with_reindex(namespace, |ns| {\n                    let mut changed = !removed.is_empty();\n                    for name in removed.into_iter() {\n                        ns.policy.server_authorizations.remove(&name);\n                    }\n                    for (name, saz) in added.into_iter() {\n                        changed = ns.policy.update_server_authz(name, saz) || changed;\n                    }\n                    changed\n                });\n            }\n        }\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<k8s::policy::AuthorizationPolicy> for Index {\n    fn apply(&mut self, policy: k8s::policy::AuthorizationPolicy) {\n        let ns = policy.namespace().unwrap();\n        let name = policy.name_unchecked();\n        let _span = info_span!(\"apply\", %ns, saz = %name).entered();\n\n        let spec = match authorization_policy::Spec::try_from(policy.spec) {\n            Ok(spec) => spec,\n            Err(error) => {\n                tracing::warn!(%error, \"Invalid authorization policy\");\n                return;\n            }\n        };\n\n        self.ns_or_default_with_reindex(ns, |ns| ns.policy.update_authz_policy(name, spec))\n    }\n\n    fn delete(&mut self, ns: String, ap: String) {\n        let _span = info_span!(\"delete\", %ns, %ap).entered();\n        tracing::trace!(name = %ap, \"Delete\");\n        self.ns_with_reindex(ns, |ns| {\n            ns.policy.authorization_policies.remove(&ap).is_some()\n        })\n    }\n\n    fn reset(\n        &mut self,\n        policies: Vec<k8s::policy::AuthorizationPolicy>,\n        deleted: HashMap<String, HashSet<String>>,\n    ) {\n        let _span = info_span!(\"reset\");\n\n        tracing::trace!(?deleted, ?policies, \"Reset\");\n        // Aggregate all of the updates by namespace so that we only reindex\n        // once per namespace.\n        type Ns = NsUpdate<String, authorization_policy::Spec>;\n        let mut updates_by_ns = HashMap::<String, Ns>::default();\n        for policy in policies.into_iter() {\n            let namespace = policy\n                .namespace()\n                .expect(\"authorizationpolicy must be namespaced\");\n            let name = policy.name_unchecked();\n            match authorization_policy::Spec::try_from(policy.spec) {\n                Ok(spec) => updates_by_ns\n                    .entry(namespace)\n                    .or_default()\n                    .added\n                    .push((name, spec)),\n                Err(error) => {\n                    tracing::warn!(ns = %namespace, %name, %error, \"Illegal server authorization update\")\n                }\n            }\n        }\n        for (ns, names) in deleted.into_iter() {\n            updates_by_ns.entry(ns).or_default().removed = names;\n        }\n\n        for (namespace, Ns { added, removed }) in updates_by_ns.into_iter() {\n            if added.is_empty() {\n                // If there are no live resources in the namespace, we do not\n                // want to create a default namespace instance, we just want to\n                // clear out all resources for the namespace (and then drop the\n                // whole namespace, if necessary).\n                self.ns_with_reindex(namespace, |ns| {\n                    ns.policy.authorization_policies.clear();\n                    true\n                });\n            } else {\n                // Otherwise, we take greater care to reindex only when the\n                // state actually changed. The vast majority of resets will see\n                // no actual data change.\n                self.ns_or_default_with_reindex(namespace, |ns| {\n                    let mut changed = !removed.is_empty();\n                    for name in removed.into_iter() {\n                        ns.policy.authorization_policies.remove(&name);\n                    }\n                    for (name, spec) in added.into_iter() {\n                        changed = ns.policy.update_authz_policy(name, spec) || changed;\n                    }\n                    changed\n                });\n            }\n        }\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<k8s::policy::MeshTLSAuthentication> for Index {\n    fn apply(&mut self, authn: k8s::policy::MeshTLSAuthentication) {\n        let ns = authn\n            .namespace()\n            .expect(\"MeshTLSAuthentication must have a namespace\");\n        let name = authn.name_unchecked();\n        let _span = info_span!(\"apply\", %ns, %name).entered();\n\n        let spec = match meshtls_authentication::Spec::try_from_resource(authn, &self.cluster_info)\n        {\n            Ok(spec) => spec,\n            Err(error) => {\n                tracing::warn!(%error, \"Invalid MeshTLSAuthentication\");\n                return;\n            }\n        };\n\n        if self.authentications.update_meshtls(ns, name, spec) {\n            self.reindex_all();\n        }\n    }\n\n    fn delete(&mut self, ns: String, name: String) {\n        let _span = info_span!(\"delete\", %ns, %name).entered();\n\n        if let Entry::Occupied(mut ns) = self.authentications.by_ns.entry(ns) {\n            tracing::debug!(\"Deleting MeshTLSAuthentication\");\n            ns.get_mut().network.remove(&name);\n            if ns.get().is_empty() {\n                ns.remove();\n            }\n            self.reindex_all();\n        } else {\n            tracing::warn!(\"Namespace already deleted!\");\n        }\n    }\n\n    fn reset(\n        &mut self,\n        authns: Vec<k8s::policy::MeshTLSAuthentication>,\n        deleted: HashMap<String, HashSet<String>>,\n    ) {\n        let _span = info_span!(\"reset\");\n\n        let mut changed = false;\n\n        for authn in authns.into_iter() {\n            let namespace = authn\n                .namespace()\n                .expect(\"meshtlsauthentication must be namespaced\");\n            let name = authn.name_unchecked();\n            let spec = match meshtls_authentication::Spec::try_from_resource(\n                authn,\n                &self.cluster_info,\n            ) {\n                Ok(spec) => spec,\n                Err(error) => {\n                    tracing::warn!(ns = %namespace, %name, %error, \"Invalid MeshTLSAuthentication\");\n                    continue;\n                }\n            };\n            changed = self.authentications.update_meshtls(namespace, name, spec) || changed;\n        }\n        for (namespace, names) in deleted.into_iter() {\n            if let Entry::Occupied(mut ns) = self.authentications.by_ns.entry(namespace) {\n                for name in names.into_iter() {\n                    ns.get_mut().meshtls.remove(&name);\n                }\n                if ns.get().is_empty() {\n                    ns.remove();\n                }\n            }\n        }\n\n        if changed {\n            self.reindex_all();\n        }\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<k8s::policy::NetworkAuthentication> for Index {\n    fn apply(&mut self, authn: k8s::policy::NetworkAuthentication) {\n        let ns = authn.namespace().unwrap();\n        let name = authn.name_unchecked();\n        let _span = info_span!(\"apply\", %ns, %name).entered();\n\n        let spec = match network_authentication::Spec::try_from(authn.spec) {\n            Ok(spec) => spec,\n            Err(error) => {\n                tracing::warn!(%error, \"Invalid NetworkAuthentication\");\n                return;\n            }\n        };\n\n        if self.authentications.update_network(ns, name, spec) {\n            self.reindex_all();\n        }\n    }\n\n    fn delete(&mut self, ns: String, name: String) {\n        let _span = info_span!(\"delete\", %ns, %name).entered();\n\n        if let Entry::Occupied(mut ns) = self.authentications.by_ns.entry(ns) {\n            tracing::debug!(\"Deleting MeshTLSAuthentication\");\n\n            ns.get_mut().network.remove(&name);\n            if ns.get().is_empty() {\n                ns.remove();\n            }\n            self.reindex_all();\n        } else {\n            tracing::warn!(\"Namespace already deleted!\");\n        }\n    }\n\n    fn reset(\n        &mut self,\n        authns: Vec<k8s::policy::NetworkAuthentication>,\n        deleted: HashMap<String, HashSet<String>>,\n    ) {\n        let _span = info_span!(\"reset\");\n\n        let mut changed = false;\n\n        for authn in authns.into_iter() {\n            let namespace = authn\n                .namespace()\n                .expect(\"meshtlsauthentication must be namespaced\");\n            let name = authn.name_unchecked();\n            let spec = match network_authentication::Spec::try_from(authn.spec) {\n                Ok(spec) => spec,\n                Err(error) => {\n                    tracing::warn!(ns = %namespace, %name, %error, \"Invalid NetworkAuthentication\");\n                    return;\n                }\n            };\n            changed = self.authentications.update_network(namespace, name, spec) || changed;\n        }\n        for (namespace, names) in deleted.into_iter() {\n            if let Entry::Occupied(mut ns) = self.authentications.by_ns.entry(namespace) {\n                for name in names.into_iter() {\n                    ns.get_mut().meshtls.remove(&name);\n                }\n                if ns.get().is_empty() {\n                    ns.remove();\n                }\n            }\n        }\n\n        if changed {\n            self.reindex_all();\n        }\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<k8s::policy::HttpLocalRateLimitPolicy> for Index {\n    fn apply(&mut self, policy: k8s::policy::HttpLocalRateLimitPolicy) {\n        let ns = policy.namespace().unwrap();\n        let name = policy.name_unchecked();\n        let _span = info_span!(\"apply\", %ns, saz = %name).entered();\n\n        let spec = match ratelimit_policy::Spec::try_from(policy) {\n            Ok(spec) => spec,\n            Err(error) => {\n                tracing::warn!(%error, \"Invalid rate limit policy\");\n                return;\n            }\n        };\n\n        self.ns_or_default_with_reindex(ns, |ns| ns.policy.update_ratelimit_policy(name, spec))\n    }\n\n    fn delete(&mut self, ns: String, ap: String) {\n        let _span = info_span!(\"delete\", %ns, %ap).entered();\n        tracing::trace!(name = %ap, \"Delete\");\n        self.ns_with_reindex(ns, |ns| ns.policy.ratelimit_policies.remove(&ap).is_some())\n    }\n\n    fn reset(\n        &mut self,\n        policies: Vec<k8s::policy::HttpLocalRateLimitPolicy>,\n        deleted: HashMap<String, HashSet<String>>,\n    ) {\n        let _span = info_span!(\"reset\");\n\n        tracing::trace!(?deleted, ?policies, \"Reset\");\n        // Aggregate all of the updates by namespace so that we only reindex\n        // once per namespace.\n        type Ns = NsUpdate<String, ratelimit_policy::Spec>;\n        let mut updates_by_ns = HashMap::<String, Ns>::default();\n        for policy in policies.into_iter() {\n            let namespace = policy\n                .namespace()\n                .expect(\"ratelimitolicy must be namespaced\");\n            let name = policy.name_unchecked();\n            match ratelimit_policy::Spec::try_from(policy) {\n                Ok(spec) => updates_by_ns\n                    .entry(namespace)\n                    .or_default()\n                    .added\n                    .push((name, spec)),\n                Err(error) => {\n                    tracing::warn!(ns = %namespace, %name, %error, \"Illegal server ratelimit update\")\n                }\n            }\n        }\n        for (ns, names) in deleted.into_iter() {\n            updates_by_ns.entry(ns).or_default().removed = names;\n        }\n\n        for (namespace, Ns { added, removed }) in updates_by_ns.into_iter() {\n            if added.is_empty() {\n                // If there are no live resources in the namespace, we do not\n                // want to create a default namespace instance, we just want to\n                // clear out all resources for the namespace (and then drop the\n                // whole namespace, if necessary).\n                self.ns_with_reindex(namespace, |ns| {\n                    ns.policy.ratelimit_policies.clear();\n                    true\n                });\n            } else {\n                // Otherwise, we take greater care to reindex only when the\n                // state actually changed. The vast majority of resets will see\n                // no actual data change.\n                self.ns_or_default_with_reindex(namespace, |ns| {\n                    let mut changed = !removed.is_empty();\n                    for name in removed.into_iter() {\n                        ns.policy.ratelimit_policies.remove(&name);\n                    }\n                    for (name, spec) in added.into_iter() {\n                        changed = ns.policy.update_ratelimit_policy(name, spec) || changed;\n                    }\n                    changed\n                });\n            }\n        }\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<k8s::policy::HttpRoute> for Index {\n    fn apply(&mut self, route: k8s::policy::HttpRoute) {\n        self.apply_http_route(route)\n    }\n\n    fn delete(&mut self, ns: String, name: String) {\n        let gkn = name.gkn::<k8s::policy::HttpRoute>();\n        self.delete_http_route(ns, gkn)\n    }\n\n    fn reset(\n        &mut self,\n        routes: Vec<k8s::policy::HttpRoute>,\n        deleted: HashMap<String, HashSet<String>>,\n    ) {\n        self.reset_http_route(routes, deleted)\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<gateway::HTTPRoute> for Index {\n    fn apply(&mut self, route: gateway::HTTPRoute) {\n        self.apply_http_route(route)\n    }\n\n    fn delete(&mut self, ns: String, name: String) {\n        let gkn = name.gkn::<gateway::HTTPRoute>();\n        self.delete_http_route(ns, gkn)\n    }\n\n    fn reset(\n        &mut self,\n        routes: Vec<gateway::HTTPRoute>,\n        deleted: HashMap<String, HashSet<String>>,\n    ) {\n        self.reset_http_route(routes, deleted)\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<gateway::GRPCRoute> for Index {\n    fn apply(&mut self, route: gateway::GRPCRoute) {\n        self.apply_grpc_route(route)\n    }\n\n    fn delete(&mut self, ns: String, name: String) {\n        let gkn = name.gkn::<gateway::GRPCRoute>();\n        self.delete_grpc_route(ns, gkn)\n    }\n\n    fn reset(\n        &mut self,\n        routes: Vec<gateway::GRPCRoute>,\n        deleted: HashMap<String, HashSet<String>>,\n    ) {\n        self.reset_grpc_route(routes, deleted)\n    }\n}\n\n// === impl NemspaceIndex ===\n\nimpl NamespaceIndex {\n    fn get_or_default(&mut self, ns: String) -> &mut Namespace {\n        self.by_ns\n            .entry(ns.clone())\n            .or_insert_with(|| Namespace::new(ns, self.cluster_info.clone()))\n    }\n\n    /// Gets the given namespace and, if it exists, passes it to the given\n    /// function. If the function returns true, all pods in the namespace are\n    /// reindexed; or, if the function returns false and the namespace is empty,\n    /// it is removed from the index.\n    fn get_with_reindex(\n        &mut self,\n        namespace: String,\n        authns: &AuthenticationNsIndex,\n        f: impl FnOnce(&mut Namespace) -> bool,\n    ) {\n        if let Entry::Occupied(mut ns) = self.by_ns.entry(namespace) {\n            if f(ns.get_mut()) {\n                if ns.get().is_empty() {\n                    tracing::debug!(namespace = ns.key(), \"Removing empty namespace index\");\n                    ns.remove();\n                } else {\n                    ns.get_mut().reindex(authns);\n                }\n            }\n        }\n    }\n\n    /// Gets the given namespace (or creates it) and passes it to the given\n    /// function. If the function returns true, all pods in the namespace are\n    /// reindexed.\n    fn get_or_default_with_reindex(\n        &mut self,\n        namespace: String,\n        authns: &AuthenticationNsIndex,\n        f: impl FnOnce(&mut Namespace) -> bool,\n    ) {\n        let ns = self.get_or_default(namespace);\n        if f(ns) {\n            ns.reindex(authns);\n        }\n    }\n}\n\n// === impl Namespace ===\n\nimpl Namespace {\n    fn new(namespace: String, cluster_info: Arc<ClusterInfo>) -> Self {\n        Namespace {\n            pods: PodIndex {\n                namespace: namespace.clone(),\n                by_name: HashMap::default(),\n            },\n            external_workloads: ExternalWorkloadIndex {\n                namespace: namespace.clone(),\n                by_name: HashMap::default(),\n            },\n            policy: PolicyIndex {\n                namespace,\n                cluster_info,\n                servers: HashMap::default(),\n                server_authorizations: HashMap::default(),\n                authorization_policies: HashMap::default(),\n                ratelimit_policies: HashMap::default(),\n                http_routes: HashMap::default(),\n                grpc_routes: HashMap::default(),\n            },\n        }\n    }\n\n    /// Returns true if the index does not include any resources.\n    #[inline]\n    fn is_empty(&self) -> bool {\n        self.pods.is_empty() && self.policy.is_empty() && self.external_workloads.is_empty()\n    }\n\n    #[inline]\n    fn reindex(&mut self, authns: &AuthenticationNsIndex) {\n        self.pods.reindex(&self.policy, authns);\n        self.external_workloads.reindex(&self.policy, authns);\n    }\n}\n\n// === impl PodIndex ===\n\nimpl PodIndex {\n    #[inline]\n    fn is_empty(&self) -> bool {\n        self.by_name.is_empty()\n    }\n\n    fn update(\n        &mut self,\n        name: String,\n        meta: workload::Meta,\n        port_names: HashMap<String, PortSet>,\n        probes: PortMap<BTreeSet<String>>,\n    ) -> Result<Option<&mut Pod>> {\n        let pod = match self.by_name.entry(name.clone()) {\n            Entry::Vacant(entry) => entry.insert(Pod {\n                meta,\n                port_names,\n                port_servers: PortMap::default(),\n                probes,\n            }),\n\n            Entry::Occupied(entry) => {\n                let pod = entry.into_mut();\n\n                // Pod labels and annotations may change at runtime, but the\n                // port list may not\n                if pod.port_names != port_names {\n                    bail!(\"pod {name} port names must not change\");\n                }\n\n                // If there aren't meaningful changes, then don't bother doing\n                // any more work.\n                if pod.meta == meta {\n                    tracing::debug!(pod = %name, \"No changes\");\n                    return Ok(None);\n                }\n                tracing::debug!(pod = %name, \"Updating\");\n                pod.meta = meta;\n                pod\n            }\n        };\n        Ok(Some(pod))\n    }\n\n    fn reindex(&mut self, policy: &PolicyIndex, authns: &AuthenticationNsIndex) {\n        let _span = info_span!(\"reindex\", ns = %self.namespace).entered();\n        for (name, pod) in self.by_name.iter_mut() {\n            let _span = info_span!(\"pod\", pod = %name).entered();\n            pod.reindex_servers(policy, authns);\n        }\n    }\n}\n\n// === impl Pod ===\n\nimpl Pod {\n    /// Determines the policies for ports on this pod.\n    fn reindex_servers(&mut self, policy: &PolicyIndex, authentications: &AuthenticationNsIndex) {\n        // Keep track of the ports that are already known in the pod so that, after applying server\n        // matches, we can ensure remaining ports are set to the default policy.\n        let mut unmatched_ports = self.port_servers.keys().copied().collect::<PortSet>();\n\n        // Keep track of which ports have been matched to servers to that we can detect when\n        // multiple servers match a single port.\n        //\n        // We start with capacity for the known ports on the pod; but this can grow if servers\n        // select additional ports.\n        let mut matched_ports = PortMap::with_capacity_and_hasher(\n            unmatched_ports.len(),\n            std::hash::BuildHasherDefault::<PortHasher>::default(),\n        );\n\n        // Sort by creation and then name, similarly to HTTPRoutes, to enforce\n        // precedence.\n        let mut servers = policy.servers.iter().collect::<Vec<_>>();\n        servers.sort_by(|(aname, asrv), (bname, bsrv)| {\n            asrv.created_at\n                .cmp(&bsrv.created_at)\n                .then_with(|| aname.cmp(bname))\n        });\n        for (srvname, server) in servers {\n            if let Selector::Pod(pod_selector) = &server.selector {\n                if pod_selector.matches(&self.meta.labels) {\n                    for port in self.select_ports(&server.port_ref).into_iter() {\n                        // If the port is already matched to a server, then log a warning and skip\n                        // updating it so it doesn't flap between servers.\n                        if let Some(prior) = matched_ports.get(&port) {\n                            tracing::warn!(\n                                port = %port,\n                                server = %prior,\n                                conflict = %srvname,\n                                \"Port already matched by another server; skipping\"\n                            );\n                            continue;\n                        }\n\n                        let s = policy.inbound_server(\n                            srvname.clone(),\n                            server,\n                            authentications,\n                            self.probes\n                                .get(&port)\n                                .into_iter()\n                                .flatten()\n                                .map(|p| p.as_str()),\n                        );\n                        self.update_server(port, srvname, s);\n\n                        matched_ports.insert(port, srvname.clone());\n                        unmatched_ports.remove(&port);\n                    }\n                }\n            }\n        }\n\n        // Reset all remaining ports to the default policy.\n        for port in unmatched_ports.into_iter() {\n            self.set_default_server(port, &policy.cluster_info);\n        }\n    }\n\n    /// Updates a pod-port to use the given named server.\n    ///\n    /// The name is used explicity (and not derived from the `server` itself) to\n    /// ensure that we're not handling a default server.\n    fn update_server(&mut self, port: NonZeroU16, name: &str, server: InboundServer) {\n        match self.port_servers.entry(port) {\n            Entry::Vacant(entry) => {\n                tracing::trace!(port = %port, server = %name, \"Creating server\");\n                let (watch, _) = watch::channel(server);\n                entry.insert(WorkloadPortServer {\n                    name: Some(name.to_string()),\n                    watch,\n                });\n            }\n\n            Entry::Occupied(mut entry) => {\n                let ps = entry.get_mut();\n\n                ps.watch.send_if_modified(|current| {\n                    if ps.name.as_deref() == Some(name) && *current == server {\n                        tracing::trace!(port = %port, server = %name, \"Skipped redundant server update\");\n                        tracing::trace!(?server);\n                        return false;\n                    }\n\n                    // If the port's server previously matched a different server,\n                    // this can either mean that multiple servers currently match\n                    // the pod:port, or that we're in the middle of an update. We\n                    // make the opportunistic choice to assume the cluster is\n                    // configured coherently so we take the update. The admission\n                    // controller should prevent conflicts.\n                    tracing::trace!(port = %port, server = %name, \"Updating server\");\n                    if ps.name.as_deref() != Some(name) {\n                        ps.name = Some(name.to_string());\n                    }\n\n                    *current = server;\n                    true\n                });\n            }\n        }\n\n        tracing::debug!(port = %port, server = %name, \"Updated server\");\n    }\n\n    /// Updates a pod-port to use the given named server.\n    fn set_default_server(&mut self, port: NonZeroU16, config: &ClusterInfo) {\n        let server = PolicyIndex::default_inbound_server(\n            port,\n            &self.meta.settings,\n            self.probes\n                .get(&port)\n                .into_iter()\n                .flatten()\n                .map(|p| p.as_str()),\n            config,\n        );\n        match self.port_servers.entry(port) {\n            Entry::Vacant(entry) => {\n                tracing::debug!(%port, server = %config.default_policy, \"Creating default server\");\n                let (watch, _) = watch::channel(server);\n                entry.insert(WorkloadPortServer { name: None, watch });\n            }\n\n            Entry::Occupied(mut entry) => {\n                let ps = entry.get_mut();\n                ps.watch.send_if_modified(|current| {\n                    // Avoid sending redundant updates.\n                    if *current == server {\n                        tracing::trace!(%port, server = %config.default_policy, \"Default server already set\");\n                        return false;\n                    }\n\n                    tracing::debug!(%port, server = %config.default_policy, \"Setting default server\");\n                    ps.name = None;\n                    *current = server;\n                    true\n                });\n            }\n        }\n    }\n\n    /// Enumerates ports.\n    ///\n    /// A named port may refer to an arbitrary number of port numbers.\n    fn select_ports(&mut self, port_ref: &Port) -> Vec<NonZeroU16> {\n        match port_ref {\n            Port::Number(p) => Some(*p).into_iter().collect(),\n            Port::Name(name) => self\n                .port_names\n                .get(name)\n                .into_iter()\n                .flatten()\n                .cloned()\n                .collect(),\n        }\n    }\n\n    fn port_server_or_default(\n        &mut self,\n        port: NonZeroU16,\n        config: &ClusterInfo,\n    ) -> &mut WorkloadPortServer {\n        match self.port_servers.entry(port) {\n            Entry::Occupied(entry) => entry.into_mut(),\n            Entry::Vacant(entry) => {\n                let (watch, _) = watch::channel(PolicyIndex::default_inbound_server(\n                    port,\n                    &self.meta.settings,\n                    self.probes\n                        .get(&port)\n                        .into_iter()\n                        .flatten()\n                        .map(|p| p.as_str()),\n                    config,\n                ));\n                entry.insert(WorkloadPortServer { name: None, watch })\n            }\n        }\n    }\n}\n\n// === impl ExternalWorkloadIndex ===\n\nimpl ExternalWorkloadIndex {\n    #[inline]\n    fn is_empty(&self) -> bool {\n        self.by_name.is_empty()\n    }\n\n    /// Indexes an external workload resource and computes any changes in the\n    /// workload's state.\n    ///\n    /// If the workload is indexed for the first time, or some of its settings\n    /// have changed (e.g. metadata) then indexing will trigger a namespace-wide\n    /// server re-index to push new state to the clients.\n    ///\n    /// Otherwise, if nothing has changed, do not trigger re-indexing.\n    fn update(\n        &mut self,\n        name: String,\n        meta: workload::Meta,\n        port_names: HashMap<String, NonZeroU16>,\n    ) -> Result<Option<&mut ExternalWorkload>> {\n        let workload = match self.by_name.entry(name.clone()) {\n            Entry::Vacant(entry) => entry.insert(ExternalWorkload {\n                meta,\n                port_names,\n                port_servers: PortMap::default(),\n            }),\n            Entry::Occupied(entry) => {\n                let workload = entry.into_mut();\n\n                if workload.meta == meta && workload.port_names == port_names {\n                    tracing::debug!(external_workload = %name, \"No changes\");\n                    return Ok(None);\n                }\n\n                if workload.meta != meta {\n                    tracing::trace!(external_workload = %name, \"Updating workload's metadata\");\n                    workload.meta = meta;\n                }\n\n                if workload.port_names != port_names {\n                    tracing::trace!(external_workload = %name, \"Updating workload's ports\");\n                    workload.port_names = port_names;\n                }\n\n                tracing::debug!(external_workload = %name, \"Updating\");\n                workload\n            }\n        };\n        Ok(Some(workload))\n    }\n\n    /// For each external workload in a namespace, re-compute the server and\n    /// authorization policy states to determine if new changes need to be\n    /// pushed to clients.\n    fn reindex(&mut self, policy: &PolicyIndex, authns: &AuthenticationNsIndex) {\n        let _span = info_span!(\"reindex\", ns = %self.namespace).entered();\n        for (name, ext_workload) in self.by_name.iter_mut() {\n            let _span = info_span!(\"external_workload\", external_workload = %name).entered();\n            ext_workload.reindex_servers(policy, authns);\n        }\n    }\n}\n\nimpl ExternalWorkload {\n    /// Determines the policies for ports on this workload\n    fn reindex_servers(&mut self, policy: &PolicyIndex, authentications: &AuthenticationNsIndex) {\n        // Keep track of ports that are already known so that they may receive\n        // default policies if they are still not selected by a server.\n        //\n        // TODO (matei): we could eagerly fill this out if we require users to\n        // always document a workload's ports. i.e. right now a non-named port\n        // will be lazily discovered only when traffic is sent, we could eagerly\n        // set policies for it (but I feel like for now, this should be similar in\n        // behaviour to a pod)\n        //\n        let mut unmatched_ports = self.port_servers.keys().copied().collect::<PortSet>();\n\n        // Keep track of which ports have been matched with servers so that we\n        // can detect when more than one server matches a single port.\n        let mut matched_ports = PortMap::with_capacity_and_hasher(\n            unmatched_ports.len(),\n            std::hash::BuildHasherDefault::<PortHasher>::default(),\n        );\n\n        // Sort by creation and then name, similarly to HTTPRoutes, to enforce\n        // precedence.\n        let mut servers = policy.servers.iter().collect::<Vec<_>>();\n        servers.sort_by(|(aname, asrv), (bname, bsrv)| {\n            asrv.created_at\n                .cmp(&bsrv.created_at)\n                .then_with(|| aname.cmp(bname))\n        });\n        for (srvname, server) in servers {\n            if let Selector::ExternalWorkload(selector) = &server.selector {\n                if selector.matches(&self.meta.labels) {\n                    // Each server selects exactly one port on an\n                    // external workload\n                    //\n                    // Note: an external workload has only one set of ports. A\n                    // pod has a union, each container declares its own set.\n                    let port = if let Some(srv_port) = self.selects_port(&server.port_ref) {\n                        srv_port\n                    } else {\n                        // If server references a named port, and our workload\n                        // contains no such port, then skip this server.\n                        continue;\n                    };\n\n                    if let Some(prior) = matched_ports.get(&port) {\n                        // If a different server has already matched this\n                        tracing::warn!(\n                        %port,\n                        server = %prior,\n                        conflict = %srvname,\n                        \"Port already matched by another server; skipping\"\n                        );\n                        continue;\n                    }\n\n                    let s = policy.inbound_server(\n                        srvname.clone(),\n                        server,\n                        authentications,\n                        Vec::new().into_iter(),\n                    );\n\n                    self.update_server(port, srvname, s);\n                    matched_ports.insert(port, srvname.clone());\n                    unmatched_ports.remove(&port);\n                }\n            }\n        }\n\n        // Reset all other ports that were previously selected to defaults\n        for port in unmatched_ports.into_iter() {\n            self.set_default_server(port, &policy.cluster_info);\n        }\n    }\n\n    /// Updates an external workload-port to use a given named server.\n    ///\n    /// We use name explicitly (and not derived from the 'server') to ensure we\n    /// are not handling a default server.\n    fn update_server(&mut self, port: NonZeroU16, name: &str, server: InboundServer) {\n        match self.port_servers.entry(port) {\n            Entry::Vacant(entry) => {\n                tracing::trace!(port = %port, server = %name, \"Creating server\");\n                let (watch, _) = watch::channel(server);\n                entry.insert(WorkloadPortServer {\n                    name: Some(name.to_string()),\n                    watch,\n                });\n            }\n\n            Entry::Occupied(mut entry) => {\n                let ps = entry.get_mut();\n\n                ps.watch.send_if_modified(|current| {\n                    if ps.name.as_deref() == Some(name) && *current == server {\n                        tracing::trace!(port = %port, server = %name, \"Skipped redundant server update\");\n                        tracing::trace!(?server);\n                        return false;\n                    }\n\n                    // If the port's server previously matched a different\n                    // server, this can either mean that multiple servers\n                    // currently match the external_workload:port, or that we're\n                    // in the middle of an update. We make the opportunistic\n                    // choice to assume the cluster is configured coherently so\n                    // we take the update. The admission controller should\n                    // prevent conflicts.\n                    tracing::trace!(port = %port, server = %name, \"Updating server\");\n                    if ps.name.as_deref() != Some(name) {\n                        ps.name = Some(name.to_string());\n                    }\n\n                    *current = server;\n                    true\n                });\n            }\n        }\n\n        tracing::debug!(port = %port, server = %name, \"Updated server\");\n    }\n\n    /// Updates a workload-port to use a given named server.\n    fn set_default_server(&mut self, port: NonZeroU16, config: &ClusterInfo) {\n        // Create a default server policy, without authorising any probe paths\n        let server = PolicyIndex::default_inbound_server(\n            port,\n            &self.meta.settings,\n            Vec::new().into_iter(),\n            config,\n        );\n        match self.port_servers.entry(port) {\n            Entry::Vacant(entry) => {\n                tracing::debug!(%port, server = %config.default_policy, \"Creating default server\");\n                let (watch, _) = watch::channel(server);\n                entry.insert(WorkloadPortServer { name: None, watch });\n            }\n\n            Entry::Occupied(mut entry) => {\n                // A server must have selected this before; override with\n                // default policy\n                let ps = entry.get_mut();\n                ps.watch.send_if_modified(|current| {\n                    // Avoid sending redundant updates\n                    if *current == server {\n                        tracing::trace!(%port, server = %config.default_policy, \"Default server already set\");\n                        return false;\n                    }\n\n                    tracing::debug!(%port, server = %config.default_policy, \"Setting default server\");\n                    ps.name = None;\n                    *current = server;\n                    true\n                });\n            }\n        }\n    }\n\n    /// Returns an optional port when a server references a known external\n    /// workload port.\n    ///\n    /// Unlike a pod, an external workload has only one set of ports. Names\n    /// within the set are unique, and as a result, only one port will ever\n    /// match a given name.\n    fn selects_port(&mut self, port_ref: &Port) -> Option<NonZeroU16> {\n        match port_ref {\n            Port::Number(p) => Some(*p),\n            Port::Name(name) => self.port_names.get(name).cloned(),\n        }\n    }\n\n    fn port_server_or_default(\n        &mut self,\n        port: NonZeroU16,\n        config: &ClusterInfo,\n    ) -> &mut WorkloadPortServer {\n        match self.port_servers.entry(port) {\n            Entry::Occupied(entry) => entry.into_mut(),\n            Entry::Vacant(entry) => {\n                let (watch, _) = watch::channel(PolicyIndex::default_inbound_server(\n                    port,\n                    &self.meta.settings,\n                    Vec::new().into_iter(),\n                    config,\n                ));\n                entry.insert(WorkloadPortServer { name: None, watch })\n            }\n        }\n    }\n}\n\n// === impl PolicyIndex ===\n\nimpl PolicyIndex {\n    #[inline]\n    fn is_empty(&self) -> bool {\n        self.servers.is_empty()\n            && self.server_authorizations.is_empty()\n            && self.authorization_policies.is_empty()\n            && self.http_routes.is_empty()\n    }\n\n    fn update_server(&mut self, name: String, server: server::Server) -> bool {\n        match self.servers.entry(name.clone()) {\n            Entry::Vacant(entry) => {\n                entry.insert(server);\n            }\n            Entry::Occupied(entry) => {\n                let srv = entry.into_mut();\n                if *srv == server {\n                    tracing::debug!(server = %name, \"no changes\");\n                    return false;\n                }\n                tracing::debug!(server = %name, \"updating\");\n                *srv = server;\n            }\n        }\n        true\n    }\n\n    fn update_server_authz(\n        &mut self,\n        name: String,\n        server_authz: server_authorization::ServerAuthz,\n    ) -> bool {\n        match self.server_authorizations.entry(name) {\n            Entry::Vacant(entry) => {\n                entry.insert(server_authz);\n            }\n            Entry::Occupied(entry) => {\n                let saz = entry.into_mut();\n                if *saz == server_authz {\n                    return false;\n                }\n                *saz = server_authz;\n            }\n        }\n        true\n    }\n\n    fn update_authz_policy(&mut self, name: String, spec: authorization_policy::Spec) -> bool {\n        match self.authorization_policies.entry(name) {\n            Entry::Vacant(entry) => {\n                entry.insert(spec);\n            }\n            Entry::Occupied(entry) => {\n                let ap = entry.into_mut();\n                if *ap == spec {\n                    return false;\n                }\n                *ap = spec;\n            }\n        }\n        true\n    }\n\n    fn update_ratelimit_policy(&mut self, name: String, spec: ratelimit_policy::Spec) -> bool {\n        match self.ratelimit_policies.entry(name) {\n            Entry::Vacant(entry) => {\n                entry.insert(spec);\n            }\n            Entry::Occupied(entry) => {\n                let rl = entry.into_mut();\n                if *rl == spec {\n                    return false;\n                }\n                *rl = spec;\n            }\n        }\n        true\n    }\n\n    fn default_inbound_server<'p>(\n        port: NonZeroU16,\n        settings: &workload::Settings,\n        probe_paths: impl Iterator<Item = &'p str>,\n        config: &ClusterInfo,\n    ) -> InboundServer {\n        let protocol = if settings.opaque_ports.contains(&port) {\n            ProxyProtocol::Opaque\n        } else {\n            ProxyProtocol::Detect {\n                timeout: config.default_detect_timeout,\n            }\n        };\n\n        let mut policy = settings.default_policy.unwrap_or(config.default_policy);\n        if settings.require_id_ports.contains(&port) {\n            if let DefaultPolicy::Allow {\n                ref mut authenticated_only,\n                ..\n            } = policy\n            {\n                *authenticated_only = true;\n            }\n        }\n\n        let authorizations = policy.default_authzs(config);\n\n        let ratelimit = Default::default();\n\n        let http_routes = config.default_inbound_http_routes(probe_paths);\n\n        InboundServer {\n            reference: ServerRef::Default(policy.as_str()),\n            protocol,\n            authorizations,\n            ratelimit,\n            http_routes,\n            grpc_routes: HashMap::default(),\n        }\n    }\n\n    fn inbound_server<'p>(\n        &self,\n        name: String,\n        server: &server::Server,\n        authentications: &AuthenticationNsIndex,\n        probe_paths: impl Iterator<Item = &'p str>,\n    ) -> InboundServer {\n        tracing::trace!(%name, ?server, \"Creating inbound server\");\n        let authorizations = self.client_authzs(&name, server, authentications);\n        let ratelimit = self.client_ratelimit(&name);\n        let http_routes = self.http_routes(&name, authentications, probe_paths);\n        let grpc_routes = self.grpc_routes(&name, authentications);\n\n        InboundServer {\n            reference: ServerRef::Server(name),\n            authorizations,\n            ratelimit,\n            protocol: server.protocol.clone(),\n            http_routes,\n            grpc_routes,\n        }\n    }\n\n    fn client_authzs(\n        &self,\n        server_name: &str,\n        server: &server::Server,\n        authentications: &AuthenticationNsIndex,\n    ) -> HashMap<AuthorizationRef, ClientAuthorization> {\n        let mut authzs = HashMap::default();\n        for (name, saz) in self.server_authorizations.iter() {\n            if saz.server_selector.selects(server_name, &server.labels) {\n                authzs.insert(\n                    AuthorizationRef::ServerAuthorization(name.to_string()),\n                    saz.authz.clone(),\n                );\n            }\n        }\n\n        for (name, spec) in self.authorization_policies.iter() {\n            // Skip the policy if it doesn't apply to the server.\n            match &spec.target {\n                authorization_policy::Target::Server(name) => {\n                    if name != server_name {\n                        tracing::trace!(\n                            ns = %self.namespace,\n                            authorizationpolicy = %name,\n                            server = %server_name,\n                            target = %name,\n                            \"AuthorizationPolicy does not target server\",\n                        );\n                        continue;\n                    }\n                }\n                authorization_policy::Target::Namespace => {}\n                authorization_policy::Target::HttpRoute(_)\n                | authorization_policy::Target::GrpcRoute(_) => {\n                    // Policies which target routes will be attached to\n                    // the route authorizations and should not be included in\n                    // the server authorizations.\n                    continue;\n                }\n            }\n\n            tracing::trace!(\n                ns = %self.namespace,\n                authorizationpolicy = %name,\n                server = %server_name,\n                \"AuthorizationPolicy targets server\",\n            );\n            tracing::trace!(authns = ?spec.authentications);\n\n            let authz = match self.policy_client_authz(spec, authentications) {\n                Ok(authz) => authz,\n                Err(error) => {\n                    tracing::info!(\n                        server = %server_name,\n                        authorizationpolicy = %name,\n                        %error,\n                        \"Illegal AuthorizationPolicy; ignoring\",\n                    );\n                    continue;\n                }\n            };\n\n            let reference = AuthorizationRef::AuthorizationPolicy(name.to_string());\n            authzs.insert(reference, authz);\n        }\n\n        if let Some(p) = server.access_policy {\n            authzs.extend(p.default_authzs(&self.cluster_info));\n        }\n\n        authzs\n    }\n\n    fn client_ratelimit(&self, server_name: &str) -> Option<RateLimit> {\n        use ratelimit_policy::{ClientRef, Target};\n\n        let (name, spec) = self\n            .ratelimit_policies\n            .iter()\n            .find(|(_, spec)| matches!(spec.target, Target::Server(ref n) if n == server_name && spec.accepted_by_server(server_name)))?;\n\n        tracing::trace!(\n            ns = %self.namespace,\n            ratelimitpolicy = %name,\n            server = %server_name,\n            \"HTTPLocalRateLimitPolicy targets server\",\n        );\n\n        let overrides = spec\n            .overrides\n            .iter()\n            .map(|ovr| {\n                let client_identities = ovr\n                    .client_refs\n                    .iter()\n                    .map(|ClientRef::ServiceAccount { namespace, name }| {\n                        let namespace = namespace.as_deref().unwrap_or(&self.namespace);\n                        self.cluster_info.service_account_identity(namespace, name)\n                    })\n                    .collect();\n\n                Override {\n                    requests_per_second: ovr.requests_per_second,\n                    client_identities,\n                }\n            })\n            .collect();\n\n        Some(RateLimit {\n            name: name.to_string(),\n            total: spec.total.as_ref().map(|l| Limit {\n                requests_per_second: l.requests_per_second,\n            }),\n            identity: spec.identity.as_ref().map(|l| Limit {\n                requests_per_second: l.requests_per_second,\n            }),\n            overrides,\n        })\n    }\n\n    fn route_client_authzs(\n        &self,\n        gkn: &GroupKindName,\n        authentications: &AuthenticationNsIndex,\n    ) -> HashMap<AuthorizationRef, ClientAuthorization> {\n        let mut authzs = HashMap::default();\n\n        for (name, spec) in &self.authorization_policies {\n            // Skip the policy if it doesn't apply to the route.\n            match &spec.target {\n                authorization_policy::Target::HttpRoute(n) if n.eq_ignore_ascii_case(gkn) => {\n                    tracing::trace!(\n                        ns = %self.namespace,\n                        authorizationpolicy = %name,\n                        route = ?gkn,\n                        \"AuthorizationPolicy targets HttpRoute\",\n                    );\n                }\n                authorization_policy::Target::GrpcRoute(n) if n.eq_ignore_ascii_case(gkn) => {\n                    tracing::trace!(\n                        ns = %self.namespace,\n                        authorizationpolicy = %name,\n                        route = ?gkn,\n                        \"AuthorizationPolicy targets GrpcRoute\",\n                    );\n                }\n                _ => {\n                    tracing::trace!(\n                        ns = %self.namespace,\n                        authorizationpolicy = %name,\n                        route = ?gkn,\n                        target = ?spec.target,\n                        \"AuthorizationPolicy does not target HttpRoute\",\n                    );\n                    continue;\n                }\n            }\n\n            tracing::trace!(authns = ?spec.authentications);\n\n            let authz = match self.policy_client_authz(spec, authentications) {\n                Ok(authz) => authz,\n                Err(error) => {\n                    tracing::info!(\n                        route = ?gkn,\n                        authorizationpolicy = %name,\n                        %error,\n                        \"Illegal AuthorizationPolicy; ignoring\",\n                    );\n                    continue;\n                }\n            };\n\n            let reference = AuthorizationRef::AuthorizationPolicy(name.to_string());\n            authzs.insert(reference, authz);\n        }\n\n        authzs\n    }\n\n    fn http_routes<'p>(\n        &self,\n        server_name: &str,\n        authentications: &AuthenticationNsIndex,\n        probe_paths: impl Iterator<Item = &'p str>,\n    ) -> HashMap<RouteRef, HttpRoute> {\n        let routes = self\n            .http_routes\n            .iter()\n            .filter(|(_, route)| route.selects_server(server_name))\n            .filter(|(_, route)| route.accepted_by_server(server_name))\n            .map(|(gkn, route)| {\n                let mut route = route.route.clone();\n                route.authorizations = self.route_client_authzs(gkn, authentications);\n                (RouteRef::Resource(gkn.clone()), route)\n            })\n            .collect::<HashMap<_, _>>();\n        if !routes.is_empty() {\n            return routes;\n        }\n        self.cluster_info.default_inbound_http_routes(probe_paths)\n    }\n\n    fn grpc_routes(\n        &self,\n        server_name: &str,\n        authentications: &AuthenticationNsIndex,\n    ) -> HashMap<RouteRef, GrpcRoute> {\n        let routes = self\n            .grpc_routes\n            .iter()\n            .filter(|(_, route)| route.selects_server(server_name))\n            .filter(|(_, route)| route.accepted_by_server(server_name))\n            .map(|(gkn, route)| {\n                let mut route = route.route.clone();\n                route.authorizations = self.route_client_authzs(gkn, authentications);\n                (RouteRef::Resource(gkn.clone()), route)\n            })\n            .collect::<HashMap<_, _>>();\n        if !routes.is_empty() {\n            return routes;\n        }\n        [(RouteRef::Default(\"default\"), GrpcRoute::default())].into()\n    }\n\n    fn policy_client_authz(\n        &self,\n        spec: &authorization_policy::Spec,\n        all_authentications: &AuthenticationNsIndex,\n    ) -> Result<ClientAuthorization> {\n        use authorization_policy::AuthenticationTarget;\n\n        let mut identities = None;\n        for tgt in spec.authentications.iter() {\n            match tgt {\n                AuthenticationTarget::MeshTLS {\n                    ref namespace,\n                    ref name,\n                } => {\n                    let namespace = namespace.as_deref().unwrap_or(&self.namespace);\n                    let _span = tracing::trace_span!(\"mesh_tls\", ns = %namespace, %name).entered();\n                    tracing::trace!(\"Finding MeshTLSAuthentication...\");\n                    let authn = all_authentications\n                        .by_ns\n                        .get(namespace)\n                        .and_then(|ns| ns.meshtls.get(name))\n                        .ok_or_else(|| {\n                            anyhow!(\n                                \"could not find MeshTLSAuthentication {name} in namespace {namespace}\"\n                            )\n                        })?;\n                    tracing::trace!(ids = ?authn.matches, \"Found MeshTLSAuthentication\");\n                    if identities.is_some() {\n                        bail!(\"policy must not include multiple MeshTLSAuthentications\");\n                    }\n                    let ids = authn.matches.clone();\n                    identities = Some(ids);\n                }\n                AuthenticationTarget::ServiceAccount {\n                    ref namespace,\n                    ref name,\n                } => {\n                    // There can only be a single required ServiceAccount. This is\n                    // enforced by the admission controller.\n                    if identities.is_some() {\n                        bail!(\"policy must not include multiple ServiceAccounts\");\n                    }\n                    let namespace = namespace.as_deref().unwrap_or(&self.namespace);\n                    let id = self.cluster_info.service_account_identity(namespace, name);\n                    identities = Some(vec![IdentityMatch::Exact(id)])\n                }\n                _network => {}\n            }\n        }\n\n        let mut networks = None;\n        for tgt in spec.authentications.iter() {\n            if let AuthenticationTarget::Network {\n                ref namespace,\n                ref name,\n            } = tgt\n            {\n                let namespace = namespace.as_deref().unwrap_or(&self.namespace);\n                tracing::trace!(ns = %namespace, %name, \"Finding NetworkAuthentication\");\n                if let Some(ns) = all_authentications.by_ns.get(namespace) {\n                    if let Some(authn) = ns.network.get(name).as_ref() {\n                        tracing::trace!(ns = %namespace, %name, nets = ?authn.matches, \"Found NetworkAuthentication\");\n                        // There can only be a single required NetworkAuthentication. This is\n                        // enforced by the admission controller.\n                        if networks.is_some() {\n                            bail!(\"policy must not include multiple NetworkAuthentications\");\n                        }\n                        let nets = authn.matches.clone();\n                        networks = Some(nets);\n                        continue;\n                    }\n                }\n                bail!(\"could not find NetworkAuthentication {name} in namespace {namespace}\");\n            }\n        }\n\n        Ok(ClientAuthorization {\n            // If MTLS identities are configured, use them. Otherwise, do not require\n            // authentication.\n            authentication: identities\n                .map(ClientAuthentication::TlsAuthenticated)\n                .unwrap_or(ClientAuthentication::Unauthenticated),\n\n            // If networks are configured, use them. Otherwise, this applies to all networks.\n            networks: networks.unwrap_or_else(|| {\n                vec![\n                    NetworkMatch {\n                        net: Ipv4Net::default().into(),\n                        except: vec![],\n                    },\n                    NetworkMatch {\n                        net: Ipv6Net::default().into(),\n                        except: vec![],\n                    },\n                ]\n            }),\n        })\n    }\n\n    fn update_http_route(&mut self, gkn: GroupKindName, route: RouteBinding<HttpRoute>) -> bool {\n        match self.http_routes.entry(gkn) {\n            Entry::Vacant(entry) => {\n                entry.insert(route);\n            }\n            Entry::Occupied(mut entry) => {\n                if *entry.get() == route {\n                    return false;\n                }\n                entry.insert(route);\n            }\n        }\n        true\n    }\n\n    fn update_grpc_route(&mut self, gkn: GroupKindName, route: RouteBinding<GrpcRoute>) -> bool {\n        match self.grpc_routes.entry(gkn) {\n            Entry::Vacant(entry) => {\n                entry.insert(route);\n            }\n            Entry::Occupied(mut entry) => {\n                if *entry.get() == route {\n                    return false;\n                }\n                entry.insert(route);\n            }\n        }\n        true\n    }\n}\n\n// === impl AuthenticationNsIndex ===\n\nimpl AuthenticationNsIndex {\n    fn update_meshtls(\n        &mut self,\n        namespace: String,\n        name: String,\n        spec: meshtls_authentication::Spec,\n    ) -> bool {\n        match self.by_ns.entry(namespace).or_default().meshtls.entry(name) {\n            Entry::Vacant(entry) => {\n                entry.insert(spec);\n            }\n            Entry::Occupied(mut entry) => {\n                if *entry.get() == spec {\n                    return false;\n                }\n                entry.insert(spec);\n            }\n        }\n\n        true\n    }\n\n    fn update_network(\n        &mut self,\n        namespace: String,\n        name: String,\n        spec: network_authentication::Spec,\n    ) -> bool {\n        match self.by_ns.entry(namespace).or_default().network.entry(name) {\n            Entry::Vacant(entry) => {\n                entry.insert(spec);\n            }\n            Entry::Occupied(mut entry) => {\n                if *entry.get() == spec {\n                    return false;\n                }\n                entry.insert(spec);\n            }\n        }\n\n        true\n    }\n}\n\n// === impl AuthenticationIndex ===\n\nimpl AuthenticationIndex {\n    #[inline]\n    fn is_empty(&self) -> bool {\n        self.meshtls.is_empty() && self.network.is_empty()\n    }\n}\n\n// === imp NsUpdate ===\n\nimpl<K, T> Default for NsUpdate<K, T> {\n    fn default() -> Self {\n        Self {\n            added: vec![],\n            removed: Default::default(),\n        }\n    }\n}\n\n// === impl ClusterInfo ===\n\nimpl ClusterInfo {\n    fn default_inbound_http_routes<'p>(\n        &self,\n        probe_paths: impl Iterator<Item = &'p str>,\n    ) -> HashMap<RouteRef, HttpRoute> {\n        let mut routes = HashMap::with_capacity(2);\n\n        // If no routes are defined for the server, use a default route that\n        // matches all requests. Default authorizations are instrumented on\n        // the server.\n        routes.insert(RouteRef::Default(\"default\"), HttpRoute::default());\n\n        // If there are no probe networks, there are no probe routes to\n        // authorize.\n        if self.probe_networks.is_empty() {\n            return routes;\n        }\n\n        // Generate an `Exact` path match for each probe path defined on the\n        // pod.\n        let matches: Vec<HttpRouteMatch> = probe_paths\n            .map(|path| HttpRouteMatch {\n                path: Some(PathMatch::Exact(path.to_string())),\n                headers: vec![],\n                query_params: vec![],\n                method: Some(Method::GET),\n            })\n            .collect();\n\n        // If there are no matches, then are no probe routes to authorize.\n        if matches.is_empty() {\n            return routes;\n        }\n\n        // Probes are authorized on the configured probe networks only.\n        let authorizations = std::iter::once((\n            AuthorizationRef::Default(\"probe\"),\n            ClientAuthorization {\n                networks: self\n                    .probe_networks\n                    .iter()\n                    .copied()\n                    .map(Into::into)\n                    .collect(),\n                authentication: ClientAuthentication::Unauthenticated,\n            },\n        ))\n        .collect();\n\n        let probe_route = HttpRoute {\n            hostnames: Vec::new(),\n            rules: vec![InboundRouteRule {\n                matches,\n                filters: Vec::new(),\n            }],\n            authorizations,\n            creation_timestamp: None,\n        };\n        routes.insert(RouteRef::Default(\"probe\"), probe_route);\n\n        routes\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/meshtls_authentication.rs",
    "content": "use crate::ClusterInfo;\nuse anyhow::Result;\nuse linkerd_policy_controller_core::IdentityMatch;\nuse linkerd_policy_controller_k8s_api::{\n    policy::MeshTLSAuthentication, Namespace, ResourceExt, ServiceAccount,\n};\n\n#[derive(Debug, PartialEq)]\npub(crate) struct Spec {\n    pub matches: Vec<IdentityMatch>,\n}\n\nimpl Spec {\n    pub(crate) fn try_from_resource(\n        ma: MeshTLSAuthentication,\n        cluster: &ClusterInfo,\n    ) -> anyhow::Result<Self> {\n        let namespace = ma\n            .namespace()\n            .expect(\"MeshTLSAuthentication must have a namespace\");\n\n        let identities = ma.spec.identities.into_iter().flatten().map(|s| {\n            Ok(s.parse::<IdentityMatch>()\n                .expect(\"identity match parsing is infallible\"))\n        });\n\n        let identity_refs = ma.spec.identity_refs.into_iter().flatten().map(|tgt| {\n            if tgt.targets_kind::<ServiceAccount>() {\n                let ns = tgt.namespace.as_deref().unwrap_or(&namespace);\n                let id = cluster.service_account_identity(ns, &tgt.name);\n                Ok(IdentityMatch::Exact(id))\n            } else if tgt.targets_kind::<Namespace>() {\n                let id = cluster.namespace_identity(tgt.name.as_str());\n                Ok(id.parse::<IdentityMatch>()?)\n            } else {\n                anyhow::bail!(\"unsupported target type: {:?}\", tgt.canonical_kind())\n            }\n        });\n\n        let matches = identities\n            .chain(identity_refs)\n            .collect::<Result<Vec<_>>>()?;\n        if matches.is_empty() {\n            anyhow::bail!(\"No identities configured\");\n        }\n\n        Ok(Spec { matches })\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/network_authentication.rs",
    "content": "use linkerd_policy_controller_core::NetworkMatch;\nuse linkerd_policy_controller_k8s_api::policy::NetworkAuthenticationSpec;\n\n#[derive(Debug, PartialEq)]\npub(crate) struct Spec {\n    pub matches: Vec<NetworkMatch>,\n}\n\nimpl TryFrom<NetworkAuthenticationSpec> for Spec {\n    type Error = anyhow::Error;\n\n    fn try_from(spec: NetworkAuthenticationSpec) -> anyhow::Result<Self> {\n        let matches = spec\n            .networks\n            .into_iter()\n            .map(|n| NetworkMatch {\n                net: n.cidr.into(),\n                except: n.except.into_iter().flatten().map(Into::into).collect(),\n            })\n            .collect::<Vec<_>>();\n\n        if matches.is_empty() {\n            anyhow::bail!(\"No networks configured\");\n        }\n\n        Ok(Spec { matches })\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/ratelimit_policy.rs",
    "content": "use anyhow::Result;\nuse chrono::{offset::Utc, DateTime};\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s,\n    policy::{LocalTargetRef, NamespacedTargetRef},\n    ServiceAccount, Time,\n};\nuse std::fmt;\n\n#[derive(Debug, PartialEq)]\npub(crate) struct Spec {\n    pub creation_timestamp: Option<DateTime<Utc>>,\n    pub target: Target,\n    pub total: Option<Limit>,\n    pub identity: Option<Limit>,\n    pub overrides: Vec<Override>,\n    pub status: Status,\n}\n\n#[derive(Clone, Debug, Eq, PartialEq)]\npub(crate) enum Target {\n    Server(String),\n}\n\n#[derive(Debug, PartialEq)]\npub(crate) struct Limit {\n    pub requests_per_second: u32,\n}\n\n#[derive(Debug, PartialEq)]\npub(crate) struct Override {\n    pub requests_per_second: u32,\n    pub client_refs: Vec<ClientRef>,\n}\n\n#[derive(Debug, PartialEq)]\npub(crate) enum ClientRef {\n    ServiceAccount {\n        namespace: Option<String>,\n        name: String,\n    },\n}\n\n#[derive(Clone, Debug, Eq, PartialEq)]\npub struct Status {\n    pub conditions: Vec<Condition>,\n    pub target: Target,\n}\n\n#[derive(Clone, Debug, Eq, PartialEq)]\npub struct Condition {\n    pub type_: ConditionType,\n    pub status: bool,\n}\n\n#[derive(Clone, Debug, Eq, PartialEq)]\npub enum ConditionType {\n    Accepted,\n}\n\nimpl Spec {\n    pub fn accepted_by_server(&self, name: &str) -> bool {\n        self.status.target == Target::Server(name.to_string())\n            && self\n                .status\n                .conditions\n                .iter()\n                .any(|condition| condition.type_ == ConditionType::Accepted && condition.status)\n    }\n}\n\nimpl TryFrom<k8s::policy::HttpLocalRateLimitPolicy> for Spec {\n    type Error = anyhow::Error;\n\n    fn try_from(rl: k8s::policy::HttpLocalRateLimitPolicy) -> Result<Self> {\n        let creation_timestamp = rl.metadata.creation_timestamp.map(|Time(t)| t);\n        let conditions = rl.status.map_or(vec![], |status| {\n            status\n            .conditions\n            .iter()\n            .filter_map(|condition| {\n                let type_ = match condition.type_.as_ref() {\n                    \"Accepted\" => ConditionType::Accepted,\n                    condition_type => {\n                        tracing::warn!(%status.target_ref.name, %condition_type, \"Unexpected condition type found in status\");\n                        return None;\n                    }\n                };\n                let status = match condition.status.as_ref() {\n                    \"True\" => true,\n                    \"False\" => false,\n                    condition_status => {\n                        tracing::warn!(%status.target_ref.name, %type_, %condition_status, \"Unexpected condition status found in status\");\n                        return None\n                    },\n                };\n                Some(Condition { type_, status })\n            }).collect()\n        });\n        let target = target(rl.spec.target_ref)?;\n\n        Ok(Self {\n            creation_timestamp,\n            target: target.clone(),\n            total: rl.spec.total.map(|lim| Limit {\n                requests_per_second: lim.requests_per_second,\n            }),\n            identity: rl.spec.identity.map(|lim| Limit {\n                requests_per_second: lim.requests_per_second,\n            }),\n            overrides: rl\n                .spec\n                .overrides\n                .unwrap_or_default()\n                .into_iter()\n                .map(|o| {\n                    let client_refs = o\n                        .client_refs\n                        .into_iter()\n                        .map(client_ref)\n                        .collect::<Result<Vec<_>>>();\n                    client_refs.map(|refs| Override {\n                        requests_per_second: o.requests_per_second,\n                        client_refs: refs,\n                    })\n                })\n                .collect::<Result<Vec<_>>>()?,\n            status: Status { conditions, target },\n        })\n    }\n}\n\nimpl fmt::Display for ConditionType {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::Accepted => write!(f, \"Accepted\"),\n        }\n    }\n}\n\nfn target(t: LocalTargetRef) -> Result<Target> {\n    match t {\n        t if t.targets_kind::<k8s::policy::Server>() => Ok(Target::Server(t.name)),\n        _ => anyhow::bail!(\"unsupported rate limit target type: {}\", t.canonical_kind()),\n    }\n}\n\nfn client_ref(t: NamespacedTargetRef) -> Result<ClientRef> {\n    if t.targets_kind::<ServiceAccount>() {\n        Ok(ClientRef::ServiceAccount {\n            namespace: t.namespace,\n            name: t.name,\n        })\n    } else {\n        anyhow::bail!(\"unsupported client reference: {}\", t.canonical_kind());\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/routes.rs",
    "content": "use anyhow::Result;\nuse linkerd_policy_controller_core::POLICY_CONTROLLER_NAME;\nuse linkerd_policy_controller_k8s_api::{gateway, policy};\nuse std::fmt;\n\n#[derive(Clone, Debug, Eq, PartialEq)]\npub struct RouteBinding<R> {\n    pub parents: Vec<ParentRef>,\n    pub route: R,\n    pub statuses: Vec<Status>,\n}\n\n#[derive(Clone, Debug, Eq, PartialEq)]\npub enum ParentRef {\n    Server(String),\n}\n\n#[derive(Clone, Debug, Eq, PartialEq)]\npub struct Status {\n    pub parent: ParentRef,\n    pub conditions: Vec<Condition>,\n}\n\n#[derive(Clone, Debug, Eq, PartialEq)]\npub struct Condition {\n    pub type_: ConditionType,\n    pub status: bool,\n}\n\n#[derive(Clone, Debug, Eq, PartialEq)]\npub enum ConditionType {\n    Accepted,\n}\n\n#[derive(Clone, Debug, thiserror::Error)]\npub enum InvalidParentRef {\n    #[error(\"route resource may not reference a parent Server in an other namespace\")]\n    ServerInAnotherNamespace,\n\n    #[error(\"route resource may not reference a parent by port\")]\n    SpecifiesPort,\n\n    #[error(\"route resource may not reference a parent by section name\")]\n    SpecifiesSection,\n}\n\nimpl<R> RouteBinding<R> {\n    #[inline]\n    pub fn selects_server(&self, name: &str) -> bool {\n        self.parents\n            .iter()\n            .any(|p| matches!(p, ParentRef::Server(n) if n == name))\n    }\n\n    #[inline]\n    pub fn accepted_by_server(&self, name: &str) -> bool {\n        self.statuses.iter().any(|status| {\n            status.parent == ParentRef::Server(name.to_string())\n                && status\n                    .conditions\n                    .iter()\n                    .any(|condition| condition.type_ == ConditionType::Accepted && condition.status)\n        })\n    }\n}\n\nimpl ParentRef {\n    pub(crate) fn collect_from_http(\n        route_ns: Option<&str>,\n        parent_refs: Option<Vec<gateway::HTTPRouteParentRefs>>,\n    ) -> Result<Vec<Self>, InvalidParentRef> {\n        let parents = parent_refs\n            .into_iter()\n            .flatten()\n            .filter_map(|parent_ref| Self::from_http_parent_ref(route_ns, parent_ref))\n            .collect::<Result<Vec<_>, InvalidParentRef>>()?;\n\n        Ok(parents)\n    }\n\n    fn from_http_parent_ref(\n        route_ns: Option<&str>,\n        parent_ref: gateway::HTTPRouteParentRefs,\n    ) -> Option<Result<Self, InvalidParentRef>> {\n        // Skip parent refs that don't target a `Server` resource.\n        if !policy::httproute::parent_ref_targets_kind::<policy::Server>(&parent_ref)\n            || parent_ref.name.is_empty()\n        {\n            return None;\n        }\n\n        let gateway::HTTPRouteParentRefs {\n            group: _,\n            kind: _,\n            namespace,\n            name,\n            section_name,\n            port,\n        } = parent_ref;\n\n        if namespace.is_some() && namespace.as_deref() != route_ns {\n            return Some(Err(InvalidParentRef::ServerInAnotherNamespace));\n        }\n        if port.is_some() {\n            return Some(Err(InvalidParentRef::SpecifiesPort));\n        }\n        if section_name.is_some() {\n            return Some(Err(InvalidParentRef::SpecifiesSection));\n        }\n\n        Some(Ok(ParentRef::Server(name)))\n    }\n\n    pub(crate) fn collect_from_grpc(\n        route_ns: Option<&str>,\n        parent_refs: Option<Vec<gateway::GRPCRouteParentRefs>>,\n    ) -> Result<Vec<Self>, InvalidParentRef> {\n        let parents = parent_refs\n            .into_iter()\n            .flatten()\n            .filter_map(|parent_ref| Self::from_grpc_parent_ref(route_ns, parent_ref))\n            .collect::<Result<Vec<_>, InvalidParentRef>>()?;\n\n        Ok(parents)\n    }\n\n    fn from_grpc_parent_ref(\n        route_ns: Option<&str>,\n        parent_ref: gateway::GRPCRouteParentRefs,\n    ) -> Option<Result<Self, InvalidParentRef>> {\n        // Skip parent refs that don't target a `Server` resource.\n        if !policy::grpcroute::parent_ref_targets_kind::<policy::Server>(&parent_ref)\n            || parent_ref.name.is_empty()\n        {\n            return None;\n        }\n\n        let gateway::GRPCRouteParentRefs {\n            group: _,\n            kind: _,\n            namespace,\n            name,\n            section_name,\n            port,\n        } = parent_ref;\n\n        if namespace.is_some() && namespace.as_deref() != route_ns {\n            return Some(Err(InvalidParentRef::ServerInAnotherNamespace));\n        }\n        if port.is_some() {\n            return Some(Err(InvalidParentRef::SpecifiesPort));\n        }\n        if section_name.is_some() {\n            return Some(Err(InvalidParentRef::SpecifiesSection));\n        }\n\n        Some(Ok(ParentRef::Server(name)))\n    }\n}\n\nimpl Status {\n    pub fn collect_from_http(status: gateway::HTTPRouteStatus) -> Vec<Self> {\n        status\n            .parents\n            .iter()\n            .filter(|status| status.controller_name == POLICY_CONTROLLER_NAME)\n            .filter_map(Self::from_http_parent_status)\n            .collect::<Vec<_>>()\n    }\n\n    fn from_http_parent_status(status: &gateway::HTTPRouteStatusParents) -> Option<Self> {\n        // Only match parent statuses that belong to resources of\n        // `kind: Server`.\n        match status.parent_ref.kind.as_deref() {\n            Some(\"Server\") => (),\n            _ => return None,\n        }\n\n        let conditions = status\n            .conditions\n            .iter()\n            .flatten()\n            .filter_map(|condition| {\n                let type_ = match condition.type_.as_ref() {\n                    \"Accepted\" => ConditionType::Accepted,\n                    condition_type => {\n                        tracing::warn!(%status.parent_ref.name, %condition_type, \"Unexpected condition type found in parent status\");\n                        return None;\n                    }\n                };\n                let status = match condition.status.as_ref() {\n                    \"True\" => true,\n                    \"False\" => false,\n                    condition_status => {\n                        tracing::warn!(%status.parent_ref.name, %type_, %condition_status, \"Unexpected condition status found in parent status\");\n                        return None\n                    },\n                };\n                Some(Condition { type_, status })\n            })\n            .collect();\n\n        Some(Status {\n            parent: ParentRef::Server(status.parent_ref.name.to_string()),\n            conditions,\n        })\n    }\n\n    pub fn collect_from_grpc(status: gateway::GRPCRouteStatus) -> Vec<Self> {\n        status\n            .parents\n            .iter()\n            .filter(|status| status.controller_name == POLICY_CONTROLLER_NAME)\n            .filter_map(Self::from_grpc_parent_status)\n            .collect::<Vec<_>>()\n    }\n\n    fn from_grpc_parent_status(status: &gateway::GRPCRouteStatusParents) -> Option<Self> {\n        // Only match parent statuses that belong to resources of\n        // `kind: Server`.\n        match status.parent_ref.kind.as_deref() {\n            Some(\"Server\") => (),\n            _ => return None,\n        }\n\n        let conditions = status\n            .conditions\n            .iter()\n            .flatten()\n            .filter_map(|condition| {\n                let type_ = match condition.type_.as_ref() {\n                    \"Accepted\" => ConditionType::Accepted,\n                    condition_type => {\n                        tracing::warn!(%status.parent_ref.name, %condition_type, \"Unexpected condition type found in parent status\");\n                        return None;\n                    }\n                };\n                let status = match condition.status.as_ref() {\n                    \"True\" => true,\n                    \"False\" => false,\n                    condition_status => {\n                        tracing::warn!(%status.parent_ref.name, %type_, %condition_status, \"Unexpected condition status found in parent status\");\n                        return None\n                    },\n                };\n                Some(Condition { type_, status })\n            })\n            .collect();\n\n        Some(Status {\n            parent: ParentRef::Server(status.parent_ref.name.to_string()),\n            conditions,\n        })\n    }\n}\n\nimpl fmt::Display for ConditionType {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::Accepted => write!(f, \"Accepted\"),\n        }\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/server.rs",
    "content": "use crate::{ClusterInfo, DefaultPolicy};\nuse linkerd_policy_controller_core::inbound::ProxyProtocol;\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s, policy::server::Port, policy::server::Selector,\n};\n\n/// The parts of a `Server` resource that can change.\n#[derive(Debug, PartialEq)]\npub(crate) struct Server {\n    pub labels: k8s::Labels,\n    pub selector: Selector,\n    pub port_ref: Port,\n    pub protocol: ProxyProtocol,\n    pub access_policy: Option<DefaultPolicy>,\n    pub created_at: Option<k8s::Time>,\n}\n\nimpl Server {\n    pub(crate) fn from_resource(srv: k8s::policy::Server, cluster: &ClusterInfo) -> Self {\n        Self {\n            labels: srv.metadata.labels.into(),\n            selector: srv.spec.selector,\n            port_ref: srv.spec.port,\n            protocol: proxy_protocol(srv.spec.proxy_protocol, cluster),\n            access_policy: srv.spec.access_policy.and_then(|p| p.parse().ok()),\n            created_at: srv.metadata.creation_timestamp,\n        }\n    }\n}\n\nfn proxy_protocol(\n    p: Option<k8s::policy::server::ProxyProtocol>,\n    cluster: &ClusterInfo,\n) -> ProxyProtocol {\n    match p {\n        None | Some(k8s::policy::server::ProxyProtocol::Unknown) => ProxyProtocol::Detect {\n            timeout: cluster.default_detect_timeout,\n        },\n        Some(k8s::policy::server::ProxyProtocol::Http1) => ProxyProtocol::Http1,\n        Some(k8s::policy::server::ProxyProtocol::Http2) => ProxyProtocol::Http2,\n        Some(k8s::policy::server::ProxyProtocol::Grpc) => ProxyProtocol::Grpc,\n        Some(k8s::policy::server::ProxyProtocol::Opaque) => ProxyProtocol::Opaque,\n        Some(k8s::policy::server::ProxyProtocol::Tls) => ProxyProtocol::Tls,\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/server_authorization.rs",
    "content": "use crate::ClusterInfo;\nuse anyhow::Result;\nuse linkerd_policy_controller_core::{\n    inbound::{ClientAuthentication, ClientAuthorization},\n    IdentityMatch, NetworkMatch,\n};\nuse linkerd_policy_controller_k8s_api::{self as k8s, policy::server_authorization::MeshTls};\n\n/// The parts of a `ServerAuthorization` resource that can change.\n#[derive(Debug, PartialEq)]\npub(crate) struct ServerAuthz {\n    pub authz: ClientAuthorization,\n    pub server_selector: ServerSelector,\n}\n\n/// Selects `Server`s for a `ServerAuthoriation`\n#[derive(Clone, Debug, PartialEq)]\npub(crate) enum ServerSelector {\n    Name(String),\n    Selector(k8s::labels::Selector),\n}\n\nimpl ServerAuthz {\n    pub(crate) fn from_resource(\n        saz: k8s::policy::ServerAuthorization,\n        cluster: &ClusterInfo,\n    ) -> Result<Self> {\n        let authz = {\n            let namespace = saz\n                .metadata\n                .namespace\n                .as_deref()\n                .expect(\"resource must be namespaced\");\n            client_authz(saz.spec.client, namespace, cluster)?\n        };\n        let server_selector = saz.spec.server.into();\n        Ok(Self {\n            authz,\n            server_selector,\n        })\n    }\n}\n\n// === impl ServerSelector ===\n\nimpl From<k8s::policy::server_authorization::Server> for ServerSelector {\n    fn from(s: k8s::policy::server_authorization::Server) -> Self {\n        s.name\n            .map(Self::Name)\n            .unwrap_or_else(|| Self::Selector(s.selector.unwrap_or_default()))\n    }\n}\n\nimpl ServerSelector {\n    pub(crate) fn selects(&self, name: &str, labels: &k8s::Labels) -> bool {\n        match self {\n            Self::Name(n) => *n == name,\n            Self::Selector(selector) => selector.matches(labels),\n        }\n    }\n}\n\nfn client_authz(\n    client: k8s::policy::server_authorization::Client,\n    namespace: &str,\n    cluster: &ClusterInfo,\n) -> Result<ClientAuthorization> {\n    let networks = client\n        .networks\n        .into_iter()\n        .flatten()\n        .map(|net| NetworkMatch {\n            net: net.cidr.into(),\n            except: net.except.into_iter().flatten().map(Into::into).collect(),\n        })\n        .collect();\n\n    let authentication = if client.unauthenticated {\n        ClientAuthentication::Unauthenticated\n    } else if let Some(mtls) = client.mesh_tls {\n        client_mtls_authn(mtls, namespace, cluster)?\n    } else {\n        anyhow::bail!(\"no client authentication configured\");\n    };\n\n    Ok(ClientAuthorization {\n        networks,\n        authentication,\n    })\n}\n\nfn client_mtls_authn(\n    mtls: MeshTls,\n    namespace: &str,\n    cluster: &ClusterInfo,\n) -> Result<ClientAuthentication> {\n    if mtls.unauthenticated_tls {\n        return Ok(ClientAuthentication::TlsUnauthenticated);\n    }\n\n    let ids = mtls\n        .identities\n        .into_iter()\n        .flatten()\n        .map(|id| match id.parse() {\n            Ok(id) => id,\n            Err(e) => match e {},\n        });\n\n    let sas = mtls.service_accounts.into_iter().flatten().map(|sa| {\n        let ns = sa.namespace.as_deref().unwrap_or(namespace);\n        IdentityMatch::Exact(cluster.service_account_identity(ns, &sa.name))\n    });\n\n    let identities = ids.chain(sas).collect::<Vec<_>>();\n    if identities.is_empty() {\n        anyhow::bail!(\"authorization authorizes no clients\");\n    }\n\n    Ok(ClientAuthentication::TlsAuthenticated(identities))\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/tests/annotation.rs",
    "content": "use super::*;\n\n/// Tests that pod servers are configured with defaults based on the\n/// workload-defined `DefaultPolicy` policy.\n///\n/// Iterates through each default policy and validates that it produces expected\n/// configurations.\n#[test]\nfn default_policy_annotated() {\n    for default in &DEFAULTS {\n        let test = TestConfig::from_default_policy(match *default {\n            // Invert default to ensure override applies.\n            DefaultPolicy::Deny => DefaultPolicy::Allow {\n                authenticated_only: false,\n                cluster_only: false,\n            },\n            _ => DefaultPolicy::Deny,\n        });\n\n        // Initially create the pod without an annotation and check that it gets\n        // the global default.\n        let mut pod = mk_pod(\"ns-0\", \"pod-0\", Some((\"container-0\", None)));\n        test.index\n            .write()\n            .reset(vec![pod.clone()], Default::default());\n\n        let mut rx = test\n            .index\n            .write()\n            .pod_server_rx(\"ns-0\", \"pod-0\", 2222.try_into().unwrap())\n            .expect(\"pod-0.ns-0 should exist\");\n        assert_eq!(\n            rx.borrow_and_update().reference,\n            ServerRef::Default(test.default_policy.as_str()),\n        );\n\n        // Update the annotation on the pod and check that the watch is updated\n        // with the new default.\n        pod.annotations_mut().insert(\n            \"config.linkerd.io/default-inbound-policy\".into(),\n            default.to_string(),\n        );\n        test.index.write().apply(pod);\n        assert!(rx.has_changed().unwrap());\n        assert_eq!(rx.borrow().reference, ServerRef::Default(default.as_str()));\n    }\n}\n\n/// Tests that an invalid workload annotation is ignored in favor of the global\n/// default.\n#[tokio::test]\nasync fn default_policy_annotated_invalid() {\n    let test = TestConfig::default();\n\n    let mut p = mk_pod(\"ns-0\", \"pod-0\", Some((\"container-0\", None)));\n    p.annotations_mut().insert(\n        \"config.linkerd.io/default-inbound-policy\".into(),\n        \"bogus\".into(),\n    );\n    test.index.write().reset(vec![p], Default::default());\n\n    // Lookup port 2222 -> default config.\n    let rx = test\n        .index\n        .write()\n        .pod_server_rx(\"ns-0\", \"pod-0\", 2222.try_into().unwrap())\n        .expect(\"pod must exist in lookups\");\n    assert_eq!(*rx.borrow(), test.default_server());\n}\n\n#[test]\nfn opaque_annotated() {\n    for default in &DEFAULTS {\n        let test = TestConfig::from_default_policy(*default);\n\n        let mut p = mk_pod(\"ns-0\", \"pod-0\", Some((\"container-0\", None)));\n        p.annotations_mut()\n            .insert(\"config.linkerd.io/opaque-ports\".into(), \"2222\".into());\n        test.index.write().reset(vec![p], Default::default());\n\n        let mut server = test.default_server();\n        server.protocol = ProxyProtocol::Opaque;\n\n        let rx = test\n            .index\n            .write()\n            .pod_server_rx(\"ns-0\", \"pod-0\", 2222.try_into().unwrap())\n            .expect(\"pod-0.ns-0 should exist\");\n        assert_eq!(*rx.borrow(), server);\n    }\n}\n\n#[test]\nfn authenticated_annotated() {\n    for default in &DEFAULTS {\n        let test = TestConfig::from_default_policy(*default);\n\n        let mut p = mk_pod(\"ns-0\", \"pod-0\", Some((\"container-0\", None)));\n        p.annotations_mut().insert(\n            \"config.linkerd.io/proxy-require-identity-inbound-ports\".into(),\n            \"2222\".into(),\n        );\n        test.index.write().reset(vec![p], Default::default());\n\n        let config = {\n            let policy = match *default {\n                DefaultPolicy::Allow { cluster_only, .. } => DefaultPolicy::Allow {\n                    cluster_only,\n                    authenticated_only: true,\n                },\n                DefaultPolicy::Deny => DefaultPolicy::Deny,\n                DefaultPolicy::Audit => DefaultPolicy::Audit,\n            };\n            InboundServer {\n                reference: ServerRef::Default(policy.as_str()),\n                authorizations: mk_default_policy(policy, test.cluster.networks),\n                ratelimit: None,\n                protocol: ProxyProtocol::Detect {\n                    timeout: test.detect_timeout,\n                },\n                http_routes: mk_default_http_routes(),\n                grpc_routes: Default::default(),\n            }\n        };\n\n        let rx = test\n            .index\n            .write()\n            .pod_server_rx(\"ns-0\", \"pod-0\", 2222.try_into().unwrap())\n            .expect(\"pod-0.ns-0 should exist\");\n        assert_eq!(*rx.borrow(), config);\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/tests/authorization_policy.rs",
    "content": "use super::*;\nuse k8s_openapi::apimachinery::pkg::apis::meta::v1 as metav1;\nuse linkerd_policy_controller_core::{inbound, routes};\nuse linkerd_policy_controller_k8s_api::{gateway, policy};\n\n#[test]\nfn links_authorization_policy_with_mtls_name() {\n    let test = TestConfig::default();\n\n    let mut pod = mk_pod(\"ns-0\", \"pod-0\", Some((\"container-0\", None)));\n    pod.labels_mut()\n        .insert(\"app\".to_string(), \"app-0\".to_string());\n    test.index.write().apply(pod);\n\n    let mut rx = test\n        .index\n        .write()\n        .pod_server_rx(\"ns-0\", \"pod-0\", 8080.try_into().unwrap())\n        .expect(\"pod-0.ns-0 should exist\");\n    assert_eq!(*rx.borrow_and_update(), test.default_server());\n\n    test.index.write().apply(mk_server(\n        \"ns-0\",\n        \"srv-8080\",\n        Port::Number(8080.try_into().unwrap()),\n        None,\n        Some((\"app\", \"app-0\")),\n        Some(k8s::policy::server::ProxyProtocol::Http1),\n    ));\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(\n        *rx.borrow_and_update(),\n        InboundServer {\n            reference: ServerRef::Server(\"srv-8080\".to_string()),\n            authorizations: Default::default(),\n            ratelimit: None,\n            protocol: ProxyProtocol::Http1,\n            http_routes: mk_default_http_routes(),\n            grpc_routes: mk_default_grpc_routes(),\n        },\n    );\n\n    let authz = ClientAuthorization {\n        networks: vec![\"10.0.0.0/8\".parse::<IpNet>().unwrap().into()],\n        authentication: ClientAuthentication::TlsAuthenticated(vec![IdentityMatch::Exact(\n            \"foo.bar\".to_string(),\n        )]),\n    };\n    test.index.write().apply(mk_authorization_policy(\n        \"ns-0\",\n        \"authz-foo\",\n        Some(\"srv-8080\"),\n        vec![\n            NamespacedTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"NetworkAuthentication\".to_string(),\n                name: \"net-foo\".to_string(),\n                namespace: None,\n            },\n            NamespacedTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"MeshTLSAuthentication\".to_string(),\n                namespace: Some(\"ns-1\".to_string()),\n                name: \"mtls-bar\".to_string(),\n            },\n        ],\n    ));\n    test.index.write().apply(mk_network_authentication(\n        \"ns-0\".to_string(),\n        \"net-foo\".to_string(),\n        vec![k8s::policy::network_authentication::Network {\n            cidr: \"10.0.0.0/8\".parse().unwrap(),\n            except: None,\n        }],\n    ));\n    test.index.write().apply(mk_meshtls_authentication(\n        \"ns-1\",\n        \"mtls-bar\",\n        Some(\"foo.bar\".to_string()),\n        None,\n    ));\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(\n        *rx.borrow(),\n        InboundServer {\n            reference: ServerRef::Server(\"srv-8080\".to_string()),\n            authorizations: hashmap!(\n                AuthorizationRef::AuthorizationPolicy(\"authz-foo\".to_string()) => authz\n            )\n            .into_iter()\n            .collect(),\n            ratelimit: None,\n            protocol: ProxyProtocol::Http1,\n            http_routes: mk_default_http_routes(),\n            grpc_routes: mk_default_grpc_routes(),\n        },\n    );\n}\n\n#[test]\nfn authorization_targets_namespace() {\n    let test = TestConfig::default();\n\n    let mut pod = mk_pod(\"ns-0\", \"pod-0\", Some((\"container-0\", None)));\n    pod.labels_mut()\n        .insert(\"app\".to_string(), \"app-0\".to_string());\n    test.index.write().apply(pod);\n\n    let mut rx = test\n        .index\n        .write()\n        .pod_server_rx(\"ns-0\", \"pod-0\", 8080.try_into().unwrap())\n        .expect(\"pod-0.ns-0 should exist\");\n    assert_eq!(*rx.borrow_and_update(), test.default_server());\n\n    test.index.write().apply(mk_server(\n        \"ns-0\",\n        \"srv-8080\",\n        Port::Number(8080.try_into().unwrap()),\n        None,\n        Some((\"app\", \"app-0\")),\n        Some(k8s::policy::server::ProxyProtocol::Http1),\n    ));\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(\n        *rx.borrow_and_update(),\n        InboundServer {\n            reference: ServerRef::Server(\"srv-8080\".to_string()),\n            authorizations: Default::default(),\n            ratelimit: None,\n            protocol: ProxyProtocol::Http1,\n            http_routes: mk_default_http_routes(),\n            grpc_routes: mk_default_grpc_routes(),\n        },\n    );\n\n    let authz = ClientAuthorization {\n        networks: vec![\"10.0.0.0/8\".parse::<IpNet>().unwrap().into()],\n        authentication: ClientAuthentication::TlsAuthenticated(vec![IdentityMatch::Exact(\n            \"foo.bar\".to_string(),\n        )]),\n    };\n    test.index.write().apply(mk_authorization_policy(\n        \"ns-0\",\n        \"authz-foo\",\n        Option::<&str>::None,\n        vec![\n            NamespacedTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"NetworkAuthentication\".to_string(),\n                name: \"net-foo\".to_string(),\n                namespace: None,\n            },\n            NamespacedTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"MeshTLSAuthentication\".to_string(),\n                namespace: Some(\"ns-1\".to_string()),\n                name: \"mtls-bar\".to_string(),\n            },\n        ],\n    ));\n    test.index.write().apply(mk_network_authentication(\n        \"ns-0\".to_string(),\n        \"net-foo\".to_string(),\n        vec![k8s::policy::network_authentication::Network {\n            cidr: \"10.0.0.0/8\".parse().unwrap(),\n            except: None,\n        }],\n    ));\n    test.index.write().apply(mk_meshtls_authentication(\n        \"ns-1\",\n        \"mtls-bar\",\n        Some(\"foo.bar\".to_string()),\n        None,\n    ));\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(\n        *rx.borrow(),\n        InboundServer {\n            reference: ServerRef::Server(\"srv-8080\".to_string()),\n            authorizations: hashmap!(\n                AuthorizationRef::AuthorizationPolicy(\"authz-foo\".to_string()) => authz\n            )\n            .into_iter()\n            .collect(),\n            ratelimit: None,\n            protocol: ProxyProtocol::Http1,\n            http_routes: mk_default_http_routes(),\n            grpc_routes: mk_default_grpc_routes(),\n        },\n    );\n}\n\n#[test]\nfn links_authorization_policy_with_service_account() {\n    let test = TestConfig::default();\n\n    let mut pod = mk_pod(\"ns-0\", \"pod-0\", Some((\"container-0\", None)));\n    pod.labels_mut()\n        .insert(\"app\".to_string(), \"app-0\".to_string());\n    test.index.write().apply(pod);\n\n    let mut rx = test\n        .index\n        .write()\n        .pod_server_rx(\"ns-0\", \"pod-0\", 8080.try_into().unwrap())\n        .expect(\"pod-0.ns-0 should exist\");\n    assert_eq!(*rx.borrow_and_update(), test.default_server());\n\n    test.index.write().apply(mk_server(\n        \"ns-0\",\n        \"srv-8080\",\n        Port::Number(8080.try_into().unwrap()),\n        None,\n        Some((\"app\", \"app-0\")),\n        Some(k8s::policy::server::ProxyProtocol::Http1),\n    ));\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(\n        *rx.borrow_and_update(),\n        InboundServer {\n            reference: ServerRef::Server(\"srv-8080\".to_string()),\n            authorizations: Default::default(),\n            ratelimit: None,\n            protocol: ProxyProtocol::Http1,\n            http_routes: mk_default_http_routes(),\n            grpc_routes: mk_default_grpc_routes(),\n        },\n    );\n\n    let authz = ClientAuthorization {\n        networks: vec![\"10.0.0.0/8\".parse::<IpNet>().unwrap().into()],\n        authentication: ClientAuthentication::TlsAuthenticated(vec![IdentityMatch::Exact(\n            \"foo.ns-0.serviceaccount.identity.linkerd.cluster.example.com\".to_string(),\n        )]),\n    };\n    test.index.write().apply(mk_authorization_policy(\n        \"ns-0\",\n        \"authz-foo\",\n        Some(\"srv-8080\"),\n        vec![\n            NamespacedTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"NetworkAuthentication\".to_string(),\n                name: \"net-foo\".to_string(),\n                namespace: None,\n            },\n            NamespacedTargetRef {\n                group: None,\n                kind: \"ServiceAccount\".to_string(),\n                namespace: Some(\"ns-0\".to_string()),\n                name: \"foo\".to_string(),\n            },\n        ],\n    ));\n    test.index.write().apply(mk_network_authentication(\n        \"ns-0\".to_string(),\n        \"net-foo\".to_string(),\n        vec![k8s::policy::network_authentication::Network {\n            cidr: \"10.0.0.0/8\".parse().unwrap(),\n            except: None,\n        }],\n    ));\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(\n        *rx.borrow(),\n        InboundServer {\n            reference: ServerRef::Server(\"srv-8080\".to_string()),\n            authorizations: hashmap!(\n                AuthorizationRef::AuthorizationPolicy(\"authz-foo\".to_string()) => authz\n            )\n            .into_iter()\n            .collect(),\n            ratelimit: None,\n            protocol: ProxyProtocol::Http1,\n            http_routes: mk_default_http_routes(),\n            grpc_routes: mk_default_grpc_routes(),\n        },\n    );\n}\n\n#[test]\nfn authorization_policy_prevents_index_deletion() {\n    let test = TestConfig::default();\n\n    // Create policy resources: Server, HttpRoute, AuthorizationPolicy, and NetworkAuthentication.\n    let server = mk_server(\n        \"ns-0\",\n        \"srv-8080\",\n        Port::Number(8080.try_into().unwrap()),\n        None,\n        Some((\"app\", \"app-0\")),\n        Some(k8s::policy::server::ProxyProtocol::Http1),\n    );\n    test.index.write().apply(server.clone());\n\n    let authz = ClientAuthorization {\n        networks: vec![\"10.0.0.0/8\".parse::<IpNet>().unwrap().into()],\n        authentication: ClientAuthentication::TlsAuthenticated(vec![IdentityMatch::Exact(\n            \"foo.ns-0.serviceaccount.identity.linkerd.cluster.example.com\".to_string(),\n        )]),\n    };\n    let authz_policy = k8s::policy::AuthorizationPolicy {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(\"ns-0\".to_string()),\n            name: Some(\"authz-foo\".to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::AuthorizationPolicySpec {\n            target_ref: LocalTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"HTTPRoute\".to_string(),\n                name: \"route-foo\".to_string(),\n            },\n            required_authentication_refs: vec![\n                NamespacedTargetRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: \"NetworkAuthentication\".to_string(),\n                    name: \"net-foo\".to_string(),\n                    namespace: None,\n                },\n                NamespacedTargetRef {\n                    group: None,\n                    kind: \"ServiceAccount\".to_string(),\n                    namespace: Some(\"ns-0\".to_string()),\n                    name: \"foo\".to_string(),\n                },\n            ],\n        },\n    };\n    test.index.write().apply(authz_policy.clone());\n    let route = mk_http_route(\"ns-0\", \"route-foo\", \"srv-8080\");\n    test.index.write().apply(route.clone());\n    let net_authn = mk_network_authentication(\n        \"ns-0\".to_string(),\n        \"net-foo\".to_string(),\n        vec![k8s::policy::network_authentication::Network {\n            cidr: \"10.0.0.0/8\".parse().unwrap(),\n            except: None,\n        }],\n    );\n    test.index.write().apply(net_authn.clone());\n\n    // Now we delete the server, and HTTPRoute.\n    <Index as IndexNamespacedResource<k8s::policy::Server>>::delete(\n        &mut test.index.write(),\n        \"ns-0\".to_string(),\n        \"srv-8080\".to_string(),\n    );\n    <Index as IndexNamespacedResource<k8s::policy::HttpRoute>>::delete(\n        &mut test.index.write(),\n        \"ns-0\".to_string(),\n        \"route-foo\".to_string(),\n    );\n\n    // Recreate the pod, server, and HTTPRoute.\n    test.index.write().apply(server);\n    test.index.write().apply(route);\n\n    let mut pod = mk_pod(\"ns-0\", \"pod-0\", Some((\"container-0\", None)));\n    pod.labels_mut()\n        .insert(\"app\".to_string(), \"app-0\".to_string());\n    test.index.write().apply(pod.clone());\n\n    let rx = test\n        .index\n        .write()\n        .pod_server_rx(\"ns-0\", \"pod-0\", 8080.try_into().unwrap())\n        .expect(\"pod-0.ns-0 should exist\");\n\n    // AuthorizationPolicy should apply.\n    assert_eq!(\n        *rx.borrow(),\n        InboundServer {\n            reference: ServerRef::Server(\"srv-8080\".to_string()),\n            authorizations: Default::default(),\n            ratelimit: None,\n            protocol: ProxyProtocol::Http1,\n            http_routes: hashmap!(RouteRef::Resource(routes::GroupKindName{\n                group: \"policy.linkerd.io\".into(),\n                kind: \"HTTPRoute\".into(),\n                name: \"route-foo\".into(),\n            }) => HttpRoute {\n                rules: vec![inbound::InboundRouteRule {\n                    matches: vec![routes::HttpRouteMatch {\n                        path: Some(routes::PathMatch::Prefix(\"/foo\".to_string())),\n                        headers: vec![],\n                        query_params: vec![],\n                        method: None,\n                    }],\n                    filters: vec![],\n                }],\n                authorizations: hashmap!(\n                    AuthorizationRef::AuthorizationPolicy(\"authz-foo\".to_string()) => authz.clone()\n                )\n                .into_iter()\n                .collect(),\n                ..Default::default()\n            })\n            .into_iter()\n            .collect(),\n            grpc_routes: mk_default_grpc_routes(),\n        },\n    );\n}\n\nfn mk_authorization_policy(\n    ns: impl ToString,\n    name: impl ToString,\n    server: Option<impl ToString>,\n    authns: impl IntoIterator<Item = NamespacedTargetRef>,\n) -> k8s::policy::AuthorizationPolicy {\n    k8s::policy::AuthorizationPolicy {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::AuthorizationPolicySpec {\n            target_ref: match server {\n                Some(server) => LocalTargetRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: \"Server\".to_string(),\n                    name: server.to_string(),\n                },\n                None => LocalTargetRef {\n                    group: Some(\"core\".to_string()),\n                    kind: \"Namespace\".to_string(),\n                    name: ns.to_string(),\n                },\n            },\n            required_authentication_refs: authns.into_iter().collect(),\n        },\n    }\n}\n\nfn mk_meshtls_authentication(\n    ns: impl ToString,\n    name: impl ToString,\n    identities: impl IntoIterator<Item = String>,\n    refs: impl IntoIterator<Item = NamespacedTargetRef>,\n) -> k8s::policy::MeshTLSAuthentication {\n    let identities = identities.into_iter().collect::<Vec<_>>();\n    let identity_refs = refs.into_iter().collect::<Vec<_>>();\n    k8s::policy::MeshTLSAuthentication {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::MeshTLSAuthenticationSpec {\n            identities: if identities.is_empty() {\n                None\n            } else {\n                Some(identities)\n            },\n            identity_refs: if identity_refs.is_empty() {\n                None\n            } else {\n                Some(identity_refs)\n            },\n        },\n    }\n}\n\nfn mk_network_authentication(\n    ns: impl ToString,\n    name: impl ToString,\n    networks: impl IntoIterator<Item = k8s::policy::network_authentication::Network>,\n) -> k8s::policy::NetworkAuthentication {\n    k8s::policy::NetworkAuthentication {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::NetworkAuthenticationSpec {\n            networks: networks.into_iter().collect(),\n        },\n    }\n}\n\nfn mk_http_route(\n    ns: impl ToString,\n    name: impl ToString,\n    server: impl ToString,\n) -> k8s::policy::HttpRoute {\n    k8s::policy::HttpRoute {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::HttpRouteSpec {\n            parent_refs: Some(vec![gateway::HTTPRouteParentRefs {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: Some(\"Server\".to_string()),\n                namespace: Some(ns.to_string()),\n                name: server.to_string(),\n                section_name: None,\n                port: None,\n            }]),\n            rules: Some(vec![policy::httproute::HttpRouteRule {\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::PathPrefix),\n                    }),\n                    ..Default::default()\n                }]),\n                filters: None,\n                backend_refs: None,\n                timeouts: None,\n            }]),\n            ..Default::default()\n        },\n        status: Some(gateway::HTTPRouteStatus {\n            parents: vec![gateway::HTTPRouteStatusParents {\n                conditions: Some(vec![metav1::Condition {\n                    type_: \"Accepted\".to_string(),\n                    status: \"True\".to_string(),\n                    message: String::new(),\n                    reason: String::new(),\n                    last_transition_time: metav1::Time(chrono::Utc::now()),\n                    observed_generation: None,\n                }]),\n                parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: Some(\"Server\".to_string()),\n                    namespace: Some(ns.to_string()),\n                    name: server.to_string(),\n                    section_name: None,\n                    port: None,\n                },\n                controller_name: \"linkerd.io/policy-controller\".to_string(),\n            }],\n        }),\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/tests/grpc_routes.rs",
    "content": "use super::*;\n\n#[test]\nfn server_with_default_route() {\n    let test = TestConfig::default();\n    // Create pod.\n    let mut pod = mk_pod(\"ns-0\", \"pod-0\", Some((\"container-0\", None)));\n    pod.labels_mut()\n        .insert(\"app\".to_string(), \"app-0\".to_string());\n    test.index.write().apply(pod);\n\n    let mut rx = test\n        .index\n        .write()\n        .pod_server_rx(\"ns-0\", \"pod-0\", 8080.try_into().unwrap())\n        .expect(\"pod-0.ns-0 should exist\");\n    assert_eq!(*rx.borrow_and_update(), test.default_server());\n\n    // Create server.\n    test.index.write().apply(mk_server(\n        \"ns-0\",\n        \"srv-8080\",\n        Port::Number(8080.try_into().unwrap()),\n        Some((\"app\", \"app-0\")),\n        Some((\"app\", \"app-0\")),\n        Some(k8s::policy::server::ProxyProtocol::Grpc),\n    ));\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(\n        *rx.borrow_and_update(),\n        InboundServer {\n            reference: ServerRef::Server(\"srv-8080\".to_string()),\n            authorizations: Default::default(),\n            ratelimit: None,\n            protocol: ProxyProtocol::Grpc,\n            http_routes: mk_default_http_routes(),\n            grpc_routes: mk_default_grpc_routes(),\n        },\n    );\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/tests/http_routes.rs",
    "content": "use super::*;\nuse crate::routes::ExplicitGKN;\nuse linkerd_policy_controller_core::{\n    routes::{HttpRouteMatch, Method, PathMatch},\n    POLICY_CONTROLLER_NAME,\n};\nuse linkerd_policy_controller_k8s_api::{gateway, policy};\n\nconst POLICY_API_GROUP: &str = \"policy.linkerd.io\";\n\n#[test]\nfn route_attaches_to_server() {\n    let test = TestConfig::default();\n    // Create pod.\n    let mut pod = mk_pod(\"ns-0\", \"pod-0\", Some((\"container-0\", None)));\n    pod.labels_mut()\n        .insert(\"app\".to_string(), \"app-0\".to_string());\n    test.index.write().apply(pod);\n\n    let mut rx = test\n        .index\n        .write()\n        .pod_server_rx(\"ns-0\", \"pod-0\", 8080.try_into().unwrap())\n        .expect(\"pod-0.ns-0 should exist\");\n    assert_eq!(*rx.borrow_and_update(), test.default_server());\n\n    // Create server.\n    test.index.write().apply(mk_server(\n        \"ns-0\",\n        \"srv-8080\",\n        Port::Number(8080.try_into().unwrap()),\n        Some((\"app\", \"app-0\")),\n        Some((\"app\", \"app-0\")),\n        Some(k8s::policy::server::ProxyProtocol::Http1),\n    ));\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(\n        *rx.borrow_and_update(),\n        InboundServer {\n            reference: ServerRef::Server(\"srv-8080\".to_string()),\n            authorizations: Default::default(),\n            ratelimit: None,\n            protocol: ProxyProtocol::Http1,\n            http_routes: mk_default_http_routes(),\n            grpc_routes: mk_default_grpc_routes(),\n        },\n    );\n\n    // Create route.\n    test.index\n        .write()\n        .apply(mk_route(\"ns-0\", \"route-foo\", \"srv-8080\"));\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(\n        rx.borrow().reference,\n        ServerRef::Server(\"srv-8080\".to_string())\n    );\n    assert!(rx\n        .borrow_and_update()\n        .http_routes\n        .contains_key(&RouteRef::Resource(\"route-foo\".gkn::<policy::HttpRoute>())));\n\n    // Create authz policy.\n    test.index.write().apply(mk_authorization_policy(\n        \"ns-0\",\n        \"authz-foo\",\n        \"route-foo\",\n        vec![NamespacedTargetRef {\n            group: None,\n            kind: \"ServiceAccount\".to_string(),\n            namespace: Some(\"ns-0\".to_string()),\n            name: \"foo\".to_string(),\n        }],\n    ));\n\n    assert!(rx.has_changed().unwrap());\n    assert!(\n        rx.borrow().http_routes[&RouteRef::Resource(\"route-foo\".gkn::<policy::HttpRoute>())]\n            .authorizations\n            .contains_key(&AuthorizationRef::AuthorizationPolicy(\n                \"authz-foo\".to_string()\n            ))\n    );\n}\n\n#[test]\nfn routes_created_for_probes() {\n    let policy = DefaultPolicy::Allow {\n        authenticated_only: false,\n        cluster_only: true,\n    };\n    let probe_networks = vec![\"10.0.0.1/24\".parse().unwrap()];\n    let test = TestConfig::from_default_policy_with_probes(policy, probe_networks);\n\n    // Create a pod.\n    let container = k8s::Container {\n        liveness_probe: Some(k8s::Probe {\n            http_get: Some(k8s::HTTPGetAction {\n                path: Some(\"/liveness-container-1\".to_string()),\n                port: k8s::IntOrString::Int(5432),\n                ..Default::default()\n            }),\n            ..Default::default()\n        }),\n        readiness_probe: Some(k8s::Probe {\n            http_get: Some(k8s::HTTPGetAction {\n                path: Some(\"/ready-container-1\".to_string()),\n                port: k8s::IntOrString::Int(5432),\n                ..Default::default()\n            }),\n            ..Default::default()\n        }),\n        ..Default::default()\n    };\n    let mut pod = mk_pod_with_containers(\"ns-0\", \"pod-0\", Some(container));\n    pod.labels_mut()\n        .insert(\"app\".to_string(), \"app-0\".to_string());\n    test.index.write().apply(pod);\n\n    let mut rx = test\n        .index\n        .write()\n        .pod_server_rx(\"ns-0\", \"pod-0\", 5432.try_into().unwrap())\n        .expect(\"pod-0.ns-0 should exist\");\n\n    let mut expected_authorizations = HashMap::default();\n    expected_authorizations.insert(\n        AuthorizationRef::Default(\"probe\"),\n        ClientAuthorization {\n            networks: vec![\"10.0.0.1/24\".parse::<IpNet>().unwrap().into()],\n            authentication: ClientAuthentication::Unauthenticated,\n        },\n    );\n    let liveness_match = HttpRouteMatch {\n        path: Some(PathMatch::Exact(\"/liveness-container-1\".to_string())),\n        headers: vec![],\n        query_params: vec![],\n        method: Some(Method::GET),\n    };\n    let ready_match = HttpRouteMatch {\n        path: Some(PathMatch::Exact(\"/ready-container-1\".to_string())),\n        headers: vec![],\n        query_params: vec![],\n        method: Some(Method::GET),\n    };\n\n    // No Server is configured for the port, so expect the probe paths to be\n    // authorized.\n    let update = rx.borrow_and_update();\n    let probes = update.http_routes.get(&RouteRef::Default(\"probe\")).unwrap();\n    let probes_rules = probes.rules.first().unwrap();\n    assert!(\n        probes_rules.matches.contains(&liveness_match),\n        \"matches: {:#?}\",\n        probes_rules.matches\n    );\n    assert!(\n        probes_rules.matches.contains(&ready_match),\n        \"matches: {:#?}\",\n        probes_rules.matches\n    );\n    assert_eq!(probes.authorizations, expected_authorizations);\n    drop(update);\n\n    // // Create server.\n    test.index.write().apply(mk_server(\n        \"ns-0\",\n        \"srv-5432\",\n        Port::Number(5432.try_into().unwrap()),\n        Some((\"app\", \"app-0\")),\n        Some((\"app\", \"app-0\")),\n        Some(k8s::policy::server::ProxyProtocol::Http1),\n    ));\n    assert!(rx.has_changed().unwrap());\n\n    // // No routes are configured for the Server, so we should still expect the\n    // // Pod's probe paths to be authorized.\n    let update = rx.borrow_and_update();\n    let probes = update.http_routes.get(&RouteRef::Default(\"probe\")).unwrap();\n    let probes_rules = probes.rules.first().unwrap();\n    assert!(\n        probes_rules.matches.contains(&liveness_match),\n        \"matches: {:#?}\",\n        probes_rules.matches\n    );\n    assert!(\n        probes_rules.matches.contains(&ready_match),\n        \"matches: {:#?}\",\n        probes_rules.matches\n    );\n    assert_eq!(probes.authorizations, expected_authorizations);\n    drop(update);\n\n    // Create route.\n    test.index\n        .write()\n        .apply(mk_route(\"ns-0\", \"route-foo\", \"srv-5432\"));\n    assert!(rx.has_changed().unwrap());\n\n    // A route is now configured for the Server, so the Pod's probe paths\n    // should not be automatically authorized.\n    assert!(!rx\n        .borrow_and_update()\n        .http_routes\n        .contains_key(&RouteRef::Default(\"probes\")));\n}\n\nfn mk_route(\n    ns: impl ToString,\n    name: impl ToString,\n    server: impl ToString,\n) -> k8s::policy::HttpRoute {\n    use chrono::Utc;\n    use k8s::{policy::httproute::*, Time};\n\n    HttpRoute {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            creation_timestamp: Some(Time(Utc::now())),\n            ..Default::default()\n        },\n        spec: HttpRouteSpec {\n            parent_refs: Some(vec![gateway::HTTPRouteParentRefs {\n                group: Some(POLICY_API_GROUP.to_string()),\n                kind: Some(\"Server\".to_string()),\n                namespace: None,\n                name: server.to_string(),\n                section_name: None,\n                port: None,\n            }]),\n            hostnames: None,\n            rules: Some(vec![HttpRouteRule {\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo/bar\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::PathPrefix),\n                    }),\n                    headers: None,\n                    query_params: None,\n                    method: Some(gateway::HTTPRouteRulesMatchesMethod::Get),\n                }]),\n                filters: None,\n                backend_refs: None,\n                timeouts: None,\n            }]),\n        },\n        status: Some(gateway::HTTPRouteStatus {\n            parents: vec![gateway::HTTPRouteStatusParents {\n                parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n                    group: Some(POLICY_API_GROUP.to_string()),\n                    kind: Some(\"Server\".to_string()),\n                    namespace: None,\n                    name: server.to_string(),\n                    section_name: None,\n                    port: None,\n                },\n                controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                conditions: Some(vec![k8s::Condition {\n                    last_transition_time: k8s::Time(chrono::DateTime::<chrono::Utc>::MIN_UTC),\n                    message: \"\".to_string(),\n                    observed_generation: None,\n                    reason: \"Accepted\".to_string(),\n                    status: \"True\".to_string(),\n                    type_: \"Accepted\".to_string(),\n                }]),\n            }],\n        }),\n    }\n}\nfn mk_authorization_policy(\n    ns: impl ToString,\n    name: impl ToString,\n    route: impl ToString,\n    authns: impl IntoIterator<Item = NamespacedTargetRef>,\n) -> k8s::policy::AuthorizationPolicy {\n    k8s::policy::AuthorizationPolicy {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::AuthorizationPolicySpec {\n            target_ref: LocalTargetRef {\n                group: Some(POLICY_API_GROUP.to_string()),\n                kind: \"HttpRoute\".to_string(),\n                name: route.to_string(),\n            },\n            required_authentication_refs: authns.into_iter().collect(),\n        },\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/tests/ratelimit_policy.rs",
    "content": "use super::*;\nuse linkerd_policy_controller_core::inbound::{Limit, Override, RateLimit};\n\n#[test]\nfn ratelimit_policy_with_server() {\n    let test = TestConfig::default();\n\n    let mut pod = mk_pod(\"ns-0\", \"pod-0\", Some((\"container-0\", None)));\n    pod.labels_mut()\n        .insert(\"app\".to_string(), \"app-0\".to_string());\n    test.index.write().apply(pod);\n\n    let mut rx = test\n        .index\n        .write()\n        .pod_server_rx(\"ns-0\", \"pod-0\", 8080.try_into().unwrap())\n        .expect(\"pod-0.ns-0 should exist\");\n    assert_eq!(*rx.borrow_and_update(), test.default_server());\n\n    test.index.write().apply(mk_server(\n        \"ns-0\",\n        \"srv-8080\",\n        Port::Number(8080.try_into().unwrap()),\n        None,\n        Some((\"app\", \"app-0\")),\n        Some(k8s::policy::server::ProxyProtocol::Http1),\n    ));\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(\n        *rx.borrow_and_update(),\n        InboundServer {\n            reference: ServerRef::Server(\"srv-8080\".to_string()),\n            authorizations: Default::default(),\n            ratelimit: None,\n            protocol: ProxyProtocol::Http1,\n            http_routes: mk_default_http_routes(),\n            grpc_routes: mk_default_grpc_routes(),\n        },\n    );\n\n    let ratelimit = RateLimit {\n        name: \"ratelimit-0\".to_string(),\n        total: Some(Limit {\n            requests_per_second: 1000,\n        }),\n        identity: None,\n        overrides: vec![Override {\n            requests_per_second: 500,\n            client_identities: vec![\n                \"client-0.ns-0.serviceaccount.identity.linkerd.cluster.example.com\".to_string(),\n            ],\n        }],\n    };\n    test.index.write().apply(mk_ratelimit(\n        \"ns-0\",\n        \"ratelimit-0\",\n        Some(k8s::policy::Limit {\n            requests_per_second: 1000,\n        }),\n        vec![k8s::policy::Override {\n            requests_per_second: 500,\n            client_refs: vec![NamespacedTargetRef {\n                group: None,\n                kind: \"ServiceAccount\".to_string(),\n                name: \"client-0\".to_string(),\n                namespace: None,\n            }],\n        }],\n        \"srv-8080\",\n    ));\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(\n        *rx.borrow_and_update(),\n        InboundServer {\n            reference: ServerRef::Server(\"srv-8080\".to_string()),\n            authorizations: Default::default(),\n            ratelimit: Some(ratelimit),\n            protocol: ProxyProtocol::Http1,\n            http_routes: mk_default_http_routes(),\n            grpc_routes: mk_default_grpc_routes(),\n        },\n    );\n}\n\nfn mk_ratelimit(\n    ns: impl ToString,\n    name: impl ToString,\n    total: Option<k8s::policy::Limit>,\n    overrides: Vec<k8s::policy::Override>,\n    server_name: impl ToString,\n) -> k8s::policy::HttpLocalRateLimitPolicy {\n    k8s::policy::HttpLocalRateLimitPolicy {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::RateLimitPolicySpec {\n            target_ref: LocalTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"Server\".to_string(),\n                name: server_name.to_string(),\n            },\n            total,\n            identity: None,\n            overrides: Some(overrides),\n        },\n        status: Some(k8s::policy::HttpLocalRateLimitPolicyStatus {\n            conditions: vec![k8s::Condition {\n                last_transition_time: k8s::Time(chrono::DateTime::<chrono::Utc>::MIN_UTC),\n                message: \"\".to_string(),\n                observed_generation: None,\n                reason: \"\".to_string(),\n                status: \"True\".to_string(),\n                type_: \"Accepted\".to_string(),\n            }],\n            target_ref: LocalTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"Server\".to_string(),\n                name: server_name.to_string(),\n            },\n        }),\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/tests/server.rs",
    "content": "use super::*;\n\n#[test]\nfn links_named_server_port() {\n    let test = TestConfig::default();\n\n    let mut pod = mk_pod(\n        \"ns-0\",\n        \"pod-0\",\n        Some((\n            \"container-0\",\n            Some(ContainerPort {\n                name: Some(\"admin\".to_string()),\n                container_port: 8080,\n                protocol: Some(\"TCP\".to_string()),\n                ..ContainerPort::default()\n            }),\n        )),\n    );\n    pod.labels_mut()\n        .insert(\"app\".to_string(), \"app-0\".to_string());\n    test.index.write().apply(pod);\n\n    let mut rx = test\n        .index\n        .write()\n        .pod_server_rx(\"ns-0\", \"pod-0\", 8080)\n        .expect(\"pod-0.ns-0 should exist\");\n    assert_eq!(*rx.borrow_and_update(), test.default_server());\n\n    test.index.write().apply(mk_server(\n        \"ns-0\",\n        \"srv-admin\",\n        Port::Name(\"admin\".to_string()),\n        None,\n        Some((\"app\", \"app-0\")),\n        Some(k8s::policy::server::ProxyProtocol::Http1),\n    ));\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(\n        *rx.borrow_and_update(),\n        InboundServer {\n            reference: ServerRef::Server(\"srv-admin\".to_string()),\n            authorizations: Default::default(),\n            protocol: ProxyProtocol::Http1,\n        },\n    );\n}\n\n#[test]\nfn links_unnamed_server_port() {\n    let test = TestConfig::default();\n\n    let mut pod = mk_pod(\"ns-0\", \"pod-0\", Some((\"container-0\", None)));\n    pod.labels_mut()\n        .insert(\"app\".to_string(), \"app-0\".to_string());\n    test.index.write().apply(pod);\n\n    let mut rx = test\n        .index\n        .write()\n        .pod_server_rx(\"ns-0\", \"pod-0\", 8080)\n        .expect(\"pod-0.ns-0 should exist\");\n    assert_eq!(*rx.borrow_and_update(), test.default_server());\n\n    test.index.write().apply(mk_server(\n        \"ns-0\",\n        \"srv-8080\",\n        Port::Number(8080),\n        None,\n        Some((\"app\", \"app-0\")),\n        Some(k8s::policy::server::ProxyProtocol::Http1),\n    ));\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(\n        *rx.borrow_and_update(),\n        InboundServer {\n            reference: ServerRef::Server(\"srv-8080\".to_string()),\n            authorizations: Default::default(),\n            protocol: ProxyProtocol::Http1,\n        },\n    );\n}\n\n#[test]\nfn server_update_deselects_pod() {\n    let test = TestConfig::default();\n\n    test.index.write().reset(\n        vec![mk_pod(\"ns-0\", \"pod-0\", Some((\"container-0\", None)))],\n        Default::default(),\n    );\n\n    let mut srv = mk_server(\n        \"ns-0\",\n        \"srv-0\",\n        Port::Number(2222),\n        None,\n        None,\n        Some(k8s::policy::server::ProxyProtocol::Http2),\n    );\n    test.index\n        .write()\n        .reset(vec![srv.clone()], Default::default());\n\n    // The default policy applies for all ports.\n    let mut rx = test\n        .index\n        .write()\n        .pod_server_rx(\"ns-0\", \"pod-0\", 2222)\n        .unwrap();\n    assert_eq!(\n        *rx.borrow_and_update(),\n        InboundServer {\n            reference: ServerRef::Server(\"srv-0\".to_string()),\n            protocol: ProxyProtocol::Http2,\n            authorizations: Default::default(),\n        }\n    );\n\n    test.index.write().apply({\n        srv.spec.pod_selector = Some((\"label\", \"value\")).into_iter().collect();\n        srv\n    });\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(*rx.borrow(), test.default_server());\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/tests/server_authorization.rs",
    "content": "use super::*;\n\n#[test]\nfn links_server_authz_by_name() {\n    link_server_authz(ServerSelector::Name(\"srv-8080\".to_string()))\n}\n\n#[test]\nfn links_server_authz_by_label() {\n    link_server_authz(ServerSelector::Selector(\n        Some((\"app\", \"app-0\")).into_iter().collect(),\n    ));\n}\n\n#[inline]\nfn link_server_authz(selector: ServerSelector) {\n    let test = TestConfig::default();\n\n    let mut pod = mk_pod(\"ns-0\", \"pod-0\", Some((\"container-0\", None)));\n    pod.labels_mut()\n        .insert(\"app\".to_string(), \"app-0\".to_string());\n    test.index.write().apply(pod);\n\n    let mut rx = test\n        .index\n        .write()\n        .pod_server_rx(\"ns-0\", \"pod-0\", 8080.try_into().unwrap())\n        .expect(\"pod-0.ns-0 should exist\");\n    assert_eq!(*rx.borrow_and_update(), test.default_server());\n\n    test.index.write().apply(mk_server(\n        \"ns-0\",\n        \"srv-8080\",\n        Port::Number(8080.try_into().unwrap()),\n        Some((\"app\", \"app-0\")),\n        Some((\"app\", \"app-0\")),\n        Some(k8s::policy::server::ProxyProtocol::Http1),\n    ));\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(\n        *rx.borrow_and_update(),\n        InboundServer {\n            reference: ServerRef::Server(\"srv-8080\".to_string()),\n            authorizations: Default::default(),\n            ratelimit: None,\n            protocol: ProxyProtocol::Http1,\n            http_routes: mk_default_http_routes(),\n            grpc_routes: mk_default_grpc_routes(),\n        },\n    );\n    test.index.write().apply(mk_server_authz(\n        \"ns-0\",\n        \"authz-foo\",\n        selector,\n        k8s::policy::server_authorization::Client {\n            networks: Some(vec![k8s::policy::server_authorization::Network {\n                cidr: \"10.0.0.0/8\".parse().unwrap(),\n                except: None,\n            }]),\n            unauthenticated: false,\n            mesh_tls: Some(k8s::policy::server_authorization::MeshTls {\n                identities: Some(vec![\"foo.bar\".to_string()]),\n                ..Default::default()\n            }),\n        },\n    ));\n    assert!(rx.has_changed().unwrap());\n    assert_eq!(\n        rx.borrow().reference,\n        ServerRef::Server(\"srv-8080\".to_string())\n    );\n    assert_eq!(rx.borrow().protocol, ProxyProtocol::Http1,);\n    assert!(rx\n        .borrow()\n        .authorizations\n        .contains_key(&AuthorizationRef::ServerAuthorization(\n            \"authz-foo\".to_string()\n        )));\n}\n\nfn mk_server_authz(\n    ns: impl ToString,\n    name: impl ToString,\n    selector: ServerSelector,\n    client: k8s::policy::server_authorization::Client,\n) -> k8s::policy::ServerAuthorization {\n    k8s::policy::ServerAuthorization {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::ServerAuthorizationSpec {\n            server: match selector {\n                ServerSelector::Name(n) => k8s::policy::server_authorization::Server {\n                    name: Some(n),\n                    selector: None,\n                },\n                ServerSelector::Selector(s) => k8s::policy::server_authorization::Server {\n                    selector: Some(s),\n                    name: None,\n                },\n            },\n            client,\n        },\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/tests.rs",
    "content": "mod annotation;\nmod authorization_policy;\nmod grpc_routes;\nmod http_routes;\nmod ratelimit_policy;\nmod server_authorization;\n\nuse crate::{\n    defaults::DefaultPolicy,\n    inbound::index::{Index, SharedIndex},\n    inbound::server_authorization::ServerSelector,\n    ClusterInfo,\n};\nuse ahash::AHashMap as HashMap;\nuse kubert::index::IndexNamespacedResource;\nuse linkerd_policy_controller_core::{\n    inbound::{\n        AuthorizationRef, ClientAuthentication, ClientAuthorization, GrpcRoute, HttpRoute,\n        InboundServer, ProxyProtocol, RouteRef, ServerRef,\n    },\n    IdentityMatch, IpNet, Ipv4Net, Ipv6Net, NetworkMatch,\n};\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s,\n    api::core::v1::{Container, ContainerPort},\n    policy::{server::Port, LocalTargetRef, NamespacedTargetRef},\n    ResourceExt,\n};\nuse maplit::*;\nuse std::sync::Arc;\nuse tokio::time;\n\n#[test]\nfn pod_must_exist_for_lookup() {\n    let test = TestConfig::default();\n    test.index\n        .write()\n        .pod_server_rx(\"ns-0\", \"pod-0\", 8080.try_into().unwrap())\n        .expect_err(\"pod-0.ns-0 must not exist\");\n}\n\nstruct TestConfig {\n    index: SharedIndex,\n    detect_timeout: time::Duration,\n    default_policy: DefaultPolicy,\n    cluster: ClusterInfo,\n    _tracing: tracing::subscriber::DefaultGuard,\n}\n\nconst DEFAULTS: [DefaultPolicy; 6] = [\n    DefaultPolicy::Deny,\n    DefaultPolicy::Allow {\n        authenticated_only: true,\n        cluster_only: false,\n    },\n    DefaultPolicy::Allow {\n        authenticated_only: false,\n        cluster_only: false,\n    },\n    DefaultPolicy::Allow {\n        authenticated_only: true,\n        cluster_only: true,\n    },\n    DefaultPolicy::Allow {\n        authenticated_only: false,\n        cluster_only: true,\n    },\n    DefaultPolicy::Audit,\n];\n\npub fn mk_pod_with_containers(\n    ns: impl ToString,\n    name: impl ToString,\n    containers: impl IntoIterator<Item = Container>,\n) -> k8s::Pod {\n    k8s::Pod {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: Some(k8s::api::core::v1::PodSpec {\n            containers: containers.into_iter().collect(),\n            ..Default::default()\n        }),\n        ..k8s::Pod::default()\n    }\n}\n\nfn mk_pod(\n    ns: impl ToString,\n    name: impl ToString,\n    containers: impl IntoIterator<Item = (impl ToString, impl IntoIterator<Item = ContainerPort>)>,\n) -> k8s::Pod {\n    let containers = containers.into_iter().map(|(name, ports)| Container {\n        name: name.to_string(),\n        ports: Some(ports.into_iter().collect()),\n        ..Default::default()\n    });\n    mk_pod_with_containers(ns, name, containers)\n}\n\nfn mk_server(\n    ns: impl ToString,\n    name: impl ToString,\n    port: Port,\n    srv_labels: impl IntoIterator<Item = (&'static str, &'static str)>,\n    pod_labels: impl IntoIterator<Item = (&'static str, &'static str)>,\n    proxy_protocol: Option<k8s::policy::server::ProxyProtocol>,\n) -> k8s::policy::Server {\n    k8s::policy::Server {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            labels: Some(\n                srv_labels\n                    .into_iter()\n                    .map(|(k, v)| (k.to_string(), v.to_string()))\n                    .collect(),\n            ),\n            ..Default::default()\n        },\n        spec: k8s::policy::ServerSpec {\n            port,\n            selector: k8s::policy::server::Selector::Pod(pod_labels.into_iter().collect()),\n            proxy_protocol,\n            access_policy: None,\n        },\n    }\n}\n\nfn mk_default_policy(\n    da: DefaultPolicy,\n    cluster_nets: Vec<IpNet>,\n) -> HashMap<AuthorizationRef, ClientAuthorization> {\n    let all_nets = vec![Ipv4Net::default().into(), Ipv6Net::default().into()];\n\n    let cluster_nets = cluster_nets.into_iter().map(NetworkMatch::from).collect();\n\n    let authed = ClientAuthentication::TlsAuthenticated(vec![IdentityMatch::Suffix(vec![])]);\n\n    match da {\n        DefaultPolicy::Deny => None,\n        DefaultPolicy::Allow {\n            authenticated_only: true,\n            cluster_only: false,\n        } => Some((\n            AuthorizationRef::Default(\"all-authenticated\"),\n            ClientAuthorization {\n                authentication: authed,\n                networks: all_nets,\n            },\n        )),\n        DefaultPolicy::Allow {\n            authenticated_only: false,\n            cluster_only: false,\n        } => Some((\n            AuthorizationRef::Default(\"all-unauthenticated\"),\n            ClientAuthorization {\n                authentication: ClientAuthentication::Unauthenticated,\n                networks: all_nets,\n            },\n        )),\n        DefaultPolicy::Allow {\n            authenticated_only: true,\n            cluster_only: true,\n        } => Some((\n            AuthorizationRef::Default(\"cluster-authenticated\"),\n            ClientAuthorization {\n                authentication: authed,\n                networks: cluster_nets,\n            },\n        )),\n        DefaultPolicy::Allow {\n            authenticated_only: false,\n            cluster_only: true,\n        } => Some((\n            AuthorizationRef::Default(\"cluster-unauthenticated\"),\n            ClientAuthorization {\n                authentication: ClientAuthentication::Unauthenticated,\n                networks: cluster_nets,\n            },\n        )),\n        DefaultPolicy::Audit => Some((\n            AuthorizationRef::Default(\"audit\"),\n            ClientAuthorization {\n                authentication: ClientAuthentication::Unauthenticated,\n                networks: all_nets,\n            },\n        )),\n    }\n    .into_iter()\n    .collect()\n}\n\nfn mk_default_http_routes() -> HashMap<RouteRef, HttpRoute> {\n    Some((RouteRef::Default(\"default\"), HttpRoute::default()))\n        .into_iter()\n        .collect()\n}\n\nfn mk_default_grpc_routes() -> HashMap<RouteRef, GrpcRoute> {\n    Some((RouteRef::Default(\"default\"), GrpcRoute::default()))\n        .into_iter()\n        .collect()\n}\n\nimpl TestConfig {\n    fn from_default_policy(default_policy: DefaultPolicy) -> Self {\n        Self::from_default_policy_with_probes(default_policy, vec![])\n    }\n\n    fn from_default_policy_with_probes(\n        default_policy: DefaultPolicy,\n        probe_networks: Vec<IpNet>,\n    ) -> Self {\n        let _tracing = Self::init_tracing();\n        let cluster_net = \"192.0.2.0/24\".parse().unwrap();\n        let detect_timeout = time::Duration::from_secs(1);\n        let cluster = ClusterInfo {\n            networks: vec![cluster_net],\n            control_plane_ns: \"linkerd\".to_string(),\n            identity_domain: \"cluster.example.com\".into(),\n            dns_domain: \"cluster.example.com\".into(),\n            default_policy,\n            default_detect_timeout: detect_timeout,\n            default_opaque_ports: Default::default(),\n            probe_networks,\n            global_egress_network_namespace: Arc::new(\"linkerd-egress\".to_string()),\n        };\n        let index = Index::shared(cluster.clone());\n        Self {\n            index,\n            cluster,\n            detect_timeout,\n            default_policy,\n            _tracing,\n        }\n    }\n\n    fn default_server(&self) -> InboundServer {\n        InboundServer {\n            reference: ServerRef::Default(self.default_policy.as_str()),\n            authorizations: mk_default_policy(self.default_policy, self.cluster.networks.clone()),\n            ratelimit: None,\n            protocol: ProxyProtocol::Detect {\n                timeout: self.detect_timeout,\n            },\n            http_routes: mk_default_http_routes(),\n            grpc_routes: Default::default(),\n        }\n    }\n\n    fn init_tracing() -> tracing::subscriber::DefaultGuard {\n        tracing::subscriber::set_default(\n            tracing_subscriber::fmt()\n                .with_test_writer()\n                .with_max_level(tracing::Level::TRACE)\n                .finish(),\n        )\n    }\n}\n\nimpl Default for TestConfig {\n    fn default() -> TestConfig {\n        Self::from_default_policy(DefaultPolicy::Allow {\n            authenticated_only: false,\n            cluster_only: true,\n        })\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound/workload.rs",
    "content": "use crate::defaults::DefaultPolicy;\nuse crate::ports::{parse_portset, PortMap, PortSet};\nuse ahash::AHashMap as HashMap;\nuse anyhow::Result;\nuse linkerd_policy_controller_k8s_api as k8s;\nuse std::{collections::BTreeSet, num::NonZeroU16};\n\n/// Holds workload metadata/config that can change.\n#[derive(Debug, PartialEq)]\npub(crate) struct Meta {\n    /// The workload's labels. Used by `Server` selectors.\n    pub labels: k8s::Labels,\n\n    // Workload-specific settings (i.e., derived from annotations).\n    pub settings: Settings,\n}\n\n/// Per-workload settings, as configured by the workload's annotations.\n#[derive(Debug, Default, PartialEq)]\npub(crate) struct Settings {\n    pub require_id_ports: PortSet,\n    pub opaque_ports: PortSet,\n    pub default_policy: Option<DefaultPolicy>,\n}\n\n/// Gets the set of named ports with `protocol: TCP` from a pod spec.\npub(crate) fn pod_tcp_ports_by_name(spec: &k8s::PodSpec) -> HashMap<String, PortSet> {\n    let mut ports = HashMap::<String, PortSet>::default();\n    for (port, name) in spec\n        .containers\n        .iter()\n        .chain(spec.init_containers.iter().flatten())\n        .flat_map(|c| c.ports.iter().flatten())\n        .filter_map(named_tcp_port)\n    {\n        ports.entry(name.to_string()).or_default().insert(port);\n    }\n    ports\n}\n\n/// Gets the set of named ports withn `protocol: TCP` from an external workload\n/// spec.\n///\n/// Since an external workload has only one set of ports, each name is\n/// guaranteed to be unique.\npub(crate) fn external_tcp_ports_by_name(\n    spec: &k8s::external_workload::ExternalWorkloadSpec,\n) -> HashMap<String, NonZeroU16> {\n    let mut ports = HashMap::<String, NonZeroU16>::default();\n    for (port, name) in spec\n        .ports\n        .iter()\n        .flatten()\n        .filter_map(named_external_tcp_port)\n    {\n        ports.insert(name.into(), port);\n    }\n    ports\n}\n\n/// Gets the container probe ports for a Pod.\n///\n/// The result is a mapping for each probe port exposed by a container in the\n/// Pod and the paths for which probes are expected.\npub(crate) fn pod_http_probes(pod: &k8s::PodSpec) -> PortMap<BTreeSet<String>> {\n    let mut probes = PortMap::<BTreeSet<String>>::default();\n    for (port, path) in pod\n        .containers\n        .iter()\n        .chain(pod.init_containers.iter().flatten())\n        .flat_map(container_http_probe_paths)\n    {\n        probes.entry(port).or_default().insert(path);\n    }\n    probes\n}\n\nfn container_http_probe_paths(\n    container: &k8s::Container,\n) -> impl Iterator<Item = (NonZeroU16, String)> + '_ {\n    fn find_by_name(name: &str, ports: &[k8s::ContainerPort]) -> Option<NonZeroU16> {\n        for (p, n) in ports.iter().filter_map(named_tcp_port) {\n            if n.eq_ignore_ascii_case(name) {\n                return Some(p);\n            }\n        }\n        None\n    }\n\n    fn get_port(port: &k8s::IntOrString, container: &k8s::Container) -> Option<NonZeroU16> {\n        match port {\n            k8s::IntOrString::Int(p) => u16::try_from(*p).ok()?.try_into().ok(),\n            k8s::IntOrString::String(n) => find_by_name(n, container.ports.as_ref()?),\n        }\n    }\n\n    (container.liveness_probe.iter())\n        .chain(container.readiness_probe.iter())\n        .chain(container.startup_probe.iter())\n        .filter_map(|p| {\n            let probe = p.http_get.as_ref()?;\n            let port = get_port(&probe.port, container)?;\n            let path = probe.path.as_deref().unwrap_or(\"/\");\n            match http::Uri::try_from(path) {\n                Ok(uri) => Some((port, uri.path().to_string())),\n                Err(error) => {\n                    tracing::warn!(%error, path, \"Invalid probe path\");\n                    None\n                }\n            }\n        })\n}\n\nfn named_tcp_port(port: &k8s::ContainerPort) -> Option<(NonZeroU16, &str)> {\n    if let Some(ref proto) = port.protocol {\n        if !proto.eq_ignore_ascii_case(\"TCP\") {\n            return None;\n        }\n    }\n    let p = u16::try_from(port.container_port)\n        .and_then(NonZeroU16::try_from)\n        .ok()?;\n    let n = port.name.as_deref()?;\n    Some((p, n))\n}\n\nfn named_external_tcp_port(spec: &k8s::external_workload::PortSpec) -> Option<(NonZeroU16, &str)> {\n    if let Some(ref proto) = spec.protocol {\n        if !proto.eq_ignore_ascii_case(\"TCP\") {\n            return None;\n        }\n    }\n    let n = spec.name.as_deref()?;\n    Some((spec.port, n))\n}\n\n// === impl Meta ===\n\nimpl Meta {\n    pub(crate) fn from_metadata(meta: k8s::ObjectMeta) -> Self {\n        let settings = Settings::from_metadata(&meta);\n        tracing::trace!(?settings);\n        Self {\n            settings,\n            labels: meta.labels.into(),\n        }\n    }\n}\n\n// === impl Settings ===\n\nimpl Settings {\n    /// Reads pod settings from the pod metadata including:\n    ///\n    /// - Opaque ports\n    /// - Ports that require identity\n    /// - The pod's default policy\n    pub(crate) fn from_metadata(meta: &k8s::ObjectMeta) -> Self {\n        let anns = match meta.annotations.as_ref() {\n            None => return Self::default(),\n            Some(anns) => anns,\n        };\n\n        let default_policy = default_policy(anns).unwrap_or_else(|error| {\n            tracing::warn!(%error, \"Invalid default policy annotation value\");\n            None\n        });\n\n        let opaque_ports =\n            ports_annotation(anns, \"config.linkerd.io/opaque-ports\").unwrap_or_default();\n        let require_id_ports = ports_annotation(\n            anns,\n            \"config.linkerd.io/proxy-require-identity-inbound-ports\",\n        )\n        .unwrap_or_default();\n\n        Self {\n            default_policy,\n            opaque_ports,\n            require_id_ports,\n        }\n    }\n}\n\n/// Attempts to read a default policy override from an annotation map.\nfn default_policy(\n    ann: &std::collections::BTreeMap<String, String>,\n) -> Result<Option<DefaultPolicy>> {\n    if let Some(v) = ann.get(\"config.linkerd.io/default-inbound-policy\") {\n        let mode = v.parse()?;\n        return Ok(Some(mode));\n    }\n\n    Ok(None)\n}\n\n/// Reads `annotation` from the provided set of annotations, parsing it as a port set.  If the\n/// annotation is not set or is invalid, the empty set is returned.\npub(crate) fn ports_annotation(\n    annotations: &std::collections::BTreeMap<String, String>,\n    annotation: &str,\n) -> Option<PortSet> {\n    annotations.get(annotation).map(|spec| {\n        parse_portset(spec).unwrap_or_else(|error| {\n            tracing::info!(%spec, %error, %annotation, \"Invalid ports list\");\n            Default::default()\n        })\n    })\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use linkerd_policy_controller_k8s_api as k8s;\n\n    #[test]\n    fn probe_multiple_paths() {\n        let probes = pod_http_probes(&k8s::PodSpec {\n            containers: vec![\n                k8s::Container {\n                    liveness_probe: Some(k8s::Probe {\n                        http_get: Some(k8s::HTTPGetAction {\n                            path: Some(\"/liveness-container-1\".to_string()),\n                            port: k8s::IntOrString::Int(5432),\n                            ..Default::default()\n                        }),\n                        ..Default::default()\n                    }),\n                    readiness_probe: Some(k8s::Probe {\n                        http_get: Some(k8s::HTTPGetAction {\n                            path: Some(\"/ready-container-1\".to_string()),\n                            port: k8s::IntOrString::Int(5432),\n                            ..Default::default()\n                        }),\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                },\n                k8s::Container {\n                    ports: Some(vec![k8s::ContainerPort {\n                        name: Some(\"named-1\".to_string()),\n                        container_port: 6543,\n                        ..Default::default()\n                    }]),\n                    liveness_probe: Some(k8s::Probe {\n                        http_get: Some(k8s::HTTPGetAction {\n                            path: Some(\"/liveness-container-2\".to_string()),\n                            port: k8s::IntOrString::String(\"named-1\".to_string()),\n                            ..Default::default()\n                        }),\n                        ..Default::default()\n                    }),\n                    readiness_probe: Some(k8s::Probe {\n                        http_get: Some(k8s::HTTPGetAction {\n                            path: Some(\"/ready-container-2\".to_string()),\n                            port: k8s::IntOrString::Int(6543),\n                            ..Default::default()\n                        }),\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                },\n            ],\n            init_containers: Some(vec![k8s::Container {\n                liveness_probe: Some(k8s::Probe {\n                    http_get: Some(k8s::HTTPGetAction {\n                        path: Some(\"/live\".to_string()),\n                        port: k8s::IntOrString::Int(4191),\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                }),\n                readiness_probe: Some(k8s::Probe {\n                    http_get: Some(k8s::HTTPGetAction {\n                        path: Some(\"/ready\".to_string()),\n                        port: k8s::IntOrString::Int(4191),\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                }),\n                ..Default::default()\n            }]),\n            ..Default::default()\n        });\n\n        let port_5432 = u16::try_from(5432).and_then(NonZeroU16::try_from).unwrap();\n        let mut expected_5432 = BTreeSet::new();\n        expected_5432.insert(\"/liveness-container-1\".to_string());\n        expected_5432.insert(\"/ready-container-1\".to_string());\n        assert!(probes.contains_key(&port_5432));\n        assert_eq!(*probes.get(&port_5432).unwrap(), expected_5432);\n\n        let port_6543 = u16::try_from(6543).and_then(NonZeroU16::try_from).unwrap();\n        let mut expected_6543 = BTreeSet::new();\n        expected_6543.insert(\"/liveness-container-2\".to_string());\n        expected_6543.insert(\"/ready-container-2\".to_string());\n        assert!(probes.contains_key(&port_6543));\n        assert_eq!(*probes.get(&port_6543).unwrap(), expected_6543);\n\n        let port_4191 = u16::try_from(4191).and_then(NonZeroU16::try_from).unwrap();\n        let mut expected_4191 = BTreeSet::new();\n        expected_4191.insert(\"/live\".to_string());\n        expected_4191.insert(\"/ready\".to_string());\n        assert!(probes.contains_key(&port_4191));\n        assert_eq!(*probes.get(&port_4191).unwrap(), expected_4191);\n    }\n\n    #[test]\n    fn probe_ignores_udp() {\n        let probes = pod_http_probes(&k8s::PodSpec {\n            containers: vec![k8s::Container {\n                ports: Some(vec![k8s::ContainerPort {\n                    container_port: 6543,\n                    name: Some(\"named\".to_string()),\n                    protocol: Some(\"UDP\".to_string()),\n                    ..Default::default()\n                }]),\n                liveness_probe: Some(k8s::Probe {\n                    http_get: Some(k8s::HTTPGetAction {\n                        port: k8s::IntOrString::String(\"named\".to_string()),\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                }),\n                ..Default::default()\n            }],\n            ..Default::default()\n        });\n\n        assert!(probes.is_empty());\n    }\n\n    #[test]\n    fn probe_only_references_within_container() {\n        let probes = pod_http_probes(&k8s::PodSpec {\n            containers: vec![\n                k8s::Container {\n                    liveness_probe: Some(k8s::Probe {\n                        http_get: Some(k8s::HTTPGetAction {\n                            port: k8s::IntOrString::String(\"named\".to_string()),\n                            ..Default::default()\n                        }),\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                },\n                k8s::Container {\n                    ports: Some(vec![k8s::ContainerPort {\n                        container_port: 6543,\n                        name: Some(\"named\".to_string()),\n                        protocol: Some(\"TCP\".to_string()),\n                        ..Default::default()\n                    }]),\n                    ..Default::default()\n                },\n            ],\n            ..Default::default()\n        });\n\n        assert!(probes.is_empty());\n    }\n\n    #[test]\n    fn probe_ports_optional() {\n        let probes = pod_http_probes(&k8s::PodSpec {\n            containers: vec![k8s::Container {\n                liveness_probe: Some(k8s::Probe {\n                    http_get: Some(k8s::HTTPGetAction {\n                        port: k8s::IntOrString::Int(8080),\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                }),\n                ..Default::default()\n            }],\n            ..Default::default()\n        });\n\n        assert_eq!(probes.len(), 1);\n        let paths = probes.get(&8080.try_into().unwrap()).unwrap();\n        assert_eq!(paths.len(), 1);\n        assert_eq!(paths.iter().next().unwrap(), \"/\");\n    }\n\n    #[test]\n    fn probe_with_params() {\n        let probes = pod_http_probes(&k8s::PodSpec {\n            containers: vec![k8s::Container {\n                liveness_probe: Some(k8s::Probe {\n                    http_get: Some(k8s::HTTPGetAction {\n                        path: Some(\"/liveness-container-1?foo=bar\".to_string()),\n                        port: k8s::IntOrString::Int(5432),\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                }),\n                ..Default::default()\n            }],\n            ..Default::default()\n        });\n\n        assert_eq!(probes.len(), 1);\n        let paths = probes.get(&5432.try_into().unwrap()).unwrap();\n        assert_eq!(paths.len(), 1);\n        assert_eq!(paths.iter().next().unwrap(), \"/liveness-container-1\");\n    }\n\n    #[test]\n    fn tcp_ports_by_name() {\n        let spec = k8s::PodSpec {\n            containers: vec![k8s::Container {\n                ports: Some(vec![\n                    k8s::ContainerPort {\n                        container_port: 8080,\n                        name: Some(\"http\".to_string()),\n                        protocol: Some(\"TCP\".to_string()),\n                        ..Default::default()\n                    },\n                    k8s::ContainerPort {\n                        container_port: 8801,\n                        name: Some(\"grpc\".to_string()),\n                        protocol: Some(\"TCP\".to_string()),\n                        ..Default::default()\n                    },\n                ]),\n                ..Default::default()\n            }],\n            init_containers: Some(vec![k8s::Container {\n                ports: Some(vec![k8s::ContainerPort {\n                    container_port: 4191,\n                    name: Some(\"linkerd-admin\".to_string()),\n                    protocol: Some(\"TCP\".to_string()),\n                    ..Default::default()\n                }]),\n                ..Default::default()\n            }]),\n            ..Default::default()\n        };\n\n        let ports = pod_tcp_ports_by_name(&spec);\n        assert_eq!(ports.len(), 3);\n        let mut expected = PortSet::default();\n        expected.insert(8080.try_into().unwrap());\n        assert_eq!(ports.get(\"http\").unwrap(), &expected);\n\n        expected = PortSet::default();\n        expected.insert(8801.try_into().unwrap());\n        assert_eq!(ports.get(\"grpc\").unwrap(), &expected);\n\n        expected = PortSet::default();\n        expected.insert(4191.try_into().unwrap());\n        assert_eq!(ports.get(\"linkerd-admin\").unwrap(), &expected);\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/inbound.rs",
    "content": "pub mod authorization_policy;\npub mod index;\nmod meshtls_authentication;\nmod network_authentication;\nmod ratelimit_policy;\nmod routes;\nmod server;\npub mod server_authorization;\nmod workload;\n\npub use index::{metrics, Index, SharedIndex};\n\n#[cfg(test)]\nmod tests;\n"
  },
  {
    "path": "policy-controller/k8s/index/src/lib.rs",
    "content": "//! The policy controller serves discovery requests from inbound proxies, indicating how the proxy\n//! should admit connections into a Pod. It watches the following cluster resources:\n//!\n//! - A `Namespace` may be annotated with a default-allow policy that applies to all pods in the\n//!   namespace (unless they are annotated with a default policy).\n//! - Each `Pod` enumerate its ports. We maintain an index of each pod's ports, linked to `Server`\n//!   objects.\n//! - Each `Server` selects over pods in the same namespace.\n//! - Each `ServerAuthorization` selects over `Server` instances in the same namespace.  When a\n//!   `ServerAuthorization` is updated, we find all the `Server` instances it selects and update\n//!   their authorizations and publishes these updates on the server's broadcast channel.\n//!\n//! ```text\n//! [ Pod ] -> [ Port ] <- [ Server ] <- [ ServerAuthorization ]\n//! ```\n//!\n//! Lookups against this index are initiated for a single pod & port.\n//!\n//! The Pod, Server, and ServerAuthorization indices are all scoped within a namespace index, as\n//! these resources cannot reference resources in other namespaces. This scoping helps to narrow the\n//! search space when processing updates and linking resources.\n\n#![deny(warnings, rust_2018_idioms)]\n#![forbid(unsafe_code)]\n\nmod cluster_info;\nmod defaults;\npub mod inbound;\npub mod outbound;\npub mod ports;\npub mod routes;\n\npub use cluster_info::ClusterInfo;\npub use defaults::DefaultPolicy;\npub use inbound::authorization_policy;\n"
  },
  {
    "path": "policy-controller/k8s/index/src/outbound/index/egress_network.rs",
    "content": "use chrono::{offset::Utc, DateTime};\nuse linkerd_policy_controller_k8s_api::policy::{Cidr, Network, TrafficPolicy};\nuse linkerd_policy_controller_k8s_api::{policy as linkerd_k8s_api, ResourceExt};\nuse std::net::IpAddr;\n\n#[derive(Debug)]\npub(crate) struct EgressNetwork {\n    pub networks: Vec<Network>,\n    pub name: String,\n    pub namespace: String,\n    pub creation_timestamp: Option<DateTime<Utc>>,\n    pub traffic_policy: TrafficPolicy,\n}\n\n#[derive(Debug, PartialEq, Eq)]\nstruct MatchedEgressNetwork {\n    matched_network_size: usize,\n    name: String,\n    namespace: String,\n    creation_timestamp: Option<DateTime<Utc>>,\n    pub traffic_policy: TrafficPolicy,\n}\n\n// === impl EgressNetwork ===\n\nimpl EgressNetwork {\n    pub(crate) fn from_resource(\n        r: &linkerd_k8s_api::EgressNetwork,\n        cluster_networks: Vec<Cidr>,\n    ) -> Self {\n        let name = r.name_unchecked();\n        let namespace = r.namespace().expect(\"EgressNetwork must have a namespace\");\n        let creation_timestamp = r.creation_timestamp().map(|d| d.0);\n        let traffic_policy = r.spec.traffic_policy.clone();\n\n        let networks = r.spec.networks.clone().unwrap_or_else(|| {\n            let (v6, v4) = cluster_networks.iter().cloned().partition(Cidr::is_ipv6);\n\n            vec![\n                Network {\n                    cidr: \"0.0.0.0/0\".parse().expect(\"should parse\"),\n                    except: Some(v4),\n                },\n                Network {\n                    cidr: \"::/0\".parse().expect(\"should parse\"),\n                    except: Some(v6),\n                },\n            ]\n        });\n\n        EgressNetwork {\n            name,\n            namespace,\n            networks,\n            creation_timestamp,\n            traffic_policy,\n        }\n    }\n}\n\n// Attempts to find the best matching network for a certain discovery look-up.\n// Logic is:\n// 1. if there are Egress networks in the source_namespace, only these are considered\n// 2. otherwise only networks from the global egress network namespace are considered\n// 3. the target IP is matched against the networks of the EgressNetwork\n// 4. ambiguity is resolved as by comparing the networks using compare_matched_egress_network\npub(crate) fn resolve_egress_network<'n>(\n    addr: IpAddr,\n    source_namespace: String,\n    global_egress_network_namespace: &str,\n    nets: impl Iterator<Item = &'n EgressNetwork>,\n) -> Option<super::ResourceRef> {\n    let (same_ns, rest): (Vec<_>, Vec<_>) = nets\n        .filter(|en| {\n            en.namespace == source_namespace || en.namespace == *global_egress_network_namespace\n        })\n        .partition(|un| un.namespace == source_namespace);\n    let to_pick_from = if !same_ns.is_empty() { same_ns } else { rest };\n\n    to_pick_from\n        .iter()\n        .filter_map(|egress_network| {\n            let matched_network_size = match_network(&egress_network.networks, addr)?;\n            Some(MatchedEgressNetwork {\n                name: egress_network.name.clone(),\n                namespace: egress_network.namespace.clone(),\n                matched_network_size,\n                creation_timestamp: egress_network.creation_timestamp,\n                traffic_policy: egress_network.traffic_policy.clone(),\n            })\n        })\n        .max_by(compare_matched_egress_network)\n        .map(|m| super::ResourceRef {\n            kind: super::ResourceKind::EgressNetwork,\n            name: m.name,\n            namespace: m.namespace,\n        })\n}\n\n// Finds a CIDR that contains the given IpAddr. When there are\n// multiple CIDRS that match this criteria, the CIDR that is most\n// specific (as in having the smallest address space) wins.\nfn match_network(networks: &[Network], addr: IpAddr) -> Option<usize> {\n    networks\n        .iter()\n        .filter(|c| c.contains(addr))\n        .min_by(|a, b| a.block_size().cmp(&b.block_size()))\n        .map(Network::block_size)\n}\n\n// This logic compares two MatchedEgressNetwork objects with the purpose\n// of picking the one that is more specific. The disambiguation rules are\n// as follows:\n//  1. prefer the more specific network match (smaller address space size)\n//  2. prefer older resource\n//  3. all being equal, rely on alphabetical sort of namespace/name\nfn compare_matched_egress_network(\n    a: &MatchedEgressNetwork,\n    b: &MatchedEgressNetwork,\n) -> std::cmp::Ordering {\n    b.matched_network_size\n        .cmp(&a.matched_network_size)\n        .then_with(|| a.creation_timestamp.cmp(&b.creation_timestamp).reverse())\n        .then_with(|| a.namespace.cmp(&b.namespace).reverse())\n        .then_with(|| a.name.cmp(&b.name).reverse())\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    const EGRESS_NETS_NS: &str = \"linkerd-external\";\n\n    #[test]\n    fn test_picks_smallest_cidr() {\n        let ip_addr = \"192.168.0.4\".parse().unwrap();\n        let networks = [\n            EgressNetwork {\n                networks: vec![Network {\n                    cidr: \"192.168.0.1/16\".parse().unwrap(),\n                    except: None,\n                }],\n                name: \"net-1\".to_string(),\n                namespace: EGRESS_NETS_NS.to_string(),\n                creation_timestamp: None,\n                traffic_policy: TrafficPolicy::Allow,\n            },\n            EgressNetwork {\n                networks: vec![Network {\n                    cidr: \"192.168.0.1/24\".parse().unwrap(),\n                    except: None,\n                }],\n                name: \"net-2\".to_string(),\n                namespace: EGRESS_NETS_NS.to_string(),\n                creation_timestamp: None,\n                traffic_policy: TrafficPolicy::Allow,\n            },\n        ];\n\n        let resolved =\n            resolve_egress_network(ip_addr, \"ns\".into(), EGRESS_NETS_NS, networks.iter());\n        assert_eq!(resolved.unwrap().name, \"net-2\".to_string())\n    }\n\n    #[test]\n    fn test_picks_local_ns() {\n        let ip_addr = \"192.168.0.4\".parse().unwrap();\n        let networks = [\n            EgressNetwork {\n                networks: vec![Network {\n                    cidr: \"192.168.0.1/16\".parse().unwrap(),\n                    except: None,\n                }],\n                name: \"net-1\".to_string(),\n                namespace: \"ns-1\".to_string(),\n                creation_timestamp: None,\n                traffic_policy: TrafficPolicy::Allow,\n            },\n            EgressNetwork {\n                networks: vec![Network {\n                    cidr: \"192.168.0.1/24\".parse().unwrap(),\n                    except: None,\n                }],\n                name: \"net-2\".to_string(),\n                namespace: EGRESS_NETS_NS.to_string(),\n                creation_timestamp: None,\n                traffic_policy: TrafficPolicy::Allow,\n            },\n        ];\n\n        let resolved =\n            resolve_egress_network(ip_addr, \"ns-1\".into(), EGRESS_NETS_NS, networks.iter());\n        assert_eq!(resolved.unwrap().name, \"net-1\".to_string())\n    }\n\n    #[test]\n    fn does_not_pick_network_in_unralated_ns() {\n        let ip_addr = \"192.168.0.4\".parse().unwrap();\n        let networks = [EgressNetwork {\n            networks: vec![Network {\n                cidr: \"192.168.0.1/16\".parse().unwrap(),\n                except: None,\n            }],\n            name: \"net-1\".to_string(),\n            namespace: \"other-ns\".to_string(),\n            creation_timestamp: None,\n            traffic_policy: TrafficPolicy::Allow,\n        }];\n\n        let resolved =\n            resolve_egress_network(ip_addr, \"ns-1\".into(), EGRESS_NETS_NS, networks.iter());\n        assert!(resolved.is_none());\n    }\n\n    #[test]\n    fn test_picks_older_resource() {\n        let ip_addr = \"192.168.0.4\".parse().unwrap();\n        let networks = [\n            EgressNetwork {\n                networks: vec![Network {\n                    cidr: \"192.168.0.1/16\".parse().unwrap(),\n                    except: None,\n                }],\n                name: \"net-1\".to_string(),\n                namespace: EGRESS_NETS_NS.to_string(),\n                creation_timestamp: Some(DateTime::<Utc>::MAX_UTC),\n                traffic_policy: TrafficPolicy::Allow,\n            },\n            EgressNetwork {\n                networks: vec![Network {\n                    cidr: \"192.168.0.1/16\".parse().unwrap(),\n                    except: None,\n                }],\n                name: \"net-2\".to_string(),\n                namespace: EGRESS_NETS_NS.to_string(),\n                creation_timestamp: Some(DateTime::<Utc>::MIN_UTC),\n                traffic_policy: TrafficPolicy::Allow,\n            },\n        ];\n\n        let resolved =\n            resolve_egress_network(ip_addr, \"ns\".into(), EGRESS_NETS_NS, networks.iter());\n        assert_eq!(resolved.unwrap().name, \"net-2\".to_string())\n    }\n\n    #[test]\n    fn test_picks_alphabetical_order() {\n        let ip_addr = \"192.168.0.4\".parse().unwrap();\n        let networks = [\n            EgressNetwork {\n                networks: vec![Network {\n                    cidr: \"192.168.0.1/16\".parse().unwrap(),\n                    except: None,\n                }],\n                name: \"a\".to_string(),\n                namespace: EGRESS_NETS_NS.to_string(),\n                creation_timestamp: None,\n                traffic_policy: TrafficPolicy::Allow,\n            },\n            EgressNetwork {\n                networks: vec![Network {\n                    cidr: \"192.168.0.1/16\".parse().unwrap(),\n                    except: None,\n                }],\n                name: \"b\".to_string(),\n                namespace: EGRESS_NETS_NS.to_string(),\n                creation_timestamp: None,\n                traffic_policy: TrafficPolicy::Allow,\n            },\n        ];\n\n        let resolved =\n            resolve_egress_network(ip_addr, \"ns\".into(), EGRESS_NETS_NS, networks.iter());\n        assert_eq!(resolved.unwrap().name, \"a\".to_string())\n    }\n\n    #[test]\n    fn test_respects_exception() {\n        let ip_addr = \"192.168.0.4\".parse().unwrap();\n        let networks = [\n            EgressNetwork {\n                networks: vec![Network {\n                    cidr: \"192.168.0.1/16\".parse().unwrap(),\n                    except: Some(vec![\"192.168.0.4\".parse().unwrap()]),\n                }],\n                name: \"b\".to_string(),\n                namespace: EGRESS_NETS_NS.to_string(),\n                creation_timestamp: None,\n                traffic_policy: TrafficPolicy::Allow,\n            },\n            EgressNetwork {\n                networks: vec![Network {\n                    cidr: \"192.168.0.1/16\".parse().unwrap(),\n                    except: None,\n                }],\n                name: \"d\".to_string(),\n                namespace: EGRESS_NETS_NS.to_string(),\n                creation_timestamp: None,\n                traffic_policy: TrafficPolicy::Allow,\n            },\n        ];\n\n        let resolved =\n            resolve_egress_network(ip_addr, \"ns\".into(), EGRESS_NETS_NS, networks.iter());\n        assert_eq!(resolved.unwrap().name, \"d\".to_string())\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/outbound/index/grpc.rs",
    "content": "use std::num::NonZeroU16;\nuse std::time;\n\nuse super::{\n    parse_duration, parse_timeouts, ResourceInfo, ResourceKind, ResourcePort, ResourceRef,\n};\nuse crate::{routes, ClusterInfo};\nuse ahash::AHashMap as HashMap;\nuse anyhow::{bail, Result};\nuse kube::ResourceExt;\nuse linkerd_policy_controller_core::outbound::{\n    Backend, Filter, GrpcRetryCondition, GrpcRoute, OutboundRoute, RouteRetry, RouteTimeouts,\n    WeightedEgressNetwork, WeightedService,\n};\nuse linkerd_policy_controller_core::{outbound::OutboundRouteRule, routes::GrpcRouteMatch};\nuse linkerd_policy_controller_k8s_api::{gateway, policy, Resource, Service, Time};\n\npub(super) fn convert_route(\n    ns: &str,\n    route: gateway::GRPCRoute,\n    cluster: &ClusterInfo,\n    resource_info: &HashMap<ResourceRef, ResourceInfo>,\n) -> Result<GrpcRoute> {\n    let timeouts = parse_timeouts(route.annotations())?;\n    let retry = parse_grpc_retry(route.annotations())?;\n\n    let hostnames = route\n        .spec\n        .hostnames\n        .into_iter()\n        .flatten()\n        .map(routes::host_match)\n        .collect();\n\n    let rules = route\n        .spec\n        .rules\n        .into_iter()\n        .flatten()\n        .map(|rule| {\n            convert_rule(\n                ns,\n                rule,\n                cluster,\n                resource_info,\n                timeouts.clone(),\n                retry.clone(),\n            )\n        })\n        .collect::<Result<_>>()?;\n\n    let creation_timestamp = route.metadata.creation_timestamp.map(|Time(t)| t);\n\n    Ok(OutboundRoute {\n        hostnames,\n        rules,\n        creation_timestamp,\n    })\n}\n\nfn convert_rule(\n    ns: &str,\n    rule: gateway::GRPCRouteRules,\n    cluster: &ClusterInfo,\n    resource_info: &HashMap<ResourceRef, ResourceInfo>,\n    timeouts: RouteTimeouts,\n    retry: Option<RouteRetry<GrpcRetryCondition>>,\n) -> Result<OutboundRouteRule<GrpcRouteMatch, GrpcRetryCondition>> {\n    let matches = rule\n        .matches\n        .into_iter()\n        .flatten()\n        .map(routes::grpc::try_match)\n        .collect::<Result<_>>()?;\n\n    let backends = rule\n        .backend_refs\n        .into_iter()\n        .flatten()\n        .filter_map(|b| convert_backend(ns, b, cluster, resource_info))\n        .collect();\n\n    let filters = rule\n        .filters\n        .into_iter()\n        .flatten()\n        .map(convert_filter)\n        .collect::<Result<_>>()?;\n\n    Ok(OutboundRouteRule {\n        matches,\n        backends,\n        timeouts,\n        retry,\n        filters,\n    })\n}\n\npub(super) fn convert_backend(\n    ns: &str,\n    backend: gateway::GRPCRouteRulesBackendRefs,\n    cluster: &ClusterInfo,\n    resources: &HashMap<ResourceRef, ResourceInfo>,\n) -> Option<Backend> {\n    let backend_kind = match backend_kind(&backend) {\n        Some(backend_kind) => backend_kind,\n        None => {\n            return Some(Backend::Invalid {\n                weight: backend.weight.unwrap_or(1) as u32,\n                message: format!(\n                    \"unsupported backend type {group} {kind}\",\n                    group = backend.group.as_deref().unwrap_or(\"core\"),\n                    kind = backend.kind.as_deref().unwrap_or(\"<empty>\"),\n                ),\n            });\n        }\n    };\n\n    let filters = backend.filters;\n\n    let backend_ref = ResourceRef {\n        name: backend.name.clone(),\n        namespace: backend.namespace.unwrap_or_else(|| ns.to_string()),\n        kind: backend_kind.clone(),\n    };\n\n    let name = backend.name;\n    let weight = backend.weight.unwrap_or(1) as u32;\n\n    let filters = match filters\n        .into_iter()\n        .flatten()\n        .map(convert_backend_filter)\n        .collect::<Result<_>>()\n    {\n        Ok(filters) => filters,\n        Err(error) => {\n            return Some(Backend::Invalid {\n                weight: backend.weight.unwrap_or(1) as u32,\n                message: format!(\"unsupported backend filter: {error}\"),\n            });\n        }\n    };\n\n    let port = backend\n        .port\n        .and_then(|p| p.try_into().ok())\n        .and_then(|p: u16| NonZeroU16::try_from(p).ok());\n\n    match backend_kind {\n        ResourceKind::Service => {\n            // The gateway API dictates:\n            //\n            // Port is required when the referent is a Kubernetes Service.\n            let port = match port {\n                Some(port) => port,\n                None => {\n                    return Some(Backend::Invalid {\n                        weight,\n                        message: format!(\"missing port for backend Service {name}\"),\n                    })\n                }\n            };\n\n            Some(Backend::Service(WeightedService {\n                weight,\n                authority: cluster.service_dns_authority(&backend_ref.namespace, &name, port),\n                name,\n                namespace: backend_ref.namespace.to_string(),\n                port,\n                filters,\n                exists: resources.contains_key(&backend_ref),\n            }))\n        }\n        ResourceKind::EgressNetwork => Some(Backend::EgressNetwork(WeightedEgressNetwork {\n            weight,\n            name,\n            namespace: backend_ref.namespace.to_string(),\n            port,\n            filters,\n            exists: resources.contains_key(&backend_ref),\n        })),\n    }\n}\n\npub(crate) fn convert_filter(filter: gateway::GRPCRouteRulesFilters) -> Result<Filter> {\n    if let Some(request_header_modifier) = filter.request_header_modifier {\n        let filter = routes::grpc::request_header_modifier(request_header_modifier)?;\n        return Ok(Filter::RequestHeaderModifier(filter));\n    }\n    if let Some(response_header_modifier) = filter.response_header_modifier {\n        let filter = routes::grpc::response_header_modifier(response_header_modifier)?;\n        return Ok(Filter::ResponseHeaderModifier(filter));\n    }\n    if let Some(_request_mirror) = filter.request_mirror {\n        bail!(\"RequestMirror filter is not supported\")\n    }\n    if let Some(_extension_ref) = filter.extension_ref {\n        bail!(\"ExtensionRef filter is not supported\")\n    }\n    bail!(\"unknown filter\")\n}\n\npub(crate) fn convert_backend_filter(\n    filter: gateway::GRPCRouteRulesBackendRefsFilters,\n) -> Result<Filter> {\n    if let Some(request_header_modifier) = filter.request_header_modifier {\n        let filter = routes::grpc::backend_request_header_modifier(request_header_modifier)?;\n        return Ok(Filter::RequestHeaderModifier(filter));\n    }\n    if let Some(response_header_modifier) = filter.response_header_modifier {\n        let filter = routes::grpc::backend_response_header_modifier(response_header_modifier)?;\n        return Ok(Filter::ResponseHeaderModifier(filter));\n    }\n    if let Some(_request_mirror) = filter.request_mirror {\n        bail!(\"RequestMirror filter is not supported\")\n    }\n    if let Some(_extension_ref) = filter.extension_ref {\n        bail!(\"ExtensionRef filter is not supported\")\n    }\n    bail!(\"unknown filter\")\n}\n\npub fn parse_grpc_retry(\n    annotations: &std::collections::BTreeMap<String, String>,\n) -> Result<Option<RouteRetry<GrpcRetryCondition>>> {\n    let limit = annotations\n        .get(\"retry.linkerd.io/limit\")\n        .map(|s| s.parse::<u16>())\n        .transpose()?\n        .filter(|v| *v != 0);\n\n    let timeout = annotations\n        .get(\"retry.linkerd.io/timeout\")\n        .map(|v| parse_duration(v))\n        .transpose()?\n        .filter(|v| *v != time::Duration::ZERO);\n\n    let conditions = annotations\n        .get(\"retry.linkerd.io/grpc\")\n        .map(|v| {\n            v.split(',')\n                .map(|cond| {\n                    if cond.eq_ignore_ascii_case(\"cancelled\") {\n                        return Ok(GrpcRetryCondition::Cancelled);\n                    }\n                    if cond.eq_ignore_ascii_case(\"deadline-exceeded\") {\n                        return Ok(GrpcRetryCondition::DeadlineExceeded);\n                    }\n                    if cond.eq_ignore_ascii_case(\"internal\") {\n                        return Ok(GrpcRetryCondition::Internal);\n                    }\n                    if cond.eq_ignore_ascii_case(\"resource-exhausted\") {\n                        return Ok(GrpcRetryCondition::ResourceExhausted);\n                    }\n                    if cond.eq_ignore_ascii_case(\"unavailable\") {\n                        return Ok(GrpcRetryCondition::Unavailable);\n                    }\n                    bail!(\"Unknown grpc retry condition: {cond}\");\n                })\n                .collect::<Result<Vec<_>>>()\n        })\n        .transpose()?;\n\n    if limit.is_none() && timeout.is_none() && conditions.is_none() {\n        return Ok(None);\n    }\n\n    Ok(Some(RouteRetry {\n        limit: limit.unwrap_or(1),\n        timeout,\n        conditions,\n    }))\n}\n\npub(super) fn route_accepted_by_resource_port(\n    route_status: Option<&gateway::GRPCRouteStatus>,\n    resource_port: &ResourcePort,\n) -> bool {\n    let (kind, group) = match resource_port.kind {\n        ResourceKind::Service => (Service::kind(&()), Service::group(&())),\n        ResourceKind::EgressNetwork => (\n            policy::EgressNetwork::kind(&()),\n            policy::EgressNetwork::group(&()),\n        ),\n    };\n    let mut group = &*group;\n    if group.is_empty() {\n        group = \"core\";\n    }\n    route_status\n        .map(|status| status.parents.as_slice())\n        .unwrap_or_default()\n        .iter()\n        .any(|parent_status| {\n            let port_matches = match parent_status.parent_ref.port {\n                Some(port) => port == resource_port.port.get() as i32,\n                None => true,\n            };\n            let mut parent_group = parent_status.parent_ref.group.as_deref().unwrap_or(\"core\");\n            if parent_group.is_empty() {\n                parent_group = \"core\";\n            }\n            resource_port.name == parent_status.parent_ref.name\n                && Some(kind.as_ref()) == parent_status.parent_ref.kind.as_deref()\n                && group == parent_group\n                && port_matches\n                && parent_status\n                    .conditions\n                    .iter()\n                    .flatten()\n                    .any(|condition| condition.type_ == \"Accepted\" && condition.status == \"True\")\n        })\n}\n\npub fn route_accepted_by_service(\n    route_status: Option<&gateway::GRPCRouteStatus>,\n    service: &str,\n) -> bool {\n    let mut service_group = &*Service::group(&());\n    if service_group.is_empty() {\n        service_group = \"core\";\n    }\n    route_status\n        .map(|status| status.parents.as_slice())\n        .unwrap_or_default()\n        .iter()\n        .any(|parent_status| {\n            let mut parent_group = parent_status.parent_ref.group.as_deref().unwrap_or(\"core\");\n            if parent_group.is_empty() {\n                parent_group = \"core\";\n            }\n            parent_status.parent_ref.name == service\n                && parent_status.parent_ref.kind.as_deref() == Some(Service::kind(&()).as_ref())\n                && parent_group == service_group\n                && parent_status\n                    .conditions\n                    .iter()\n                    .flatten()\n                    .any(|condition| condition.type_ == \"Accepted\" && condition.status == \"True\")\n        })\n}\n\npub(crate) fn backend_kind(backend: &gateway::GRPCRouteRulesBackendRefs) -> Option<ResourceKind> {\n    let group = backend.group.as_deref();\n    // Backends default to `Service` if no kind is specified.\n    let kind = backend.kind.as_deref().unwrap_or(\"Service\");\n    if super::is_service(group, kind) {\n        Some(ResourceKind::Service)\n    } else if super::is_egress_network(group, kind) {\n        Some(ResourceKind::EgressNetwork)\n    } else {\n        None\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/outbound/index/http.rs",
    "content": "use std::{num::NonZeroU16, time};\n\nuse super::{\n    parse_duration, parse_timeouts, ResourceInfo, ResourceKind, ResourcePort, ResourceRef,\n};\nuse crate::{\n    routes::{self, HttpRouteResource},\n    ClusterInfo,\n};\nuse ahash::AHashMap as HashMap;\nuse anyhow::{bail, Result};\nuse kube::ResourceExt;\nuse linkerd_policy_controller_core::{\n    outbound::{\n        Backend, Filter, HttpRetryCondition, OutboundRoute, OutboundRouteRule, RouteRetry,\n        RouteTimeouts, WeightedEgressNetwork, WeightedService,\n    },\n    routes::HttpRouteMatch,\n};\nuse linkerd_policy_controller_k8s_api::{gateway, policy, Resource, Service, Time};\n\npub(super) fn convert_route(\n    ns: &str,\n    route: HttpRouteResource,\n    cluster: &ClusterInfo,\n    resource_info: &HashMap<ResourceRef, ResourceInfo>,\n) -> Result<OutboundRoute<HttpRouteMatch, HttpRetryCondition>> {\n    match route {\n        HttpRouteResource::LinkerdHttp(route) => {\n            let timeouts = parse_timeouts(route.annotations())?;\n            let retry = parse_http_retry(route.annotations())?;\n\n            let hostnames = route\n                .spec\n                .hostnames\n                .into_iter()\n                .flatten()\n                .map(routes::host_match)\n                .collect();\n\n            let rules = route\n                .spec\n                .rules\n                .into_iter()\n                .flatten()\n                .map(|r| {\n                    convert_linkerd_rule(\n                        ns,\n                        r,\n                        cluster,\n                        resource_info,\n                        timeouts.clone(),\n                        retry.clone(),\n                    )\n                })\n                .collect::<Result<_>>()?;\n\n            let creation_timestamp = route.metadata.creation_timestamp.map(|Time(t)| t);\n\n            Ok(OutboundRoute {\n                hostnames,\n                rules,\n                creation_timestamp,\n            })\n        }\n        HttpRouteResource::GatewayHttp(route) => {\n            let timeouts = parse_timeouts(route.annotations())?;\n            let retry = parse_http_retry(route.annotations())?;\n\n            let hostnames = route\n                .spec\n                .hostnames\n                .into_iter()\n                .flatten()\n                .map(routes::host_match)\n                .collect();\n\n            let rules = route\n                .spec\n                .rules\n                .into_iter()\n                .flatten()\n                .map(|r| {\n                    convert_gateway_rule(\n                        ns,\n                        r,\n                        cluster,\n                        resource_info,\n                        timeouts.clone(),\n                        retry.clone(),\n                    )\n                })\n                .collect::<Result<_>>()?;\n\n            let creation_timestamp = route.metadata.creation_timestamp.map(|Time(t)| t);\n\n            Ok(OutboundRoute {\n                hostnames,\n                rules,\n                creation_timestamp,\n            })\n        }\n    }\n}\n\nfn convert_linkerd_rule(\n    ns: &str,\n    rule: policy::httproute::HttpRouteRule,\n    cluster: &ClusterInfo,\n    resource_info: &HashMap<ResourceRef, ResourceInfo>,\n    mut timeouts: RouteTimeouts,\n    retry: Option<RouteRetry<HttpRetryCondition>>,\n) -> Result<OutboundRouteRule<HttpRouteMatch, HttpRetryCondition>> {\n    let matches = rule\n        .matches\n        .into_iter()\n        .flatten()\n        .map(routes::http::try_match)\n        .collect::<Result<_>>()?;\n\n    let backends = rule\n        .backend_refs\n        .into_iter()\n        .flatten()\n        .filter_map(|b| convert_backend(ns, b, cluster, resource_info))\n        .collect();\n\n    let filters = rule\n        .filters\n        .into_iter()\n        .flatten()\n        .map(convert_linkerd_filter)\n        .collect::<Result<_>>()?;\n\n    timeouts.request = timeouts.request.or_else(|| {\n        rule.timeouts.as_ref().and_then(|timeouts| {\n            let timeout = time::Duration::from(timeouts.request?);\n\n            // zero means \"no timeout\", per GEP-1742\n            if timeout == time::Duration::ZERO {\n                return None;\n            }\n\n            Some(timeout)\n        })\n    });\n\n    Ok(OutboundRouteRule {\n        matches,\n        backends,\n        timeouts,\n        retry,\n        filters,\n    })\n}\n\nfn convert_gateway_rule(\n    ns: &str,\n    rule: gateway::HTTPRouteRules,\n    cluster: &ClusterInfo,\n    resource_info: &HashMap<ResourceRef, ResourceInfo>,\n    mut timeouts: RouteTimeouts,\n    retry: Option<RouteRetry<HttpRetryCondition>>,\n) -> Result<OutboundRouteRule<HttpRouteMatch, HttpRetryCondition>> {\n    let matches = rule\n        .matches\n        .into_iter()\n        .flatten()\n        .map(routes::http::try_match)\n        .collect::<Result<_>>()?;\n\n    let backends = rule\n        .backend_refs\n        .into_iter()\n        .flatten()\n        .filter_map(|b| convert_backend(ns, b, cluster, resource_info))\n        .collect();\n\n    let filters = rule\n        .filters\n        .into_iter()\n        .flatten()\n        .map(convert_gateway_filter)\n        .collect::<Result<_>>()?;\n\n    timeouts.request = timeouts.request.or_else(|| {\n        rule.timeouts.as_ref().and_then(|timeouts| {\n            let timeout = parse_duration(timeouts.request.as_ref()?).ok()?;\n\n            // zero means \"no timeout\", per GEP-1742\n            if timeout == time::Duration::ZERO {\n                return None;\n            }\n\n            Some(timeout)\n        })\n    });\n\n    Ok(OutboundRouteRule {\n        matches,\n        backends,\n        timeouts,\n        retry,\n        filters,\n    })\n}\n\npub(super) fn convert_backend(\n    ns: &str,\n    backend: gateway::HTTPRouteRulesBackendRefs,\n    cluster: &ClusterInfo,\n    resources: &HashMap<ResourceRef, ResourceInfo>,\n) -> Option<Backend> {\n    let backend_kind = match backend_kind(&backend) {\n        Some(backend_kind) => backend_kind,\n        None => {\n            return Some(Backend::Invalid {\n                weight: backend.weight.unwrap_or(1) as u32,\n                message: format!(\n                    \"unsupported backend type {group} {kind}\",\n                    group = backend.group.as_deref().unwrap_or(\"core\"),\n                    kind = backend.kind.as_deref().unwrap_or(\"<empty>\"),\n                ),\n            });\n        }\n    };\n\n    let filters = backend.filters;\n\n    let backend_ref = ResourceRef {\n        name: backend.name.clone(),\n        namespace: backend.namespace.clone().unwrap_or_else(|| ns.to_string()),\n        kind: backend_kind.clone(),\n    };\n\n    let name = backend_ref.name.clone();\n    let weight = backend.weight.unwrap_or(1) as u32;\n\n    let filters = match filters\n        .into_iter()\n        .flatten()\n        .map(convert_gateway_backend_filter)\n        .collect::<Result<_>>()\n    {\n        Ok(filters) => filters,\n        Err(error) => {\n            return Some(Backend::Invalid {\n                weight,\n                message: format!(\"unsupported backend filter: {error}\"),\n            });\n        }\n    };\n\n    let port = backend\n        .port\n        .and_then(|p| p.try_into().ok())\n        .and_then(|p: u16| NonZeroU16::try_from(p).ok());\n\n    match backend_kind {\n        ResourceKind::Service => {\n            // The gateway API dictates:\n            //\n            // Port is required when the referent is a Kubernetes Service.\n            let port = match port {\n                Some(port) => port,\n                None => {\n                    return Some(Backend::Invalid {\n                        weight,\n                        message: format!(\"missing port for backend Service {name}\"),\n                    })\n                }\n            };\n\n            Some(Backend::Service(WeightedService {\n                weight,\n                authority: cluster.service_dns_authority(&backend_ref.namespace, &name, port),\n                name,\n                namespace: backend_ref.namespace.to_string(),\n                port,\n                filters,\n                exists: resources.contains_key(&backend_ref),\n            }))\n        }\n        ResourceKind::EgressNetwork => Some(Backend::EgressNetwork(WeightedEgressNetwork {\n            weight,\n            name,\n            namespace: backend_ref.namespace.to_string(),\n            port,\n            filters,\n            exists: resources.contains_key(&backend_ref),\n        })),\n    }\n}\n\nfn convert_linkerd_filter(filter: policy::httproute::HttpRouteFilter) -> Result<Filter> {\n    let filter = match filter {\n        policy::httproute::HttpRouteFilter::RequestHeaderModifier {\n            request_header_modifier,\n        } => {\n            let filter = routes::http::request_header_modifier(request_header_modifier)?;\n            Filter::RequestHeaderModifier(filter)\n        }\n\n        policy::httproute::HttpRouteFilter::ResponseHeaderModifier {\n            response_header_modifier,\n        } => {\n            let filter = routes::http::response_header_modifier(response_header_modifier)?;\n            Filter::RequestHeaderModifier(filter)\n        }\n\n        policy::httproute::HttpRouteFilter::RequestRedirect { request_redirect } => {\n            let filter = routes::http::req_redirect(request_redirect)?;\n            Filter::RequestRedirect(filter)\n        }\n    };\n    Ok(filter)\n}\n\npub(crate) fn convert_gateway_filter(filter: gateway::HTTPRouteRulesFilters) -> Result<Filter> {\n    if let Some(request_header_modifier) = filter.request_header_modifier {\n        let filter = routes::http::request_header_modifier(request_header_modifier)?;\n        return Ok(Filter::RequestHeaderModifier(filter));\n    }\n    if let Some(response_header_modifier) = filter.response_header_modifier {\n        let filter = routes::http::response_header_modifier(response_header_modifier)?;\n        return Ok(Filter::ResponseHeaderModifier(filter));\n    }\n    if let Some(request_redirect) = filter.request_redirect {\n        let filter = routes::http::req_redirect(request_redirect)?;\n        return Ok(Filter::RequestRedirect(filter));\n    }\n    if let Some(_request_mirror) = filter.request_mirror {\n        bail!(\"RequestMirror filter is not supported\")\n    }\n    if let Some(_url_rewrite) = filter.url_rewrite {\n        bail!(\"URLRewrite filter is not supported\")\n    }\n    if let Some(_extension_ref) = filter.extension_ref {\n        bail!(\"ExtensionRef filter is not supported\")\n    }\n    bail!(\"unknown filter\")\n}\n\npub(crate) fn convert_gateway_backend_filter(\n    filter: gateway::HTTPRouteRulesBackendRefsFilters,\n) -> Result<Filter> {\n    if let Some(request_header_modifier) = filter.request_header_modifier {\n        let filter = routes::http::backend_request_header_modifier(request_header_modifier)?;\n        return Ok(Filter::RequestHeaderModifier(filter));\n    }\n    if let Some(response_header_modifier) = filter.response_header_modifier {\n        let filter = routes::http::backend_response_header_modifier(response_header_modifier)?;\n        return Ok(Filter::ResponseHeaderModifier(filter));\n    }\n    if let Some(request_redirect) = filter.request_redirect {\n        let filter = routes::http::backend_req_redirect(request_redirect)?;\n        return Ok(Filter::RequestRedirect(filter));\n    }\n    if let Some(_request_mirror) = filter.request_mirror {\n        bail!(\"RequestMirror filter is not supported\")\n    }\n    if let Some(_url_rewrite) = filter.url_rewrite {\n        bail!(\"URLRewrite filter is not supported\")\n    }\n    if let Some(_extension_ref) = filter.extension_ref {\n        bail!(\"ExtensionRef filter is not supported\")\n    }\n    bail!(\"unknown filter\")\n}\n\npub fn parse_http_retry(\n    annotations: &std::collections::BTreeMap<String, String>,\n) -> Result<Option<RouteRetry<HttpRetryCondition>>> {\n    let limit = annotations\n        .get(\"retry.linkerd.io/limit\")\n        .map(|s| s.parse::<u16>())\n        .transpose()?\n        .filter(|v| *v != 0);\n\n    let timeout = annotations\n        .get(\"retry.linkerd.io/timeout\")\n        .map(|v| parse_duration(v))\n        .transpose()?\n        .filter(|v| *v != time::Duration::ZERO);\n\n    fn to_code(s: &str) -> Option<u32> {\n        let code = s.parse::<u32>().ok()?;\n        if (100..600).contains(&code) {\n            Some(code)\n        } else {\n            None\n        }\n    }\n\n    let conditions = annotations\n        .get(\"retry.linkerd.io/http\")\n        .map(|v| {\n            v.split(',')\n                .map(|cond| {\n                    if cond.eq_ignore_ascii_case(\"5xx\") {\n                        return Ok(HttpRetryCondition {\n                            status_min: 500,\n                            status_max: 599,\n                        });\n                    }\n                    if cond.eq_ignore_ascii_case(\"gateway-error\") {\n                        return Ok(HttpRetryCondition {\n                            status_min: 502,\n                            status_max: 504,\n                        });\n                    }\n\n                    if let Some(code) = to_code(cond) {\n                        return Ok(HttpRetryCondition {\n                            status_min: code,\n                            status_max: code,\n                        });\n                    }\n                    if let Some((start, end)) = cond.split_once('-') {\n                        if let (Some(s), Some(e)) = (to_code(start), to_code(end)) {\n                            if s <= e {\n                                return Ok(HttpRetryCondition {\n                                    status_min: s,\n                                    status_max: e,\n                                });\n                            }\n                        }\n                    }\n\n                    bail!(\"invalid retry condition: {v}\");\n                })\n                .collect::<Result<Vec<_>>>()\n        })\n        .transpose()?;\n\n    if limit.is_none() && timeout.is_none() && conditions.is_none() {\n        return Ok(None);\n    }\n\n    Ok(Some(RouteRetry {\n        limit: limit.unwrap_or(1),\n        timeout,\n        conditions,\n    }))\n}\n\npub(super) fn route_accepted_by_resource_port(\n    route_status: Option<&gateway::HTTPRouteStatus>,\n    resource_port: &ResourcePort,\n) -> bool {\n    let (kind, group) = match resource_port.kind {\n        ResourceKind::Service => (Service::kind(&()), Service::group(&())),\n        ResourceKind::EgressNetwork => (\n            policy::EgressNetwork::kind(&()),\n            policy::EgressNetwork::group(&()),\n        ),\n    };\n    let mut group = &*group;\n    if group.is_empty() {\n        group = \"core\";\n    }\n    route_status\n        .map(|status| status.parents.as_slice())\n        .unwrap_or_default()\n        .iter()\n        .any(|parent_status| {\n            let port_matches = match parent_status.parent_ref.port {\n                Some(port) => port == resource_port.port.get() as i32,\n                None => true,\n            };\n            let mut parent_group = parent_status.parent_ref.group.as_deref().unwrap_or(\"core\");\n            if parent_group.is_empty() {\n                parent_group = \"core\";\n            }\n            resource_port.name == parent_status.parent_ref.name\n                && Some(kind.as_ref()) == parent_status.parent_ref.kind.as_deref()\n                && group == parent_group\n                && port_matches\n                && parent_status\n                    .conditions\n                    .iter()\n                    .flatten()\n                    .any(|condition| condition.type_ == \"Accepted\" && condition.status == \"True\")\n        })\n}\n\npub fn route_accepted_by_service(\n    route_status: Option<&gateway::HTTPRouteStatus>,\n    service: &str,\n) -> bool {\n    let mut service_group = &*Service::group(&());\n    if service_group.is_empty() {\n        service_group = \"core\";\n    }\n    route_status\n        .map(|status| status.parents.as_slice())\n        .unwrap_or_default()\n        .iter()\n        .any(|parent_status| {\n            let mut parent_group = parent_status.parent_ref.group.as_deref().unwrap_or(\"core\");\n            if parent_group.is_empty() {\n                parent_group = \"core\";\n            }\n            parent_status.parent_ref.name == service\n                && parent_status.parent_ref.kind.as_deref() == Some(Service::kind(&()).as_ref())\n                && parent_group == service_group\n                && parent_status\n                    .conditions\n                    .iter()\n                    .flatten()\n                    .any(|condition| condition.type_ == \"Accepted\" && condition.status == \"True\")\n        })\n}\n\npub(crate) fn backend_kind(backend: &gateway::HTTPRouteRulesBackendRefs) -> Option<ResourceKind> {\n    let group = backend.group.as_deref();\n    // Backends default to `Service` if no kind is specified.\n    let kind = backend.kind.as_deref().unwrap_or(\"Service\");\n    if super::is_service(group, kind) {\n        Some(ResourceKind::Service)\n    } else if super::is_egress_network(group, kind) {\n        Some(ResourceKind::EgressNetwork)\n    } else {\n        None\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/outbound/index/metrics.rs",
    "content": "use prometheus_client::{\n    collector::Collector,\n    encoding::{DescriptorEncoder, EncodeMetric},\n    metrics::{gauge::ConstGauge, MetricType},\n    registry::Registry,\n};\n\nuse super::SharedIndex;\n\n#[derive(Debug)]\nstruct Instrumented(SharedIndex);\n\npub fn register(reg: &mut Registry, index: SharedIndex) {\n    reg.register_collector(Box::new(Instrumented(index)));\n}\n\nimpl Collector for Instrumented {\n    fn encode(&self, mut encoder: DescriptorEncoder<'_>) -> Result<(), std::fmt::Error> {\n        let this = self.0.read();\n\n        let service_encoder = encoder.encode_descriptor(\n            \"service_index_size\",\n            \"The number of entires in service index\",\n            None,\n            MetricType::Gauge,\n        )?;\n        let services = ConstGauge::new(this.services_by_ip.len() as u32);\n        services.encode(service_encoder)?;\n\n        let service_info_encoder = encoder.encode_descriptor(\n            \"service_info_index_size\",\n            \"The number of entires in the service info index\",\n            None,\n            MetricType::Gauge,\n        )?;\n        let service_infos = ConstGauge::new(this.resource_info.len() as u32);\n        service_infos.encode(service_info_encoder)?;\n\n        let mut service_route_encoder = encoder.encode_descriptor(\n            \"service_route_index_size\",\n            \"The number of entires in the service route index\",\n            None,\n            MetricType::Gauge,\n        )?;\n        for (ns, index) in &this.namespaces.by_ns {\n            let labels = vec![(\"namespace\", ns.as_str())];\n            let service_routes = ConstGauge::new(index.service_http_routes.len() as u32);\n            let service_route_encoder = service_route_encoder.encode_family(&labels)?;\n            service_routes.encode(service_route_encoder)?;\n        }\n\n        let mut service_port_route_encoder = encoder.encode_descriptor(\n            \"service_port_route_index_size\",\n            \"The number of entires in the service port route index\",\n            None,\n            MetricType::Gauge,\n        )?;\n        for (ns, index) in &this.namespaces.by_ns {\n            let labels = vec![(\"namespace\", ns.as_str())];\n            let service_port_routes = ConstGauge::new(index.resource_port_routes.len() as u32);\n            let service_port_route_encoder = service_port_route_encoder.encode_family(&labels)?;\n            service_port_routes.encode(service_port_route_encoder)?;\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/outbound/index/tcp.rs",
    "content": "use std::num::NonZeroU16;\n\nuse super::{ResourceInfo, ResourceKind, ResourcePort, ResourceRef};\nuse crate::ClusterInfo;\nuse ahash::AHashMap as HashMap;\nuse anyhow::{bail, Result};\nuse linkerd_policy_controller_core::outbound::{Backend, WeightedEgressNetwork, WeightedService};\nuse linkerd_policy_controller_core::outbound::{TcpRoute, TcpRouteRule};\nuse linkerd_policy_controller_k8s_api::Service;\nuse linkerd_policy_controller_k8s_api::{gateway, policy, Resource, Time};\n\npub(super) fn convert_route(\n    ns: &str,\n    route: gateway::TCPRoute,\n    cluster: &ClusterInfo,\n    resource_info: &HashMap<ResourceRef, ResourceInfo>,\n) -> Result<TcpRoute> {\n    if route.spec.rules.len() != 1 {\n        bail!(\"TCPRoute needs to have one rule\");\n    }\n\n    let rule = route.spec.rules.first().expect(\"already checked\");\n\n    let backends = rule\n        .backend_refs\n        .clone()\n        .into_iter()\n        .flatten()\n        .filter_map(|b| convert_backend(ns, b, cluster, resource_info))\n        .collect();\n\n    let creation_timestamp = route.metadata.creation_timestamp.map(|Time(t)| t);\n\n    Ok(TcpRoute {\n        rule: TcpRouteRule { backends },\n        creation_timestamp,\n    })\n}\n\npub(super) fn convert_backend(\n    ns: &str,\n    backend: gateway::TCPRouteRulesBackendRefs,\n    cluster: &ClusterInfo,\n    resources: &HashMap<ResourceRef, ResourceInfo>,\n) -> Option<Backend> {\n    let backend_kind = match backend_kind(&backend) {\n        Some(backend_kind) => backend_kind,\n        None => {\n            return Some(Backend::Invalid {\n                weight: backend.weight.unwrap_or(1) as u32,\n                message: format!(\n                    \"unsupported backend type {group} {kind}\",\n                    group = backend.group.as_deref().unwrap_or(\"core\"),\n                    kind = backend.kind.as_deref().unwrap_or(\"<empty>\"),\n                ),\n            });\n        }\n    };\n\n    let backend_ref = ResourceRef {\n        name: backend.name.clone(),\n        namespace: backend.namespace.unwrap_or_else(|| ns.to_string()),\n        kind: backend_kind.clone(),\n    };\n\n    let name = backend.name;\n    let weight = backend.weight.unwrap_or(1) as u32;\n\n    let port = backend\n        .port\n        .and_then(|p| p.try_into().ok())\n        .and_then(|p: u16| NonZeroU16::try_from(p).ok());\n\n    match backend_kind {\n        ResourceKind::Service => {\n            // The gateway API dictates:\n            //\n            // Port is required when the referent is a Kubernetes Service.\n            let port = match port {\n                Some(port) => port,\n                None => {\n                    return Some(Backend::Invalid {\n                        weight,\n                        message: format!(\"missing port for backend Service {name}\"),\n                    })\n                }\n            };\n\n            Some(Backend::Service(WeightedService {\n                weight,\n                authority: cluster.service_dns_authority(&backend_ref.namespace, &name, port),\n                name,\n                namespace: backend_ref.namespace.to_string(),\n                port,\n                filters: vec![],\n                exists: resources.contains_key(&backend_ref),\n            }))\n        }\n        ResourceKind::EgressNetwork => Some(Backend::EgressNetwork(WeightedEgressNetwork {\n            weight,\n            name,\n            namespace: backend_ref.namespace.to_string(),\n            port,\n            filters: vec![],\n            exists: resources.contains_key(&backend_ref),\n        })),\n    }\n}\n\npub(super) fn route_accepted_by_resource_port(\n    route_status: Option<&gateway::TCPRouteStatus>,\n    resource_port: &ResourcePort,\n) -> bool {\n    let (kind, group) = match resource_port.kind {\n        ResourceKind::Service => (Service::kind(&()), Service::group(&())),\n        ResourceKind::EgressNetwork => (\n            policy::EgressNetwork::kind(&()),\n            policy::EgressNetwork::group(&()),\n        ),\n    };\n    let mut group = &*group;\n    if group.is_empty() {\n        group = \"core\";\n    }\n    route_status\n        .map(|status| status.parents.as_slice())\n        .unwrap_or_default()\n        .iter()\n        .any(|parent_status| {\n            let port_matches = match parent_status.parent_ref.port {\n                Some(port) => port == resource_port.port.get() as i32,\n                None => true,\n            };\n            let mut parent_group = parent_status.parent_ref.group.as_deref().unwrap_or(\"core\");\n            if parent_group.is_empty() {\n                parent_group = \"core\";\n            }\n            resource_port.name == parent_status.parent_ref.name\n                && Some(kind.as_ref()) == parent_status.parent_ref.kind.as_deref()\n                && group == parent_group\n                && port_matches\n                && parent_status\n                    .conditions\n                    .iter()\n                    .flatten()\n                    .any(|condition| condition.type_ == \"Accepted\" && condition.status == \"True\")\n        })\n}\n\npub fn route_accepted_by_service(\n    route_status: Option<&gateway::TCPRouteStatus>,\n    service: &str,\n) -> bool {\n    let mut service_group = &*Service::group(&());\n    if service_group.is_empty() {\n        service_group = \"core\";\n    }\n    route_status\n        .map(|status| status.parents.as_slice())\n        .unwrap_or_default()\n        .iter()\n        .any(|parent_status| {\n            let mut parent_group = parent_status.parent_ref.group.as_deref().unwrap_or(\"core\");\n            if parent_group.is_empty() {\n                parent_group = \"core\";\n            }\n            parent_status.parent_ref.name == service\n                && parent_status.parent_ref.kind.as_deref() == Some(Service::kind(&()).as_ref())\n                && parent_group == service_group\n                && parent_status\n                    .conditions\n                    .iter()\n                    .flatten()\n                    .any(|condition| condition.type_ == \"Accepted\" && condition.status == \"True\")\n        })\n}\n\npub(crate) fn backend_kind(backend: &gateway::TCPRouteRulesBackendRefs) -> Option<ResourceKind> {\n    let group = backend.group.as_deref();\n    // Backends default to `Service` if no kind is specified.\n    let kind = backend.kind.as_deref().unwrap_or(\"Service\");\n    if super::is_service(group, kind) {\n        Some(ResourceKind::Service)\n    } else if super::is_egress_network(group, kind) {\n        Some(ResourceKind::EgressNetwork)\n    } else {\n        None\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/outbound/index/tls.rs",
    "content": "use std::num::NonZeroU16;\n\nuse super::{ResourceInfo, ResourceKind, ResourcePort, ResourceRef};\nuse crate::{routes, ClusterInfo};\nuse ahash::AHashMap as HashMap;\nuse anyhow::{bail, Result};\nuse linkerd_policy_controller_core::outbound::{\n    Backend, TcpRouteRule, TlsRoute, WeightedEgressNetwork, WeightedService,\n};\nuse linkerd_policy_controller_k8s_api::{gateway, policy, Resource, Service, Time};\n\npub(super) fn convert_route(\n    ns: &str,\n    route: gateway::TLSRoute,\n    cluster: &ClusterInfo,\n    resource_info: &HashMap<ResourceRef, ResourceInfo>,\n) -> Result<TlsRoute> {\n    if route.spec.rules.len() != 1 {\n        bail!(\"TLSRoute needs to have one rule\");\n    }\n\n    let rule = route.spec.rules.first().expect(\"already checked\");\n\n    let hostnames = route\n        .spec\n        .hostnames\n        .into_iter()\n        .flatten()\n        .map(routes::host_match)\n        .collect();\n\n    let backends = rule\n        .backend_refs\n        .clone()\n        .into_iter()\n        .flatten()\n        .filter_map(|b| convert_backend(ns, b, cluster, resource_info))\n        .collect();\n\n    let creation_timestamp = route.metadata.creation_timestamp.map(|Time(t)| t);\n\n    Ok(TlsRoute {\n        hostnames,\n        rule: TcpRouteRule { backends },\n        creation_timestamp,\n    })\n}\n\npub(super) fn convert_backend(\n    ns: &str,\n    backend: gateway::TLSRouteRulesBackendRefs,\n    cluster: &ClusterInfo,\n    resources: &HashMap<ResourceRef, ResourceInfo>,\n) -> Option<Backend> {\n    let backend_kind = match backend_kind(&backend) {\n        Some(backend_kind) => backend_kind,\n        None => {\n            return Some(Backend::Invalid {\n                weight: backend.weight.unwrap_or(1) as u32,\n                message: format!(\n                    \"unsupported backend type {group} {kind}\",\n                    group = backend.group.as_deref().unwrap_or(\"core\"),\n                    kind = backend.kind.as_deref().unwrap_or(\"<empty>\"),\n                ),\n            });\n        }\n    };\n\n    let backend_ref = ResourceRef {\n        name: backend.name.clone(),\n        namespace: backend.namespace.unwrap_or_else(|| ns.to_string()),\n        kind: backend_kind.clone(),\n    };\n\n    let name = backend.name;\n    let weight = backend.weight.unwrap_or(1) as u32;\n\n    let port = backend\n        .port\n        .and_then(|p| p.try_into().ok())\n        .and_then(|p: u16| NonZeroU16::try_from(p).ok());\n\n    match backend_kind {\n        ResourceKind::Service => {\n            // The gateway API dictates:\n            //\n            // Port is required when the referent is a Kubernetes Service.\n            let port = match port {\n                Some(port) => port,\n                None => {\n                    return Some(Backend::Invalid {\n                        weight,\n                        message: format!(\"missing port for backend Service {name}\"),\n                    })\n                }\n            };\n\n            Some(Backend::Service(WeightedService {\n                weight,\n                authority: cluster.service_dns_authority(&backend_ref.namespace, &name, port),\n                name,\n                namespace: backend_ref.namespace.to_string(),\n                port,\n                filters: vec![],\n                exists: resources.contains_key(&backend_ref),\n            }))\n        }\n        ResourceKind::EgressNetwork => Some(Backend::EgressNetwork(WeightedEgressNetwork {\n            weight,\n            name,\n            namespace: backend_ref.namespace.to_string(),\n            port,\n            filters: vec![],\n            exists: resources.contains_key(&backend_ref),\n        })),\n    }\n}\n\npub(super) fn route_accepted_by_resource_port(\n    route_status: Option<&gateway::TLSRouteStatus>,\n    resource_port: &ResourcePort,\n) -> bool {\n    let (kind, group) = match resource_port.kind {\n        ResourceKind::Service => (Service::kind(&()), Service::group(&())),\n        ResourceKind::EgressNetwork => (\n            policy::EgressNetwork::kind(&()),\n            policy::EgressNetwork::group(&()),\n        ),\n    };\n    let mut group = &*group;\n    if group.is_empty() {\n        group = \"core\";\n    }\n    route_status\n        .map(|status| status.parents.as_slice())\n        .unwrap_or_default()\n        .iter()\n        .any(|parent_status| {\n            let port_matches = match parent_status.parent_ref.port {\n                Some(port) => port == resource_port.port.get() as i32,\n                None => true,\n            };\n            let mut parent_group = parent_status.parent_ref.group.as_deref().unwrap_or(\"core\");\n            if parent_group.is_empty() {\n                parent_group = \"core\";\n            }\n            resource_port.name == parent_status.parent_ref.name\n                && Some(kind.as_ref()) == parent_status.parent_ref.kind.as_deref()\n                && group == parent_group\n                && port_matches\n                && parent_status\n                    .conditions\n                    .iter()\n                    .flatten()\n                    .any(|condition| condition.type_ == \"Accepted\" && condition.status == \"True\")\n        })\n}\n\npub fn route_accepted_by_service(\n    route_status: Option<&gateway::TLSRouteStatus>,\n    service: &str,\n) -> bool {\n    let mut service_group = &*Service::group(&());\n    if service_group.is_empty() {\n        service_group = \"core\";\n    }\n    route_status\n        .map(|status| status.parents.as_slice())\n        .unwrap_or_default()\n        .iter()\n        .any(|parent_status| {\n            let mut parent_group = parent_status.parent_ref.group.as_deref().unwrap_or(\"core\");\n            if parent_group.is_empty() {\n                parent_group = \"core\";\n            }\n            parent_status.parent_ref.name == service\n                && parent_status.parent_ref.kind.as_deref() == Some(Service::kind(&()).as_ref())\n                && parent_group == service_group\n                && parent_status\n                    .conditions\n                    .iter()\n                    .flatten()\n                    .any(|condition| condition.type_ == \"Accepted\" && condition.status == \"True\")\n        })\n}\n\npub(crate) fn backend_kind(backend: &gateway::TLSRouteRulesBackendRefs) -> Option<ResourceKind> {\n    let group = backend.group.as_deref();\n    // Backends default to `Service` if no kind is specified.\n    let kind = backend.kind.as_deref().unwrap_or(\"Service\");\n    if super::is_service(group, kind) {\n        Some(ResourceKind::Service)\n    } else if super::is_egress_network(group, kind) {\n        Some(ResourceKind::EgressNetwork)\n    } else {\n        None\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/outbound/index.rs",
    "content": "use crate::{\n    ports::{ports_annotation, PortMap, PortSet},\n    routes::{ExplicitGKN, HttpRouteResource, ImpliedGKN},\n    ClusterInfo,\n};\nuse ahash::AHashMap as HashMap;\nuse anyhow::{bail, ensure, Result};\nuse egress_network::EgressNetwork;\nuse linkerd_policy_controller_core::{\n    outbound::{\n        AppProtocol, Backend, Backoff, FailureAccrual, GrpcRetryCondition, GrpcRoute,\n        HttpRetryCondition, HttpRoute, Kind, OutboundDiscoverTarget, OutboundPolicy, ParentInfo,\n        ResourceTarget, RouteRetry, RouteSet, RouteTimeouts, TcpRoute, TlsRoute, TrafficPolicy,\n    },\n    routes::GroupKindNamespaceName,\n};\nuse linkerd_policy_controller_k8s_api::{\n    gateway,\n    policy::{self as linkerd_k8s_api, Cidr},\n    ResourceExt, Service,\n};\nuse parking_lot::RwLock;\nuse std::{\n    collections::hash_map::Entry, hash::Hash, net::IpAddr, num::NonZeroU16, str::FromStr,\n    sync::Arc, time,\n};\nuse tokio::sync::watch;\n\n#[allow(dead_code)]\n#[derive(Debug)]\npub struct Index {\n    namespaces: NamespaceIndex,\n    services_by_ip: HashMap<IpAddr, ServicePorts>,\n    egress_networks_by_ref: HashMap<ResourceRef, EgressNetwork>,\n    // holds information about resources. currently EgressNetworks and Services\n    resource_info: HashMap<ResourceRef, ResourceInfo>,\n\n    cluster_networks: Vec<linkerd_k8s_api::Cidr>,\n    global_egress_network_namespace: Arc<String>,\n\n    // holds a no-op sender to which all clients that have been returned\n    // a Fallback policy are subsribed. It is used to force these clients\n    // to reconnect an obtain new policy once the current one may no longer\n    // be valid\n    fallback_polcy_tx: watch::Sender<()>,\n}\n\npub mod egress_network;\npub mod grpc;\npub mod http;\npub mod metrics;\npub mod tcp;\npub(crate) mod tls;\n\npub type SharedIndex = Arc<RwLock<Index>>;\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub enum ResourceKind {\n    EgressNetwork,\n    Service,\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct ResourceRef {\n    pub kind: ResourceKind,\n    pub name: String,\n    pub namespace: String,\n}\n\n#[derive(Debug)]\nstruct ServicePorts {\n    service: ResourceRef,\n    ports: PortSet,\n}\n\n/// Holds all `Pod`, `Server`, and `ServerAuthorization` indices by-namespace.\n#[derive(Debug)]\nstruct NamespaceIndex {\n    cluster_info: Arc<ClusterInfo>,\n    by_ns: HashMap<String, Namespace>,\n}\n\n#[derive(Debug)]\nstruct Namespace {\n    /// Stores an observable handle for each known resource:port,\n    /// as well as any route resources in the cluster that specify\n    /// a port.\n    resource_port_routes: HashMap<ResourcePort, ResourceRoutes>,\n    /// Stores the route resources (by service name) that do not\n    /// explicitly target a port. These are only valid for Service\n    /// as EgressNetworks cannot be parents without an explicit\n    /// port declaration\n    service_http_routes: HashMap<String, RouteSet<HttpRoute>>,\n    service_grpc_routes: HashMap<String, RouteSet<GrpcRoute>>,\n    service_tls_routes: HashMap<String, RouteSet<TlsRoute>>,\n    service_tcp_routes: HashMap<String, RouteSet<TcpRoute>>,\n    namespace: Arc<String>,\n}\n\n#[derive(Debug)]\nstruct ResourceInfo {\n    app_protocols: PortMap<AppProtocol>,\n    accrual: Option<FailureAccrual>,\n    http_retry: Option<RouteRetry<HttpRetryCondition>>,\n    grpc_retry: Option<RouteRetry<GrpcRetryCondition>>,\n    timeouts: RouteTimeouts,\n    traffic_policy: Option<TrafficPolicy>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\nstruct ResourcePort {\n    kind: ResourceKind,\n    name: String,\n    port: NonZeroU16,\n}\n\n#[derive(Debug)]\nstruct ResourceRoutes {\n    parent_info: ParentInfo,\n    namespace: Arc<String>,\n    port: NonZeroU16,\n    watches_by_ns: HashMap<String, RoutesWatch>,\n    app_protocol: Option<AppProtocol>,\n    accrual: Option<FailureAccrual>,\n    http_retry: Option<RouteRetry<HttpRetryCondition>>,\n    grpc_retry: Option<RouteRetry<GrpcRetryCondition>>,\n    timeouts: RouteTimeouts,\n}\n\n#[derive(Debug)]\nstruct RoutesWatch {\n    parent_info: ParentInfo,\n    app_protocol: Option<AppProtocol>,\n    accrual: Option<FailureAccrual>,\n    http_retry: Option<RouteRetry<HttpRetryCondition>>,\n    grpc_retry: Option<RouteRetry<GrpcRetryCondition>>,\n    timeouts: RouteTimeouts,\n    http_routes: RouteSet<HttpRoute>,\n    grpc_routes: RouteSet<GrpcRoute>,\n    tls_routes: RouteSet<TlsRoute>,\n    tcp_routes: RouteSet<TcpRoute>,\n    watch: watch::Sender<OutboundPolicy>,\n}\n\nimpl kubert::index::IndexNamespacedResource<linkerd_k8s_api::HttpRoute> for Index {\n    fn apply(&mut self, route: linkerd_k8s_api::HttpRoute) {\n        self.apply_http(HttpRouteResource::LinkerdHttp(route))\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        let gknn = name\n            .gkn::<linkerd_k8s_api::HttpRoute>()\n            .namespaced(namespace);\n        tracing::debug!(?gknn, \"deleting route\");\n        for ns_index in self.namespaces.by_ns.values_mut() {\n            ns_index.delete_http_route(&gknn);\n        }\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<gateway::HTTPRoute> for Index {\n    fn apply(&mut self, route: gateway::HTTPRoute) {\n        self.apply_http(HttpRouteResource::GatewayHttp(route))\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        let gknn = name.gkn::<gateway::HTTPRoute>().namespaced(namespace);\n        for ns_index in self.namespaces.by_ns.values_mut() {\n            ns_index.delete_http_route(&gknn);\n        }\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<gateway::GRPCRoute> for Index {\n    fn apply(&mut self, route: gateway::GRPCRoute) {\n        self.apply_grpc(route)\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        let gknn = name.gkn::<gateway::GRPCRoute>().namespaced(namespace);\n        for ns_index in self.namespaces.by_ns.values_mut() {\n            ns_index.delete_grpc_route(&gknn);\n        }\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<gateway::TLSRoute> for Index {\n    fn apply(&mut self, route: gateway::TLSRoute) {\n        self.apply_tls(route)\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        let gknn = name.gkn::<gateway::TLSRoute>().namespaced(namespace);\n        for ns_index in self.namespaces.by_ns.values_mut() {\n            ns_index.delete_tls_route(&gknn);\n        }\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<gateway::TCPRoute> for Index {\n    fn apply(&mut self, route: gateway::TCPRoute) {\n        self.apply_tcp(route)\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        let gknn = name.gkn::<gateway::TCPRoute>().namespaced(namespace);\n        for ns_index in self.namespaces.by_ns.values_mut() {\n            ns_index.delete_tcp_route(&gknn);\n        }\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<Service> for Index {\n    fn apply(&mut self, service: Service) {\n        let name = service.name_unchecked();\n        let ns = service.namespace().expect(\"Service must have a namespace\");\n        tracing::debug!(name, ns, \"indexing service\");\n        let accrual = parse_accrual_config(service.annotations())\n            .map_err(|error| tracing::warn!(%error, service=name, namespace=ns, \"Failed to parse accrual config\"))\n            .unwrap_or_default();\n\n        let mut app_protocols = service\n            .spec\n            .as_ref()\n            .and_then(|spec| {\n                spec.ports.as_ref().map(|ports| {\n                    ports\n                        .iter()\n                        .filter_map(|port| {\n                            port.app_protocol.as_ref().and_then(|p| {\n                                Some((\n                                    NonZeroU16::new(port.port as u16)?,\n                                    AppProtocol::from_str(p.as_str()).expect(\"Infalliable\"),\n                                ))\n                            })\n                        })\n                        .collect::<PortMap<AppProtocol>>()\n                })\n            })\n            .unwrap_or_default();\n        let opaque_ports =\n            ports_annotation(service.annotations(), \"config.linkerd.io/opaque-ports\")\n                .unwrap_or_else(|| self.namespaces.cluster_info.default_opaque_ports.clone());\n        for opaque_port in opaque_ports {\n            match app_protocols.entry(opaque_port) {\n                Entry::Occupied(occupied) => {\n                    tracing::debug!(\n                        appProtocol = ?occupied.get(),\n                        port = opaque_port.get(),\n                        ns,\n                        name,\n                        \"`appProtocol` set on service port, ignoring opaque port setting\"\n                    );\n                }\n                Entry::Vacant(vacant) => {\n                    vacant.insert(AppProtocol::Opaque);\n                }\n            }\n        }\n\n        let timeouts = parse_timeouts(service.annotations())\n            .map_err(|error| tracing::warn!(%error, service=name, namespace=ns, \"Failed to parse timeouts\"))\n            .unwrap_or_default();\n\n        let http_retry = http::parse_http_retry(service.annotations()).map_err(|error| {\n            tracing::warn!(%error, service=name, namespace=ns, \"Failed to parse http retry\")\n        }).unwrap_or_default();\n        let grpc_retry = grpc::parse_grpc_retry(service.annotations()).map_err(|error| {\n            tracing::warn!(%error, service=name, namespace=ns, \"Failed to parse grpc retry\")\n        }).unwrap_or_default();\n\n        let service_ref = ResourceRef {\n            kind: ResourceKind::Service,\n            name: name.clone(),\n            namespace: ns.clone(),\n        };\n        self.services_by_ip.retain(|_, v| v.service != service_ref);\n        if let Some(cluster_ips) = service\n            .spec\n            .as_ref()\n            .and_then(|spec| spec.cluster_ips.as_deref())\n        {\n            for cluster_ip in cluster_ips {\n                if cluster_ip == \"None\" {\n                    continue;\n                }\n                match cluster_ip.parse() {\n                    Ok(addr) => {\n                        service.spec.as_ref().and_then(|spec| {\n                            spec.ports.as_ref().map(|ports| {\n                                ports.iter().for_each(|port| {\n                                    let port = match port.port.try_into().ok().and_then(NonZeroU16::new) {\n                                        Some(port) => port,\n                                        None => {\n                                            tracing::warn!(%port.port, service=name, \"Invalid service port\");\n                                            return;\n                                        }\n                                    };\n                                    tracing::debug!(\n                                        %addr,\n                                        port,\n                                        service = name,\n                                        \"inserting service into ip index\"\n                                    );\n                                    self.services_by_ip\n                                        .entry(addr)\n                                        .or_insert(\n                                            ServicePorts { service: service_ref.clone(), ports: Default::default() }\n                                        ).ports.insert(port);\n                                });\n                            })\n                        });\n                    }\n                    Err(error) => {\n                        tracing::warn!(%error, service=name, cluster_ip, \"Invalid cluster ip\");\n                    }\n                }\n            }\n        }\n\n        let service_info = ResourceInfo {\n            app_protocols,\n            accrual,\n            http_retry,\n            grpc_retry,\n            timeouts,\n            traffic_policy: None,\n        };\n\n        self.namespaces\n            .by_ns\n            .entry(ns.clone())\n            .or_insert_with(|| Namespace {\n                service_http_routes: Default::default(),\n                service_grpc_routes: Default::default(),\n                service_tls_routes: Default::default(),\n                service_tcp_routes: Default::default(),\n                resource_port_routes: Default::default(),\n                namespace: Arc::new(ns),\n            })\n            .update_resource(\n                service.name_unchecked(),\n                ResourceKind::Service,\n                &service_info,\n            );\n\n        self.resource_info.insert(\n            ResourceRef {\n                kind: ResourceKind::Service,\n                name: service.name_unchecked(),\n                namespace: service.namespace().expect(\"Service must have Namespace\"),\n            },\n            service_info,\n        );\n\n        self.reindex_resources();\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        tracing::debug!(name, namespace, \"deleting service\");\n        let service_ref = ResourceRef {\n            kind: ResourceKind::Service,\n            name,\n            namespace,\n        };\n        self.resource_info.remove(&service_ref);\n        self.services_by_ip.retain(|_, v| v.service != service_ref);\n\n        self.reindex_resources();\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<linkerd_k8s_api::EgressNetwork> for Index {\n    fn apply(&mut self, egress_network: linkerd_k8s_api::EgressNetwork) {\n        let name = egress_network.name_unchecked();\n        let ns = egress_network\n            .namespace()\n            .expect(\"EgressNetwork must have a namespace\");\n        tracing::debug!(name, ns, \"indexing EgressNetwork\");\n        let accrual = parse_accrual_config(egress_network.annotations())\n            .map_err(|error| tracing::warn!(%error, service=name, namespace=ns, \"Failed to parse accrual config\"))\n            .unwrap_or_default();\n        let opaque_ports = ports_annotation(\n            egress_network.annotations(),\n            \"config.linkerd.io/opaque-ports\",\n        )\n        .unwrap_or_else(|| self.namespaces.cluster_info.default_opaque_ports.clone());\n        let app_protocols = opaque_ports\n            .into_iter()\n            .map(|port| (port, AppProtocol::Opaque))\n            .collect();\n\n        let timeouts = parse_timeouts(egress_network.annotations())\n            .map_err(|error| tracing::warn!(%error, service=name, namespace=ns, \"Failed to parse timeouts\"))\n            .unwrap_or_default();\n\n        let http_retry = http::parse_http_retry(egress_network.annotations()).map_err(|error| {\n            tracing::warn!(%error, service=name, namespace=ns, \"Failed to parse http retry\")\n        }).unwrap_or_default();\n        let grpc_retry = grpc::parse_grpc_retry(egress_network.annotations()).map_err(|error| {\n            tracing::warn!(%error, service=name, namespace=ns, \"Failed to parse grpc retry\")\n        }).unwrap_or_default();\n\n        let egress_net_ref = ResourceRef {\n            kind: ResourceKind::EgressNetwork,\n            name: name.clone(),\n            namespace: ns.clone(),\n        };\n\n        let egress_net =\n            EgressNetwork::from_resource(&egress_network, self.cluster_networks.clone());\n\n        let traffic_policy = Some(match egress_net.traffic_policy {\n            linkerd_k8s_api::TrafficPolicy::Allow => TrafficPolicy::Allow,\n            linkerd_k8s_api::TrafficPolicy::Deny => TrafficPolicy::Deny,\n        });\n\n        self.egress_networks_by_ref\n            .insert(egress_net_ref.clone(), egress_net);\n\n        let egress_network_info = ResourceInfo {\n            app_protocols,\n            accrual,\n            http_retry,\n            grpc_retry,\n            timeouts,\n            traffic_policy,\n        };\n\n        let ns = Arc::new(ns);\n        self.namespaces\n            .by_ns\n            .entry(ns.to_string())\n            .or_insert_with(|| Namespace {\n                service_http_routes: Default::default(),\n                service_grpc_routes: Default::default(),\n                service_tls_routes: Default::default(),\n                service_tcp_routes: Default::default(),\n                resource_port_routes: Default::default(),\n                namespace: ns.clone(),\n            })\n            .update_resource(\n                egress_network.name_unchecked(),\n                ResourceKind::EgressNetwork,\n                &egress_network_info,\n            );\n\n        self.resource_info\n            .insert(egress_net_ref, egress_network_info);\n\n        self.reindex_resources();\n        self.reinitialize_egress_watches(&ns);\n        self.reinitialize_fallback_watches()\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        tracing::debug!(name, namespace, \"deleting EgressNetwork\");\n        let egress_net_ref = ResourceRef {\n            kind: ResourceKind::EgressNetwork,\n            name,\n            namespace,\n        };\n        self.egress_networks_by_ref.remove(&egress_net_ref);\n\n        self.reindex_resources();\n        self.reinitialize_egress_watches(&egress_net_ref.namespace);\n        self.reinitialize_fallback_watches()\n    }\n}\n\nimpl Index {\n    pub fn shared(cluster_info: Arc<ClusterInfo>) -> SharedIndex {\n        let cluster_networks = cluster_info.networks.clone();\n        let global_egress_network_namespace = cluster_info.global_egress_network_namespace.clone();\n\n        let (fallback_polcy_tx, _) = watch::channel(());\n        Arc::new(RwLock::new(Self {\n            namespaces: NamespaceIndex {\n                by_ns: HashMap::default(),\n                cluster_info,\n            },\n            services_by_ip: HashMap::default(),\n            egress_networks_by_ref: HashMap::default(),\n            resource_info: HashMap::default(),\n            cluster_networks: cluster_networks.into_iter().map(Cidr::from).collect(),\n            fallback_polcy_tx,\n            global_egress_network_namespace,\n        }))\n    }\n\n    pub fn is_address_in_cluster(&self, addr: IpAddr) -> bool {\n        self.cluster_networks\n            .iter()\n            .any(|net| net.contains(&addr.into()))\n    }\n\n    pub fn fallback_policy_rx(&self) -> watch::Receiver<()> {\n        self.fallback_polcy_tx.subscribe()\n    }\n\n    fn reinitialize_fallback_watches(&mut self) {\n        let (new_fallback_tx, _) = watch::channel(());\n        self.fallback_polcy_tx = new_fallback_tx;\n    }\n\n    pub fn outbound_policy_rx(\n        &mut self,\n        target: ResourceTarget,\n    ) -> Result<watch::Receiver<OutboundPolicy>> {\n        let ResourceTarget {\n            name,\n            namespace,\n            port,\n            source_namespace,\n            kind,\n        } = target;\n\n        let kind = match kind {\n            Kind::EgressNetwork { .. } => ResourceKind::EgressNetwork,\n            Kind::Service => ResourceKind::Service,\n        };\n\n        let ns = self\n            .namespaces\n            .by_ns\n            .entry(namespace.clone())\n            .or_insert_with(|| Namespace {\n                namespace: Arc::new(namespace.to_string()),\n                service_http_routes: Default::default(),\n                service_grpc_routes: Default::default(),\n                service_tls_routes: Default::default(),\n                service_tcp_routes: Default::default(),\n                resource_port_routes: Default::default(),\n            });\n\n        let key = ResourcePort { kind, name, port };\n\n        tracing::debug!(?key, \"subscribing to resource port\");\n\n        let routes =\n            ns.resource_routes_or_default(key, &self.namespaces.cluster_info, &self.resource_info);\n\n        let watch = routes.watch_for_ns_or_default(source_namespace);\n\n        Ok(watch.watch.subscribe())\n    }\n\n    pub fn lookup_service(\n        &self,\n        addr: IpAddr,\n        port: NonZeroU16,\n        source_namespace: String,\n    ) -> Option<OutboundDiscoverTarget> {\n        tracing::debug!(?addr, \"looking up service\");\n\n        let service = self.services_by_ip.get(&addr)?;\n        tracing::debug!(service=?service.service, \"found service\");\n        if service.ports.contains(&port) {\n            Some(OutboundDiscoverTarget::Resource(ResourceTarget {\n                name: service.service.name.clone(),\n                namespace: service.service.namespace.clone(),\n                port,\n                source_namespace,\n                kind: Kind::Service,\n            }))\n        } else {\n            Some(OutboundDiscoverTarget::UndefinedPort(ResourceTarget {\n                name: service.service.name.clone(),\n                namespace: service.service.namespace.clone(),\n                port,\n                source_namespace,\n                kind: Kind::Service,\n            }))\n        }\n    }\n\n    pub fn lookup_egress_network(\n        &self,\n        addr: IpAddr,\n        source_namespace: String,\n    ) -> Option<(String, String)> {\n        egress_network::resolve_egress_network(\n            addr,\n            source_namespace,\n            &self.global_egress_network_namespace,\n            self.egress_networks_by_ref.values(),\n        )\n        .map(|r| (r.namespace, r.name))\n    }\n\n    fn apply_http(&mut self, route: HttpRouteResource) {\n        tracing::debug!(name = route.name(), \"indexing httproute\");\n\n        // For each parent_ref, create a namespace index for it if it doesn't\n        // already exist.\n        for parent_ref in route.parent_refs().iter().flatten() {\n            let ns = parent_ref\n                .namespace\n                .clone()\n                .unwrap_or_else(|| route.namespace());\n\n            self.namespaces\n                .by_ns\n                .entry(ns.clone())\n                .or_insert_with(|| Namespace {\n                    namespace: Arc::new(ns),\n                    service_http_routes: Default::default(),\n                    service_grpc_routes: Default::default(),\n                    service_tls_routes: Default::default(),\n                    service_tcp_routes: Default::default(),\n                    resource_port_routes: Default::default(),\n                });\n        }\n\n        // We must send the route update to all namespace indexes in case this\n        // route's parent_refs have changed and this route must be removed by\n        // any of them.\n        self.namespaces.by_ns.values_mut().for_each(|ns| {\n            ns.apply_http_route(\n                route.clone(),\n                &self.namespaces.cluster_info,\n                &self.resource_info,\n            );\n        });\n    }\n\n    fn apply_grpc(&mut self, route: gateway::GRPCRoute) {\n        tracing::debug!(name = route.name_unchecked(), \"indexing grpcroute\");\n\n        // For each parent_ref, create a namespace index for it if it doesn't\n        // already exist.\n        for parent_ref in route.spec.parent_refs.iter().flatten() {\n            let ns = parent_ref\n                .namespace\n                .clone()\n                .unwrap_or_else(|| route.namespace().expect(\"GrpcRoute must have a namespace\"));\n\n            self.namespaces\n                .by_ns\n                .entry(ns.clone())\n                .or_insert_with(|| Namespace {\n                    namespace: Arc::new(ns),\n                    service_http_routes: Default::default(),\n                    service_grpc_routes: Default::default(),\n                    service_tls_routes: Default::default(),\n                    service_tcp_routes: Default::default(),\n                    resource_port_routes: Default::default(),\n                });\n        }\n\n        // We must send the route update to all namespace indexes in case this\n        // route's parent_refs have changed and this route must be removed by\n        // any of them.\n        for ns in self.namespaces.by_ns.values_mut() {\n            ns.apply_grpc_route(\n                route.clone(),\n                &self.namespaces.cluster_info,\n                &self.resource_info,\n            );\n        }\n    }\n\n    fn apply_tls(&mut self, route: gateway::TLSRoute) {\n        tracing::debug!(name = route.name_unchecked(), \"indexing tlsroute\");\n\n        // For each parent_ref, create a namespace index for it if it doesn't\n        // already exist.\n        for parent_ref in route.spec.parent_refs.iter().flatten() {\n            let ns = parent_ref\n                .namespace\n                .clone()\n                .unwrap_or_else(|| route.namespace().expect(\"TlsRoute must have a namespace\"));\n\n            self.namespaces\n                .by_ns\n                .entry(ns.clone())\n                .or_insert_with(|| Namespace {\n                    namespace: Arc::new(ns),\n                    service_http_routes: Default::default(),\n                    service_grpc_routes: Default::default(),\n                    service_tls_routes: Default::default(),\n                    service_tcp_routes: Default::default(),\n                    resource_port_routes: Default::default(),\n                });\n        }\n\n        // We must send the route update to all namespace indexes in case this\n        // route's parent_refs have changed and this route must be removed by\n        // any of them.\n        for ns in self.namespaces.by_ns.values_mut() {\n            ns.apply_tls_route(\n                route.clone(),\n                &self.namespaces.cluster_info,\n                &self.resource_info,\n            );\n        }\n    }\n\n    fn apply_tcp(&mut self, route: gateway::TCPRoute) {\n        tracing::debug!(name = route.name_unchecked(), \"indexing tcproute\");\n\n        // For each parent_ref, create a namespace index for it if it doesn't\n        // already exist.\n        for parent_ref in route.spec.parent_refs.iter().flatten() {\n            let ns = parent_ref\n                .namespace\n                .clone()\n                .unwrap_or_else(|| route.namespace().expect(\"TcpRoute must have a namespace\"));\n\n            self.namespaces\n                .by_ns\n                .entry(ns.clone())\n                .or_insert_with(|| Namespace {\n                    namespace: Arc::new(ns),\n                    service_http_routes: Default::default(),\n                    service_grpc_routes: Default::default(),\n                    service_tls_routes: Default::default(),\n                    service_tcp_routes: Default::default(),\n                    resource_port_routes: Default::default(),\n                });\n        }\n\n        // We must send the route update to all namespace indexes in case this\n        // route's parent_refs have changed and this route must be removed by\n        // any of them.\n        for ns in self.namespaces.by_ns.values_mut() {\n            ns.apply_tcp_route(\n                route.clone(),\n                &self.namespaces.cluster_info,\n                &self.resource_info,\n            );\n        }\n    }\n\n    fn reindex_resources(&mut self) {\n        for ns in self.namespaces.by_ns.values_mut() {\n            ns.reindex_resources(&self.resource_info);\n        }\n    }\n\n    fn reinitialize_egress_watches(&mut self, namespace: &str) {\n        for ns in self.namespaces.by_ns.values_mut() {\n            if namespace == *self.global_egress_network_namespace || namespace == *ns.namespace {\n                ns.reinitialize_egress_watches()\n            }\n        }\n    }\n}\n\nimpl Namespace {\n    fn apply_http_route(\n        &mut self,\n        route: HttpRouteResource,\n        cluster_info: &ClusterInfo,\n        resource_info: &HashMap<ResourceRef, ResourceInfo>,\n    ) {\n        tracing::debug!(?route);\n\n        let outbound_route = match http::convert_route(\n            &self.namespace,\n            route.clone(),\n            cluster_info,\n            resource_info,\n        ) {\n            Ok(route) => route,\n            Err(error) => {\n                tracing::warn!(%error, \"Failed to convert route\");\n                return;\n            }\n        };\n\n        tracing::debug!(?outbound_route);\n\n        for parent_ref in route.parent_refs().iter().flatten() {\n            let parent_kind = if is_parent_service(&parent_ref.kind, &parent_ref.group) {\n                ResourceKind::Service\n            } else if is_parent_egress_network(&parent_ref.kind, &parent_ref.group) {\n                ResourceKind::EgressNetwork\n            } else {\n                continue;\n            };\n            let route_namespace = route.namespace();\n            let parent_namespace = parent_ref.namespace.as_ref().unwrap_or(&route_namespace);\n            if *parent_namespace != *self.namespace {\n                continue;\n            }\n\n            let port = parent_ref\n                .port\n                .and_then(|p| p.try_into().ok())\n                .and_then(NonZeroU16::new);\n\n            if let Some(port) = port {\n                let resource_port = ResourcePort {\n                    kind: parent_kind,\n                    port,\n                    name: parent_ref.name.clone(),\n                };\n\n                if !http::route_accepted_by_resource_port(route.status(), &resource_port) {\n                    continue;\n                }\n\n                tracing::debug!(\n                    ?resource_port,\n                    route = route.name(),\n                    \"inserting httproute for resource\"\n                );\n\n                let service_routes =\n                    self.resource_routes_or_default(resource_port, cluster_info, resource_info);\n\n                service_routes.apply_http_route(route.gknn(), outbound_route.clone());\n            } else {\n                if !http::route_accepted_by_service(route.status(), &parent_ref.name) {\n                    continue;\n                }\n                // If the parent_ref doesn't include a port, apply this route\n                // to all ResourceRoutes which match the resource name.\n                for (ResourcePort { name, port: _, .. }, routes) in\n                    self.resource_port_routes.iter_mut()\n                {\n                    if name == &parent_ref.name {\n                        routes.apply_http_route(route.gknn(), outbound_route.clone());\n                    }\n                }\n\n                // Also add the route to the list of routes that target the\n                // resource without specifying a port.\n                self.service_http_routes\n                    .entry(parent_ref.name.clone())\n                    .or_default()\n                    .insert(route.gknn(), outbound_route.clone());\n            }\n        }\n\n        // Remove the route from all parents that are not in the route's parent_refs.\n        for (resource_port, resource_routes) in self.resource_port_routes.iter_mut() {\n            if !http::route_accepted_by_resource_port(route.status(), resource_port) {\n                resource_routes.delete_http_route(&route.gknn());\n            }\n        }\n        for (parent_name, routes) in self.service_http_routes.iter_mut() {\n            if !http::route_accepted_by_service(route.status(), parent_name) {\n                routes.remove(&route.gknn());\n            }\n        }\n    }\n\n    fn apply_grpc_route(\n        &mut self,\n        route: gateway::GRPCRoute,\n        cluster_info: &ClusterInfo,\n        resource_info: &HashMap<ResourceRef, ResourceInfo>,\n    ) {\n        tracing::debug!(?route);\n        let outbound_route = match grpc::convert_route(\n            &self.namespace,\n            route.clone(),\n            cluster_info,\n            resource_info,\n        ) {\n            Ok(route) => route,\n            Err(error) => {\n                tracing::warn!(%error, \"Failed to convert route\");\n                return;\n            }\n        };\n        let gknn = route\n            .gkn()\n            .namespaced(route.namespace().expect(\"Route must have namespace\"));\n\n        tracing::debug!(?outbound_route);\n\n        for parent_ref in route.spec.parent_refs.iter().flatten() {\n            let parent_kind = if is_parent_service(&parent_ref.kind, &parent_ref.group) {\n                ResourceKind::Service\n            } else if is_parent_egress_network(&parent_ref.kind, &parent_ref.group) {\n                ResourceKind::EgressNetwork\n            } else {\n                continue;\n            };\n            let route_namespace = route.namespace().expect(\"GrpcRoute must have a namespace\");\n            let parent_namespace = parent_ref.namespace.as_ref().unwrap_or(&route_namespace);\n            if *parent_namespace != *self.namespace {\n                continue;\n            }\n\n            let port = parent_ref\n                .port\n                .and_then(|p| p.try_into().ok())\n                .and_then(NonZeroU16::new);\n\n            if let Some(port) = port {\n                let port = ResourcePort {\n                    kind: parent_kind,\n                    port,\n                    name: parent_ref.name.clone(),\n                };\n\n                if !grpc::route_accepted_by_resource_port(route.status.as_ref(), &port) {\n                    continue;\n                }\n\n                tracing::debug!(\n                    ?port,\n                    route = route.name_unchecked(),\n                    \"inserting grpcroute for resource\"\n                );\n\n                let service_routes =\n                    self.resource_routes_or_default(port, cluster_info, resource_info);\n\n                service_routes.apply_grpc_route(gknn.clone(), outbound_route.clone());\n            } else {\n                if !grpc::route_accepted_by_service(route.status.as_ref(), &parent_ref.name) {\n                    continue;\n                }\n                // If the parent_ref doesn't include a port, apply this route\n                // to all ResourceRoutes which match the resource name.\n                self.resource_port_routes.iter_mut().for_each(\n                    |(ResourcePort { name, port: _, .. }, routes)| {\n                        if name == &parent_ref.name {\n                            routes.apply_grpc_route(gknn.clone(), outbound_route.clone());\n                        }\n                    },\n                );\n\n                // Also add the route to the list of routes that target the\n                // resource without specifying a port.\n                self.service_grpc_routes\n                    .entry(parent_ref.name.clone())\n                    .or_default()\n                    .insert(gknn.clone(), outbound_route.clone());\n            }\n        }\n\n        // Remove the route from all parents that are not in the route's parent_refs.\n        for (resource_port, resource_routes) in self.resource_port_routes.iter_mut() {\n            if !grpc::route_accepted_by_resource_port(route.status.as_ref(), resource_port) {\n                resource_routes.delete_grpc_route(&gknn);\n            }\n        }\n        for (parent_name, routes) in self.service_grpc_routes.iter_mut() {\n            if !grpc::route_accepted_by_service(route.status.as_ref(), parent_name) {\n                routes.remove(&gknn);\n            }\n        }\n    }\n\n    fn apply_tls_route(\n        &mut self,\n        route: gateway::TLSRoute,\n        cluster_info: &ClusterInfo,\n        resource_info: &HashMap<ResourceRef, ResourceInfo>,\n    ) {\n        tracing::debug!(?route);\n        let outbound_route =\n            match tls::convert_route(&self.namespace, route.clone(), cluster_info, resource_info) {\n                Ok(route) => route,\n                Err(error) => {\n                    tracing::warn!(%error, \"Failed to convert route\");\n                    return;\n                }\n            };\n\n        tracing::debug!(?outbound_route);\n\n        let gknn = route\n            .gkn()\n            .namespaced(route.namespace().expect(\"Route must have namespace\"));\n        let status = route.status.as_ref();\n\n        for parent_ref in route.spec.parent_refs.iter().flatten() {\n            let parent_kind = if is_parent_service(&parent_ref.kind, &parent_ref.group) {\n                ResourceKind::Service\n            } else if is_parent_egress_network(&parent_ref.kind, &parent_ref.group) {\n                ResourceKind::EgressNetwork\n            } else {\n                continue;\n            };\n            let route_namespace = route.namespace().expect(\"GrpcRoute must have a namespace\");\n            let parent_namespace = parent_ref.namespace.as_ref().unwrap_or(&route_namespace);\n            if *parent_namespace != *self.namespace {\n                continue;\n            }\n\n            let port = parent_ref\n                .port\n                .and_then(|p| p.try_into().ok())\n                .and_then(NonZeroU16::new);\n\n            if let Some(port) = port {\n                let port = ResourcePort {\n                    kind: parent_kind,\n                    port,\n                    name: parent_ref.name.clone(),\n                };\n\n                if !tls::route_accepted_by_resource_port(status, &port) {\n                    continue;\n                }\n\n                tracing::debug!(\n                    ?port,\n                    route = route.name_unchecked(),\n                    \"inserting tlsroute for resource\"\n                );\n\n                let resource_routes =\n                    self.resource_routes_or_default(port, cluster_info, resource_info);\n\n                resource_routes.apply_tls_route(gknn.clone(), outbound_route.clone());\n            } else {\n                if !tls::route_accepted_by_service(status, &parent_ref.name) {\n                    continue;\n                }\n                // If the parent_ref doesn't include a port, apply this route\n                // to all ResourceRoutes which match the resource name.\n                self.resource_port_routes.iter_mut().for_each(\n                    |(ResourcePort { name, port: _, .. }, routes)| {\n                        if name == &parent_ref.name {\n                            routes.apply_tls_route(gknn.clone(), outbound_route.clone());\n                        }\n                    },\n                );\n\n                // Also add the route to the list of routes that target the\n                // resource without specifying a port.\n                self.service_tls_routes\n                    .entry(parent_ref.name.clone())\n                    .or_default()\n                    .insert(gknn.clone(), outbound_route.clone());\n            }\n        }\n\n        // Remove the route from all parents that are not in the route's parent_refs.\n        for (resource_port, resource_routes) in self.resource_port_routes.iter_mut() {\n            if !tls::route_accepted_by_resource_port(status, resource_port) {\n                resource_routes.delete_tls_route(&gknn);\n            }\n        }\n        for (parent_name, routes) in self.service_tls_routes.iter_mut() {\n            if !tls::route_accepted_by_service(status, parent_name) {\n                routes.remove(&gknn);\n            }\n        }\n    }\n\n    fn apply_tcp_route(\n        &mut self,\n        route: gateway::TCPRoute,\n        cluster_info: &ClusterInfo,\n        resource_info: &HashMap<ResourceRef, ResourceInfo>,\n    ) {\n        tracing::debug!(?route);\n        let outbound_route =\n            match tcp::convert_route(&self.namespace, route.clone(), cluster_info, resource_info) {\n                Ok(route) => route,\n                Err(error) => {\n                    tracing::warn!(%error, \"Failed to convert route\");\n                    return;\n                }\n            };\n\n        tracing::debug!(?outbound_route);\n\n        let gknn = route\n            .gkn()\n            .namespaced(route.namespace().expect(\"Route must have namespace\"));\n        let status = route.status.as_ref();\n\n        for parent_ref in route.spec.parent_refs.iter().flatten() {\n            let parent_kind = if is_parent_service(&parent_ref.kind, &parent_ref.group) {\n                ResourceKind::Service\n            } else if is_parent_egress_network(&parent_ref.kind, &parent_ref.group) {\n                ResourceKind::EgressNetwork\n            } else {\n                continue;\n            };\n            let route_namespace = route.namespace().expect(\"GrpcRoute must have a namespace\");\n            let parent_namespace = parent_ref.namespace.as_ref().unwrap_or(&route_namespace);\n            if *parent_namespace != *self.namespace {\n                continue;\n            }\n\n            let port = parent_ref\n                .port\n                .and_then(|p| p.try_into().ok())\n                .and_then(NonZeroU16::new);\n\n            if let Some(port) = port {\n                let port = ResourcePort {\n                    kind: parent_kind,\n                    port,\n                    name: parent_ref.name.clone(),\n                };\n\n                if !tcp::route_accepted_by_resource_port(status, &port) {\n                    continue;\n                }\n\n                tracing::debug!(\n                    ?port,\n                    route = route.name_unchecked(),\n                    \"inserting tcproute for resource\"\n                );\n\n                let resource_routes =\n                    self.resource_routes_or_default(port, cluster_info, resource_info);\n\n                resource_routes.apply_tcp_route(gknn.clone(), outbound_route.clone());\n            } else {\n                if !tcp::route_accepted_by_service(status, &parent_ref.name) {\n                    continue;\n                }\n                // If the parent_ref doesn't include a port, apply this route\n                // to all ResourceRoutes which match the resource name.\n                self.resource_port_routes.iter_mut().for_each(\n                    |(ResourcePort { name, port: _, .. }, routes)| {\n                        if name == &parent_ref.name {\n                            routes.apply_tcp_route(gknn.clone(), outbound_route.clone());\n                        }\n                    },\n                );\n\n                // Also add the route to the list of routes that target the\n                // resource without specifying a port.\n                self.service_tcp_routes\n                    .entry(parent_ref.name.clone())\n                    .or_default()\n                    .insert(gknn.clone(), outbound_route.clone());\n            }\n        }\n\n        // Remove the route from all parents that are not in the route's parent_refs.\n        for (resource_port, resource_routes) in self.resource_port_routes.iter_mut() {\n            if !tcp::route_accepted_by_resource_port(status, resource_port) {\n                resource_routes.delete_tcp_route(&gknn);\n            }\n        }\n        for (parent_name, routes) in self.service_tcp_routes.iter_mut() {\n            if !tcp::route_accepted_by_service(status, parent_name) {\n                routes.remove(&gknn);\n            }\n        }\n    }\n\n    fn reindex_resources(&mut self, resource_info: &HashMap<ResourceRef, ResourceInfo>) {\n        let update_backend = |backend: &mut Backend| {\n            match backend {\n                Backend::Service(svc) => {\n                    let service_ref = ResourceRef {\n                        kind: ResourceKind::Service,\n                        name: svc.name.clone(),\n                        namespace: svc.namespace.clone(),\n                    };\n                    svc.exists = resource_info.contains_key(&service_ref);\n                }\n                Backend::EgressNetwork(egress_net) => {\n                    let egress_net_ref = ResourceRef {\n                        kind: ResourceKind::EgressNetwork,\n                        name: egress_net.name.clone(),\n                        namespace: egress_net.namespace.clone(),\n                    };\n                    egress_net.exists = resource_info.contains_key(&egress_net_ref);\n                }\n\n                _ => {}\n            };\n        };\n\n        for routes in self.resource_port_routes.values_mut() {\n            for watch in routes.watches_by_ns.values_mut() {\n                let http_backends = watch\n                    .http_routes\n                    .values_mut()\n                    .flat_map(|route| route.rules.iter_mut())\n                    .flat_map(|rule| rule.backends.iter_mut());\n                let grpc_backends = watch\n                    .grpc_routes\n                    .values_mut()\n                    .flat_map(|route| route.rules.iter_mut())\n                    .flat_map(|rule| rule.backends.iter_mut());\n                let tls_backends = watch\n                    .tls_routes\n                    .values_mut()\n                    .flat_map(|route| route.rule.backends.iter_mut());\n                let tcp_backends = watch\n                    .tcp_routes\n                    .values_mut()\n                    .flat_map(|route| route.rule.backends.iter_mut());\n\n                http_backends\n                    .chain(grpc_backends)\n                    .chain(tls_backends)\n                    .chain(tcp_backends)\n                    .for_each(update_backend);\n\n                watch.send_if_modified();\n            }\n        }\n\n        let http_backends = self\n            .service_http_routes\n            .values_mut()\n            .flat_map(|routes| routes.values_mut())\n            .flat_map(|route| route.rules.iter_mut())\n            .flat_map(|rule| rule.backends.iter_mut());\n        let grpc_backends = self\n            .service_grpc_routes\n            .values_mut()\n            .flat_map(|routes| routes.values_mut())\n            .flat_map(|route| route.rules.iter_mut())\n            .flat_map(|rule| rule.backends.iter_mut());\n        let tls_backends = self\n            .service_tls_routes\n            .values_mut()\n            .flat_map(|routes| routes.values_mut())\n            .flat_map(|route| route.rule.backends.iter_mut());\n        let tcp_backends = self\n            .service_tcp_routes\n            .values_mut()\n            .flat_map(|routes| routes.values_mut())\n            .flat_map(|route| route.rule.backends.iter_mut());\n\n        http_backends\n            .chain(grpc_backends)\n            .chain(tls_backends)\n            .chain(tcp_backends)\n            .for_each(update_backend);\n    }\n\n    fn reinitialize_egress_watches(&mut self) {\n        for routes in self.resource_port_routes.values_mut() {\n            if let ParentInfo::EgressNetwork { .. } = routes.parent_info {\n                routes.reinitialize_watches();\n            }\n        }\n    }\n\n    fn update_resource(&mut self, name: String, kind: ResourceKind, resource: &ResourceInfo) {\n        tracing::debug!(?name, ?resource, \"updating resource\");\n\n        for (resource_port, resource_routes) in self.resource_port_routes.iter_mut() {\n            if resource_port.name != name || kind != resource_port.kind {\n                continue;\n            }\n\n            let app_protocol = resource.app_protocols.get(&resource_port.port).cloned();\n\n            resource_routes.update_resource(\n                app_protocol,\n                resource.accrual,\n                resource.http_retry.clone(),\n                resource.grpc_retry.clone(),\n                resource.timeouts.clone(),\n                resource.traffic_policy,\n            );\n        }\n    }\n\n    fn delete_http_route(&mut self, gknn: &GroupKindNamespaceName) {\n        for resource in self.resource_port_routes.values_mut() {\n            resource.delete_http_route(gknn);\n        }\n\n        self.service_http_routes.retain(|_, routes| {\n            routes.remove(gknn);\n            !routes.is_empty()\n        });\n    }\n\n    fn delete_grpc_route(&mut self, gknn: &GroupKindNamespaceName) {\n        for resource in self.resource_port_routes.values_mut() {\n            resource.delete_grpc_route(gknn);\n        }\n\n        self.service_grpc_routes.retain(|_, routes| {\n            routes.remove(gknn);\n            !routes.is_empty()\n        });\n    }\n\n    fn delete_tls_route(&mut self, gknn: &GroupKindNamespaceName) {\n        for resource in self.resource_port_routes.values_mut() {\n            resource.delete_tls_route(gknn);\n        }\n\n        self.service_tls_routes.retain(|_, routes| {\n            routes.remove(gknn);\n            !routes.is_empty()\n        });\n    }\n\n    fn delete_tcp_route(&mut self, gknn: &GroupKindNamespaceName) {\n        for resource in self.resource_port_routes.values_mut() {\n            resource.delete_tcp_route(gknn);\n        }\n\n        self.service_tcp_routes.retain(|_, routes| {\n            routes.remove(gknn);\n            !routes.is_empty()\n        });\n    }\n\n    fn resource_routes_or_default(\n        &mut self,\n        rp: ResourcePort,\n        cluster: &ClusterInfo,\n        resource_info: &HashMap<ResourceRef, ResourceInfo>,\n    ) -> &mut ResourceRoutes {\n        self.resource_port_routes\n            .entry(rp.clone())\n            .or_insert_with(|| {\n                let resource_ref = ResourceRef {\n                    name: rp.name.clone(),\n                    namespace: self.namespace.to_string(),\n                    kind: rp.kind.clone(),\n                };\n\n                let mut parent_info = match rp.kind {\n                    ResourceKind::EgressNetwork => ParentInfo::EgressNetwork {\n                        traffic_policy: TrafficPolicy::Deny,\n                        name: resource_ref.name.clone(),\n                        namespace: resource_ref.namespace.clone(),\n                    },\n                    ResourceKind::Service => {\n                        let authority =\n                            cluster.service_dns_authority(&self.namespace, &rp.name, rp.port);\n                        ParentInfo::Service {\n                            authority,\n                            name: resource_ref.name.clone(),\n                            namespace: resource_ref.namespace.clone(),\n                        }\n                    }\n                };\n                let mut app_protocol = None;\n                let mut accrual = None;\n                let mut http_retry = None;\n                let mut grpc_retry = None;\n                let mut timeouts = Default::default();\n                if let Some(resource) = resource_info.get(&resource_ref) {\n                    app_protocol = resource.app_protocols.get(&rp.port).cloned();\n                    accrual = resource.accrual;\n                    http_retry = resource.http_retry.clone();\n                    grpc_retry = resource.grpc_retry.clone();\n                    timeouts = resource.timeouts.clone();\n\n                    if let Some(traffic_policy) = resource.traffic_policy {\n                        parent_info = ParentInfo::EgressNetwork {\n                            traffic_policy,\n                            name: resource_ref.name,\n                            namespace: resource_ref.namespace,\n                        }\n                    }\n                }\n\n                // The routes which target this Resource but don't specify\n                // a port apply to all ports. Therefore, we include them.\n                let http_routes = self\n                    .service_http_routes\n                    .get(&rp.name)\n                    .cloned()\n                    .unwrap_or_default();\n                let grpc_routes = self\n                    .service_grpc_routes\n                    .get(&rp.name)\n                    .cloned()\n                    .unwrap_or_default();\n                let tls_routes = self\n                    .service_tls_routes\n                    .get(&rp.name)\n                    .cloned()\n                    .unwrap_or_default();\n                let tcp_routes = self\n                    .service_tcp_routes\n                    .get(&rp.name)\n                    .cloned()\n                    .unwrap_or_default();\n\n                let mut resource_routes = ResourceRoutes {\n                    parent_info,\n                    app_protocol,\n                    accrual,\n                    http_retry,\n                    grpc_retry,\n                    timeouts,\n                    port: rp.port,\n                    namespace: self.namespace.clone(),\n                    watches_by_ns: Default::default(),\n                };\n\n                // Producer routes are routes in the same namespace as\n                // their parent service. Consumer routes are routes in\n                // other namespaces.\n                let (producer_http_routes, consumer_http_routes): (Vec<_>, Vec<_>) = http_routes\n                    .into_iter()\n                    .partition(|(gknn, _)| gknn.namespace == *self.namespace);\n                let (producer_grpc_routes, consumer_grpc_routes): (Vec<_>, Vec<_>) = grpc_routes\n                    .into_iter()\n                    .partition(|(gknn, _)| gknn.namespace == *self.namespace);\n                let (producer_tls_routes, consumer_tls_routes): (Vec<_>, Vec<_>) = tls_routes\n                    .into_iter()\n                    .partition(|(gknn, _)| gknn.namespace == *self.namespace);\n                let (producer_tcp_routes, consumer_tcp_routes): (Vec<_>, Vec<_>) = tcp_routes\n                    .into_iter()\n                    .partition(|(gknn, _)| gknn.namespace == *self.namespace);\n\n                for (consumer_gknn, consumer_route) in consumer_http_routes {\n                    // Consumer routes should only apply to watches from the\n                    // consumer namespace.\n                    let consumer_watch = resource_routes\n                        .watch_for_ns_or_default(consumer_gknn.namespace.to_string());\n\n                    consumer_watch.insert_http_route(consumer_gknn.clone(), consumer_route.clone());\n                }\n                for (consumer_gknn, consumer_route) in consumer_grpc_routes {\n                    // Consumer routes should only apply to watches from the\n                    // consumer namespace.\n                    let consumer_watch = resource_routes\n                        .watch_for_ns_or_default(consumer_gknn.namespace.to_string());\n\n                    consumer_watch.insert_grpc_route(consumer_gknn.clone(), consumer_route.clone());\n                }\n                for (consumer_gknn, consumer_route) in consumer_tls_routes {\n                    // Consumer routes should only apply to watches from the\n                    // consumer namespace.\n                    let consumer_watch = resource_routes\n                        .watch_for_ns_or_default(consumer_gknn.namespace.to_string());\n\n                    consumer_watch.insert_tls_route(consumer_gknn.clone(), consumer_route.clone());\n                }\n\n                for (consumer_gknn, consumer_route) in consumer_tcp_routes {\n                    // Consumer routes should only apply to watches from the\n                    // consumer namespace.\n                    let consumer_watch = resource_routes\n                        .watch_for_ns_or_default(consumer_gknn.namespace.to_string());\n\n                    consumer_watch.insert_tcp_route(consumer_gknn.clone(), consumer_route.clone());\n                }\n\n                for (producer_gknn, producer_route) in producer_http_routes {\n                    // Insert the route into the producer namespace.\n                    let producer_watch = resource_routes\n                        .watch_for_ns_or_default(producer_gknn.namespace.to_string());\n\n                    producer_watch.insert_http_route(producer_gknn.clone(), producer_route.clone());\n\n                    // Producer routes apply to clients in all namespaces, so\n                    // apply it to watches for all other namespaces too.\n                    resource_routes\n                        .watches_by_ns\n                        .iter_mut()\n                        .filter(|(namespace, _)| {\n                            namespace.as_str() != producer_gknn.namespace.as_ref()\n                        })\n                        .for_each(|(_, watch)| {\n                            watch.insert_http_route(producer_gknn.clone(), producer_route.clone())\n                        });\n                }\n\n                for (producer_gknn, producer_route) in producer_grpc_routes {\n                    // Insert the route into the producer namespace.\n                    let producer_watch = resource_routes\n                        .watch_for_ns_or_default(producer_gknn.namespace.to_string());\n\n                    producer_watch.insert_grpc_route(producer_gknn.clone(), producer_route.clone());\n\n                    // Producer routes apply to clients in all namespaces, so\n                    // apply it to watches for all other namespaces too.\n                    resource_routes\n                        .watches_by_ns\n                        .iter_mut()\n                        .filter(|(namespace, _)| {\n                            namespace.as_str() != producer_gknn.namespace.as_ref()\n                        })\n                        .for_each(|(_, watch)| {\n                            watch.insert_grpc_route(producer_gknn.clone(), producer_route.clone())\n                        });\n                }\n\n                for (producer_gknn, producer_route) in producer_tls_routes {\n                    // Insert the route into the producer namespace.\n                    let producer_watch = resource_routes\n                        .watch_for_ns_or_default(producer_gknn.namespace.to_string());\n\n                    producer_watch.insert_tls_route(producer_gknn.clone(), producer_route.clone());\n\n                    // Producer routes apply to clients in all namespaces, so\n                    // apply it to watches for all other namespaces too.\n                    resource_routes\n                        .watches_by_ns\n                        .iter_mut()\n                        .filter(|(namespace, _)| {\n                            namespace.as_str() != producer_gknn.namespace.as_ref()\n                        })\n                        .for_each(|(_, watch)| {\n                            watch.insert_tls_route(producer_gknn.clone(), producer_route.clone())\n                        });\n                }\n\n                for (producer_gknn, producer_route) in producer_tcp_routes {\n                    // Insert the route into the producer namespace.\n                    let producer_watch = resource_routes\n                        .watch_for_ns_or_default(producer_gknn.namespace.to_string());\n\n                    producer_watch.insert_tcp_route(producer_gknn.clone(), producer_route.clone());\n\n                    // Producer routes apply to clients in all namespaces, so\n                    // apply it to watches for all other namespaces too.\n                    resource_routes\n                        .watches_by_ns\n                        .iter_mut()\n                        .filter(|(namespace, _)| {\n                            namespace.as_str() != producer_gknn.namespace.as_ref()\n                        })\n                        .for_each(|(_, watch)| {\n                            watch.insert_tcp_route(producer_gknn.clone(), producer_route.clone())\n                        });\n                }\n\n                resource_routes\n            })\n    }\n}\n\n#[inline]\nfn is_service(group: Option<&str>, kind: &str) -> bool {\n    // If the group is not specified or empty, assume it's 'core'.\n    group\n        .map(|g| g.eq_ignore_ascii_case(\"core\") || g.is_empty())\n        .unwrap_or(true)\n        && kind.eq_ignore_ascii_case(\"Service\")\n}\n\n#[inline]\nfn is_egress_network(group: Option<&str>, kind: &str) -> bool {\n    // If the group is not specified or empty, assume it's 'policy.linkerd.io'.\n    group\n        .map(|g| g.eq_ignore_ascii_case(\"policy.linkerd.io\"))\n        .unwrap_or(false)\n        && kind.eq_ignore_ascii_case(\"EgressNetwork\")\n}\n\n#[inline]\npub fn is_parent_service(kind: &Option<String>, group: &Option<String>) -> bool {\n    kind.as_deref()\n        .map(|k| is_service(group.as_deref(), k))\n        // Parent refs require a `kind`.\n        .unwrap_or(false)\n}\n\n#[inline]\npub fn is_parent_egress_network(kind: &Option<String>, group: &Option<String>) -> bool {\n    kind.as_deref()\n        .map(|k| is_egress_network(group.as_deref(), k))\n        // Parent refs require a `kind`.\n        .unwrap_or(false)\n}\n\n#[inline]\npub fn is_parent_service_or_egress_network(kind: &Option<String>, group: &Option<String>) -> bool {\n    is_parent_service(kind, group) || is_parent_egress_network(kind, group)\n}\n\nimpl ResourceRoutes {\n    fn reinitialize_watches(&mut self) {\n        for watch in self.watches_by_ns.values_mut() {\n            watch.reinitialize_watch();\n        }\n    }\n\n    fn watch_for_ns_or_default(&mut self, namespace: String) -> &mut RoutesWatch {\n        // The routes from the producer namespace apply to watches in all\n        // namespaces, so we copy them.\n        let http_routes = self\n            .watches_by_ns\n            .get(self.namespace.as_ref())\n            .map(|watch| watch.http_routes.clone())\n            .unwrap_or_default();\n        let grpc_routes = self\n            .watches_by_ns\n            .get(self.namespace.as_ref())\n            .map(|watch| watch.grpc_routes.clone())\n            .unwrap_or_default();\n\n        let tls_routes = self\n            .watches_by_ns\n            .get(self.namespace.as_ref())\n            .map(|watch| watch.tls_routes.clone())\n            .unwrap_or_default();\n\n        let tcp_routes = self\n            .watches_by_ns\n            .get(self.namespace.as_ref())\n            .map(|watch| watch.tcp_routes.clone())\n            .unwrap_or_default();\n\n        self.watches_by_ns.entry(namespace).or_insert_with(|| {\n            let (sender, _) = watch::channel(OutboundPolicy {\n                parent_info: self.parent_info.clone(),\n                port: self.port,\n                app_protocol: self.app_protocol.clone(),\n                accrual: self.accrual,\n                http_retry: self.http_retry.clone(),\n                grpc_retry: self.grpc_retry.clone(),\n                timeouts: self.timeouts.clone(),\n                http_routes: http_routes.clone(),\n                grpc_routes: grpc_routes.clone(),\n                tls_routes: tls_routes.clone(),\n                tcp_routes: tcp_routes.clone(),\n            });\n\n            RoutesWatch {\n                parent_info: self.parent_info.clone(),\n                http_routes,\n                grpc_routes,\n                tls_routes,\n                tcp_routes,\n                watch: sender,\n                app_protocol: self.app_protocol.clone(),\n                accrual: self.accrual,\n                http_retry: self.http_retry.clone(),\n                grpc_retry: self.grpc_retry.clone(),\n                timeouts: self.timeouts.clone(),\n            }\n        })\n    }\n\n    fn apply_http_route(&mut self, gknn: GroupKindNamespaceName, route: HttpRoute) {\n        if *gknn.namespace == *self.namespace {\n            // This is a producer namespace route.\n            let watch = self.watch_for_ns_or_default(gknn.namespace.to_string());\n\n            watch.insert_http_route(gknn.clone(), route.clone());\n\n            // Producer routes apply to clients in all namespaces, so\n            // apply it to watches for all other namespaces too.\n            for (ns, ns_watch) in self.watches_by_ns.iter_mut() {\n                if ns != &gknn.namespace {\n                    ns_watch.insert_http_route(gknn.clone(), route.clone());\n                }\n            }\n        } else {\n            // This is a consumer namespace route and should only apply to\n            // watches from that namespace.\n            let watch = self.watch_for_ns_or_default(gknn.namespace.to_string());\n            watch.insert_http_route(gknn, route);\n        }\n    }\n\n    fn apply_grpc_route(&mut self, gknn: GroupKindNamespaceName, route: GrpcRoute) {\n        if *gknn.namespace == *self.namespace {\n            // This is a producer namespace route.\n            let watch = self.watch_for_ns_or_default(gknn.namespace.to_string());\n\n            watch.insert_grpc_route(gknn.clone(), route.clone());\n\n            // Producer routes apply to clients in all namespaces, so\n            // apply it to watches for all other namespaces too.\n            for (ns, ns_watch) in self.watches_by_ns.iter_mut() {\n                if ns != &gknn.namespace {\n                    ns_watch.insert_grpc_route(gknn.clone(), route.clone());\n                }\n            }\n        } else {\n            // This is a consumer namespace route and should only apply to\n            // watches from that namespace.\n            let watch = self.watch_for_ns_or_default(gknn.namespace.to_string());\n            watch.insert_grpc_route(gknn, route);\n        }\n    }\n\n    fn apply_tls_route(&mut self, gknn: GroupKindNamespaceName, route: TlsRoute) {\n        if *gknn.namespace == *self.namespace {\n            // This is a producer namespace route.\n            let watch = self.watch_for_ns_or_default(gknn.namespace.to_string());\n\n            watch.insert_tls_route(gknn.clone(), route.clone());\n\n            // Producer routes apply to clients in all namespaces, so\n            // apply it to watches for all other namespaces too.\n            for (ns, ns_watch) in self.watches_by_ns.iter_mut() {\n                if ns != &gknn.namespace {\n                    ns_watch.insert_tls_route(gknn.clone(), route.clone());\n                }\n            }\n        } else {\n            // This is a consumer namespace route and should only apply to\n            // watches from that namespace.\n            let watch = self.watch_for_ns_or_default(gknn.namespace.to_string());\n            watch.insert_tls_route(gknn, route);\n        }\n    }\n\n    fn apply_tcp_route(&mut self, gknn: GroupKindNamespaceName, route: TcpRoute) {\n        if *gknn.namespace == *self.namespace {\n            // This is a producer namespace route.\n            let watch = self.watch_for_ns_or_default(gknn.namespace.to_string());\n\n            watch.insert_tcp_route(gknn.clone(), route.clone());\n\n            // Producer routes apply to clients in all namespaces, so\n            // apply it to watches for all other namespaces too.\n            for (ns, ns_watch) in self.watches_by_ns.iter_mut() {\n                if ns != &gknn.namespace {\n                    ns_watch.insert_tcp_route(gknn.clone(), route.clone());\n                }\n            }\n        } else {\n            // This is a consumer namespace route and should only apply to\n            // watches from that namespace.\n            let watch = self.watch_for_ns_or_default(gknn.namespace.to_string());\n            watch.insert_tcp_route(gknn, route);\n        }\n    }\n\n    fn update_resource(\n        &mut self,\n        app_protocol: Option<AppProtocol>,\n        accrual: Option<FailureAccrual>,\n        http_retry: Option<RouteRetry<HttpRetryCondition>>,\n        grpc_retry: Option<RouteRetry<GrpcRetryCondition>>,\n        timeouts: RouteTimeouts,\n        traffic_policy: Option<TrafficPolicy>,\n    ) {\n        self.app_protocol = app_protocol.clone();\n        self.accrual = accrual;\n        self.http_retry = http_retry.clone();\n        self.grpc_retry = grpc_retry.clone();\n        self.timeouts = timeouts.clone();\n        self.update_traffic_policy(traffic_policy);\n        for watch in self.watches_by_ns.values_mut() {\n            watch.app_protocol = app_protocol.clone();\n            watch.accrual = accrual;\n            watch.http_retry = http_retry.clone();\n            watch.grpc_retry = grpc_retry.clone();\n            watch.timeouts = timeouts.clone();\n            watch.update_traffic_policy(traffic_policy);\n            watch.send_if_modified();\n        }\n    }\n\n    fn update_traffic_policy(&mut self, traffic_policy: Option<TrafficPolicy>) {\n        if let (ParentInfo::EgressNetwork { traffic_policy, .. }, Some(new)) =\n            (&mut self.parent_info, traffic_policy)\n        {\n            if *traffic_policy != new {\n                *traffic_policy = new;\n            }\n        }\n    }\n\n    fn delete_http_route(&mut self, gknn: &GroupKindNamespaceName) {\n        for watch in self.watches_by_ns.values_mut() {\n            watch.remove_http_route(gknn);\n        }\n    }\n\n    fn delete_grpc_route(&mut self, gknn: &GroupKindNamespaceName) {\n        for watch in self.watches_by_ns.values_mut() {\n            watch.remove_grpc_route(gknn);\n        }\n    }\n\n    fn delete_tls_route(&mut self, gknn: &GroupKindNamespaceName) {\n        for watch in self.watches_by_ns.values_mut() {\n            watch.remove_tls_route(gknn);\n        }\n    }\n\n    fn delete_tcp_route(&mut self, gknn: &GroupKindNamespaceName) {\n        for watch in self.watches_by_ns.values_mut() {\n            watch.remove_tcp_route(gknn);\n        }\n    }\n}\n\nimpl RoutesWatch {\n    fn reinitialize_watch(&mut self) {\n        let current_policy = self.watch.borrow().clone();\n        let (new_sender, _) = watch::channel(current_policy);\n        self.watch = new_sender;\n    }\n\n    fn update_traffic_policy(&mut self, traffic_policy: Option<TrafficPolicy>) {\n        if let (ParentInfo::EgressNetwork { traffic_policy, .. }, Some(new)) =\n            (&mut self.parent_info, traffic_policy)\n        {\n            if *traffic_policy != new {\n                *traffic_policy = new;\n            }\n        }\n    }\n\n    fn send_if_modified(&mut self) {\n        self.watch.send_if_modified(|policy| {\n            let mut modified = false;\n\n            if self.parent_info != policy.parent_info {\n                policy.parent_info = self.parent_info.clone();\n                modified = true;\n            }\n\n            if self.http_routes != policy.http_routes {\n                policy.http_routes = self.http_routes.clone();\n                modified = true;\n            }\n\n            if self.grpc_routes != policy.grpc_routes {\n                policy.grpc_routes = self.grpc_routes.clone();\n                modified = true;\n            }\n\n            if self.tls_routes != policy.tls_routes {\n                policy.tls_routes = self.tls_routes.clone();\n                modified = true;\n            }\n\n            if self.tcp_routes != policy.tcp_routes {\n                policy.tcp_routes = self.tcp_routes.clone();\n                modified = true;\n            }\n\n            if self.app_protocol != policy.app_protocol {\n                policy.app_protocol = self.app_protocol.clone();\n                modified = true;\n            }\n\n            if self.accrual != policy.accrual {\n                policy.accrual = self.accrual;\n                modified = true;\n            }\n\n            if self.http_retry != policy.http_retry {\n                policy.http_retry = self.http_retry.clone();\n                modified = true;\n            }\n\n            if self.grpc_retry != policy.grpc_retry {\n                policy.grpc_retry = self.grpc_retry.clone();\n                modified = true;\n            }\n\n            if self.timeouts != policy.timeouts {\n                policy.timeouts = self.timeouts.clone();\n                modified = true;\n            }\n\n            modified\n        });\n    }\n\n    fn insert_http_route(&mut self, gknn: GroupKindNamespaceName, route: HttpRoute) {\n        self.http_routes.insert(gknn, route);\n\n        self.send_if_modified();\n    }\n\n    fn insert_grpc_route(&mut self, gknn: GroupKindNamespaceName, route: GrpcRoute) {\n        self.grpc_routes.insert(gknn, route);\n\n        self.send_if_modified();\n    }\n\n    fn insert_tls_route(&mut self, gknn: GroupKindNamespaceName, route: TlsRoute) {\n        self.tls_routes.insert(gknn, route);\n\n        self.send_if_modified();\n    }\n\n    fn insert_tcp_route(&mut self, gknn: GroupKindNamespaceName, route: TcpRoute) {\n        self.tcp_routes.insert(gknn, route);\n\n        self.send_if_modified();\n    }\n\n    fn remove_http_route(&mut self, gknn: &GroupKindNamespaceName) {\n        self.http_routes.remove(gknn);\n        self.send_if_modified();\n    }\n\n    fn remove_grpc_route(&mut self, gknn: &GroupKindNamespaceName) {\n        self.grpc_routes.remove(gknn);\n        self.send_if_modified();\n    }\n\n    fn remove_tls_route(&mut self, gknn: &GroupKindNamespaceName) {\n        self.tls_routes.remove(gknn);\n        self.send_if_modified();\n    }\n\n    fn remove_tcp_route(&mut self, gknn: &GroupKindNamespaceName) {\n        self.tcp_routes.remove(gknn);\n        self.send_if_modified();\n    }\n}\n\npub fn parse_accrual_config(\n    annotations: &std::collections::BTreeMap<String, String>,\n) -> Result<Option<FailureAccrual>> {\n    annotations\n        .get(\"balancer.linkerd.io/failure-accrual\")\n        .map(|mode| {\n            if mode == \"consecutive\" {\n                let max_failures = annotations\n                    .get(\"balancer.linkerd.io/failure-accrual-consecutive-max-failures\")\n                    .map(|s| s.parse::<u32>())\n                    .transpose()?\n                    .unwrap_or(7);\n\n                let max_penalty = annotations\n                    .get(\"balancer.linkerd.io/failure-accrual-consecutive-max-penalty\")\n                    .map(|s| parse_duration(s))\n                    .transpose()?\n                    .unwrap_or_else(|| time::Duration::from_secs(60));\n\n                let min_penalty = annotations\n                    .get(\"balancer.linkerd.io/failure-accrual-consecutive-min-penalty\")\n                    .map(|s| parse_duration(s))\n                    .transpose()?\n                    .unwrap_or_else(|| time::Duration::from_secs(1));\n                let jitter = annotations\n                    .get(\"balancer.linkerd.io/failure-accrual-consecutive-jitter-ratio\")\n                    .map(|s| s.parse::<f32>())\n                    .transpose()?\n                    .unwrap_or(0.5);\n                ensure!(\n                    min_penalty <= max_penalty,\n                    \"min_penalty ({min_penalty:?}) cannot exceed max_penalty ({max_penalty:?})\"\n                );\n                ensure!(\n                    max_penalty > time::Duration::from_millis(0),\n                    \"max_penalty cannot be zero\"\n                );\n                ensure!(jitter >= 0.0, \"jitter cannot be negative\");\n                ensure!(jitter <= 100.0, \"jitter cannot be greater than 100\");\n\n                Ok(FailureAccrual::Consecutive {\n                    max_failures,\n                    backoff: Backoff {\n                        min_penalty,\n                        max_penalty,\n                        jitter,\n                    },\n                })\n            } else {\n                bail!(\"unsupported failure accrual mode: {mode}\");\n            }\n        })\n        .transpose()\n}\n\npub fn parse_timeouts(\n    annotations: &std::collections::BTreeMap<String, String>,\n) -> Result<RouteTimeouts> {\n    let response = annotations\n        .get(\"timeout.linkerd.io/response\")\n        .map(|s| parse_duration(s))\n        .transpose()?;\n    let request = annotations\n        .get(\"timeout.linkerd.io/request\")\n        .map(|s| parse_duration(s))\n        .transpose()?;\n    let idle = annotations\n        .get(\"timeout.linkerd.io/idle\")\n        .map(|s| parse_duration(s))\n        .transpose()?;\n    Ok(RouteTimeouts {\n        response,\n        request,\n        idle,\n    })\n}\n\nfn parse_duration(s: &str) -> Result<time::Duration> {\n    let s = s.trim();\n    let offset = s\n        .rfind(|c: char| c.is_ascii_digit())\n        .ok_or_else(|| anyhow::anyhow!(\"{s} does not contain a timeout duration value\"))?;\n    let (magnitude, unit) = s.split_at(offset + 1);\n    let magnitude = magnitude.parse::<u64>()?;\n\n    let mul = match unit {\n        \"\" if magnitude == 0 => 0,\n        \"ms\" => 1,\n        \"s\" => 1000,\n        \"m\" => 1000 * 60,\n        \"h\" => 1000 * 60 * 60,\n        \"d\" => 1000 * 60 * 60 * 24,\n        _ => bail!(\"invalid duration unit {unit} (expected one of 'ms', 's', 'm', 'h', or 'd')\"),\n    };\n\n    let ms = magnitude\n        .checked_mul(mul)\n        .ok_or_else(|| anyhow::anyhow!(\"Timeout value {s} overflows when converted to 'ms'\"))?;\n    Ok(time::Duration::from_millis(ms))\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/outbound/tests/routes/grpc.rs",
    "content": "use kube::Resource;\nuse linkerd_policy_controller_core::{\n    outbound::{Backend, Kind, ResourceTarget, WeightedEgressNetwork, WeightedService},\n    routes::GroupKindNamespaceName,\n    POLICY_CONTROLLER_NAME,\n};\nuse linkerd_policy_controller_k8s_api::{gateway, Time};\nuse tracing::Level;\n\nuse super::super::*;\n\n#[test]\nfn backend_service() {\n    tracing_subscriber::fmt()\n        .with_max_level(Level::TRACE)\n        .try_init()\n        .ok();\n\n    let test = TestConfig::default();\n    // Create apex service.\n    let apex = mk_service(\"ns\", \"apex\", 8080);\n    test.index.write().apply(apex);\n\n    // Create httproute.\n    let route = mk_route(\n        \"ns\",\n        \"route\",\n        8080,\n        \"apex\",\n        \"backend\",\n        super::BackendKind::Service,\n    );\n    test.index.write().apply(route);\n\n    let mut rx = test\n        .index\n        .write()\n        .outbound_policy_rx(ResourceTarget {\n            name: \"apex\".to_string(),\n            namespace: \"ns\".to_string(),\n            port: 8080.try_into().unwrap(),\n            source_namespace: \"ns\".to_string(),\n            kind: Kind::Service,\n        })\n        .expect(\"apex.ns should exist\");\n\n    {\n        let policy = rx.borrow_and_update();\n        let backend = policy\n            .grpc_routes\n            .get(&GroupKindNamespaceName {\n                group: gateway::GRPCRoute::group(&()),\n                kind: gateway::GRPCRoute::kind(&()),\n                namespace: \"ns\".into(),\n                name: \"route\".into(),\n            })\n            .expect(\"route should exist\")\n            .rules\n            .first()\n            .expect(\"rule should exist\")\n            .backends\n            .first()\n            .expect(\"backend should exist\");\n\n        let exists = match backend {\n            Backend::Service(WeightedService { exists, .. }) => exists,\n            _ => panic!(\"backend should be a service\"),\n        };\n\n        // Backend should not exist.\n        assert!(!exists);\n    }\n\n    // Create backend service.\n    let backend = mk_service(\"ns\", \"backend\", 8080);\n    test.index.write().apply(backend);\n    assert!(rx.has_changed().unwrap());\n\n    {\n        let policy = rx.borrow_and_update();\n        let backend = policy\n            .grpc_routes\n            .get(&GroupKindNamespaceName {\n                group: gateway::GRPCRoute::group(&()),\n                kind: gateway::GRPCRoute::kind(&()),\n                namespace: \"ns\".into(),\n                name: \"route\".into(),\n            })\n            .expect(\"route should exist\")\n            .rules\n            .first()\n            .expect(\"rule should exist\")\n            .backends\n            .first()\n            .expect(\"backend should exist\");\n\n        let exists = match backend {\n            Backend::Service(WeightedService { exists, .. }) => exists,\n            _ => panic!(\"backend should be a service\"),\n        };\n\n        // Backend should exist.\n        assert!(exists);\n    }\n}\n\n#[test]\nfn backend_egress_network() {\n    tracing_subscriber::fmt()\n        .with_max_level(Level::TRACE)\n        .try_init()\n        .ok();\n\n    let test = TestConfig::default();\n    // Create apex service.\n    let apex = mk_egress_network(\"ns\", \"apex\");\n    test.index.write().apply(apex);\n\n    // Create httproute.\n    let route = mk_route(\n        \"ns\",\n        \"route\",\n        8080,\n        \"apex\",\n        \"apex\",\n        super::BackendKind::Egress,\n    );\n    test.index.write().apply(route);\n\n    let mut rx = test\n        .index\n        .write()\n        .outbound_policy_rx(ResourceTarget {\n            name: \"apex\".to_string(),\n            namespace: \"ns\".to_string(),\n            port: 8080.try_into().unwrap(),\n            source_namespace: \"ns\".to_string(),\n            kind: Kind::EgressNetwork(\"192.168.0.1:8080\".parse().unwrap()),\n        })\n        .expect(\"apex.ns should exist\");\n\n    {\n        let policy = rx.borrow_and_update();\n        let backend = policy\n            .grpc_routes\n            .get(&GroupKindNamespaceName {\n                group: gateway::GRPCRoute::group(&()),\n                kind: gateway::GRPCRoute::kind(&()),\n                namespace: \"ns\".into(),\n                name: \"route\".into(),\n            })\n            .expect(\"route should exist\")\n            .rules\n            .first()\n            .expect(\"rule should exist\")\n            .backends\n            .first()\n            .expect(\"backend should exist\");\n\n        let exists = match backend {\n            Backend::Invalid { .. } => &false,\n            Backend::EgressNetwork(WeightedEgressNetwork { exists, .. }) => exists,\n            _ => panic!(\"backend should be an egress network, but got {backend:?}\"),\n        };\n\n        // Backend should exist.\n        assert!(exists);\n    }\n}\n\nfn mk_route(\n    ns: impl ToString,\n    name: impl ToString,\n    port: u16,\n    parent: impl ToString,\n    backend_name: impl ToString,\n    backend: super::BackendKind,\n) -> gateway::GRPCRoute {\n    let (group, kind) = match backend {\n        super::BackendKind::Service => (\"core\".to_string(), \"Service\".to_string()),\n        super::BackendKind::Egress => {\n            (\"policy.linkerd.io\".to_string(), \"EgressNetwork\".to_string())\n        }\n    };\n\n    gateway::GRPCRoute {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            creation_timestamp: Some(Time(Utc::now())),\n            ..Default::default()\n        },\n        spec: gateway::GRPCRouteSpec {\n            parent_refs: Some(vec![gateway::GRPCRouteParentRefs {\n                group: Some(group.clone()),\n                kind: Some(kind.clone()),\n                namespace: Some(ns.to_string()),\n                name: parent.to_string(),\n                section_name: None,\n                port: Some(port.into()),\n            }]),\n            hostnames: None,\n            rules: Some(vec![gateway::GRPCRouteRules {\n                name: None,\n                matches: Some(vec![gateway::GRPCRouteRulesMatches {\n                    headers: None,\n                    method: Some(gateway::GRPCRouteRulesMatchesMethod {\n                        method: Some(\"Test\".to_string()),\n                        service: Some(\"io.linkerd.Testing\".to_string()),\n                        r#type: Some(gateway::GRPCRouteRulesMatchesMethodType::Exact),\n                    }),\n                }]),\n                filters: None,\n                backend_refs: Some(vec![gateway::GRPCRouteRulesBackendRefs {\n                    filters: None,\n                    weight: None,\n                    group: Some(group.clone()),\n                    kind: Some(kind.clone()),\n                    namespace: Some(ns.to_string()),\n                    name: backend_name.to_string(),\n                    port: Some(port.into()),\n                }]),\n                session_persistence: None,\n            }]),\n        },\n        status: Some(gateway::GRPCRouteStatus {\n            parents: vec![gateway::GRPCRouteStatusParents {\n                parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n                    group: Some(group.clone()),\n                    kind: Some(kind.clone()),\n                    namespace: Some(ns.to_string()),\n                    name: parent.to_string(),\n                    section_name: None,\n                    port: Some(port.into()),\n                },\n                controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                conditions: Some(vec![k8s::Condition {\n                    last_transition_time: Time(chrono::DateTime::<Utc>::MIN_UTC),\n                    message: \"\".to_string(),\n                    observed_generation: None,\n                    reason: \"Accepted\".to_string(),\n                    status: \"True\".to_string(),\n                    type_: \"Accepted\".to_string(),\n                }]),\n            }],\n        }),\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/outbound/tests/routes/http.rs",
    "content": "use kube::Resource;\nuse linkerd_policy_controller_core::{\n    outbound::{Backend, Kind, ResourceTarget, WeightedEgressNetwork, WeightedService},\n    routes::GroupKindNamespaceName,\n    POLICY_CONTROLLER_NAME,\n};\nuse linkerd_policy_controller_k8s_api::gateway;\nuse tracing::Level;\n\nuse super::super::*;\n\n#[test]\nfn backend_service() {\n    tracing_subscriber::fmt()\n        .with_max_level(Level::TRACE)\n        .try_init()\n        .ok();\n\n    let test = TestConfig::default();\n\n    // Create apex service.\n    let apex = mk_service(\"ns\", \"apex\", 8080);\n    test.index.write().apply(apex);\n\n    // Create httproute.\n    let route = mk_route(\n        \"ns\",\n        \"route\",\n        8080,\n        \"apex\",\n        \"backend\",\n        super::BackendKind::Service,\n    );\n    test.index.write().apply(route);\n\n    let mut rx = test\n        .index\n        .write()\n        .outbound_policy_rx(ResourceTarget {\n            name: \"apex\".to_string(),\n            namespace: \"ns\".to_string(),\n            port: 8080.try_into().unwrap(),\n            source_namespace: \"ns\".to_string(),\n            kind: Kind::Service,\n        })\n        .expect(\"apex.ns should exist\");\n\n    {\n        let policy = rx.borrow_and_update();\n        let backend = policy\n            .http_routes\n            .get(&GroupKindNamespaceName {\n                group: k8s::policy::HttpRoute::group(&()),\n                kind: k8s::policy::HttpRoute::kind(&()),\n                namespace: \"ns\".into(),\n                name: \"route\".into(),\n            })\n            .expect(\"route should exist\")\n            .rules\n            .first()\n            .expect(\"rule should exist\")\n            .backends\n            .first()\n            .expect(\"backend should exist\");\n\n        let exists = match backend {\n            Backend::Invalid { .. } => &false,\n            Backend::Service(WeightedService { exists, .. }) => exists,\n            _ => panic!(\"backend should be a service, but got {backend:?}\"),\n        };\n\n        // Backend should not exist.\n        assert!(!exists);\n    }\n\n    // Create backend service.\n    let backend = mk_service(\"ns\", \"backend\", 8080);\n    test.index.write().apply(backend);\n    assert!(rx.has_changed().unwrap());\n\n    {\n        let policy = rx.borrow_and_update();\n        let backend = policy\n            .http_routes\n            .get(&GroupKindNamespaceName {\n                group: k8s::policy::HttpRoute::group(&()),\n                kind: k8s::policy::HttpRoute::kind(&()),\n                namespace: \"ns\".into(),\n                name: \"route\".into(),\n            })\n            .expect(\"route should exist\")\n            .rules\n            .first()\n            .expect(\"rule should exist\")\n            .backends\n            .first()\n            .expect(\"backend should exist\");\n\n        let exists = match backend {\n            Backend::Service(WeightedService { exists, .. }) => exists,\n            backend => panic!(\"backend should be a service, but got {backend:?}\"),\n        };\n\n        // Backend should exist.\n        assert!(exists);\n    }\n}\n\n#[test]\nfn backend_egress_network() {\n    tracing_subscriber::fmt()\n        .with_max_level(Level::TRACE)\n        .try_init()\n        .ok();\n\n    let test = TestConfig::default();\n\n    // Create apex service.\n    let apex = mk_egress_network(\"ns\", \"apex\");\n    test.index.write().apply(apex);\n\n    // Create httproute.\n    let route = mk_route(\n        \"ns\",\n        \"route\",\n        8080,\n        \"apex\",\n        \"apex\",\n        super::BackendKind::Egress,\n    );\n    test.index.write().apply(route);\n\n    let mut rx = test\n        .index\n        .write()\n        .outbound_policy_rx(ResourceTarget {\n            name: \"apex\".to_string(),\n            namespace: \"ns\".to_string(),\n            port: 8080.try_into().unwrap(),\n            source_namespace: \"ns\".to_string(),\n            kind: Kind::EgressNetwork(\"192.168.0.1:8080\".parse().unwrap()),\n        })\n        .expect(\"apex.ns should exist\");\n\n    {\n        let policy = rx.borrow_and_update();\n        let backend = policy\n            .http_routes\n            .get(&GroupKindNamespaceName {\n                group: k8s::policy::HttpRoute::group(&()),\n                kind: k8s::policy::HttpRoute::kind(&()),\n                namespace: \"ns\".into(),\n                name: \"route\".into(),\n            })\n            .expect(\"route should exist\")\n            .rules\n            .first()\n            .expect(\"rule should exist\")\n            .backends\n            .first()\n            .expect(\"backend should exist\");\n\n        let exists = match backend {\n            Backend::Invalid { .. } => &false,\n            Backend::EgressNetwork(WeightedEgressNetwork { exists, .. }) => exists,\n            _ => panic!(\"backend should be an egress network, but got {backend:?}\"),\n        };\n\n        // Backend should exist.\n        assert!(exists);\n    }\n}\n\nfn mk_route(\n    ns: impl ToString,\n    name: impl ToString,\n    port: u16,\n    parent: impl ToString,\n    backend_name: impl ToString,\n    backend: super::BackendKind,\n) -> k8s::policy::HttpRoute {\n    use k8s::{policy::httproute::*, Time};\n    let (group, kind) = match backend {\n        super::BackendKind::Service => (\"core\".to_string(), \"Service\".to_string()),\n        super::BackendKind::Egress => {\n            (\"policy.linkerd.io\".to_string(), \"EgressNetwork\".to_string())\n        }\n    };\n\n    HttpRoute {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            creation_timestamp: Some(Time(Utc::now())),\n            ..Default::default()\n        },\n        spec: HttpRouteSpec {\n            parent_refs: Some(vec![gateway::HTTPRouteParentRefs {\n                group: Some(group.clone()),\n                kind: Some(kind.clone()),\n                namespace: Some(ns.to_string()),\n                name: parent.to_string(),\n                section_name: None,\n                port: Some(port.into()),\n            }]),\n            hostnames: None,\n            rules: Some(vec![HttpRouteRule {\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo/bar\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    headers: None,\n                    query_params: None,\n                    method: Some(gateway::HTTPRouteRulesMatchesMethod::Get),\n                }]),\n                filters: None,\n                backend_refs: Some(vec![gateway::HTTPRouteRulesBackendRefs {\n                    weight: None,\n                    group: Some(group.clone()),\n                    kind: Some(kind.clone()),\n                    namespace: Some(ns.to_string()),\n                    name: backend_name.to_string(),\n                    port: Some(port.into()),\n                    filters: None,\n                }]),\n                timeouts: None,\n            }]),\n        },\n        status: Some(gateway::HTTPRouteStatus {\n            parents: vec![gateway::HTTPRouteStatusParents {\n                parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n                    group: Some(group),\n                    kind: Some(kind),\n                    namespace: Some(ns.to_string()),\n                    name: parent.to_string(),\n                    section_name: None,\n                    port: Some(port.into()),\n                },\n                controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                conditions: Some(vec![k8s::Condition {\n                    last_transition_time: Time(chrono::DateTime::<Utc>::MIN_UTC),\n                    message: \"\".to_string(),\n                    observed_generation: None,\n                    reason: \"Accepted\".to_string(),\n                    status: \"True\".to_string(),\n                    type_: \"Accepted\".to_string(),\n                }]),\n            }],\n        }),\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/outbound/tests/routes/tcp.rs",
    "content": "use kube::Resource;\nuse linkerd_policy_controller_core::{\n    outbound::{Backend, Kind, ResourceTarget, WeightedEgressNetwork, WeightedService},\n    routes::GroupKindNamespaceName,\n    POLICY_CONTROLLER_NAME,\n};\nuse linkerd_policy_controller_k8s_api::{gateway, Time};\nuse tracing::Level;\n\nuse super::super::*;\n\n#[test]\nfn backend_service() {\n    tracing_subscriber::fmt()\n        .with_max_level(Level::TRACE)\n        .try_init()\n        .ok();\n\n    let test = TestConfig::default();\n    // Create apex service.\n    let apex = mk_service(\"ns\", \"apex\", 8080);\n    test.index.write().apply(apex);\n\n    // Create tcproute.\n    let route = mk_route(\n        \"ns\",\n        \"route\",\n        8080,\n        \"apex\",\n        \"backend\",\n        super::BackendKind::Service,\n    );\n    test.index.write().apply(route);\n\n    let mut rx = test\n        .index\n        .write()\n        .outbound_policy_rx(ResourceTarget {\n            name: \"apex\".to_string(),\n            namespace: \"ns\".to_string(),\n            port: 8080.try_into().unwrap(),\n            source_namespace: \"ns\".to_string(),\n            kind: Kind::Service,\n        })\n        .expect(\"apex.ns should exist\");\n\n    {\n        let policy = rx.borrow_and_update();\n        let backend = policy\n            .tcp_routes\n            .get(&GroupKindNamespaceName {\n                group: gateway::TCPRoute::group(&()),\n                kind: gateway::TCPRoute::kind(&()),\n                namespace: \"ns\".into(),\n                name: \"route\".into(),\n            })\n            .expect(\"route should exist\")\n            .rule\n            .backends\n            .first()\n            .expect(\"backend should exist\");\n\n        let exists = match backend {\n            Backend::Service(WeightedService { exists, .. }) => exists,\n            _ => panic!(\"backend should be a service\"),\n        };\n\n        // Backend should not exist.\n        assert!(!exists);\n    }\n\n    // Create backend service.\n    let backend = mk_service(\"ns\", \"backend\", 8080);\n    test.index.write().apply(backend);\n    assert!(rx.has_changed().unwrap());\n\n    {\n        let policy = rx.borrow_and_update();\n        let backend = policy\n            .tcp_routes\n            .get(&GroupKindNamespaceName {\n                group: gateway::TCPRoute::group(&()),\n                kind: gateway::TCPRoute::kind(&()),\n                namespace: \"ns\".into(),\n                name: \"route\".into(),\n            })\n            .expect(\"route should exist\")\n            .rule\n            .backends\n            .first()\n            .expect(\"backend should exist\");\n\n        let exists = match backend {\n            Backend::Service(WeightedService { exists, .. }) => exists,\n            _ => panic!(\"backend should be a service\"),\n        };\n\n        // Backend should exist.\n        assert!(exists);\n    }\n}\n\n#[test]\nfn backend_egress_network() {\n    tracing_subscriber::fmt()\n        .with_max_level(Level::TRACE)\n        .try_init()\n        .ok();\n\n    let test = TestConfig::default();\n    // Create apex service.\n    let apex = mk_egress_network(\"ns\", \"apex\");\n    test.index.write().apply(apex);\n\n    // Create tcproute.\n    let route = mk_route(\n        \"ns\",\n        \"route\",\n        8080,\n        \"apex\",\n        \"apex\",\n        super::BackendKind::Egress,\n    );\n    test.index.write().apply(route);\n\n    let mut rx = test\n        .index\n        .write()\n        .outbound_policy_rx(ResourceTarget {\n            name: \"apex\".to_string(),\n            namespace: \"ns\".to_string(),\n            port: 8080.try_into().unwrap(),\n            source_namespace: \"ns\".to_string(),\n            kind: Kind::EgressNetwork(\"192.168.0.1:8080\".parse().unwrap()),\n        })\n        .expect(\"apex.ns should exist\");\n\n    {\n        let policy = rx.borrow_and_update();\n        let backend = policy\n            .tcp_routes\n            .get(&GroupKindNamespaceName {\n                group: gateway::TCPRoute::group(&()),\n                kind: gateway::TCPRoute::kind(&()),\n                namespace: \"ns\".into(),\n                name: \"route\".into(),\n            })\n            .expect(\"route should exist\")\n            .rule\n            .backends\n            .first()\n            .expect(\"backend should exist\");\n\n        let exists = match backend {\n            Backend::Invalid { .. } => &false,\n            Backend::EgressNetwork(WeightedEgressNetwork { exists, .. }) => exists,\n            _ => panic!(\"backend should be an egress network, but got {backend:?}\"),\n        };\n\n        // Backend should exist.\n        assert!(exists);\n    }\n}\n\nfn mk_route(\n    ns: impl ToString,\n    name: impl ToString,\n    port: u16,\n    parent: impl ToString,\n    backend_name: impl ToString,\n    backend: super::BackendKind,\n) -> gateway::TCPRoute {\n    let (group, kind) = match backend {\n        super::BackendKind::Service => (\"core\".to_string(), \"Service\".to_string()),\n        super::BackendKind::Egress => {\n            (\"policy.linkerd.io\".to_string(), \"EgressNetwork\".to_string())\n        }\n    };\n\n    gateway::TCPRoute {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            creation_timestamp: Some(Time(Utc::now())),\n            ..Default::default()\n        },\n        spec: gateway::TCPRouteSpec {\n            parent_refs: Some(vec![gateway::TCPRouteParentRefs {\n                group: Some(group.clone()),\n                kind: Some(kind.clone()),\n                namespace: Some(ns.to_string()),\n                name: parent.to_string(),\n                section_name: None,\n                port: Some(port.into()),\n            }]),\n            rules: vec![gateway::TCPRouteRules {\n                name: None,\n                backend_refs: Some(vec![gateway::TCPRouteRulesBackendRefs {\n                    weight: None,\n                    group: Some(group.clone()),\n                    kind: Some(kind.clone()),\n                    namespace: Some(ns.to_string()),\n                    name: backend_name.to_string(),\n                    port: Some(port.into()),\n                }]),\n            }],\n        },\n        status: Some(gateway::TCPRouteStatus {\n            parents: vec![gateway::TCPRouteStatusParents {\n                parent_ref: gateway::TCPRouteStatusParentsParentRef {\n                    group: Some(group.clone()),\n                    kind: Some(kind.clone()),\n                    namespace: Some(ns.to_string()),\n                    name: parent.to_string(),\n                    section_name: None,\n                    port: Some(port.into()),\n                },\n                controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                conditions: Some(vec![k8s::Condition {\n                    last_transition_time: Time(chrono::DateTime::<Utc>::MIN_UTC),\n                    message: \"\".to_string(),\n                    observed_generation: None,\n                    reason: \"Accepted\".to_string(),\n                    status: \"True\".to_string(),\n                    type_: \"Accepted\".to_string(),\n                }]),\n            }],\n        }),\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/outbound/tests/routes/tls.rs",
    "content": "use kube::Resource;\nuse linkerd_policy_controller_core::{\n    outbound::{Backend, Kind, ResourceTarget, WeightedEgressNetwork, WeightedService},\n    routes::GroupKindNamespaceName,\n    POLICY_CONTROLLER_NAME,\n};\nuse linkerd_policy_controller_k8s_api::{gateway, Time};\nuse tracing::Level;\n\nuse super::super::*;\n\n#[test]\nfn backend_service() {\n    tracing_subscriber::fmt()\n        .with_max_level(Level::TRACE)\n        .try_init()\n        .ok();\n\n    let test = TestConfig::default();\n    // Create apex service.\n    let apex = mk_service(\"ns\", \"apex\", 8080);\n    test.index.write().apply(apex);\n\n    // Create tlsroute.\n    let route = mk_route(\n        \"ns\",\n        \"route\",\n        8080,\n        \"apex\",\n        \"backend\",\n        super::BackendKind::Service,\n    );\n    test.index.write().apply(route);\n\n    let mut rx = test\n        .index\n        .write()\n        .outbound_policy_rx(ResourceTarget {\n            name: \"apex\".to_string(),\n            namespace: \"ns\".to_string(),\n            port: 8080.try_into().unwrap(),\n            source_namespace: \"ns\".to_string(),\n            kind: Kind::Service,\n        })\n        .expect(\"apex.ns should exist\");\n\n    {\n        let policy = rx.borrow_and_update();\n        let backend = policy\n            .tls_routes\n            .get(&GroupKindNamespaceName {\n                group: gateway::TLSRoute::group(&()),\n                kind: gateway::TLSRoute::kind(&()),\n                namespace: \"ns\".into(),\n                name: \"route\".into(),\n            })\n            .expect(\"route should exist\")\n            .rule\n            .backends\n            .first()\n            .expect(\"backend should exist\");\n\n        let exists = match backend {\n            Backend::Service(WeightedService { exists, .. }) => exists,\n            _ => panic!(\"backend should be a service\"),\n        };\n\n        // Backend should not exist.\n        assert!(!exists);\n    }\n\n    // Create backend service.\n    let backend = mk_service(\"ns\", \"backend\", 8080);\n    test.index.write().apply(backend);\n    assert!(rx.has_changed().unwrap());\n\n    {\n        let policy = rx.borrow_and_update();\n        let backend = policy\n            .tls_routes\n            .get(&GroupKindNamespaceName {\n                group: gateway::TLSRoute::group(&()),\n                kind: gateway::TLSRoute::kind(&()),\n                namespace: \"ns\".into(),\n                name: \"route\".into(),\n            })\n            .expect(\"route should exist\")\n            .rule\n            .backends\n            .first()\n            .expect(\"backend should exist\");\n\n        let exists = match backend {\n            Backend::Service(WeightedService { exists, .. }) => exists,\n            _ => panic!(\"backend should be a service\"),\n        };\n\n        // Backend should exist.\n        assert!(exists);\n    }\n}\n\n#[test]\nfn backend_egress_network() {\n    tracing_subscriber::fmt()\n        .with_max_level(Level::TRACE)\n        .try_init()\n        .ok();\n\n    let test = TestConfig::default();\n    // Create apex service.\n    let apex = mk_egress_network(\"ns\", \"apex\");\n    test.index.write().apply(apex);\n\n    // Create tlsroute.\n    let route = mk_route(\n        \"ns\",\n        \"route\",\n        8080,\n        \"apex\",\n        \"apex\",\n        super::BackendKind::Egress,\n    );\n    test.index.write().apply(route);\n\n    let mut rx = test\n        .index\n        .write()\n        .outbound_policy_rx(ResourceTarget {\n            name: \"apex\".to_string(),\n            namespace: \"ns\".to_string(),\n            port: 8080.try_into().unwrap(),\n            source_namespace: \"ns\".to_string(),\n            kind: Kind::EgressNetwork(\"192.168.0.1:8080\".parse().unwrap()),\n        })\n        .expect(\"apex.ns should exist\");\n\n    {\n        let policy = rx.borrow_and_update();\n        let backend = policy\n            .tls_routes\n            .get(&GroupKindNamespaceName {\n                group: gateway::TLSRoute::group(&()),\n                kind: gateway::TLSRoute::kind(&()),\n                namespace: \"ns\".into(),\n                name: \"route\".into(),\n            })\n            .expect(\"route should exist\")\n            .rule\n            .backends\n            .first()\n            .expect(\"backend should exist\");\n\n        let exists = match backend {\n            Backend::Invalid { .. } => &false,\n            Backend::EgressNetwork(WeightedEgressNetwork { exists, .. }) => exists,\n            _ => panic!(\"backend should be an egress network, but got {backend:?}\"),\n        };\n\n        // Backend should exist.\n        assert!(exists);\n    }\n}\n\nfn mk_route(\n    ns: impl ToString,\n    name: impl ToString,\n    port: u16,\n    parent: impl ToString,\n    backend_name: impl ToString,\n    backend: super::BackendKind,\n) -> gateway::TLSRoute {\n    let (group, kind) = match backend {\n        super::BackendKind::Service => (\"core\".to_string(), \"Service\".to_string()),\n        super::BackendKind::Egress => {\n            (\"policy.linkerd.io\".to_string(), \"EgressNetwork\".to_string())\n        }\n    };\n\n    gateway::TLSRoute {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            creation_timestamp: Some(Time(Utc::now())),\n            ..Default::default()\n        },\n        spec: gateway::TLSRouteSpec {\n            parent_refs: Some(vec![gateway::TLSRouteParentRefs {\n                group: Some(group.clone()),\n                kind: Some(kind.clone()),\n                namespace: Some(ns.to_string()),\n                name: parent.to_string(),\n                section_name: None,\n                port: Some(port.into()),\n            }]),\n            hostnames: None,\n            rules: vec![gateway::TLSRouteRules {\n                name: None,\n                backend_refs: Some(vec![gateway::TLSRouteRulesBackendRefs {\n                    weight: None,\n                    group: Some(group.clone()),\n                    kind: Some(kind.clone()),\n                    namespace: Some(ns.to_string()),\n                    name: backend_name.to_string(),\n                    port: Some(port.into()),\n                }]),\n            }],\n        },\n        status: Some(gateway::TLSRouteStatus {\n            parents: vec![gateway::TLSRouteStatusParents {\n                parent_ref: gateway::TLSRouteStatusParentsParentRef {\n                    group: Some(group.clone()),\n                    kind: Some(kind.clone()),\n                    namespace: Some(ns.to_string()),\n                    name: parent.to_string(),\n                    section_name: None,\n                    port: Some(port.into()),\n                },\n                controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                conditions: Some(vec![k8s::Condition {\n                    last_transition_time: Time(chrono::DateTime::<Utc>::MIN_UTC),\n                    message: \"\".to_string(),\n                    observed_generation: None,\n                    reason: \"Accepted\".to_string(),\n                    status: \"True\".to_string(),\n                    type_: \"Accepted\".to_string(),\n                }]),\n            }],\n        }),\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/outbound/tests/routes.rs",
    "content": "mod grpc;\nmod http;\nmod tcp;\nmod tls;\n\nenum BackendKind {\n    Egress,\n    Service,\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/outbound/tests.rs",
    "content": "use std::{sync::Arc, vec};\n\nuse crate::{\n    defaults::DefaultPolicy,\n    outbound::index::{Index, SharedIndex},\n    ClusterInfo,\n};\nuse k8s_openapi::chrono::Utc;\nuse kubert::index::IndexNamespacedResource;\nuse linkerd_policy_controller_core::{outbound, IpNet};\nuse linkerd_policy_controller_core::{\n    outbound::{Kind, ResourceTarget},\n    POLICY_CONTROLLER_NAME,\n};\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s, gateway,\n    policy::{self, EgressNetwork},\n};\nuse tokio::time;\nuse tracing::Level;\n\nmod routes;\n\nstruct TestConfig {\n    index: SharedIndex,\n}\n\npub fn mk_service(ns: impl ToString, name: impl ToString, port: i32) -> k8s::Service {\n    k8s::Service {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: Some(k8s::api::core::v1::ServiceSpec {\n            ports: Some(vec![k8s::api::core::v1::ServicePort {\n                port,\n                ..Default::default()\n            }]),\n            ..Default::default()\n        }),\n        ..Default::default()\n    }\n}\n\npub fn mk_egress_network(ns: impl ToString, name: impl ToString) -> policy::EgressNetwork {\n    policy::EgressNetwork {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: policy::EgressNetworkSpec {\n            traffic_policy: policy::TrafficPolicy::Allow,\n            networks: None,\n        },\n        status: Some(policy::EgressNetworkStatus {\n            conditions: vec![k8s::Condition {\n                last_transition_time: k8s::Time(Utc::now()),\n                message: \"\".to_string(),\n                observed_generation: None,\n                reason: \"Accepted\".to_string(),\n                status: \"True\".to_string(),\n                type_: \"Accepted\".to_string(),\n            }],\n        }),\n    }\n}\n\nimpl TestConfig {\n    fn from_default_policy(default_policy: DefaultPolicy) -> Self {\n        Self::from_default_policy_with_probes(default_policy, vec![])\n    }\n\n    fn from_default_policy_with_probes(\n        default_policy: DefaultPolicy,\n        probe_networks: Vec<IpNet>,\n    ) -> Self {\n        let cluster_net = \"192.0.2.0/24\".parse().unwrap();\n        let detect_timeout = time::Duration::from_secs(1);\n        let cluster = ClusterInfo {\n            networks: vec![cluster_net],\n            control_plane_ns: \"linkerd\".to_string(),\n            identity_domain: \"cluster.example.com\".into(),\n            dns_domain: \"cluster.example.com\".into(),\n            default_policy,\n            default_detect_timeout: detect_timeout,\n            default_opaque_ports: Default::default(),\n            probe_networks,\n            global_egress_network_namespace: Arc::new(\"linkerd-egress\".to_string()),\n        };\n        let index = Index::shared(Arc::new(cluster));\n        Self { index }\n    }\n}\n\nimpl Default for TestConfig {\n    fn default() -> TestConfig {\n        Self::from_default_policy(DefaultPolicy::Allow {\n            authenticated_only: false,\n            cluster_only: true,\n        })\n    }\n}\n\n#[test]\nfn switch_to_another_egress_network_parent() {\n    tracing_subscriber::fmt()\n        .with_max_level(Level::TRACE)\n        .try_init()\n        .ok();\n\n    let test = TestConfig::default();\n    // Create network b.\n    let network_b = mk_egress_network(\"ns\", \"b\");\n    test.index.write().apply(network_b);\n\n    let (ns, name) = test\n        .index\n        .write()\n        .lookup_egress_network(\"192.168.0.1\".parse().unwrap(), \"ns\".to_string())\n        .expect(\"should resolve\");\n\n    assert_eq!(ns, \"ns\".to_string());\n    assert_eq!(name, \"b\".to_string());\n\n    let mut rx_b = test\n        .index\n        .write()\n        .outbound_policy_rx(ResourceTarget {\n            name,\n            namespace: ns.clone(),\n            port: 8080.try_into().unwrap(),\n            source_namespace: ns,\n            kind: Kind::EgressNetwork(\"192.168.0.1:8080\".parse().unwrap()),\n        })\n        .expect(\"b.ns should exist\");\n\n    // first resolution is for network B\n    let policy_b = rx_b.borrow_and_update();\n    assert_eq!(policy_b.parent_namespace(), \"ns\");\n    assert_eq!(policy_b.parent_name(), \"b\");\n    drop(policy_b);\n\n    // Create network a.\n    let network_a = mk_egress_network(\"ns\", \"a\");\n    test.index.write().apply(network_a);\n\n    // watch should be dropped at this point\n    assert!(rx_b.has_changed().is_err());\n\n    // now a new resolution should resolve network a\n\n    let (ns, name) = test\n        .index\n        .write()\n        .lookup_egress_network(\"192.168.0.1\".parse().unwrap(), \"ns\".to_string())\n        .expect(\"should resolve\");\n\n    let mut rx_a = test\n        .index\n        .write()\n        .outbound_policy_rx(ResourceTarget {\n            name,\n            namespace: ns.clone(),\n            port: 8080.try_into().unwrap(),\n            source_namespace: ns,\n            kind: Kind::EgressNetwork(\"192.168.0.1:8080\".parse().unwrap()),\n        })\n        .expect(\"a.ns should exist\");\n\n    // second resolution is for network A\n    let policy_b = rx_a.borrow_and_update();\n    assert_eq!(policy_b.parent_namespace(), \"ns\");\n    assert_eq!(policy_b.parent_name(), \"a\");\n}\n\n#[test]\nfn fallback_rx_closed_when_egress_net_created() {\n    tracing_subscriber::fmt()\n        .with_max_level(Level::TRACE)\n        .try_init()\n        .ok();\n\n    let test = TestConfig::default();\n\n    let fallback_rx = test.index.read().fallback_policy_rx();\n    assert!(fallback_rx.has_changed().is_ok());\n\n    // Create network.\n    let network = mk_egress_network(\"ns\", \"egress-net\");\n    test.index.write().apply(network);\n\n    assert!(fallback_rx.has_changed().is_err());\n}\n\n#[test]\nfn fallback_rx_closed_when_egress_net_deleted() {\n    tracing_subscriber::fmt()\n        .with_max_level(Level::TRACE)\n        .try_init()\n        .ok();\n\n    let test = TestConfig::default();\n\n    // Create network.\n    let network = mk_egress_network(\"ns\", \"egress-net\");\n    test.index.write().apply(network);\n\n    let fallback_rx = test.index.read().fallback_policy_rx();\n    assert!(fallback_rx.has_changed().is_ok());\n\n    <Index as kubert::index::IndexNamespacedResource<EgressNetwork>>::delete(\n        &mut test.index.write(),\n        \"ns\".into(),\n        \"egress-net\".into(),\n    );\n\n    assert!(fallback_rx.has_changed().is_err());\n}\n\n#[test]\nfn update_backend_on_route_with_no_port() {\n    tracing_subscriber::fmt()\n        .with_max_level(Level::TRACE)\n        .try_init()\n        .ok();\n\n    let test = TestConfig::default();\n    let ns = \"ns\";\n\n    let parent = k8s::Service {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(\"parent-svc\".to_string()),\n            ..Default::default()\n        },\n        spec: Some(k8s::ServiceSpec {\n            cluster_ip: Some(\"1.1.1.1\".to_string()),\n            cluster_ips: Some(vec![\"1.1.1.1\".to_string()]),\n            type_: Some(\"ClusterIP\".to_string()),\n            ..Default::default()\n        }),\n        status: None,\n    };\n\n    test.index.write().apply(parent);\n\n    let parent_ref = gateway::HTTPRouteParentRefs {\n        name: \"parent-svc\".to_string(),\n        namespace: Some(ns.to_string()),\n        kind: Some(\"Service\".to_string()),\n        group: Some(\"core\".to_string()),\n        section_name: None,\n        port: None, // Route is attached to parent without specifying port.\n    };\n\n    let route = gateway::HTTPRoute {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(\"foo-route\".to_string()),\n            ..Default::default()\n        },\n        spec: gateway::HTTPRouteSpec {\n            parent_refs: Some(vec![parent_ref.clone()]),\n            hostnames: None,\n            rules: Some(vec![gateway::HTTPRouteRules {\n                name: None,\n                matches: None,\n                filters: None,\n                // Reference to a backend that doesn't exist yet.\n                backend_refs: Some(vec![gateway::HTTPRouteRulesBackendRefs {\n                    weight: Some(1),\n                    group: Some(\"core\".to_string()),\n                    kind: Some(\"Service\".to_string()),\n                    name: \"backend-svc\".to_string(),\n                    namespace: Some(ns.to_string()),\n                    port: Some(8080),\n\n                    filters: None,\n                }]),\n                ..Default::default()\n            }]),\n        },\n        status: Some(gateway::HTTPRouteStatus {\n            parents: vec![gateway::HTTPRouteStatusParents {\n                parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n                    name: \"parent-svc\".to_string(),\n                    namespace: Some(ns.to_string()),\n                    kind: Some(\"Service\".to_string()),\n                    group: Some(\"core\".to_string()),\n                    section_name: None,\n                    port: None, // Route is attached to parent without specifying port.\n                },\n                controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                conditions: Some(vec![k8s::Condition {\n                    last_transition_time: k8s::Time(Utc::now()),\n                    message: \"\".to_string(),\n                    observed_generation: None,\n                    reason: \"Accepted\".to_string(),\n                    status: \"True\".to_string(),\n                    type_: \"Accepted\".to_string(),\n                }]),\n            }],\n        }),\n    };\n\n    test.index.write().apply(route);\n\n    // Create the backend.\n    let backend = k8s::Service {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(\"backend-svc\".to_string()),\n            ..Default::default()\n        },\n        spec: Some(k8s::ServiceSpec {\n            cluster_ip: Some(\"1.1.1.2\".to_string()),\n            cluster_ips: Some(vec![\"1.1.1.2\".to_string()]),\n            type_: Some(\"ClusterIP\".to_string()),\n            ..Default::default()\n        }),\n        status: None,\n    };\n    test.index.write().apply(backend);\n\n    let mut rx = test\n        .index\n        .write()\n        .outbound_policy_rx(ResourceTarget {\n            name: \"parent-svc\".to_string(),\n            namespace: ns.to_string(),\n            port: 8080.try_into().unwrap(),\n            source_namespace: ns.to_string(),\n            kind: Kind::Service,\n        })\n        .expect(\"parent-svc should exist\");\n\n    // Backend should be valid.\n    let policy = rx.borrow_and_update();\n    assert_eq!(policy.parent_namespace(), \"ns\");\n    assert_eq!(policy.parent_name(), \"parent-svc\");\n    let (gknn, outbound_route) = policy.http_routes.iter().next().unwrap();\n    assert_eq!(gknn.name, \"foo-route\");\n    assert_eq!(gknn.namespace, ns);\n    let rule = outbound_route.rules.first().unwrap();\n    let backend = rule.backends.first().unwrap();\n    if let outbound::Backend::Service(outbound::WeightedService {\n        weight: _,\n        authority: _,\n        name,\n        namespace,\n        port: _,\n        filters: _,\n        exists,\n    }) = backend\n    {\n        assert_eq!(name, \"backend-svc\");\n        assert_eq!(namespace, ns);\n        assert!(exists);\n    } else {\n        panic!(\"expected outbound::Backend::Service\");\n    }\n    drop(policy);\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/outbound.rs",
    "content": "pub mod index;\n\npub use index::{metrics, Index, ResourceRef, SharedIndex};\n\n#[cfg(test)]\nmod tests;\n"
  },
  {
    "path": "policy-controller/k8s/index/src/ports.rs",
    "content": "use anyhow::{bail, Context, Result};\nuse std::num::NonZeroU16;\n\n/// A `HashSet` specialized for ports.\n///\n/// Because ports are `u16` values, this type avoids the overhead of actually\n/// hashing ports.\npub type PortSet = std::collections::HashSet<NonZeroU16, std::hash::BuildHasherDefault<PortHasher>>;\n\n/// A `HashMap` specialized for ports.\n///\n/// Because ports are `NonZeroU16` values, this type avoids the overhead of\n/// actually hashing ports.\npub(crate) type PortMap<V> =\n    std::collections::HashMap<NonZeroU16, V, std::hash::BuildHasherDefault<PortHasher>>;\n\n/// A hasher for ports.\n///\n/// Because ports are single `NonZeroU16` values, we don't have to hash them; we can just use\n/// the integer values as hashes directly.\n///\n/// Borrowed from the proxy.\n#[derive(Debug, Default)]\npub struct PortHasher(u16);\n\n// === impl PortHasher ===\n\nimpl std::hash::Hasher for PortHasher {\n    fn write(&mut self, _: &[u8]) {\n        unreachable!(\"hashing a `u16` calls `write_u16`\");\n    }\n\n    #[inline]\n    fn write_u16(&mut self, port: u16) {\n        self.0 = port;\n    }\n\n    #[inline]\n    fn finish(&self) -> u64 {\n        self.0 as u64\n    }\n}\n\n/// Reads `annotation` from the provided set of annotations, parsing it as a port set.  If the\n/// annotation is not set or is invalid, the empty set is returned.\npub(crate) fn ports_annotation(\n    annotations: &std::collections::BTreeMap<String, String>,\n    annotation: &str,\n) -> Option<PortSet> {\n    annotations.get(annotation).map(|spec| {\n        parse_portset(spec).unwrap_or_else(|error| {\n            tracing::info!(%spec, %error, %annotation, \"Invalid ports list\");\n            Default::default()\n        })\n    })\n}\n\n/// Read a comma-separated of ports or port ranges from the given string.\npub fn parse_portset(s: &str) -> Result<PortSet> {\n    let mut ports = PortSet::default();\n\n    for spec in s.split(',') {\n        match spec.split_once('-') {\n            None => {\n                if !spec.trim().is_empty() {\n                    let port = spec.trim().parse().context(\"parsing port\")?;\n                    ports.insert(port);\n                }\n            }\n            Some((floor, ceil)) => {\n                let floor = floor.trim().parse::<NonZeroU16>().context(\"parsing port\")?;\n                let ceil = ceil.trim().parse::<NonZeroU16>().context(\"parsing port\")?;\n                if floor > ceil {\n                    bail!(\"Port range must be increasing\");\n                }\n                ports.extend(\n                    (u16::from(floor)..=u16::from(ceil)).map(|p| NonZeroU16::try_from(p).unwrap()),\n                );\n            }\n        }\n    }\n\n    Ok(ports)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    macro_rules! ports {\n        ($($x:expr),+ $(,)?) => (\n            vec![$($x),+]\n                .into_iter()\n                .map(NonZeroU16::try_from)\n                .collect::<Result<PortSet, _>>()\n                .unwrap()\n        );\n    }\n\n    #[test]\n    fn parse_portset() {\n        use super::parse_portset;\n\n        assert!(parse_portset(\"\").unwrap().is_empty(), \"empty\");\n        assert!(parse_portset(\"0\").is_err(), \"0\");\n        assert_eq!(parse_portset(\"1\").unwrap(), ports![1], \"1\");\n        assert_eq!(parse_portset(\"1-3\").unwrap(), ports![1, 2, 3], \"1-2\");\n        assert_eq!(parse_portset(\"4,1-2\").unwrap(), ports![1, 2, 4], \"4,1-2\");\n        assert!(parse_portset(\"2-1\").is_err(), \"2-1\");\n        assert!(parse_portset(\"2-\").is_err(), \"2-\");\n        assert!(parse_portset(\"65537\").is_err(), \"65537\");\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/routes/grpc.rs",
    "content": "use anyhow::{bail, Result};\nuse linkerd_policy_controller_core::routes;\nuse linkerd_policy_controller_k8s_api::gateway;\n\npub fn try_match(\n    gateway::GRPCRouteRulesMatches { headers, method }: gateway::GRPCRouteRulesMatches,\n) -> Result<routes::GrpcRouteMatch> {\n    let headers = headers\n        .into_iter()\n        .flatten()\n        .map(header_match)\n        .collect::<Result<_>>()?;\n\n    let method = method\n        .map(|value| {\n            if value.r#type == Some(gateway::GRPCRouteRulesMatchesMethodType::RegularExpression) {\n                bail!(\n                    \"unsupported GRPCRoute method match type: {:?}\",\n                    value.r#type\n                );\n            }\n            Ok(routes::GrpcMethodMatch {\n                method: value.method,\n                service: value.service,\n            })\n        })\n        .transpose()?;\n\n    Ok(routes::GrpcRouteMatch { headers, method })\n}\n\npub fn header_match(\n    header_match: gateway::GRPCRouteRulesMatchesHeaders,\n) -> Result<routes::HeaderMatch> {\n    match header_match.r#type {\n        Some(gateway::GRPCRouteRulesMatchesHeadersType::Exact) | None => Ok(\n            routes::HeaderMatch::Exact(header_match.name.parse()?, header_match.value.parse()?),\n        ),\n        Some(gateway::GRPCRouteRulesMatchesHeadersType::RegularExpression) => Ok(\n            routes::HeaderMatch::Regex(header_match.name.parse()?, header_match.value.parse()?),\n        ),\n    }\n}\n\npub fn request_header_modifier(\n    gateway::GRPCRouteRulesFiltersRequestHeaderModifier { set, add, remove }: gateway::GRPCRouteRulesFiltersRequestHeaderModifier,\n) -> Result<routes::HeaderModifierFilter> {\n    Ok(routes::HeaderModifierFilter {\n        add: add\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::GRPCRouteRulesFiltersRequestHeaderModifierAdd { name, value }| {\n                    Ok((name.parse()?, value.parse()?))\n                },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        set: set\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::GRPCRouteRulesFiltersRequestHeaderModifierSet { name, value }| {\n                    Ok((name.parse()?, value.parse()?))\n                },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        remove: remove\n            .into_iter()\n            .flatten()\n            .map(routes::HeaderName::try_from)\n            .collect::<Result<_, _>>()?,\n    })\n}\n\npub fn backend_request_header_modifier(\n    gateway::GRPCRouteRulesBackendRefsFiltersRequestHeaderModifier { set, add, remove }: gateway::GRPCRouteRulesBackendRefsFiltersRequestHeaderModifier,\n) -> Result<routes::HeaderModifierFilter> {\n    Ok(routes::HeaderModifierFilter {\n        add: add\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::GRPCRouteRulesBackendRefsFiltersRequestHeaderModifierAdd {\n                     name,\n                     value,\n                 }| { Ok((name.parse()?, value.parse()?)) },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        set: set\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::GRPCRouteRulesBackendRefsFiltersRequestHeaderModifierSet {\n                     name,\n                     value,\n                 }| { Ok((name.parse()?, value.parse()?)) },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        remove: remove\n            .into_iter()\n            .flatten()\n            .map(routes::HeaderName::try_from)\n            .collect::<Result<_, _>>()?,\n    })\n}\n\npub fn response_header_modifier(\n    gateway::GRPCRouteRulesFiltersResponseHeaderModifier { set, add, remove }: gateway::GRPCRouteRulesFiltersResponseHeaderModifier,\n) -> Result<routes::HeaderModifierFilter> {\n    Ok(routes::HeaderModifierFilter {\n        add: add\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::GRPCRouteRulesFiltersResponseHeaderModifierAdd { name, value }| {\n                    Ok((name.parse()?, value.parse()?))\n                },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        set: set\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::GRPCRouteRulesFiltersResponseHeaderModifierSet { name, value }| {\n                    Ok((name.parse()?, value.parse()?))\n                },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        remove: remove\n            .into_iter()\n            .flatten()\n            .map(routes::HeaderName::try_from)\n            .collect::<Result<_, _>>()?,\n    })\n}\n\npub fn backend_response_header_modifier(\n    gateway::GRPCRouteRulesBackendRefsFiltersResponseHeaderModifier { set, add, remove }: gateway::GRPCRouteRulesBackendRefsFiltersResponseHeaderModifier,\n) -> Result<routes::HeaderModifierFilter> {\n    Ok(routes::HeaderModifierFilter {\n        add: add\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::GRPCRouteRulesBackendRefsFiltersResponseHeaderModifierAdd {\n                     name,\n                     value,\n                 }| { Ok((name.parse()?, value.parse()?)) },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        set: set\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::GRPCRouteRulesBackendRefsFiltersResponseHeaderModifierSet {\n                     name,\n                     value,\n                 }| { Ok((name.parse()?, value.parse()?)) },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        remove: remove\n            .into_iter()\n            .flatten()\n            .map(routes::HeaderName::try_from)\n            .collect::<Result<_, _>>()?,\n    })\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/routes/http.rs",
    "content": "use anyhow::{bail, Result};\nuse linkerd_policy_controller_core::routes;\nuse linkerd_policy_controller_k8s_api::gateway;\nuse std::num::NonZeroU16;\n\npub fn try_match(\n    gateway::HTTPRouteRulesMatches {\n        path,\n        headers,\n        query_params,\n        method,\n    }: gateway::HTTPRouteRulesMatches,\n) -> Result<routes::HttpRouteMatch> {\n    let path = path.map(path_match).transpose()?;\n\n    let headers = headers\n        .into_iter()\n        .flatten()\n        .map(header_match)\n        .collect::<Result<_>>()?;\n\n    let query_params = query_params\n        .into_iter()\n        .flatten()\n        .map(query_param_match)\n        .collect::<Result<_>>()?;\n\n    let method = method.map(|m| match m {\n        gateway::HTTPRouteRulesMatchesMethod::Get => routes::Method::GET,\n        gateway::HTTPRouteRulesMatchesMethod::Head => routes::Method::HEAD,\n        gateway::HTTPRouteRulesMatchesMethod::Post => routes::Method::POST,\n        gateway::HTTPRouteRulesMatchesMethod::Put => routes::Method::PUT,\n        gateway::HTTPRouteRulesMatchesMethod::Delete => routes::Method::DELETE,\n        gateway::HTTPRouteRulesMatchesMethod::Connect => routes::Method::CONNECT,\n        gateway::HTTPRouteRulesMatchesMethod::Options => routes::Method::OPTIONS,\n        gateway::HTTPRouteRulesMatchesMethod::Trace => routes::Method::TRACE,\n        gateway::HTTPRouteRulesMatchesMethod::Patch => routes::Method::PATCH,\n    });\n\n    Ok(routes::HttpRouteMatch {\n        path,\n        headers,\n        query_params,\n        method,\n    })\n}\n\npub fn path_match(path_match: gateway::HTTPRouteRulesMatchesPath) -> Result<routes::PathMatch> {\n    let value = path_match.value.unwrap_or_else(|| \"/\".to_string());\n    match path_match.r#type {\n        Some(gateway::HTTPRouteRulesMatchesPathType::Exact) => {\n            if !value.starts_with('/') {\n                bail!(\"HttpPathMatch paths must be absolute (begin with `/`); {value:?} is not an absolute path\")\n            }\n            Ok(routes::PathMatch::Exact(value))\n        }\n        Some(gateway::HTTPRouteRulesMatchesPathType::PathPrefix) | None => {\n            if !value.starts_with('/') {\n                bail!(\"HttpPathMatch paths must be absolute (begin with `/`); {value:?} is not an absolute path\")\n            }\n            Ok(routes::PathMatch::Prefix(value))\n        }\n        Some(gateway::HTTPRouteRulesMatchesPathType::RegularExpression) => value\n            .parse()\n            .map(routes::PathMatch::Regex)\n            .map_err(Into::into),\n    }\n}\n\npub fn header_match(\n    header_match: gateway::HTTPRouteRulesMatchesHeaders,\n) -> Result<routes::HeaderMatch> {\n    match header_match.r#type {\n        Some(gateway::HTTPRouteRulesMatchesHeadersType::Exact) | None => Ok(\n            routes::HeaderMatch::Exact(header_match.name.parse()?, header_match.value.parse()?),\n        ),\n        Some(gateway::HTTPRouteRulesMatchesHeadersType::RegularExpression) => Ok(\n            routes::HeaderMatch::Regex(header_match.name.parse()?, header_match.value.parse()?),\n        ),\n    }\n}\n\npub fn query_param_match(\n    query_match: gateway::HTTPRouteRulesMatchesQueryParams,\n) -> Result<routes::QueryParamMatch> {\n    match query_match.r#type {\n        Some(gateway::HTTPRouteRulesMatchesQueryParamsType::Exact) | None => Ok(\n            routes::QueryParamMatch::Exact(query_match.name, query_match.value),\n        ),\n        Some(gateway::HTTPRouteRulesMatchesQueryParamsType::RegularExpression) => Ok(\n            routes::QueryParamMatch::Regex(query_match.name, query_match.value.parse()?),\n        ),\n    }\n}\n\npub fn request_header_modifier(\n    gateway::HTTPRouteRulesFiltersRequestHeaderModifier { set, add, remove }: gateway::HTTPRouteRulesFiltersRequestHeaderModifier,\n) -> Result<routes::HeaderModifierFilter> {\n    Ok(routes::HeaderModifierFilter {\n        add: add\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::HTTPRouteRulesFiltersRequestHeaderModifierAdd { name, value }| {\n                    Ok((name.parse()?, value.parse()?))\n                },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        set: set\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::HTTPRouteRulesFiltersRequestHeaderModifierSet { name, value }| {\n                    Ok((name.parse()?, value.parse()?))\n                },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        remove: remove\n            .into_iter()\n            .flatten()\n            .map(routes::HeaderName::try_from)\n            .collect::<Result<_, _>>()?,\n    })\n}\n\npub fn backend_request_header_modifier(\n    gateway::HTTPRouteRulesBackendRefsFiltersRequestHeaderModifier { set, add, remove }: gateway::HTTPRouteRulesBackendRefsFiltersRequestHeaderModifier,\n) -> Result<routes::HeaderModifierFilter> {\n    Ok(routes::HeaderModifierFilter {\n        add: add\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::HTTPRouteRulesBackendRefsFiltersRequestHeaderModifierAdd {\n                     name,\n                     value,\n                 }| { Ok((name.parse()?, value.parse()?)) },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        set: set\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::HTTPRouteRulesBackendRefsFiltersRequestHeaderModifierSet {\n                     name,\n                     value,\n                 }| { Ok((name.parse()?, value.parse()?)) },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        remove: remove\n            .into_iter()\n            .flatten()\n            .map(routes::HeaderName::try_from)\n            .collect::<Result<_, _>>()?,\n    })\n}\n\npub fn response_header_modifier(\n    gateway::HTTPRouteRulesFiltersResponseHeaderModifier { set, add, remove }: gateway::HTTPRouteRulesFiltersResponseHeaderModifier,\n) -> Result<routes::HeaderModifierFilter> {\n    Ok(routes::HeaderModifierFilter {\n        add: add\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::HTTPRouteRulesFiltersResponseHeaderModifierAdd { name, value }| {\n                    Ok((name.parse()?, value.parse()?))\n                },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        set: set\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::HTTPRouteRulesFiltersResponseHeaderModifierSet { name, value }| {\n                    Ok((name.parse()?, value.parse()?))\n                },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        remove: remove\n            .into_iter()\n            .flatten()\n            .map(routes::HeaderName::try_from)\n            .collect::<Result<_, _>>()?,\n    })\n}\n\npub fn backend_response_header_modifier(\n    gateway::HTTPRouteRulesBackendRefsFiltersResponseHeaderModifier { set, add, remove }: gateway::HTTPRouteRulesBackendRefsFiltersResponseHeaderModifier,\n) -> Result<routes::HeaderModifierFilter> {\n    Ok(routes::HeaderModifierFilter {\n        add: add\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::HTTPRouteRulesBackendRefsFiltersResponseHeaderModifierAdd {\n                     name,\n                     value,\n                 }| { Ok((name.parse()?, value.parse()?)) },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        set: set\n            .into_iter()\n            .flatten()\n            .map(\n                |gateway::HTTPRouteRulesBackendRefsFiltersResponseHeaderModifierSet {\n                     name,\n                     value,\n                 }| { Ok((name.parse()?, value.parse()?)) },\n            )\n            .collect::<Result<Vec<_>>>()?,\n        remove: remove\n            .into_iter()\n            .flatten()\n            .map(routes::HeaderName::try_from)\n            .collect::<Result<_, _>>()?,\n    })\n}\n\npub fn req_redirect(\n    gateway::HTTPRouteRulesFiltersRequestRedirect {\n        scheme,\n        hostname,\n        path,\n        port,\n        status_code,\n    }: gateway::HTTPRouteRulesFiltersRequestRedirect,\n) -> Result<routes::RequestRedirectFilter> {\n    let scheme = scheme.map(|s| match s {\n        gateway::HTTPRouteRulesFiltersRequestRedirectScheme::Http => routes::Scheme::HTTP,\n        gateway::HTTPRouteRulesFiltersRequestRedirectScheme::Https => routes::Scheme::HTTPS,\n    });\n    Ok(routes::RequestRedirectFilter {\n        scheme,\n        host: hostname,\n        path: path.map(path_modifier).transpose()?,\n        port: port\n            .and_then(|p| p.try_into().ok())\n            .and_then(NonZeroU16::new),\n        status: status_code\n            .map(|code| code.try_into())\n            .transpose()?\n            .map(routes::StatusCode::from_u16)\n            .transpose()?,\n    })\n}\n\npub fn backend_req_redirect(\n    gateway::HTTPRouteRulesBackendRefsFiltersRequestRedirect {\n        scheme,\n        hostname,\n        path,\n        port,\n        status_code,\n    }: gateway::HTTPRouteRulesBackendRefsFiltersRequestRedirect,\n) -> Result<routes::RequestRedirectFilter> {\n    let scheme = scheme.map(|s| match s {\n        gateway::HTTPRouteRulesBackendRefsFiltersRequestRedirectScheme::Http => {\n            routes::Scheme::HTTP\n        }\n        gateway::HTTPRouteRulesBackendRefsFiltersRequestRedirectScheme::Https => {\n            routes::Scheme::HTTPS\n        }\n    });\n    Ok(routes::RequestRedirectFilter {\n        scheme,\n        host: hostname,\n        path: path.map(backend_path_modifier).transpose()?,\n        port: port\n            .and_then(|p| p.try_into().ok())\n            .and_then(NonZeroU16::new),\n        status: status_code\n            .map(|code| code.try_into())\n            .transpose()?\n            .map(routes::StatusCode::from_u16)\n            .transpose()?,\n    })\n}\n\nfn path_modifier(\n    path_modifier: gateway::HTTPRouteRulesFiltersRequestRedirectPath,\n) -> Result<routes::PathModifier> {\n    if let Some(path) = path_modifier.replace_full_path {\n        if !path.starts_with('/') {\n            bail!(\n                \"RequestRedirect filters may only contain absolute paths \\\n                    (starting with '/'); {path:?} is not an absolute path\"\n            )\n        }\n        return Ok(routes::PathModifier::Full(path));\n    }\n    if let Some(path) = path_modifier.replace_prefix_match {\n        if !path.starts_with('/') {\n            bail!(\n                \"RequestRedirect filters may only contain absolute paths \\\n                    (starting with '/'); {path:?} is not an absolute path\"\n            )\n        }\n        return Ok(routes::PathModifier::Prefix(path));\n    }\n    bail!(\"RequestRedirect filter must contain either replace_full_path or replace_prefix_match\")\n}\n\nfn backend_path_modifier(\n    path_modifier: gateway::HTTPRouteRulesBackendRefsFiltersRequestRedirectPath,\n) -> Result<routes::PathModifier> {\n    if let Some(path) = path_modifier.replace_full_path {\n        if !path.starts_with('/') {\n            bail!(\n                \"RequestRedirect filters may only contain absolute paths \\\n                    (starting with '/'); {path:?} is not an absolute path\"\n            )\n        }\n        return Ok(routes::PathModifier::Full(path));\n    }\n    if let Some(path) = path_modifier.replace_prefix_match {\n        if !path.starts_with('/') {\n            bail!(\n                \"RequestRedirect filters may only contain absolute paths \\\n                    (starting with '/'); {path:?} is not an absolute path\"\n            )\n        }\n        return Ok(routes::PathModifier::Prefix(path));\n    }\n    bail!(\"RequestRedirect filter must contain either replace_full_path or replace_prefix_match\")\n}\n"
  },
  {
    "path": "policy-controller/k8s/index/src/routes.rs",
    "content": "use linkerd_policy_controller_core::routes::{GroupKindName, GroupKindNamespaceName, HostMatch};\nuse linkerd_policy_controller_k8s_api::{gateway, policy, Resource, ResourceExt};\n\npub mod grpc;\npub mod http;\n\n#[derive(Debug, Clone)]\npub(crate) enum HttpRouteResource {\n    LinkerdHttp(policy::HttpRoute),\n    GatewayHttp(gateway::HTTPRoute),\n}\n\nimpl HttpRouteResource {\n    pub(crate) fn name(&self) -> String {\n        match self {\n            Self::LinkerdHttp(route) => route.name_unchecked(),\n            Self::GatewayHttp(route) => route.name_unchecked(),\n        }\n    }\n\n    pub(crate) fn namespace(&self) -> String {\n        match self {\n            Self::LinkerdHttp(route) => route.namespace().expect(\"HttpRoute must have a namespace\"),\n            Self::GatewayHttp(route) => route.namespace().expect(\"HttpRoute must have a namespace\"),\n        }\n    }\n\n    pub(crate) fn parent_refs(&self) -> &Option<Vec<gateway::HTTPRouteParentRefs>> {\n        match self {\n            Self::LinkerdHttp(route) => &route.spec.parent_refs,\n            Self::GatewayHttp(route) => &route.spec.parent_refs,\n        }\n    }\n\n    pub(crate) fn status(&self) -> Option<&gateway::HTTPRouteStatus> {\n        match self {\n            Self::LinkerdHttp(route) => route.status.as_ref(),\n            Self::GatewayHttp(route) => route.status.as_ref(),\n        }\n    }\n\n    pub(crate) fn gknn(&self) -> GroupKindNamespaceName {\n        match self {\n            Self::LinkerdHttp(route) => route\n                .gkn()\n                .namespaced(route.namespace().expect(\"Route must have namespace\")),\n            Self::GatewayHttp(route) => route\n                .gkn()\n                .namespaced(route.namespace().expect(\"Route must have namespace\")),\n        }\n    }\n}\n\npub trait ExplicitGKN {\n    fn gkn<R: Resource<DynamicType = ()>>(&self) -> GroupKindName;\n}\npub trait ImpliedGKN {\n    fn gkn(&self) -> GroupKindName;\n}\n\nimpl<R: Sized + Resource<DynamicType = ()>> ImpliedGKN for R {\n    fn gkn(&self) -> GroupKindName {\n        let (kind, group, name) = (\n            Self::kind(&()),\n            Self::group(&()),\n            self.name_unchecked().into(),\n        );\n\n        GroupKindName { group, kind, name }\n    }\n}\n\nimpl ExplicitGKN for str {\n    fn gkn<R: Resource<DynamicType = ()>>(&self) -> GroupKindName {\n        let (kind, group, name) = (R::kind(&()), R::group(&()), self.to_string().into());\n\n        GroupKindName { group, kind, name }\n    }\n}\n\npub fn host_match(hostname: String) -> HostMatch {\n    if hostname.starts_with(\"*.\") {\n        let mut reverse_labels = hostname\n            .split('.')\n            .skip(1)\n            .map(|label| label.to_string())\n            .collect::<Vec<String>>();\n        reverse_labels.reverse();\n        HostMatch::Suffix { reverse_labels }\n    } else {\n        HostMatch::Exact(hostname)\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/Cargo.toml",
    "content": "[package]\nname = \"linkerd-policy-controller-k8s-status\"\nversion = \"0.1.0\"\nedition = \"2021\"\nlicense = \"Apache-2.0\"\npublish = false\n\n[dependencies]\nahash = \"0.8\"\nanyhow = \"1\"\n# Fix for https://github.com/chronotope/chrono/issues/602\nchrono = { version = \"0.4.44\", default-features = false, features = [\"clock\"] }\nparking_lot = \"0.12\"\nprometheus-client = { workspace = true }\nserde = \"1\"\nserde_json = \"1.0.149\"\nthiserror = \"2\"\ntokio = { version = \"1\", features = [\"macros\"] }\ntracing = \"0.1.44\"\n\nlinkerd-policy-controller-core = { workspace = true }\nlinkerd-policy-controller-k8s-api = { workspace = true }\n\n[dependencies.kubert]\nworkspace = true\ndefault-features = false\nfeatures = [\n    \"index\",\n    \"lease\",\n]\n\n[dev-dependencies.tokio]\nversion = \"1\"\nfeatures = [\"macros\"]\n"
  },
  {
    "path": "policy-controller/k8s/status/src/index.rs",
    "content": "use crate::{\n    ratelimit,\n    resource_id::{NamespaceGroupKindName, ResourceId},\n    routes,\n    service::Service,\n};\n\nuse ahash::{AHashMap as HashMap, AHashSet as HashSet};\nuse chrono::{offset::Utc, DateTime};\nuse kubert::lease::Claim;\nuse linkerd_policy_controller_core::{routes::GroupKindName, IpNet, POLICY_CONTROLLER_NAME};\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s, gateway,\n    policy::{self, Cidr, Network},\n    NamespaceResourceScope, Resource, ResourceExt, Time,\n};\nuse parking_lot::RwLock;\nuse prometheus_client::{\n    metrics::{counter::Counter, histogram::Histogram},\n    registry::{Registry, Unit},\n};\nuse serde::de::DeserializeOwned;\nuse std::{collections::hash_map::Entry, sync::Arc};\nuse tokio::{\n    sync::{mpsc, watch::Receiver},\n    time::{self, Duration},\n};\n\npub(crate) const POLICY_API_GROUP: &str = \"policy.linkerd.io\";\npub(crate) const GATEWAY_API_GROUP: &str = \"gateway.networking.k8s.io\";\n\nmod conditions {\n    pub const RESOLVED_REFS: &str = \"ResolvedRefs\";\n    pub const ACCEPTED: &str = \"Accepted\";\n}\nmod reasons {\n    pub const RESOLVED_REFS: &str = \"ResolvedRefs\";\n    pub const BACKEND_NOT_FOUND: &str = \"BackendNotFound\";\n    pub const INVALID_KIND: &str = \"InvalidKind\";\n    pub const NO_MATCHING_PARENT: &str = \"NoMatchingParent\";\n    pub const NO_MATCHING_TARGET: &str = \"NoMatchingTarget\";\n    pub const ROUTE_REASON_CONFLICTED: &str = \"RouteReasonConflicted\";\n    pub const RATELIMIT_REASON_ALREADY_EXISTS: &str = \"RateLimitReasonAlreadyExists\";\n    pub const EGRESS_NET_REASON_OVERLAP: &str = \"EgressReasonNetworkOverlap\";\n}\n\nmod cond_statuses {\n    pub const STATUS_TRUE: &str = \"True\";\n    pub const STATUS_FALSE: &str = \"False\";\n}\n\npub type SharedIndex = Arc<RwLock<Index>>;\n\npub struct Controller {\n    claims: Receiver<Arc<Claim>>,\n    client: k8s::Client,\n    name: String,\n    updates: mpsc::Receiver<Update>,\n    patch_timeout: Duration,\n\n    metrics: ControllerMetrics,\n}\n\npub struct ControllerMetrics {\n    patch_succeeded: Counter,\n    patch_failed: Counter,\n    patch_timeout: Counter,\n    patch_duration: Histogram,\n    patch_dequeues: Counter,\n    patch_drops: Counter,\n}\n\npub struct Index {\n    /// Used to compare against the current claim's claimant to determine if\n    /// this policy controller is the leader.\n    name: String,\n\n    /// Used in the IndexNamespacedResource trait methods to check who the\n    /// current leader is and if updates should be sent to the Controller.\n    claims: Receiver<Arc<Claim>>,\n    updates: mpsc::Sender<Update>,\n\n    /// Maps route ids to a list of their parent and backend refs,\n    /// regardless of if those parents have accepted the route.\n    http_route_refs: HashMap<NamespaceGroupKindName, HTTPRouteRef>,\n    grpc_route_refs: HashMap<NamespaceGroupKindName, GRPCRouteRef>,\n    tcp_route_refs: HashMap<NamespaceGroupKindName, TCPRouteRef>,\n    tls_route_refs: HashMap<NamespaceGroupKindName, TLSRouteRef>,\n\n    /// Maps rate limit ids to a list of details about these rate limits.\n    ratelimits: HashMap<ResourceId, HttpLocalRateLimitPolicyRef>,\n\n    /// Maps egress network ids to a list of details about these networks.\n    egress_networks: HashMap<ResourceId, EgressNetworkRef>,\n\n    servers: HashSet<ResourceId>,\n    services: HashMap<ResourceId, Service>,\n    cluster_networks: Vec<Cidr>,\n\n    metrics: IndexMetrics,\n}\n\npub struct IndexMetrics {\n    patch_enqueues: Counter,\n    patch_channel_full: Counter,\n}\n\n#[derive(Clone, PartialEq, Debug)]\npub(crate) struct RouteRef<S> {\n    pub(crate) parents: Vec<routes::ParentReference>,\n    pub(crate) backends: Vec<routes::BackendReference>,\n    pub(crate) statuses: Vec<S>,\n}\n\npub(crate) type HTTPRouteRef = RouteRef<gateway::HTTPRouteStatus>;\npub(crate) type GRPCRouteRef = RouteRef<gateway::GRPCRouteStatus>;\npub(crate) type TLSRouteRef = RouteRef<gateway::TLSRouteStatus>;\npub(crate) type TCPRouteRef = RouteRef<gateway::TCPRouteStatus>;\n\n#[derive(Clone, PartialEq, Debug)]\nstruct HttpLocalRateLimitPolicyRef {\n    creation_timestamp: Option<DateTime<Utc>>,\n    target_ref: ratelimit::TargetReference,\n    status_conditions: Vec<k8s::Condition>,\n}\n\n#[derive(Clone, PartialEq, Debug)]\nstruct EgressNetworkRef {\n    networks: Vec<Network>,\n    status_conditions: Vec<k8s::Condition>,\n}\n\nimpl EgressNetworkRef {\n    fn is_accepted(&self) -> bool {\n        self.status_conditions\n            .iter()\n            .any(|c| c.type_ == *conditions::ACCEPTED && c.status == *cond_statuses::STATUS_TRUE)\n    }\n}\n\n#[derive(Debug, PartialEq)]\npub struct Update {\n    pub id: NamespaceGroupKindName,\n    pub patch: k8s::Patch<serde_json::Value>,\n}\n\nimpl ControllerMetrics {\n    pub fn register(prom: &mut Registry) -> Self {\n        let patch_succeeded = Counter::default();\n        prom.register(\n            \"patches\",\n            \"Count of successful patch operations\",\n            patch_succeeded.clone(),\n        );\n\n        let patch_failed = Counter::default();\n        prom.register(\n            \"patch_api_errors\",\n            \"Count of patch operations that failed with an API error\",\n            patch_failed.clone(),\n        );\n\n        let patch_timeout = Counter::default();\n        prom.register(\n            \"patch_timeouts\",\n            \"Count of patch operations that did not complete within the timeout\",\n            patch_timeout.clone(),\n        );\n\n        let patch_duration = Histogram::new([0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0]);\n        prom.register_with_unit(\n            \"patch_duration\",\n            \"Histogram of time taken to apply patch operations\",\n            Unit::Seconds,\n            patch_duration.clone(),\n        );\n\n        let patch_dequeues = Counter::default();\n        prom.register(\n            \"patch_dequeues\",\n            \"Count of patches dequeued from the updates channel\",\n            patch_dequeues.clone(),\n        );\n\n        let patch_drops = Counter::default();\n        prom.register(\n            \"patch_drops\",\n            \"Count of patches dropped because we are not the leader\",\n            patch_drops.clone(),\n        );\n\n        Self {\n            patch_succeeded,\n            patch_failed,\n            patch_timeout,\n            patch_duration,\n            patch_dequeues,\n            patch_drops,\n        }\n    }\n}\n\nimpl IndexMetrics {\n    pub fn register(prom: &mut Registry) -> Self {\n        let patch_enqueues = Counter::default();\n        prom.register(\n            \"patch_enqueues\",\n            \"Count of patches enqueued to the updates channel\",\n            patch_enqueues.clone(),\n        );\n\n        let patch_channel_full = Counter::default();\n        prom.register(\n            \"patch_enqueue_overflows\",\n            \"Count of patches dropped because the updates channel is full\",\n            patch_channel_full.clone(),\n        );\n\n        Self {\n            patch_enqueues,\n            patch_channel_full,\n        }\n    }\n}\n\nimpl Controller {\n    pub fn new(\n        claims: Receiver<Arc<Claim>>,\n        client: k8s::Client,\n        name: String,\n        updates: mpsc::Receiver<Update>,\n        patch_timeout: Duration,\n        metrics: ControllerMetrics,\n    ) -> Self {\n        Self {\n            claims,\n            client,\n            name,\n            updates,\n            patch_timeout,\n            metrics,\n        }\n    }\n\n    /// Process updates received from the index; each update is a patch that\n    /// should be applied to update the status of a route. A patch should\n    /// only be applied if we are the holder of the write lease.\n    pub async fn run(mut self) {\n        // Select between the write lease claim changing and receiving updates\n        // from the index. If the lease claim changes, then check if we are\n        // now the leader. If so, we should apply the patches received;\n        // otherwise, we should drain the updates queue but not apply any\n        // patches since another policy controller is responsible for that.\n        let mut was_leader = false;\n        loop {\n            // Refresh the state of the lease on each iteration to ensure we're\n            // checking expiration.\n            let is_leader = self.claims.borrow_and_update().is_current_for(&self.name);\n            if was_leader != is_leader {\n                tracing::info!(leader=%is_leader, \"Status controller leadership change\");\n            }\n            was_leader = is_leader;\n\n            tokio::select! {\n                biased;\n                res = self.claims.changed() => {\n                    res.expect(\"Claims watch must not be dropped\");\n                }\n\n                Some(Update { id, patch}) = self.updates.recv() => {\n                    self.metrics.patch_dequeues.inc();\n                    // If this policy controller is not the leader, it should\n                    // process through the updates queue but not actually patch\n                    // any resources.\n                    if is_leader {\n                        if id.is_a::<policy::HttpRoute>() {\n                            self.patch::<policy::HttpRoute>(&id.gkn.name, &id.namespace, patch).await;\n                        } else if id.is_a::<gateway::HTTPRoute>() {\n                            self.patch::<gateway::HTTPRoute>(&id.gkn.name, &id.namespace, patch).await;\n                        } else if id.is_a::<gateway::GRPCRoute>() {\n                            self.patch::<gateway::GRPCRoute>(&id.gkn.name, &id.namespace, patch).await;\n                        } else if id.is_a::<gateway::TCPRoute>() {\n                            self.patch::<gateway::TCPRoute>(&id.gkn.name, &id.namespace, patch).await;\n                        } else if id.is_a::<gateway::TLSRoute>() {\n                            self.patch::<gateway::TLSRoute>(&id.gkn.name, &id.namespace, patch).await;\n                        } else if id.is_a::<policy::HttpLocalRateLimitPolicy>() {\n                            self.patch::<policy::HttpLocalRateLimitPolicy>(&id.gkn.name, &id.namespace, patch).await;\n                        } else if id.is_a::<policy::EgressNetwork>() {\n                            self.patch::<policy::EgressNetwork>(&id.gkn.name, &id.namespace, patch).await;\n                        }\n                    } else {\n                        tracing::debug!(?id, \"Dropping patch because we are not the leader\");\n                        self.metrics.patch_drops.inc();\n                    }\n                }\n            }\n        }\n    }\n\n    #[tracing::instrument(\n        level = tracing::Level::ERROR,\n        skip(self, patch),\n        fields(\n            group=%K::group(&Default::default()),\n            kind=%K::kind(&Default::default()),\n        ),\n    )]\n    async fn patch<K>(&self, name: &str, namespace: &str, patch: k8s::Patch<serde_json::Value>)\n    where\n        K: Resource<Scope = NamespaceResourceScope>,\n        <K as Resource>::DynamicType: Default,\n        K: DeserializeOwned,\n    {\n        tracing::trace!(?patch);\n        let api = k8s::Api::<K>::namespaced(self.client.clone(), namespace);\n        let patch_params = k8s::PatchParams::apply(POLICY_CONTROLLER_NAME);\n        let start = time::Instant::now();\n        let result = time::timeout(\n            self.patch_timeout,\n            api.patch_status(name, &patch_params, &patch),\n        )\n        .await;\n        let elapsed = start.elapsed();\n        tracing::trace!(?elapsed);\n        match result {\n            Ok(Ok(_)) => {\n                self.metrics.patch_succeeded.inc();\n                self.metrics.patch_duration.observe(elapsed.as_secs_f64());\n                tracing::info!(\"Patched status\");\n            }\n            Ok(Err(error)) => {\n                self.metrics.patch_failed.inc();\n                self.metrics.patch_duration.observe(elapsed.as_secs_f64());\n                tracing::error!(%error);\n            }\n            Err(_) => {\n                self.metrics.patch_timeout.inc();\n                tracing::error!(\"Timed out\");\n            }\n        }\n    }\n}\n\nimpl Index {\n    pub fn shared(\n        name: impl ToString,\n        claims: Receiver<Arc<Claim>>,\n        updates: mpsc::Sender<Update>,\n        metrics: IndexMetrics,\n        cluster_networks: Vec<IpNet>,\n    ) -> SharedIndex {\n        let cluster_networks = cluster_networks.into_iter().map(Into::into).collect();\n        Arc::new(RwLock::new(Self {\n            name: name.to_string(),\n            claims,\n            updates,\n            http_route_refs: HashMap::new(),\n            grpc_route_refs: HashMap::new(),\n            tls_route_refs: HashMap::new(),\n            tcp_route_refs: HashMap::new(),\n            ratelimits: HashMap::new(),\n            egress_networks: HashMap::new(),\n            servers: HashSet::new(),\n            services: HashMap::new(),\n            metrics,\n            cluster_networks,\n        }))\n    }\n\n    /// When the write leaseholder changes or a time duration has elapsed,\n    /// the index reconciles the statuses for all routes on the cluster.\n    ///\n    /// This reconciliation loop ensures that if errors occur when the\n    /// Controller applies patches or the write leaseholder changes, all\n    /// routes have an up-to-date status.\n    pub async fn run(index: Arc<RwLock<Self>>, reconciliation_period: Duration) {\n        // Extract what we need from the index so we don't need to lock it for\n        // housekeeping.\n        let (instance, mut claims) = {\n            let idx = index.read();\n            (idx.name.clone(), idx.claims.clone())\n        };\n\n        // The timer is reset when this instance becomes the leader and it is\n        // polled as long as it is the leader. The timer ensures that\n        // reconciliation happens at consistent intervals after leadership is\n        // acquired.\n        let mut timer = time::interval(reconciliation_period);\n        timer.set_missed_tick_behavior(time::MissedTickBehavior::Delay);\n\n        let mut was_leader = false;\n        loop {\n            // Refresh the state of the lease on each iteration to ensure we're\n            // checking expiration.\n            let is_leader = claims.borrow_and_update().is_current_for(&instance);\n            if is_leader && !was_leader {\n                tracing::debug!(\"Became leader; resetting timer\");\n                timer.reset_immediately();\n            }\n            was_leader = is_leader;\n\n            tokio::select! {\n                // Eagerly process claim updates to track leadership changes. If\n                // the claim changes, refesh the leadership status.\n                biased;\n                res = claims.changed() => {\n                    res.expect(\"Claims watch must not be dropped\");\n                    if tracing::enabled!(tracing::Level::TRACE) {\n                        let c = claims.borrow();\n                        tracing::trace!(claim=?*c, \"Changed\");\n                    }\n                }\n\n                // Only wait for the timer if this instance is the leader.\n                _ = timer.tick(), if is_leader => {\n                    index.read().reconcile_if_leader();\n                }\n            }\n        }\n    }\n\n    // If the network is new or its contents have changed, return true, so that a\n    // patch is generated; otherwise return false.\n    fn update_egress_net(&mut self, id: ResourceId, net: &EgressNetworkRef) -> bool {\n        match self.egress_networks.entry(id) {\n            Entry::Vacant(entry) => {\n                entry.insert(net.clone());\n            }\n            Entry::Occupied(mut entry) => {\n                if entry.get() == net {\n                    return false;\n                }\n                entry.insert(net.clone());\n            }\n        }\n        true\n    }\n\n    // If the route is new or its contents have changed, return true, so that a\n    // patch is generated; otherwise return false.\n    pub(crate) fn update_http_route(\n        &mut self,\n        id: NamespaceGroupKindName,\n        route: &HTTPRouteRef,\n    ) -> bool {\n        match self.http_route_refs.entry(id) {\n            Entry::Vacant(entry) => {\n                entry.insert(route.clone());\n            }\n            Entry::Occupied(mut entry) => {\n                if entry.get() == route {\n                    return false;\n                }\n                entry.insert(route.clone());\n            }\n        }\n        true\n    }\n\n    // If the route is new or its contents have changed, return true, so that a\n    // patch is generated; otherwise return false.\n    pub(crate) fn update_grpc_route(\n        &mut self,\n        id: NamespaceGroupKindName,\n        route: &GRPCRouteRef,\n    ) -> bool {\n        match self.grpc_route_refs.entry(id) {\n            Entry::Vacant(entry) => {\n                entry.insert(route.clone());\n            }\n            Entry::Occupied(mut entry) => {\n                if entry.get() == route {\n                    return false;\n                }\n                entry.insert(route.clone());\n            }\n        }\n        true\n    }\n\n    // If the route is new or its contents have changed, return true, so that a\n    // patch is generated; otherwise return false.\n    pub(crate) fn update_tls_route(\n        &mut self,\n        id: NamespaceGroupKindName,\n        route: &TLSRouteRef,\n    ) -> bool {\n        match self.tls_route_refs.entry(id) {\n            Entry::Vacant(entry) => {\n                entry.insert(route.clone());\n            }\n            Entry::Occupied(mut entry) => {\n                if entry.get() == route {\n                    return false;\n                }\n                entry.insert(route.clone());\n            }\n        }\n        true\n    }\n\n    // If the route is new or its contents have changed, return true, so that a\n    // patch is generated; otherwise return false.\n    pub(crate) fn update_tcp_route(\n        &mut self,\n        id: NamespaceGroupKindName,\n        route: &TCPRouteRef,\n    ) -> bool {\n        match self.tcp_route_refs.entry(id) {\n            Entry::Vacant(entry) => {\n                entry.insert(route.clone());\n            }\n            Entry::Occupied(mut entry) => {\n                if entry.get() == route {\n                    return false;\n                }\n                entry.insert(route.clone());\n            }\n        }\n        true\n    }\n\n    // If the rate limit is new or its contents have changed, return true, so that a patch is\n    // generated; otherwise return false.\n    fn update_ratelimit(\n        &mut self,\n        id: ResourceId,\n        ratelimit: &HttpLocalRateLimitPolicyRef,\n    ) -> bool {\n        match self.ratelimits.entry(id) {\n            Entry::Vacant(entry) => {\n                entry.insert(ratelimit.clone());\n            }\n            Entry::Occupied(mut entry) => {\n                if entry.get() == ratelimit {\n                    return false;\n                }\n                entry.insert(ratelimit.clone());\n            }\n        }\n        true\n    }\n\n    // This method determines whether a parent that a route attempts to\n    // attach to has any routes attached that are in conflict with the one\n    // that we are about to attach. This is done following the logs outlined in:\n    // https://gateway-api.sigs.k8s.io/geps/gep-1426/#route-types\n    pub fn parent_has_conflicting_routes(\n        &self,\n        parent_ref: &routes::ParentReference,\n        candidate_kind: &str,\n    ) -> bool {\n        let grpc_kind = gateway::GRPCRoute::kind(&());\n        let http_kind = gateway::HTTPRoute::kind(&());\n        let tls_kind = gateway::TLSRoute::kind(&());\n        let tcp_kind = gateway::TCPRoute::kind(&());\n\n        if *candidate_kind == grpc_kind {\n            false\n        } else if *candidate_kind == http_kind {\n            self.grpc_route_refs\n                .values()\n                .any(|route| route.parents.contains(parent_ref))\n        } else if *candidate_kind == tls_kind {\n            self.grpc_route_refs\n                .values()\n                .any(|route| route.parents.contains(parent_ref))\n                || self\n                    .http_route_refs\n                    .values()\n                    .any(|route| route.parents.contains(parent_ref))\n        } else if *candidate_kind == tcp_kind {\n            self.grpc_route_refs\n                .values()\n                .any(|route| route.parents.contains(parent_ref))\n                || self\n                    .http_route_refs\n                    .values()\n                    .any(|route| route.parents.contains(parent_ref))\n                || self\n                    .tls_route_refs\n                    .values()\n                    .any(|route| route.parents.contains(parent_ref))\n        } else {\n            false\n        }\n    }\n\n    fn parent_condition_server(\n        &self,\n        server: &ResourceId,\n        id: &NamespaceGroupKindName,\n        parent_ref: &routes::ParentReference,\n    ) -> k8s::Condition {\n        if self.servers.contains(server) {\n            if self.parent_has_conflicting_routes(parent_ref, &id.gkn.kind) {\n                route_conflicted()\n            } else {\n                accepted()\n            }\n        } else {\n            no_matching_parent()\n        }\n    }\n\n    fn parent_condition_service(\n        &self,\n        service: &ResourceId,\n        id: &NamespaceGroupKindName,\n        parent_ref: &routes::ParentReference,\n    ) -> k8s::Condition {\n        // service is a valid parent if it exists and it has a cluster_ip.\n        match self.services.get(service) {\n            Some(svc) if svc.valid_parent_service() => {\n                if self.parent_has_conflicting_routes(parent_ref, &id.gkn.kind) {\n                    route_conflicted()\n                } else {\n                    accepted()\n                }\n            }\n            Some(_svc) => headless_parent(),\n            None => no_matching_parent(),\n        }\n    }\n\n    fn parent_condition_egress_network(\n        &self,\n        egress_net: &ResourceId,\n        id: &NamespaceGroupKindName,\n        parent_ref: &routes::ParentReference,\n    ) -> k8s::Condition {\n        // egress network is a valid parent if it exists and is accepted.\n        match self.egress_networks.get(egress_net) {\n            Some(egress_net) if egress_net.is_accepted() => {\n                if self.parent_has_conflicting_routes(parent_ref, &id.gkn.kind) {\n                    route_conflicted()\n                } else {\n                    accepted()\n                }\n            }\n            Some(_) => egress_net_not_accepted(),\n            None => no_matching_parent(),\n        }\n    }\n\n    fn grpc_parent_status(\n        &self,\n        id: &NamespaceGroupKindName,\n        parent_ref: &routes::ParentReference,\n        backend_condition: k8s::Condition,\n    ) -> Option<gateway::GRPCRouteStatusParents> {\n        match parent_ref {\n            routes::ParentReference::Server(server) => {\n                let condition = self.parent_condition_server(server, id, parent_ref);\n                Some(gateway::GRPCRouteStatusParents {\n                    parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n                        group: Some(POLICY_API_GROUP.to_string()),\n                        kind: Some(\"Server\".to_string()),\n                        namespace: Some(server.namespace.clone()),\n                        name: server.name.clone(),\n                        section_name: None,\n                        port: None,\n                    },\n                    controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                    conditions: Some(vec![condition]),\n                })\n            }\n\n            routes::ParentReference::Service(service, port) => {\n                let condition = self.parent_condition_service(service, id, parent_ref);\n                Some(gateway::GRPCRouteStatusParents {\n                    parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n                        group: Some(\"core\".to_string()),\n                        kind: Some(\"Service\".to_string()),\n                        namespace: Some(service.namespace.clone()),\n                        name: service.name.clone(),\n                        section_name: None,\n                        port: port.map(Into::into),\n                    },\n                    controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                    conditions: Some(vec![condition, backend_condition]),\n                })\n            }\n\n            routes::ParentReference::EgressNetwork(egress_net, port) => {\n                let condition = self.parent_condition_egress_network(egress_net, id, parent_ref);\n                Some(gateway::GRPCRouteStatusParents {\n                    parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n                        group: Some(\"policy.linkerd.io\".to_string()),\n                        kind: Some(\"EgressNetwork\".to_string()),\n                        namespace: Some(egress_net.namespace.clone()),\n                        name: egress_net.name.clone(),\n                        section_name: None,\n                        port: port.map(Into::into),\n                    },\n                    controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                    conditions: Some(vec![condition, backend_condition]),\n                })\n            }\n            routes::ParentReference::UnknownKind => None,\n        }\n    }\n\n    fn http_parent_status(\n        &self,\n        id: &NamespaceGroupKindName,\n        parent_ref: &routes::ParentReference,\n        backend_condition: k8s::Condition,\n    ) -> Option<gateway::HTTPRouteStatusParents> {\n        match parent_ref {\n            routes::ParentReference::Server(server) => {\n                let condition = self.parent_condition_server(server, id, parent_ref);\n                Some(gateway::HTTPRouteStatusParents {\n                    parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n                        group: Some(POLICY_API_GROUP.to_string()),\n                        kind: Some(\"Server\".to_string()),\n                        namespace: Some(server.namespace.clone()),\n                        name: server.name.clone(),\n                        section_name: None,\n                        port: None,\n                    },\n                    controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                    conditions: Some(vec![condition]),\n                })\n            }\n\n            routes::ParentReference::Service(service, port) => {\n                let condition = self.parent_condition_service(service, id, parent_ref);\n                Some(gateway::HTTPRouteStatusParents {\n                    parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n                        group: Some(\"core\".to_string()),\n                        kind: Some(\"Service\".to_string()),\n                        namespace: Some(service.namespace.clone()),\n                        name: service.name.clone(),\n                        section_name: None,\n                        port: port.map(Into::into),\n                    },\n                    controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                    conditions: Some(vec![condition, backend_condition]),\n                })\n            }\n\n            routes::ParentReference::EgressNetwork(egress_net, port) => {\n                let condition = self.parent_condition_egress_network(egress_net, id, parent_ref);\n                Some(gateway::HTTPRouteStatusParents {\n                    parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n                        group: Some(\"policy.linkerd.io\".to_string()),\n                        kind: Some(\"EgressNetwork\".to_string()),\n                        namespace: Some(egress_net.namespace.clone()),\n                        name: egress_net.name.clone(),\n                        section_name: None,\n                        port: port.map(Into::into),\n                    },\n                    controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                    conditions: Some(vec![condition, backend_condition]),\n                })\n            }\n            routes::ParentReference::UnknownKind => None,\n        }\n    }\n\n    fn tls_parent_status(\n        &self,\n        id: &NamespaceGroupKindName,\n        parent_ref: &routes::ParentReference,\n        backend_condition: k8s::Condition,\n    ) -> Option<gateway::TLSRouteStatusParents> {\n        match parent_ref {\n            routes::ParentReference::Server(server) => {\n                let condition = self.parent_condition_server(server, id, parent_ref);\n                Some(gateway::TLSRouteStatusParents {\n                    parent_ref: gateway::TLSRouteStatusParentsParentRef {\n                        group: Some(POLICY_API_GROUP.to_string()),\n                        kind: Some(\"Server\".to_string()),\n                        namespace: Some(server.namespace.clone()),\n                        name: server.name.clone(),\n                        section_name: None,\n                        port: None,\n                    },\n                    controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                    conditions: Some(vec![condition]),\n                })\n            }\n\n            routes::ParentReference::Service(service, port) => {\n                let condition = self.parent_condition_service(service, id, parent_ref);\n                Some(gateway::TLSRouteStatusParents {\n                    parent_ref: gateway::TLSRouteStatusParentsParentRef {\n                        group: Some(\"core\".to_string()),\n                        kind: Some(\"Service\".to_string()),\n                        namespace: Some(service.namespace.clone()),\n                        name: service.name.clone(),\n                        section_name: None,\n                        port: port.map(Into::into),\n                    },\n                    controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                    conditions: Some(vec![condition, backend_condition]),\n                })\n            }\n\n            routes::ParentReference::EgressNetwork(egress_net, port) => {\n                let condition = self.parent_condition_egress_network(egress_net, id, parent_ref);\n                Some(gateway::TLSRouteStatusParents {\n                    parent_ref: gateway::TLSRouteStatusParentsParentRef {\n                        group: Some(\"policy.linkerd.io\".to_string()),\n                        kind: Some(\"EgressNetwork\".to_string()),\n                        namespace: Some(egress_net.namespace.clone()),\n                        name: egress_net.name.clone(),\n                        section_name: None,\n                        port: port.map(Into::into),\n                    },\n                    controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                    conditions: Some(vec![condition, backend_condition]),\n                })\n            }\n            routes::ParentReference::UnknownKind => None,\n        }\n    }\n\n    fn tcp_parent_status(\n        &self,\n        id: &NamespaceGroupKindName,\n        parent_ref: &routes::ParentReference,\n        backend_condition: k8s::Condition,\n    ) -> Option<gateway::TCPRouteStatusParents> {\n        match parent_ref {\n            routes::ParentReference::Server(server) => {\n                let condition = self.parent_condition_server(server, id, parent_ref);\n                Some(gateway::TCPRouteStatusParents {\n                    parent_ref: gateway::TCPRouteStatusParentsParentRef {\n                        group: Some(POLICY_API_GROUP.to_string()),\n                        kind: Some(\"Server\".to_string()),\n                        namespace: Some(server.namespace.clone()),\n                        name: server.name.clone(),\n                        section_name: None,\n                        port: None,\n                    },\n                    controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                    conditions: Some(vec![condition]),\n                })\n            }\n\n            routes::ParentReference::Service(service, port) => {\n                let condition = self.parent_condition_service(service, id, parent_ref);\n                Some(gateway::TCPRouteStatusParents {\n                    parent_ref: gateway::TCPRouteStatusParentsParentRef {\n                        group: Some(\"core\".to_string()),\n                        kind: Some(\"Service\".to_string()),\n                        namespace: Some(service.namespace.clone()),\n                        name: service.name.clone(),\n                        section_name: None,\n                        port: port.map(Into::into),\n                    },\n                    controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                    conditions: Some(vec![condition, backend_condition]),\n                })\n            }\n\n            routes::ParentReference::EgressNetwork(egress_net, port) => {\n                let condition = self.parent_condition_egress_network(egress_net, id, parent_ref);\n                Some(gateway::TCPRouteStatusParents {\n                    parent_ref: gateway::TCPRouteStatusParentsParentRef {\n                        group: Some(\"policy.linkerd.io\".to_string()),\n                        kind: Some(\"EgressNetwork\".to_string()),\n                        namespace: Some(egress_net.namespace.clone()),\n                        name: egress_net.name.clone(),\n                        section_name: None,\n                        port: port.map(Into::into),\n                    },\n                    controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                    conditions: Some(vec![condition, backend_condition]),\n                })\n            }\n            routes::ParentReference::UnknownKind => None,\n        }\n    }\n\n    fn backend_condition(\n        &self,\n        parent_ref: &routes::ParentReference,\n        backend_refs: &[routes::BackendReference],\n    ) -> k8s::Condition {\n        for backend_ref in backend_refs.iter() {\n            match backend_ref {\n                routes::BackendReference::Unknown => {\n                    // If even one backend has a reference to an unknown / unsupported\n                    // reference, return invalid backend condition\n                    return invalid_backend_kind(\"\");\n                }\n\n                routes::BackendReference::Service(service) => {\n                    if !self.services.contains_key(service) {\n                        return backend_not_found();\n                    }\n                }\n                routes::BackendReference::EgressNetwork(egress_net) => {\n                    if !self.egress_networks.contains_key(egress_net) {\n                        return backend_not_found();\n                    }\n\n                    match parent_ref {\n                        routes::ParentReference::EgressNetwork(parent_resource, _)\n                            if parent_resource == egress_net =>\n                        {\n                            continue;\n                        }\n                        _ => {\n                            let message =\n                            \"EgressNetwork backend needs to be on a route that has an EgressNetwork parent\";\n                            return invalid_backend_kind(message);\n                        }\n                    }\n                }\n            }\n        }\n\n        resolved_refs()\n    }\n\n    fn make_http_route_patch(\n        &self,\n        id: &NamespaceGroupKindName,\n        route: &HTTPRouteRef,\n    ) -> Option<k8s::Patch<serde_json::Value>> {\n        // To preserve any statuses from other controllers, we copy those\n        // statuses.\n        let unowned_statuses = route\n            .statuses\n            .iter()\n            .flat_map(|status| status.parents.clone())\n            .filter(|status| status.controller_name != POLICY_CONTROLLER_NAME);\n\n        // Compute a status for each parent_ref which has a kind we support.\n        let parent_statuses = route.parents.iter().filter_map(|parent_ref| {\n            let backend_condition = self.backend_condition(parent_ref, &route.backends);\n            self.http_parent_status(id, parent_ref, backend_condition.clone())\n        });\n\n        let all_statuses = unowned_statuses.chain(parent_statuses).collect::<Vec<_>>();\n        let route_statuses = route\n            .statuses\n            .iter()\n            .flat_map(|status| status.parents.clone())\n            .collect::<Vec<_>>();\n        if eq_time_insensitive_http_route_parent_statuses(&all_statuses, &route_statuses) {\n            return None;\n        }\n\n        let status = gateway::HTTPRouteStatus {\n            parents: all_statuses,\n        };\n\n        make_patch(id, status)\n    }\n\n    fn make_grpc_route_patch(\n        &self,\n        id: &NamespaceGroupKindName,\n        route: &GRPCRouteRef,\n    ) -> Option<k8s::Patch<serde_json::Value>> {\n        // To preserve any statuses from other controllers, we copy those\n        // statuses.\n        let unowned_statuses = route\n            .statuses\n            .iter()\n            .flat_map(|status| status.parents.clone())\n            .filter(|status| status.controller_name != POLICY_CONTROLLER_NAME);\n\n        // Compute a status for each parent_ref which has a kind we support.\n        let parent_statuses = route.parents.iter().filter_map(|parent_ref| {\n            let backend_condition = self.backend_condition(parent_ref, &route.backends);\n            self.grpc_parent_status(id, parent_ref, backend_condition.clone())\n        });\n\n        let all_statuses = unowned_statuses.chain(parent_statuses).collect::<Vec<_>>();\n        let route_statuses = route\n            .statuses\n            .iter()\n            .flat_map(|status| status.parents.clone())\n            .collect::<Vec<_>>();\n\n        if eq_time_insensitive_grpc_route_parent_statuses(&all_statuses, &route_statuses) {\n            return None;\n        }\n\n        let status = gateway::GRPCRouteStatus {\n            parents: all_statuses,\n        };\n\n        make_patch(id, status)\n    }\n\n    fn make_tls_route_patch(\n        &self,\n        id: &NamespaceGroupKindName,\n        route: &TLSRouteRef,\n    ) -> Option<k8s::Patch<serde_json::Value>> {\n        // To preserve any statuses from other controllers, we copy those\n        // statuses.\n        let unowned_statuses = route\n            .statuses\n            .iter()\n            .flat_map(|status| status.parents.clone())\n            .filter(|status| status.controller_name != POLICY_CONTROLLER_NAME);\n\n        // Compute a status for each parent_ref which has a kind we support.\n        let parent_statuses = route.parents.iter().filter_map(|parent_ref| {\n            let backend_condition = self.backend_condition(parent_ref, &route.backends);\n            self.tls_parent_status(id, parent_ref, backend_condition.clone())\n        });\n\n        let all_statuses = unowned_statuses.chain(parent_statuses).collect::<Vec<_>>();\n        let route_statuses = route\n            .statuses\n            .iter()\n            .flat_map(|status| status.parents.clone())\n            .collect::<Vec<_>>();\n\n        if eq_time_insensitive_tls_route_parent_statuses(&all_statuses, &route_statuses) {\n            return None;\n        }\n\n        let status = gateway::TLSRouteStatus {\n            parents: all_statuses,\n        };\n\n        make_patch(id, status)\n    }\n\n    fn make_tcp_route_patch(\n        &self,\n        id: &NamespaceGroupKindName,\n        route: &TCPRouteRef,\n    ) -> Option<k8s::Patch<serde_json::Value>> {\n        // To preserve any statuses from other controllers, we copy those\n        // statuses.\n        let unowned_statuses = route\n            .statuses\n            .iter()\n            .flat_map(|status| status.parents.clone())\n            .filter(|status| status.controller_name != POLICY_CONTROLLER_NAME);\n\n        // Compute a status for each parent_ref which has a kind we support.\n        let parent_statuses = route.parents.iter().filter_map(|parent_ref| {\n            let backend_condition = self.backend_condition(parent_ref, &route.backends);\n            self.tcp_parent_status(id, parent_ref, backend_condition.clone())\n        });\n\n        let all_statuses = unowned_statuses.chain(parent_statuses).collect::<Vec<_>>();\n        let route_statuses = route\n            .statuses\n            .iter()\n            .flat_map(|status| status.parents.clone())\n            .collect::<Vec<_>>();\n\n        if eq_time_insensitive_tcp_route_parent_statuses(&all_statuses, &route_statuses) {\n            return None;\n        }\n\n        let status = gateway::TCPRouteStatus {\n            parents: all_statuses,\n        };\n\n        make_patch(id, status)\n    }\n\n    fn target_ref_status(\n        &self,\n        id: &NamespaceGroupKindName,\n        target_ref: &ratelimit::TargetReference,\n    ) -> Option<policy::HttpLocalRateLimitPolicyStatus> {\n        match target_ref {\n            ratelimit::TargetReference::Server(server) => {\n                let condition = if self.servers.contains(server) {\n                    // Collect rate limits for this server, sorted by creation timestamp and then\n                    // by name. If the current RL is the first one in the list, it is accepted.\n                    let mut rate_limits = self\n                        .ratelimits\n                        .iter()\n                        .filter(|(_, rl_ref)| rl_ref.target_ref == *target_ref)\n                        .collect::<Vec<_>>();\n                    rate_limits.sort_by(|(a_id, a), (b_id, b)| {\n                        let by_ts = match (&a.creation_timestamp, &b.creation_timestamp) {\n                            (Some(a_ts), Some(b_ts)) => a_ts.cmp(b_ts),\n                            (None, None) => std::cmp::Ordering::Equal,\n                            // entries with timestamps are preferred over ones without\n                            (Some(_), None) => return std::cmp::Ordering::Less,\n                            (None, Some(_)) => return std::cmp::Ordering::Greater,\n                        };\n                        by_ts.then_with(|| a_id.name.cmp(&b_id.name))\n                    });\n\n                    let Some((first_id, _)) = rate_limits.first() else {\n                        // No rate limits exist for this server; we shouldn't reach this point!\n                        return None;\n                    };\n\n                    if first_id.name == id.gkn.name {\n                        accepted()\n                    } else {\n                        ratelimit_already_exists()\n                    }\n                } else {\n                    no_matching_target()\n                };\n\n                Some(policy::HttpLocalRateLimitPolicyStatus {\n                    conditions: vec![condition],\n                    target_ref: policy::LocalTargetRef {\n                        group: Some(POLICY_API_GROUP.to_string()),\n                        kind: \"Server\".to_string(),\n                        name: server.name.clone(),\n                    },\n                })\n            }\n            ratelimit::TargetReference::UnknownKind => None,\n        }\n    }\n\n    fn make_ratelimit_patch(\n        &self,\n        id: &NamespaceGroupKindName,\n        ratelimit: &HttpLocalRateLimitPolicyRef,\n    ) -> Option<k8s::Patch<serde_json::Value>> {\n        let status = self.target_ref_status(id, &ratelimit.target_ref)?;\n        if eq_time_insensitive_conditions(&status.conditions, &ratelimit.status_conditions) {\n            return None;\n        }\n\n        make_patch(id, status)\n    }\n\n    fn network_condition(&self, egress_net: &EgressNetworkRef) -> k8s::Condition {\n        for egress_network_block in &egress_net.networks {\n            for cluster_network_block in &self.cluster_networks {\n                if egress_network_block.intersect(cluster_network_block) {\n                    return in_cluster_net_overlap();\n                }\n            }\n        }\n\n        accepted()\n    }\n\n    fn make_egress_net_patch(\n        &self,\n        id: &NamespaceGroupKindName,\n        egress_net: &EgressNetworkRef,\n    ) -> Option<k8s::Patch<serde_json::Value>> {\n        let unowned_conditions = egress_net\n            .status_conditions\n            .iter()\n            .filter(|c| c.type_ != conditions::ACCEPTED)\n            .cloned();\n\n        let all_conditions: Vec<linkerd_policy_controller_k8s_api::Condition> = unowned_conditions\n            .chain(std::iter::once(self.network_condition(egress_net)))\n            .collect::<Vec<_>>();\n\n        if eq_time_insensitive_conditions(&all_conditions, &egress_net.status_conditions) {\n            return None;\n        }\n\n        let status = policy::EgressNetworkStatus {\n            conditions: all_conditions,\n        };\n\n        make_patch(id, status)\n    }\n\n    /// If this instance is the leader, reconcile the statuses for all resources\n    /// for which we control the status.\n    fn reconcile_if_leader(&self) {\n        let lease = self.claims.borrow();\n        if !lease.is_current_for(&self.name) {\n            tracing::trace!(%lease.holder, ?lease.expiry, \"Reconcilation skipped\");\n            return;\n        }\n        drop(lease);\n\n        tracing::trace!(\n            egressnetworks = self.egress_networks.len(),\n            http_routes = self.http_route_refs.len(),\n            grpc_routes = self.grpc_route_refs.len(),\n            tls_routes = self.tls_route_refs.len(),\n            tcp_routes = self.tcp_route_refs.len(),\n            httplocalratelimits = self.ratelimits.len(),\n            \"Reconciling\"\n        );\n        let egressnetworks = self.reconcile_egress_networks();\n        let routes = self.reconcile_routes();\n        let ratelimits = self.reconcile_ratelimits();\n\n        if egressnetworks + routes + ratelimits > 0 {\n            tracing::debug!(egressnetworks, routes, ratelimits, \"Reconciled\");\n        }\n    }\n\n    fn reconcile_egress_networks(&self) -> usize {\n        let mut patches = 0;\n        for (id, net) in self.egress_networks.iter() {\n            let id = NamespaceGroupKindName {\n                namespace: id.namespace.clone(),\n                gkn: GroupKindName {\n                    group: policy::EgressNetwork::group(&()),\n                    kind: policy::EgressNetwork::kind(&()),\n                    name: id.name.clone().into(),\n                },\n            };\n\n            if let Some(patch) = self.make_egress_net_patch(&id, net) {\n                match self.updates.try_send(Update {\n                    id: id.clone(),\n                    patch,\n                }) {\n                    Ok(()) => {\n                        patches += 1;\n                        self.metrics.patch_enqueues.inc();\n                    }\n                    Err(error) => {\n                        self.metrics.patch_channel_full.inc();\n                        tracing::error!(%id.namespace, route = ?id.gkn, %error, \"Failed to send egress network patch\");\n                    }\n                }\n            }\n        }\n        patches\n    }\n\n    fn reconcile_routes(&self) -> usize {\n        let mut patches = 0;\n        let http_patches = self\n            .http_route_refs\n            .iter()\n            .filter_map(|(id, route)| self.make_http_route_patch(id, route).map(|p| (id, p)));\n        let grpc_patches = self\n            .grpc_route_refs\n            .iter()\n            .filter_map(|(id, route)| self.make_grpc_route_patch(id, route).map(|p| (id, p)));\n        let tls_patches = self\n            .tls_route_refs\n            .iter()\n            .filter_map(|(id, route)| self.make_tls_route_patch(id, route).map(|p| (id, p)));\n        let tcp_patches = self\n            .tcp_route_refs\n            .iter()\n            .filter_map(|(id, route)| self.make_tcp_route_patch(id, route).map(|p| (id, p)));\n\n        for (id, patch) in http_patches\n            .chain(grpc_patches)\n            .chain(tls_patches)\n            .chain(tcp_patches)\n        {\n            match self.updates.try_send(Update {\n                id: id.clone(),\n                patch,\n            }) {\n                Ok(()) => {\n                    patches += 1;\n                    self.metrics.patch_enqueues.inc();\n                }\n                Err(error) => {\n                    self.metrics.patch_channel_full.inc();\n                    tracing::error!(%id.namespace, route = ?id.gkn, %error, \"Failed to send route patch\");\n                }\n            }\n        }\n        patches\n    }\n\n    fn reconcile_ratelimits(&self) -> usize {\n        let mut patches = 0;\n        for (id, rl) in self.ratelimits.iter() {\n            let id = NamespaceGroupKindName {\n                namespace: id.namespace.clone(),\n                gkn: GroupKindName {\n                    group: policy::HttpLocalRateLimitPolicy::group(&()),\n                    kind: policy::HttpLocalRateLimitPolicy::kind(&()),\n                    name: id.name.clone().into(),\n                },\n            };\n\n            if let Some(patch) = self.make_ratelimit_patch(&id, rl) {\n                match self.updates.try_send(Update {\n                    id: id.clone(),\n                    patch,\n                }) {\n                    Ok(()) => {\n                        patches += 1;\n                        self.metrics.patch_enqueues.inc();\n                    }\n                    Err(error) => {\n                        self.metrics.patch_channel_full.inc();\n                        tracing::error!(%id.namespace, ratelimit = ?id.gkn, %error, \"Failed to send ratelimit patch\");\n                    }\n                }\n            }\n        }\n        patches\n    }\n\n    #[tracing::instrument(level = \"debug\", skip(self, net))]\n    fn index_egress_network(&mut self, id: ResourceId, net: EgressNetworkRef) {\n        tracing::trace!(?net);\n        // Insert into the index; if the network is already in the index, and it hasn't\n        // changed, skip creating a patch.\n        if !self.update_egress_net(id, &net) {\n            return;\n        }\n\n        self.reconcile_if_leader();\n    }\n\n    #[tracing::instrument(level = \"debug\", skip(self, ratelimit))]\n    fn index_ratelimit(&mut self, id: ResourceId, ratelimit: HttpLocalRateLimitPolicyRef) {\n        tracing::trace!(?ratelimit);\n        // Insert into the index; if the route is already in the index, and it hasn't\n        // changed, skip creating a patch.\n        if !self.update_ratelimit(id.clone(), &ratelimit) {\n            return;\n        }\n\n        self.reconcile_if_leader();\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<policy::HttpRoute> for Index {\n    fn apply(&mut self, resource: policy::HttpRoute) {\n        let namespace = resource\n            .namespace()\n            .expect(\"HTTPRoute must have a namespace\");\n        let name = resource.name_unchecked();\n        let id = NamespaceGroupKindName {\n            namespace: namespace.clone(),\n            gkn: GroupKindName {\n                group: policy::HttpRoute::group(&()),\n                kind: policy::HttpRoute::kind(&()),\n                name: name.into(),\n            },\n        };\n\n        // Create the route parents\n        let parents =\n            routes::http::make_parents(&namespace, &resource.spec.parent_refs.unwrap_or_default());\n\n        // Create the route backends\n        let backends = routes::http::make_backends(\n            &namespace,\n            resource\n                .spec\n                .rules\n                .into_iter()\n                .flatten()\n                .flat_map(|rule| rule.backend_refs)\n                .flatten(),\n        );\n\n        let statuses = resource\n            .status\n            .into_iter()\n            .flat_map(|status| status.parents)\n            .collect();\n\n        // Construct route and insert into the index; if the HTTPRoute is\n        // already in the index, and it hasn't changed, skip creating a patch.\n        let route = HTTPRouteRef {\n            parents,\n            backends,\n            statuses: vec![gateway::HTTPRouteStatus { parents: statuses }],\n        };\n        tracing::trace!(?route);\n        // Insert into the index; if the route is already in the index, and it hasn't\n        // changed, skip creating a patch.\n        if !self.update_http_route(id.clone(), &route) {\n            return;\n        }\n\n        self.reconcile_if_leader();\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        let id = NamespaceGroupKindName {\n            namespace,\n            gkn: GroupKindName {\n                group: policy::HttpRoute::group(&()),\n                kind: policy::HttpRoute::kind(&()),\n                name: name.into(),\n            },\n        };\n        self.http_route_refs.remove(&id);\n    }\n\n    // Since apply only reindexes a single HTTPRoute at a time, there's no need\n    // to handle resets specially.\n}\n\nimpl kubert::index::IndexNamespacedResource<gateway::HTTPRoute> for Index {\n    fn apply(&mut self, resource: gateway::HTTPRoute) {\n        let namespace = resource\n            .namespace()\n            .expect(\"HTTPRoute must have a namespace\");\n        let name = resource.name_unchecked();\n        let id = NamespaceGroupKindName {\n            namespace: namespace.clone(),\n            gkn: GroupKindName {\n                group: gateway::HTTPRoute::group(&()),\n                kind: gateway::HTTPRoute::kind(&()),\n                name: name.into(),\n            },\n        };\n\n        // Create the route parents\n        let parents =\n            routes::http::make_parents(&namespace, &resource.spec.parent_refs.unwrap_or_default());\n\n        // Create the route backends\n        let backends = routes::http::make_backends(\n            &namespace,\n            resource\n                .spec\n                .rules\n                .into_iter()\n                .flatten()\n                .flat_map(|rule| rule.backend_refs)\n                .flatten(),\n        );\n\n        let statuses = resource\n            .status\n            .into_iter()\n            .flat_map(|status| status.parents)\n            .collect();\n\n        // Construct route and insert into the index; if the HTTPRoute is\n        // already in the index, and it hasn't changed, skip creating a patch.\n        let route = RouteRef {\n            parents,\n            backends,\n            statuses: vec![gateway::HTTPRouteStatus { parents: statuses }],\n        };\n        tracing::trace!(?route);\n        // Insert into the index; if the route is already in the index, and it hasn't\n        // changed, skip creating a patch.\n        if !self.update_http_route(id.clone(), &route) {\n            return;\n        }\n\n        self.reconcile_if_leader();\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        let id = NamespaceGroupKindName {\n            namespace,\n            gkn: GroupKindName {\n                group: gateway::HTTPRoute::group(&()),\n                kind: gateway::HTTPRoute::kind(&()),\n                name: name.into(),\n            },\n        };\n        self.http_route_refs.remove(&id);\n    }\n\n    // Since apply only reindexes a single HTTPRoute at a time, there's no need\n    // to handle resets specially.\n}\n\nimpl kubert::index::IndexNamespacedResource<gateway::GRPCRoute> for Index {\n    fn apply(&mut self, resource: gateway::GRPCRoute) {\n        let namespace = resource\n            .namespace()\n            .expect(\"GRPCRoute must have a namespace\");\n        let name = resource.name_unchecked();\n        let id = NamespaceGroupKindName {\n            namespace: namespace.clone(),\n            gkn: GroupKindName {\n                name: name.into(),\n                kind: gateway::GRPCRoute::kind(&()),\n                group: gateway::GRPCRoute::group(&()),\n            },\n        };\n\n        // Create the route parents\n        let parents =\n            routes::grpc::make_parents(&namespace, &resource.spec.parent_refs.unwrap_or_default());\n\n        // Create the route backends\n        let backends = routes::grpc::make_backends(\n            &namespace,\n            resource\n                .spec\n                .rules\n                .into_iter()\n                .flatten()\n                .flat_map(|rule| rule.backend_refs)\n                .flatten(),\n        );\n\n        let statuses = resource\n            .status\n            .into_iter()\n            .flat_map(|status| status.parents)\n            .collect();\n\n        // Construct route and insert into the index; if the GRPCRoute is\n        // already in the index and it hasn't changed, skip creating a patch.\n        let route = RouteRef {\n            parents,\n            backends,\n            statuses: vec![gateway::GRPCRouteStatus { parents: statuses }],\n        };\n        tracing::trace!(?route);\n        // Insert into the index; if the route is already in the index, and it hasn't\n        // changed, skip creating a patch.\n        if !self.update_grpc_route(id.clone(), &route) {\n            return;\n        }\n\n        self.reconcile_if_leader();\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        let id = NamespaceGroupKindName {\n            namespace,\n            gkn: GroupKindName {\n                name: name.into(),\n                kind: gateway::GRPCRoute::kind(&()),\n                group: gateway::GRPCRoute::group(&()),\n            },\n        };\n        self.grpc_route_refs.remove(&id);\n    }\n\n    // Since apply only reindexes a single GRPCRoute at a time, there's no need\n    // to handle resets specially.\n}\n\nimpl kubert::index::IndexNamespacedResource<gateway::TLSRoute> for Index {\n    fn apply(&mut self, resource: gateway::TLSRoute) {\n        let namespace = resource\n            .namespace()\n            .expect(\"TlsRoute must have a namespace\");\n        let name = resource.name_unchecked();\n        let id = NamespaceGroupKindName {\n            namespace: namespace.clone(),\n            gkn: GroupKindName {\n                group: gateway::TLSRoute::group(&()),\n                kind: gateway::TLSRoute::kind(&()),\n                name: name.into(),\n            },\n        };\n\n        // Create the route parents\n        let parents =\n            routes::tls::make_parents(&namespace, &resource.spec.parent_refs.unwrap_or_default());\n\n        // Create the route backends\n        let backends = routes::tls::make_backends(\n            &namespace,\n            resource\n                .spec\n                .rules\n                .into_iter()\n                .flat_map(|rule| rule.backend_refs)\n                .flatten(),\n        );\n\n        let statuses = resource\n            .status\n            .into_iter()\n            .flat_map(|status| status.parents)\n            .collect();\n\n        // Construct route and insert into the index; if the TLSRoute is\n        // already in the index, and it hasn't changed, skip creating a patch.\n        let route = RouteRef {\n            parents,\n            backends,\n            statuses: vec![gateway::TLSRouteStatus { parents: statuses }],\n        };\n        tracing::trace!(?route);\n        // Insert into the index; if the route is already in the index, and it hasn't\n        // changed, skip creating a patch.\n        if !self.update_tls_route(id.clone(), &route) {\n            return;\n        }\n\n        self.reconcile_if_leader();\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        let id = NamespaceGroupKindName {\n            namespace,\n            gkn: GroupKindName {\n                group: gateway::TLSRoute::group(&()),\n                kind: gateway::TLSRoute::kind(&()),\n                name: name.into(),\n            },\n        };\n        self.tls_route_refs.remove(&id);\n    }\n\n    // Since apply only reindexes a single HTTPRoute at a time, there's no need\n    // to handle resets specially.\n}\n\nimpl kubert::index::IndexNamespacedResource<gateway::TCPRoute> for Index {\n    fn apply(&mut self, resource: gateway::TCPRoute) {\n        let namespace = resource\n            .namespace()\n            .expect(\"TcpRoute must have a namespace\");\n        let name = resource.name_unchecked();\n        let id = NamespaceGroupKindName {\n            namespace: namespace.clone(),\n            gkn: GroupKindName {\n                group: gateway::TCPRoute::group(&()),\n                kind: gateway::TCPRoute::kind(&()),\n                name: name.into(),\n            },\n        };\n\n        // Create the route parents\n        let parents =\n            routes::tcp::make_parents(&namespace, &resource.spec.parent_refs.unwrap_or_default());\n\n        // Create the route backends\n        let backends = routes::tcp::make_backends(\n            &namespace,\n            resource\n                .spec\n                .rules\n                .into_iter()\n                .flat_map(|rule| rule.backend_refs)\n                .flatten(),\n        );\n\n        let statuses = resource\n            .status\n            .into_iter()\n            .flat_map(|status| status.parents)\n            .collect();\n\n        // Construct route and insert into the index; if the TCPRoute is\n        // already in the index, and it hasn't changed, skip creating a patch.\n        let route = RouteRef {\n            parents,\n            backends,\n            statuses: vec![gateway::TCPRouteStatus { parents: statuses }],\n        };\n        tracing::trace!(?route);\n        // Insert into the index; if the route is already in the index, and it hasn't\n        // changed, skip creating a patch.\n        if !self.update_tcp_route(id.clone(), &route) {\n            return;\n        }\n\n        self.reconcile_if_leader();\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        let id = NamespaceGroupKindName {\n            namespace,\n            gkn: GroupKindName {\n                group: gateway::TCPRoute::group(&()),\n                kind: gateway::TCPRoute::kind(&()),\n                name: name.into(),\n            },\n        };\n        self.tcp_route_refs.remove(&id);\n    }\n\n    // Since apply only reindexes a single HTTPRoute at a time, there's no need\n    // to handle resets specially.\n}\n\nimpl kubert::index::IndexNamespacedResource<policy::Server> for Index {\n    fn apply(&mut self, resource: policy::Server) {\n        let namespace = resource.namespace().expect(\"Server must have a namespace\");\n        let name = resource.name_unchecked();\n        self.servers.insert(ResourceId::new(namespace, name));\n        self.reconcile_if_leader();\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        self.servers.remove(&ResourceId::new(namespace, name));\n        self.reconcile_if_leader();\n    }\n\n    // Since apply only reindexes a single Server at a time, there's no need\n    // to handle resets specially.\n}\n\nimpl kubert::index::IndexNamespacedResource<k8s::Service> for Index {\n    fn apply(&mut self, resource: k8s::Service) {\n        let namespace = resource.namespace().expect(\"Service must have a namespace\");\n        let name = resource.name_unchecked();\n        self.services\n            .insert(ResourceId::new(namespace, name), resource.into());\n        self.reconcile_if_leader();\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        self.services.remove(&ResourceId::new(namespace, name));\n        self.reconcile_if_leader();\n    }\n\n    // Since apply only reindexes a single Service at a time, there's no need\n    // to handle resets specially.\n}\n\nimpl kubert::index::IndexNamespacedResource<policy::HttpLocalRateLimitPolicy> for Index {\n    fn apply(&mut self, resource: policy::HttpLocalRateLimitPolicy) {\n        let namespace = resource\n            .namespace()\n            .expect(\"HTTPLocalRateLimitPolicy must have a namespace\");\n        let name = resource.name_unchecked();\n\n        let status_conditions = resource\n            .status\n            .into_iter()\n            .flat_map(|s| s.conditions)\n            .collect();\n\n        let id = ResourceId::new(namespace.clone(), name);\n        let creation_timestamp = resource.metadata.creation_timestamp.map(|Time(t)| t);\n        let target_ref = ratelimit::TargetReference::make_target_ref(&namespace, &resource.spec);\n\n        let rl = HttpLocalRateLimitPolicyRef {\n            creation_timestamp,\n            target_ref,\n            status_conditions,\n        };\n\n        self.index_ratelimit(id, rl);\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        let id = ResourceId::new(namespace, name);\n        self.ratelimits.remove(&id);\n        self.reconcile_if_leader();\n    }\n}\n\nimpl kubert::index::IndexNamespacedResource<policy::EgressNetwork> for Index {\n    fn apply(&mut self, resource: policy::EgressNetwork) {\n        let namespace = resource\n            .namespace()\n            .expect(\"EgressNetwork must have a namespace\");\n        let name = resource.name_unchecked();\n\n        let status_conditions = resource\n            .status\n            .into_iter()\n            .flat_map(|s| s.conditions)\n            .collect();\n\n        let networks = resource.spec.networks.unwrap_or_else(|| {\n            let (v6, v4) = self\n                .cluster_networks\n                .iter()\n                .cloned()\n                .partition(Cidr::is_ipv6);\n\n            vec![\n                Network {\n                    cidr: \"0.0.0.0/0\".parse().expect(\"should parse\"),\n                    except: Some(v4),\n                },\n                Network {\n                    cidr: \"::/0\".parse().expect(\"should parse\"),\n                    except: Some(v6),\n                },\n            ]\n        });\n\n        let id = ResourceId::new(namespace, name);\n\n        let net = EgressNetworkRef {\n            status_conditions,\n            networks,\n        };\n\n        self.index_egress_network(id, net);\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        let id = ResourceId::new(namespace, name);\n        self.egress_networks.remove(&id);\n        self.reconcile_if_leader();\n    }\n}\n\npub(crate) fn make_patch<Status>(\n    resource_id: &NamespaceGroupKindName,\n    status: Status,\n) -> Option<k8s::Patch<serde_json::Value>>\nwhere\n    Status: serde::Serialize,\n{\n    match resource_id.api_version() {\n        Err(error) => {\n            tracing::error!(error = %error, \"Failed to create patch for resource\");\n            None\n        }\n        Ok(api_version) => {\n            let patch = serde_json::json!({\n                \"apiVersion\": api_version,\n                    \"kind\": &resource_id.gkn.kind,\n                    \"name\": &resource_id.gkn.name,\n                    \"status\": status,\n            });\n\n            Some(k8s::Patch::Merge(patch))\n        }\n    }\n}\n\nfn now() -> DateTime<Utc> {\n    #[cfg(not(test))]\n    let now = Utc::now();\n    #[cfg(test)]\n    let now = DateTime::<Utc>::MIN_UTC;\n    now\n}\n\npub(crate) fn no_matching_parent() -> k8s::Condition {\n    k8s::Condition {\n        last_transition_time: k8s::Time(now()),\n        message: \"\".to_string(),\n        observed_generation: None,\n        reason: reasons::NO_MATCHING_PARENT.to_string(),\n        status: cond_statuses::STATUS_FALSE.to_string(),\n        type_: conditions::ACCEPTED.to_string(),\n    }\n}\n\npub(crate) fn no_matching_target() -> k8s::Condition {\n    k8s::Condition {\n        last_transition_time: k8s::Time(now()),\n        message: \"\".to_string(),\n        observed_generation: None,\n        reason: reasons::NO_MATCHING_TARGET.to_string(),\n        status: cond_statuses::STATUS_FALSE.to_string(),\n        type_: conditions::ACCEPTED.to_string(),\n    }\n}\n\nfn headless_parent() -> k8s::Condition {\n    k8s::Condition {\n        last_transition_time: k8s::Time(now()),\n        message: \"parent service must have a ClusterIP\".to_string(),\n        observed_generation: None,\n        reason: reasons::NO_MATCHING_PARENT.to_string(),\n        status: cond_statuses::STATUS_FALSE.to_string(),\n        type_: conditions::ACCEPTED.to_string(),\n    }\n}\n\nfn egress_net_not_accepted() -> k8s::Condition {\n    k8s::Condition {\n        last_transition_time: k8s::Time(now()),\n        message: \"EgressNetwork parent has not been accepted\".to_string(),\n        observed_generation: None,\n        reason: reasons::NO_MATCHING_PARENT.to_string(),\n        status: cond_statuses::STATUS_FALSE.to_string(),\n        type_: conditions::ACCEPTED.to_string(),\n    }\n}\n\npub(crate) fn route_conflicted() -> k8s::Condition {\n    k8s::Condition {\n        last_transition_time: k8s::Time(now()),\n        message: \"\".to_string(),\n        observed_generation: None,\n        reason: reasons::ROUTE_REASON_CONFLICTED.to_string(),\n        status: cond_statuses::STATUS_FALSE.to_string(),\n        type_: conditions::ACCEPTED.to_string(),\n    }\n}\n\npub(crate) fn ratelimit_already_exists() -> k8s::Condition {\n    k8s::Condition {\n        last_transition_time: k8s::Time(now()),\n        message: \"\".to_string(),\n        observed_generation: None,\n        reason: reasons::RATELIMIT_REASON_ALREADY_EXISTS.to_string(),\n        status: cond_statuses::STATUS_FALSE.to_string(),\n        type_: conditions::ACCEPTED.to_string(),\n    }\n}\n\npub(crate) fn accepted() -> k8s::Condition {\n    k8s::Condition {\n        last_transition_time: k8s::Time(now()),\n        message: \"\".to_string(),\n        observed_generation: None,\n        reason: conditions::ACCEPTED.to_string(),\n        status: cond_statuses::STATUS_TRUE.to_string(),\n        type_: conditions::ACCEPTED.to_string(),\n    }\n}\n\npub(crate) fn in_cluster_net_overlap() -> k8s::Condition {\n    k8s::Condition {\n        last_transition_time: k8s::Time(now()),\n        message: \"networks overlap with clusterNetworks\".to_string(),\n        observed_generation: None,\n        reason: reasons::EGRESS_NET_REASON_OVERLAP.to_string(),\n        status: cond_statuses::STATUS_FALSE.to_string(),\n        type_: conditions::ACCEPTED.to_string(),\n    }\n}\n\npub(crate) fn resolved_refs() -> k8s::Condition {\n    k8s::Condition {\n        last_transition_time: k8s::Time(now()),\n        message: \"\".to_string(),\n        observed_generation: None,\n        reason: reasons::RESOLVED_REFS.to_string(),\n        status: cond_statuses::STATUS_TRUE.to_string(),\n        type_: conditions::RESOLVED_REFS.to_string(),\n    }\n}\n\npub(crate) fn backend_not_found() -> k8s::Condition {\n    k8s::Condition {\n        last_transition_time: k8s::Time(now()),\n        message: \"\".to_string(),\n        observed_generation: None,\n        reason: reasons::BACKEND_NOT_FOUND.to_string(),\n        status: cond_statuses::STATUS_FALSE.to_string(),\n        type_: conditions::RESOLVED_REFS.to_string(),\n    }\n}\n\npub(crate) fn invalid_backend_kind(message: &str) -> k8s::Condition {\n    k8s::Condition {\n        last_transition_time: k8s::Time(now()),\n        message: message.to_string(),\n        observed_generation: None,\n        reason: reasons::INVALID_KIND.to_string(),\n        status: cond_statuses::STATUS_FALSE.to_string(),\n        type_: conditions::RESOLVED_REFS.to_string(),\n    }\n}\n\npub(crate) fn eq_time_insensitive_http_route_parent_statuses(\n    left: &[gateway::HTTPRouteStatusParents],\n    right: &[gateway::HTTPRouteStatusParents],\n) -> bool {\n    if left.len() != right.len() {\n        return false;\n    }\n\n    // Create sorted versions of the input slices\n    let mut left_sorted: Vec<_> = left.to_vec();\n    let mut right_sorted: Vec<_> = right.to_vec();\n\n    left_sorted.sort_by(|a, b| {\n        a.controller_name\n            .cmp(&b.controller_name)\n            .then_with(|| a.parent_ref.name.cmp(&b.parent_ref.name))\n            .then_with(|| a.parent_ref.namespace.cmp(&b.parent_ref.namespace))\n    });\n    right_sorted.sort_by(|a, b| {\n        a.controller_name\n            .cmp(&b.controller_name)\n            .then_with(|| a.parent_ref.name.cmp(&b.parent_ref.name))\n            .then_with(|| a.parent_ref.namespace.cmp(&b.parent_ref.namespace))\n    });\n\n    // Compare each element in sorted order\n    left_sorted.iter().zip(right_sorted.iter()).all(|(l, r)| {\n        let cond_eq = match (&l.conditions, &r.conditions) {\n            (Some(l), Some(r)) => eq_time_insensitive_conditions(l.as_ref(), r.as_ref()),\n            (None, None) => true,\n            _ => false,\n        };\n        l.parent_ref == r.parent_ref && l.controller_name == r.controller_name && cond_eq\n    })\n}\n\npub(crate) fn eq_time_insensitive_grpc_route_parent_statuses(\n    left: &[gateway::GRPCRouteStatusParents],\n    right: &[gateway::GRPCRouteStatusParents],\n) -> bool {\n    if left.len() != right.len() {\n        return false;\n    }\n\n    // Create sorted versions of the input slices\n    let mut left_sorted: Vec<_> = left.to_vec();\n    let mut right_sorted: Vec<_> = right.to_vec();\n\n    left_sorted.sort_by(|a, b| {\n        a.controller_name\n            .cmp(&b.controller_name)\n            .then_with(|| a.parent_ref.name.cmp(&b.parent_ref.name))\n            .then_with(|| a.parent_ref.namespace.cmp(&b.parent_ref.namespace))\n    });\n    right_sorted.sort_by(|a, b| {\n        a.controller_name\n            .cmp(&b.controller_name)\n            .then_with(|| a.parent_ref.name.cmp(&b.parent_ref.name))\n            .then_with(|| a.parent_ref.namespace.cmp(&b.parent_ref.namespace))\n    });\n\n    // Compare each element in sorted order\n    left_sorted.iter().zip(right_sorted.iter()).all(|(l, r)| {\n        let cond_eq = match (&l.conditions, &r.conditions) {\n            (Some(l), Some(r)) => eq_time_insensitive_conditions(l.as_ref(), r.as_ref()),\n            (None, None) => true,\n            _ => false,\n        };\n        l.parent_ref == r.parent_ref && l.controller_name == r.controller_name && cond_eq\n    })\n}\n\npub(crate) fn eq_time_insensitive_tls_route_parent_statuses(\n    left: &[gateway::TLSRouteStatusParents],\n    right: &[gateway::TLSRouteStatusParents],\n) -> bool {\n    if left.len() != right.len() {\n        return false;\n    }\n\n    // Create sorted versions of the input slices\n    let mut left_sorted: Vec<_> = left.to_vec();\n    let mut right_sorted: Vec<_> = right.to_vec();\n\n    left_sorted.sort_by(|a, b| {\n        a.controller_name\n            .cmp(&b.controller_name)\n            .then_with(|| a.parent_ref.name.cmp(&b.parent_ref.name))\n            .then_with(|| a.parent_ref.namespace.cmp(&b.parent_ref.namespace))\n    });\n    right_sorted.sort_by(|a, b| {\n        a.controller_name\n            .cmp(&b.controller_name)\n            .then_with(|| a.parent_ref.name.cmp(&b.parent_ref.name))\n            .then_with(|| a.parent_ref.namespace.cmp(&b.parent_ref.namespace))\n    });\n\n    // Compare each element in sorted order\n    left_sorted.iter().zip(right_sorted.iter()).all(|(l, r)| {\n        let cond_eq = match (&l.conditions, &r.conditions) {\n            (Some(l), Some(r)) => eq_time_insensitive_conditions(l.as_ref(), r.as_ref()),\n            (None, None) => true,\n            _ => false,\n        };\n        l.parent_ref == r.parent_ref && l.controller_name == r.controller_name && cond_eq\n    })\n}\n\npub(crate) fn eq_time_insensitive_tcp_route_parent_statuses(\n    left: &[gateway::TCPRouteStatusParents],\n    right: &[gateway::TCPRouteStatusParents],\n) -> bool {\n    if left.len() != right.len() {\n        return false;\n    }\n\n    // Create sorted versions of the input slices\n    let mut left_sorted: Vec<_> = left.to_vec();\n    let mut right_sorted: Vec<_> = right.to_vec();\n\n    left_sorted.sort_by(|a, b| {\n        a.controller_name\n            .cmp(&b.controller_name)\n            .then_with(|| a.parent_ref.name.cmp(&b.parent_ref.name))\n            .then_with(|| a.parent_ref.namespace.cmp(&b.parent_ref.namespace))\n    });\n    right_sorted.sort_by(|a, b| {\n        a.controller_name\n            .cmp(&b.controller_name)\n            .then_with(|| a.parent_ref.name.cmp(&b.parent_ref.name))\n            .then_with(|| a.parent_ref.namespace.cmp(&b.parent_ref.namespace))\n    });\n\n    // Compare each element in sorted order\n    left_sorted.iter().zip(right_sorted.iter()).all(|(l, r)| {\n        let cond_eq = match (&l.conditions, &r.conditions) {\n            (Some(l), Some(r)) => eq_time_insensitive_conditions(l.as_ref(), r.as_ref()),\n            (None, None) => true,\n            _ => false,\n        };\n        l.parent_ref == r.parent_ref && l.controller_name == r.controller_name && cond_eq\n    })\n}\n\nfn eq_time_insensitive_conditions(left: &[k8s::Condition], right: &[k8s::Condition]) -> bool {\n    if left.len() != right.len() {\n        return false;\n    }\n\n    left.iter().zip(right.iter()).all(|(l, r)| {\n        l.message == r.message\n            && l.observed_generation == r.observed_generation\n            && l.reason == r.reason\n            && l.status == r.status\n            && l.type_ == r.type_\n    })\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/lib.rs",
    "content": "mod index;\nmod ratelimit;\nmod resource_id;\nmod routes;\nmod service;\n\n#[cfg(test)]\nmod tests;\n\npub use self::index::{Controller, ControllerMetrics, Index, IndexMetrics};\n"
  },
  {
    "path": "policy-controller/k8s/status/src/ratelimit.rs",
    "content": "use crate::resource_id::ResourceId;\nuse linkerd_policy_controller_k8s_api::policy as linkerd_k8s_api;\n\n#[derive(Clone, Eq, PartialEq, Debug)]\npub enum TargetReference {\n    Server(ResourceId),\n    UnknownKind,\n}\n\nimpl TargetReference {\n    pub(crate) fn make_target_ref(\n        namespace: &str,\n        rl: &linkerd_k8s_api::RateLimitPolicySpec,\n    ) -> TargetReference {\n        if rl.target_ref.targets_kind::<linkerd_k8s_api::Server>() {\n            Self::Server(ResourceId::new(\n                namespace.to_string(),\n                rl.target_ref.name.clone(),\n            ))\n        } else {\n            Self::UnknownKind\n        }\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/resource_id.rs",
    "content": "use crate::index::{GATEWAY_API_GROUP, POLICY_API_GROUP};\nuse linkerd_policy_controller_core::routes::GroupKindName;\nuse linkerd_policy_controller_k8s_api::{gateway, policy as linkerd_k8s_api, Resource};\nuse std::borrow::Cow;\n\n#[derive(Clone, Debug, Eq, Hash, PartialEq)]\npub struct ResourceId {\n    pub namespace: String,\n    pub name: String,\n}\n\nimpl ResourceId {\n    pub fn new(namespace: String, name: String) -> Self {\n        Self { namespace, name }\n    }\n}\n\n#[derive(Clone, Debug, Eq, Hash, PartialEq)]\npub struct NamespaceGroupKindName {\n    pub namespace: String,\n    pub gkn: GroupKindName,\n}\n\nimpl NamespaceGroupKindName {\n    pub fn api_version(&self) -> anyhow::Result<Cow<'static, str>> {\n        match (self.gkn.group.as_ref(), self.gkn.kind.as_ref()) {\n            (POLICY_API_GROUP, \"HTTPRoute\") => Ok(linkerd_k8s_api::HttpRoute::api_version(&())),\n            (POLICY_API_GROUP, \"HTTPLocalRateLimitPolicy\") => {\n                Ok(linkerd_k8s_api::HttpLocalRateLimitPolicy::api_version(&()))\n            }\n            (POLICY_API_GROUP, \"EgressNetwork\") => {\n                Ok(linkerd_k8s_api::EgressNetwork::api_version(&()))\n            }\n            (GATEWAY_API_GROUP, \"HTTPRoute\") => Ok(gateway::HTTPRoute::api_version(&())),\n            (GATEWAY_API_GROUP, \"GRPCRoute\") => Ok(gateway::GRPCRoute::api_version(&())),\n            (GATEWAY_API_GROUP, \"TCPRoute\") => Ok(gateway::TCPRoute::api_version(&())),\n            (GATEWAY_API_GROUP, \"TLSRoute\") => Ok(gateway::TLSRoute::api_version(&())),\n            (group, kind) => {\n                anyhow::bail!(\"unknown group + kind combination: ({group}, {kind})\")\n            }\n        }\n    }\n\n    pub fn is_a<K>(&self) -> bool\n    where\n        K: Resource<DynamicType = ()>,\n    {\n        self.gkn.group == K::group(&()) && self.gkn.kind == K::kind(&())\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/routes/grpc.rs",
    "content": "use super::{BackendReference, ParentReference, ResourceId};\nuse anyhow::Result;\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s, gateway,\n    policy::{\n        self,\n        grpcroute::{backend_ref_targets_kind, parent_ref_targets_kind},\n    },\n};\n\npub(crate) fn make_backends(\n    namespace: &str,\n    backends: impl Iterator<Item = gateway::GRPCRouteRulesBackendRefs>,\n) -> Vec<BackendReference> {\n    backends\n        .map(|backend_ref| to_backend_ref(&backend_ref, namespace))\n        .collect()\n}\n\npub(crate) fn make_parents(\n    namespace: &str,\n    parents: &[gateway::GRPCRouteParentRefs],\n) -> Vec<ParentReference> {\n    parents\n        .iter()\n        .filter_map(|pr| {\n            to_parent_ref(pr, namespace)\n                .inspect_err(|error| tracing::error!(?error, \"failed to make parent reference\"))\n                .ok()\n        })\n        .collect()\n}\n\nfn to_parent_ref(\n    parent_ref: &gateway::GRPCRouteParentRefs,\n    default_namespace: &str,\n) -> Result<ParentReference> {\n    if parent_ref_targets_kind::<policy::Server>(parent_ref) {\n        // If the parent reference does not have a namespace, default to using\n        // the route's namespace.\n        let namespace = parent_ref.namespace.as_deref().unwrap_or(default_namespace);\n        Result::Ok(ParentReference::Server(ResourceId::new(\n            namespace.to_string(),\n            parent_ref.name.clone(),\n        )))\n    } else if parent_ref_targets_kind::<k8s::Service>(parent_ref) {\n        // If the parent reference does not have a namespace, default to using\n        // the route's namespace.\n        let namespace = parent_ref.namespace.as_deref().unwrap_or(default_namespace);\n        Result::Ok(ParentReference::Service(\n            ResourceId::new(namespace.to_string(), parent_ref.name.clone()),\n            parent_ref.port.map(|p| p.try_into()).transpose()?,\n        ))\n    } else if parent_ref_targets_kind::<policy::EgressNetwork>(parent_ref) {\n        // If the parent reference does not have a namespace, default to using\n        // the route's namespace.\n        let namespace = parent_ref.namespace.as_deref().unwrap_or(default_namespace);\n        Result::Ok(ParentReference::EgressNetwork(\n            ResourceId::new(namespace.to_string(), parent_ref.name.clone()),\n            parent_ref.port.map(|p| p.try_into()).transpose()?,\n        ))\n    } else {\n        Result::Ok(ParentReference::UnknownKind)\n    }\n}\n\nfn to_backend_ref(\n    backend_ref: &gateway::GRPCRouteRulesBackendRefs,\n    default_namespace: &str,\n) -> BackendReference {\n    if backend_ref_targets_kind::<k8s::Service>(backend_ref) {\n        let namespace = backend_ref\n            .namespace\n            .as_deref()\n            .unwrap_or(default_namespace);\n        BackendReference::Service(ResourceId::new(\n            namespace.to_string(),\n            backend_ref.name.clone(),\n        ))\n    } else if backend_ref_targets_kind::<policy::EgressNetwork>(backend_ref) {\n        let namespace = backend_ref\n            .namespace\n            .as_deref()\n            .unwrap_or(default_namespace);\n        BackendReference::EgressNetwork(ResourceId::new(\n            namespace.to_string(),\n            backend_ref.name.clone(),\n        ))\n    } else {\n        BackendReference::Unknown\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use crate::index::POLICY_API_GROUP;\n    use linkerd_policy_controller_k8s_api as k8s;\n\n    #[test]\n    fn backendrefs_from_route() {\n        let route = gateway::GRPCRoute {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(\"foo\".to_string()),\n                name: Some(\"foo\".to_string()),\n                ..Default::default()\n            },\n            spec: gateway::GRPCRouteSpec {\n                parent_refs: None,\n                hostnames: None,\n                rules: Some(vec![\n                    gateway::GRPCRouteRules {\n                        name: None,\n                        matches: None,\n                        filters: None,\n                        backend_refs: Some(vec![\n                            gateway::GRPCRouteRulesBackendRefs {\n                                group: None,\n                                kind: None,\n                                name: \"ref-1\".to_string(),\n                                namespace: Some(\"default\".to_string()),\n                                port: None,\n                                filters: None,\n                                weight: None,\n                            },\n                            gateway::GRPCRouteRulesBackendRefs {\n                                group: None,\n                                kind: None,\n                                name: \"ref-2\".to_string(),\n                                namespace: None,\n                                port: None,\n                                filters: None,\n                                weight: None,\n                            },\n                        ]),\n                        session_persistence: None,\n                    },\n                    gateway::GRPCRouteRules {\n                        name: None,\n                        matches: None,\n                        filters: None,\n                        backend_refs: Some(vec![gateway::GRPCRouteRulesBackendRefs {\n                            group: Some(\"Core\".to_string()),\n                            kind: Some(\"Service\".to_string()),\n                            name: \"ref-3\".to_string(),\n                            namespace: Some(\"default\".to_string()),\n                            port: None,\n                            filters: None,\n                            weight: None,\n                        }]),\n                        session_persistence: None,\n                    },\n                    gateway::GRPCRouteRules {\n                        name: None,\n                        matches: None,\n                        filters: None,\n                        backend_refs: None,\n                        session_persistence: None,\n                    },\n                ]),\n            },\n            status: None,\n        };\n\n        let result = make_backends(\n            route\n                .metadata\n                .namespace\n                .as_deref()\n                .expect(\"GrpcRoute must have namespace\"),\n            route\n                .spec\n                .rules\n                .into_iter()\n                .flatten()\n                .flat_map(|rule| rule.backend_refs)\n                .flatten(),\n        );\n        assert_eq!(\n            3,\n            result.len(),\n            \"expected only three BackendReferences from route\"\n        );\n        result.into_iter().for_each(|backend_ref| {\n            assert!(matches!(backend_ref, BackendReference::Service(_)));\n        })\n    }\n\n    #[test]\n    fn backendrefs_from_multiple_types() {\n        let route = gateway::GRPCRoute {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(\"default\".to_string()),\n                name: Some(\"foo\".to_string()),\n                ..Default::default()\n            },\n            spec: gateway::GRPCRouteSpec {\n                parent_refs: None,\n                hostnames: None,\n                rules: Some(vec![gateway::GRPCRouteRules {\n                    name: None,\n                    matches: None,\n                    filters: None,\n                    backend_refs: Some(vec![\n                        gateway::GRPCRouteRulesBackendRefs {\n                            group: None,\n                            kind: None,\n                            name: \"ref-1\".to_string(),\n                            namespace: None,\n                            port: None,\n                            filters: None,\n                            weight: None,\n                        },\n                        gateway::GRPCRouteRulesBackendRefs {\n                            group: Some(POLICY_API_GROUP.to_string()),\n                            kind: Some(\"EgressNetwork\".to_string()),\n                            name: \"ref-3\".to_string(),\n                            namespace: None,\n                            port: Some(555),\n                            filters: None,\n                            weight: None,\n                        },\n                        gateway::GRPCRouteRulesBackendRefs {\n                            group: Some(POLICY_API_GROUP.to_string()),\n                            kind: Some(\"Server\".to_string()),\n                            name: \"ref-2\".to_string(),\n                            namespace: None,\n                            port: None,\n                            filters: None,\n                            weight: None,\n                        },\n                    ]),\n                    session_persistence: None,\n                }]),\n            },\n            status: None,\n        };\n\n        let result = make_backends(\n            route\n                .metadata\n                .namespace\n                .as_deref()\n                .expect(\"GrpcRoute must have namespace\"),\n            route\n                .spec\n                .rules\n                .into_iter()\n                .flatten()\n                .flat_map(|rule| rule.backend_refs)\n                .flatten(),\n        );\n        assert_eq!(\n            3,\n            result.len(),\n            \"expected only two BackendReferences from route\"\n        );\n        let mut iter = result.into_iter();\n        let service = iter.next().unwrap();\n        assert!(matches!(service, BackendReference::Service(_)));\n        let egress_net = iter.next().unwrap();\n        assert!(matches!(egress_net, BackendReference::EgressNetwork(_)));\n        let unknown = iter.next().unwrap();\n        assert!(matches!(unknown, BackendReference::Unknown))\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/routes/http.rs",
    "content": "use super::{BackendReference, ParentReference, ResourceId};\nuse anyhow::Result;\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s, gateway,\n    policy::{\n        self,\n        httproute::{backend_ref_targets_kind, parent_ref_targets_kind},\n    },\n};\n\npub(crate) fn make_backends(\n    namespace: &str,\n    backends: impl Iterator<Item = gateway::HTTPRouteRulesBackendRefs>,\n) -> Vec<BackendReference> {\n    backends.map(|br| to_backend_ref(&br, namespace)).collect()\n}\n\npub(crate) fn make_parents(\n    namespace: &str,\n    parents: &[gateway::HTTPRouteParentRefs],\n) -> Vec<ParentReference> {\n    parents\n        .iter()\n        .filter_map(|pr| {\n            to_parent_ref(pr, namespace)\n                .inspect_err(|error| tracing::error!(?error, \"failed to make parent reference\"))\n                .ok()\n        })\n        .collect()\n}\n\nfn to_parent_ref(\n    parent_ref: &gateway::HTTPRouteParentRefs,\n    default_namespace: &str,\n) -> Result<ParentReference> {\n    if parent_ref_targets_kind::<policy::Server>(parent_ref) {\n        // If the parent reference does not have a namespace, default to using\n        // the route's namespace.\n        let namespace = parent_ref.namespace.as_deref().unwrap_or(default_namespace);\n        Result::Ok(ParentReference::Server(ResourceId::new(\n            namespace.to_string(),\n            parent_ref.name.clone(),\n        )))\n    } else if parent_ref_targets_kind::<k8s::Service>(parent_ref) {\n        // If the parent reference does not have a namespace, default to using\n        // the route's namespace.\n        let namespace = parent_ref.namespace.as_deref().unwrap_or(default_namespace);\n        Result::Ok(ParentReference::Service(\n            ResourceId::new(namespace.to_string(), parent_ref.name.clone()),\n            parent_ref.port.map(|p| p.try_into()).transpose()?,\n        ))\n    } else if parent_ref_targets_kind::<policy::EgressNetwork>(parent_ref) {\n        // If the parent reference does not have a namespace, default to using\n        // the route's namespace.\n        let namespace = parent_ref.namespace.as_deref().unwrap_or(default_namespace);\n        Result::Ok(ParentReference::EgressNetwork(\n            ResourceId::new(namespace.to_string(), parent_ref.name.clone()),\n            parent_ref.port.map(|p| p.try_into()).transpose()?,\n        ))\n    } else {\n        Result::Ok(ParentReference::UnknownKind)\n    }\n}\n\nfn to_backend_ref(\n    backend_ref: &gateway::HTTPRouteRulesBackendRefs,\n    default_namespace: &str,\n) -> BackendReference {\n    if backend_ref_targets_kind::<k8s::Service>(backend_ref) {\n        let namespace = backend_ref\n            .namespace\n            .as_deref()\n            .unwrap_or(default_namespace);\n        let name = backend_ref.name.clone();\n        BackendReference::Service(ResourceId::new(namespace.to_string(), name))\n    } else if backend_ref_targets_kind::<policy::EgressNetwork>(backend_ref) {\n        let namespace = backend_ref\n            .namespace\n            .as_deref()\n            .unwrap_or(default_namespace);\n        let name = backend_ref.name.clone();\n        BackendReference::EgressNetwork(ResourceId::new(namespace.to_string(), name))\n    } else {\n        BackendReference::Unknown\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use crate::index::POLICY_API_GROUP;\n    use linkerd_policy_controller_k8s_api::{self as k8s_core_api, policy};\n\n    #[test]\n    fn backendrefs_from_route() {\n        let route = policy::HttpRoute {\n            metadata: k8s_core_api::ObjectMeta {\n                namespace: Some(\"foo\".to_string()),\n                name: Some(\"foo\".to_string()),\n                ..Default::default()\n            },\n            spec: policy::HttpRouteSpec {\n                parent_refs: None,\n                hostnames: None,\n                rules: Some(vec![\n                    policy::httproute::HttpRouteRule {\n                        matches: None,\n                        filters: None,\n                        backend_refs: Some(vec![\n                            gateway::HTTPRouteRulesBackendRefs {\n                                weight: None,\n                                group: None,\n                                kind: None,\n                                name: \"ref-1\".to_string(),\n                                namespace: Some(\"default\".to_string()),\n                                port: None,\n                                filters: None,\n                            },\n                            gateway::HTTPRouteRulesBackendRefs {\n                                weight: None,\n                                group: None,\n                                kind: None,\n                                name: \"ref-2\".to_string(),\n                                namespace: None,\n                                port: None,\n                                filters: None,\n                            },\n                        ]),\n                        timeouts: None,\n                    },\n                    policy::httproute::HttpRouteRule {\n                        matches: None,\n                        filters: None,\n                        backend_refs: Some(vec![gateway::HTTPRouteRulesBackendRefs {\n                            weight: None,\n                            group: Some(\"Core\".to_string()),\n                            kind: Some(\"Service\".to_string()),\n                            name: \"ref-3\".to_string(),\n                            namespace: Some(\"default\".to_string()),\n                            port: None,\n                            filters: None,\n                        }]),\n                        timeouts: None,\n                    },\n                    policy::httproute::HttpRouteRule {\n                        matches: None,\n                        filters: None,\n                        backend_refs: None,\n                        timeouts: None,\n                    },\n                ]),\n            },\n            status: None,\n        };\n\n        let result = make_backends(\n            route\n                .metadata\n                .namespace\n                .as_deref()\n                .expect(\"HttpRoute must have namespace\"),\n            route\n                .spec\n                .rules\n                .into_iter()\n                .flatten()\n                .flat_map(|rule| rule.backend_refs)\n                .flatten(),\n        );\n        assert_eq!(\n            3,\n            result.len(),\n            \"expected only three BackendReferences from route\"\n        );\n        result.into_iter().for_each(|backend_ref| {\n            assert!(matches!(backend_ref, BackendReference::Service(_)));\n        })\n    }\n\n    #[test]\n    fn backendrefs_from_multiple_types() {\n        let route = policy::HttpRoute {\n            metadata: k8s_core_api::ObjectMeta {\n                namespace: Some(\"default\".to_string()),\n                name: Some(\"foo\".to_string()),\n                ..Default::default()\n            },\n            spec: policy::HttpRouteSpec {\n                parent_refs: None,\n                hostnames: None,\n                rules: Some(vec![policy::httproute::HttpRouteRule {\n                    matches: None,\n                    filters: None,\n                    backend_refs: Some(vec![\n                        gateway::HTTPRouteRulesBackendRefs {\n                            weight: None,\n                            group: None,\n                            kind: None,\n                            name: \"ref-1\".to_string(),\n                            namespace: None,\n                            port: None,\n                            filters: None,\n                        },\n                        gateway::HTTPRouteRulesBackendRefs {\n                            weight: None,\n                            group: Some(POLICY_API_GROUP.to_string()),\n                            kind: Some(\"EgressNetwork\".to_string()),\n                            name: \"ref-3\".to_string(),\n                            namespace: None,\n                            port: Some(555),\n                            filters: None,\n                        },\n                        gateway::HTTPRouteRulesBackendRefs {\n                            weight: None,\n                            group: Some(POLICY_API_GROUP.to_string()),\n                            kind: Some(\"Server\".to_string()),\n                            name: \"ref-2\".to_string(),\n                            namespace: None,\n                            port: None,\n                            filters: None,\n                        },\n                    ]),\n                    timeouts: None,\n                }]),\n            },\n            status: None,\n        };\n\n        let result = make_backends(\n            route\n                .metadata\n                .namespace\n                .as_deref()\n                .expect(\"HttpRoute must have namespace\"),\n            route\n                .spec\n                .rules\n                .into_iter()\n                .flatten()\n                .flat_map(|rule| rule.backend_refs)\n                .flatten(),\n        );\n        assert_eq!(\n            3,\n            result.len(),\n            \"expected only two BackendReferences from route\"\n        );\n        let mut iter = result.into_iter();\n        let service = iter.next().unwrap();\n        assert!(matches!(service, BackendReference::Service(_)));\n        let egress_net = iter.next().unwrap();\n        assert!(matches!(egress_net, BackendReference::EgressNetwork(_)));\n        let unknown = iter.next().unwrap();\n        assert!(matches!(unknown, BackendReference::Unknown))\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/routes/tcp.rs",
    "content": "use super::{BackendReference, ParentReference, ResourceId};\nuse anyhow::Result;\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s, gateway,\n    policy::{\n        self,\n        tcproute::{backend_ref_targets_kind, parent_ref_targets_kind},\n    },\n};\n\npub(crate) fn make_backends(\n    namespace: &str,\n    backends: impl Iterator<Item = gateway::TCPRouteRulesBackendRefs>,\n) -> Vec<BackendReference> {\n    backends.map(|br| to_backend_ref(&br, namespace)).collect()\n}\n\npub(crate) fn make_parents(\n    namespace: &str,\n    parents: &[gateway::TCPRouteParentRefs],\n) -> Vec<ParentReference> {\n    parents\n        .iter()\n        .filter_map(|pr| {\n            to_parent_ref(pr, namespace)\n                .inspect_err(|error| tracing::error!(?error, \"failed to make parent reference\"))\n                .ok()\n        })\n        .collect()\n}\n\nfn to_parent_ref(\n    parent_ref: &gateway::TCPRouteParentRefs,\n    default_namespace: &str,\n) -> Result<ParentReference> {\n    if parent_ref_targets_kind::<policy::Server>(parent_ref) {\n        // If the parent reference does not have a namespace, default to using\n        // the route's namespace.\n        let namespace = parent_ref.namespace.as_deref().unwrap_or(default_namespace);\n        Result::Ok(ParentReference::Server(ResourceId::new(\n            namespace.to_string(),\n            parent_ref.name.clone(),\n        )))\n    } else if parent_ref_targets_kind::<k8s::Service>(parent_ref) {\n        // If the parent reference does not have a namespace, default to using\n        // the route's namespace.\n        let namespace = parent_ref.namespace.as_deref().unwrap_or(default_namespace);\n        Result::Ok(ParentReference::Service(\n            ResourceId::new(namespace.to_string(), parent_ref.name.clone()),\n            parent_ref.port.map(|p| p.try_into()).transpose()?,\n        ))\n    } else if parent_ref_targets_kind::<policy::EgressNetwork>(parent_ref) {\n        // If the parent reference does not have a namespace, default to using\n        // the route's namespace.\n        let namespace = parent_ref.namespace.as_deref().unwrap_or(default_namespace);\n        Result::Ok(ParentReference::EgressNetwork(\n            ResourceId::new(namespace.to_string(), parent_ref.name.clone()),\n            parent_ref.port.map(|p| p.try_into()).transpose()?,\n        ))\n    } else {\n        Result::Ok(ParentReference::UnknownKind)\n    }\n}\n\nfn to_backend_ref(\n    backend_ref: &gateway::TCPRouteRulesBackendRefs,\n    default_namespace: &str,\n) -> BackendReference {\n    if backend_ref_targets_kind::<k8s::Service>(backend_ref) {\n        let namespace = backend_ref\n            .namespace\n            .as_deref()\n            .unwrap_or(default_namespace);\n        BackendReference::Service(ResourceId::new(\n            namespace.to_string(),\n            backend_ref.name.clone(),\n        ))\n    } else if backend_ref_targets_kind::<policy::EgressNetwork>(backend_ref) {\n        let namespace = backend_ref\n            .namespace\n            .as_deref()\n            .unwrap_or(default_namespace);\n        BackendReference::EgressNetwork(ResourceId::new(\n            namespace.to_string(),\n            backend_ref.name.clone(),\n        ))\n    } else {\n        BackendReference::Unknown\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use crate::index::POLICY_API_GROUP;\n\n    #[test]\n    fn backendrefs_from_route() {\n        let route = gateway::TCPRoute {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(\"foo\".to_string()),\n                name: Some(\"foo\".to_string()),\n                ..Default::default()\n            },\n            spec: gateway::TCPRouteSpec {\n                parent_refs: None,\n                rules: vec![\n                    gateway::TCPRouteRules {\n                        name: None,\n                        backend_refs: Some(vec![\n                            gateway::TCPRouteRulesBackendRefs {\n                                weight: None,\n                                group: None,\n                                kind: None,\n                                name: \"ref-1\".to_string(),\n                                namespace: Some(\"default\".to_string()),\n                                port: None,\n                            },\n                            gateway::TCPRouteRulesBackendRefs {\n                                weight: None,\n                                group: None,\n                                kind: None,\n                                name: \"ref-2\".to_string(),\n                                namespace: None,\n                                port: None,\n                            },\n                        ]),\n                    },\n                    gateway::TCPRouteRules {\n                        name: None,\n                        backend_refs: Some(vec![gateway::TCPRouteRulesBackendRefs {\n                            weight: None,\n                            group: Some(\"Core\".to_string()),\n                            kind: Some(\"Service\".to_string()),\n                            name: \"ref-3\".to_string(),\n                            namespace: Some(\"default\".to_string()),\n                            port: None,\n                        }]),\n                    },\n                ],\n            },\n            status: None,\n        };\n\n        let result: Vec<_> = route\n            .spec\n            .rules\n            .into_iter()\n            .flat_map(|rule| rule.backend_refs)\n            .flatten()\n            .map(|br| to_backend_ref(&br, \"default\"))\n            .collect();\n\n        assert_eq!(\n            3,\n            result.len(),\n            \"expected only three BackendReferences from route\"\n        );\n        result.into_iter().for_each(|backend_ref| {\n            assert!(matches!(backend_ref, BackendReference::Service(_)));\n        })\n    }\n\n    #[test]\n    fn backendrefs_from_multiple_types() {\n        let route = gateway::TCPRoute {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(\"default\".to_string()),\n                name: Some(\"foo\".to_string()),\n                ..Default::default()\n            },\n            spec: gateway::TCPRouteSpec {\n                parent_refs: None,\n                rules: vec![gateway::TCPRouteRules {\n                    name: None,\n                    backend_refs: Some(vec![\n                        gateway::TCPRouteRulesBackendRefs {\n                            weight: None,\n                            group: None,\n                            kind: None,\n                            name: \"ref-1\".to_string(),\n                            namespace: None,\n                            port: None,\n                        },\n                        gateway::TCPRouteRulesBackendRefs {\n                            weight: None,\n                            group: Some(POLICY_API_GROUP.to_string()),\n                            kind: Some(\"EgressNetwork\".to_string()),\n                            name: \"ref-3\".to_string(),\n                            namespace: None,\n                            port: Some(555),\n                        },\n                        gateway::TCPRouteRulesBackendRefs {\n                            weight: None,\n                            group: Some(POLICY_API_GROUP.to_string()),\n                            kind: Some(\"Server\".to_string()),\n                            name: \"ref-2\".to_string(),\n                            namespace: None,\n                            port: None,\n                        },\n                    ]),\n                }],\n            },\n            status: None,\n        };\n\n        let result: Vec<_> = route\n            .spec\n            .rules\n            .into_iter()\n            .flat_map(|rule| rule.backend_refs)\n            .flatten()\n            .map(|br| to_backend_ref(&br, \"default\"))\n            .collect();\n\n        assert_eq!(\n            3,\n            result.len(),\n            \"expected only two BackendReferences from route\"\n        );\n        let mut iter = result.into_iter();\n        let service = iter.next().unwrap();\n        assert!(matches!(service, BackendReference::Service(_)));\n        let egress_net = iter.next().unwrap();\n        assert!(matches!(egress_net, BackendReference::EgressNetwork(_)));\n        let unknown = iter.next().unwrap();\n        assert!(matches!(unknown, BackendReference::Unknown))\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/routes/tls.rs",
    "content": "use super::{BackendReference, ParentReference, ResourceId};\nuse anyhow::Result;\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s, gateway,\n    policy::{\n        self,\n        tlsroute::{backend_ref_targets_kind, parent_ref_targets_kind},\n    },\n};\n\npub(crate) fn make_backends(\n    namespace: &str,\n    backends: impl Iterator<Item = gateway::TLSRouteRulesBackendRefs>,\n) -> Vec<BackendReference> {\n    backends.map(|br| to_backend_ref(&br, namespace)).collect()\n}\n\npub(crate) fn make_parents(\n    namespace: &str,\n    parents: &[gateway::TLSRouteParentRefs],\n) -> Vec<ParentReference> {\n    parents\n        .iter()\n        .filter_map(|pr| {\n            to_parent_ref(pr, namespace)\n                .inspect_err(|error| tracing::error!(?error, \"failed to make parent reference\"))\n                .ok()\n        })\n        .collect()\n}\n\nfn to_parent_ref(\n    parent_ref: &gateway::TLSRouteParentRefs,\n    default_namespace: &str,\n) -> Result<ParentReference> {\n    if parent_ref_targets_kind::<policy::Server>(parent_ref) {\n        // If the parent reference does not have a namespace, default to using\n        // the route's namespace.\n        let namespace = parent_ref.namespace.as_deref().unwrap_or(default_namespace);\n        Result::Ok(ParentReference::Server(ResourceId::new(\n            namespace.to_string(),\n            parent_ref.name.clone(),\n        )))\n    } else if parent_ref_targets_kind::<k8s::Service>(parent_ref) {\n        // If the parent reference does not have a namespace, default to using\n        // the route's namespace.\n        let namespace = parent_ref.namespace.as_deref().unwrap_or(default_namespace);\n        Result::Ok(ParentReference::Service(\n            ResourceId::new(namespace.to_string(), parent_ref.name.clone()),\n            parent_ref.port.map(|p| p.try_into()).transpose()?,\n        ))\n    } else if parent_ref_targets_kind::<policy::EgressNetwork>(parent_ref) {\n        // If the parent reference does not have a namespace, default to using\n        // the route's namespace.\n        let namespace = parent_ref.namespace.as_deref().unwrap_or(default_namespace);\n        Result::Ok(ParentReference::EgressNetwork(\n            ResourceId::new(namespace.to_string(), parent_ref.name.clone()),\n            parent_ref.port.map(|p| p.try_into()).transpose()?,\n        ))\n    } else {\n        Result::Ok(ParentReference::UnknownKind)\n    }\n}\n\nfn to_backend_ref(\n    backend_ref: &gateway::TLSRouteRulesBackendRefs,\n    default_namespace: &str,\n) -> BackendReference {\n    if backend_ref_targets_kind::<k8s::Service>(backend_ref) {\n        let namespace = backend_ref\n            .namespace\n            .as_deref()\n            .unwrap_or(default_namespace);\n        BackendReference::Service(ResourceId::new(\n            namespace.to_string(),\n            backend_ref.name.clone(),\n        ))\n    } else if backend_ref_targets_kind::<policy::EgressNetwork>(backend_ref) {\n        let namespace = backend_ref\n            .namespace\n            .as_deref()\n            .unwrap_or(default_namespace);\n        BackendReference::EgressNetwork(ResourceId::new(\n            namespace.to_string(),\n            backend_ref.name.clone(),\n        ))\n    } else {\n        BackendReference::Unknown\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use crate::index::POLICY_API_GROUP;\n\n    #[test]\n    fn backendrefs_from_route() {\n        let route = gateway::TLSRoute {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(\"foo\".to_string()),\n                name: Some(\"foo\".to_string()),\n                ..Default::default()\n            },\n            spec: gateway::TLSRouteSpec {\n                parent_refs: None,\n                hostnames: None,\n                rules: vec![\n                    gateway::TLSRouteRules {\n                        name: None,\n                        backend_refs: Some(vec![\n                            gateway::TLSRouteRulesBackendRefs {\n                                weight: None,\n                                group: None,\n                                kind: None,\n                                name: \"ref-1\".to_string(),\n                                namespace: Some(\"default\".to_string()),\n                                port: None,\n                            },\n                            gateway::TLSRouteRulesBackendRefs {\n                                weight: None,\n                                group: None,\n                                kind: None,\n                                name: \"ref-2\".to_string(),\n                                namespace: None,\n                                port: None,\n                            },\n                        ]),\n                    },\n                    gateway::TLSRouteRules {\n                        name: None,\n                        backend_refs: Some(vec![gateway::TLSRouteRulesBackendRefs {\n                            weight: None,\n                            group: Some(\"Core\".to_string()),\n                            kind: Some(\"Service\".to_string()),\n                            name: \"ref-3\".to_string(),\n                            namespace: Some(\"default\".to_string()),\n                            port: None,\n                        }]),\n                    },\n                ],\n            },\n            status: None,\n        };\n\n        let result: Vec<_> = route\n            .spec\n            .rules\n            .into_iter()\n            .flat_map(|rule| rule.backend_refs)\n            .flatten()\n            .map(|br| to_backend_ref(&br, \"default\"))\n            .collect();\n\n        assert_eq!(\n            3,\n            result.len(),\n            \"expected only three BackendReferences from route\"\n        );\n        result.into_iter().for_each(|backend_ref| {\n            assert!(matches!(backend_ref, BackendReference::Service(_)));\n        })\n    }\n\n    #[test]\n    fn backendrefs_from_multiple_types() {\n        let route = gateway::TLSRoute {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(\"default\".to_string()),\n                name: Some(\"foo\".to_string()),\n                ..Default::default()\n            },\n            spec: gateway::TLSRouteSpec {\n                parent_refs: None,\n                hostnames: None,\n                rules: vec![gateway::TLSRouteRules {\n                    name: None,\n                    backend_refs: Some(vec![\n                        gateway::TLSRouteRulesBackendRefs {\n                            weight: None,\n                            group: None,\n                            kind: None,\n                            name: \"ref-1\".to_string(),\n                            namespace: None,\n                            port: None,\n                        },\n                        gateway::TLSRouteRulesBackendRefs {\n                            weight: None,\n                            group: Some(POLICY_API_GROUP.to_string()),\n                            kind: Some(\"EgressNetwork\".to_string()),\n                            name: \"ref-3\".to_string(),\n                            namespace: None,\n                            port: Some(555),\n                        },\n                        gateway::TLSRouteRulesBackendRefs {\n                            weight: None,\n                            group: Some(POLICY_API_GROUP.to_string()),\n                            kind: Some(\"Server\".to_string()),\n                            name: \"ref-2\".to_string(),\n                            namespace: None,\n                            port: None,\n                        },\n                    ]),\n                }],\n            },\n            status: None,\n        };\n\n        let result: Vec<_> = route\n            .spec\n            .rules\n            .into_iter()\n            .flat_map(|rule| rule.backend_refs)\n            .flatten()\n            .map(|br| to_backend_ref(&br, \"default\"))\n            .collect();\n\n        assert_eq!(\n            3,\n            result.len(),\n            \"expected only two BackendReferences from route\"\n        );\n        let mut iter = result.into_iter();\n        let service = iter.next().unwrap();\n        assert!(matches!(service, BackendReference::Service(_)));\n        let egress_net = iter.next().unwrap();\n        assert!(matches!(egress_net, BackendReference::EgressNetwork(_)));\n        let unknown = iter.next().unwrap();\n        assert!(matches!(unknown, BackendReference::Unknown))\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/routes.rs",
    "content": "use crate::resource_id::ResourceId;\n\npub(crate) mod grpc;\npub(crate) mod http;\npub(crate) mod tcp;\npub(crate) mod tls;\n\n/// Represents an xRoute's parent reference from its spec.\n///\n/// This is separate from the policy controller index's `InboundParentRef`\n/// because it does not validate that the parent reference is not in another\n/// namespace. This is something that should be relaxed in the future in the\n/// policy controller's index, and we could then consider consolidating these\n/// types into a single shared lib.\n#[derive(Clone, Eq, PartialEq, Debug)]\npub enum ParentReference {\n    Server(ResourceId),\n    Service(ResourceId, Option<u16>),\n    EgressNetwork(ResourceId, Option<u16>),\n    UnknownKind,\n}\n\n#[derive(Clone, Eq, PartialEq, Debug)]\npub enum BackendReference {\n    Service(ResourceId),\n    EgressNetwork(ResourceId),\n    Unknown,\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/service.rs",
    "content": "use linkerd_policy_controller_k8s_api as k8s_core_api;\n\n#[derive(Default)]\npub(crate) struct Service {\n    cluster_ip: Option<String>,\n    type_: Option<String>,\n}\n\nimpl Service {\n    pub(crate) fn valid_parent_service(&self) -> bool {\n        let cluster_ip = self\n            .cluster_ip\n            .as_ref()\n            .filter(|cip| !cip.eq_ignore_ascii_case(\"none\"))\n            .is_some();\n        let external_name = self.type_.as_deref() == Some(\"ExternalName\");\n        cluster_ip && !external_name\n    }\n}\n\nimpl From<k8s_core_api::Service> for Service {\n    fn from(svc: k8s_core_api::Service) -> Self {\n        svc.spec\n            .map(|spec| Self {\n                cluster_ip: spec.cluster_ip,\n                type_: spec.type_,\n            })\n            .unwrap_or_default()\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/tests/conflict.rs",
    "content": "#[cfg(test)]\nuse crate::{\n    index::{GRPCRouteRef, HTTPRouteRef, SharedIndex, TCPRouteRef, TLSRouteRef},\n    resource_id::{NamespaceGroupKindName, ResourceId},\n    routes,\n    tests::default_cluster_networks,\n    Index, IndexMetrics,\n};\nuse chrono::{DateTime, Utc};\nuse linkerd_policy_controller_core::routes::GroupKindName;\nuse linkerd_policy_controller_k8s_api::{gateway, Resource};\nuse std::{sync::Arc, vec};\nuse tokio::sync::{mpsc, watch};\n\nenum ParentRefType {\n    Service,\n    EgressNetwork,\n}\n\nfn make_index() -> SharedIndex {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, _) = mpsc::channel(10000);\n    Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    )\n}\n\nfn grpc_route_no_conflict(p: ParentRefType) {\n    let index = make_index();\n\n    let parent = match p {\n        ParentRefType::Service => routes::ParentReference::Service(\n            ResourceId::new(\"ns\".to_string(), \"service\".to_string()),\n            None,\n        ),\n\n        ParentRefType::EgressNetwork => routes::ParentReference::EgressNetwork(\n            ResourceId::new(\"ns\".to_string(), \"egress-net\".to_string()),\n            None,\n        ),\n    };\n\n    index.write().update_grpc_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::GRPCRoute::group(&()),\n                kind: gateway::GRPCRoute::kind(&()),\n                name: \"grpc-1\".into(),\n            },\n        },\n        &GRPCRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n    index.write().update_http_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::HTTPRoute::group(&()),\n                kind: gateway::HTTPRoute::kind(&()),\n                name: \"http-1\".into(),\n            },\n        },\n        &HTTPRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n    index.write().update_tls_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::TLSRoute::group(&()),\n                kind: gateway::TLSRoute::kind(&()),\n                name: \"tls-1\".into(),\n            },\n        },\n        &TLSRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n    index.write().update_tcp_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::TCPRoute::group(&()),\n                kind: gateway::TCPRoute::kind(&()),\n                name: \"tcp-1\".into(),\n            },\n        },\n        &TCPRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n\n    assert!(!index\n        .read()\n        .parent_has_conflicting_routes(&parent, \"GRPCRoute\"));\n}\n\nfn http_route_conflict_grpc(p: ParentRefType) {\n    let index = make_index();\n\n    let parent = match p {\n        ParentRefType::Service => routes::ParentReference::Service(\n            ResourceId::new(\"ns\".to_string(), \"service\".to_string()),\n            None,\n        ),\n\n        ParentRefType::EgressNetwork => routes::ParentReference::EgressNetwork(\n            ResourceId::new(\"ns\".to_string(), \"egress-net\".to_string()),\n            None,\n        ),\n    };\n\n    index.write().update_grpc_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::GRPCRoute::group(&()),\n                kind: gateway::GRPCRoute::kind(&()),\n                name: \"grpc-1\".into(),\n            },\n        },\n        &GRPCRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n\n    assert!(index\n        .read()\n        .parent_has_conflicting_routes(&parent, \"HTTPRoute\"));\n}\n\nfn http_route_no_conflict(p: ParentRefType) {\n    let index = make_index();\n\n    let parent = match p {\n        ParentRefType::Service => routes::ParentReference::Service(\n            ResourceId::new(\"ns\".to_string(), \"service\".to_string()),\n            None,\n        ),\n\n        ParentRefType::EgressNetwork => routes::ParentReference::EgressNetwork(\n            ResourceId::new(\"ns\".to_string(), \"egress-net\".to_string()),\n            None,\n        ),\n    };\n\n    index.write().update_http_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::HTTPRoute::group(&()),\n                kind: gateway::HTTPRoute::kind(&()),\n                name: \"http-1\".into(),\n            },\n        },\n        &HTTPRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n    index.write().update_tls_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::TLSRoute::group(&()),\n                kind: gateway::TLSRoute::kind(&()),\n                name: \"tls-1\".into(),\n            },\n        },\n        &TLSRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n    index.write().update_tcp_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::TCPRoute::group(&()),\n                kind: gateway::TCPRoute::kind(&()),\n                name: \"tcp-1\".into(),\n            },\n        },\n        &TCPRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n\n    assert!(!index\n        .read()\n        .parent_has_conflicting_routes(&parent, \"HTTPRoute\"));\n}\n\nfn tls_route_conflict_http(p: ParentRefType) {\n    let index = make_index();\n\n    let parent = match p {\n        ParentRefType::Service => routes::ParentReference::Service(\n            ResourceId::new(\"ns\".to_string(), \"service\".to_string()),\n            None,\n        ),\n\n        ParentRefType::EgressNetwork => routes::ParentReference::EgressNetwork(\n            ResourceId::new(\"ns\".to_string(), \"egress-net\".to_string()),\n            None,\n        ),\n    };\n\n    index.write().update_http_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::HTTPRoute::group(&()),\n                kind: gateway::HTTPRoute::kind(&()),\n                name: \"http-1\".into(),\n            },\n        },\n        &HTTPRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n\n    assert!(index\n        .read()\n        .parent_has_conflicting_routes(&parent, \"TLSRoute\"));\n}\n\nfn tls_route_conflict_grpc(p: ParentRefType) {\n    let index = make_index();\n\n    let parent = match p {\n        ParentRefType::Service => routes::ParentReference::Service(\n            ResourceId::new(\"ns\".to_string(), \"service\".to_string()),\n            None,\n        ),\n\n        ParentRefType::EgressNetwork => routes::ParentReference::EgressNetwork(\n            ResourceId::new(\"ns\".to_string(), \"egress-net\".to_string()),\n            None,\n        ),\n    };\n\n    index.write().update_grpc_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::GRPCRoute::group(&()),\n                kind: gateway::GRPCRoute::kind(&()),\n                name: \"grpc-1\".into(),\n            },\n        },\n        &GRPCRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n\n    assert!(index\n        .read()\n        .parent_has_conflicting_routes(&parent, \"TLSRoute\"));\n}\n\nfn tls_route_no_conflict(p: ParentRefType) {\n    let index = make_index();\n\n    let parent = match p {\n        ParentRefType::Service => routes::ParentReference::Service(\n            ResourceId::new(\"ns\".to_string(), \"service\".to_string()),\n            None,\n        ),\n\n        ParentRefType::EgressNetwork => routes::ParentReference::EgressNetwork(\n            ResourceId::new(\"ns\".to_string(), \"egress-net\".to_string()),\n            None,\n        ),\n    };\n\n    index.write().update_tls_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::TLSRoute::group(&()),\n                kind: gateway::TLSRoute::kind(&()),\n                name: \"tls-1\".into(),\n            },\n        },\n        &TLSRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n    index.write().update_tcp_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::TCPRoute::group(&()),\n                kind: gateway::TCPRoute::kind(&()),\n                name: \"tcp-1\".into(),\n            },\n        },\n        &TCPRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n\n    assert!(!index\n        .read()\n        .parent_has_conflicting_routes(&parent, \"TLSRoute\"));\n}\n\nfn tcp_route_conflict_grpc(p: ParentRefType) {\n    let index = make_index();\n\n    let parent = match p {\n        ParentRefType::Service => routes::ParentReference::Service(\n            ResourceId::new(\"ns\".to_string(), \"service\".to_string()),\n            None,\n        ),\n\n        ParentRefType::EgressNetwork => routes::ParentReference::EgressNetwork(\n            ResourceId::new(\"ns\".to_string(), \"egress-net\".to_string()),\n            None,\n        ),\n    };\n\n    index.write().update_grpc_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::GRPCRoute::group(&()),\n                kind: gateway::GRPCRoute::kind(&()),\n                name: \"grpc-1\".into(),\n            },\n        },\n        &GRPCRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n\n    assert!(index\n        .read()\n        .parent_has_conflicting_routes(&parent, \"TCPRoute\"));\n}\n\nfn tcp_route_conflict_http(p: ParentRefType) {\n    let index = make_index();\n\n    let parent = match p {\n        ParentRefType::Service => routes::ParentReference::Service(\n            ResourceId::new(\"ns\".to_string(), \"service\".to_string()),\n            None,\n        ),\n\n        ParentRefType::EgressNetwork => routes::ParentReference::EgressNetwork(\n            ResourceId::new(\"ns\".to_string(), \"egress-net\".to_string()),\n            None,\n        ),\n    };\n\n    index.write().update_http_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::HTTPRoute::group(&()),\n                kind: gateway::HTTPRoute::kind(&()),\n                name: \"http-1\".into(),\n            },\n        },\n        &HTTPRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n\n    assert!(index\n        .read()\n        .parent_has_conflicting_routes(&parent, \"TCPRoute\"));\n}\n\nfn tcp_route_conflict_tls(p: ParentRefType) {\n    let index = make_index();\n\n    let parent = match p {\n        ParentRefType::Service => routes::ParentReference::Service(\n            ResourceId::new(\"ns\".to_string(), \"service\".to_string()),\n            None,\n        ),\n\n        ParentRefType::EgressNetwork => routes::ParentReference::EgressNetwork(\n            ResourceId::new(\"ns\".to_string(), \"egress-net\".to_string()),\n            None,\n        ),\n    };\n\n    index.write().update_tls_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::TLSRoute::group(&()),\n                kind: gateway::TLSRoute::kind(&()),\n                name: \"tls-1\".into(),\n            },\n        },\n        &TLSRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n\n    assert!(index\n        .read()\n        .parent_has_conflicting_routes(&parent, \"TCPRoute\"));\n}\n\nfn tcp_route_no_conflict(p: ParentRefType) {\n    let index = make_index();\n\n    let parent = match p {\n        ParentRefType::Service => routes::ParentReference::Service(\n            ResourceId::new(\"ns\".to_string(), \"service\".to_string()),\n            None,\n        ),\n\n        ParentRefType::EgressNetwork => routes::ParentReference::EgressNetwork(\n            ResourceId::new(\"ns\".to_string(), \"egress-net\".to_string()),\n            None,\n        ),\n    };\n\n    index.write().update_tcp_route(\n        NamespaceGroupKindName {\n            namespace: \"default\".to_string(),\n            gkn: GroupKindName {\n                group: gateway::TCPRoute::group(&()),\n                kind: gateway::TCPRoute::kind(&()),\n                name: \"tcp-1\".into(),\n            },\n        },\n        &TCPRouteRef {\n            parents: vec![parent.clone()],\n            statuses: vec![],\n            backends: vec![],\n        },\n    );\n\n    assert!(!index\n        .read()\n        .parent_has_conflicting_routes(&parent, \"TCPRoute\"));\n}\n\n#[test]\nfn grpc_route_no_conflict_service() {\n    grpc_route_no_conflict(ParentRefType::Service)\n}\n\n#[test]\nfn http_route_conflict_grpc_service() {\n    http_route_conflict_grpc(ParentRefType::Service)\n}\n\n#[test]\nfn http_route_no_conflict_service() {\n    http_route_no_conflict(ParentRefType::Service)\n}\n\n#[test]\nfn tls_route_conflict_http_service() {\n    tls_route_conflict_http(ParentRefType::Service)\n}\n\n#[test]\nfn tls_route_conflict_grpc_service() {\n    tls_route_conflict_grpc(ParentRefType::Service)\n}\n\n#[test]\nfn tls_route_no_conflict_service() {\n    tls_route_no_conflict(ParentRefType::Service)\n}\n\n#[test]\nfn tcp_route_conflict_grpc_service() {\n    tcp_route_conflict_grpc(ParentRefType::Service)\n}\n\n#[test]\nfn tcp_route_conflict_http_service() {\n    tcp_route_conflict_http(ParentRefType::Service)\n}\n\n#[test]\nfn tcp_route_conflict_tls_service() {\n    tcp_route_conflict_tls(ParentRefType::Service)\n}\n\n#[test]\nfn tcp_route_no_conflict_service() {\n    tcp_route_no_conflict(ParentRefType::Service)\n}\n\n#[test]\nfn grpc_route_no_conflict_egress_network() {\n    grpc_route_no_conflict(ParentRefType::EgressNetwork)\n}\n\n#[test]\nfn http_route_conflict_grpc_egress_network() {\n    http_route_conflict_grpc(ParentRefType::EgressNetwork)\n}\n\n#[test]\nfn http_route_no_conflict_egress_network() {\n    http_route_no_conflict(ParentRefType::EgressNetwork)\n}\n\n#[test]\nfn tls_route_conflict_http_egress_network() {\n    tls_route_conflict_http(ParentRefType::EgressNetwork)\n}\n\n#[test]\nfn tls_route_conflict_grpc_egress_network() {\n    tls_route_conflict_grpc(ParentRefType::EgressNetwork)\n}\n\n#[test]\nfn tls_route_no_conflict_egress_network() {\n    tls_route_no_conflict(ParentRefType::EgressNetwork)\n}\n\n#[test]\nfn tcp_route_conflict_grpc_egress_network() {\n    tcp_route_conflict_grpc(ParentRefType::EgressNetwork)\n}\n\n#[test]\nfn tcp_route_conflict_http_egress_network() {\n    tcp_route_conflict_http(ParentRefType::EgressNetwork)\n}\n\n#[test]\nfn tcp_route_conflict_tls_egress_network() {\n    tcp_route_conflict_tls(ParentRefType::EgressNetwork)\n}\n\n#[test]\nfn tcp_route_no_conflict_egress_network() {\n    tcp_route_no_conflict(ParentRefType::EgressNetwork)\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/tests/egress_network.rs",
    "content": "use crate::{\n    index::{accepted, in_cluster_net_overlap},\n    resource_id::NamespaceGroupKindName,\n    tests::default_cluster_networks,\n    Index, IndexMetrics,\n};\nuse chrono::{DateTime, Utc};\nuse kubert::index::IndexNamespacedResource;\nuse linkerd_policy_controller_core::routes::GroupKindName;\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s_core_api,\n    policy::{self as linkerd_k8s_api, EgressNetworkStatus},\n    Resource,\n};\nuse std::{sync::Arc, vec};\nuse tokio::sync::{mpsc, watch};\n\n#[test]\nfn egress_network_with_no_networks_specified() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    let id = NamespaceGroupKindName {\n        namespace: \"ns\".to_string(),\n        gkn: GroupKindName {\n            group: linkerd_k8s_api::EgressNetwork::group(&()),\n            kind: linkerd_k8s_api::EgressNetwork::kind(&()),\n            name: \"egress\".into(),\n        },\n    };\n\n    let egress_network = linkerd_k8s_api::EgressNetwork {\n        metadata: k8s_core_api::ObjectMeta {\n            name: Some(id.gkn.name.to_string()),\n            namespace: Some(id.namespace.clone()),\n            ..Default::default()\n        },\n        spec: linkerd_k8s_api::EgressNetworkSpec {\n            networks: None,\n            traffic_policy: linkerd_k8s_api::TrafficPolicy::Allow,\n        },\n        status: None,\n    };\n\n    index.write().apply(egress_network.clone());\n\n    // Create the expected update.\n    let status = EgressNetworkStatus {\n        conditions: vec![accepted()],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn egress_network_with_nonoverlapping_networks_specified() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    let id = NamespaceGroupKindName {\n        namespace: \"ns\".to_string(),\n        gkn: GroupKindName {\n            group: linkerd_k8s_api::EgressNetwork::group(&()),\n            kind: linkerd_k8s_api::EgressNetwork::kind(&()),\n            name: \"egress\".into(),\n        },\n    };\n\n    let egress_network = linkerd_k8s_api::EgressNetwork {\n        metadata: k8s_core_api::ObjectMeta {\n            name: Some(id.gkn.name.to_string()),\n            namespace: Some(id.namespace.clone()),\n            ..Default::default()\n        },\n        spec: linkerd_k8s_api::EgressNetworkSpec {\n            networks: Some(vec![linkerd_k8s_api::Network {\n                cidr: \"0.0.0.0/0\".parse().unwrap(),\n                except: Some(vec![\n                    \"10.0.0.0/8\".parse().unwrap(),\n                    \"100.64.0.0/10\".parse().unwrap(),\n                    \"172.16.0.0/12\".parse().unwrap(),\n                    \"192.168.0.0/16\".parse().unwrap(),\n                    \"fd00::/8\".parse().unwrap(),\n                ]),\n            }]),\n            traffic_policy: linkerd_k8s_api::TrafficPolicy::Allow,\n        },\n        status: None,\n    };\n\n    index.write().apply(egress_network.clone());\n\n    // Create the expected update.\n    let status = EgressNetworkStatus {\n        conditions: vec![accepted()],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn egress_network_with_overlapping_networks_specified() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    let id = NamespaceGroupKindName {\n        namespace: \"ns\".to_string(),\n        gkn: GroupKindName {\n            group: linkerd_k8s_api::EgressNetwork::group(&()),\n            kind: linkerd_k8s_api::EgressNetwork::kind(&()),\n            name: \"egress\".into(),\n        },\n    };\n\n    let egress_network = linkerd_k8s_api::EgressNetwork {\n        metadata: k8s_core_api::ObjectMeta {\n            name: Some(id.gkn.name.to_string()),\n            namespace: Some(id.namespace.clone()),\n            ..Default::default()\n        },\n        spec: linkerd_k8s_api::EgressNetworkSpec {\n            networks: Some(vec![linkerd_k8s_api::Network {\n                cidr: \"0.0.0.0/0\".parse().unwrap(),\n                except: Some(vec![\n                    \"10.0.0.0/8\".parse().unwrap(),\n                    \"100.64.0.0/10\".parse().unwrap(),\n                    \"192.168.0.0/16\".parse().unwrap(),\n                ]),\n            }]),\n            traffic_policy: linkerd_k8s_api::TrafficPolicy::Allow,\n        },\n        status: None,\n    };\n\n    index.write().apply(egress_network.clone());\n\n    // Create the expected update.\n    let status = EgressNetworkStatus {\n        conditions: vec![in_cluster_net_overlap()],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/tests/ratelimit.rs",
    "content": "use crate::{\n    index::{accepted, no_matching_target, ratelimit_already_exists, SharedIndex, Update},\n    resource_id::NamespaceGroupKindName,\n    tests::{default_cluster_networks, make_server},\n    Index, IndexMetrics,\n};\nuse chrono::{DateTime, Utc};\nuse kubert::index::IndexNamespacedResource;\nuse linkerd_policy_controller_core::routes::GroupKindName;\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s_core_api,\n    policy::{self as linkerd_k8s_api},\n    Resource,\n};\nuse std::sync::Arc;\nuse tokio::sync::{\n    mpsc::{self, Receiver},\n    watch,\n};\n\n#[test]\nfn ratelimit_accepted() {\n    let (index, mut updates_rx) = make_index_updates_rx();\n\n    // create server\n    let server = make_server(\n        \"ns\",\n        \"server-1\",\n        8080,\n        vec![(\"app\", \"server\")],\n        vec![],\n        None,\n    );\n    index.write().apply(server);\n\n    // create an associated rate limit\n    let (ratelimit_id, ratelimit) = make_ratelimit(\"rl-1\".to_string(), \"server-1\".to_string());\n    index.write().apply(ratelimit);\n\n    let expected_status = linkerd_k8s_api::HttpLocalRateLimitPolicyStatus {\n        conditions: vec![accepted()],\n        target_ref: linkerd_k8s_api::LocalTargetRef {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: \"Server\".to_string(),\n            name: \"server-1\".to_string(),\n        },\n    };\n\n    let expected_patch = crate::index::make_patch(&ratelimit_id, expected_status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(ratelimit_id, update.id);\n    assert_eq!(expected_patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn ratelimit_not_accepted_no_matching_target() {\n    let (index, mut updates_rx) = make_index_updates_rx();\n\n    // create server\n    let server = make_server(\n        \"ns\",\n        \"server-1\",\n        8080,\n        vec![(\"app\", \"server\")],\n        vec![],\n        None,\n    );\n    index.write().apply(server);\n\n    // create an associated rate limit\n    let (ratelimit_id, ratelimit) = make_ratelimit(\"rl-1\".to_string(), \"server-2\".to_string());\n    index.write().apply(ratelimit);\n\n    let expected_status = linkerd_k8s_api::HttpLocalRateLimitPolicyStatus {\n        conditions: vec![no_matching_target()],\n        target_ref: linkerd_k8s_api::LocalTargetRef {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: \"Server\".to_string(),\n            name: \"server-2\".to_string(),\n        },\n    };\n\n    let expected_patch = crate::index::make_patch(&ratelimit_id, expected_status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(ratelimit_id, update.id);\n    assert_eq!(expected_patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn ratelimit_not_accepted_already_exists() {\n    let (index, mut updates_rx) = make_index_updates_rx();\n\n    // create server\n    let server = make_server(\n        \"ns\",\n        \"server-1\",\n        8080,\n        vec![(\"app\", \"server\")],\n        vec![],\n        None,\n    );\n    index.write().apply(server);\n\n    // create an associated rate limit\n    let (rl_1_id, rl_1) = make_ratelimit(\"rl-1\".to_string(), \"server-1\".to_string());\n    index.write().apply(rl_1);\n\n    let expected_status = linkerd_k8s_api::HttpLocalRateLimitPolicyStatus {\n        conditions: vec![accepted()],\n        target_ref: linkerd_k8s_api::LocalTargetRef {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: \"Server\".to_string(),\n            name: \"server-1\".to_string(),\n        },\n    };\n\n    let rl_1_expected_patch = crate::index::make_patch(&rl_1_id, expected_status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(rl_1_id, update.id);\n    assert_eq!(rl_1_expected_patch, update.patch);\n    assert!(updates_rx.try_recv().is_err());\n\n    // create another rate limit for the same server\n    let (rl_2_id, rl_2) = make_ratelimit(\"rl-2\".to_string(), \"server-1\".to_string());\n    index.write().apply(rl_2);\n\n    let expected_status = linkerd_k8s_api::HttpLocalRateLimitPolicyStatus {\n        conditions: vec![ratelimit_already_exists()],\n        target_ref: linkerd_k8s_api::LocalTargetRef {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: \"Server\".to_string(),\n            name: \"server-1\".to_string(),\n        },\n    };\n\n    let rl_2_expected_patch = crate::index::make_patch(&rl_2_id, expected_status).unwrap();\n\n    let update_1 = updates_rx.try_recv().unwrap();\n    let update_2 = updates_rx.try_recv().unwrap();\n    assert!(updates_rx.try_recv().is_err());\n\n    // we should receive updates for both rate limits in any order\n    if update_1.id == rl_1_id {\n        assert_eq!(rl_1_id, update_1.id);\n        assert_eq!(rl_1_expected_patch, update_1.patch);\n        assert_eq!(rl_2_id, update_2.id);\n        assert_eq!(rl_2_expected_patch, update_2.patch);\n    } else {\n        assert_eq!(rl_1_id, update_2.id);\n        assert_eq!(rl_1_expected_patch, update_2.patch);\n        assert_eq!(rl_2_id, update_1.id);\n        assert_eq!(rl_2_expected_patch, update_1.patch);\n    }\n}\n\nfn make_index_updates_rx() -> (SharedIndex, Receiver<Update>) {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    (index, updates_rx)\n}\n\nfn make_ratelimit(\n    name: String,\n    server: String,\n) -> (\n    NamespaceGroupKindName,\n    linkerd_k8s_api::HttpLocalRateLimitPolicy,\n) {\n    let ratelimit_id = NamespaceGroupKindName {\n        namespace: \"ns\".to_string(),\n        gkn: GroupKindName {\n            group: linkerd_k8s_api::HttpLocalRateLimitPolicy::group(&()),\n            kind: linkerd_k8s_api::HttpLocalRateLimitPolicy::kind(&()),\n            name: name.clone().into(),\n        },\n    };\n\n    let ratelimit = linkerd_k8s_api::HttpLocalRateLimitPolicy {\n        metadata: k8s_core_api::ObjectMeta {\n            name: Some(name),\n            namespace: Some(\"ns\".to_string()),\n            ..Default::default()\n        },\n        spec: linkerd_k8s_api::RateLimitPolicySpec {\n            target_ref: linkerd_k8s_api::LocalTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"Server\".to_string(),\n                name: server,\n            },\n            total: Some(linkerd_k8s_api::Limit {\n                requests_per_second: 1,\n            }),\n            identity: None,\n            overrides: None,\n        },\n        status: None,\n    };\n\n    (ratelimit_id, ratelimit)\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/tests/routes/grpc.rs",
    "content": "use crate::{\n    index::{\n        accepted, backend_not_found, invalid_backend_kind, no_matching_parent, resolved_refs,\n        route_conflicted, POLICY_API_GROUP,\n    },\n    resource_id::NamespaceGroupKindName,\n    tests::{default_cluster_networks, make_server},\n    Index, IndexMetrics,\n};\nuse chrono::{DateTime, Utc};\nuse kubert::index::IndexNamespacedResource;\nuse linkerd_policy_controller_core::{routes::GroupKindName, POLICY_CONTROLLER_NAME};\nuse linkerd_policy_controller_k8s_api::{self as k8s, gateway, policy, Resource, ResourceExt};\nuse std::sync::Arc;\nuse tokio::sync::{mpsc, watch};\n\npub(crate) fn make_parent_status(\n    namespace: impl ToString,\n    name: impl ToString,\n    type_: impl ToString,\n    status: impl ToString,\n    reason: impl ToString,\n) -> gateway::GRPCRouteStatusParents {\n    let condition = k8s::Condition {\n        message: \"\".to_string(),\n        type_: type_.to_string(),\n        observed_generation: None,\n        reason: reason.to_string(),\n        status: status.to_string(),\n        last_transition_time: k8s::Time(DateTime::<Utc>::MIN_UTC),\n    };\n    gateway::GRPCRouteStatusParents {\n        conditions: Some(vec![condition]),\n        parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n            port: None,\n            section_name: None,\n            name: name.to_string(),\n            kind: Some(\"Server\".to_string()),\n            namespace: Some(namespace.to_string()),\n            group: Some(POLICY_API_GROUP.to_string()),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n    }\n}\n\n#[test]\nfn route_with_valid_service_backends() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply one backend service\n    let backend1 = super::make_service(\"ns-0\", \"backend-1\");\n    index.write().apply(backend1.clone());\n\n    // Apply one backend service\n    let backend2 = super::make_service(\"ns-0\", \"backend-2\");\n    index.write().apply(backend2.clone());\n\n    // Apply the route.\n    let parent = gateway::GRPCRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::GRPCRoute::group(&()),\n            kind: gateway::GRPCRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        Some(vec![\n            gateway::GRPCRouteRulesBackendRefs {\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: backend1.name_unchecked(),\n                namespace: backend1.namespace(),\n                port: Some(8080),\n                weight: None,\n                filters: None,\n            },\n            gateway::GRPCRouteRulesBackendRefs {\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: backend2.name_unchecked(),\n                namespace: backend2.namespace(),\n                port: Some(8080),\n                filters: None,\n                weight: None,\n            },\n        ]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // All backends exist and can be resolved.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::GRPCRouteStatusParents {\n        parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n            group: Some(\"core\".to_string()),\n            kind: Some(\"Service\".to_string()),\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: None,\n            port: Some(8080),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_with_valid_egress_network_backend() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(parent.clone());\n\n    // Apply the route.\n    let parent = gateway::GRPCRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::GRPCRoute::group(&()),\n            kind: gateway::GRPCRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        Some(vec![gateway::GRPCRouteRulesBackendRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"EgressNetwork\".to_string()),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: Some(8080),\n            weight: None,\n            filters: None,\n        }]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // All backends exist and can be resolved.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::GRPCRouteStatusParents {\n        parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_with_invalid_service_backend() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply one backend service\n    let backend = super::make_service(\"ns-0\", \"backend-1\");\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::GRPCRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::GRPCRoute::group(&()),\n            kind: gateway::GRPCRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        Some(vec![\n            gateway::GRPCRouteRulesBackendRefs {\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: backend.name_unchecked(),\n                namespace: backend.namespace(),\n                port: Some(8080),\n                filters: None,\n                weight: None,\n            },\n            gateway::GRPCRouteRulesBackendRefs {\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: \"nonexistant-backend\".to_string(),\n                namespace: backend.namespace(),\n                port: Some(8080),\n                filters: None,\n                weight: None,\n            },\n        ]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // One of the backends does not exist so the status should be BackendNotFound.\n    let backend_condition = backend_not_found();\n    let parent_status = gateway::GRPCRouteStatusParents {\n        parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_with_egress_network_backend_different_from_parent() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"svc\", accepted());\n    index.write().apply(parent.clone());\n\n    // Apply one backend egress network\n    let backend = super::make_egress_network(\"ns-0\", \"backend-1\", accepted());\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::GRPCRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::GRPCRoute::group(&()),\n            kind: gateway::GRPCRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        Some(vec![gateway::GRPCRouteRulesBackendRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"EgressNetwork\".to_string()),\n            name: backend.name_unchecked(),\n            namespace: backend.namespace(),\n            port: Some(8080),\n            filters: None,\n            weight: None,\n        }]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = invalid_backend_kind(\n        \"EgressNetwork backend needs to be on a route that has an EgressNetwork parent\",\n    );\n    let parent_status = gateway::GRPCRouteStatusParents {\n        parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_with_egress_network_backend_and_service_parent() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply one backend egress network\n    let backend = super::make_egress_network(\"ns-0\", \"backend-1\", accepted());\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::GRPCRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::GRPCRoute::group(&()),\n            kind: gateway::GRPCRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        Some(vec![gateway::GRPCRouteRulesBackendRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"EgressNetwork\".to_string()),\n            name: backend.name_unchecked(),\n            namespace: backend.namespace(),\n            port: Some(8080),\n            filters: None,\n            weight: None,\n        }]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = invalid_backend_kind(\n        \"EgressNetwork backend needs to be on a route that has an EgressNetwork parent\",\n    );\n    let parent_status = gateway::GRPCRouteStatusParents {\n        parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_with_egress_network_parent_and_service_backend() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(parent.clone());\n\n    // Apply one backend service\n    let backend = super::make_service(\"ns-0\", \"backend-1\");\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::GRPCRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::GRPCRoute::group(&()),\n            kind: gateway::GRPCRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        Some(vec![gateway::GRPCRouteRulesBackendRefs {\n            group: Some(\"core\".to_string()),\n            kind: Some(\"Service\".to_string()),\n            name: backend.name_unchecked(),\n            namespace: backend.namespace(),\n            port: Some(8080),\n            filters: None,\n            weight: None,\n        }]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::GRPCRouteStatusParents {\n        parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_accepted_after_server_create() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            name: \"route-foo\".into(),\n            kind: gateway::GRPCRoute::kind(&()),\n            group: gateway::GRPCRoute::group(&()),\n        },\n    };\n    let parent = gateway::GRPCRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"Server\".to_string()),\n        namespace: None,\n        name: \"srv-8080\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_route(&id, parent, None);\n\n    // Apply the route.\n    index.write().apply(route);\n\n    // Create the expected update.\n    let parent_status = make_parent_status(\n        &id.namespace,\n        \"srv-8080\",\n        \"Accepted\",\n        \"False\",\n        \"NoMatchingParent\",\n    );\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The first update will be that the GRPCRoute is not accepted because the\n    // Server has been created yet.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    // Apply the server\n    let server = make_server(\n        \"ns-0\",\n        \"srv-8080\",\n        8080,\n        Some((\"app\", \"app-0\")),\n        Some((\"app\", \"app-0\")),\n        Some(policy::server::ProxyProtocol::Http2),\n    );\n    index.write().apply(server);\n\n    // Create the expected update.\n    let parent_status =\n        make_parent_status(&id.namespace, \"srv-8080\", \"Accepted\", \"True\", \"Accepted\");\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the GRPCRoute is accepted because the\n    // Server has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_accepted_after_egress_network_create() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            group: gateway::GRPCRoute::group(&()),\n            kind: gateway::GRPCRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let parent = gateway::GRPCRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: Some(\"ns-0\".to_string()),\n        name: \"egress\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_route(&id, parent.clone(), None);\n\n    // Apply the route.\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = no_matching_parent();\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::GRPCRouteStatusParents {\n        parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: parent.port,\n            section_name: parent.section_name.clone(),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition.clone()]),\n    };\n\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The first update will be that the GRPCRoute is not accepted because the\n    // EgressNetwork has not been created yet.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    // Apply the egress network\n    let egress = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(egress);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let parent_status = gateway::GRPCRouteStatusParents {\n        parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: parent.port,\n            section_name: parent.section_name.clone(),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the GRPCRoute is accepted because the\n    // EgressNetwork has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_rejected_after_server_delete() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    let server = make_server(\n        \"ns-0\",\n        \"srv-8080\",\n        8080,\n        Some((\"app\", \"app-0\")),\n        Some((\"app\", \"app-0\")),\n        Some(policy::server::ProxyProtocol::Http2),\n    );\n    index.write().apply(server);\n\n    // There should be no update since there are no GRPCRoutes yet.\n    assert!(updates_rx.try_recv().is_err());\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            name: \"route-foo\".into(),\n            kind: gateway::GRPCRoute::kind(&()),\n            group: gateway::GRPCRoute::group(&()),\n        },\n    };\n    let parent = gateway::GRPCRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"Server\".to_string()),\n        namespace: None,\n        name: \"srv-8080\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_route(&id, parent, None);\n\n    // Apply the route\n    index.write().apply(route);\n\n    // Create the expected update.\n    let parent_status =\n        make_parent_status(&id.namespace, \"srv-8080\", \"Accepted\", \"True\", \"Accepted\");\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the GRPCRoute is accepted because the\n    // Server has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    {\n        let mut index = index.write();\n        <Index as IndexNamespacedResource<policy::Server>>::delete(\n            &mut index,\n            \"ns-0\".to_string(),\n            \"srv-8080\".to_string(),\n        );\n    }\n\n    // Create the expected update.\n    let parent_status =\n        make_parent_status(\"ns-0\", \"srv-8080\", \"Accepted\", \"False\", \"NoMatchingParent\");\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The third update will be that the GRPCRoute is not accepted because the\n    // Server has been deleted.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err());\n}\n\n#[test]\nfn route_rejected_after_egress_network_delete() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    let egress = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(egress);\n\n    // There should be no update since there are no TLSRoutes yet.\n    assert!(updates_rx.try_recv().is_err());\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            group: gateway::GRPCRoute::group(&()),\n            kind: gateway::GRPCRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let parent = gateway::GRPCRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: Some(\"ns-0\".to_string()),\n        name: \"egress\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_route(&id, parent.clone(), None);\n\n    // Apply the route\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::GRPCRouteStatusParents {\n        parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: parent.port,\n            section_name: parent.section_name.clone(),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition.clone()]),\n    };\n\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the GRPCRoute is accepted because the\n    // EgressNetwork has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    {\n        let mut index = index.write();\n        <Index as IndexNamespacedResource<policy::EgressNetwork>>::delete(\n            &mut index,\n            \"ns-0\".to_string(),\n            \"egress\".to_string(),\n        );\n    }\n\n    // Create the expected update.\n    let rejected_condition = no_matching_parent();\n    let parent_status = gateway::GRPCRouteStatusParents {\n        parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![rejected_condition, backend_condition.clone()]),\n    };\n\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The third update will be that the TLSRoute is not accepted because the\n    // Server has been deleted.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err());\n}\n\n#[test]\nfn service_route_type_conflict() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    let parent = gateway::GRPCRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n\n    // Apply the HTTP route.\n    let http_id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::HTTPRoute::group(&()),\n            kind: gateway::HTTPRoute::kind(&()),\n            name: \"httproute-foo\".into(),\n        },\n    };\n    let http_route = gateway::HTTPRoute {\n        status: None,\n        metadata: k8s::ObjectMeta {\n            name: Some(http_id.gkn.name.to_string()),\n            namespace: Some(http_id.namespace.clone()),\n            creation_timestamp: Some(k8s::Time(Utc::now())),\n            ..Default::default()\n        },\n        spec: gateway::HTTPRouteSpec {\n            parent_refs: Some(vec![gateway::HTTPRouteParentRefs {\n                group: parent.group.clone(),\n                kind: parent.kind.clone(),\n                name: parent.name.clone(),\n                namespace: parent.namespace.clone(),\n                port: parent.port,\n                section_name: parent.section_name.clone(),\n            }]),\n            hostnames: None,\n            rules: Some(vec![]),\n        },\n    };\n    index.write().apply(http_route);\n\n    // Create the expected update -- HTTPRoute should be accepted\n    let accepted_condition = accepted();\n    // No backends were specified, so we have vacuously resolved them all.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::GRPCRouteStatusParents {\n        parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: parent.port,\n            section_name: parent.section_name.clone(),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition.clone(), backend_condition.clone()]),\n    };\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&http_id, status).unwrap();\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(http_id, update.id);\n    assert_eq!(patch, update.patch);\n\n    // Apply the GRPC route.\n    let grpc_id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::GRPCRoute::group(&()),\n            kind: gateway::GRPCRoute::kind(&()),\n            name: \"grpcroute-foo\".into(),\n        },\n    };\n    let route = make_route(&grpc_id, parent.clone(), None);\n    index.write().apply(route);\n\n    // Two expected updates: HTTPRoute should be rejected and GRPCRoute should be accepted\n    for _ in 0..2 {\n        let update = updates_rx.try_recv().unwrap();\n        if update.id.gkn.kind == gateway::HTTPRoute::kind(&()) {\n            let conflict_condition = route_conflicted();\n            let parent_status = gateway::GRPCRouteStatusParents {\n                parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n                    group: parent.group.clone(),\n                    kind: parent.kind.clone(),\n                    name: parent.name.clone(),\n                    namespace: parent.namespace.clone(),\n                    port: parent.port,\n                    section_name: parent.section_name.clone(),\n                },\n                controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                conditions: Some(vec![conflict_condition, backend_condition.clone()]),\n            };\n            let status = gateway::GRPCRouteStatus {\n                parents: vec![parent_status],\n            };\n            let patch = crate::index::make_patch(&http_id, status).unwrap();\n            assert_eq!(patch, update.patch);\n        } else {\n            let parent_status = gateway::GRPCRouteStatusParents {\n                parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n                    group: parent.group.clone(),\n                    kind: parent.kind.clone(),\n                    name: parent.name.clone(),\n                    namespace: parent.namespace.clone(),\n                    port: parent.port,\n                    section_name: parent.section_name.clone(),\n                },\n                controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                conditions: Some(vec![accepted_condition.clone(), backend_condition.clone()]),\n            };\n            let status = gateway::GRPCRouteStatus {\n                parents: vec![parent_status],\n            };\n            let patch = crate::index::make_patch(&grpc_id, status).unwrap();\n            assert_eq!(patch, update.patch);\n        }\n    }\n\n    // No more updates.\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn egress_network_route_type_conflict() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(parent.clone());\n\n    let parent = gateway::GRPCRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n\n    // Apply the HTTP route.\n    let http_id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::HTTPRoute::group(&()),\n            kind: gateway::HTTPRoute::kind(&()),\n            name: \"httproute-foo\".into(),\n        },\n    };\n    let http_route = gateway::HTTPRoute {\n        status: None,\n        metadata: k8s::ObjectMeta {\n            name: Some(http_id.gkn.name.to_string()),\n            namespace: Some(http_id.namespace.clone()),\n            creation_timestamp: Some(k8s::Time(Utc::now())),\n            ..Default::default()\n        },\n        spec: gateway::HTTPRouteSpec {\n            parent_refs: Some(vec![gateway::HTTPRouteParentRefs {\n                group: parent.group.clone(),\n                kind: parent.kind.clone(),\n                name: parent.name.clone(),\n                namespace: parent.namespace.clone(),\n                port: parent.port,\n                section_name: parent.section_name.clone(),\n            }]),\n            hostnames: None,\n            rules: Some(vec![]),\n        },\n    };\n    index.write().apply(http_route);\n\n    // Create the expected update -- HTTPRoute should be accepted\n    let accepted_condition = accepted();\n    // No backends were specified, so we have vacuously resolved them all.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::GRPCRouteStatusParents {\n        parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: parent.port,\n            section_name: parent.section_name.clone(),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition.clone(), backend_condition.clone()]),\n    };\n    let status = gateway::GRPCRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&http_id, status).unwrap();\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(http_id, update.id);\n    assert_eq!(patch, update.patch);\n\n    // Apply the GRPC route.\n    let grpc_id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::GRPCRoute::group(&()),\n            kind: gateway::GRPCRoute::kind(&()),\n            name: \"grpcroute-foo\".into(),\n        },\n    };\n    let route = make_route(&grpc_id, parent.clone(), None);\n    index.write().apply(route);\n\n    // Two expected updates: HTTPRoute should be rejected and GRPCRoute should be accepted\n    for _ in 0..2 {\n        let update = updates_rx.try_recv().unwrap();\n        if update.id.gkn.kind == gateway::HTTPRoute::kind(&()) {\n            let conflict_condition = route_conflicted();\n            let parent_status = gateway::GRPCRouteStatusParents {\n                parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n                    group: parent.group.clone(),\n                    kind: parent.kind.clone(),\n                    name: parent.name.clone(),\n                    namespace: parent.namespace.clone(),\n                    port: parent.port,\n                    section_name: parent.section_name.clone(),\n                },\n                controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                conditions: Some(vec![conflict_condition, backend_condition.clone()]),\n            };\n            let status = gateway::GRPCRouteStatus {\n                parents: vec![parent_status],\n            };\n            let patch = crate::index::make_patch(&http_id, status).unwrap();\n            assert_eq!(patch, update.patch);\n        } else {\n            let parent_status = gateway::GRPCRouteStatusParents {\n                parent_ref: gateway::GRPCRouteStatusParentsParentRef {\n                    group: parent.group.clone(),\n                    kind: parent.kind.clone(),\n                    name: parent.name.clone(),\n                    namespace: parent.namespace.clone(),\n                    port: parent.port,\n                    section_name: parent.section_name.clone(),\n                },\n                controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                conditions: Some(vec![accepted_condition.clone(), backend_condition.clone()]),\n            };\n            let status = gateway::GRPCRouteStatus {\n                parents: vec![parent_status],\n            };\n            let patch = crate::index::make_patch(&grpc_id, status).unwrap();\n            assert_eq!(patch, update.patch);\n        }\n    }\n\n    // No more updates.\n    assert!(updates_rx.try_recv().is_err())\n}\n\nfn make_route(\n    id: &NamespaceGroupKindName,\n    parent: gateway::GRPCRouteParentRefs,\n    backends: Option<Vec<gateway::GRPCRouteRulesBackendRefs>>,\n) -> gateway::GRPCRoute {\n    gateway::GRPCRoute {\n        status: None,\n        metadata: k8s::ObjectMeta {\n            name: Some(id.gkn.name.to_string()),\n            namespace: Some(id.namespace.clone()),\n            creation_timestamp: Some(k8s::Time(Utc::now())),\n            ..Default::default()\n        },\n        spec: gateway::GRPCRouteSpec {\n            parent_refs: Some(vec![parent]),\n            hostnames: None,\n            rules: Some(vec![gateway::GRPCRouteRules {\n                name: None,\n                filters: None,\n                backend_refs: backends,\n                matches: Some(vec![gateway::GRPCRouteRulesMatches {\n                    headers: None,\n                    method: Some(gateway::GRPCRouteRulesMatchesMethod {\n                        method: Some(\"MakeRoute\".to_string()),\n                        service: Some(\"io.linkerd.Test\".to_string()),\n                        r#type: Some(gateway::GRPCRouteRulesMatchesMethodType::Exact),\n                    }),\n                }]),\n                session_persistence: None,\n            }]),\n        },\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/tests/routes/helpers.rs",
    "content": "use super::{grpc, http};\nuse crate::index;\n\n#[test]\nfn test_eq_time_insensitive_gprc_route_parent_statuses_order_sensitive() {\n    // Create RouteParentStatus instances using make_parent_status helper\n    let status1 = grpc::make_parent_status(\"ns\", \"parent1\", \"Ready\", \"True\", \"AllGood\");\n    let status2 = grpc::make_parent_status(\"ns\", \"parent2\", \"Ready\", \"True\", \"AllGood\");\n\n    // Create two lists with the same elements in different orders\n    let list1 = vec![status1.clone(), status2.clone()];\n    let list2 = vec![status2, status1];\n\n    // Assert that eq_time_insensitive_route_parent_statuses returns true\n    // indicating that it considers the two lists equal\n    assert!(index::eq_time_insensitive_grpc_route_parent_statuses(\n        &list1, &list2\n    ));\n}\n\n#[test]\nfn test_eq_time_insensitive_http_route_parent_statuses_order_sensitive() {\n    // Create RouteParentStatus instances using make_parent_status helper\n    let status1 = http::make_parent_status(\"ns\", \"parent1\", \"Ready\", \"True\", \"AllGood\");\n    let status2 = http::make_parent_status(\"ns\", \"parent2\", \"Ready\", \"True\", \"AllGood\");\n\n    // Create two lists with the same elements in different orders\n    let list1 = vec![status1.clone(), status2.clone()];\n    let list2 = vec![status2, status1];\n\n    // Assert that eq_time_insensitive_route_parent_statuses returns true\n    // indicating that it considers the two lists equal\n    assert!(index::eq_time_insensitive_http_route_parent_statuses(\n        &list1, &list2\n    ));\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/tests/routes/http.rs",
    "content": "use crate::{\n    index::{\n        accepted, backend_not_found, invalid_backend_kind, no_matching_parent, resolved_refs,\n        POLICY_API_GROUP,\n    },\n    resource_id::NamespaceGroupKindName,\n    tests::{default_cluster_networks, make_server},\n    Index, IndexMetrics,\n};\nuse chrono::{DateTime, Utc};\nuse kubert::index::IndexNamespacedResource;\nuse linkerd_policy_controller_core::{routes::GroupKindName, POLICY_CONTROLLER_NAME};\nuse linkerd_policy_controller_k8s_api::{self as k8s, gateway, policy, Resource, ResourceExt};\nuse std::sync::Arc;\nuse tokio::sync::{mpsc, watch};\n\npub(crate) fn make_parent_status(\n    namespace: impl ToString,\n    name: impl ToString,\n    type_: impl ToString,\n    status: impl ToString,\n    reason: impl ToString,\n) -> gateway::HTTPRouteStatusParents {\n    let condition = k8s::Condition {\n        message: \"\".to_string(),\n        type_: type_.to_string(),\n        observed_generation: None,\n        reason: reason.to_string(),\n        status: status.to_string(),\n        last_transition_time: k8s::Time(DateTime::<Utc>::MIN_UTC),\n    };\n    gateway::HTTPRouteStatusParents {\n        conditions: Some(vec![condition]),\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            port: None,\n            section_name: None,\n            name: name.to_string(),\n            kind: Some(\"Server\".to_string()),\n            namespace: Some(namespace.to_string()),\n            group: Some(POLICY_API_GROUP.to_string()),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n    }\n}\n\n#[test]\nfn linkerd_route_with_no_backends() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply the route.\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace().as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: policy::HttpRoute::group(&()),\n            kind: policy::HttpRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n\n    let route = make_linkerd_route(&id, parent.clone(), None);\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // No backends were specified, so we have vacuously resolved them all.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn gateway_route_with_no_backends() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply the route.\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::HTTPRoute::group(&()),\n            kind: gateway::HTTPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_gateway_route(&id, parent.clone(), None);\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // No backends were specified, so we have vacuously resolved them all.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn linkerd_route_with_valid_service_backends() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply one backend service\n    let backend1 = super::make_service(\"ns-0\", \"backend-1\");\n    index.write().apply(backend1.clone());\n\n    // Apply one backend service\n    let backend2 = super::make_service(\"ns-0\", \"backend-2\");\n    index.write().apply(backend2.clone());\n\n    // Apply the route.\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace().as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: policy::HttpRoute::group(&()),\n            kind: policy::HttpRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let route = make_linkerd_route(\n        &id,\n        parent.clone(),\n        Some(vec![\n            gateway::HTTPRouteRulesBackendRefs {\n                weight: None,\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: backend1.name_unchecked(),\n                namespace: backend1.namespace(),\n                port: Some(8080),\n                filters: None,\n            },\n            gateway::HTTPRouteRulesBackendRefs {\n                weight: None,\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: backend2.name_unchecked(),\n                namespace: backend2.namespace(),\n                port: Some(8080),\n                filters: None,\n            },\n        ]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // All backends exist and can be resolved.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn linkerd_route_with_valid_egress_networks_backends() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index: Arc<parking_lot::lock_api::RwLock<parking_lot::RawRwLock, Index>> = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent: policy::EgressNetwork = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(parent.clone());\n\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace().as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: policy::HttpRoute::group(&()),\n            kind: policy::HttpRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let route = make_linkerd_route(\n        &id,\n        parent.clone(),\n        Some(vec![gateway::HTTPRouteRulesBackendRefs {\n            weight: None,\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"EgressNetwork\".to_string()),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: Some(8080),\n            filters: None,\n        }]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // All backends exist and can be resolved.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch: linkerd_policy_controller_k8s_api::Patch<serde_json::Value> =\n        crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn gateway_route_with_valid_service_backends() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply one backend service\n    let backend1 = super::make_service(\"ns-0\", \"backend-1\");\n    index.write().apply(backend1.clone());\n\n    // Apply one backend service\n    let backend2 = super::make_service(\"ns-0\", \"backend-2\");\n    index.write().apply(backend2.clone());\n\n    // Apply the route.\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::HTTPRoute::group(&()),\n            kind: gateway::HTTPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_gateway_route(\n        &id,\n        parent.clone(),\n        Some(vec![\n            gateway::HTTPRouteRulesBackendRefs {\n                weight: None,\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: backend1.name_unchecked(),\n                namespace: backend1.namespace(),\n                port: Some(8080),\n\n                filters: None,\n            },\n            gateway::HTTPRouteRulesBackendRefs {\n                weight: None,\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: backend2.name_unchecked(),\n                namespace: backend2.namespace(),\n                port: Some(8080),\n                filters: None,\n            },\n        ]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // All backends exist and can be resolved.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn gateway_route_with_valid_egress_networks_backends() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(parent.clone());\n\n    // Apply the route.\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::HTTPRoute::group(&()),\n            kind: gateway::HTTPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_gateway_route(\n        &id,\n        parent.clone(),\n        Some(vec![gateway::HTTPRouteRulesBackendRefs {\n            weight: None,\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"EgressNetwork\".to_string()),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: Some(8080),\n            filters: None,\n        }]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // All backends exist and can be resolved.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn linkerd_route_with_invalid_service_backend() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply one backend service\n    let backend = super::make_service(\"ns-0\", \"backend-1\");\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: policy::HttpRoute::group(&()),\n            kind: policy::HttpRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_linkerd_route(\n        &id,\n        parent.clone(),\n        Some(vec![\n            gateway::HTTPRouteRulesBackendRefs {\n                weight: None,\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: backend.name_unchecked(),\n                namespace: backend.namespace(),\n                port: Some(8080),\n                filters: None,\n            },\n            gateway::HTTPRouteRulesBackendRefs {\n                weight: None,\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: \"nonexistent-backend\".to_string(),\n                namespace: backend.namespace(),\n                port: Some(8080),\n                filters: None,\n            },\n        ]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // One of the backends does not exist so the status should be BackendNotFound.\n    let backend_condition = backend_not_found();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn linkerd_route_with_egress_network_backend_different_from_parent() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"svc\", accepted());\n    index.write().apply(parent.clone());\n\n    // Apply one backend egress network\n    let backend = super::make_egress_network(\"ns-0\", \"backend-1\", accepted());\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: policy::HttpRoute::group(&()),\n            kind: policy::HttpRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_linkerd_route(\n        &id,\n        parent.clone(),\n        Some(vec![gateway::HTTPRouteRulesBackendRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"EgressNetwork\".to_string()),\n            name: backend.name_unchecked(),\n            namespace: backend.namespace(),\n            port: Some(8080),\n            weight: None,\n            filters: None,\n        }]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = invalid_backend_kind(\n        \"EgressNetwork backend needs to be on a route that has an EgressNetwork parent\",\n    );\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn linkerd_route_with_egress_network_backend_and_service_parent() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply one backend egress network\n    let backend = super::make_egress_network(\"ns-0\", \"backend-1\", accepted());\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: policy::HttpRoute::group(&()),\n            kind: policy::HttpRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n\n    let route = make_linkerd_route(\n        &id,\n        parent.clone(),\n        Some(vec![gateway::HTTPRouteRulesBackendRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"EgressNetwork\".to_string()),\n            name: backend.name_unchecked(),\n            namespace: backend.namespace(),\n            port: Some(8080),\n            weight: None,\n            filters: None,\n        }]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = invalid_backend_kind(\n        \"EgressNetwork backend needs to be on a route that has an EgressNetwork parent\",\n    );\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn linkerd_route_with_egress_network_parent_and_service_backend() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(parent.clone());\n\n    // Apply one backend service\n    let backend = super::make_service(\"ns-0\", \"backend-1\");\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: policy::HttpRoute::group(&()),\n            kind: policy::HttpRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n\n    let route = make_linkerd_route(\n        &id,\n        parent.clone(),\n        Some(vec![gateway::HTTPRouteRulesBackendRefs {\n            group: Some(\"core\".to_string()),\n            kind: Some(\"Service\".to_string()),\n            name: backend.name_unchecked(),\n            namespace: backend.namespace(),\n            port: Some(8080),\n            weight: None,\n            filters: None,\n        }]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn gateway_route_with_invalid_service_backend() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply one backend service\n    let backend = super::make_service(\"ns-0\", \"backend-1\");\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::HTTPRoute::group(&()),\n            kind: gateway::HTTPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_gateway_route(\n        &id,\n        parent.clone(),\n        Some(vec![\n            gateway::HTTPRouteRulesBackendRefs {\n                weight: None,\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: backend.name_unchecked(),\n                namespace: backend.namespace(),\n                port: Some(8080),\n                filters: None,\n            },\n            gateway::HTTPRouteRulesBackendRefs {\n                weight: None,\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: \"nonexistent-backend\".to_string(),\n                namespace: backend.namespace(),\n                port: Some(8080),\n                filters: None,\n            },\n        ]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // One of the backends does not exist so the status should be BackendNotFound.\n    let backend_condition = backend_not_found();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn gateway_route_with_egress_network_backend_different_from_parent() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"svc\", accepted());\n    index.write().apply(parent.clone());\n\n    // Apply one backend egress network\n    let backend = super::make_egress_network(\"ns-0\", \"backend-1\", accepted());\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::HTTPRoute::group(&()),\n            kind: gateway::HTTPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_gateway_route(\n        &id,\n        parent.clone(),\n        Some(vec![gateway::HTTPRouteRulesBackendRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"EgressNetwork\".to_string()),\n            name: backend.name_unchecked(),\n            namespace: backend.namespace(),\n            port: Some(8080),\n            weight: None,\n            filters: None,\n        }]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = invalid_backend_kind(\n        \"EgressNetwork backend needs to be on a route that has an EgressNetwork parent\",\n    );\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn gateway_route_with_egress_network_backend_and_service_parent() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply one backend egress network\n    let backend = super::make_egress_network(\"ns-0\", \"backend-1\", accepted());\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::HTTPRoute::group(&()),\n            kind: gateway::HTTPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n\n    let route = make_gateway_route(\n        &id,\n        parent.clone(),\n        Some(vec![gateway::HTTPRouteRulesBackendRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"EgressNetwork\".to_string()),\n            name: backend.name_unchecked(),\n            namespace: backend.namespace(),\n            port: Some(8080),\n            weight: None,\n            filters: None,\n        }]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = invalid_backend_kind(\n        \"EgressNetwork backend needs to be on a route that has an EgressNetwork parent\",\n    );\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn gateway_route_with_egress_network_parent_and_service_backend() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(parent.clone());\n\n    // Apply one backend service\n    let backend = super::make_service(\"ns-0\", \"backend-1\");\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::HTTPRoute::group(&()),\n            kind: gateway::HTTPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n\n    let route = make_gateway_route(\n        &id,\n        parent.clone(),\n        Some(vec![gateway::HTTPRouteRulesBackendRefs {\n            group: Some(\"core\".to_string()),\n            kind: Some(\"Service\".to_string()),\n            name: backend.name_unchecked(),\n            namespace: backend.namespace(),\n            port: Some(8080),\n            weight: None,\n            filters: None,\n        }]),\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn linkerd_route_accepted_after_server_create() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            group: policy::HttpRoute::group(&()),\n            kind: policy::HttpRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"Server\".to_string()),\n        namespace: None,\n        name: \"srv-8080\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_linkerd_route(&id, parent, None);\n\n    // Apply the route.\n    index.write().apply(route);\n\n    // Create the expected update.\n    let parent_status = make_parent_status(\n        &id.namespace,\n        \"srv-8080\",\n        \"Accepted\",\n        \"False\",\n        \"NoMatchingParent\",\n    );\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The first update will be that the HTTPRoute is not accepted because the\n    // Server has been created yet.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    // Apply the server\n    let server = make_server(\n        \"ns-0\",\n        \"srv-8080\",\n        8080,\n        Some((\"app\", \"app-0\")),\n        Some((\"app\", \"app-0\")),\n        Some(policy::server::ProxyProtocol::Http1),\n    );\n    index.write().apply(server);\n\n    // Create the expected update.\n    let parent_status =\n        make_parent_status(&id.namespace, \"srv-8080\", \"Accepted\", \"True\", \"Accepted\");\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the HTTPRoute is accepted because the\n    // Server has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn linkerd_route_accepted_after_egress_network_create() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            group: policy::HttpRoute::group(&()),\n            kind: policy::HttpRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: Some(\"ns-0\".to_string()),\n        name: \"egress\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_linkerd_route(&id, parent.clone(), None);\n\n    // Apply the route.\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = no_matching_parent();\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            namespace: parent.namespace.clone(),\n            name: parent.name.clone(),\n            section_name: parent.section_name.clone(),\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition.clone()]),\n    };\n\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The first update will be that the HTTPRoute is not accepted because the\n    // EgressNetwork has not been created yet.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    // Apply the egress network\n    let egress = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(egress);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the HTTPRoute is accepted because the\n    // EgressNetwork has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn gateway_route_accepted_after_server_create() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            name: \"route-foo\".into(),\n            kind: gateway::HTTPRoute::kind(&()),\n            group: gateway::HTTPRoute::group(&()),\n        },\n    };\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"Server\".to_string()),\n        namespace: None,\n        name: \"srv-8080\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_gateway_route(&id, parent, None);\n\n    // Apply the route.\n    index.write().apply(route);\n\n    // Create the expected update.\n    let parent_status = make_parent_status(\n        &id.namespace,\n        \"srv-8080\",\n        \"Accepted\",\n        \"False\",\n        \"NoMatchingParent\",\n    );\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The first update will be that the HTTPRoute is not accepted because the\n    // Server has been created yet.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    // Apply the server\n    let server = make_server(\n        \"ns-0\",\n        \"srv-8080\",\n        8080,\n        Some((\"app\", \"app-0\")),\n        Some((\"app\", \"app-0\")),\n        Some(policy::server::ProxyProtocol::Http1),\n    );\n    index.write().apply(server);\n\n    // Create the expected update.\n    let parent_status =\n        make_parent_status(&id.namespace, \"srv-8080\", \"Accepted\", \"True\", \"Accepted\");\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the HTTPRoute is accepted because the\n    // Server has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn gateway_route_accepted_after_egress_network_create() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            group: gateway::HTTPRoute::group(&()),\n            kind: gateway::HTTPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: Some(\"ns-0\".to_string()),\n        name: \"egress\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_gateway_route(&id, parent.clone(), None);\n\n    // Apply the route.\n    index.write().apply(route);\n\n    // Create the expected update.\n    let rejected_condition = no_matching_parent();\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            namespace: parent.namespace.clone(),\n            name: parent.name.clone(),\n            section_name: parent.section_name.clone(),\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![rejected_condition, backend_condition.clone()]),\n    };\n\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The first update will be that the HTTPRoute is not accepted because the\n    // EgressNetwork has not been created yet.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    // Apply the egress network\n    let egress = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(egress);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            namespace: parent.namespace,\n            name: parent.name,\n            section_name: parent.section_name,\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the HTTPRoute is accepted because the\n    // EgressNetwork has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn linkerd_route_rejected_after_server_delete() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    let server = make_server(\n        \"ns-0\",\n        \"srv-8080\",\n        8080,\n        Some((\"app\", \"app-0\")),\n        Some((\"app\", \"app-0\")),\n        Some(policy::server::ProxyProtocol::Http1),\n    );\n    index.write().apply(server);\n\n    // There should be no update since there are no HTTPRoutes yet.\n    assert!(updates_rx.try_recv().is_err());\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            group: policy::HttpRoute::group(&()),\n            kind: policy::HttpRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"Server\".to_string()),\n        namespace: None,\n        name: \"srv-8080\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_linkerd_route(&id, parent, None);\n\n    // Apply the route\n    index.write().apply(route);\n\n    // Create the expected update.\n    let parent_status =\n        make_parent_status(&id.namespace, \"srv-8080\", \"Accepted\", \"True\", \"Accepted\");\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the HTTPRoute is accepted because the\n    // Server has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    {\n        let mut index = index.write();\n        <Index as IndexNamespacedResource<policy::Server>>::delete(\n            &mut index,\n            \"ns-0\".to_string(),\n            \"srv-8080\".to_string(),\n        );\n    }\n\n    // Create the expected update.\n    let parent_status =\n        make_parent_status(\"ns-0\", \"srv-8080\", \"Accepted\", \"False\", \"NoMatchingParent\");\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The third update will be that the HTTPRoute is not accepted because the\n    // Server has been deleted.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err());\n}\n\n#[test]\nfn linkerd_route_rejected_after_egress_network_delete() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    let egress = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(egress);\n\n    // There should be no update since there are no HTTPRoutes yet.\n    assert!(updates_rx.try_recv().is_err());\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            group: policy::HttpRoute::group(&()),\n            kind: policy::HttpRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: Some(\"ns-0\".to_string()),\n        name: \"egress\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_linkerd_route(&id, parent.clone(), None);\n\n    // Apply the route\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            namespace: parent.namespace.clone(),\n            name: parent.name.clone(),\n            section_name: parent.section_name.clone(),\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition.clone()]),\n    };\n\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the HTTPRoute is accepted because the\n    // EgressNetwork has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    {\n        let mut index = index.write();\n        <Index as IndexNamespacedResource<policy::EgressNetwork>>::delete(\n            &mut index,\n            \"ns-0\".to_string(),\n            \"egress\".to_string(),\n        );\n    }\n\n    // Create the expected update.\n    let rejected_condition = no_matching_parent();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            namespace: parent.namespace.clone(),\n            name: parent.name.clone(),\n            section_name: parent.section_name.clone(),\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![rejected_condition, backend_condition.clone()]),\n    };\n\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The third update will be that the HTTPRoute is not accepted because the\n    // EgressNetwork has been deleted.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err());\n}\n\n#[test]\nfn gateway_route_rejected_after_server_delete() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    let server = make_server(\n        \"ns-0\",\n        \"srv-8080\",\n        8080,\n        Some((\"app\", \"app-0\")),\n        Some((\"app\", \"app-0\")),\n        Some(policy::server::ProxyProtocol::Http1),\n    );\n    index.write().apply(server);\n\n    // There should be no update since there are no HTTPRoutes yet.\n    assert!(updates_rx.try_recv().is_err());\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            name: \"route-foo\".into(),\n            kind: gateway::HTTPRoute::kind(&()),\n            group: gateway::HTTPRoute::group(&()),\n        },\n    };\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"Server\".to_string()),\n        namespace: None,\n        name: \"srv-8080\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_gateway_route(&id, parent, None);\n\n    // Apply the route\n    index.write().apply(route);\n\n    // Create the expected update.\n    let parent_status =\n        make_parent_status(&id.namespace, \"srv-8080\", \"Accepted\", \"True\", \"Accepted\");\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the HTTPRoute is accepted because the\n    // Server has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    {\n        let mut index = index.write();\n        <Index as IndexNamespacedResource<policy::Server>>::delete(\n            &mut index,\n            \"ns-0\".to_string(),\n            \"srv-8080\".to_string(),\n        );\n    }\n\n    // Create the expected update.\n    let parent_status =\n        make_parent_status(\"ns-0\", \"srv-8080\", \"Accepted\", \"False\", \"NoMatchingParent\");\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The third update will be that the HTTPRoute is not accepted because the\n    // Server has been deleted.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err());\n}\n\n#[test]\nfn gateway_route_rejected_after_egress_network_delete() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    let egress = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(egress);\n\n    // There should be no update since there are no HTTPRoutes yet.\n    assert!(updates_rx.try_recv().is_err());\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            group: gateway::HTTPRoute::group(&()),\n            kind: gateway::HTTPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let parent = gateway::HTTPRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: Some(\"ns-0\".to_string()),\n        name: \"egress\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_gateway_route(&id, parent.clone(), None);\n\n    // Apply the route\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            namespace: parent.namespace.clone(),\n            name: parent.name.clone(),\n            section_name: parent.section_name.clone(),\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition.clone()]),\n    };\n\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the HTTPRoute is accepted because the\n    // EgressNetwork has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    {\n        let mut index = index.write();\n        <Index as IndexNamespacedResource<policy::EgressNetwork>>::delete(\n            &mut index,\n            \"ns-0\".to_string(),\n            \"egress\".to_string(),\n        );\n    }\n\n    // Create the expected update.\n    let rejected_condition = no_matching_parent();\n    let parent_status = gateway::HTTPRouteStatusParents {\n        parent_ref: gateway::HTTPRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            namespace: parent.namespace.clone(),\n            name: parent.name.clone(),\n            section_name: parent.section_name.clone(),\n            port: parent.port,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![rejected_condition, backend_condition.clone()]),\n    };\n\n    let status = gateway::HTTPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The third update will be that the HTTPRoute is not accepted because the\n    // EgressNetwork has been deleted.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err());\n}\n\nfn make_linkerd_route(\n    id: &NamespaceGroupKindName,\n    parent: gateway::HTTPRouteParentRefs,\n    backends: Option<Vec<gateway::HTTPRouteRulesBackendRefs>>,\n) -> policy::HttpRoute {\n    policy::HttpRoute {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(id.namespace.clone()),\n            name: Some(id.gkn.name.to_string()),\n            creation_timestamp: Some(k8s::Time(Utc::now())),\n            ..Default::default()\n        },\n        spec: policy::HttpRouteSpec {\n            parent_refs: Some(vec![parent]),\n            hostnames: None,\n            rules: Some(vec![policy::httproute::HttpRouteRule {\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo/bar\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    headers: None,\n                    query_params: None,\n                    method: Some(gateway::HTTPRouteRulesMatchesMethod::Get),\n                }]),\n                filters: None,\n                backend_refs: backends,\n                timeouts: None,\n            }]),\n        },\n        status: None,\n    }\n}\n\nfn make_gateway_route(\n    id: &NamespaceGroupKindName,\n    parent: gateway::HTTPRouteParentRefs,\n    backends: Option<Vec<gateway::HTTPRouteRulesBackendRefs>>,\n) -> gateway::HTTPRoute {\n    gateway::HTTPRoute {\n        status: None,\n        metadata: k8s::ObjectMeta {\n            name: Some(id.gkn.name.to_string()),\n            namespace: Some(id.namespace.clone()),\n            creation_timestamp: Some(k8s::Time(Utc::now())),\n            ..Default::default()\n        },\n        spec: gateway::HTTPRouteSpec {\n            parent_refs: Some(vec![parent]),\n            hostnames: None,\n            rules: Some(vec![gateway::HTTPRouteRules {\n                name: None,\n                filters: None,\n                backend_refs: backends,\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    headers: None,\n                    query_params: None,\n                    method: Some(gateway::HTTPRouteRulesMatchesMethod::Get),\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo/bar\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::PathPrefix),\n                    }),\n                }]),\n                ..Default::default()\n            }]),\n        },\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/tests/routes/tcp.rs",
    "content": "use crate::{\n    index::{\n        accepted, backend_not_found, invalid_backend_kind, no_matching_parent, resolved_refs,\n        POLICY_API_GROUP,\n    },\n    resource_id::NamespaceGroupKindName,\n    tests::{default_cluster_networks, make_server},\n    Index, IndexMetrics,\n};\nuse chrono::{DateTime, Utc};\nuse kubert::index::IndexNamespacedResource;\nuse linkerd_policy_controller_core::{routes::GroupKindName, POLICY_CONTROLLER_NAME};\nuse linkerd_policy_controller_k8s_api::{self as k8s, gateway, policy, Resource, ResourceExt};\nuse std::{sync::Arc, vec};\nuse tokio::sync::{mpsc, watch};\n\npub(crate) fn make_parent_status(\n    namespace: impl ToString,\n    name: impl ToString,\n    type_: impl ToString,\n    status: impl ToString,\n    reason: impl ToString,\n) -> gateway::TCPRouteStatusParents {\n    let condition = k8s::Condition {\n        message: \"\".to_string(),\n        type_: type_.to_string(),\n        observed_generation: None,\n        reason: reason.to_string(),\n        status: status.to_string(),\n        last_transition_time: k8s::Time(DateTime::<Utc>::MIN_UTC),\n    };\n    gateway::TCPRouteStatusParents {\n        conditions: Some(vec![condition]),\n        parent_ref: gateway::TCPRouteStatusParentsParentRef {\n            port: None,\n            section_name: None,\n            name: name.to_string(),\n            kind: Some(\"Server\".to_string()),\n            namespace: Some(namespace.to_string()),\n            group: Some(POLICY_API_GROUP.to_string()),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n    }\n}\n\n#[test]\nfn route_with_valid_service_backends() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply one backend service\n    let backend1 = super::make_service(\"ns-0\", \"backend-1\");\n    index.write().apply(backend1.clone());\n\n    // Apply one backend service\n    let backend2 = super::make_service(\"ns-0\", \"backend-2\");\n    index.write().apply(backend2.clone());\n\n    // Apply the route.\n    let parent = gateway::TCPRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TCPRoute::group(&()),\n            kind: gateway::TCPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        vec![\n            gateway::TCPRouteRulesBackendRefs {\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: backend1.name_unchecked(),\n                namespace: backend1.namespace(),\n                port: Some(8080),\n                weight: None,\n            },\n            gateway::TCPRouteRulesBackendRefs {\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: backend2.name_unchecked(),\n                namespace: backend2.namespace(),\n                port: Some(8080),\n                weight: None,\n            },\n        ],\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // All backends exist and can be resolved.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::TCPRouteStatusParents {\n        parent_ref: gateway::TCPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::TCPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_with_valid_egress_network_backend() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(parent.clone());\n\n    // Apply the route.\n    let parent = gateway::TCPRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TCPRoute::group(&()),\n            kind: gateway::TCPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        vec![gateway::TCPRouteRulesBackendRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"EgressNetwork\".to_string()),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: Some(8080),\n            weight: None,\n        }],\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // All backends exist and can be resolved.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::TCPRouteStatusParents {\n        parent_ref: gateway::TCPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::TCPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_with_invalid_service_backend() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply one backend service\n    let backend = super::make_service(\"ns-0\", \"backend-1\");\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::TCPRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TCPRoute::group(&()),\n            kind: gateway::TCPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        vec![\n            gateway::TCPRouteRulesBackendRefs {\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: backend.name_unchecked(),\n                namespace: backend.namespace(),\n                port: Some(8080),\n                weight: None,\n            },\n            gateway::TCPRouteRulesBackendRefs {\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: \"nonexistant-backend\".to_string(),\n                namespace: backend.namespace(),\n                port: Some(8080),\n                weight: None,\n            },\n        ],\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // One of the backends does not exist so the status should be BackendNotFound.\n    let backend_condition = backend_not_found();\n    let parent_status = gateway::TCPRouteStatusParents {\n        parent_ref: gateway::TCPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::TCPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_with_egress_network_backend_different_from_parent() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"svc\", accepted());\n    index.write().apply(parent.clone());\n\n    // Apply one backend egress network\n    let backend = super::make_egress_network(\"ns-0\", \"backend-1\", accepted());\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::TCPRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TCPRoute::group(&()),\n            kind: gateway::TCPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        vec![gateway::TCPRouteRulesBackendRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"EgressNetwork\".to_string()),\n            name: backend.name_unchecked(),\n            namespace: backend.namespace(),\n            port: Some(8080),\n            weight: None,\n        }],\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = invalid_backend_kind(\n        \"EgressNetwork backend needs to be on a route that has an EgressNetwork parent\",\n    );\n    let parent_status = gateway::TCPRouteStatusParents {\n        parent_ref: gateway::TCPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::TCPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_with_egress_network_backend_and_service_parent() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply one backend egress network\n    let backend = super::make_egress_network(\"ns-0\", \"backend-1\", accepted());\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::TCPRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TCPRoute::group(&()),\n            kind: gateway::TCPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        vec![gateway::TCPRouteRulesBackendRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"EgressNetwork\".to_string()),\n            name: backend.name_unchecked(),\n            namespace: backend.namespace(),\n            port: Some(8080),\n            weight: None,\n        }],\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = invalid_backend_kind(\n        \"EgressNetwork backend needs to be on a route that has an EgressNetwork parent\",\n    );\n    let parent_status = gateway::TCPRouteStatusParents {\n        parent_ref: gateway::TCPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::TCPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_with_egress_network_parent_and_service_backend() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(parent.clone());\n\n    // Apply one backend service\n    let backend = super::make_service(\"ns-0\", \"backend-1\");\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::TCPRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TCPRoute::group(&()),\n            kind: gateway::TCPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        vec![gateway::TCPRouteRulesBackendRefs {\n            group: Some(\"core\".to_string()),\n            kind: Some(\"Service\".to_string()),\n            name: backend.name_unchecked(),\n            namespace: backend.namespace(),\n            port: Some(8080),\n            weight: None,\n        }],\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::TCPRouteStatusParents {\n        parent_ref: gateway::TCPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::TCPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_accepted_after_server_create() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            name: \"route-foo\".into(),\n            kind: gateway::TCPRoute::kind(&()),\n            group: gateway::TCPRoute::group(&()),\n        },\n    };\n    let parent = gateway::TCPRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"Server\".to_string()),\n        namespace: None,\n        name: \"srv-8080\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_route(&id, parent, vec![]);\n\n    // Apply the route.\n    index.write().apply(route);\n\n    // Create the expected update.\n    let parent_status = make_parent_status(\n        &id.namespace,\n        \"srv-8080\",\n        \"Accepted\",\n        \"False\",\n        \"NoMatchingParent\",\n    );\n    let status = gateway::TCPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The first update will be that the TLSRoute is not accepted because the\n    // Server has been created yet.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    // Apply the server\n    let server = make_server(\n        \"ns-0\",\n        \"srv-8080\",\n        8080,\n        Some((\"app\", \"app-0\")),\n        Some((\"app\", \"app-0\")),\n        Some(policy::server::ProxyProtocol::Http2),\n    );\n    index.write().apply(server);\n\n    // Create the expected update.\n    let parent_status =\n        make_parent_status(&id.namespace, \"srv-8080\", \"Accepted\", \"True\", \"Accepted\");\n    let status = gateway::TCPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the TCPRoute is accepted because the\n    // Server has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_accepted_after_egress_network_create() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            group: gateway::TCPRoute::group(&()),\n            kind: gateway::TCPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let parent = gateway::TCPRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: Some(\"ns-0\".to_string()),\n        name: \"egress\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_route(&id, parent.clone(), vec![]);\n\n    // Apply the route.\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = no_matching_parent();\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::TCPRouteStatusParents {\n        parent_ref: gateway::TCPRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: parent.port,\n            section_name: parent.section_name.clone(),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition.clone()]),\n    };\n\n    let status = gateway::TCPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The first update will be that the TCPRoute is not accepted because the\n    // EgressNetwork has not been created yet.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    // Apply the egress network\n    let egress = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(egress);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let parent_status = gateway::TCPRouteStatusParents {\n        parent_ref: gateway::TCPRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n\n    let status = gateway::TCPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the TCPRoute is accepted because the\n    // EgressNetwork has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_rejected_after_server_delete() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    let server = make_server(\n        \"ns-0\",\n        \"srv-8080\",\n        8080,\n        Some((\"app\", \"app-0\")),\n        Some((\"app\", \"app-0\")),\n        Some(policy::server::ProxyProtocol::Http2),\n    );\n    index.write().apply(server);\n\n    // There should be no update since there are no TLSRoutes yet.\n    assert!(updates_rx.try_recv().is_err());\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            name: \"route-foo\".into(),\n            kind: gateway::TCPRoute::kind(&()),\n            group: gateway::TCPRoute::group(&()),\n        },\n    };\n    let parent = gateway::TCPRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"Server\".to_string()),\n        namespace: None,\n        name: \"srv-8080\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_route(&id, parent, vec![]);\n\n    // Apply the route\n    index.write().apply(route);\n\n    // Create the expected update.\n    let parent_status =\n        make_parent_status(&id.namespace, \"srv-8080\", \"Accepted\", \"True\", \"Accepted\");\n    let status = gateway::TCPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the TCPRoute is accepted because the\n    // Server has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    {\n        let mut index = index.write();\n        <Index as IndexNamespacedResource<policy::Server>>::delete(\n            &mut index,\n            \"ns-0\".to_string(),\n            \"srv-8080\".to_string(),\n        );\n    }\n\n    // Create the expected update.\n    let parent_status =\n        make_parent_status(\"ns-0\", \"srv-8080\", \"Accepted\", \"False\", \"NoMatchingParent\");\n    let status = gateway::TCPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The third update will be that the TCPRoute is not accepted because the\n    // Server has been deleted.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err());\n}\n\n#[test]\nfn route_rejected_after_egress_network_delete() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    let egress = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(egress);\n\n    // There should be no update since there are no TCPRoutes yet.\n    assert!(updates_rx.try_recv().is_err());\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            group: gateway::TCPRoute::group(&()),\n            kind: gateway::TCPRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let parent = gateway::TCPRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: Some(\"ns-0\".to_string()),\n        name: \"egress\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_route(&id, parent.clone(), vec![]);\n\n    // Apply the route\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::TCPRouteStatusParents {\n        parent_ref: gateway::TCPRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: parent.port,\n            section_name: parent.section_name.clone(),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition.clone()]),\n    };\n\n    let status = gateway::TCPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the TCPRoute is accepted because the\n    // EgressNetwork has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    {\n        let mut index = index.write();\n        <Index as IndexNamespacedResource<policy::EgressNetwork>>::delete(\n            &mut index,\n            \"ns-0\".to_string(),\n            \"egress\".to_string(),\n        );\n    }\n\n    // Create the expected update.\n    let rejected_condition = no_matching_parent();\n    let parent_status = gateway::TCPRouteStatusParents {\n        parent_ref: gateway::TCPRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: parent.port,\n            section_name: parent.section_name.clone(),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![rejected_condition, backend_condition.clone()]),\n    };\n\n    let status = gateway::TCPRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The third update will be that the TCPRoute is not accepted because the\n    // Server has been deleted.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err());\n}\n\nfn make_route(\n    id: &NamespaceGroupKindName,\n    parent: gateway::TCPRouteParentRefs,\n    backends: Vec<gateway::TCPRouteRulesBackendRefs>,\n) -> gateway::TCPRoute {\n    gateway::TCPRoute {\n        status: None,\n        metadata: k8s::ObjectMeta {\n            name: Some(id.gkn.name.to_string()),\n            namespace: Some(id.namespace.clone()),\n            creation_timestamp: Some(k8s::Time(Utc::now())),\n            ..Default::default()\n        },\n        spec: gateway::TCPRouteSpec {\n            parent_refs: Some(vec![parent]),\n            rules: vec![gateway::TCPRouteRules {\n                name: None,\n                backend_refs: Some(backends),\n            }],\n        },\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/tests/routes/tls.rs",
    "content": "use crate::{\n    index::{\n        accepted, backend_not_found, invalid_backend_kind, no_matching_parent, resolved_refs,\n        route_conflicted, POLICY_API_GROUP,\n    },\n    resource_id::NamespaceGroupKindName,\n    tests::{default_cluster_networks, make_server},\n    Index, IndexMetrics,\n};\nuse chrono::{DateTime, Utc};\nuse kubert::index::IndexNamespacedResource;\nuse linkerd_policy_controller_core::{routes::GroupKindName, POLICY_CONTROLLER_NAME};\nuse linkerd_policy_controller_k8s_api::{self as k8s, gateway, policy, Resource, ResourceExt};\nuse std::{sync::Arc, vec};\nuse tokio::sync::{mpsc, watch};\n\npub(crate) fn make_parent_status(\n    namespace: impl ToString,\n    name: impl ToString,\n    type_: impl ToString,\n    status: impl ToString,\n    reason: impl ToString,\n) -> gateway::TLSRouteStatusParents {\n    let condition = k8s::Condition {\n        message: \"\".to_string(),\n        type_: type_.to_string(),\n        observed_generation: None,\n        reason: reason.to_string(),\n        status: status.to_string(),\n        last_transition_time: k8s::Time(DateTime::<Utc>::MIN_UTC),\n    };\n    gateway::TLSRouteStatusParents {\n        conditions: Some(vec![condition]),\n        parent_ref: gateway::TLSRouteStatusParentsParentRef {\n            port: None,\n            section_name: None,\n            name: name.to_string(),\n            kind: Some(\"Server\".to_string()),\n            namespace: Some(namespace.to_string()),\n            group: Some(POLICY_API_GROUP.to_string()),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n    }\n}\n\n#[test]\nfn route_with_valid_service_backends() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply one backend service\n    let backend1 = super::make_service(\"ns-0\", \"backend-1\");\n    index.write().apply(backend1.clone());\n\n    // Apply one backend service\n    let backend2 = super::make_service(\"ns-0\", \"backend-2\");\n    index.write().apply(backend2.clone());\n\n    // Apply the route.\n    let parent = gateway::TLSRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TLSRoute::group(&()),\n            kind: gateway::TLSRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        vec![\n            gateway::TLSRouteRulesBackendRefs {\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: backend1.name_unchecked(),\n                namespace: backend1.namespace(),\n                port: Some(8080),\n                weight: None,\n            },\n            gateway::TLSRouteRulesBackendRefs {\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: backend2.name_unchecked(),\n                namespace: backend2.namespace(),\n                port: Some(8080),\n                weight: None,\n            },\n        ],\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // All backends exist and can be resolved.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::TLSRouteStatusParents {\n        parent_ref: gateway::TLSRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_with_valid_egress_network_backend() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(parent.clone());\n\n    // Apply the route.\n    let parent = gateway::TLSRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TLSRoute::group(&()),\n            kind: gateway::TLSRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        vec![gateway::TLSRouteRulesBackendRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"EgressNetwork\".to_string()),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: Some(8080),\n            weight: None,\n        }],\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // All backends exist and can be resolved.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::TLSRouteStatusParents {\n        parent_ref: gateway::TLSRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_with_invalid_service_backend() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply one backend service\n    let backend = super::make_service(\"ns-0\", \"backend-1\");\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::TLSRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TLSRoute::group(&()),\n            kind: gateway::TLSRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        vec![\n            gateway::TLSRouteRulesBackendRefs {\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: backend.name_unchecked(),\n                namespace: backend.namespace(),\n                port: Some(8080),\n                weight: None,\n            },\n            gateway::TLSRouteRulesBackendRefs {\n                group: Some(\"core\".to_string()),\n                kind: Some(\"Service\".to_string()),\n                name: \"nonexistant-backend\".to_string(),\n                namespace: backend.namespace(),\n                port: Some(8080),\n                weight: None,\n            },\n        ],\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    // One of the backends does not exist so the status should be BackendNotFound.\n    let backend_condition = backend_not_found();\n    let parent_status = gateway::TLSRouteStatusParents {\n        parent_ref: gateway::TLSRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_with_egress_network_backend_different_from_parent() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"svc\", accepted());\n    index.write().apply(parent.clone());\n\n    // Apply one backend egress network\n    let backend = super::make_egress_network(\"ns-0\", \"backend-1\", accepted());\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::TLSRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TLSRoute::group(&()),\n            kind: gateway::TLSRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        vec![gateway::TLSRouteRulesBackendRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"EgressNetwork\".to_string()),\n            name: backend.name_unchecked(),\n            namespace: backend.namespace(),\n            port: Some(8080),\n            weight: None,\n        }],\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = invalid_backend_kind(\n        \"EgressNetwork backend needs to be on a route that has an EgressNetwork parent\",\n    );\n    let parent_status = gateway::TLSRouteStatusParents {\n        parent_ref: gateway::TLSRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_with_egress_network_backend_and_service_parent() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    // Apply one backend egress network\n    let backend = super::make_egress_network(\"ns-0\", \"backend-1\", accepted());\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::TLSRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TLSRoute::group(&()),\n            kind: gateway::TLSRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        vec![gateway::TLSRouteRulesBackendRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"EgressNetwork\".to_string()),\n            name: backend.name_unchecked(),\n            namespace: backend.namespace(),\n            port: Some(8080),\n            weight: None,\n        }],\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = invalid_backend_kind(\n        \"EgressNetwork backend needs to be on a route that has an EgressNetwork parent\",\n    );\n    let parent_status = gateway::TLSRouteStatusParents {\n        parent_ref: gateway::TLSRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_with_egress_network_parent_and_service_backend() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(parent.clone());\n\n    // Apply one backend service\n    let backend = super::make_service(\"ns-0\", \"backend-1\");\n    index.write().apply(backend.clone());\n\n    // Apply the route.\n    let parent = gateway::TLSRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n    let id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TLSRoute::group(&()),\n            kind: gateway::TLSRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let route = make_route(\n        &id,\n        parent.clone(),\n        vec![gateway::TLSRouteRulesBackendRefs {\n            group: Some(\"core\".to_string()),\n            kind: Some(\"Service\".to_string()),\n            name: backend.name_unchecked(),\n            namespace: backend.namespace(),\n            port: Some(8080),\n            weight: None,\n        }],\n    );\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::TLSRouteStatusParents {\n        parent_ref: gateway::TLSRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_accepted_after_server_create() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            name: \"route-foo\".into(),\n            kind: gateway::TLSRoute::kind(&()),\n            group: gateway::TLSRoute::group(&()),\n        },\n    };\n    let parent = gateway::TLSRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"Server\".to_string()),\n        namespace: None,\n        name: \"srv-8080\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_route(&id, parent, vec![]);\n\n    // Apply the route.\n    index.write().apply(route);\n\n    // Create the expected update.\n    let parent_status = make_parent_status(\n        &id.namespace,\n        \"srv-8080\",\n        \"Accepted\",\n        \"False\",\n        \"NoMatchingParent\",\n    );\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The first update will be that the TLSRoute is not accepted because the\n    // Server has been created yet.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    // Apply the server\n    let server = make_server(\n        \"ns-0\",\n        \"srv-8080\",\n        8080,\n        Some((\"app\", \"app-0\")),\n        Some((\"app\", \"app-0\")),\n        Some(policy::server::ProxyProtocol::Http2),\n    );\n    index.write().apply(server);\n\n    // Create the expected update.\n    let parent_status =\n        make_parent_status(&id.namespace, \"srv-8080\", \"Accepted\", \"True\", \"Accepted\");\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the TCPRoute is accepted because the\n    // Server has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_accepted_after_egress_network_create() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            group: gateway::TLSRoute::group(&()),\n            kind: gateway::TLSRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let parent = gateway::TLSRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: Some(\"ns-0\".to_string()),\n        name: \"egress\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_route(&id, parent.clone(), vec![]);\n\n    // Apply the route.\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = no_matching_parent();\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::TLSRouteStatusParents {\n        parent_ref: gateway::TLSRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: parent.port,\n            section_name: parent.section_name.clone(),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition.clone()]),\n    };\n\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The first update will be that the TLSRoute is not accepted because the\n    // EgressNetwork has not been created yet.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    // Apply the egress network\n    let egress = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(egress);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let parent_status = gateway::TLSRouteStatusParents {\n        parent_ref: gateway::TLSRouteStatusParentsParentRef {\n            group: parent.group,\n            kind: parent.kind,\n            name: parent.name,\n            namespace: parent.namespace,\n            port: parent.port,\n            section_name: parent.section_name,\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition]),\n    };\n\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the TLSRoute is accepted because the\n    // EgressNetwork has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn route_rejected_after_server_delete() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    let server = make_server(\n        \"ns-0\",\n        \"srv-8080\",\n        8080,\n        Some((\"app\", \"app-0\")),\n        Some((\"app\", \"app-0\")),\n        Some(policy::server::ProxyProtocol::Http2),\n    );\n    index.write().apply(server);\n\n    // There should be no update since there are no TLSRoutes yet.\n    assert!(updates_rx.try_recv().is_err());\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            name: \"route-foo\".into(),\n            kind: gateway::TLSRoute::kind(&()),\n            group: gateway::TLSRoute::group(&()),\n        },\n    };\n    let parent = gateway::TLSRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"Server\".to_string()),\n        namespace: None,\n        name: \"srv-8080\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_route(&id, parent, vec![]);\n\n    // Apply the route\n    index.write().apply(route);\n\n    // Create the expected update.\n    let parent_status =\n        make_parent_status(&id.namespace, \"srv-8080\", \"Accepted\", \"True\", \"Accepted\");\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the TLSRoutes is accepted because the\n    // Server has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    {\n        let mut index = index.write();\n        <Index as IndexNamespacedResource<policy::Server>>::delete(\n            &mut index,\n            \"ns-0\".to_string(),\n            \"srv-8080\".to_string(),\n        );\n    }\n\n    // Create the expected update.\n    let parent_status =\n        make_parent_status(\"ns-0\", \"srv-8080\", \"Accepted\", \"False\", \"NoMatchingParent\");\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The third update will be that the TLSRoutes is not accepted because the\n    // Server has been deleted.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err());\n}\n\n#[test]\nfn route_rejected_after_egress_network_delete() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    let egress = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(egress);\n\n    // There should be no update since there are no TLSRoutes yet.\n    assert!(updates_rx.try_recv().is_err());\n\n    // Create the route id and route\n    let id = NamespaceGroupKindName {\n        namespace: \"ns-0\".to_string(),\n        gkn: GroupKindName {\n            group: gateway::TLSRoute::group(&()),\n            kind: gateway::TLSRoute::kind(&()),\n            name: \"route-foo\".into(),\n        },\n    };\n    let parent = gateway::TLSRouteParentRefs {\n        group: Some(POLICY_API_GROUP.to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: Some(\"ns-0\".to_string()),\n        name: \"egress\".to_string(),\n        section_name: None,\n        port: None,\n    };\n    let route = make_route(&id, parent.clone(), vec![]);\n\n    // Apply the route\n    index.write().apply(route);\n\n    // Create the expected update.\n    let accepted_condition = accepted();\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::TLSRouteStatusParents {\n        parent_ref: gateway::TLSRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: parent.port,\n            section_name: parent.section_name.clone(),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition, backend_condition.clone()]),\n    };\n\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The second update will be that the TLSRoute is accepted because the\n    // EgressNetwork has been created.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n\n    {\n        let mut index = index.write();\n        <Index as IndexNamespacedResource<policy::EgressNetwork>>::delete(\n            &mut index,\n            \"ns-0\".to_string(),\n            \"egress\".to_string(),\n        );\n    }\n\n    // Create the expected update.\n    let rejected_condition = no_matching_parent();\n    let parent_status = gateway::TLSRouteStatusParents {\n        parent_ref: gateway::TLSRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: parent.port,\n            section_name: parent.section_name.clone(),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![rejected_condition, backend_condition.clone()]),\n    };\n\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&id, status).unwrap();\n\n    // The third update will be that the TLSRoute is not accepted because the\n    // Server has been deleted.\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(id, update.id);\n    assert_eq!(patch, update.patch);\n    assert!(updates_rx.try_recv().is_err());\n}\n\n#[test]\nfn service_route_type_conflict() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent service\n    let parent = super::make_service(\"ns-0\", \"svc\");\n    index.write().apply(parent.clone());\n\n    let parent = gateway::TLSRouteParentRefs {\n        group: Some(\"core\".to_string()),\n        kind: Some(\"Service\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n\n    // Apply the TCP route.\n    let tcp_id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TCPRoute::group(&()),\n            kind: gateway::TCPRoute::kind(&()),\n            name: \"tcproute-foo\".into(),\n        },\n    };\n    let tcp_route = gateway::TCPRoute {\n        status: None,\n        metadata: k8s::ObjectMeta {\n            name: Some(tcp_id.gkn.name.to_string()),\n            namespace: Some(tcp_id.namespace.clone()),\n            creation_timestamp: Some(k8s::Time(Utc::now())),\n            ..Default::default()\n        },\n        spec: gateway::TCPRouteSpec {\n            parent_refs: Some(vec![gateway::TCPRouteParentRefs {\n                group: parent.group.clone(),\n                kind: parent.kind.clone(),\n                name: parent.name.clone(),\n                namespace: parent.namespace.clone(),\n                port: parent.port,\n                section_name: parent.section_name.clone(),\n            }]),\n            rules: vec![],\n        },\n    };\n    index.write().apply(tcp_route);\n\n    // Create the expected update -- TCPRoute should be accepted\n    let accepted_condition = accepted();\n    // No backends were specified, so we have vacuously resolved them all.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::TLSRouteStatusParents {\n        parent_ref: gateway::TLSRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: parent.port,\n            section_name: parent.section_name.clone(),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition.clone(), backend_condition.clone()]),\n    };\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&tcp_id, status).unwrap();\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(tcp_id, update.id);\n    assert_eq!(patch, update.patch);\n\n    // Apply the TLS route.\n    let tls_id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TLSRoute::group(&()),\n            kind: gateway::TLSRoute::kind(&()),\n            name: \"tlsroute-foo\".into(),\n        },\n    };\n    let route = make_route(&tls_id, parent.clone(), vec![]);\n    index.write().apply(route);\n\n    // Two expected updates: TCPRoute should be rejected and TLSRoute should be accepted\n    for _ in 0..2 {\n        let update = updates_rx.try_recv().unwrap();\n        if update.id.gkn.kind == gateway::TCPRoute::kind(&()) {\n            let conflict_condition = route_conflicted();\n            let parent_status = gateway::TLSRouteStatusParents {\n                parent_ref: gateway::TLSRouteStatusParentsParentRef {\n                    group: parent.group.clone(),\n                    kind: parent.kind.clone(),\n                    name: parent.name.clone(),\n                    namespace: parent.namespace.clone(),\n                    port: parent.port,\n                    section_name: parent.section_name.clone(),\n                },\n                controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                conditions: Some(vec![conflict_condition, backend_condition.clone()]),\n            };\n            let status = gateway::TLSRouteStatus {\n                parents: vec![parent_status],\n            };\n            let patch = crate::index::make_patch(&tcp_id, status).unwrap();\n            assert_eq!(patch, update.patch);\n        } else {\n            let parent_status = gateway::TLSRouteStatusParents {\n                parent_ref: gateway::TLSRouteStatusParentsParentRef {\n                    group: parent.group.clone(),\n                    kind: parent.kind.clone(),\n                    name: parent.name.clone(),\n                    namespace: parent.namespace.clone(),\n                    port: parent.port,\n                    section_name: parent.section_name.clone(),\n                },\n                controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                conditions: Some(vec![accepted_condition.clone(), backend_condition.clone()]),\n            };\n            let status = gateway::TLSRouteStatus {\n                parents: vec![parent_status],\n            };\n            let patch = crate::index::make_patch(&tls_id, status).unwrap();\n            assert_eq!(patch, update.patch);\n        }\n    }\n\n    // No more updates.\n    assert!(updates_rx.try_recv().is_err())\n}\n\n#[test]\nfn egress_network_route_type_conflict() {\n    let hostname = \"test\";\n    let claim = kubert::lease::Claim {\n        holder: \"test\".to_string(),\n        expiry: DateTime::<Utc>::MAX_UTC,\n    };\n    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));\n    let (updates_tx, mut updates_rx) = mpsc::channel(10000);\n    let index = Index::shared(\n        hostname,\n        claims_rx,\n        updates_tx,\n        IndexMetrics::register(&mut Default::default()),\n        default_cluster_networks(),\n    );\n\n    // Apply the parent egress network\n    let parent = super::make_egress_network(\"ns-0\", \"egress\", accepted());\n    index.write().apply(parent.clone());\n\n    let parent = gateway::TLSRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: parent.namespace(),\n        name: parent.name_unchecked(),\n        section_name: None,\n        port: Some(8080),\n    };\n\n    // Apply the TCP route.\n    let tcp_id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TCPRoute::group(&()),\n            kind: gateway::TCPRoute::kind(&()),\n            name: \"tcproute-foo\".into(),\n        },\n    };\n    let tcp_route = gateway::TCPRoute {\n        status: None,\n        metadata: k8s::ObjectMeta {\n            name: Some(tcp_id.gkn.name.to_string()),\n            namespace: Some(tcp_id.namespace.clone()),\n            creation_timestamp: Some(k8s::Time(Utc::now())),\n            ..Default::default()\n        },\n        spec: gateway::TCPRouteSpec {\n            parent_refs: Some(vec![gateway::TCPRouteParentRefs {\n                group: parent.group.clone(),\n                kind: parent.kind.clone(),\n                name: parent.name.clone(),\n                namespace: parent.namespace.clone(),\n                port: parent.port,\n                section_name: parent.section_name.clone(),\n            }]),\n            rules: vec![],\n        },\n    };\n    index.write().apply(tcp_route);\n\n    // Create the expected update -- TCPRoute should be accepted\n    let accepted_condition = accepted();\n    // No backends were specified, so we have vacuously resolved them all.\n    let backend_condition = resolved_refs();\n    let parent_status = gateway::TLSRouteStatusParents {\n        parent_ref: gateway::TLSRouteStatusParentsParentRef {\n            group: parent.group.clone(),\n            kind: parent.kind.clone(),\n            name: parent.name.clone(),\n            namespace: parent.namespace.clone(),\n            port: parent.port,\n            section_name: parent.section_name.clone(),\n        },\n        controller_name: POLICY_CONTROLLER_NAME.to_string(),\n        conditions: Some(vec![accepted_condition.clone(), backend_condition.clone()]),\n    };\n    let status = gateway::TLSRouteStatus {\n        parents: vec![parent_status],\n    };\n    let patch = crate::index::make_patch(&tcp_id, status).unwrap();\n    let update = updates_rx.try_recv().unwrap();\n    assert_eq!(tcp_id, update.id);\n    assert_eq!(patch, update.patch);\n\n    // Apply the TLS route.\n    let tls_id = NamespaceGroupKindName {\n        namespace: parent.namespace.as_deref().unwrap().to_string(),\n        gkn: GroupKindName {\n            group: gateway::TLSRoute::group(&()),\n            kind: gateway::TLSRoute::kind(&()),\n            name: \"tlsroute-foo\".into(),\n        },\n    };\n    let route = make_route(&tls_id, parent.clone(), vec![]);\n    index.write().apply(route);\n\n    // Two expected updates: TCP should be rejected and TLSRoute should be accepted\n    for _ in 0..2 {\n        let update = updates_rx.try_recv().unwrap();\n        if update.id.gkn.kind == gateway::TCPRoute::kind(&()) {\n            let conflict_condition = route_conflicted();\n            let parent_status = gateway::TLSRouteStatusParents {\n                parent_ref: gateway::TLSRouteStatusParentsParentRef {\n                    group: parent.group.clone(),\n                    kind: parent.kind.clone(),\n                    name: parent.name.clone(),\n                    namespace: parent.namespace.clone(),\n                    port: parent.port,\n                    section_name: parent.section_name.clone(),\n                },\n                controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                conditions: Some(vec![conflict_condition, backend_condition.clone()]),\n            };\n            let status = gateway::TLSRouteStatus {\n                parents: vec![parent_status],\n            };\n            let patch = crate::index::make_patch(&tcp_id, status).unwrap();\n            assert_eq!(patch, update.patch);\n        } else {\n            let parent_status = gateway::TLSRouteStatusParents {\n                parent_ref: gateway::TLSRouteStatusParentsParentRef {\n                    group: parent.group.clone(),\n                    kind: parent.kind.clone(),\n                    name: parent.name.clone(),\n                    namespace: parent.namespace.clone(),\n                    port: parent.port,\n                    section_name: parent.section_name.clone(),\n                },\n                controller_name: POLICY_CONTROLLER_NAME.to_string(),\n                conditions: Some(vec![accepted_condition.clone(), backend_condition.clone()]),\n            };\n            let status = gateway::TLSRouteStatus {\n                parents: vec![parent_status],\n            };\n            let patch = crate::index::make_patch(&tls_id, status).unwrap();\n            assert_eq!(patch, update.patch);\n        }\n    }\n\n    // No more updates.\n    assert!(updates_rx.try_recv().is_err())\n}\n\nfn make_route(\n    id: &NamespaceGroupKindName,\n    parent: gateway::TLSRouteParentRefs,\n    backends: Vec<gateway::TLSRouteRulesBackendRefs>,\n) -> gateway::TLSRoute {\n    gateway::TLSRoute {\n        status: None,\n        metadata: k8s::ObjectMeta {\n            name: Some(id.gkn.name.to_string()),\n            namespace: Some(id.namespace.clone()),\n            creation_timestamp: Some(k8s::Time(Utc::now())),\n            ..Default::default()\n        },\n        spec: gateway::TLSRouteSpec {\n            parent_refs: Some(vec![parent]),\n            hostnames: None,\n            rules: vec![gateway::TLSRouteRules {\n                name: None,\n                backend_refs: Some(backends),\n            }],\n        },\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/tests/routes.rs",
    "content": "use linkerd_policy_controller_k8s_api::{self as k8s_core_api, policy as linkerd_k8s_api};\n\nmod grpc;\nmod helpers;\nmod http;\nmod tcp;\nmod tls;\n\nfn make_service(\n    namespace: impl ToString,\n    name: impl ToString,\n) -> k8s_core_api::api::core::v1::Service {\n    k8s_core_api::api::core::v1::Service {\n        metadata: k8s_core_api::ObjectMeta {\n            namespace: Some(namespace.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: Some(k8s_core_api::ServiceSpec {\n            cluster_ip: Some(\"1.2.3.4\".to_string()),\n            cluster_ips: Some(vec![\"1.2.3.4\".to_string()]),\n            ..Default::default()\n        }),\n        status: None,\n    }\n}\n\nfn make_egress_network(\n    namespace: impl ToString,\n    name: impl ToString,\n    condition: k8s_core_api::Condition,\n) -> linkerd_k8s_api::EgressNetwork {\n    linkerd_k8s_api::EgressNetwork {\n        metadata: k8s_core_api::ObjectMeta {\n            namespace: Some(namespace.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: linkerd_k8s_api::EgressNetworkSpec {\n            networks: Some(vec![linkerd_k8s_api::Network {\n                cidr: \"0.0.0.0/0\".parse().unwrap(),\n                except: Some(vec![\n                    \"10.0.0.0/8\".parse().unwrap(),\n                    \"100.64.0.0/10\".parse().unwrap(),\n                    \"172.16.0.0/12\".parse().unwrap(),\n                    \"192.168.0.0/16\".parse().unwrap(),\n                    \"fd00::/8\".parse().unwrap(),\n                ]),\n            }]),\n            traffic_policy: linkerd_k8s_api::TrafficPolicy::Allow,\n        },\n        status: Some(linkerd_k8s_api::EgressNetworkStatus {\n            conditions: vec![condition],\n        }),\n    }\n}\n"
  },
  {
    "path": "policy-controller/k8s/status/src/tests.rs",
    "content": "use linkerd_policy_controller_core::IpNet;\nuse linkerd_policy_controller_k8s_api::{self as k8s_core_api, policy as linkerd_k8s_api};\nmod conflict;\nmod egress_network;\nmod ratelimit;\nmod routes;\n\npub fn default_cluster_networks() -> Vec<IpNet> {\n    vec![\n        \"10.0.0.0/8\".parse().unwrap(),\n        \"100.64.0.0/10\".parse().unwrap(),\n        \"172.16.0.0/12\".parse().unwrap(),\n        \"192.168.0.0/16\".parse().unwrap(),\n        \"fd00::/8\".parse().unwrap(),\n    ]\n}\n\npub fn make_server(\n    namespace: impl ToString,\n    name: impl ToString,\n    port: u16,\n    srv_labels: impl IntoIterator<Item = (&'static str, &'static str)>,\n    pod_labels: impl IntoIterator<Item = (&'static str, &'static str)>,\n    proxy_protocol: Option<linkerd_k8s_api::server::ProxyProtocol>,\n) -> linkerd_k8s_api::Server {\n    let port = linkerd_k8s_api::server::Port::Number(port.try_into().unwrap());\n    linkerd_k8s_api::Server {\n        metadata: k8s_core_api::ObjectMeta {\n            namespace: Some(namespace.to_string()),\n            name: Some(name.to_string()),\n            labels: Some(\n                srv_labels\n                    .into_iter()\n                    .map(|(k, v)| (k.to_string(), v.to_string()))\n                    .collect(),\n            ),\n            ..Default::default()\n        },\n        spec: linkerd_k8s_api::ServerSpec {\n            port,\n            selector: linkerd_k8s_api::server::Selector::Pod(pod_labels.into_iter().collect()),\n            proxy_protocol,\n            access_policy: None,\n        },\n    }\n}\n"
  },
  {
    "path": "policy-controller/runtime/Cargo.toml",
    "content": "[package]\nname = \"linkerd-policy-controller-runtime\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\nlicense = \"Apache-2.0\"\npublish = false\n\n[dependencies]\nanyhow = \"1\"\nasync-trait = \"0.1\"\nbytes = \"1\"\ndrain = \"0.2\"\nfutures = { version = \"0.3\", default-features = false }\nhttp-body-util = \"0.1\"\nhyper = { workspace = true, features = [\"http1\", \"http2\", \"server\"] }\nhyper-util = { workspace = true }\nipnet = { version = \"2\", default-features = false }\nk8s-openapi = { workspace = true }\nopenssl = { version = \"0.10.76\", optional = true }\nparking_lot = \"0.12\"\nprometheus-client = { workspace = true }\nregex = \"1\"\nserde = \"1\"\nserde_json = \"1\"\nthiserror = \"2\"\ntokio-stream = { version = \"0.1\", features = [\"sync\"] }\ntower = { workspace = true }\ntracing = \"0.1\"\n\nlinkerd-policy-controller-core = { workspace = true }\nlinkerd-policy-controller-grpc = { workspace = true }\nlinkerd-policy-controller-k8s-api = { workspace = true }\nlinkerd-policy-controller-k8s-index = { workspace = true }\nlinkerd-policy-controller-k8s-status = { workspace = true }\n\n[dependencies.clap]\nversion = \"4\"\ndefault-features = false\nfeatures = [\"derive\", \"env\", \"std\"]\n\n[dependencies.kube]\nworkspace = true\ndefault-features = false\nfeatures = [\"admission\", \"derive\", \"rustls-tls\"]\n\n[dependencies.kubert]\nworkspace = true\ndefault-features = false\nfeatures = [\n    \"clap\",\n    \"index\",\n    \"lease\",\n    \"prometheus-client\",\n    \"runtime\",\n    \"server\",\n    \"rustls-tls\"\n]\n\n[dependencies.tokio]\nversion = \"1\"\nfeatures = [\"macros\", \"parking_lot\", \"rt\", \"rt-multi-thread\", \"signal\"]\n\n[dependencies.tonic]\nworkspace = true\ndefault-features = false\nfeatures = [\"transport\", \"router\"]\n"
  },
  {
    "path": "policy-controller/runtime/src/admission.rs",
    "content": "use super::validation;\nuse crate::k8s::policy::{\n    httproute, AuthorizationPolicy, AuthorizationPolicySpec, EgressNetwork, EgressNetworkSpec,\n    HttpLocalRateLimitPolicy, HttpRoute, HttpRouteSpec, MeshTLSAuthentication,\n    MeshTLSAuthenticationSpec, NamespacedTargetRef, Network, NetworkAuthentication,\n    NetworkAuthenticationSpec, RateLimitPolicySpec, Server, ServerAuthorization,\n    ServerAuthorizationSpec, ServerSpec,\n};\nuse anyhow::{anyhow, bail, ensure, Context, Result};\nuse futures::future;\nuse http_body_util::BodyExt;\nuse hyper::{http, Request, Response};\nuse k8s_openapi::api::core::v1::{Namespace, ServiceAccount};\nuse kube::{core::DynamicObject, Resource, ResourceExt};\nuse linkerd_policy_controller_k8s_api::gateway;\nuse linkerd_policy_controller_k8s_index::{self as index, outbound::index as outbound_index};\nuse serde::de::DeserializeOwned;\nuse std::collections::BTreeMap;\nuse thiserror::Error;\nuse tracing::{debug, info, trace, warn};\n\n#[derive(Clone)]\npub struct Admission {}\n\n#[derive(Debug, Error)]\npub enum Error {\n    #[error(\"failed to read request body: {0}\")]\n    Request(#[from] hyper::Error),\n\n    #[error(\"failed to encode json response: {0}\")]\n    Json(#[from] serde_json::Error),\n}\n\ntype Review = kube::core::admission::AdmissionReview<DynamicObject>;\ntype AdmissionRequest = kube::core::admission::AdmissionRequest<DynamicObject>;\ntype AdmissionResponse = kube::core::admission::AdmissionResponse;\ntype AdmissionReview = kube::core::admission::AdmissionReview<DynamicObject>;\n\n#[async_trait::async_trait]\ntrait Validate<T> {\n    async fn validate(\n        self,\n        ns: &str,\n        name: &str,\n        annotations: &BTreeMap<String, String>,\n        spec: T,\n    ) -> Result<()>;\n}\n\ntype Body = http_body_util::Full<bytes::Bytes>;\n\n// === impl AdmissionService ===\n\nimpl tower::Service<Request<hyper::body::Incoming>> for Admission {\n    type Response = Response<Body>;\n    type Error = Error;\n    type Future = future::BoxFuture<'static, Result<Response<Body>, Error>>;\n\n    fn poll_ready(\n        &mut self,\n        _cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<std::result::Result<(), Self::Error>> {\n        std::task::Poll::Ready(Ok(()))\n    }\n\n    fn call(&mut self, req: Request<hyper::body::Incoming>) -> Self::Future {\n        trace!(?req);\n        if req.method() != http::Method::POST || req.uri().path() != \"/\" {\n            return Box::pin(future::ok(\n                Response::builder()\n                    .status(http::StatusCode::NOT_FOUND)\n                    .body(Body::default())\n                    .expect(\"not found response must be valid\"),\n            ));\n        }\n\n        let admission = self.clone();\n        Box::pin(async move {\n            use bytes::Buf;\n            let bytes = req.into_body().collect().await?.to_bytes();\n            let review: Review = match serde_json::from_reader(bytes.reader()) {\n                Ok(review) => review,\n                Err(error) => {\n                    warn!(%error, \"Failed to parse request body\");\n                    return json_response(AdmissionResponse::invalid(error).into_review());\n                }\n            };\n            trace!(?review);\n\n            let rsp = match review.try_into() {\n                Ok(req) => {\n                    debug!(?req);\n                    admission.admit(req).await\n                }\n                Err(error) => {\n                    warn!(%error, \"Invalid admission request\");\n                    AdmissionResponse::invalid(error)\n                }\n            };\n            debug!(?rsp);\n            json_response(rsp.into_review())\n        })\n    }\n}\n\nimpl Admission {\n    pub fn new() -> Self {\n        Self {}\n    }\n\n    async fn admit(self, req: AdmissionRequest) -> AdmissionResponse {\n        if is_kind::<AuthorizationPolicy>(&req) {\n            return self.admit_spec::<AuthorizationPolicySpec>(req).await;\n        }\n\n        if is_kind::<MeshTLSAuthentication>(&req) {\n            return self.admit_spec::<MeshTLSAuthenticationSpec>(req).await;\n        }\n\n        if is_kind::<NetworkAuthentication>(&req) {\n            return self.admit_spec::<NetworkAuthenticationSpec>(req).await;\n        }\n\n        if is_kind::<Server>(&req) {\n            return self.admit_spec::<ServerSpec>(req).await;\n        };\n\n        if is_kind::<ServerAuthorization>(&req) {\n            return self.admit_spec::<ServerAuthorizationSpec>(req).await;\n        };\n\n        if is_kind::<HttpRoute>(&req) {\n            return self.admit_spec::<HttpRouteSpec>(req).await;\n        }\n\n        if is_kind::<EgressNetwork>(&req) {\n            return self.admit_spec::<EgressNetworkSpec>(req).await;\n        }\n\n        if is_kind::<gateway::HTTPRoute>(&req) {\n            return self.admit_spec::<gateway::HTTPRouteSpec>(req).await;\n        }\n\n        if is_kind::<gateway::GRPCRoute>(&req) {\n            return self.admit_spec::<gateway::GRPCRouteSpec>(req).await;\n        }\n\n        if is_kind::<gateway::TLSRoute>(&req) {\n            return self.admit_spec::<gateway::TLSRouteSpec>(req).await;\n        }\n\n        if is_kind::<gateway::TCPRoute>(&req) {\n            return self.admit_spec::<gateway::TCPRouteSpec>(req).await;\n        }\n\n        if is_kind::<HttpLocalRateLimitPolicy>(&req) {\n            return self.admit_spec::<RateLimitPolicySpec>(req).await;\n        }\n\n        AdmissionResponse::invalid(format_args!(\n            \"unsupported resource type: {}.{}.{}\",\n            req.kind.group, req.kind.version, req.kind.kind\n        ))\n    }\n\n    async fn admit_spec<T>(self, req: AdmissionRequest) -> AdmissionResponse\n    where\n        T: DeserializeOwned,\n        Self: Validate<T>,\n    {\n        let rsp = AdmissionResponse::from(&req);\n\n        let kind = req.kind.kind.clone();\n        let (obj, spec) = match parse_spec::<T>(req) {\n            Ok(spec) => spec,\n            Err(error) => {\n                info!(%error, \"Failed to parse {} spec\", kind);\n                return rsp.deny(error);\n            }\n        };\n\n        let ns = obj.namespace().unwrap_or_default();\n        let name = obj.name_any();\n        let annotations = obj.annotations();\n\n        if let Err(error) = self.validate(&ns, &name, annotations, spec).await {\n            info!(%error, %ns, %name, %kind, \"Denied\");\n            return rsp.deny(error);\n        }\n\n        rsp\n    }\n}\n\nfn is_kind<T>(req: &AdmissionRequest) -> bool\nwhere\n    T: Resource,\n    T::DynamicType: Default,\n{\n    let dt = Default::default();\n    req.kind.group.eq_ignore_ascii_case(&T::group(&dt))\n        && req.kind.kind.eq_ignore_ascii_case(&T::kind(&dt))\n}\n\nfn json_response(rsp: AdmissionReview) -> Result<Response<Body>, Error> {\n    let bytes = serde_json::to_vec(&rsp)?;\n    Ok(Response::builder()\n        .status(http::StatusCode::OK)\n        .header(http::header::CONTENT_TYPE, \"application/json\")\n        .body(Body::from(bytes))\n        .expect(\"admission review response must be valid\"))\n}\n\nfn parse_spec<T: DeserializeOwned>(req: AdmissionRequest) -> Result<(DynamicObject, T)> {\n    let obj = req\n        .object\n        .ok_or_else(|| anyhow!(\"admission request missing 'object\"))?;\n\n    let spec = {\n        let data = obj\n            .data\n            .get(\"spec\")\n            .cloned()\n            .ok_or_else(|| anyhow!(\"admission request missing 'spec'\"))?;\n        serde_json::from_value(data)?\n    };\n\n    Ok((obj, spec))\n}\n\n#[async_trait::async_trait]\nimpl Validate<AuthorizationPolicySpec> for Admission {\n    async fn validate(\n        self,\n        ns: &str,\n        _name: &str,\n        _annotations: &BTreeMap<String, String>,\n        spec: AuthorizationPolicySpec,\n    ) -> Result<()> {\n        if spec.target_ref.targets_kind::<Namespace>() && spec.target_ref.name != ns {\n            bail!(\"cannot target another namespace: {}\", &spec.target_ref.name);\n        }\n\n        let mtls_authns_count = spec\n            .required_authentication_refs\n            .iter()\n            .filter(|authn| authn.targets_kind::<MeshTLSAuthentication>())\n            .count();\n        if mtls_authns_count > 1 {\n            bail!(\"only a single MeshTLSAuthentication may be set\");\n        }\n\n        let sa_authns_count = spec\n            .required_authentication_refs\n            .iter()\n            .filter(|authn| authn.targets_kind::<ServiceAccount>())\n            .count();\n        if sa_authns_count > 1 {\n            bail!(\"only a single ServiceAccount may be set\");\n        }\n\n        if mtls_authns_count + sa_authns_count > 1 {\n            bail!(\"a MeshTLSAuthentication and ServiceAccount may not be set together\");\n        }\n\n        let net_authns_count = spec\n            .required_authentication_refs\n            .iter()\n            .filter(|authn| authn.targets_kind::<NetworkAuthentication>())\n            .count();\n        if net_authns_count > 1 {\n            bail!(\"only a single NetworkAuthentication may be set\");\n        }\n\n        if mtls_authns_count + sa_authns_count + net_authns_count\n            < spec.required_authentication_refs.len()\n        {\n            let kinds = spec\n                .required_authentication_refs\n                .iter()\n                .filter(|authn| {\n                    !authn.targets_kind::<MeshTLSAuthentication>()\n                        && !authn.targets_kind::<NetworkAuthentication>()\n                        && !authn.targets_kind::<ServiceAccount>()\n                })\n                .map(|authn| authn.canonical_kind())\n                .collect::<Vec<_>>();\n            bail!(\"unsupported authentication kind(s): {}\", kinds.join(\", \"));\n        }\n\n        // Confirm that the index will be able to read this spec.\n        index::authorization_policy::validate(spec)?;\n\n        Ok(())\n    }\n}\n\nfn validate_identity_ref(id: &NamespacedTargetRef) -> Result<()> {\n    if id.targets_kind::<ServiceAccount>() {\n        return Ok(());\n    }\n\n    if id.targets_kind::<Namespace>() {\n        if id.namespace.is_some() {\n            bail!(\"Namespace identity_ref is cluster-scoped and cannot have a namespace\");\n        }\n        return Ok(());\n    }\n\n    bail!(\"invalid identity target kind: {}\", id.canonical_kind());\n}\n\n#[async_trait::async_trait]\nimpl Validate<MeshTLSAuthenticationSpec> for Admission {\n    async fn validate(\n        self,\n        _ns: &str,\n        _name: &str,\n        _annotations: &BTreeMap<String, String>,\n        spec: MeshTLSAuthenticationSpec,\n    ) -> Result<()> {\n        for id in spec.identities.iter().flatten() {\n            if let Err(err) = validation::validate_identity(id) {\n                bail!(\"id {id} is invalid: {err}\");\n            }\n        }\n\n        for id in spec.identity_refs.iter().flatten() {\n            validate_identity_ref(id)?;\n        }\n\n        Ok(())\n    }\n}\n\n#[async_trait::async_trait]\nimpl Validate<ServerSpec> for Admission {\n    /// Checks that `spec` has an `accessPolicy` with a valid value.\n    async fn validate(\n        self,\n        _ns: &str,\n        _name: &str,\n        _annotations: &BTreeMap<String, String>,\n        spec: ServerSpec,\n    ) -> Result<()> {\n        if let Some(policy) = spec.access_policy {\n            policy\n                .parse::<index::DefaultPolicy>()\n                .map_err(|err| anyhow!(\"Invalid 'accessPolicy' field: {err}\"))?;\n        }\n\n        Ok(())\n    }\n}\n\n#[async_trait::async_trait]\nimpl Validate<NetworkAuthenticationSpec> for Admission {\n    async fn validate(\n        self,\n        _ns: &str,\n        _name: &str,\n        _annotations: &BTreeMap<String, String>,\n        spec: NetworkAuthenticationSpec,\n    ) -> Result<()> {\n        if spec.networks.is_empty() {\n            bail!(\"at least one network must be specified\");\n        }\n\n        validate_networks(spec.networks)\n    }\n}\n\n#[async_trait::async_trait]\nimpl Validate<EgressNetworkSpec> for Admission {\n    async fn validate(\n        self,\n        _ns: &str,\n        _name: &str,\n        _annotations: &BTreeMap<String, String>,\n        spec: EgressNetworkSpec,\n    ) -> Result<()> {\n        if let Some(networks) = spec.networks {\n            if networks.is_empty() {\n                bail!(\"at least one network must be specified\");\n            }\n\n            return validate_networks(networks);\n        }\n\n        Ok(())\n    }\n}\n\nfn validate_networks(networks: Vec<Network>) -> Result<()> {\n    for net in networks.into_iter() {\n        for except in net.except.into_iter().flatten() {\n            if except.contains(&net.cidr) {\n                bail!(\n                    \"cidr '{}' is completely negated by exception '{}'\",\n                    net.cidr,\n                    except\n                );\n            }\n            if !net.cidr.contains(&except) {\n                bail!(\n                    \"cidr '{}' does not include exception '{}'\",\n                    net.cidr,\n                    except\n                );\n            }\n        }\n    }\n\n    Ok(())\n}\n\n#[async_trait::async_trait]\nimpl Validate<ServerAuthorizationSpec> for Admission {\n    async fn validate(\n        self,\n        _ns: &str,\n        _name: &str,\n        _annotations: &BTreeMap<String, String>,\n        spec: ServerAuthorizationSpec,\n    ) -> Result<()> {\n        if let Some(mtls) = spec.client.mesh_tls.as_ref() {\n            if spec.client.unauthenticated {\n                bail!(\"`unauthenticated` must be false if `mesh_tls` is specified\");\n            }\n            if mtls.unauthenticated_tls {\n                let ids = mtls.identities.as_ref().map(|ids| ids.len()).unwrap_or(0);\n                let sas = mtls\n                    .service_accounts\n                    .as_ref()\n                    .map(|sas| sas.len())\n                    .unwrap_or(0);\n                if ids + sas > 0 {\n                    bail!(\"`unauthenticatedTLS` be false if any `identities` or `service_accounts` is specified\");\n                }\n            }\n        }\n\n        for net in spec.client.networks.into_iter().flatten() {\n            for except in net.except.into_iter().flatten() {\n                if except.contains(&net.cidr) {\n                    bail!(\n                        \"cidr '{}' is completely negated by exception '{}'\",\n                        net.cidr,\n                        except\n                    );\n                }\n                if !net.cidr.contains(&except) {\n                    bail!(\n                        \"cidr '{}' does not include exception '{}'\",\n                        net.cidr,\n                        except\n                    );\n                }\n            }\n        }\n\n        Ok(())\n    }\n}\n\nfn validate_match(httproute_rules_match: gateway::HTTPRouteRulesMatches) -> Result<()> {\n    index::routes::http::try_match(httproute_rules_match).map(|_| ())\n}\n\n#[async_trait::async_trait]\nimpl Validate<HttpRouteSpec> for Admission {\n    async fn validate(\n        self,\n        _ns: &str,\n        _name: &str,\n        annotations: &BTreeMap<String, String>,\n        spec: HttpRouteSpec,\n    ) -> Result<()> {\n        for parent in spec.parent_refs.iter().flatten() {\n            if outbound_index::is_parent_egress_network(&parent.kind, &parent.group)\n                && parent.port.is_none()\n            {\n                bail!(\"cannot target an EgressNetwork without specifying a port\");\n            }\n        }\n\n        if spec.parent_refs.iter().flatten().any(|parent| {\n            outbound_index::is_parent_service_or_egress_network(&parent.kind, &parent.group)\n        }) {\n            outbound_index::http::parse_http_retry(annotations)?;\n            outbound_index::parse_accrual_config(annotations)?;\n            outbound_index::parse_timeouts(annotations)?;\n        }\n\n        fn validate_filter(filter: httproute::HttpRouteFilter) -> Result<()> {\n            match filter {\n                httproute::HttpRouteFilter::RequestHeaderModifier {\n                    request_header_modifier,\n                } => index::routes::http::request_header_modifier(request_header_modifier)\n                    .map(|_| ()),\n                httproute::HttpRouteFilter::ResponseHeaderModifier {\n                    response_header_modifier,\n                } => index::routes::http::response_header_modifier(response_header_modifier)\n                    .map(|_| ()),\n                httproute::HttpRouteFilter::RequestRedirect { request_redirect } => {\n                    index::routes::http::req_redirect(request_redirect).map(|_| ())\n                }\n            }\n        }\n\n        fn validate_timeouts(timeouts: httproute::HttpRouteTimeouts) -> Result<()> {\n            use std::time::Duration;\n\n            if let Some(t) = timeouts.backend_request {\n                ensure!(\n                    !t.is_negative(),\n                    \"backendRequest timeout must not be negative\"\n                );\n            }\n\n            if let Some(t) = timeouts.request {\n                ensure!(!t.is_negative(), \"request timeout must not be negative\");\n            }\n\n            if let (Some(req), Some(backend_req)) = (timeouts.request, timeouts.backend_request) {\n                ensure!(\n                    Duration::from(req) >= Duration::from(backend_req),\n                    \"backendRequest timeout ({backend_req}) must not be greater than request timeout ({req})\"\n                );\n            }\n            Ok(())\n        }\n\n        // Validate the rules in this spec.\n        // This is essentially equivalent to the indexer's conversion function\n        // from `HttpRouteSpec` to `InboundRouteBinding`, except that we don't\n        // actually allocate stuff in order to return an `InboundRouteBinding`.\n        for httproute::HttpRouteRule {\n            filters,\n            matches,\n            timeouts,\n            ..\n        } in spec.rules.into_iter().flatten()\n        {\n            for m in matches.into_iter().flatten() {\n                validate_match(m)?;\n            }\n\n            for f in filters.into_iter().flatten() {\n                validate_filter(f)?;\n            }\n\n            if let Some(timeouts) = timeouts {\n                validate_timeouts(timeouts)?;\n            }\n        }\n\n        Ok(())\n    }\n}\n\nfn validate_http_backend_if_service(br: &gateway::HTTPRouteRulesBackendRefs) -> Result<()> {\n    let is_service = matches!(br.group.as_deref(), Some(\"core\") | Some(\"\") | None)\n        && matches!(br.kind.as_deref(), Some(\"Service\") | None);\n\n    // If the backend reference is a Service, it must have a port. If it is not\n    // a Service, then we have to admit it for interoperability with other\n    // controllers.\n    if is_service && matches!(br.port, None | Some(0)) {\n        bail!(\"cannot reference a Service without a port\");\n    }\n\n    Ok(())\n}\n\nfn validate_grpc_backend_if_service(br: &gateway::GRPCRouteRulesBackendRefs) -> Result<()> {\n    let is_service = matches!(br.group.as_deref(), Some(\"core\") | Some(\"\") | None)\n        && matches!(br.kind.as_deref(), Some(\"Service\") | None);\n\n    // If the backend reference is a Service, it must have a port. If it is not\n    // a Service, then we have to admit it for interoperability with other\n    // controllers.\n    if is_service && matches!(br.port, None | Some(0)) {\n        bail!(\"cannot reference a Service without a port\");\n    }\n\n    Ok(())\n}\n\n#[async_trait::async_trait]\nimpl Validate<gateway::HTTPRouteSpec> for Admission {\n    async fn validate(\n        self,\n        _ns: &str,\n        _name: &str,\n        annotations: &BTreeMap<String, String>,\n        spec: gateway::HTTPRouteSpec,\n    ) -> Result<()> {\n        for parent in spec.parent_refs.iter().flatten() {\n            if outbound_index::is_parent_egress_network(&parent.kind, &parent.group)\n                && parent.port.is_none()\n            {\n                bail!(\"cannot target an EgressNetwork without specifying a port\");\n            }\n        }\n\n        if spec.parent_refs.iter().flatten().any(|parent| {\n            outbound_index::is_parent_service_or_egress_network(&parent.kind, &parent.group)\n        }) {\n            outbound_index::http::parse_http_retry(annotations)?;\n            outbound_index::parse_accrual_config(annotations)?;\n            outbound_index::parse_timeouts(annotations)?;\n        }\n\n        fn validate_filter(filter: gateway::HTTPRouteRulesFilters) -> Result<()> {\n            if let Some(request_header_modifier) = filter.request_header_modifier {\n                index::routes::http::request_header_modifier(request_header_modifier)?;\n            }\n            if let Some(response_header_modifier) = filter.response_header_modifier {\n                index::routes::http::response_header_modifier(response_header_modifier)?;\n            }\n            if let Some(request_redirect) = filter.request_redirect {\n                index::routes::http::req_redirect(request_redirect)?;\n            }\n            Ok(())\n        }\n\n        // Validate the rules in this spec.\n        // This is essentially equivalent to the indexer's conversion function\n        // from `HttpRouteSpec` to `InboundRouteBinding`, except that we don't\n        // actually allocate stuff in order to return an `InboundRouteBinding`.\n        for gateway::HTTPRouteRules {\n            filters,\n            matches,\n            backend_refs,\n            ..\n        } in spec.rules.into_iter().flatten()\n        {\n            for m in matches.into_iter().flatten() {\n                validate_match(m)?;\n            }\n\n            for f in filters.into_iter().flatten() {\n                validate_filter(f)?;\n            }\n\n            for br in backend_refs.iter().flatten() {\n                validate_http_backend_if_service(br).context(\"invalid backendRef\")?;\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[async_trait::async_trait]\nimpl Validate<gateway::GRPCRouteSpec> for Admission {\n    async fn validate(\n        self,\n        _ns: &str,\n        _name: &str,\n        annotations: &BTreeMap<String, String>,\n        spec: gateway::GRPCRouteSpec,\n    ) -> Result<()> {\n        for parent in spec.parent_refs.iter().flatten() {\n            if outbound_index::is_parent_egress_network(&parent.kind, &parent.group)\n                && parent.port.is_none()\n            {\n                bail!(\"cannot target an EgressNetwork without specifying a port\");\n            }\n        }\n\n        if spec.parent_refs.iter().flatten().any(|parent| {\n            outbound_index::is_parent_service_or_egress_network(&parent.kind, &parent.group)\n        }) {\n            outbound_index::grpc::parse_grpc_retry(annotations)?;\n            outbound_index::parse_accrual_config(annotations)?;\n            outbound_index::parse_timeouts(annotations)?;\n        }\n\n        fn validate_filter(filter: gateway::GRPCRouteRulesFilters) -> Result<()> {\n            if let Some(request_header_modifier) = filter.request_header_modifier {\n                index::routes::grpc::request_header_modifier(request_header_modifier)?;\n            }\n            if let Some(response_header_modifier) = filter.response_header_modifier {\n                index::routes::grpc::response_header_modifier(response_header_modifier)?;\n            }\n            Ok(())\n        }\n\n        fn validate_match_rule(matches: gateway::GRPCRouteRulesMatches) -> Result<()> {\n            index::routes::grpc::try_match(matches).map(|_| ())\n        }\n\n        // Validate the rules in this spec.\n        // This is essentially just a check to ensure that none\n        // of the rules are improperly constructed (e.g. include\n        // a `GrpcMethodMatch` rule where neither `method.method`\n        // nor `method.service` actually contain a value)\n        for gateway::GRPCRouteRules {\n            filters,\n            matches,\n            backend_refs,\n            ..\n        } in spec.rules.into_iter().flatten()\n        {\n            for rule in matches.into_iter().flatten() {\n                validate_match_rule(rule)?;\n            }\n\n            for filter in filters.into_iter().flatten() {\n                validate_filter(filter)?;\n            }\n\n            for br in backend_refs.iter().flatten() {\n                validate_grpc_backend_if_service(br).context(\"invalid backendRef\")?;\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[async_trait::async_trait]\nimpl Validate<gateway::TLSRouteSpec> for Admission {\n    async fn validate(\n        self,\n        _ns: &str,\n        _name: &str,\n        _annotations: &BTreeMap<String, String>,\n        spec: gateway::TLSRouteSpec,\n    ) -> Result<()> {\n        for parent in spec.parent_refs.iter().flatten() {\n            if outbound_index::is_parent_egress_network(&parent.kind, &parent.group)\n                && parent.port.is_none()\n            {\n                bail!(\"cannot target an EgressNetwork without specifying a port\");\n            }\n        }\n\n        if spec.rules.len() != 1 {\n            bail!(\"TlsRoute supports a single rule only\")\n        }\n\n        Ok(())\n    }\n}\n\n#[async_trait::async_trait]\nimpl Validate<gateway::TCPRouteSpec> for Admission {\n    async fn validate(\n        self,\n        _ns: &str,\n        _name: &str,\n        _annotations: &BTreeMap<String, String>,\n        spec: gateway::TCPRouteSpec,\n    ) -> Result<()> {\n        for parent in spec.parent_refs.iter().flatten() {\n            if outbound_index::is_parent_egress_network(&parent.kind, &parent.group)\n                && parent.port.is_none()\n            {\n                bail!(\"cannot target an EgressNetwork without specifying a port\");\n            }\n        }\n\n        if spec.rules.len() != 1 {\n            bail!(\"TcpRoute supports a single rule only\")\n        }\n\n        Ok(())\n    }\n}\n\n#[async_trait::async_trait]\nimpl Validate<RateLimitPolicySpec> for Admission {\n    async fn validate(\n        self,\n        _ns: &str,\n        _name: &str,\n        _annotations: &BTreeMap<String, String>,\n        spec: RateLimitPolicySpec,\n    ) -> Result<()> {\n        if !spec.target_ref.targets_kind::<Server>() {\n            bail!(\n                \"invalid targetRef kind: {}\",\n                spec.target_ref.canonical_kind()\n            );\n        }\n\n        if let Some(total) = spec.total {\n            if total.requests_per_second == 0 {\n                bail!(\"total.requestsPerSecond must be greater than 0\");\n            }\n\n            if let Some(ref identity) = spec.identity {\n                if identity.requests_per_second > total.requests_per_second {\n                    bail!(\"identity.requestsPerSecond must be less than or equal to total.requestsPerSecond\");\n                }\n            }\n\n            for ovr in spec.overrides.clone().unwrap_or_default().iter() {\n                if ovr.requests_per_second > total.requests_per_second {\n                    bail!(\"override.requestsPerSecond must be less than or equal to total.requestsPerSecond\");\n                }\n            }\n        }\n\n        if let Some(identity) = spec.identity {\n            if identity.requests_per_second == 0 {\n                bail!(\"identity.requestsPerSecond must be greater than 0\");\n            }\n        }\n\n        for ovr in spec.overrides.unwrap_or_default().iter() {\n            if ovr.requests_per_second == 0 {\n                bail!(\"override.requestsPerSecond must be greater than 0\");\n            }\n\n            for target_ref in ovr.client_refs.iter() {\n                if !target_ref.targets_kind::<ServiceAccount>() {\n                    bail!(\"overrides.clientRefs must target a ServiceAccount\");\n                }\n            }\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "policy-controller/runtime/src/args.rs",
    "content": "use crate::{\n    admission::Admission,\n    core::IpNet,\n    grpc::{self, metrics::GrpcServerMetricsFamily},\n    index::{self, ports::parse_portset, ClusterInfo, DefaultPolicy},\n    index_list::IndexList,\n    k8s::{self, gateway, Client, Resource},\n    lease, status, InboundDiscover, OutboundDiscover,\n};\nuse anyhow::{bail, Result};\nuse clap::Parser;\nuse futures::prelude::*;\nuse kube::{core::DeserializeGuard, runtime::watcher, ResourceExt};\nuse prometheus_client::registry::Registry;\nuse serde::de::DeserializeOwned;\nuse std::fmt::Debug;\nuse std::{net::SocketAddr, sync::Arc};\nuse tokio::{sync::mpsc, time::Duration};\nuse tonic::transport::Server;\nuse tracing::{info, info_span, instrument, Instrument};\n\nconst DETECT_TIMEOUT: Duration = Duration::from_secs(10);\nconst RECONCILIATION_PERIOD: Duration = Duration::from_secs(10);\n\n// The maximum number of status patches to buffer. As a conservative estimate,\n// we assume that sending a patch will take at least 1ms, so we set the buffer\n// size to be the same as the reconciliation period in milliseconds.\nconst STATUS_UPDATE_QUEUE_SIZE: usize = RECONCILIATION_PERIOD.as_millis() as usize;\n\n#[derive(Debug, Parser)]\n#[clap(name = \"policy\", about = \"A policy resource controller\")]\npub struct Args {\n    #[clap(\n        long,\n        default_value = \"linkerd=info,warn\",\n        env = \"LINKERD_POLICY_CONTROLLER_LOG\"\n    )]\n    log_level: kubert::LogFilter,\n\n    #[clap(long, default_value = \"plain\")]\n    log_format: kubert::LogFormat,\n\n    #[clap(flatten)]\n    client: kubert::ClientArgs,\n\n    #[clap(flatten)]\n    server: kubert::ServerArgs,\n\n    #[clap(flatten)]\n    admin: kubert::AdminArgs,\n\n    /// Disables the admission controller server.\n    #[clap(long)]\n    admission_controller_disabled: bool,\n\n    #[clap(long, default_value = \"0.0.0.0:8090\")]\n    grpc_addr: SocketAddr,\n\n    /// Network CIDRs of pod IPs.\n    ///\n    /// The default includes all private networks.\n    #[clap(\n        long,\n        default_value = \"10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16\"\n    )]\n    cluster_networks: IpNets,\n\n    #[clap(long, default_value = \"cluster.local\")]\n    identity_domain: String,\n\n    #[clap(long, default_value = \"cluster.local\")]\n    cluster_domain: String,\n\n    #[clap(long, default_value = \"all-unauthenticated\")]\n    default_policy: DefaultPolicy,\n\n    #[clap(long, default_value = \"linkerd-destination\")]\n    policy_deployment_name: String,\n\n    #[clap(long, default_value = \"linkerd\")]\n    control_plane_namespace: String,\n\n    /// Network CIDRs of all expected probes.\n    #[clap(long)]\n    probe_networks: Option<IpNets>,\n\n    #[clap(long)]\n    default_opaque_ports: String,\n\n    #[clap(long, default_value = \"5000\")]\n    patch_timeout_ms: u64,\n\n    #[clap(long)]\n    allow_l5d_request_headers: bool,\n\n    #[clap(long, default_value = \"linkerd-egress\")]\n    global_egress_network_namespace: String,\n}\n\nimpl Args {\n    #[inline]\n    pub async fn parse_and_run() -> Result<()> {\n        Self::parse().run().await\n    }\n\n    pub async fn run(self) -> Result<()> {\n        let Self {\n            admin,\n            client,\n            log_level,\n            log_format,\n            server,\n            grpc_addr,\n            admission_controller_disabled,\n            identity_domain,\n            cluster_domain,\n            cluster_networks: IpNets(cluster_networks),\n            default_policy,\n            policy_deployment_name,\n            control_plane_namespace,\n            probe_networks,\n            default_opaque_ports,\n            patch_timeout_ms,\n            allow_l5d_request_headers,\n            global_egress_network_namespace,\n        } = self;\n\n        let server = if admission_controller_disabled {\n            None\n        } else {\n            Some(server)\n        };\n\n        let probe_networks = probe_networks.map(|IpNets(nets)| nets).unwrap_or_default();\n        let global_egress_network_namespace = Arc::new(global_egress_network_namespace);\n        let default_opaque_ports = parse_portset(&default_opaque_ports)?;\n        let cluster_info = Arc::new(ClusterInfo {\n            networks: cluster_networks.clone(),\n            identity_domain,\n            control_plane_ns: control_plane_namespace.clone(),\n            dns_domain: cluster_domain.clone(),\n            default_policy,\n            default_detect_timeout: DETECT_TIMEOUT,\n            default_opaque_ports,\n            probe_networks,\n            global_egress_network_namespace,\n        });\n\n        // Build the API index data structures which will maintain information\n        // necessary for serving the inbound policy and outbound policy gRPC APIs.\n        let inbound_index = index::inbound::Index::shared(cluster_info.clone());\n        let outbound_index = index::outbound::Index::shared(cluster_info.clone());\n\n        let mut prom = <Registry>::default();\n        let resource_status = prom.sub_registry_with_prefix(\"resource_status\");\n        let status_metrics = status::ControllerMetrics::register(resource_status);\n        let status_index_metrcs = status::IndexMetrics::register(resource_status);\n\n        index::outbound::metrics::register(\n            prom.sub_registry_with_prefix(\"outbound_index\"),\n            outbound_index.clone(),\n        );\n        index::inbound::metrics::register(\n            prom.sub_registry_with_prefix(\"inbound_index\"),\n            inbound_index.clone(),\n        );\n        let rt_metrics = kubert::RuntimeMetrics::register(prom.sub_registry_with_prefix(\"kube\"));\n        let grpc_metrics = grpc::metrics::GrpcServerMetricsFamily::register(\n            prom.sub_registry_with_prefix(\"grpc_server\"),\n        );\n\n        let mut runtime = kubert::Runtime::builder()\n            .with_log(log_level, log_format)\n            .with_metrics(rt_metrics)\n            .with_admin(admin.into_builder().with_prometheus(prom))\n            .with_client(client)\n            .with_optional_server(server)\n            .build()\n            .await?;\n\n        let hostname =\n            std::env::var(\"HOSTNAME\").expect(\"Failed to fetch `HOSTNAME` environment variable\");\n\n        let claims = lease::init(\n            &runtime,\n            &control_plane_namespace,\n            &policy_deployment_name,\n            &hostname,\n        )\n        .await?;\n\n        // Build the status index which will maintain information necessary for\n        // updating the status field of policy resources.\n        let (updates_tx, updates_rx) = mpsc::channel(STATUS_UPDATE_QUEUE_SIZE);\n        let status_index = status::Index::shared(\n            hostname.clone(),\n            claims.clone(),\n            updates_tx,\n            status_index_metrcs,\n            cluster_networks.clone(),\n        );\n\n        // Spawn resource watches.\n\n        let pods = guarded_watch::<k8s::Pod, _>(\n            &mut runtime,\n            watcher::Config::default().labels(\"linkerd.io/control-plane-ns\"),\n        );\n\n        tokio::spawn(\n            kubert::index::namespaced(inbound_index.clone(), pods).instrument(info_span!(\"pods\")),\n        );\n\n        let external_workloads = guarded_watch::<k8s::external_workload::ExternalWorkload, _>(\n            &mut runtime,\n            watcher::Config::default(),\n        );\n        tokio::spawn(\n            kubert::index::namespaced(inbound_index.clone(), external_workloads)\n                .instrument(info_span!(\"external_workloads\")),\n        );\n\n        let servers =\n            guarded_watch::<k8s::policy::Server, _>(&mut runtime, watcher::Config::default());\n        let servers_indexes = IndexList::new(inbound_index.clone())\n            .push(status_index.clone())\n            .shared();\n        tokio::spawn(\n            kubert::index::namespaced(servers_indexes, servers).instrument(info_span!(\"servers\")),\n        );\n\n        let server_authzs = guarded_watch::<k8s::policy::ServerAuthorization, _>(\n            &mut runtime,\n            watcher::Config::default(),\n        );\n        tokio::spawn(\n            kubert::index::namespaced(inbound_index.clone(), server_authzs)\n                .instrument(info_span!(\"serverauthorizations\")),\n        );\n\n        let authz_policies = guarded_watch::<k8s::policy::AuthorizationPolicy, _>(\n            &mut runtime,\n            watcher::Config::default(),\n        );\n        tokio::spawn(\n            kubert::index::namespaced(inbound_index.clone(), authz_policies)\n                .instrument(info_span!(\"authorizationpolicies\")),\n        );\n\n        let mtls_authns = guarded_watch::<k8s::policy::MeshTLSAuthentication, _>(\n            &mut runtime,\n            watcher::Config::default(),\n        );\n        tokio::spawn(\n            kubert::index::namespaced(inbound_index.clone(), mtls_authns)\n                .instrument(info_span!(\"meshtlsauthentications\")),\n        );\n\n        let network_authns = guarded_watch::<k8s::policy::NetworkAuthentication, _>(\n            &mut runtime,\n            watcher::Config::default(),\n        );\n        tokio::spawn(\n            kubert::index::namespaced(inbound_index.clone(), network_authns)\n                .instrument(info_span!(\"networkauthentications\")),\n        );\n\n        let ratelimit_policies = guarded_watch::<k8s::policy::HttpLocalRateLimitPolicy, _>(\n            &mut runtime,\n            watcher::Config::default(),\n        );\n        let ratelimit_policies_indexes = IndexList::new(inbound_index.clone())\n            .push(status_index.clone())\n            .shared();\n        tokio::spawn(\n            kubert::index::namespaced(ratelimit_policies_indexes.clone(), ratelimit_policies)\n                .instrument(info_span!(\"httplocalratelimitpolicies\")),\n        );\n\n        let http_routes_indexes = IndexList::new(inbound_index.clone())\n            .push(outbound_index.clone())\n            .push(status_index.clone())\n            .shared();\n\n        if api_resource_exists::<k8s::policy::HttpRoute>(&runtime.client()).await {\n            let http_routes = guarded_watch::<k8s::policy::HttpRoute, _>(\n                &mut runtime,\n                watcher::Config::default(),\n            );\n\n            tokio::spawn(\n                kubert::index::namespaced(http_routes_indexes.clone(), http_routes)\n                    .instrument(info_span!(\"httproutes.policy.linkerd.io\")),\n            );\n        } else {\n            tracing::warn!(\n                \"httproutes.policy.linkerd.io resource kind not found, skipping watches\"\n            );\n        }\n\n        if api_resource_exists::<gateway::HTTPRoute>(&runtime.client()).await {\n            let gateway_http_routes =\n                guarded_watch::<gateway::HTTPRoute, _>(&mut runtime, watcher::Config::default());\n            tokio::spawn(\n                kubert::index::namespaced(http_routes_indexes, gateway_http_routes)\n                    .instrument(info_span!(\"httproutes.gateway.networking.k8s.io\")),\n            );\n        } else {\n            tracing::warn!(\n                \"httproutes.gateway.networking.k8s.io resource kind not found, skipping watches\"\n            );\n        }\n\n        if api_resource_exists::<gateway::GRPCRoute>(&runtime.client()).await {\n            let gateway_grpc_routes =\n                guarded_watch::<gateway::GRPCRoute, _>(&mut runtime, watcher::Config::default());\n            let gateway_grpc_routes_indexes = IndexList::new(outbound_index.clone())\n                .push(inbound_index.clone())\n                .push(status_index.clone())\n                .shared();\n            tokio::spawn(\n                kubert::index::namespaced(gateway_grpc_routes_indexes.clone(), gateway_grpc_routes)\n                    .instrument(info_span!(\"grpcroutes.gateway.networking.k8s.io\")),\n            );\n        } else {\n            tracing::warn!(\n                \"grpcroutes.gateway.networking.k8s.io resource kind not found, skipping watches\"\n            );\n        }\n\n        if api_resource_exists::<gateway::TLSRoute>(&runtime.client()).await {\n            let tls_routes =\n                guarded_watch::<gateway::TLSRoute, _>(&mut runtime, watcher::Config::default());\n            let tls_routes_indexes = IndexList::new(status_index.clone())\n                .push(outbound_index.clone())\n                .shared();\n            tokio::spawn(\n                kubert::index::namespaced(tls_routes_indexes.clone(), tls_routes)\n                    .instrument(info_span!(\"tlsroutes.gateway.networking.k8s.io\")),\n            );\n        } else {\n            tracing::warn!(\n                \"tlsroutes.gateway.networking.k8s.io resource kind not found, skipping watches\"\n            );\n        }\n\n        if api_resource_exists::<gateway::TCPRoute>(&runtime.client()).await {\n            let tcp_routes =\n                guarded_watch::<gateway::TCPRoute, _>(&mut runtime, watcher::Config::default());\n            let tcp_routes_indexes = IndexList::new(status_index.clone())\n                .push(outbound_index.clone())\n                .shared();\n            tokio::spawn(\n                kubert::index::namespaced(tcp_routes_indexes.clone(), tcp_routes)\n                    .instrument(info_span!(\"tcproutes.gateway.networking.k8s.io\")),\n            );\n        } else {\n            tracing::warn!(\n                \"tcproutes.gateway.networking.k8s.io resource kind not found, skipping watches\"\n            );\n        }\n\n        let services = guarded_watch::<k8s::Service, _>(&mut runtime, watcher::Config::default());\n        let services_indexes = IndexList::new(outbound_index.clone())\n            .push(status_index.clone())\n            .shared();\n        tokio::spawn(\n            kubert::index::namespaced(services_indexes, services)\n                .instrument(info_span!(\"services\")),\n        );\n\n        let egress_networks = guarded_watch::<k8s::policy::EgressNetwork, _>(\n            &mut runtime,\n            watcher::Config::default(),\n        );\n        let egress_networks_indexes = IndexList::new(status_index.clone())\n            .push(outbound_index.clone())\n            .shared();\n        tokio::spawn(\n            kubert::index::namespaced(egress_networks_indexes, egress_networks)\n                .instrument(info_span!(\"egressnetworks\")),\n        );\n\n        // Spawn the status Controller reconciliation.\n        tokio::spawn(\n            status::Index::run(status_index.clone(), RECONCILIATION_PERIOD)\n                .instrument(info_span!(\"status_index\")),\n        );\n\n        // Run the gRPC server, serving results by looking up against the index handle.\n        tokio::spawn(grpc(\n            grpc_addr,\n            cluster_domain,\n            cluster_networks,\n            allow_l5d_request_headers,\n            inbound_index,\n            outbound_index,\n            grpc_metrics.clone(),\n            runtime.shutdown_handle(),\n        ));\n\n        let client = runtime.client();\n        let status_controller = status::Controller::new(\n            claims,\n            client,\n            hostname,\n            updates_rx,\n            Duration::from_millis(patch_timeout_ms),\n            status_metrics,\n        );\n        tokio::spawn(\n            status_controller\n                .run()\n                .instrument(info_span!(\"status_controller\")),\n        );\n\n        let runtime = runtime.spawn_server(Admission::new);\n\n        // Block the main thread on the shutdown signal. Once it fires, wait for the background tasks to\n        // complete before exiting.\n        if runtime.run().await.is_err() {\n            bail!(\"Aborted\");\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Clone, Debug)]\nstruct IpNets(Vec<IpNet>);\n\nimpl std::str::FromStr for IpNets {\n    type Err = anyhow::Error;\n    fn from_str(s: &str) -> Result<Self> {\n        s.split(',')\n            .map(|n| n.parse().map_err(Into::into))\n            .collect::<Result<Vec<IpNet>>>()\n            .map(Self)\n    }\n}\n\n#[instrument(skip_all, fields(port = %addr.port()))]\n#[allow(clippy::too_many_arguments)]\nasync fn grpc(\n    addr: SocketAddr,\n    cluster_domain: String,\n    cluster_networks: Vec<IpNet>,\n    allow_l5d_request_headers: bool,\n    inbound_index: index::inbound::SharedIndex,\n    outbound_index: index::outbound::SharedIndex,\n    metrics: GrpcServerMetricsFamily,\n    drain: drain::Watch,\n) -> Result<()> {\n    let inbound_discover = InboundDiscover::new(inbound_index);\n    let inbound_svc = grpc::inbound::InboundPolicyServer::new(\n        inbound_discover,\n        cluster_networks,\n        drain.clone(),\n        metrics.clone(),\n    )\n    .svc();\n\n    let outbound_discover = OutboundDiscover::new(outbound_index);\n    let outbound_svc = grpc::outbound::OutboundPolicyServer::new(\n        outbound_discover,\n        cluster_domain,\n        allow_l5d_request_headers,\n        drain.clone(),\n        metrics,\n    )\n    .svc();\n\n    let (close_tx, close_rx) = tokio::sync::oneshot::channel();\n    tokio::pin! {\n        let srv = Server::builder().add_service(inbound_svc).add_service(outbound_svc).serve_with_shutdown(addr, close_rx.map(|_| {}));\n    }\n\n    info!(%addr, \"policy gRPC server listening\");\n    tokio::select! {\n        res = (&mut srv) => res?,\n        handle = drain.signaled() => {\n            let _ = close_tx.send(());\n            handle.release_after(srv).await?\n        }\n    }\n    Ok(())\n}\n\nasync fn api_resource_exists<T>(client: &Client) -> bool\nwhere\n    T: Resource,\n    T::DynamicType: Default,\n{\n    let dt = Default::default();\n    client\n        .list_api_group_resources(&T::api_version(&dt))\n        .await\n        .ok()\n        .iter()\n        .flat_map(|r| r.resources.iter())\n        .any(|r| r.kind == T::kind(&dt))\n}\n\n// A watch that uses DeserializeGuard to skip resources which fail to deserialize.\n// Any deserialization errors are logged as warnings and the event is skipped.\nfn guarded_watch<R, T>(\n    runtime: &mut kubert::Runtime<T>,\n    watcher_config: watcher::Config,\n) -> impl Stream<Item = watcher::Event<R>>\nwhere\n    R: Resource + DeserializeOwned + Clone + Debug + Send + 'static,\n    R::DynamicType: Default,\n{\n    runtime\n        .watch_all::<DeserializeGuard<R>>(watcher_config)\n        .filter_map(async |item| {\n            let log_warning = |t: &DeserializeGuard<R>| {\n                if let Err(ref err) = t.0 {\n                    tracing::warn!(\n                        \"skipping invalid {} resource {}/{}: {}\",\n                        R::kind(&Default::default()),\n                        t.namespace().unwrap_or(\"<cluster>\".to_string()),\n                        t.name_any(),\n                        err,\n                    );\n                }\n            };\n            match item {\n                watcher::Event::Apply(t) => {\n                    log_warning(&t);\n                    t.0.ok().map(watcher::Event::Apply)\n                }\n                watcher::Event::Delete(t) => {\n                    log_warning(&t);\n                    t.0.ok().map(watcher::Event::Delete)\n                }\n                watcher::Event::Init => Some(watcher::Event::<R>::Init),\n                watcher::Event::InitApply(t) => {\n                    log_warning(&t);\n                    t.0.ok().map(watcher::Event::InitApply)\n                }\n                watcher::Event::InitDone => Some(watcher::Event::<R>::InitDone),\n            }\n        })\n}\n"
  },
  {
    "path": "policy-controller/runtime/src/index_list.rs",
    "content": "use std::sync::Arc;\n\nuse kubert::index::IndexNamespacedResource;\nuse parking_lot::RwLock;\n\n/// A list of indexes for a specific resource type.\n///\n/// An `IndexList` itself can then act as an index for that resource, and fans updates\n/// out to each index in the list by cloning the update.\npub struct IndexList<A, T = A> {\n    index: Arc<RwLock<A>>,\n    tail: Option<T>,\n}\n\nimpl<A, T, R> IndexNamespacedResource<R> for IndexList<A, T>\nwhere\n    A: IndexNamespacedResource<R>,\n    T: IndexNamespacedResource<R>,\n    R: Clone,\n{\n    fn apply(&mut self, resource: R) {\n        if let Some(tail) = &mut self.tail {\n            tail.apply(resource.clone());\n        }\n        self.index.write().apply(resource);\n    }\n\n    fn delete(&mut self, namespace: String, name: String) {\n        if let Some(tail) = &mut self.tail {\n            tail.delete(namespace.clone(), name.clone());\n        }\n        self.index.write().delete(namespace, name);\n    }\n}\n\nimpl<A, T> IndexList<A, T> {\n    pub fn push<B>(self, index: Arc<RwLock<B>>) -> IndexList<B, IndexList<A, T>> {\n        IndexList {\n            index,\n            tail: Some(self),\n        }\n    }\n\n    pub fn shared(self) -> Arc<RwLock<Self>> {\n        Arc::new(RwLock::new(self))\n    }\n}\n\nimpl<A> IndexList<A> {\n    /// Returns a new `IndexList`.\n    ///\n    /// The second type parameter in the return value here can be anything that\n    /// implements `IndexNamespacedResource<R>`, since it will just be `None`.\n    /// Ideally, the type should be `!` (bottom) but `A` is conveniently available,\n    /// so we use that.\n    pub fn new(index: Arc<RwLock<A>>) -> IndexList<A, A> {\n        IndexList { index, tail: None }\n    }\n}\n"
  },
  {
    "path": "policy-controller/runtime/src/lease.rs",
    "content": "use crate::k8s::{self, api::apps::v1::Deployment, ObjectMeta, Resource};\nuse anyhow::Result;\nuse k8s_openapi::api::coordination::v1 as coordv1;\nuse kube::api::PatchParams;\nuse std::sync::Arc;\nuse tokio::{sync::watch, time};\n\nconst LEASE_DURATION: time::Duration = time::Duration::from_secs(30);\nconst LEASE_NAME: &str = \"policy-controller-write\";\nconst RENEW_GRACE_PERIOD: time::Duration = time::Duration::from_secs(1);\nconst FIELD_MANAGER: &str = \"policy-controller\";\n\npub async fn init<T>(\n    runtime: &kubert::Runtime<T>,\n    namespace: &str,\n    deployment_name: &str,\n    claimant: &str,\n) -> Result<watch::Receiver<Arc<kubert::lease::Claim>>> {\n    let params = kubert::LeaseParams {\n        name: LEASE_NAME.to_string(),\n        namespace: namespace.to_string(),\n        claimant: claimant.to_string(),\n        lease_duration: LEASE_DURATION,\n        renew_grace_period: RENEW_GRACE_PERIOD,\n        field_manager: Some(FIELD_MANAGER.into()),\n    };\n\n    // Fetch the policy-controller deployment so that we can use it as an owner\n    // reference of the Lease.\n    let api = k8s::Api::<Deployment>::namespaced(runtime.client(), namespace);\n    let mut tries = 3;\n    let deployment = loop {\n        tries -= 1;\n        let error = match api.get(deployment_name).await {\n            Ok(deploy) => {\n                tracing::debug!(?deploy, \"Found Deployment\");\n                break deploy;\n            }\n            Err(k8s::Error::Api(error)) => error.into(),\n            Err(k8s::Error::Service(error)) => error,\n            Err(k8s::Error::HyperError(error)) => error.into(),\n            Err(error) => {\n                return Err(error.into());\n            }\n        };\n        if tries == 0 {\n            anyhow::bail!(error);\n        }\n        tracing::warn!(?error, \"Failed to fetch deployment, retrying in 1s...\");\n        time::sleep(time::Duration::from_secs(1)).await;\n    };\n\n    let patch = kube::api::Patch::Apply(coordv1::Lease {\n        metadata: ObjectMeta {\n            name: Some(params.name.clone()),\n            namespace: Some(params.namespace.clone()),\n            // Specifying a resource version of \"0\" means that we will\n            // only create the Lease if it does not already exist.\n            resource_version: Some(\"0\".to_string()),\n            owner_references: Some(vec![deployment.controller_owner_ref(&()).unwrap()]),\n            labels: Some(\n                [\n                    (\n                        \"linkerd.io/control-plane-component\".to_string(),\n                        \"destination\".to_string(),\n                    ),\n                    (\n                        \"linkerd.io/control-plane-ns\".to_string(),\n                        params.namespace.clone(),\n                    ),\n                ]\n                .into_iter()\n                .collect(),\n            ),\n            ..Default::default()\n        },\n        spec: None,\n    });\n    let patch_params = PatchParams {\n        field_manager: Some(\"policy-controller\".to_string()),\n        ..Default::default()\n    };\n    let api = k8s::Api::<coordv1::Lease>::namespaced(runtime.client(), namespace);\n\n    // An individual request may timeout or hit a transient error, so we try up to 3 times with a brief pause.\n    let mut tries = 3;\n    loop {\n        tries -= 1;\n        let error = match api.patch(LEASE_NAME, &patch_params, &patch).await {\n            Ok(lease) => {\n                tracing::info!(?lease, \"Created Lease\");\n                break;\n            }\n            Err(k8s::Error::Api(error)) if error.code >= 500 => error.into(),\n            Err(k8s::Error::Api(error)) => {\n                tracing::debug!(?error, \"Lease already exists\");\n                break;\n            }\n            Err(k8s::Error::Service(error)) => error,\n            Err(k8s::Error::HyperError(error)) => error.into(),\n            Err(error) => {\n                return Err(error.into());\n            }\n        };\n        if tries == 0 {\n            anyhow::bail!(error);\n        }\n        tracing::warn!(?error, \"Failed to create Lease, retrying in 1s...\");\n        time::sleep(time::Duration::from_secs(1)).await;\n    }\n\n    let (claim, _task) = runtime.spawn_lease(params).await?;\n    Ok(claim)\n}\n"
  },
  {
    "path": "policy-controller/runtime/src/lib.rs",
    "content": "pub use linkerd_policy_controller_core as core;\npub use linkerd_policy_controller_grpc as grpc;\npub use linkerd_policy_controller_k8s_api as k8s;\npub use linkerd_policy_controller_k8s_index as index;\npub use linkerd_policy_controller_k8s_status as status;\n\nmod admission;\nmod args;\nmod index_list;\nmod validation;\n\nmod lease;\npub use self::args::Args;\n\nuse std::{net::IpAddr, num::NonZeroU16};\n\n#[derive(Clone, Debug)]\nstruct InboundDiscover(index::inbound::SharedIndex);\n\n#[derive(Clone, Debug)]\nstruct OutboundDiscover(index::outbound::SharedIndex);\n\nimpl InboundDiscover {\n    pub fn new(index: index::inbound::SharedIndex) -> Self {\n        Self(index)\n    }\n}\n\nimpl OutboundDiscover {\n    pub fn new(index: index::outbound::SharedIndex) -> Self {\n        Self(index)\n    }\n}\n\n#[async_trait::async_trait]\nimpl core::inbound::DiscoverInboundServer<(grpc::workload::Workload, NonZeroU16)>\n    for InboundDiscover\n{\n    async fn get_inbound_server(\n        &self,\n        (workload, port): (grpc::workload::Workload, NonZeroU16),\n    ) -> anyhow::Result<Option<core::inbound::InboundServer>> {\n        let grpc::workload::Workload { namespace, kind } = workload;\n        let rx = match kind {\n            grpc::workload::Kind::External(name) => self\n                .0\n                .write()\n                .external_workload_server_rx(&namespace, &name, port),\n            grpc::workload::Kind::Pod(name) => {\n                self.0.write().pod_server_rx(&namespace, &name, port)\n            }\n        };\n\n        if let Ok(rx) = rx {\n            let server = (*rx.borrow()).clone();\n            Ok(Some(server))\n        } else {\n            Ok(None)\n        }\n    }\n\n    async fn watch_inbound_server(\n        &self,\n        (workload, port): (grpc::workload::Workload, NonZeroU16),\n    ) -> anyhow::Result<Option<core::inbound::InboundServerStream>> {\n        let grpc::workload::Workload { namespace, kind } = workload;\n        let rx = match kind {\n            grpc::workload::Kind::External(name) => self\n                .0\n                .write()\n                .external_workload_server_rx(&namespace, &name, port),\n            grpc::workload::Kind::Pod(name) => {\n                self.0.write().pod_server_rx(&namespace, &name, port)\n            }\n        };\n\n        if let Ok(rx) = rx {\n            Ok(Some(Box::pin(tokio_stream::wrappers::WatchStream::new(rx))))\n        } else {\n            Ok(None)\n        }\n    }\n}\n\n#[async_trait::async_trait]\nimpl\n    core::outbound::DiscoverOutboundPolicy<\n        core::outbound::ResourceTarget,\n        core::outbound::OutboundDiscoverTarget,\n    > for OutboundDiscover\n{\n    async fn get_outbound_policy(\n        &self,\n        resource: core::outbound::ResourceTarget,\n    ) -> anyhow::Result<Option<core::outbound::OutboundPolicy>> {\n        let rx = match self.0.write().outbound_policy_rx(resource.clone()) {\n            Ok(rx) => rx,\n            Err(error) => {\n                tracing::error!(%error, \"Failed to get outbound policy rx\");\n                return Ok(None);\n            }\n        };\n\n        let policy = (*rx.borrow()).clone();\n        Ok(Some(policy))\n    }\n\n    async fn watch_outbound_policy(\n        &self,\n        target: core::outbound::ResourceTarget,\n    ) -> anyhow::Result<Option<core::outbound::OutboundPolicyStream>> {\n        match self.0.write().outbound_policy_rx(target) {\n            Ok(rx) => Ok(Some(Box::pin(tokio_stream::wrappers::WatchStream::new(rx)))),\n            Err(_) => Ok(None),\n        }\n    }\n\n    async fn watch_external_policy(&self) -> core::outbound::ExternalPolicyStream {\n        Box::pin(tokio_stream::wrappers::WatchStream::new(\n            self.0.read().fallback_policy_rx(),\n        ))\n    }\n\n    fn lookup_ip(\n        &self,\n        addr: IpAddr,\n        port: NonZeroU16,\n        source_namespace: String,\n    ) -> Option<core::outbound::OutboundDiscoverTarget> {\n        let index = self.0.read();\n        if let Some(target) = index.lookup_service(addr, port, source_namespace.clone()) {\n            return Some(target);\n        }\n\n        if let Some((namespace, name)) = index.lookup_egress_network(addr, source_namespace.clone())\n        {\n            return Some(core::outbound::OutboundDiscoverTarget::Resource(\n                core::outbound::ResourceTarget {\n                    name,\n                    namespace,\n                    port,\n                    source_namespace,\n                    kind: core::outbound::Kind::EgressNetwork(std::net::SocketAddr::new(\n                        addr,\n                        port.into(),\n                    )),\n                },\n            ));\n        }\n\n        if !index.is_address_in_cluster(addr) {\n            return Some(core::outbound::OutboundDiscoverTarget::External(\n                std::net::SocketAddr::new(addr, port.into()),\n            ));\n        }\n\n        None\n    }\n}\n"
  },
  {
    "path": "policy-controller/runtime/src/validation.rs",
    "content": "use anyhow::Result;\nuse thiserror::Error;\n\nuse regex::Regex;\n\nconst SCHEME_PREFIX: &str = \"spiffe://\";\nconst VALID_TRUST_DOMAIN_CHARS: &str = \"abcdefghijklmnopqrstuvwxyz0123456789-._\";\nconst VALID_PATH_SEGMENT_CHARS: &str =\n    \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._\";\n\nconst DNS_LIKE_IDENTITY_REGEX: &str =\n    r\"^(\\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\";\n\n#[derive(Debug, Error, PartialEq, Clone)]\npub enum IdError {\n    /// The trust domain name of SPIFFE ID cannot be empty.\n    #[error(\"SPIFFE trust domain is missing\")]\n    MissingTrustDomain,\n\n    /// A trust domain name can only contain chars in a limited char set.\n    #[error(\n        \"SPIFFE trust domain characters are limited to lowercase letters, numbers, dots, dashes, and \\\n         underscores\"\n    )]\n    BadTrustDomainChar,\n\n    /// A path segment can only contain chars in a limited char set.\n    #[error(\n        \"SPIFFE path segment characters are limited to letters, numbers, dots, dashes, and underscores\"\n    )]\n    BadPathSegmentChar,\n\n    /// Path cannot contain empty segments, e.g '//'\n    #[error(\"SPIFFE path cannot contain empty segments\")]\n    EmptySegment,\n\n    /// Path cannot contain dot segments, e.g '/.', '/..'\n    #[error(\"SPIFFE path cannot contain dot segments\")]\n    DotSegment,\n\n    /// Path cannot have a trailing slash.\n    #[error(\"SPIFFE path cannot have a trailing slash\")]\n    TrailingSlash,\n\n    #[error(\n        \"identity must be a valid SPIFFE id or a DNS SAN, matching the regex: {}\",\n        DNS_LIKE_IDENTITY_REGEX\n    )]\n    Invalid,\n}\n\n// Validates that an ID is either in DNS or SPIFFE form. SPIFFE\n// validation is based on https://github.com/spiffe/spiffe/blob/27b59b81ba8c56885ac5d4be73b35b9b3305fd7a/standards/SPIFFE-ID.md.\n// Implementation is based on: https://github.com/maxlambrecht/rust-spiffe/blob/3d3614f70d0d7a4b9190ab9650e224f2ac362368/spiffe/src/spiffe_id/mod.rs\npub(crate) fn validate_identity(id: &str) -> Result<(), IdError> {\n    if let Some(rest) = id.strip_prefix(SCHEME_PREFIX) {\n        let i = rest.find('/').unwrap_or(rest.len());\n\n        if i == 0 {\n            return Err(IdError::MissingTrustDomain);\n        }\n\n        let td = &rest[..i];\n        if td.chars().any(|c| !VALID_TRUST_DOMAIN_CHARS.contains(c)) {\n            return Err(IdError::BadTrustDomainChar);\n        }\n\n        let path = &rest[i..];\n\n        if !path.is_empty() {\n            validate_path(path)?;\n        }\n\n        Ok(())\n    } else {\n        let regex = Regex::new(DNS_LIKE_IDENTITY_REGEX).expect(\"should_compile\");\n        if !regex.is_match(id) {\n            return Err(IdError::Invalid);\n        }\n        Ok(())\n    }\n}\n\n/// Validates that a path string is a conformant path for a SPIFFE ID.\n/// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path\nfn validate_path(path: &str) -> Result<(), IdError> {\n    let chars = path.char_indices().peekable();\n    let mut segment_start = 0;\n\n    for (idx, c) in chars {\n        if c == '/' {\n            match &path[segment_start..idx] {\n                \"/\" => return Err(IdError::EmptySegment),\n                \"/.\" | \"/..\" => return Err(IdError::DotSegment),\n                _ => {}\n            }\n            segment_start = idx;\n            continue;\n        }\n\n        if !VALID_PATH_SEGMENT_CHARS.contains(c) {\n            return Err(IdError::BadPathSegmentChar);\n        }\n    }\n\n    match &path[segment_start..] {\n        \"/\" => return Err(IdError::TrailingSlash),\n        \"/.\" | \"/..\" => return Err(IdError::DotSegment),\n        _ => {}\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn valid_dns() {\n        assert!(validate_identity(\"system.local\").is_ok())\n    }\n\n    #[test]\n    fn valid_dns_all() {\n        assert!(validate_identity(\"*\").is_ok())\n    }\n\n    #[test]\n    fn valid_dns_prefix() {\n        assert!(validate_identity(\"*.system.local\").is_ok())\n    }\n\n    #[test]\n    fn invalid_dns_suffix() {\n        let err = validate_identity(\"system.local.*\").unwrap_err();\n        assert_eq!(err, IdError::Invalid);\n    }\n\n    #[test]\n    fn invalid_dns_trailing_dot() {\n        let err = validate_identity(\"system.local.\").unwrap_err();\n        assert_eq!(err, IdError::Invalid);\n    }\n\n    #[test]\n    fn invalid_dns_leading_dot() {\n        let err = validate_identity(\".system.local\").unwrap_err();\n        assert_eq!(err, IdError::Invalid);\n    }\n\n    #[test]\n    fn invalid_dns_double_dots() {\n        let err = validate_identity(\"system..local\").unwrap_err();\n        assert_eq!(err, IdError::Invalid);\n    }\n\n    #[test]\n    fn valid_spiffe_no_path() {\n        assert!(validate_identity(\"spiffe://trustdomain\").is_ok())\n    }\n\n    #[test]\n    fn valid_spiffe_with_path() {\n        assert!(validate_identity(\"spiffe://trustdomain/path/element\").is_ok())\n    }\n\n    #[test]\n    fn invalid_spiffe_scheme() {\n        let err = validate_identity(\"http://domain.test/path/element\").unwrap_err();\n        assert_eq!(err, IdError::Invalid);\n    }\n\n    #[test]\n    fn invalid_spiffe_wrong_scheme() {\n        let err = validate_identity(\"spiffe:/path/element\").unwrap_err();\n        assert_eq!(err, IdError::Invalid);\n    }\n\n    #[test]\n    fn invalid_spiffe_empty_trust_domain() {\n        let err = validate_identity(\"spiffe:///path/element\").unwrap_err();\n        assert_eq!(err, IdError::MissingTrustDomain);\n    }\n\n    #[test]\n    fn invalid_spiffe_no_slashes_in_scheme() {\n        let err = validate_identity(\"spiffe:path/element\").unwrap_err();\n        assert_eq!(err, IdError::Invalid);\n    }\n\n    #[test]\n    fn invalid_spiffe_uri_with_query() {\n        let err = validate_identity(\"spiffe://domain.test/path/element?query=\").unwrap_err();\n        assert_eq!(err, IdError::BadPathSegmentChar);\n    }\n\n    #[test]\n    fn invalid_spiffe_uri_with_fragment() {\n        let err = validate_identity(\"spiffe://domain.test/path/element#fragment-1\").unwrap_err();\n        assert_eq!(err, IdError::BadPathSegmentChar);\n    }\n\n    #[test]\n    fn invalid_spiffe_uri_with_str_port() {\n        let err = validate_identity(\"spiffe://domain.test:8080/path/element\").unwrap_err();\n        assert_eq!(err, IdError::BadTrustDomainChar);\n    }\n\n    #[test]\n    fn invalid_spiffe_uri_with_user_info() {\n        let err = validate_identity(\"spiffe://user:password@test.org/path/element\").unwrap_err();\n        assert_eq!(err, IdError::BadTrustDomainChar);\n    }\n\n    #[test]\n    fn invalid_spiffe_uri_with_trailing_slash() {\n        let err = validate_identity(\"spiffe://test.org/\").unwrap_err();\n        assert_eq!(err, IdError::TrailingSlash);\n    }\n\n    #[test]\n    fn invalid_spiffe_uri_with_empty_segment() {\n        let err = validate_identity(\"spiffe://test.org//\").unwrap_err();\n        assert_eq!(err, IdError::EmptySegment);\n    }\n\n    #[test]\n    fn invalid_spiffe_uri_str_with_path_with_trailing_slash() {\n        let err = validate_identity(\"spiffe://test.org/path/other/\").unwrap_err();\n        assert_eq!(err, IdError::TrailingSlash);\n    }\n\n    #[test]\n    fn invalid_spiffe_uri_str_with_dot_segment() {\n        let err = validate_identity(\"spiffe://test.org/./other\").unwrap_err();\n        assert_eq!(err, IdError::DotSegment);\n    }\n\n    #[test]\n    fn invalid_spiffe_uri_str_with_double_dot_segment() {\n        let err = validate_identity(\"spiffe://test.org/../other\").unwrap_err();\n        assert_eq!(err, IdError::DotSegment);\n    }\n}\n"
  },
  {
    "path": "policy-controller/src/main.rs",
    "content": "#![deny(warnings, rust_2018_idioms)]\n#![forbid(unsafe_code)]\n\n#[cfg(all(target_os = \"linux\", target_arch = \"x86_64\", target_env = \"gnu\"))]\n#[global_allocator]\nstatic GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;\n\n#[tokio::main]\nasync fn main() -> anyhow::Result<()> {\n    if rustls::crypto::aws_lc_rs::default_provider()\n        .install_default()\n        .is_err()\n    {\n        anyhow::bail!(\"No other crypto provider should be installed yet\");\n    }\n\n    linkerd_policy_controller_runtime::Args::parse_and_run().await\n}\n"
  },
  {
    "path": "policy-test/Cargo.toml",
    "content": "[package]\nname = \"linkerd-policy-test\"\nversion = \"0.1.0\"\nedition = \"2021\"\nlicense = \"Apache-2.0\"\npublish = false\n\n[features]\ndefault = [\"gateway-api-experimental\"]\ngateway-api-experimental = []\n\n[dependencies]\nanyhow = \"1\"\nbytes = \"1\"\nhttp-body-util = \"0.1\"\nhyper = { workspace = true, features = [\"client\", \"http2\"] }\nhyper-util = { workspace = true }\nfutures = { version = \"0.3\", default-features = false }\nipnet = \"2\"\nk8s-openapi = { workspace = true }\nmaplit = \"1\"\nrand = \"0.10\"\nserde = \"1\"\nserde_json = \"1\"\nschemars = \"0.8\"\ntonic = { workspace = true }\ntokio = { version = \"1\", features = [\"macros\", \"rt\"] }\ntower = { workspace = true }\ntracing = \"0.1\"\ntracing-subscriber = { version = \"0.3\", features = [\"env-filter\"] }\n\nlinkerd-policy-controller-core = { path = \"../policy-controller/core\" }\nlinkerd-policy-controller-k8s-api = { path = \"../policy-controller/k8s/api\" }\nlinkerd-policy-controller-grpc = { path = \"../policy-controller/grpc\" }\n\n[dependencies.kube]\nworkspace = true\ndefault-features = false\nfeatures = [\"client\", \"rustls-tls\", \"aws-lc-rs\", \"runtime\", \"ws\"]\n\n[dependencies.linkerd2-proxy-api]\nworkspace = true\nfeatures = [\"inbound\", \"outbound\"]\n\n[dev-dependencies]\ntokio-test = \"0.4\"\nregex = \"1\"\n"
  },
  {
    "path": "policy-test/README.md",
    "content": "# Policy controller tests\n\nThe `policy-test` crate includes integration tests for the policy controller.\n\n## Running locally\n\n```sh\n:; just policy-test\n```\n\n## Running in CI\n\nSee the [workflow](.github/workflows/policy_controller.yml).\n"
  },
  {
    "path": "policy-test/src/admission.rs",
    "content": "use crate::with_temp_ns;\n\npub async fn accepts<F, T>(f: F)\nwhere\n    F: FnOnce(String) -> T + Send + 'static,\n    T: Clone\n        + Send\n        + Sync\n        + std::fmt::Debug\n        + kube::Resource<Scope = kube::core::NamespaceResourceScope>\n        + serde::de::DeserializeOwned\n        + serde::Serialize,\n    T::DynamicType: Default,\n{\n    with_temp_ns(|client, ns| async move {\n        let api = kube::Api::namespaced(client, &ns);\n        let obj = f(ns);\n        let res = api.create(&kube::api::PostParams::default(), &obj).await;\n        res.expect(\"resource must apply\");\n    })\n    .await;\n}\n\npub async fn rejects<F, T>(f: F)\nwhere\n    F: FnOnce(String) -> T + Send + 'static,\n    T: Clone\n        + Send\n        + Sync\n        + std::fmt::Debug\n        + kube::Resource<Scope = kube::core::NamespaceResourceScope>\n        + serde::de::DeserializeOwned\n        + serde::Serialize,\n    T::DynamicType: Default,\n{\n    with_temp_ns(|client, ns| async move {\n        let api = kube::Api::namespaced(client, &ns);\n        let obj = f(ns);\n        let res = api.create(&kube::api::PostParams::default(), &obj).await;\n        res.expect_err(\"resource must not apply\");\n    })\n    .await;\n}\n"
  },
  {
    "path": "policy-test/src/bb.rs",
    "content": "use k8s_openapi::apimachinery::pkg::util::intstr::IntOrString;\nuse linkerd_policy_controller_k8s_api::{self as k8s};\nuse maplit::{btreemap, convert_args};\n\n#[derive(Debug, Clone)]\npub struct Terminus {\n    name: String,\n    ns: String,\n    percent_failure: usize,\n}\n\nimpl Terminus {\n    const PORT: i32 = 80;\n    const PORT_NAME: &'static str = \"http\";\n    const APP: &'static str = \"bb-terminus\";\n    pub const SERVICE_NAME: &'static str = Self::APP;\n\n    /// Builds a `bb-terminus` pod that can be configured to fail some (or all)\n    /// requests.\n    pub fn new(ns: impl ToString) -> Self {\n        Self {\n            name: \"bb-terminus\".to_string(),\n            ns: ns.to_string(),\n            percent_failure: 0,\n        }\n    }\n\n    #[track_caller]\n    pub fn percent_failure(self, percent_failure: usize) -> Self {\n        assert!(percent_failure <= 100);\n        Self {\n            percent_failure,\n            ..self\n        }\n    }\n\n    /// Sets the `bb-terminus` pod's name. By default, the name is \"bb-terminus\".\n    pub fn named(self, name: impl ToString) -> Self {\n        Self {\n            name: name.to_string(),\n            ..self\n        }\n    }\n\n    /// Returns the constructed [`k8s::Pod`].\n    pub fn to_pod(self) -> k8s::Pod {\n        let args = [\n            \"terminus\",\n            \"--h1-server-port\",\n            \"80\",\n            \"--response-text\",\n            &self.name,\n            \"--percent-failure\",\n            &self.percent_failure.to_string(),\n        ]\n        .into_iter()\n        .map(String::from)\n        .collect();\n        k8s::Pod {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(self.ns),\n                name: Some(self.name),\n                annotations: Some(convert_args!(btreemap!(\n                    \"linkerd.io/inject\" => \"enabled\",\n                    \"config.linkerd.io/proxy-log-level\" => \"linkerd=trace,info\",\n                ))),\n                labels: Some(convert_args!(btreemap!(\n                    \"app\" => Self::APP,\n                ))),\n                ..Default::default()\n            },\n            spec: Some(k8s::PodSpec {\n                containers: vec![k8s::api::core::v1::Container {\n                    name: \"bb-terminus\".to_string(),\n                    image: Some(\"buoyantio/bb:latest\".to_string()),\n                    ports: Some(vec![k8s::api::core::v1::ContainerPort {\n                        name: Some(Self::PORT_NAME.to_string()),\n                        container_port: Self::PORT,\n                        ..Default::default()\n                    }]),\n                    args: Some(args),\n                    ..Default::default()\n                }],\n                ..Default::default()\n            }),\n            ..k8s::Pod::default()\n        }\n    }\n\n    /// Returns a ClusterIP [`k8s::api::core::v1::Service`] that selects pods\n    /// with the label `app: bb-terminus`.\n    pub fn service(ns: &str) -> k8s::api::core::v1::Service {\n        k8s::api::core::v1::Service {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(Self::SERVICE_NAME.to_string()),\n                ..Default::default()\n            },\n            spec: Some(k8s::api::core::v1::ServiceSpec {\n                type_: Some(\"ClusterIP\".to_string()),\n                selector: Some(convert_args!(btreemap!(\n                    \"app\" => Self::APP,\n                ))),\n                ports: Some(vec![k8s::api::core::v1::ServicePort {\n                    port: Self::PORT,\n                    target_port: Some(IntOrString::String(Self::PORT_NAME.to_string())),\n                    ..Default::default()\n                }]),\n                ..Default::default()\n            }),\n            ..Default::default()\n        }\n    }\n}\n"
  },
  {
    "path": "policy-test/src/curl.rs",
    "content": "use super::{create, create_ready_pod, LinkerdInject};\nuse kube::{api::LogParams, ResourceExt};\nuse linkerd_policy_controller_k8s_api::{self as k8s};\nuse maplit::{btreemap, convert_args};\nuse tokio::time;\n\n#[derive(Clone)]\n#[must_use]\npub struct Runner {\n    namespace: String,\n    client: kube::Client,\n}\n\n#[derive(Clone)]\npub struct Running {\n    namespace: String,\n    name: String,\n    client: kube::Client,\n}\n\n/// A handle to a running `curl` container which can perform multiple requests\n/// using `kubectl exec`.\n#[derive(Clone)]\npub struct Execable {\n    running: Running,\n    api: kube::Api<k8s::Pod>,\n}\n\nimpl Runner {\n    // @TODO(alpeb): point to `latest` once the fix for this bug is released:\n    // https://github.com/curl/curl/issues/17554\n    const CURL_IMAGE: &'static str = \"docker.io/curlimages/curl:8.13.0\";\n\n    pub async fn init(client: &kube::Client, ns: &str) -> Runner {\n        let runner = Runner {\n            namespace: ns.to_string(),\n            client: client.clone(),\n        };\n        tokio::time::timeout(tokio::time::Duration::from_secs(60), runner.create_rbac())\n            .await\n            .expect(\"must create RBAC within a minute\");\n\n        runner\n    }\n\n    /// Creates a configmap that prevents curl pods from executing.\n    pub async fn create_lock(&self) {\n        create(\n            &self.client,\n            k8s::api::core::v1::ConfigMap {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(self.namespace.clone()),\n                    name: Some(\"curl-lock\".to_string()),\n                    ..Default::default()\n                },\n                ..Default::default()\n            },\n        )\n        .await;\n    }\n\n    /// Deletes the lock configmap, allowing curl pods to execute.\n    pub async fn delete_lock(&self) {\n        tracing::trace!(ns = %self.namespace, \"Deleting curl-lock\");\n        let api = kube::Api::<k8s::api::core::v1::ConfigMap>::namespaced(\n            self.client.clone(),\n            &self.namespace,\n        );\n        kube::runtime::wait::delete::delete_and_finalize(\n            api,\n            \"curl-lock\",\n            &kube::api::DeleteParams::foreground(),\n        )\n        .await\n        .expect(\"curl-lock must be deleted\");\n        tracing::debug!(ns = %self.namespace, \"Deleted curl-lock\");\n    }\n\n    /// Runs a [`k8s::Pod`] that runs curl against the provided target URL.\n    ///\n    /// The pod:\n    /// - has the `linkerd.io/inject` annotation set, based on the\n    ///   `linkerd_inject` parameter;\n    /// - runs under the service account `curl`;\n    /// - does not actually execute curl until the `curl-lock` configmap is not\n    ///   present\n    pub async fn run(&self, name: &str, target_url: &str, inject: LinkerdInject) -> Running {\n        create(\n            &self.client,\n            Self::gen_pod(&self.namespace, name, target_url, inject),\n        )\n        .await;\n        Running {\n            client: self.client.clone(),\n            namespace: self.namespace.clone(),\n            name: name.to_string(),\n        }\n    }\n\n    /// Runs a [`k8s::Pod`] with a `curl` container which can execute HTTP\n    /// requests using `kubectl exec`.\n    ///\n    /// The pod:\n    /// - has the `linkerd.io/inject` annotation set, based on the\n    ///   `linkerd_inject` parameter;\n    /// - does not execute any requests unless [`Execable::get`] or\n    ///   [`Execable::exec`] are called on the returned [`Execable`] handle.\n    pub async fn run_execable(&self, name: &str, inject: LinkerdInject) -> Execable {\n        let pod = k8s::Pod {\n            metadata: Self::curl_meta(&self.namespace, name, inject),\n            spec: Some(k8s::PodSpec {\n                service_account: Some(\"curl\".to_string()),\n                containers: vec![k8s::api::core::v1::Container {\n                    name: \"curl\".to_string(),\n                    image: Some(Self::CURL_IMAGE.to_string()),\n                    command: Some(vec![\"sleep\".to_string(), \"infinite\".to_string()]),\n                    ..Default::default()\n                }],\n                restart_policy: Some(\"Never\".to_string()),\n                ..Default::default()\n            }),\n            ..k8s::Pod::default()\n        };\n        create_ready_pod(&self.client, pod).await;\n        let running = Running {\n            client: self.client.clone(),\n            namespace: self.namespace.clone(),\n            name: name.to_string(),\n        };\n        running.inits_complete().await;\n        let api = kube::Api::<k8s::Pod>::namespaced(running.client.clone(), &running.namespace);\n        Execable { running, api }\n    }\n\n    /// Creates a service account and RBAC to allow curl pods to watch the\n    /// curl-lock configmap.\n    async fn create_rbac(&self) {\n        create(\n            &self.client,\n            k8s::api::core::v1::ServiceAccount {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(self.namespace.clone()),\n                    name: Some(\"curl\".to_string()),\n                    ..Default::default()\n                },\n                ..Default::default()\n            },\n        )\n        .await;\n        super::await_service_account(&self.client, &self.namespace, \"curl\").await;\n\n        create(\n            &self.client,\n            k8s::api::rbac::v1::Role {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(self.namespace.clone()),\n                    name: Some(\"curl-lock\".to_string()),\n                    ..Default::default()\n                },\n                rules: Some(vec![k8s::api::rbac::v1::PolicyRule {\n                    api_groups: Some(vec![\"\".to_string()]),\n                    resources: Some(vec![\"configmaps\".to_string()]),\n                    verbs: vec![\"get\".to_string(), \"list\".to_string(), \"watch\".to_string()],\n                    ..Default::default()\n                }]),\n            },\n        )\n        .await;\n\n        create(\n            &self.client,\n            k8s::api::rbac::v1::RoleBinding {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(self.namespace.clone()),\n                    name: Some(\"curl-lock\".to_string()),\n                    ..Default::default()\n                },\n                role_ref: k8s::api::rbac::v1::RoleRef {\n                    api_group: \"rbac.authorization.k8s.io\".to_string(),\n                    kind: \"Role\".to_string(),\n                    name: \"curl-lock\".to_string(),\n                },\n                subjects: Some(vec![k8s::api::rbac::v1::Subject {\n                    kind: \"ServiceAccount\".to_string(),\n                    name: \"curl\".to_string(),\n                    namespace: Some(self.namespace.clone()),\n                    ..Default::default()\n                }]),\n            },\n        )\n        .await;\n    }\n\n    fn gen_pod(ns: &str, name: &str, target_url: &str, inject: LinkerdInject) -> k8s::Pod {\n        k8s::Pod {\n            metadata: Self::curl_meta(ns, name, inject),\n            spec: Some(k8s::PodSpec {\n                service_account: Some(\"curl\".to_string()),\n                init_containers: Some(vec![k8s::api::core::v1::Container {\n                    name: \"wait-for-web\".to_string(),\n                    image: Some(\"docker.io/chainguard/kubectl:latest-dev\".to_string()),\n                    // In CI, we can hit failures where the watch isn't updated\n                    // after the configmap is deleted, even with a long timeout.\n                    // Instead, we use a relatively short timeout and retry the\n                    // wait to get a better chance.\n                    command: Some(vec![\"sh\".to_string(), \"-c\".to_string()]),\n                    args: Some(vec![format!(\n                        \"for i in $(seq 12) ; do \\\n                            echo waiting 10s for curl-lock to be deleted ; \\\n                            if kubectl wait --timeout=10s --for=delete --namespace={ns} cm/curl-lock ; then \\\n                                echo curl-lock deleted ; \\\n                                exit 0 ; \\\n                            fi ; \\\n                        done ; \\\n                        exit 1\"\n                    )]),\n                    ..Default::default()\n                }]),\n                containers: vec![k8s::api::core::v1::Container {\n                    name: \"curl\".to_string(),\n                    image: Some(Self::CURL_IMAGE.to_string()),\n                    args: Some(\n                        vec![\"curl\", \"-sf\", \"-o\", \"/dev/null\", \"-w\", \"%{http_code}\", \"--max-time\", \"10\", \"--retry\", \"10\", \"--retry-delay\", \"2\", target_url]\n                            .into_iter()\n                            .map(Into::into)\n                            .collect(),\n                    ),\n                    ..Default::default()\n                }],\n                restart_policy: Some(\"Never\".to_string()),\n                ..Default::default()\n            }),\n            ..k8s::Pod::default()\n        }\n    }\n\n    fn curl_meta(ns: &str, name: &str, inject: LinkerdInject) -> k8s::ObjectMeta {\n        k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            annotations: Some(convert_args!(btreemap!(\n                \"linkerd.io/inject\" => inject.to_string(),\n                \"config.linkerd.io/proxy-log-level\" => \"linkerd=trace,info\",\n            ))),\n            ..Default::default()\n        }\n    }\n}\n\nimpl Running {\n    pub fn name(&self) -> &str {\n        &self.name\n    }\n\n    /// Waits for the pod to have an IP address and returns it.\n    pub async fn ip(&self) -> std::net::IpAddr {\n        super::await_pod_ip(&self.client, &self.namespace, &self.name).await\n    }\n\n    async fn finished(&self, api: &kube::Api<k8s::Pod>) -> k8s::Pod {\n        tracing::debug!(ns = %self.namespace, pod = %self.name, \"Waiting for exit code\");\n\n        let finished = kube::runtime::wait::await_condition(\n            api.clone(),\n            &self.name,\n            |obj: Option<&k8s::Pod>| -> bool { obj.and_then(get_exit_code).is_some() },\n        );\n        let pod = match time::timeout(time::Duration::from_secs(60), finished).await {\n            Ok(Ok(Some(pod))) => pod,\n            Ok(Ok(None)) => unreachable!(\"Condition must wait for pod\"),\n            Ok(Err(error)) => panic!(\"Failed to wait for exit code: {}: {}\", self.name, error),\n            Err(_timeout) => {\n                panic!(\"Timeout waiting for exit code: {}\", self.name);\n            }\n        };\n\n        let code = get_exit_code(&pod).expect(\"curl pod must have an exit code\");\n        tracing::debug!(pod = %self.name, %code, \"Curl exited\");\n        for c in &pod.spec.as_ref().unwrap().containers {\n            super::logs(&self.client, &self.namespace, &self.name, &c.name).await;\n        }\n\n        pod\n    }\n\n    /// Waits for the curl container to complete and returns its exit code.\n    pub async fn exit_code(self) -> i32 {\n        self.inits_complete().await;\n        let api = kube::Api::namespaced(self.client.clone(), &self.namespace);\n\n        let pod = self.finished(&api).await;\n        let code = get_exit_code(&pod).expect(\"curl pod must have an exit code\");\n\n        if let Err(error) = api\n            .delete(&self.name, &kube::api::DeleteParams::foreground())\n            .await\n        {\n            tracing::trace!(%error, name = %self.name, \"Failed to delete pod\");\n        }\n\n        code\n    }\n\n    /// Waits for the curl container to complete and returns the http status\n    /// code.\n    pub async fn http_status_code(self) -> hyper::StatusCode {\n        self.inits_complete().await;\n        let api = kube::Api::namespaced(self.client.clone(), &self.namespace);\n\n        let pod = self.finished(&api).await;\n        let log = api\n            .logs(\n                &pod.name_unchecked(),\n                &LogParams {\n                    container: Some(\"curl\".to_string()),\n                    ..Default::default()\n                },\n            )\n            .await\n            .expect(\"must be able to get curl logs\");\n\n        if let Err(error) = api\n            .delete(&self.name, &kube::api::DeleteParams::foreground())\n            .await\n        {\n            tracing::trace!(%error, name = %self.name, \"Failed to delete pod\");\n        }\n\n        let status_code = log.lines().last().expect(\"curl must emit a status code\");\n        hyper::StatusCode::try_from(status_code).expect(\"curl must emit a valid status code\")\n    }\n\n    async fn inits_complete(&self) {\n        let api = kube::Api::namespaced(self.client.clone(), &self.namespace);\n        let init_complete = kube::runtime::wait::await_condition(\n            api,\n            &self.name,\n            |pod: Option<&k8s::Pod>| -> bool {\n                if let Some(pod) = pod {\n                    if let Some(status) = pod.status.as_ref() {\n                        return status\n                            .init_container_statuses\n                            .iter()\n                            .flatten()\n                            .filter(|init| init.name != \"linkerd-proxy\")\n                            .all(|init| {\n                                init.state\n                                    .as_ref()\n                                    .map(|s| s.terminated.is_some())\n                                    .unwrap_or(false)\n                            });\n                    }\n                }\n                false\n            },\n        );\n\n        match time::timeout(time::Duration::from_secs(120), init_complete).await {\n            Ok(Ok(_pod)) => {}\n            Ok(Err(error)) => panic!(\"Failed to watch pod status: {}: {}\", self.name, error),\n            Err(_timeout) => {\n                panic!(\n                    \"Timeout waiting for init containers to complete: {}\",\n                    self.name\n                );\n            }\n        };\n    }\n}\n\nimpl Execable {\n    /// Execute the provided `command` in the `curl` pod, returning the process'\n    /// stdout as a `String`.\n    pub async fn exec<I, T>(&self, command: I) -> Result<String, Box<dyn std::error::Error>>\n    where\n        I: IntoIterator<Item = T> + std::fmt::Debug,\n        T: Into<String>,\n    {\n        use tokio::io::AsyncReadExt;\n        tracing::debug!(?command, \"curl::exec\");\n        let mut process = self\n            .api\n            .exec(\n                &self.running.name,\n                command,\n                &kube::api::AttachParams {\n                    container: Some(\"curl\".to_string()),\n                    stdout: true,\n                    stderr: true,\n                    ..Default::default()\n                },\n            )\n            .await\n            .expect(\"must be able to exec\");\n        let mut stdout = process.stdout().expect(\"AttachParams should have stdout\");\n        let mut stderr = process.stderr().expect(\"AttachParams should have stderr\");\n        process.join().await?;\n        let mut stdout_buf = String::new();\n        stdout.read_to_string(&mut stdout_buf).await?;\n        let mut stderr_buf = String::new();\n        match stderr.read_to_string(&mut stderr_buf).await {\n            Ok(_) => tracing::debug!(\"curl stderr:\\n{stderr_buf}\"),\n            Err(error) => tracing::warn!(\"Failed to read curl stderr: {error}\"),\n        }\n        Ok(stdout_buf)\n    }\n\n    /// Execute an HTTP GET request for the provided `target_url` in the `curl` pod, returning the\n    /// status code.\n    #[tracing::instrument(\n        level = \"debug\",\n        name = \"curl::get\",\n        skip(self),\n        ret(Display),\n        err(Display)\n    )]\n    pub async fn get(\n        &self,\n        target_url: &str,\n    ) -> Result<hyper::StatusCode, Box<dyn std::error::Error>> {\n        let command = [\n            \"curl\",\n            \"-sfv\",\n            \"-o\",\n            \"/dev/null\",\n            \"-w\",\n            \"%{http_code}\",\n            \"--max-time\",\n            \"10\",\n            target_url,\n        ];\n        self.exec(command)\n            .await?\n            .parse::<hyper::StatusCode>()\n            .map_err(Into::into)\n    }\n}\n\nfn get_exit_code(pod: &k8s::Pod) -> Option<i32> {\n    let c = pod\n        .status\n        .as_ref()?\n        .container_statuses\n        .as_ref()?\n        .iter()\n        .find(|c| c.name == \"curl\")?;\n    let code = c.state.as_ref()?.terminated.as_ref()?.exit_code;\n    Some(code)\n}\n"
  },
  {
    "path": "policy-test/src/grpc.rs",
    "content": "//! A gRPC client for the inbound policy API.\n//!\n//! This client currently discovers a destination controller pod via the k8s API and uses port\n//! forwarding to connect to a running instance.\n\nuse anyhow::Result;\nuse futures::{future, prelude::*};\npub use linkerd2_proxy_api::*;\nuse linkerd2_proxy_api::{\n    inbound::inbound_server_policies_client::InboundServerPoliciesClient,\n    outbound::outbound_policies_client::OutboundPoliciesClient,\n};\nuse linkerd_policy_controller_grpc::workload;\nuse linkerd_policy_controller_k8s_api::{self as k8s, ResourceExt};\nuse tokio::io;\n\n#[macro_export]\nmacro_rules! assert_is_default_all_unauthenticated {\n    ($config:expr) => {\n        assert_default_all_unauthenticated_labels!($config);\n        assert_eq!($config.authorizations.len(), 1);\n    };\n}\n\n#[macro_export]\nmacro_rules! assert_default_all_unauthenticated_labels {\n    ($config:expr) => {\n        assert_eq!(\n            $config.labels,\n            vec![\n                (\"group\".to_string(), \"\".to_string()),\n                (\"kind\".to_string(), \"default\".to_string()),\n                (\"name\".to_string(), \"all-unauthenticated\".to_string()),\n            ]\n            .into_iter()\n            .collect()\n        );\n    };\n}\n\n#[macro_export]\nmacro_rules! assert_protocol_detect {\n    ($config:expr) => {{\n        use linkerd2_proxy_api::inbound;\n\n        assert_eq!(\n            $config.protocol,\n            Some(inbound::ProxyProtocol {\n                kind: Some(inbound::proxy_protocol::Kind::Detect(\n                    inbound::proxy_protocol::Detect {\n                        timeout: Some(std::time::Duration::from_secs(10).try_into().unwrap()),\n                        http_routes: vec![\n                            $crate::grpc::defaults::http_route(),\n                            $crate::grpc::defaults::probe_route(),\n                        ],\n                        http_local_rate_limit: None,\n                    }\n                )),\n            }),\n        );\n    }};\n}\n\n#[macro_export]\nmacro_rules! assert_protocol_detect_external {\n    ($config:expr) => {{\n        use linkerd2_proxy_api::inbound;\n\n        assert_eq!(\n            $config.protocol,\n            Some(inbound::ProxyProtocol {\n                kind: Some(inbound::proxy_protocol::Kind::Detect(\n                    inbound::proxy_protocol::Detect {\n                        timeout: Some(std::time::Duration::from_secs(10).try_into().unwrap()),\n                        http_routes: vec![$crate::grpc::defaults::http_route()],\n                        http_local_rate_limit: None,\n                    }\n                ))\n            })\n        )\n    }};\n}\n\n#[macro_export]\nmacro_rules! assert_default_accrual_backoff {\n    ($backoff:expr) => {{\n        use linkerd2_proxy_api::outbound;\n        let default_backoff = outbound::ExponentialBackoff {\n            min_backoff: Some(std::time::Duration::from_secs(1).try_into().unwrap()),\n            max_backoff: Some(std::time::Duration::from_secs(60).try_into().unwrap()),\n            jitter_ratio: 0.5 as f32,\n        };\n        assert_eq!(&default_backoff, $backoff)\n    }};\n}\n\n#[derive(Debug)]\npub struct InboundPolicyClient {\n    client: InboundServerPoliciesClient<GrpcHttp>,\n}\n\n#[derive(Debug)]\npub struct OutboundPolicyClient {\n    client: OutboundPoliciesClient<GrpcHttp>,\n}\n\n#[derive(Debug)]\nstruct GrpcHttp {\n    tx: hyper::client::conn::http2::SendRequest<tonic::body::Body>,\n}\n\nasync fn get_policy_controller_pod(client: &kube::Client) -> Result<String> {\n    let params =\n        kube::api::ListParams::default().labels(\"linkerd.io/control-plane-component=destination\");\n    let mut pods = kube::Api::<k8s::Pod>::namespaced(client.clone(), \"linkerd\")\n        .list(&params)\n        .await?;\n    let pod = pods\n        .items\n        .pop()\n        .ok_or_else(|| anyhow::anyhow!(\"no destination controller pods found\"))?;\n    Ok(pod.name_unchecked())\n}\n\nasync fn connect_port_forward(\n    client: &kube::Client,\n    pod: &str,\n) -> Result<impl io::AsyncRead + io::AsyncWrite + Unpin> {\n    loop {\n        let mut pf = match kube::Api::<k8s::Pod>::namespaced(client.clone(), \"linkerd\")\n            .portforward(pod, &[8090])\n            .await\n        {\n            Err(kube::Error::UpgradeConnection(\n                kube::client::UpgradeConnectionError::ProtocolSwitch(status),\n            )) => {\n                tracing::info!(?status, \"Flakey port forward; retrying\");\n                tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;\n                continue;\n            }\n            res => res?,\n        };\n\n        let io = pf.take_stream(8090).expect(\"must have a stream\");\n        return Ok(io);\n    }\n}\n\n// === impl InboundPolicyClient ===\n\nimpl InboundPolicyClient {\n    pub async fn port_forwarded(client: &kube::Client) -> Self {\n        let pod = get_policy_controller_pod(client)\n            .await\n            .expect(\"failed to find a policy controller pod\");\n        let io = connect_port_forward(client, &pod)\n            .await\n            .expect(\"failed to establish a port forward\");\n        let http = GrpcHttp::handshake(io)\n            .await\n            .expect(\"failed to connect to the gRPC server\");\n        Self {\n            client: InboundServerPoliciesClient::new(http),\n        }\n    }\n\n    pub async fn get_port(\n        &mut self,\n        ns: &str,\n        pod: &str,\n        port: u16,\n    ) -> Result<inbound::Server, tonic::Status> {\n        let rsp = self\n            .client\n            .get_port(tonic::Request::new(inbound::PortSpec {\n                workload: format!(\"{ns}:{pod}\"),\n                port: port as u32,\n            }))\n            .await?;\n        Ok(rsp.into_inner())\n    }\n\n    //TODO (matei): we should move our tests over to the new token format\n    pub async fn watch_port(\n        &mut self,\n        ns: &str,\n        pod: &str,\n        port: u16,\n    ) -> Result<tonic::Streaming<inbound::Server>, tonic::Status> {\n        let rsp = self\n            .client\n            .watch_port(tonic::Request::new(inbound::PortSpec {\n                workload: format!(\"{ns}:{pod}\"),\n                port: port as u32,\n            }))\n            .await?;\n        Ok(rsp.into_inner())\n    }\n\n    //TODO (matei): see if we can collapse this into `get_port` once it supports\n    //new token format\n    pub async fn get_port_for_external_workload(\n        &mut self,\n        ns: &str,\n        name: &str,\n        port: u16,\n    ) -> Result<inbound::Server, tonic::Status> {\n        let token = serde_json::to_string(&workload::Workload {\n            kind: workload::Kind::External(name.into()),\n            namespace: ns.into(),\n        })\n        .unwrap();\n        let rsp = self\n            .client\n            .get_port(tonic::Request::new(inbound::PortSpec {\n                workload: token,\n                port: port as u32,\n            }))\n            .await?;\n\n        Ok(rsp.into_inner())\n    }\n\n    pub async fn watch_port_for_external_workload(\n        &mut self,\n        ns: &str,\n        name: &str,\n        port: u16,\n    ) -> Result<tonic::Streaming<inbound::Server>, tonic::Status> {\n        let token = serde_json::to_string(&workload::Workload {\n            kind: workload::Kind::External(name.into()),\n            namespace: ns.into(),\n        })\n        .unwrap();\n        let rsp = self\n            .client\n            .watch_port(tonic::Request::new(inbound::PortSpec {\n                workload: token,\n                port: port as u32,\n            }))\n            .await?;\n\n        Ok(rsp.into_inner())\n    }\n}\n\n// === impl OutboundPolicyClient ===\n\nimpl OutboundPolicyClient {\n    pub async fn port_forwarded(client: &kube::Client) -> Self {\n        let pod = get_policy_controller_pod(client)\n            .await\n            .expect(\"failed to find a policy controller pod\");\n        let io = connect_port_forward(client, &pod)\n            .await\n            .expect(\"failed to establish a port forward\");\n        let http = GrpcHttp::handshake(io)\n            .await\n            .expect(\"failed to connect to the gRPC server\");\n        Self {\n            client: OutboundPoliciesClient::new(http),\n        }\n    }\n\n    pub async fn get(\n        &mut self,\n        ns: &str,\n        svc: &k8s::Service,\n        port: u16,\n    ) -> Result<outbound::OutboundPolicy, tonic::Status> {\n        use std::net::Ipv4Addr;\n        let address = svc\n            .spec\n            .as_ref()\n            .expect(\"Service must have a spec\")\n            .cluster_ip\n            .as_ref()\n            .expect(\"Service must have a cluster ip\");\n        let ip = address.parse::<Ipv4Addr>().unwrap();\n        let rsp = self\n            .client\n            .get(tonic::Request::new(outbound::TrafficSpec {\n                source_workload: format!(\"{ns}:client\"),\n                target: Some(outbound::traffic_spec::Target::Addr(net::TcpAddress {\n                    ip: Some(net::IpAddress {\n                        ip: Some(net::ip_address::Ip::Ipv4(ip.into())),\n                    }),\n                    port: port as u32,\n                })),\n            }))\n            .await?;\n        Ok(rsp.into_inner())\n    }\n\n    pub async fn watch_ip(\n        &mut self,\n        ns: &str,\n        addr: &str,\n        port: u16,\n    ) -> Result<tonic::Streaming<outbound::OutboundPolicy>, tonic::Status> {\n        use std::net::Ipv4Addr;\n        let ip = addr.parse::<Ipv4Addr>().unwrap();\n        let rsp = self\n            .client\n            .watch(tonic::Request::new(outbound::TrafficSpec {\n                source_workload: format!(\"{ns}:client\"),\n                target: Some(outbound::traffic_spec::Target::Addr(net::TcpAddress {\n                    ip: Some(net::IpAddress {\n                        ip: Some(net::ip_address::Ip::Ipv4(ip.into())),\n                    }),\n                    port: port as u32,\n                })),\n            }))\n            .await?;\n        Ok(rsp.into_inner())\n    }\n}\n\n// === impl GrpcHttp ===\n\nimpl GrpcHttp {\n    async fn handshake<I>(io: I) -> Result<Self>\n    where\n        I: io::AsyncRead + io::AsyncWrite + Unpin + Send + 'static,\n    {\n        let (tx, conn) =\n            hyper::client::conn::http2::Builder::new(hyper_util::rt::TokioExecutor::new())\n                .handshake(hyper_util::rt::TokioIo::new(io))\n                .await?;\n        tokio::spawn(conn);\n        Ok(Self { tx })\n    }\n}\n\nimpl tower::Service<hyper::Request<tonic::body::Body>> for GrpcHttp {\n    type Response = hyper::Response<hyper::body::Incoming>;\n    type Error = hyper::Error;\n    type Future = future::BoxFuture<'static, Result<Self::Response, Self::Error>>;\n\n    fn poll_ready(\n        &mut self,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<Result<(), Self::Error>> {\n        self.tx.poll_ready(cx)\n    }\n\n    fn call(&mut self, req: hyper::Request<tonic::body::Body>) -> Self::Future {\n        let (mut parts, body) = req.into_parts();\n\n        let mut uri = parts.uri.into_parts();\n        uri.scheme = Some(hyper::http::uri::Scheme::HTTP);\n        uri.authority = Some(\n            \"linkerd-destination.linkerd.svc.cluster.local:8090\"\n                .parse()\n                .unwrap(),\n        );\n        parts.uri = hyper::Uri::from_parts(uri).unwrap();\n\n        self.tx\n            .send_request(hyper::Request::from_parts(parts, body))\n            .boxed()\n    }\n}\n\npub mod defaults {\n    use super::*;\n\n    pub fn proxy_protocol(\n        local_rate_limit: Option<inbound::HttpLocalRateLimit>,\n    ) -> inbound::ProxyProtocol {\n        use inbound::proxy_protocol::{Http1, Kind};\n        inbound::ProxyProtocol {\n            kind: Some(Kind::Http1(Http1 {\n                routes: vec![http_route(), probe_route()],\n                local_rate_limit,\n            })),\n        }\n    }\n\n    pub fn proxy_protocol_no_ratelimit() -> inbound::ProxyProtocol {\n        proxy_protocol(None)\n    }\n\n    pub fn proxy_protocol_external() -> inbound::ProxyProtocol {\n        use inbound::proxy_protocol::{Http1, Kind};\n        inbound::ProxyProtocol {\n            kind: Some(Kind::Http1(Http1 {\n                routes: vec![http_route()],\n                local_rate_limit: None,\n            })),\n        }\n    }\n\n    pub fn http_local_ratelimit(\n        name: &str,\n        rps_total: Option<u32>,\n        rps_identity: Option<u32>,\n        overrides: Vec<(u32, Vec<String>)>,\n    ) -> inbound::HttpLocalRateLimit {\n        use inbound::http_local_rate_limit::{r#override, Limit, Override};\n        use meta::{metadata, Metadata, Resource};\n\n        let overrides = overrides\n            .iter()\n            .map(|ovr| {\n                let identities = r#override::ClientIdentities {\n                    identities: ovr\n                        .1\n                        .iter()\n                        .map(|name| inbound::Identity {\n                            name: name.to_string(),\n                        })\n                        .collect(),\n                };\n                Override {\n                    limit: Some(Limit {\n                        requests_per_second: ovr.0,\n                    }),\n                    clients: Some(identities),\n                }\n            })\n            .collect();\n\n        inbound::HttpLocalRateLimit {\n            metadata: Some(Metadata {\n                kind: Some(metadata::Kind::Resource(Resource {\n                    group: \"policy.linkerd.io\".to_string(),\n                    kind: \"HTTPLocalRateLimitPolicy\".to_string(),\n                    name: name.to_owned(),\n                    ..Default::default()\n                })),\n            }),\n            total: rps_total.map(|requests_per_second| Limit {\n                requests_per_second,\n            }),\n            identity: rps_identity.map(|requests_per_second| Limit {\n                requests_per_second,\n            }),\n            overrides,\n        }\n    }\n\n    pub fn http_route() -> inbound::HttpRoute {\n        use http_route::{path_match, HttpRouteMatch, PathMatch};\n        use inbound::{http_route::Rule, HttpRoute};\n        use meta::{metadata, Metadata};\n\n        HttpRoute {\n            metadata: Some(Metadata {\n                kind: Some(metadata::Kind::Default(\"default\".to_owned())),\n            }),\n            rules: vec![Rule {\n                matches: vec![HttpRouteMatch {\n                    path: Some(PathMatch {\n                        kind: Some(path_match::Kind::Prefix(\"/\".to_owned())),\n                    }),\n                    ..HttpRouteMatch::default()\n                }],\n                ..Rule::default()\n            }],\n            ..HttpRoute::default()\n        }\n    }\n\n    pub fn probe_route() -> inbound::HttpRoute {\n        use http_route::{path_match, HttpRouteMatch, PathMatch};\n        use http_types::HttpMethod;\n        use inbound::{\n            authn::{Permit, PermitUnauthenticated},\n            http_route::Rule,\n            Authn, Authz, HttpRoute, Network,\n        };\n        use ipnet::IpNet;\n        use maplit::{convert_args, hashmap};\n        use meta::{metadata, Metadata};\n\n        HttpRoute {\n            metadata: Some(Metadata {\n                kind: Some(metadata::Kind::Default(\"probe\".to_string())),\n            }),\n            authorizations: vec![Authz {\n                networks: vec![\n                    Network {\n                        net: Some(\"0.0.0.0/0\".parse::<IpNet>().unwrap().into()),\n                        ..Network::default()\n                    },\n                    Network {\n                        net: Some(\"::/0\".parse::<IpNet>().unwrap().into()),\n                        ..Network::default()\n                    },\n                ],\n                authentication: Some(Authn {\n                    permit: Some(Permit::Unauthenticated(PermitUnauthenticated {})),\n                }),\n                labels: convert_args!(hashmap!(\n                    \"kind\" => \"default\",\n                    \"name\" => \"probe\",\n                    \"group\" => \"\",\n                )),\n                metadata: Some(Metadata {\n                    kind: Some(metadata::Kind::Default(\"probe\".to_string())),\n                }),\n            }],\n            rules: vec![Rule {\n                matches: vec![\n                    HttpRouteMatch {\n                        path: Some(PathMatch {\n                            kind: Some(path_match::Kind::Exact(\"/live\".to_string())),\n                        }),\n                        method: Some(HttpMethod {\n                            r#type: Some(http_types::http_method::Type::Registered(\n                                http_types::http_method::Registered::Get.into(),\n                            )),\n                        }),\n                        ..HttpRouteMatch::default()\n                    },\n                    HttpRouteMatch {\n                        path: Some(PathMatch {\n                            kind: Some(path_match::Kind::Exact(\"/ready\".to_string())),\n                        }),\n                        method: Some(HttpMethod {\n                            r#type: Some(http_types::http_method::Type::Registered(\n                                http_types::http_method::Registered::Get.into(),\n                            )),\n                        }),\n                        ..HttpRouteMatch::default()\n                    },\n                ],\n                ..Rule::default()\n            }],\n            ..HttpRoute::default()\n        }\n    }\n}\n"
  },
  {
    "path": "policy-test/src/lib.rs",
    "content": "#![deny(warnings, rust_2018_idioms)]\n#![allow(clippy::large_enum_variant)]\n#![forbid(unsafe_code)]\n\npub mod admission;\npub mod bb;\npub mod curl;\npub mod grpc;\npub mod outbound_api;\npub mod test_route;\npub mod web;\n\nuse kube::runtime::wait::Condition;\nuse linkerd_policy_controller_k8s_api::{self as k8s, gateway, policy, ResourceExt};\nuse maplit::{btreemap, convert_args};\nuse test_route::TestRoute;\nuse tokio::time;\nuse tracing::Instrument;\n\n#[derive(Copy, Clone, Debug)]\npub enum LinkerdInject {\n    Enabled,\n    Disabled,\n}\n\n#[derive(Clone, Debug)]\npub enum Resource {\n    Service(k8s::Service),\n    EgressNetwork(policy::EgressNetwork),\n}\n\n/// Creates a cluster-scoped resource.\npub async fn create_cluster_scoped<T>(client: &kube::Client, obj: T) -> T\nwhere\n    T: kube::Resource<Scope = kube::core::ClusterResourceScope>,\n    T: serde::Serialize + serde::de::DeserializeOwned + Clone + std::fmt::Debug,\n    T::DynamicType: Default,\n{\n    let params = kube::api::PostParams {\n        field_manager: Some(\"linkerd-policy-test\".to_string()),\n        ..Default::default()\n    };\n    let api = kube::Api::<T>::all(client.clone());\n    tracing::trace!(?obj, \"Creating\");\n    api.create(&params, &obj)\n        .await\n        .expect(\"failed to create resource\")\n}\n\n/// Creates a cluster-scoped resource.\npub async fn delete_cluster_scoped<T>(client: &kube::Client, obj: T)\nwhere\n    T: kube::Resource<Scope = kube::core::ClusterResourceScope>,\n    T: serde::Serialize + serde::de::DeserializeOwned + Clone + std::fmt::Debug,\n    T::DynamicType: Default,\n{\n    let params = kube::api::DeleteParams {\n        ..Default::default()\n    };\n    let api = kube::Api::<T>::all(client.clone());\n    tracing::trace!(?obj, \"Deleting\");\n    api.delete(&obj.name_unchecked(), &params).await.unwrap();\n}\n\n/// Creates a namespace-scoped resource.\npub async fn create<T>(client: &kube::Client, obj: T) -> T\nwhere\n    T: kube::Resource<Scope = kube::core::NamespaceResourceScope>,\n    T: serde::Serialize + serde::de::DeserializeOwned + Clone + std::fmt::Debug,\n    T::DynamicType: Default,\n{\n    let params = kube::api::PostParams {\n        field_manager: Some(\"linkerd-policy-test\".to_string()),\n        ..Default::default()\n    };\n    let api = obj\n        .namespace()\n        .map(|ns| kube::Api::<T>::namespaced(client.clone(), &ns))\n        .unwrap_or_else(|| kube::Api::<T>::default_namespaced(client.clone()));\n    tracing::trace!(?obj, \"Creating\");\n    api.create(&params, &obj)\n        .await\n        .expect(\"failed to create resource\")\n}\n\n/// Deletes a namespace-scoped resource.\npub async fn delete<T>(client: &kube::Client, obj: T)\nwhere\n    T: kube::Resource<Scope = kube::core::NamespaceResourceScope>,\n    T: serde::Serialize + serde::de::DeserializeOwned + Clone + std::fmt::Debug,\n    T::DynamicType: Default,\n{\n    let params = kube::api::DeleteParams::default();\n    let api = obj\n        .namespace()\n        .map(|ns| kube::Api::<T>::namespaced(client.clone(), &ns))\n        .unwrap_or_else(|| kube::Api::<T>::default_namespaced(client.clone()));\n\n    tracing::trace!(?obj, \"Deleting\");\n    api.delete(&obj.name_any(), &params)\n        .await\n        .expect(\"failed to delete resource\");\n}\n\n/// Updates a namespace-scoped resource.\npub async fn update<T>(client: &kube::Client, mut new: T) -> T\nwhere\n    T: kube::Resource<Scope = kube::core::NamespaceResourceScope>,\n    T: serde::Serialize + serde::de::DeserializeOwned + Clone + std::fmt::Debug,\n    T::DynamicType: Default,\n{\n    let params = kube::api::PostParams {\n        field_manager: Some(\"linkerd-policy-test\".to_string()),\n        ..Default::default()\n    };\n    let api = new\n        .namespace()\n        .map(|ns| kube::Api::<T>::namespaced(client.clone(), &ns))\n        .unwrap_or_else(|| kube::Api::<T>::default_namespaced(client.clone()));\n\n    let old = api.get_metadata(&new.name_unchecked()).await.unwrap();\n\n    new.meta_mut().resource_version = old.resource_version();\n    tracing::trace!(?new, \"Updating\");\n    api.replace(&new.name_unchecked(), &params, &new)\n        .await\n        .expect(\"failed to create resource\")\n}\n\npub async fn await_condition<T>(\n    client: &kube::Client,\n    ns: &str,\n    name: &str,\n    cond: impl kube::runtime::wait::Condition<T>,\n) -> Option<T>\nwhere\n    T: kube::Resource<Scope = kube::core::NamespaceResourceScope>,\n    T: serde::Serialize + serde::de::DeserializeOwned + Clone + std::fmt::Debug + Send + 'static,\n    T::DynamicType: Default,\n{\n    let api = kube::Api::namespaced(client.clone(), ns);\n    time::timeout(\n        time::Duration::from_secs(60),\n        kube::runtime::wait::await_condition(api, name, cond),\n    )\n    .await\n    .expect(\"condition timed out\")\n    .expect(\"API call failed\")\n}\n\n/// Creates a pod and waits for all of its containers to be ready.\npub async fn create_ready_pod(client: &kube::Client, pod: k8s::Pod) -> k8s::Pod {\n    let pod_ready = |obj: Option<&k8s::Pod>| -> bool {\n        if let Some(pod) = obj {\n            if let Some(status) = &pod.status {\n                if let Some(containers) = &status.container_statuses {\n                    return containers.iter().all(|c| c.ready);\n                }\n            }\n        }\n        false\n    };\n\n    let pod = create(client, pod).await;\n    let pod = await_condition(\n        client,\n        &pod.namespace().unwrap(),\n        &pod.name_unchecked(),\n        pod_ready,\n    )\n    .await\n    .unwrap();\n\n    tracing::trace!(\n        pod = %pod.name_any(),\n        ip = %pod\n            .status.as_ref().expect(\"pod must have a status\")\n            .pod_ips.as_ref().unwrap()[0]\n            .ip,\n        containers = ?pod\n            .spec.as_ref().expect(\"pod must have a spec\")\n            .containers.iter().map(|c| &*c.name).collect::<Vec<_>>(),\n        \"Ready\",\n    );\n\n    pod\n}\n\npub async fn await_pod_ip(client: &kube::Client, ns: &str, name: &str) -> std::net::IpAddr {\n    fn get_ip(pod: &k8s::Pod) -> Option<String> {\n        pod.status.as_ref()?.pod_ip.clone()\n    }\n\n    let pod = await_condition(client, ns, name, |obj: Option<&k8s::Pod>| -> bool {\n        if let Some(pod) = obj {\n            return get_ip(pod).is_some();\n        }\n        false\n    })\n    .await\n    .expect(\"must fetch pod\");\n    get_ip(&pod)\n        .expect(\"pod must have an IP\")\n        .parse()\n        .expect(\"pod IP must be valid\")\n}\n\n// Waits until an HttpRoute with the given namespace and name has been accepted.\npub async fn await_route_accepted<R: TestRoute>(client: &kube::Client, route: &R) {\n    await_condition(\n        client,\n        &route.namespace().unwrap(),\n        &route.name_unchecked(),\n        |obj: Option<&R>| -> bool {\n            obj.is_some_and(|route| {\n                let conditions = route\n                    .conditions()\n                    .unwrap_or_default()\n                    .into_iter()\n                    .cloned()\n                    .collect::<Vec<_>>();\n                is_status_accepted(&conditions)\n            })\n        },\n    )\n    .await;\n}\n\n// Waits until an HttpRoute with the given namespace and name has a status set\n// on it, then returns the generic route status representation.\npub async fn await_gateway_route_status(\n    client: &kube::Client,\n    ns: &str,\n    name: &str,\n) -> gateway::HTTPRouteStatus {\n    let route_status = await_condition(\n        client,\n        ns,\n        name,\n        |obj: Option<&gateway::HTTPRoute>| -> bool {\n            obj.and_then(|route| route.status.as_ref()).is_some()\n        },\n    )\n    .await\n    .expect(\"must fetch route\")\n    .status\n    .expect(\"route must contain a status representation\");\n    tracing::trace!(?route_status, name, ns, \"got route status\");\n    route_status\n}\n\n// Waits until an EgressNet with the given namespace and name has a status set.\npub async fn await_egress_net_status(\n    client: &kube::Client,\n    ns: &str,\n    name: &str,\n) -> policy::egress_network::EgressNetworkStatus {\n    let egress_net_status = await_condition(\n        client,\n        ns,\n        name,\n        |obj: Option<&policy::EgressNetwork>| -> bool {\n            obj.and_then(|en| en.status.as_ref()).is_some()\n        },\n    )\n    .await\n    .expect(\"must fetch route\")\n    .status\n    .expect(\"Egress net must contain a status representation\");\n    tracing::trace!(?egress_net_status, name, ns, \"got egress net status\");\n    egress_net_status\n}\n\n// Waits until an GrpcRoute with the given namespace and name has a status set\n// on it, then returns the generic route status representation.\npub async fn await_grpc_route_status(\n    client: &kube::Client,\n    ns: &str,\n    name: &str,\n) -> gateway::GRPCRouteStatus {\n    let route_status = await_condition(\n        client,\n        ns,\n        name,\n        |obj: Option<&gateway::GRPCRoute>| -> bool {\n            obj.and_then(|route| route.status.as_ref()).is_some()\n        },\n    )\n    .await\n    .expect(\"must fetch route\")\n    .status\n    .expect(\"route must contain a status representation\");\n    tracing::trace!(?route_status, name, ns, \"got route status\");\n    route_status\n}\n\n#[cfg(feature = \"gateway-api-experimental\")]\n// Waits until an TlsRoute with the given namespace and name has a status set\n// on it, then returns the generic route status representation.\npub async fn await_tls_route_status(\n    client: &kube::Client,\n    ns: &str,\n    name: &str,\n) -> gateway::TLSRouteStatus {\n    let route_status = await_condition(\n        client,\n        ns,\n        name,\n        |obj: Option<&gateway::TLSRoute>| -> bool {\n            obj.and_then(|route| route.status.as_ref()).is_some()\n        },\n    )\n    .await\n    .expect(\"must fetch route\")\n    .status\n    .expect(\"route must contain a status representation\");\n    tracing::trace!(?route_status, name, ns, \"got route status\");\n    route_status\n}\n\n#[cfg(feature = \"gateway-api-experimental\")]\n// Waits until an TcpRoute with the given namespace and name has a status set\n// on it, then returns the generic route status representation.\npub async fn await_tcp_route_status(\n    client: &kube::Client,\n    ns: &str,\n    name: &str,\n) -> gateway::TCPRouteStatus {\n    let route_status = await_condition(\n        client,\n        ns,\n        name,\n        |obj: Option<&gateway::TCPRoute>| -> bool {\n            obj.and_then(|route| route.status.as_ref()).is_some()\n        },\n    )\n    .await\n    .expect(\"must fetch route\")\n    .status\n    .expect(\"route must contain a status representation\");\n    tracing::trace!(?route_status, name, ns, \"got route status\");\n    route_status\n}\n\n// Wait for the endpoints controller to populate the Endpoints resource.\npub fn endpoints_ready(obj: Option<&k8s::Endpoints>) -> bool {\n    if let Some(ep) = obj {\n        return ep\n            .subsets\n            .iter()\n            .flatten()\n            .flat_map(|s| &s.addresses)\n            .any(|a| !a.is_empty());\n    }\n    false\n}\n\npub fn egress_network_traffic_policy_is(\n    traffic_policy: policy::TrafficPolicy,\n) -> impl Condition<policy::EgressNetwork> + 'static {\n    move |egress_net: Option<&policy::EgressNetwork>| {\n        if let Some(egress_net) = &egress_net {\n            return egress_net\n                .status\n                .as_ref()\n                .is_some_and(|s| is_status_accepted(&s.conditions))\n                && egress_net.spec.traffic_policy == traffic_policy;\n        }\n        false\n    }\n}\n\nfn is_status_accepted(conditions: &[k8s::Condition]) -> bool {\n    conditions\n        .iter()\n        .any(|c| c.type_ == \"Accepted\" && c.status == \"True\")\n}\n\n#[track_caller]\npub fn assert_status_accepted(conditions: Vec<k8s::Condition>) {\n    assert!(\n        is_status_accepted(&conditions),\n        \"status must be accepted: {conditions:?}\"\n    );\n}\n\n#[tracing::instrument(skip_all, fields(%pod, %container))]\npub async fn logs(client: &kube::Client, ns: &str, pod: &str, container: &str) {\n    let params = kube::api::LogParams {\n        container: Some(container.to_string()),\n        ..kube::api::LogParams::default()\n    };\n    let log = kube::Api::<k8s::Pod>::namespaced(client.clone(), ns)\n        .logs(pod, &params)\n        .await\n        .expect(\"must fetch logs\");\n    for message in log.lines() {\n        tracing::trace!(%message);\n    }\n}\n\nimpl Resource {\n    pub fn group(&self) -> String {\n        match self {\n            Self::EgressNetwork(_) => \"policy.linkerd.io\".to_string(),\n            Self::Service(_) => \"core\".to_string(),\n        }\n    }\n\n    pub fn kind(&self) -> String {\n        match self {\n            Self::EgressNetwork(_) => \"EgressNetwork\".to_string(),\n            Self::Service(_) => \"Service\".to_string(),\n        }\n    }\n\n    pub fn name(&self) -> String {\n        match self {\n            Self::EgressNetwork(e) => e.name_unchecked(),\n            Self::Service(s) => s.name_unchecked(),\n        }\n    }\n\n    pub fn namespace(&self) -> String {\n        match self {\n            Self::EgressNetwork(e) => e.namespace().unwrap(),\n            Self::Service(s) => s.namespace().unwrap(),\n        }\n    }\n\n    pub fn ip(&self) -> String {\n        match self {\n            // For EgressNetwork, we can just return a non-private\n            // IP address as our default cluster setup dictates that\n            // all non-private networks are considered egress. Since\n            // we do not modify this setting in tests for the time being,\n            // returning 1.1.1.1 is fine.\n            Self::EgressNetwork(_) => \"1.1.1.1\".to_string(),\n            Self::Service(s) => s\n                .spec\n                .as_ref()\n                .expect(\"Service must have a spec\")\n                .cluster_ip\n                .as_ref()\n                .expect(\"Service must have a cluster ip\")\n                .clone(),\n        }\n    }\n}\n\n/// Creates a service resource.\npub async fn create_parent(client: &kube::Client, parent: Resource) -> Resource {\n    match parent {\n        Resource::Service(svc) => Resource::Service(create(client, svc).await),\n        Resource::EgressNetwork(enet) => Resource::EgressNetwork(create(client, enet).await),\n    }\n}\n\n/// Creates a service resource.\npub async fn create_service(\n    client: &kube::Client,\n    ns: &str,\n    name: &str,\n    port: i32,\n) -> k8s::Service {\n    let svc = mk_service(ns, name, port);\n\n    create(client, svc).await\n}\n\n/// Creates an egress network resource.\npub async fn create_egress_network(\n    client: &kube::Client,\n    ns: &str,\n    name: &str,\n) -> policy::EgressNetwork {\n    let en = mk_egress_net(ns, name);\n    create(client, en).await\n}\n\n/// Creates a service resource.\npub async fn create_opaque_service(\n    client: &kube::Client,\n    ns: &str,\n    name: &str,\n    port: i32,\n) -> k8s::Service {\n    let svc = mk_service(ns, name, port);\n    let svc = annotate_service(\n        svc,\n        std::iter::once((\"config.linkerd.io/opaque-ports\", port)),\n    );\n\n    create(client, svc).await\n}\n\n/// Creates an egress network resource.\npub async fn create_opaque_egress_network(\n    client: &kube::Client,\n    ns: &str,\n    name: &str,\n    port: i32,\n) -> policy::EgressNetwork {\n    let egress = mk_egress_net(ns, name);\n    let egress = annotate_egress_net(\n        egress,\n        std::iter::once((\"config.linkerd.io/opaque-ports\", port)),\n    );\n\n    create(client, egress).await\n}\n\n/// Creates a service resource with given annotations.\npub async fn create_annotated_service(\n    client: &kube::Client,\n    ns: &str,\n    name: &str,\n    port: i32,\n    annotations: std::collections::BTreeMap<String, String>,\n) -> k8s::Service {\n    let svc = annotate_service(mk_service(ns, name, port), annotations);\n    create(client, svc).await\n}\n\n/// Creates an egress network resource with given annotations.\npub async fn create_annotated_egress_network(\n    client: &kube::Client,\n    ns: &str,\n    name: &str,\n    annotations: std::collections::BTreeMap<String, String>,\n) -> policy::EgressNetwork {\n    let enet = annotate_egress_net(mk_egress_net(ns, name), annotations);\n    create(client, enet).await\n}\n\npub fn annotate_service<K, V>(\n    mut svc: k8s::Service,\n    annotations: impl IntoIterator<Item = (K, V)>,\n) -> k8s::Service\nwhere\n    K: ToString,\n    V: ToString,\n{\n    svc.annotations_mut().extend(\n        annotations\n            .into_iter()\n            .map(|(k, v)| (k.to_string(), v.to_string())),\n    );\n    svc\n}\n\npub fn annotate_egress_net<K, V>(\n    mut egress_net: policy::EgressNetwork,\n    annotations: impl IntoIterator<Item = (K, V)>,\n) -> policy::EgressNetwork\nwhere\n    K: ToString,\n    V: ToString,\n{\n    egress_net.annotations_mut().extend(\n        annotations\n            .into_iter()\n            .map(|(k, v)| (k.to_string(), v.to_string())),\n    );\n    egress_net\n}\n\npub fn mk_service(ns: &str, name: &str, port: i32) -> k8s::Service {\n    k8s::Service {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: Some(k8s::ServiceSpec {\n            ports: Some(vec![k8s::ServicePort {\n                port,\n                ..Default::default()\n            }]),\n            ..Default::default()\n        }),\n        ..k8s::Service::default()\n    }\n}\n\npub fn mk_egress_net(ns: &str, name: &str) -> policy::EgressNetwork {\n    policy::EgressNetwork {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: policy::EgressNetworkSpec {\n            networks: None,\n            traffic_policy: policy::egress_network::TrafficPolicy::Allow,\n        },\n        status: None,\n    }\n}\n\n#[track_caller]\npub fn assert_resource_meta(\n    meta: &Option<grpc::meta::Metadata>,\n    parent_ref: gateway::HTTPRouteParentRefs,\n    port: u16,\n) {\n    println!(\"meta: {meta:?}\");\n    tracing::debug!(?meta, ?parent_ref, port, \"Asserting parent metadata\");\n    let mut group = parent_ref.group.unwrap();\n    if group.is_empty() {\n        group = \"core\".to_string();\n    }\n    assert_eq!(\n        meta,\n        &Some(grpc::meta::Metadata {\n            kind: Some(grpc::meta::metadata::Kind::Resource(grpc::meta::Resource {\n                group,\n                kind: parent_ref.kind.unwrap(),\n                name: parent_ref.name,\n                namespace: parent_ref.namespace.unwrap(),\n                section: \"\".to_string(),\n                port: port.into()\n            })),\n        })\n    );\n}\n\npub fn mk_route(\n    ns: &str,\n    name: &str,\n    parent_refs: Option<Vec<gateway::HTTPRouteParentRefs>>,\n) -> policy::HttpRoute {\n    use policy::httproute as api;\n    api::HttpRoute {\n        metadata: kube::api::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: policy::HttpRouteSpec {\n            parent_refs,\n            hostnames: None,\n            rules: Some(vec![]),\n        },\n        status: None,\n    }\n}\n\npub fn find_route_condition<'a>(\n    statuses: impl IntoIterator<Item = &'a gateway::HTTPRouteStatusParents>,\n    parent_name: &'static str,\n) -> Option<&'a k8s::Condition> {\n    statuses\n        .into_iter()\n        .find(|parent_status| parent_status.parent_ref.name == parent_name)\n        .expect(\"route must have at least one status set\")\n        .conditions\n        .iter()\n        .flatten()\n        .find(|cond| cond.type_ == \"Accepted\")\n}\n\n/// Runs a test with a random namespace that is deleted on test completion\npub async fn with_temp_ns<F, Fut>(test: F)\nwhere\n    F: FnOnce(kube::Client, String) -> Fut,\n    Fut: std::future::Future<Output = ()> + Send + 'static,\n{\n    let _tracing = init_tracing();\n\n    let context = std::env::var(\"POLICY_TEST_CONTEXT\").ok();\n    tracing::trace!(?context, \"Initializing client\");\n    let client = match context {\n        None => kube::Client::try_default()\n            .await\n            .expect(\"must initialize kubernetes client\"),\n        Some(context) => {\n            let opts = kube::config::KubeConfigOptions {\n                context: Some(context),\n                cluster: None,\n                user: None,\n            };\n            kube::Config::from_kubeconfig(&opts)\n                .await\n                .expect(\"must initialize kubernetes client config\")\n                .try_into()\n                .expect(\"must initialize kubernetes client\")\n        }\n    };\n\n    let api = kube::Api::<k8s::Namespace>::all(client.clone());\n\n    let name = format!(\"linkerd-policy-test-{}\", random_suffix(6));\n    tracing::debug!(namespace = %name, \"Creating\");\n    let ns = create_cluster_scoped(\n        &client,\n        k8s::Namespace {\n            metadata: k8s::ObjectMeta {\n                name: Some(name),\n                labels: Some(convert_args!(btreemap!(\n                    \"linkerd-policy-test\" => std::thread::current().name().unwrap_or(\"\"),\n                ))),\n                ..Default::default()\n            },\n            ..Default::default()\n        },\n    )\n    .await;\n    tracing::trace!(?ns);\n    tokio::time::timeout(\n        tokio::time::Duration::from_secs(15),\n        await_service_account(&client, &ns.name_unchecked(), \"default\"),\n    )\n    .await\n    .expect(\"Timed out waiting for a serviceaccount\");\n\n    tracing::trace!(\"Spawning\");\n    let ns_name = ns.name_unchecked();\n    let test = test(client.clone(), ns_name.clone());\n    let res = tokio::spawn(test.instrument(tracing::info_span!(\"test\", ns = %ns_name))).await;\n    if res.is_err() {\n        // If the test failed, list the state of all pods/containers in the namespace.\n        let pods = kube::Api::<k8s::Pod>::namespaced(client.clone(), &ns_name)\n            .list(&Default::default())\n            .await\n            .expect(\"Failed to get pod status\");\n        for p in pods.items {\n            let pod = p.name_unchecked();\n            if let Some(status) = p.status {\n                let _span = tracing::info_span!(\"pod\", ns = %ns_name, name = %pod).entered();\n                tracing::trace!(reason = ?status.reason, message = ?status.message);\n                for c in status.init_container_statuses.into_iter().flatten() {\n                    tracing::trace!(init_container = %c.name, ready = %c.ready, state = ?c.state);\n                }\n                for c in status.container_statuses.into_iter().flatten() {\n                    tracing::trace!(container = %c.name, ready = %c.ready, state = ?c.state);\n                }\n            }\n        }\n\n        // Then stop tracing so the log is not further polluted with more\n        // information about cleanup after the failure was printed.\n        drop(_tracing);\n    }\n\n    if std::env::var(\"POLICY_TEST_NO_CLEANUP\").is_err() {\n        tracing::debug!(ns = %ns.name_unchecked(), \"Deleting\");\n        api.delete(&ns.name_unchecked(), &kube::api::DeleteParams::background())\n            .await\n            .expect(\"failed to delete Namespace\");\n    }\n    if let Err(err) = res {\n        std::panic::resume_unwind(err.into_panic());\n    }\n}\n\nasync fn await_resource<T>(\n    mut watcher: impl futures::Stream<\n            Item = Result<kube::runtime::watcher::Event<T>, kube::runtime::watcher::Error>,\n        > + Unpin,\n    predicate: impl Fn(&T) -> bool,\n) where\n    T: kube::Resource + std::fmt::Debug,\n{\n    use futures::StreamExt;\n\n    loop {\n        let ev = watcher\n            .next()\n            .await\n            .expect(\"watch must not end\")\n            .expect(\"watch must not fail\");\n        tracing::info!(?ev);\n        match ev {\n            kube::runtime::watcher::Event::InitApply(resource)\n            | kube::runtime::watcher::Event::Apply(resource)\n                if predicate(&resource) =>\n            {\n                break\n            }\n            _ => {}\n        }\n    }\n}\n\npub async fn await_service_account(client: &kube::Client, ns: &str, name: &str) {\n    tracing::trace!(%ns, \"Waiting for namespace\");\n\n    let label_selector = format!(\"kubernetes.io/metadata.name={}\", ns);\n    let watcher_config = kube::runtime::watcher::Config::default().labels(&label_selector);\n    tokio::pin! {\n        let namespaces = kube::runtime::watcher(\n            kube::Api::<k8s::Namespace>::all(client.clone()),\n            watcher_config,\n        );\n    }\n    await_resource(namespaces, |namespace| namespace.name_unchecked() == ns).await;\n\n    tracing::trace!(%name, %ns, \"Waiting for serviceaccount\");\n\n    tokio::pin! {\n        let sas = kube::runtime::watcher(\n            kube::Api::<k8s::ServiceAccount>::namespaced(client.clone(), ns),\n            Default::default(),\n        );\n    }\n    await_resource(sas, |sa| sa.name_unchecked() == name).await;\n\n    // XXX In some versions of k8s, it may be appropriate to wait for the\n    // ServiceAccount's secret to be created, but, as of v1.24,\n    // ServiceAccounts don't have secrets.\n}\n\npub fn random_suffix(len: usize) -> String {\n    use rand::RngExt;\n\n    rand::rng()\n        .sample_iter(&LowercaseAlphanumeric)\n        .take(len)\n        .map(char::from)\n        .collect()\n}\n\npub fn egress_network_parent_ref(\n    ns: impl ToString,\n    port: Option<u16>,\n) -> gateway::HTTPRouteParentRefs {\n    gateway::HTTPRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"EgressNetwork\".to_string()),\n        namespace: Some(ns.to_string()),\n        name: \"my-egress-net\".to_string(),\n        section_name: None,\n        port: port.map(Into::into),\n    }\n}\n\nfn init_tracing() -> tracing::subscriber::DefaultGuard {\n    tracing::subscriber::set_default(\n        tracing_subscriber::fmt()\n            .with_test_writer()\n            .with_thread_names(true)\n            .without_time()\n            .with_env_filter(\n                tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {\n                    \"trace,tower=info,hyper=info,kube=info,h2=info\"\n                        .parse()\n                        .unwrap()\n                }),\n            )\n            .finish(),\n    )\n}\n\nstruct LowercaseAlphanumeric;\n\n// Modified from `rand::distributions::Alphanumeric`\n//\n// Copyright 2018 Developers of the Rand project\n// Copyright (c) 2014 The Rust Project Developers\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\nimpl rand::distr::Distribution<u8> for LowercaseAlphanumeric {\n    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> u8 {\n        const RANGE: u32 = 26 + 10;\n        const CHARSET: &[u8] = b\"abcdefghijklmnopqrstuvwxyz0123456789\";\n        loop {\n            let var = rng.next_u32() >> (32 - 6);\n            if var < RANGE {\n                return CHARSET[var as usize];\n            }\n        }\n    }\n}\n\n// === imp LinkerdInject ===\n\nimpl std::fmt::Display for LinkerdInject {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            LinkerdInject::Enabled => write!(f, \"enabled\"),\n            LinkerdInject::Disabled => write!(f, \"disabled\"),\n        }\n    }\n}\n"
  },
  {
    "path": "policy-test/src/outbound_api.rs",
    "content": "use crate::{grpc, test_route::TestRoute};\nuse linkerd_policy_controller_k8s_api::gateway;\nuse std::time::Duration;\nuse tokio::time;\n\npub async fn retry_watch_outbound_policy(\n    client: &kube::Client,\n    ns: &str,\n    ip: &str,\n    port: u16,\n) -> tonic::Streaming<grpc::outbound::OutboundPolicy> {\n    // Port-forward to the control plane and start watching the service's\n    // outbound policy.\n    let mut policy_api = grpc::OutboundPolicyClient::port_forwarded(client).await;\n    loop {\n        match policy_api.watch_ip(ns, ip, port).await {\n            Ok(rx) => return rx,\n            Err(error) => {\n                tracing::error!(?error, ns, ip, port, \"failed to watch outbound policy\");\n                time::sleep(Duration::from_secs(1)).await;\n            }\n        }\n    }\n}\n\n// detect_http_routes asserts that the given outbound policy has a proxy protcol\n// of \"Detect\" and then invokes the given function with the Http1 and Http2\n// routes from the Detect.\n#[track_caller]\npub fn detect_http_routes<F>(config: &grpc::outbound::OutboundPolicy, f: F)\nwhere\n    F: Fn(&[grpc::outbound::HttpRoute]),\n{\n    let kind = config\n        .protocol\n        .as_ref()\n        .expect(\"must have proxy protocol\")\n        .kind\n        .as_ref()\n        .expect(\"must have kind\");\n    if let grpc::outbound::proxy_protocol::Kind::Detect(grpc::outbound::proxy_protocol::Detect {\n        opaque: _,\n        timeout: _,\n        http1,\n        http2,\n    }) = kind\n    {\n        let http1 = http1\n            .as_ref()\n            .expect(\"proxy protocol must have http1 field\");\n        let http2 = http2\n            .as_ref()\n            .expect(\"proxy protocol must have http2 field\");\n        f(&http1.routes);\n        f(&http2.routes);\n    } else {\n        panic!(\"proxy protocol must be Detect; actually got:\\n{kind:#?}\")\n    }\n}\n\n#[track_caller]\npub fn http1_routes(config: &grpc::outbound::OutboundPolicy) -> &[grpc::outbound::HttpRoute] {\n    let kind = config\n        .protocol\n        .as_ref()\n        .expect(\"must have proxy protocol\")\n        .kind\n        .as_ref()\n        .expect(\"must have kind\");\n    if let grpc::outbound::proxy_protocol::Kind::Http1(grpc::outbound::proxy_protocol::Http1 {\n        routes,\n        failure_accrual: _,\n    }) = kind\n    {\n        routes\n    } else {\n        panic!(\"proxy protocol must be Grpc; actually got:\\n{kind:#?}\")\n    }\n}\n\n#[track_caller]\npub fn http2_routes(config: &grpc::outbound::OutboundPolicy) -> &[grpc::outbound::HttpRoute] {\n    let kind = config\n        .protocol\n        .as_ref()\n        .expect(\"must have proxy protocol\")\n        .kind\n        .as_ref()\n        .expect(\"must have kind\");\n    if let grpc::outbound::proxy_protocol::Kind::Http2(grpc::outbound::proxy_protocol::Http2 {\n        routes,\n        failure_accrual: _,\n    }) = kind\n    {\n        routes\n    } else {\n        panic!(\"proxy protocol must be Grpc; actually got:\\n{kind:#?}\")\n    }\n}\n\n#[track_caller]\npub fn grpc_routes(config: &grpc::outbound::OutboundPolicy) -> &[grpc::outbound::GrpcRoute] {\n    let kind = config\n        .protocol\n        .as_ref()\n        .expect(\"must have proxy protocol\")\n        .kind\n        .as_ref()\n        .expect(\"must have kind\");\n    if let grpc::outbound::proxy_protocol::Kind::Grpc(grpc::outbound::proxy_protocol::Grpc {\n        routes,\n        failure_accrual: _,\n    }) = kind\n    {\n        routes\n    } else {\n        panic!(\"proxy protocol must be Grpc; actually got:\\n{kind:#?}\")\n    }\n}\n\n#[cfg(feature = \"gateway-api-experimental\")]\n#[track_caller]\npub fn tls_routes(config: &grpc::outbound::OutboundPolicy) -> &[grpc::outbound::TlsRoute] {\n    let kind = config\n        .protocol\n        .as_ref()\n        .expect(\"must have proxy protocol\")\n        .kind\n        .as_ref()\n        .expect(\"must have kind\");\n    if let grpc::outbound::proxy_protocol::Kind::Tls(grpc::outbound::proxy_protocol::Tls {\n        routes,\n    }) = kind\n    {\n        routes\n    } else {\n        panic!(\"proxy protocol must be Tls; actually got:\\n{kind:#?}\")\n    }\n}\n\n#[cfg(feature = \"gateway-api-experimental\")]\n#[track_caller]\npub fn tcp_routes(config: &grpc::outbound::OutboundPolicy) -> &[grpc::outbound::OpaqueRoute] {\n    let kind = config\n        .protocol\n        .as_ref()\n        .expect(\"must have proxy protocol\")\n        .kind\n        .as_ref()\n        .expect(\"must have kind\");\n    if let grpc::outbound::proxy_protocol::Kind::Opaque(grpc::outbound::proxy_protocol::Opaque {\n        routes,\n    }) = kind\n    {\n        routes\n    } else {\n        panic!(\"proxy protocol must be Opaque; actually got:\\n{kind:#?}\")\n    }\n}\n\n#[track_caller]\npub fn detect_failure_accrual<F>(config: &grpc::outbound::OutboundPolicy, f: F)\nwhere\n    F: Fn(Option<&grpc::outbound::FailureAccrual>),\n{\n    let kind = config\n        .protocol\n        .as_ref()\n        .expect(\"must have proxy protocol\")\n        .kind\n        .as_ref()\n        .expect(\"must have kind\");\n    if let grpc::outbound::proxy_protocol::Kind::Detect(grpc::outbound::proxy_protocol::Detect {\n        opaque: _,\n        timeout: _,\n        http1,\n        http2,\n    }) = kind\n    {\n        let http1 = http1\n            .as_ref()\n            .expect(\"proxy protocol must have http1 field\");\n        let http2 = http2\n            .as_ref()\n            .expect(\"proxy protocol must have http2 field\");\n        f(http1.failure_accrual.as_ref());\n        f(http2.failure_accrual.as_ref());\n    } else {\n        panic!(\"proxy protocol must be Detect; actually got:\\n{kind:#?}\")\n    }\n}\n\n#[track_caller]\npub fn failure_accrual_consecutive(\n    accrual: Option<&grpc::outbound::FailureAccrual>,\n) -> &grpc::outbound::failure_accrual::ConsecutiveFailures {\n    assert!(\n        accrual.is_some(),\n        \"failure accrual must be configured for service\"\n    );\n    let kind = accrual\n        .unwrap()\n        .kind\n        .as_ref()\n        .expect(\"failure accrual must have kind\");\n    let grpc::outbound::failure_accrual::Kind::ConsecutiveFailures(accrual) = kind;\n    accrual\n}\n\n#[track_caller]\npub fn assert_route_is_default<R: TestRoute>(\n    route: &R::Route,\n    parent: &gateway::HTTPRouteParentRefs,\n    port: u16,\n) {\n    let rules = &R::rules_first_available(route);\n    let backends = assert_singleton(rules);\n    let backend = R::backend(*assert_singleton(backends));\n    assert_backend_matches_reference(backend, parent, port);\n\n    let route_meta = R::extract_meta(route);\n    match route_meta.kind.as_ref().unwrap() {\n        grpc::meta::metadata::Kind::Default(_) => {}\n        grpc::meta::metadata::Kind::Resource(r) => {\n            panic!(\"route expected to be default but got resource {r:?}\")\n        }\n    }\n}\n\n#[track_caller]\npub fn assert_backend_matches_reference(\n    backend: &grpc::outbound::Backend,\n    obj_ref: &gateway::HTTPRouteParentRefs,\n    port: u16,\n) {\n    let mut group = obj_ref.group.as_deref();\n    if group == Some(\"\") {\n        group = Some(\"core\");\n    }\n    match backend.metadata.as_ref().unwrap().kind.as_ref().unwrap() {\n        grpc::meta::metadata::Kind::Resource(resource) => {\n            assert_eq!(resource.name, obj_ref.name);\n            assert_eq!(Some(&resource.namespace), obj_ref.namespace.as_ref());\n            assert_eq!(Some(resource.group.as_str()), group);\n            assert_eq!(Some(&resource.kind), obj_ref.kind.as_ref());\n            assert_eq!(resource.port, u32::from(port));\n        }\n        grpc::meta::metadata::Kind::Default(_) => {\n            panic!(\"backend expected to be resource but got default\")\n        }\n    }\n}\n\n#[track_caller]\npub fn assert_singleton<T>(ts: &[T]) -> &T {\n    assert_eq!(ts.len(), 1);\n    ts.first().unwrap()\n}\n"
  },
  {
    "path": "policy-test/src/test_route.rs",
    "content": "use k8s_openapi::Resource;\nuse linkerd2_proxy_api::{meta, meta::Metadata, outbound};\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s, gateway, policy, Condition, Resource as _, ResourceExt,\n};\n\nuse crate::outbound_api::{detect_http_routes, grpc_routes};\n#[cfg(feature = \"gateway-api-experimental\")]\nuse crate::outbound_api::{tcp_routes, tls_routes};\n\npub trait TestRoute:\n    kube::Resource<Scope = kube::core::NamespaceResourceScope, DynamicType: Default>\n    + serde::Serialize\n    + serde::de::DeserializeOwned\n    + Clone\n    + std::fmt::Debug\n    + Send\n    + Sync\n    + 'static\n{\n    type Route;\n    type Backend;\n    type Filter;\n\n    fn make_route(\n        ns: impl ToString,\n        parents: Vec<gateway::HTTPRouteParentRefs>,\n        rules: Vec<Vec<gateway::HTTPRouteRulesBackendRefs>>,\n    ) -> Self;\n    fn routes<F>(config: &outbound::OutboundPolicy, f: F)\n    where\n        F: Fn(&[Self::Route]);\n    fn set_parent_name(&mut self, parent_name: String);\n    fn extract_meta(route: &Self::Route) -> &Metadata;\n    fn backend_filters(backend: &Self::Backend) -> Vec<&Self::Filter>;\n    fn rules_first_available(route: &Self::Route) -> Vec<Vec<&Self::Backend>>;\n    fn rules_random_available(route: &Self::Route) -> Vec<Vec<&Self::Backend>>;\n    fn backend(backend: &Self::Backend) -> &outbound::Backend;\n    fn conditions(&self) -> Option<Vec<&Condition>>;\n    fn is_failure_filter(filter: &Self::Filter) -> bool;\n\n    fn meta_eq(&self, meta: &Metadata) -> bool {\n        let meta = match &meta.kind {\n            Some(meta::metadata::Kind::Resource(r)) => r,\n            _ => return false,\n        };\n        let dt = Default::default();\n        self.meta().name.as_ref() == Some(&meta.name)\n            && self.meta().namespace.as_ref() == Some(&meta.namespace)\n            && Self::kind(&dt) == meta.kind\n            && Self::group(&dt) == meta.group\n    }\n}\n\n#[allow(async_fn_in_trait)]\npub trait TestParent:\n    kube::Resource<Scope = kube::core::NamespaceResourceScope, DynamicType: Default>\n    + serde::Serialize\n    + serde::de::DeserializeOwned\n    + Clone\n    + std::fmt::Debug\n    + Send\n    + Sync\n{\n    fn make_parent(ns: impl ToString) -> Self {\n        Self::make_parent_with_protocol(ns, None)\n    }\n    fn make_parent_with_protocol(ns: impl ToString, app_protocol: Option<String>) -> Self;\n    fn make_backend(ns: impl ToString) -> Option<Self>;\n    fn conditions(&self) -> Vec<&Condition>;\n    fn obj_ref(&self) -> gateway::HTTPRouteParentRefs;\n    fn backend_ref(&self, port: u16) -> gateway::HTTPRouteRulesBackendRefs {\n        let dt = Default::default();\n        gateway::HTTPRouteRulesBackendRefs {\n            weight: None,\n            group: Some(Self::group(&dt).to_string()),\n            kind: Some(Self::kind(&dt).to_string()),\n            name: self.name_unchecked(),\n            namespace: self.namespace(),\n            port: Some(port.into()),\n            filters: None,\n        }\n    }\n    fn ip(&self) -> &str;\n}\n\nimpl TestRoute for gateway::HTTPRoute {\n    type Route = outbound::HttpRoute;\n    type Backend = outbound::http_route::RouteBackend;\n    type Filter = outbound::http_route::Filter;\n\n    fn make_route(\n        ns: impl ToString,\n        parents: Vec<gateway::HTTPRouteParentRefs>,\n        rules: Vec<Vec<gateway::HTTPRouteRulesBackendRefs>>,\n    ) -> Self {\n        let rules = rules\n            .into_iter()\n            .map(|backends| {\n                let backends = backends.into_iter().collect();\n                gateway::HTTPRouteRules {\n                    name: None,\n                    matches: Some(vec![]),\n                    filters: None,\n                    backend_refs: Some(backends),\n                    ..Default::default()\n                }\n            })\n            .collect();\n        gateway::HTTPRoute {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(\"foo-route\".to_string()),\n                ..Default::default()\n            },\n            spec: gateway::HTTPRouteSpec {\n                parent_refs: Some(parents),\n                hostnames: None,\n                rules: Some(rules),\n            },\n            status: None,\n        }\n    }\n\n    fn routes<F>(config: &outbound::OutboundPolicy, f: F)\n    where\n        F: Fn(&[outbound::HttpRoute]),\n    {\n        detect_http_routes(config, f);\n    }\n\n    fn extract_meta(route: &outbound::HttpRoute) -> &Metadata {\n        route.metadata.as_ref().unwrap()\n    }\n\n    fn backend_filters(\n        backend: &outbound::http_route::RouteBackend,\n    ) -> Vec<&outbound::http_route::Filter> {\n        backend.filters.iter().collect()\n    }\n\n    fn rules_first_available(\n        route: &outbound::HttpRoute,\n    ) -> Vec<Vec<&outbound::http_route::RouteBackend>> {\n        route\n            .rules\n            .iter()\n            .map(\n                |rule| match rule.backends.as_ref().unwrap().kind.as_ref().unwrap() {\n                    outbound::http_route::distribution::Kind::FirstAvailable(first_available) => {\n                        first_available.backends.iter().collect()\n                    }\n                    _ => panic!(\"unexpected distribution kind\"),\n                },\n            )\n            .collect()\n    }\n\n    fn rules_random_available(\n        route: &outbound::HttpRoute,\n    ) -> Vec<Vec<&outbound::http_route::RouteBackend>> {\n        route\n            .rules\n            .iter()\n            .map(\n                |rule| match rule.backends.as_ref().unwrap().kind.as_ref().unwrap() {\n                    outbound::http_route::distribution::Kind::RandomAvailable(random_available) => {\n                        random_available\n                            .backends\n                            .iter()\n                            .map(|backend| backend.backend.as_ref().unwrap())\n                            .collect()\n                    }\n                    _ => panic!(\"unexpected distribution kind\"),\n                },\n            )\n            .collect()\n    }\n\n    fn backend(backend: &outbound::http_route::RouteBackend) -> &outbound::Backend {\n        backend.backend.as_ref().unwrap()\n    }\n\n    fn conditions(&self) -> Option<Vec<&Condition>> {\n        self.status.as_ref().map(|status| {\n            status\n                .parents\n                .iter()\n                .flat_map(|parent_status| &parent_status.conditions)\n                .flatten()\n                .collect()\n        })\n    }\n\n    fn is_failure_filter(filter: &outbound::http_route::Filter) -> bool {\n        matches!(\n            filter.kind.as_ref().unwrap(),\n            outbound::http_route::filter::Kind::FailureInjector(_)\n        )\n    }\n\n    fn set_parent_name(&mut self, parent_name: String) {\n        self.spec\n            .parent_refs\n            .as_mut()\n            .unwrap()\n            .first_mut()\n            .unwrap()\n            .name = parent_name;\n    }\n}\n\nimpl TestRoute for policy::HttpRoute {\n    type Route = outbound::HttpRoute;\n    type Backend = outbound::http_route::RouteBackend;\n    type Filter = outbound::http_route::Filter;\n\n    fn make_route(\n        ns: impl ToString,\n        parents: Vec<gateway::HTTPRouteParentRefs>,\n        rules: Vec<Vec<gateway::HTTPRouteRulesBackendRefs>>,\n    ) -> Self {\n        let rules = rules\n            .into_iter()\n            .map(|backends| {\n                let backends = backends.into_iter().collect();\n                policy::httproute::HttpRouteRule {\n                    matches: Some(vec![]),\n                    filters: None,\n                    timeouts: None,\n                    backend_refs: Some(backends),\n                }\n            })\n            .collect();\n        policy::HttpRoute {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(\"foo-route\".to_string()),\n                ..Default::default()\n            },\n            spec: policy::HttpRouteSpec {\n                parent_refs: Some(parents),\n                hostnames: None,\n                rules: Some(rules),\n            },\n            status: None,\n        }\n    }\n\n    fn routes<F>(config: &outbound::OutboundPolicy, f: F)\n    where\n        F: Fn(&[outbound::HttpRoute]),\n    {\n        detect_http_routes(config, f);\n    }\n\n    fn extract_meta(route: &outbound::HttpRoute) -> &Metadata {\n        route.metadata.as_ref().unwrap()\n    }\n\n    fn backend_filters(\n        backend: &outbound::http_route::RouteBackend,\n    ) -> Vec<&outbound::http_route::Filter> {\n        backend.filters.iter().collect()\n    }\n\n    fn rules_first_available(\n        route: &outbound::HttpRoute,\n    ) -> Vec<Vec<&outbound::http_route::RouteBackend>> {\n        route\n            .rules\n            .iter()\n            .map(\n                |rule| match rule.backends.as_ref().unwrap().kind.as_ref().unwrap() {\n                    outbound::http_route::distribution::Kind::FirstAvailable(first_available) => {\n                        first_available.backends.iter().collect()\n                    }\n                    _ => panic!(\"unexpected distribution kind\"),\n                },\n            )\n            .collect()\n    }\n\n    fn rules_random_available(\n        route: &outbound::HttpRoute,\n    ) -> Vec<Vec<&outbound::http_route::RouteBackend>> {\n        route\n            .rules\n            .iter()\n            .map(\n                |rule| match rule.backends.as_ref().unwrap().kind.as_ref().unwrap() {\n                    outbound::http_route::distribution::Kind::RandomAvailable(random_available) => {\n                        random_available\n                            .backends\n                            .iter()\n                            .map(|backend| backend.backend.as_ref().unwrap())\n                            .collect()\n                    }\n                    _ => panic!(\"unexpected distribution kind\"),\n                },\n            )\n            .collect()\n    }\n\n    fn backend(backend: &outbound::http_route::RouteBackend) -> &outbound::Backend {\n        backend.backend.as_ref().unwrap()\n    }\n\n    fn conditions(&self) -> Option<Vec<&Condition>> {\n        self.status.as_ref().map(|status| {\n            status\n                .parents\n                .iter()\n                .flat_map(|parent_status| &parent_status.conditions)\n                .flatten()\n                .collect()\n        })\n    }\n\n    fn is_failure_filter(filter: &outbound::http_route::Filter) -> bool {\n        matches!(\n            filter.kind.as_ref().unwrap(),\n            outbound::http_route::filter::Kind::FailureInjector(_)\n        )\n    }\n\n    fn set_parent_name(&mut self, parent_name: String) {\n        self.spec\n            .parent_refs\n            .as_mut()\n            .unwrap()\n            .first_mut()\n            .unwrap()\n            .name = parent_name;\n    }\n}\n\nimpl TestRoute for gateway::GRPCRoute {\n    type Route = outbound::GrpcRoute;\n    type Backend = outbound::grpc_route::RouteBackend;\n    type Filter = outbound::grpc_route::Filter;\n\n    fn make_route(\n        ns: impl ToString,\n        parents: Vec<gateway::HTTPRouteParentRefs>,\n        rules: Vec<Vec<gateway::HTTPRouteRulesBackendRefs>>,\n    ) -> Self {\n        let rules = rules\n            .into_iter()\n            .map(|backends| {\n                let backends = backends\n                    .into_iter()\n                    .map(|br| gateway::GRPCRouteRulesBackendRefs {\n                        filters: None,\n                        weight: br.weight,\n                        group: br.group,\n                        kind: br.kind,\n                        name: br.name,\n                        namespace: br.namespace,\n                        port: br.port,\n                    })\n                    .collect();\n                gateway::GRPCRouteRules {\n                    name: None,\n                    matches: Some(vec![]),\n                    filters: None,\n                    backend_refs: Some(backends),\n                    session_persistence: None,\n                }\n            })\n            .collect();\n        gateway::GRPCRoute {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(\"foo-route\".to_string()),\n                ..Default::default()\n            },\n            spec: gateway::GRPCRouteSpec {\n                parent_refs: Some(\n                    parents\n                        .into_iter()\n                        .map(|parents| gateway::GRPCRouteParentRefs {\n                            group: parents.group,\n                            kind: parents.kind,\n                            namespace: parents.namespace,\n                            name: parents.name,\n                            section_name: parents.section_name,\n                            port: parents.port,\n                        })\n                        .collect(),\n                ),\n                hostnames: None,\n                rules: Some(rules),\n            },\n            status: None,\n        }\n    }\n\n    fn routes<F>(config: &outbound::OutboundPolicy, f: F)\n    where\n        F: Fn(&[outbound::GrpcRoute]),\n    {\n        f(grpc_routes(config));\n    }\n\n    fn extract_meta(route: &outbound::GrpcRoute) -> &Metadata {\n        route.metadata.as_ref().unwrap()\n    }\n\n    fn backend_filters(\n        backend: &outbound::grpc_route::RouteBackend,\n    ) -> Vec<&outbound::grpc_route::Filter> {\n        backend.filters.iter().collect()\n    }\n\n    fn rules_first_available(\n        route: &outbound::GrpcRoute,\n    ) -> Vec<Vec<&outbound::grpc_route::RouteBackend>> {\n        route\n            .rules\n            .iter()\n            .map(\n                |rule| match rule.backends.as_ref().unwrap().kind.as_ref().unwrap() {\n                    outbound::grpc_route::distribution::Kind::FirstAvailable(first_available) => {\n                        first_available.backends.iter().collect()\n                    }\n                    _ => panic!(\"unexpected distribution kind\"),\n                },\n            )\n            .collect()\n    }\n\n    fn rules_random_available(\n        route: &outbound::GrpcRoute,\n    ) -> Vec<Vec<&outbound::grpc_route::RouteBackend>> {\n        route\n            .rules\n            .iter()\n            .map(\n                |rule| match rule.backends.as_ref().unwrap().kind.as_ref().unwrap() {\n                    outbound::grpc_route::distribution::Kind::RandomAvailable(random_available) => {\n                        random_available\n                            .backends\n                            .iter()\n                            .map(|backend| backend.backend.as_ref().unwrap())\n                            .collect()\n                    }\n                    _ => panic!(\"unexpected distribution kind\"),\n                },\n            )\n            .collect()\n    }\n\n    fn backend(backend: &outbound::grpc_route::RouteBackend) -> &outbound::Backend {\n        backend.backend.as_ref().unwrap()\n    }\n\n    fn conditions(&self) -> Option<Vec<&Condition>> {\n        self.status.as_ref().map(|status| {\n            status\n                .parents\n                .iter()\n                .flat_map(|parent_status| &parent_status.conditions)\n                .flatten()\n                .collect()\n        })\n    }\n\n    fn is_failure_filter(filter: &outbound::grpc_route::Filter) -> bool {\n        matches!(\n            filter.kind.as_ref().unwrap(),\n            outbound::grpc_route::filter::Kind::FailureInjector(_)\n        )\n    }\n\n    fn set_parent_name(&mut self, parent_name: String) {\n        self.spec\n            .parent_refs\n            .as_mut()\n            .unwrap()\n            .first_mut()\n            .unwrap()\n            .name = parent_name;\n    }\n}\n\n#[cfg(feature = \"gateway-api-experimental\")]\nimpl TestRoute for gateway::TLSRoute {\n    type Route = outbound::TlsRoute;\n    type Backend = outbound::tls_route::RouteBackend;\n    type Filter = outbound::tls_route::Filter;\n\n    fn make_route(\n        ns: impl ToString,\n        parents: Vec<gateway::HTTPRouteParentRefs>,\n        rules: Vec<Vec<gateway::HTTPRouteRulesBackendRefs>>,\n    ) -> Self {\n        let rules = rules\n            .into_iter()\n            .map(|backends| gateway::TLSRouteRules {\n                name: None,\n                backend_refs: Some(\n                    backends\n                        .into_iter()\n                        .map(|br| gateway::TLSRouteRulesBackendRefs {\n                            weight: br.weight,\n                            group: br.group,\n                            kind: br.kind,\n                            name: br.name,\n                            namespace: br.namespace,\n                            port: br.port,\n                        })\n                        .collect(),\n                ),\n            })\n            .collect();\n        gateway::TLSRoute {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(\"foo-route\".to_string()),\n                ..Default::default()\n            },\n            spec: gateway::TLSRouteSpec {\n                parent_refs: Some(\n                    parents\n                        .into_iter()\n                        .map(|parent| gateway::TLSRouteParentRefs {\n                            group: parent.group,\n                            kind: parent.kind,\n                            namespace: parent.namespace,\n                            name: parent.name,\n                            section_name: parent.section_name,\n                            port: parent.port,\n                        })\n                        .collect(),\n                ),\n                hostnames: None,\n                rules,\n            },\n            status: None,\n        }\n    }\n\n    fn routes<F>(config: &outbound::OutboundPolicy, f: F)\n    where\n        F: Fn(&[outbound::TlsRoute]),\n    {\n        f(tls_routes(config));\n    }\n\n    fn extract_meta(route: &outbound::TlsRoute) -> &Metadata {\n        route.metadata.as_ref().unwrap()\n    }\n\n    fn backend_filters(\n        backend: &outbound::tls_route::RouteBackend,\n    ) -> Vec<&outbound::tls_route::Filter> {\n        backend.filters.iter().collect()\n    }\n\n    fn rules_first_available(\n        route: &outbound::TlsRoute,\n    ) -> Vec<Vec<&outbound::tls_route::RouteBackend>> {\n        route\n            .rules\n            .iter()\n            .map(\n                |rule| match rule.backends.as_ref().unwrap().kind.as_ref().unwrap() {\n                    outbound::tls_route::distribution::Kind::FirstAvailable(first_available) => {\n                        first_available.backends.iter().collect()\n                    }\n                    _ => panic!(\"unexpected distribution kind\"),\n                },\n            )\n            .collect()\n    }\n\n    fn rules_random_available(\n        route: &outbound::TlsRoute,\n    ) -> Vec<Vec<&outbound::tls_route::RouteBackend>> {\n        route\n            .rules\n            .iter()\n            .map(\n                |rule| match rule.backends.as_ref().unwrap().kind.as_ref().unwrap() {\n                    outbound::tls_route::distribution::Kind::RandomAvailable(random_available) => {\n                        random_available\n                            .backends\n                            .iter()\n                            .map(|backend| backend.backend.as_ref().unwrap())\n                            .collect()\n                    }\n                    _ => panic!(\"unexpected distribution kind\"),\n                },\n            )\n            .collect()\n    }\n\n    fn backend(backend: &outbound::tls_route::RouteBackend) -> &outbound::Backend {\n        backend.backend.as_ref().unwrap()\n    }\n\n    fn conditions(&self) -> Option<Vec<&Condition>> {\n        self.status.as_ref().map(|status| {\n            status\n                .parents\n                .iter()\n                .flat_map(|parent_status| &parent_status.conditions)\n                .flatten()\n                .collect()\n        })\n    }\n\n    fn is_failure_filter(filter: &outbound::tls_route::Filter) -> bool {\n        matches!(\n            filter.kind.as_ref().unwrap(),\n            outbound::tls_route::filter::Kind::Invalid(_)\n        )\n    }\n\n    fn set_parent_name(&mut self, parent_name: String) {\n        self.spec\n            .parent_refs\n            .as_mut()\n            .unwrap()\n            .first_mut()\n            .unwrap()\n            .name = parent_name;\n    }\n}\n\n#[cfg(feature = \"gateway-api-experimental\")]\nimpl TestRoute for gateway::TCPRoute {\n    type Route = outbound::OpaqueRoute;\n    type Backend = outbound::opaque_route::RouteBackend;\n    type Filter = outbound::opaque_route::Filter;\n\n    fn make_route(\n        ns: impl ToString,\n        parents: Vec<gateway::HTTPRouteParentRefs>,\n        rules: Vec<Vec<gateway::HTTPRouteRulesBackendRefs>>,\n    ) -> Self {\n        let rules = rules\n            .into_iter()\n            .map(|backends| gateway::TCPRouteRules {\n                name: None,\n                backend_refs: Some(\n                    backends\n                        .into_iter()\n                        .map(|br| gateway::TCPRouteRulesBackendRefs {\n                            weight: br.weight,\n                            group: br.group,\n                            kind: br.kind,\n                            name: br.name,\n                            namespace: br.namespace,\n                            port: br.port,\n                        })\n                        .collect(),\n                ),\n            })\n            .collect();\n        gateway::TCPRoute {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(\"foo-route\".to_string()),\n                ..Default::default()\n            },\n            spec: gateway::TCPRouteSpec {\n                parent_refs: Some(\n                    parents\n                        .into_iter()\n                        .map(|parent| gateway::TCPRouteParentRefs {\n                            group: parent.group,\n                            kind: parent.kind,\n                            namespace: parent.namespace,\n                            name: parent.name,\n                            section_name: parent.section_name,\n                            port: parent.port,\n                        })\n                        .collect(),\n                ),\n                rules,\n            },\n            status: None,\n        }\n    }\n\n    fn routes<F>(config: &outbound::OutboundPolicy, f: F)\n    where\n        F: Fn(&[outbound::OpaqueRoute]),\n    {\n        f(tcp_routes(config));\n    }\n\n    fn extract_meta(route: &outbound::OpaqueRoute) -> &Metadata {\n        route.metadata.as_ref().unwrap()\n    }\n\n    fn backend_filters(\n        backend: &outbound::opaque_route::RouteBackend,\n    ) -> Vec<&outbound::opaque_route::Filter> {\n        backend.filters.iter().collect()\n    }\n\n    fn rules_first_available(\n        route: &outbound::OpaqueRoute,\n    ) -> Vec<Vec<&outbound::opaque_route::RouteBackend>> {\n        route\n            .rules\n            .iter()\n            .map(\n                |rule| match rule.backends.as_ref().unwrap().kind.as_ref().unwrap() {\n                    outbound::opaque_route::distribution::Kind::FirstAvailable(first_available) => {\n                        first_available.backends.iter().collect()\n                    }\n                    _ => panic!(\"unexpected distribution kind\"),\n                },\n            )\n            .collect()\n    }\n\n    fn rules_random_available(\n        route: &outbound::OpaqueRoute,\n    ) -> Vec<Vec<&outbound::opaque_route::RouteBackend>> {\n        route\n            .rules\n            .iter()\n            .map(\n                |rule| match rule.backends.as_ref().unwrap().kind.as_ref().unwrap() {\n                    outbound::opaque_route::distribution::Kind::RandomAvailable(\n                        random_available,\n                    ) => random_available\n                        .backends\n                        .iter()\n                        .map(|backend| backend.backend.as_ref().unwrap())\n                        .collect(),\n                    _ => panic!(\"unexpected distribution kind\"),\n                },\n            )\n            .collect()\n    }\n\n    fn backend(backend: &outbound::opaque_route::RouteBackend) -> &outbound::Backend {\n        backend.backend.as_ref().unwrap()\n    }\n\n    fn conditions(&self) -> Option<Vec<&Condition>> {\n        self.status.as_ref().map(|status| {\n            status\n                .parents\n                .iter()\n                .flat_map(|parent_status| &parent_status.conditions)\n                .flatten()\n                .collect()\n        })\n    }\n\n    fn is_failure_filter(filter: &outbound::opaque_route::Filter) -> bool {\n        matches!(\n            filter.kind.as_ref().unwrap(),\n            outbound::opaque_route::filter::Kind::Invalid(_)\n        )\n    }\n\n    fn set_parent_name(&mut self, parent_name: String) {\n        self.spec\n            .parent_refs\n            .as_mut()\n            .unwrap()\n            .first_mut()\n            .unwrap()\n            .name = parent_name;\n    }\n}\n\nimpl TestParent for k8s::Service {\n    fn make_parent_with_protocol(ns: impl ToString, app_protocol: Option<String>) -> Self {\n        k8s::Service {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(\"my-svc\".to_string()),\n                ..Default::default()\n            },\n            spec: Some(k8s::ServiceSpec {\n                ports: Some(vec![\n                    k8s::ServicePort {\n                        name: Some(\"port-one\".to_string()),\n                        port: 4191,\n                        app_protocol: app_protocol.clone(),\n                        ..Default::default()\n                    },\n                    k8s::ServicePort {\n                        name: Some(\"port-two\".to_string()),\n                        port: 9999,\n                        app_protocol,\n                        ..Default::default()\n                    },\n                ]),\n                ..Default::default()\n            }),\n            ..k8s::Service::default()\n        }\n    }\n\n    fn make_backend(ns: impl ToString) -> Option<Self> {\n        let service = k8s::Service {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(\"backend\".to_string()),\n                ..Default::default()\n            },\n            spec: Some(k8s::ServiceSpec {\n                ports: Some(vec![k8s::ServicePort {\n                    port: 4191,\n                    ..Default::default()\n                }]),\n                ..Default::default()\n            }),\n            ..k8s::Service::default()\n        };\n        Some(service)\n    }\n\n    fn conditions(&self) -> Vec<&Condition> {\n        self.status\n            .as_ref()\n            .unwrap()\n            .conditions\n            .as_ref()\n            .unwrap()\n            .iter()\n            .collect()\n    }\n\n    fn obj_ref(&self) -> gateway::HTTPRouteParentRefs {\n        gateway::HTTPRouteParentRefs {\n            kind: Some(k8s::Service::KIND.to_string()),\n            name: self.name_unchecked(),\n            namespace: self.namespace(),\n            group: Some(k8s::Service::GROUP.to_string()),\n            section_name: None,\n            port: Some(4191),\n        }\n    }\n\n    fn ip(&self) -> &str {\n        self.spec.as_ref().unwrap().cluster_ip.as_ref().unwrap()\n    }\n}\n\nimpl TestParent for policy::EgressNetwork {\n    fn make_parent_with_protocol(ns: impl ToString, app_protocol: Option<String>) -> Self {\n        assert!(\n            app_protocol.is_none(),\n            \"`appProtocol` is not supported by EgressNetwork\"\n        );\n\n        policy::EgressNetwork {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(\"my-egress\".to_string()),\n                ..Default::default()\n            },\n            spec: policy::EgressNetworkSpec {\n                networks: None,\n                traffic_policy: policy::egress_network::TrafficPolicy::Allow,\n            },\n            status: None,\n        }\n    }\n\n    fn make_backend(_ns: impl ToString) -> Option<Self> {\n        None\n    }\n\n    fn conditions(&self) -> Vec<&Condition> {\n        self.status.as_ref().unwrap().conditions.iter().collect()\n    }\n\n    fn obj_ref(&self) -> gateway::HTTPRouteParentRefs {\n        gateway::HTTPRouteParentRefs {\n            kind: Some(policy::EgressNetwork::kind(&()).to_string()),\n            name: self.name_unchecked(),\n            namespace: self.namespace(),\n            group: Some(policy::EgressNetwork::group(&()).to_string()),\n            section_name: None,\n            port: Some(4191),\n        }\n    }\n\n    fn ip(&self) -> &str {\n        // For EgressNetwork, we can just return a non-private\n        // IP address as our default cluster setup dictates that\n        // all non-private networks are considered egress. Since\n        // we do not modify this setting in tests for the time being,\n        // returning 1.1.1.1 is fine.\n        \"1.1.1.1\"\n    }\n}\n"
  },
  {
    "path": "policy-test/src/web.rs",
    "content": "use k8s_openapi::apimachinery::pkg::util::intstr::IntOrString;\nuse linkerd_policy_controller_k8s_api::{self as k8s};\nuse maplit::{btreemap, convert_args};\n\npub fn pod(ns: &str) -> k8s::Pod {\n    k8s::Pod {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(\"web\".to_string()),\n            annotations: Some(convert_args!(btreemap!(\n                \"linkerd.io/inject\" => \"enabled\",\n                \"config.linkerd.io/proxy-log-level\" => \"linkerd=trace,info\",\n            ))),\n            labels: Some(convert_args!(btreemap!(\n                \"app\" => \"web\",\n            ))),\n            ..Default::default()\n        },\n        spec: Some(k8s::PodSpec {\n            containers: vec![k8s::api::core::v1::Container {\n                name: \"hokay\".to_string(),\n                image: Some(\"ghcr.io/olix0r/hokay:latest\".to_string()),\n                ports: Some(vec![k8s::api::core::v1::ContainerPort {\n                    name: Some(\"http\".to_string()),\n                    container_port: 8080,\n                    ..Default::default()\n                }]),\n                ..Default::default()\n            }],\n            ..Default::default()\n        }),\n        ..k8s::Pod::default()\n    }\n}\n\npub fn server(ns: &str, access_policy: Option<String>) -> k8s::policy::Server {\n    k8s::policy::Server {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(\"web\".to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::ServerSpec {\n            selector: k8s::policy::server::Selector::Pod(k8s::labels::Selector::from_iter(Some((\n                \"app\", \"web\",\n            )))),\n            port: k8s::policy::server::Port::Name(\"http\".to_string()),\n            proxy_protocol: Some(k8s::policy::server::ProxyProtocol::Http1),\n            access_policy,\n        },\n    }\n}\n\npub fn service(ns: &str) -> k8s::api::core::v1::Service {\n    k8s::api::core::v1::Service {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(\"web\".to_string()),\n            ..Default::default()\n        },\n        spec: Some(k8s::api::core::v1::ServiceSpec {\n            type_: Some(\"ClusterIP\".to_string()),\n            selector: Some(convert_args!(btreemap!(\n                \"app\" => \"web\"\n            ))),\n            ports: Some(vec![k8s::api::core::v1::ServicePort {\n                port: 80,\n                target_port: Some(IntOrString::String(\"http\".to_string())),\n                ..Default::default()\n            }]),\n            ..Default::default()\n        }),\n        ..Default::default()\n    }\n}\n"
  },
  {
    "path": "policy-test/tests/admit_authorization_policy.rs",
    "content": "use linkerd_policy_controller_k8s_api::{\n    self as api,\n    policy::{AuthorizationPolicy, AuthorizationPolicySpec, LocalTargetRef, NamespacedTargetRef},\n};\nuse linkerd_policy_test::admission;\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_valid() {\n    admission::accepts(|ns| AuthorizationPolicy {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: AuthorizationPolicySpec {\n            target_ref: LocalTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"Server\".to_string(),\n                name: \"api\".to_string(),\n            },\n            required_authentication_refs: vec![\n                NamespacedTargetRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: \"MeshTLSAuthentication\".to_string(),\n                    name: \"mtls-clients\".to_string(),\n                    namespace: None,\n                },\n                NamespacedTargetRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: \"NetworkAuthentication\".to_string(),\n                    name: \"cluster-nets\".to_string(),\n                    namespace: Some(\"linkerd\".to_string()),\n                },\n            ],\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_targets_namespace() {\n    admission::accepts(|ns| AuthorizationPolicy {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns.clone()),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: AuthorizationPolicySpec {\n            target_ref: LocalTargetRef {\n                group: None,\n                kind: \"Namespace\".to_string(),\n                name: ns,\n            },\n            required_authentication_refs: vec![\n                NamespacedTargetRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: \"MeshTLSAuthentication\".to_string(),\n                    name: \"mtls-clients\".to_string(),\n                    namespace: None,\n                },\n                NamespacedTargetRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: \"NetworkAuthentication\".to_string(),\n                    name: \"cluster-nets\".to_string(),\n                    namespace: Some(\"linkerd\".to_string()),\n                },\n            ],\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_targets_other_namespace() {\n    admission::rejects(|ns| AuthorizationPolicy {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: AuthorizationPolicySpec {\n            target_ref: LocalTargetRef {\n                group: None,\n                kind: \"Namespace\".to_string(),\n                name: \"foobar\".to_string(),\n            },\n            required_authentication_refs: vec![\n                NamespacedTargetRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: \"MeshTLSAuthentication\".to_string(),\n                    name: \"mtls-clients\".to_string(),\n                    namespace: None,\n                },\n                NamespacedTargetRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: \"NetworkAuthentication\".to_string(),\n                    name: \"cluster-nets\".to_string(),\n                    namespace: Some(\"linkerd\".to_string()),\n                },\n            ],\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_targets_route() {\n    admission::accepts(|ns| AuthorizationPolicy {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: AuthorizationPolicySpec {\n            target_ref: LocalTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"HttpRoute\".to_string(),\n                name: \"route-foo\".to_string(),\n            },\n            required_authentication_refs: vec![\n                NamespacedTargetRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: \"MeshTLSAuthentication\".to_string(),\n                    name: \"mtls-clients\".to_string(),\n                    namespace: None,\n                },\n                NamespacedTargetRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: \"NetworkAuthentication\".to_string(),\n                    name: \"cluster-nets\".to_string(),\n                    namespace: Some(\"linkerd\".to_string()),\n                },\n            ],\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_valid_with_only_meshtls() {\n    admission::accepts(|ns| AuthorizationPolicy {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: AuthorizationPolicySpec {\n            target_ref: LocalTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"Server\".to_string(),\n                name: \"api\".to_string(),\n            },\n            required_authentication_refs: vec![NamespacedTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"MeshTLSAuthentication\".to_string(),\n                name: \"mtls-clients\".to_string(),\n                namespace: None,\n            }],\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_valid_with_only_network() {\n    admission::accepts(|ns| AuthorizationPolicy {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: AuthorizationPolicySpec {\n            target_ref: LocalTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"Server\".to_string(),\n                name: \"api\".to_string(),\n            },\n            required_authentication_refs: vec![NamespacedTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"NetworkAuthentication\".to_string(),\n                name: \"cluster-nets\".to_string(),\n                namespace: Some(\"linkerd\".to_string()),\n            }],\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_empty_required_authentications() {\n    admission::accepts(|ns| AuthorizationPolicy {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: AuthorizationPolicySpec {\n            target_ref: LocalTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"Server\".to_string(),\n                name: \"deny\".to_string(),\n            },\n            required_authentication_refs: vec![],\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_missing_required_authentications() {\n    #[derive(\n        Clone,\n        Debug,\n        kube::CustomResource,\n        serde::Deserialize,\n        serde::Serialize,\n        schemars::JsonSchema,\n    )]\n    #[kube(\n        group = \"policy.linkerd.io\",\n        version = \"v1alpha1\",\n        kind = \"AuthorizationPolicy\",\n        namespaced\n    )]\n    #[serde(rename_all = \"camelCase\")]\n    pub struct FakeAuthorizationPolicySpec {\n        pub target_ref: LocalTargetRef,\n        pub required_authentication_refs: Option<Vec<NamespacedTargetRef>>,\n    }\n\n    admission::rejects(|ns| AuthorizationPolicy {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: FakeAuthorizationPolicySpec {\n            target_ref: LocalTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"Server\".to_string(),\n                name: \"deny\".to_string(),\n            },\n            required_authentication_refs: None,\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_target_ref_deployment() {\n    admission::rejects(|ns| AuthorizationPolicy {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: AuthorizationPolicySpec {\n            target_ref: LocalTargetRef {\n                group: Some(\"apps\".to_string()),\n                kind: \"Deployment\".to_string(),\n                name: \"someapp\".to_string(),\n            },\n            required_authentication_refs: vec![NamespacedTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"NetworkAuthentication\".to_string(),\n                namespace: Some(\"linkerd\".to_string()),\n                name: \"cluster-nets\".to_string(),\n            }],\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_duplicate_mtls_authns() {\n    admission::rejects(|ns| AuthorizationPolicy {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: AuthorizationPolicySpec {\n            target_ref: LocalTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"Server\".to_string(),\n                name: \"some-srv\".to_string(),\n            },\n            required_authentication_refs: vec![\n                NamespacedTargetRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: \"MeshTLSAuthentication\".to_string(),\n                    namespace: Some(\"some-ns\".to_string()),\n                    name: \"some-ids\".to_string(),\n                },\n                NamespacedTargetRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: \"MeshTLSAuthentication\".to_string(),\n                    namespace: Some(\"other-ns\".to_string()),\n                    name: \"other-ids\".to_string(),\n                },\n            ],\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_duplicate_network_authns() {\n    admission::rejects(|ns| AuthorizationPolicy {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: AuthorizationPolicySpec {\n            target_ref: LocalTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"Server\".to_string(),\n                name: \"some-srv\".to_string(),\n            },\n            required_authentication_refs: vec![\n                NamespacedTargetRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: \"NetworkAuthentication\".to_string(),\n                    namespace: Some(\"some-ns\".to_string()),\n                    name: \"some-nets\".to_string(),\n                },\n                NamespacedTargetRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: \"NetworkAuthentication\".to_string(),\n                    namespace: Some(\"other-ns\".to_string()),\n                    name: \"other-nets\".to_string(),\n                },\n            ],\n        },\n    })\n    .await;\n}\n"
  },
  {
    "path": "policy-test/tests/admit_egress_networks.rs",
    "content": "use linkerd_policy_controller_k8s_api::{\n    self as api,\n    policy::{EgressNetwork, EgressNetworkSpec, Network, TrafficPolicy},\n};\nuse linkerd_policy_test::admission;\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_valid() {\n    admission::accepts(|ns| EgressNetwork {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: EgressNetworkSpec {\n            traffic_policy: TrafficPolicy::Allow,\n            networks: Some(vec![\n                Network {\n                    cidr: \"10.1.0.0/24\".parse().unwrap(),\n                    except: None,\n                },\n                Network {\n                    cidr: \"10.1.1.0/24\".parse().unwrap(),\n                    except: Some(vec![\"10.1.1.0/28\".parse().unwrap()]),\n                },\n            ]),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_empty_networks() {\n    admission::rejects(|ns| EgressNetwork {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: EgressNetworkSpec {\n            traffic_policy: TrafficPolicy::Allow,\n            networks: Some(Default::default()),\n        },\n        status: None,\n    })\n    .await;\n}\n"
  },
  {
    "path": "policy-test/tests/admit_grpc_route.rs",
    "content": "use linkerd_policy_controller_k8s_api::{self as api, gateway};\nuse linkerd_policy_test::admission;\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_valid_egress_network() {\n    admission::accepts(|ns| gateway::GRPCRoute {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns.clone()),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: gateway::GRPCRouteSpec {\n            parent_refs: Some(vec![gateway::GRPCRouteParentRefs {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: Some(\"EgressNetwork\".to_string()),\n                namespace: Some(ns.to_string()),\n                name: \"my-egress-net\".to_string(),\n                section_name: None,\n                port: Some(555),\n            }]),\n            hostnames: None,\n            rules: Some(rules()),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_egress_network_parent_with_no_port() {\n    admission::rejects(|ns| gateway::GRPCRoute {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns.clone()),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: gateway::GRPCRouteSpec {\n            parent_refs: Some(vec![gateway::GRPCRouteParentRefs {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: Some(\"EgressNetwork\".to_string()),\n                namespace: Some(ns.to_string()),\n                name: \"my-egress-net\".to_string(),\n                section_name: None,\n                port: None,\n            }]),\n            hostnames: None,\n            rules: Some(rules()),\n        },\n        status: None,\n    })\n    .await;\n}\n\nfn rules() -> Vec<gateway::GRPCRouteRules> {\n    vec![gateway::GRPCRouteRules {\n        name: None,\n        matches: Some(vec![gateway::GRPCRouteRulesMatches {\n            method: Some(gateway::GRPCRouteRulesMatchesMethod {\n                method: Some(\"foo\".to_string()),\n                service: Some(\"boo\".to_string()),\n                r#type: Some(gateway::GRPCRouteRulesMatchesMethodType::Exact),\n            }),\n            ..Default::default()\n        }]),\n        filters: None,\n        backend_refs: None,\n        session_persistence: None,\n    }]\n}\n"
  },
  {
    "path": "policy-test/tests/admit_http_local_ratelimit_policy.rs",
    "content": "use k8s_openapi::chrono;\nuse linkerd_policy_controller_k8s_api::{\n    self as api,\n    policy::{\n        HttpLocalRateLimitPolicy, HttpLocalRateLimitPolicyStatus, Limit, LocalTargetRef,\n        NamespacedTargetRef, Override, RateLimitPolicySpec,\n    },\n};\nuse linkerd_policy_test::admission;\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_valid() {\n    admission::accepts(|ns| {\n        mk_ratelimiter(ns, default_target_ref(), 1000, 100, default_overrides())\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_target_ref_deployment() {\n    let target_ref = LocalTargetRef {\n        group: Some(\"apps\".to_string()),\n        kind: \"Deployment\".to_string(),\n        name: \"api\".to_string(),\n    };\n    admission::rejects(|ns| mk_ratelimiter(ns, target_ref, 1000, 100, default_overrides())).await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_identity_rps_higher_than_total() {\n    admission::rejects(|ns| {\n        mk_ratelimiter(ns, default_target_ref(), 1000, 2000, default_overrides())\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_overrides_rps_higher_than_total() {\n    let overrides = vec![Override {\n        requests_per_second: 2000,\n        client_refs: vec![NamespacedTargetRef {\n            group: Some(\"\".to_string()),\n            kind: \"ServiceAccount\".to_string(),\n            name: \"sa-1\".to_string(),\n            namespace: Some(\"linkerd\".to_string()),\n        }],\n    }];\n    admission::rejects(|ns| mk_ratelimiter(ns, default_target_ref(), 1000, 2000, overrides)).await;\n}\n\nfn default_target_ref() -> LocalTargetRef {\n    LocalTargetRef {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: \"Server\".to_string(),\n        name: \"api\".to_string(),\n    }\n}\n\nfn default_overrides() -> Vec<Override> {\n    vec![Override {\n        requests_per_second: 200,\n        client_refs: vec![NamespacedTargetRef {\n            group: Some(\"\".to_string()),\n            kind: \"ServiceAccount\".to_string(),\n            name: \"sa-1\".to_string(),\n            namespace: Some(\"linkerd\".to_string()),\n        }],\n    }]\n}\n\nfn mk_ratelimiter(\n    namespace: String,\n    target_ref: LocalTargetRef,\n    total_rps: u32,\n    identity_rps: u32,\n    overrides: Vec<Override>,\n) -> HttpLocalRateLimitPolicy {\n    HttpLocalRateLimitPolicy {\n        metadata: api::ObjectMeta {\n            namespace: Some(namespace),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: RateLimitPolicySpec {\n            target_ref,\n            total: Some(Limit {\n                requests_per_second: total_rps,\n            }),\n            identity: Some(Limit {\n                requests_per_second: identity_rps,\n            }),\n            overrides: Some(overrides),\n        },\n        status: Some(HttpLocalRateLimitPolicyStatus {\n            conditions: vec![api::Condition {\n                last_transition_time: api::Time(chrono::DateTime::<chrono::Utc>::MIN_UTC),\n                message: \"\".to_string(),\n                observed_generation: None,\n                reason: \"\".to_string(),\n                status: \"True\".to_string(),\n                type_: \"Accepted\".to_string(),\n            }],\n            target_ref: LocalTargetRef {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: \"Server\".to_string(),\n                name: \"linkerd-admin\".to_string(),\n            },\n        }),\n    }\n}\n"
  },
  {
    "path": "policy-test/tests/admit_http_route.rs",
    "content": "use linkerd_policy_controller_k8s_api::{self as k8s, gateway, policy};\nuse linkerd_policy_test::{admission, egress_network_parent_ref};\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_valid() {\n    admission::accepts(|ns| policy::HttpRoute {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.clone()),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: policy::HttpRouteSpec {\n            parent_refs: Some(vec![server_parent_ref(ns)]),\n            hostnames: None,\n            rules: Some(rules()),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_valid_egress_network() {\n    admission::accepts(|ns| policy::HttpRoute {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.clone()),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: policy::HttpRouteSpec {\n            parent_refs: Some(vec![egress_network_parent_ref(ns, Some(555))]),\n            hostnames: None,\n            rules: Some(rules()),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_egress_network_parent_with_no_port() {\n    admission::rejects(|ns| policy::HttpRoute {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.clone()),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: policy::HttpRouteSpec {\n            parent_refs: Some(vec![egress_network_parent_ref(ns, None)]),\n            hostnames: None,\n            rules: Some(rules()),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_relative_path_match() {\n    admission::rejects(|ns| policy::HttpRoute {\n        metadata: meta(&ns),\n        spec: policy::HttpRouteSpec {\n            parent_refs: Some(vec![server_parent_ref(ns)]),\n            hostnames: None,\n            rules: Some(vec![policy::httproute::HttpRouteRule {\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"foo/bar\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    ..Default::default()\n                }]),\n                filters: None,\n                backend_refs: None,\n                timeouts: None,\n            }]),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_relative_redirect_path() {\n    admission::rejects(|ns| policy::HttpRoute {\n        metadata: meta(&ns),\n        spec: policy::HttpRouteSpec {\n            parent_refs: Some(vec![server_parent_ref(ns)]),\n            hostnames: None,\n            rules: Some(vec![policy::httproute::HttpRouteRule {\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    ..Default::default()\n                }]),\n                filters: Some(vec![policy::httproute::HttpRouteFilter::RequestRedirect {\n                    request_redirect: gateway::HTTPRouteRulesFiltersRequestRedirect {\n                        scheme: None,\n                        hostname: None,\n                        path: Some(gateway::HTTPRouteRulesFiltersRequestRedirectPath {\n                            replace_full_path: Some(\"foo/bar\".to_string()),\n                            r#type: gateway::HTTPRouteRulesFiltersRequestRedirectPathType::ReplaceFullPath,\n                            ..Default::default()\n                        }),\n                        port: None,\n                        status_code: None,\n                    },\n                }]),\n                backend_refs: None,\n                timeouts: None,\n            }]),\n        },\n        status: None,\n    })\n    .await;\n}\n\nfn server_parent_ref(ns: impl ToString) -> gateway::HTTPRouteParentRefs {\n    gateway::HTTPRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"Server\".to_string()),\n        namespace: Some(ns.to_string()),\n        name: \"my-server\".to_string(),\n        section_name: None,\n        port: None,\n    }\n}\n\nfn meta(ns: impl ToString) -> k8s::ObjectMeta {\n    k8s::ObjectMeta {\n        namespace: Some(ns.to_string()),\n        name: Some(\"test\".to_string()),\n        ..Default::default()\n    }\n}\n\nfn rules() -> Vec<policy::httproute::HttpRouteRule> {\n    vec![policy::httproute::HttpRouteRule {\n        matches: Some(vec![gateway::HTTPRouteRulesMatches {\n            path: Some(gateway::HTTPRouteRulesMatchesPath {\n                value: Some(\"/foo\".to_string()),\n                r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n            }),\n            ..Default::default()\n        }]),\n        filters: None,\n        backend_refs: None,\n        timeouts: None,\n    }]\n}\n"
  },
  {
    "path": "policy-test/tests/admit_http_route_gateway.rs",
    "content": "use linkerd_policy_controller_k8s_api::{self as api, gateway};\nuse linkerd_policy_test::admission;\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_valid() {\n    admission::accepts(|ns| gateway::HTTPRoute {\n        metadata: meta(&ns),\n        spec: gateway::HTTPRouteSpec {\n            parent_refs: Some(vec![server_parent_ref(ns)]),\n            hostnames: None,\n            rules: Some(rules()),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_not_implemented_requestmirror() {\n    admission::accepts(|ns| gateway::HTTPRoute {\n        metadata: meta(&ns),\n        spec: gateway::HTTPRouteSpec {\n            parent_refs: Some(vec![server_parent_ref(ns)]),\n            hostnames: None,\n            rules: Some(vec![gateway::HTTPRouteRules {\n                name: None,\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    ..gateway::HTTPRouteRulesMatches::default()\n                }]),\n                filters: Some(vec![gateway::HTTPRouteRulesFilters {\n                    request_mirror: Some(gateway::HTTPRouteRulesFiltersRequestMirror {\n                        backend_ref: gateway::HTTPRouteRulesFiltersRequestMirrorBackendRef {\n                            group: None,\n                            kind: None,\n                            namespace: Some(\"foo\".to_string()),\n                            name: \"foo\".to_string(),\n                            port: Some(80),\n                        },\n                        percent: Some(100),\n                        ..Default::default()\n                    }),\n                    r#type: gateway::HTTPRouteRulesFiltersType::RequestMirror,\n                    ..Default::default()\n                }]),\n                backend_refs: None,\n                ..Default::default()\n            }]),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_not_implemented_urlrewrite() {\n    admission::accepts(|ns| gateway::HTTPRoute {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns.clone()),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: gateway::HTTPRouteSpec {\n            parent_refs: Some(vec![server_parent_ref(ns)]),\n            hostnames: None,\n            rules: Some(vec![gateway::HTTPRouteRules {\n                name: None,\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    ..gateway::HTTPRouteRulesMatches::default()\n                }]),\n                filters: Some(vec![gateway::HTTPRouteRulesFilters {\n                    url_rewrite: Some(gateway::HTTPRouteRulesFiltersUrlRewrite {\n                        hostname: Some(\"foo\".to_string()),\n                        path: Some(gateway::HTTPRouteRulesFiltersUrlRewritePath {\n                            replace_full_path: Some(\"baz\".to_string()),\n                            r#type:\n                                gateway::HTTPRouteRulesFiltersUrlRewritePathType::ReplaceFullPath,\n                            ..Default::default()\n                        }),\n                    }),\n                    r#type: gateway::HTTPRouteRulesFiltersType::UrlRewrite,\n                    ..Default::default()\n                }]),\n                backend_refs: None,\n                ..Default::default()\n            }]),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_not_implemented_extensionref() {\n    admission::accepts(|ns| gateway::HTTPRoute {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns.clone()),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: gateway::HTTPRouteSpec {\n            parent_refs: Some(vec![server_parent_ref(ns)]),\n            hostnames: None,\n            rules: Some(vec![gateway::HTTPRouteRules {\n                name: None,\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    ..gateway::HTTPRouteRulesMatches::default()\n                }]),\n                filters: Some(vec![gateway::HTTPRouteRulesFilters {\n                    extension_ref: Some(gateway::HTTPRouteRulesFiltersExtensionRef {\n                        group: \"\".to_string(),\n                        kind: \"Service\".to_string(),\n                        name: \"foo\".to_string(),\n                    }),\n                    r#type: gateway::HTTPRouteRulesFiltersType::ExtensionRef,\n                    ..Default::default()\n                }]),\n                backend_refs: None,\n                ..Default::default()\n            }]),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_backend_unknown_kind() {\n    admission::accepts(|ns| gateway::HTTPRoute {\n        metadata: meta(&ns),\n        spec: gateway::HTTPRouteSpec {\n            parent_refs: Some(vec![server_parent_ref(ns)]),\n            hostnames: None,\n            rules: Some(vec![gateway::HTTPRouteRules {\n                name: None,\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    ..gateway::HTTPRouteRulesMatches::default()\n                }]),\n                filters: None,\n                backend_refs: Some(vec![gateway::HTTPRouteRulesBackendRefs {\n                    weight: None,\n                    group: Some(\"alien.example.com\".to_string()),\n                    kind: Some(\"ExoService\".to_string()),\n                    namespace: Some(\"foo\".to_string()),\n                    name: \"foo\".to_string(),\n                    port: None,\n                    filters: None,\n                }]),\n                ..Default::default()\n            }]),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_backend_service_with_port() {\n    admission::accepts(|ns| gateway::HTTPRoute {\n        metadata: meta(&ns),\n        spec: gateway::HTTPRouteSpec {\n            parent_refs: Some(vec![server_parent_ref(ns)]),\n            hostnames: None,\n            rules: Some(vec![gateway::HTTPRouteRules {\n                name: None,\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    ..gateway::HTTPRouteRulesMatches::default()\n                }]),\n                filters: None,\n                backend_refs: Some(vec![gateway::HTTPRouteRulesBackendRefs {\n                    weight: None,\n                    group: Some(\"core\".to_string()),\n                    kind: Some(\"Service\".to_string()),\n                    namespace: Some(\"foo\".to_string()),\n                    name: \"foo\".to_string(),\n                    port: Some(8080),\n                    filters: None,\n                }]),\n                ..Default::default()\n            }]),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_backend_service_implicit_with_port() {\n    admission::accepts(|ns| gateway::HTTPRoute {\n        metadata: meta(&ns),\n        spec: gateway::HTTPRouteSpec {\n            parent_refs: Some(vec![server_parent_ref(ns)]),\n            hostnames: None,\n            rules: Some(vec![gateway::HTTPRouteRules {\n                name: None,\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    ..gateway::HTTPRouteRulesMatches::default()\n                }]),\n                filters: None,\n                backend_refs: Some(vec![gateway::HTTPRouteRulesBackendRefs {\n                    weight: None,\n                    group: None,\n                    kind: None,\n                    namespace: Some(\"foo\".to_string()),\n                    name: \"foo\".to_string(),\n                    port: Some(8080),\n                    filters: None,\n                }]),\n                ..Default::default()\n            }]),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_relative_path_match() {\n    admission::rejects(|ns| gateway::HTTPRoute {\n        metadata: meta(&ns),\n        spec: gateway::HTTPRouteSpec {\n            parent_refs: Some(vec![server_parent_ref(ns)]),\n            hostnames: None,\n            rules: Some(vec![gateway::HTTPRouteRules {\n                name: None,\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    ..gateway::HTTPRouteRulesMatches::default()\n                }]),\n                filters: None,\n                backend_refs: None,\n                ..Default::default()\n            }]),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_relative_redirect_path() {\n    admission::rejects(|ns| gateway::HTTPRoute {\n        metadata: meta(&ns),\n        spec: gateway::HTTPRouteSpec {\n            parent_refs: Some(vec![server_parent_ref(ns)]),\n\n            hostnames: None,\n            rules: Some(vec![gateway::HTTPRouteRules {\n                name: None,\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    ..gateway::HTTPRouteRulesMatches::default()\n                }]),\n                filters: Some(vec![gateway::HTTPRouteRulesFilters {\n                    request_redirect: Some(gateway::HTTPRouteRulesFiltersRequestRedirect {\n                        scheme: None,\n                        hostname: None,\n                        path: Some(gateway::HTTPRouteRulesFiltersRequestRedirectPath {\n                            replace_full_path: Some(\"foo/bar\".to_string()),\n                            r#type:\n                                gateway::HTTPRouteRulesFiltersRequestRedirectPathType::ReplaceFullPath,\n                            ..Default::default()\n                        }),\n                        port: None,\n                        status_code: None,\n                    }),\n                    r#type: gateway::HTTPRouteRulesFiltersType::RequestRedirect,\n                    ..Default::default()\n                }]),\n                backend_refs: None,\n                ..Default::default()\n            }]),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_backend_service_without_port() {\n    admission::rejects(|ns| gateway::HTTPRoute {\n        metadata: meta(&ns),\n        spec: gateway::HTTPRouteSpec {\n            parent_refs: Some(vec![server_parent_ref(ns)]),\n            hostnames: None,\n            rules: Some(vec![gateway::HTTPRouteRules {\n                name: None,\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    ..gateway::HTTPRouteRulesMatches::default()\n                }]),\n                filters: None,\n                backend_refs: Some(vec![gateway::HTTPRouteRulesBackendRefs {\n                    weight: None,\n                    group: Some(\"core\".to_string()),\n                    kind: Some(\"Service\".to_string()),\n                    namespace: Some(\"foo\".to_string()),\n                    name: \"foo\".to_string(),\n                    port: None,\n                    filters: None,\n                }]),\n                ..Default::default()\n            }]),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_backend_service_implicit_without_port() {\n    admission::rejects(|ns| gateway::HTTPRoute {\n        metadata: meta(&ns),\n        spec: gateway::HTTPRouteSpec {\n            parent_refs: Some(vec![server_parent_ref(ns)]),\n\n            hostnames: None,\n            rules: Some(vec![gateway::HTTPRouteRules {\n                name: None,\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    ..gateway::HTTPRouteRulesMatches::default()\n                }]),\n                filters: None,\n                backend_refs: Some(vec![gateway::HTTPRouteRulesBackendRefs {\n                    weight: None,\n                    group: None,\n                    kind: None,\n                    namespace: Some(\"foo\".to_string()),\n                    name: \"foo\".to_string(),\n                    port: None,\n                    filters: None,\n                }]),\n                ..Default::default()\n            }]),\n        },\n        status: None,\n    })\n    .await;\n}\n\nfn server_parent_ref(ns: impl ToString) -> gateway::HTTPRouteParentRefs {\n    gateway::HTTPRouteParentRefs {\n        group: Some(\"policy.linkerd.io\".to_string()),\n        kind: Some(\"Server\".to_string()),\n        namespace: Some(ns.to_string()),\n        name: \"my-server\".to_string(),\n        section_name: None,\n        port: None,\n    }\n}\n\nfn meta(ns: impl ToString) -> api::ObjectMeta {\n    api::ObjectMeta {\n        namespace: Some(ns.to_string()),\n        name: Some(\"test\".to_string()),\n        ..Default::default()\n    }\n}\n\nfn rules() -> Vec<gateway::HTTPRouteRules> {\n    vec![gateway::HTTPRouteRules {\n        name: None,\n        matches: Some(vec![gateway::HTTPRouteRulesMatches {\n            path: Some(gateway::HTTPRouteRulesMatchesPath {\n                value: Some(\"/foo\".to_string()),\n                r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n            }),\n            ..gateway::HTTPRouteRulesMatches::default()\n        }]),\n        filters: None,\n        backend_refs: None,\n        ..Default::default()\n    }]\n}\n"
  },
  {
    "path": "policy-test/tests/admit_meshtls_authentication.rs",
    "content": "use linkerd_policy_controller_k8s_api::{\n    self as api,\n    policy::{MeshTLSAuthentication, MeshTLSAuthenticationSpec, NamespacedTargetRef},\n};\nuse linkerd_policy_test::admission;\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_valid_ref() {\n    admission::accepts(|ns| MeshTLSAuthentication {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: MeshTLSAuthenticationSpec {\n            identity_refs: Some(vec![NamespacedTargetRef {\n                group: None,\n                kind: \"ServiceAccount\".to_string(),\n                name: \"default\".to_string(),\n                namespace: None,\n            }]),\n            ..Default::default()\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_ns_ref() {\n    admission::accepts(|ns| MeshTLSAuthentication {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: MeshTLSAuthenticationSpec {\n            identity_refs: Some(vec![NamespacedTargetRef {\n                group: None,\n                kind: \"Namespace\".to_string(),\n                name: \"default\".to_string(),\n                namespace: None,\n            }]),\n            ..Default::default()\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_namespaced_namespace() {\n    admission::rejects(|ns| MeshTLSAuthentication {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: MeshTLSAuthenticationSpec {\n            identity_refs: Some(vec![NamespacedTargetRef {\n                group: None,\n                kind: \"Namespace\".to_string(),\n                name: \"default\".to_string(),\n                namespace: Some(\"default\".to_string()),\n            }]),\n            ..Default::default()\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_strings() {\n    admission::accepts(|ns| MeshTLSAuthentication {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: MeshTLSAuthenticationSpec {\n            identities: Some(vec![\"example.id\".to_string()]),\n            ..Default::default()\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_empty() {\n    admission::rejects(|ns| MeshTLSAuthentication {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: MeshTLSAuthenticationSpec::default(),\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_both_refs_and_strings() {\n    admission::rejects(|ns| MeshTLSAuthentication {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: MeshTLSAuthenticationSpec {\n            identities: Some(vec![\"example.id\".to_string()]),\n            identity_refs: Some(vec![NamespacedTargetRef {\n                group: None,\n                kind: \"ServiceAccount\".to_string(),\n                name: \"default\".to_string(),\n                namespace: None,\n            }]),\n        },\n    })\n    .await;\n}\n"
  },
  {
    "path": "policy-test/tests/admit_network_authentication.rs",
    "content": "use linkerd_policy_controller_k8s_api::{\n    self as api,\n    policy::network_authentication::{Network, NetworkAuthentication, NetworkAuthenticationSpec},\n};\nuse linkerd_policy_test::admission;\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_valid() {\n    admission::accepts(|ns| NetworkAuthentication {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: NetworkAuthenticationSpec {\n            networks: vec![\n                Network {\n                    cidr: \"10.1.0.0/24\".parse().unwrap(),\n                    except: None,\n                },\n                Network {\n                    cidr: \"10.1.1.0/24\".parse().unwrap(),\n                    except: Some(vec![\"10.1.1.0/28\".parse().unwrap()]),\n                },\n            ],\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_ip_except() {\n    admission::accepts(|ns| NetworkAuthentication {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: NetworkAuthenticationSpec {\n            networks: vec![Network {\n                cidr: \"10.1.0.0/16\".parse().unwrap(),\n                except: Some(vec![\"10.1.1.1\".parse().unwrap()]),\n            }],\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_except_whole_cidr() {\n    admission::rejects(|ns| NetworkAuthentication {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: NetworkAuthenticationSpec {\n            networks: vec![Network {\n                cidr: \"10.1.1.0/24\".parse().unwrap(),\n                except: Some(vec![\"10.1.0.0/16\".parse().unwrap()]),\n            }],\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_except_not_in_cidr() {\n    admission::rejects(|ns| NetworkAuthentication {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: NetworkAuthenticationSpec {\n            networks: vec![Network {\n                cidr: \"10.1.1.0/24\".parse().unwrap(),\n                except: Some(vec![\"10.1.2.0/24\".parse().unwrap()]),\n            }],\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_invalid_cidr() {\n    // Duplicate the CRD with relaxed validation so we can send an invalid CIDR value.\n    #[derive(\n        Clone,\n        Debug,\n        Default,\n        kube::CustomResource,\n        serde::Deserialize,\n        serde::Serialize,\n        schemars::JsonSchema,\n    )]\n    #[kube(\n        group = \"policy.linkerd.io\",\n        version = \"v1alpha1\",\n        kind = \"NetworkAuthentication\",\n        namespaced\n    )]\n    #[serde(rename_all = \"camelCase\")]\n    pub struct NetworkAuthenticationSpec {\n        pub networks: Vec<Network>,\n    }\n\n    #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]\n    #[serde(rename_all = \"camelCase\")]\n    pub struct Network {\n        pub cidr: String,\n        pub except: Option<Vec<String>>,\n    }\n\n    admission::rejects(|ns| NetworkAuthentication {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: NetworkAuthenticationSpec {\n            networks: vec![Network {\n                cidr: \"10.1.0.0/16\".to_string(),\n                except: Some(vec![\"bogus\".to_string()]),\n            }],\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_empty() {\n    admission::rejects(|ns| NetworkAuthentication {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: NetworkAuthenticationSpec { networks: vec![] },\n    })\n    .await;\n}\n"
  },
  {
    "path": "policy-test/tests/admit_server.rs",
    "content": "use linkerd_policy_controller_k8s_api::{\n    self as api,\n    policy::server::{Port, Selector, Server, ServerSpec},\n};\nuse linkerd_policy_test::{admission, with_temp_ns};\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_valid() {\n    admission::accepts(|ns| Server {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: ServerSpec {\n            selector: Selector::Pod(api::labels::Selector::default()),\n            port: Port::Number(80.try_into().unwrap()),\n            proxy_protocol: None,\n            access_policy: None,\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_server_updates() {\n    with_temp_ns(|client, ns| async move {\n        let test0 = Server {\n            metadata: api::ObjectMeta {\n                namespace: Some(ns.clone()),\n                name: Some(\"test0\".to_string()),\n                ..Default::default()\n            },\n            spec: ServerSpec {\n                selector: Selector::Pod(api::labels::Selector::from_iter(Some((\"app\", \"test\")))),\n                port: Port::Number(80.try_into().unwrap()),\n                proxy_protocol: None,\n                access_policy: None,\n            },\n        };\n\n        let api = kube::Api::namespaced(client, &ns);\n        api.create(&kube::api::PostParams::default(), &test0)\n            .await\n            .expect(\"resource must apply\");\n\n        api.patch(\n            \"test0\",\n            &kube::api::PatchParams::default(),\n            &kube::api::Patch::Merge(test0),\n        )\n        .await\n        .expect(\"resource must apply\");\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_invalid_proxy_protocol() {\n    /// Define a Server resource with an invalid proxy protocol\n    #[derive(\n        Clone,\n        Debug,\n        kube::CustomResource,\n        serde::Deserialize,\n        serde::Serialize,\n        schemars::JsonSchema,\n    )]\n    #[kube(\n        group = \"policy.linkerd.io\",\n        version = \"v1alpha1\",\n        kind = \"Server\",\n        namespaced\n    )]\n    #[serde(rename_all = \"camelCase\")]\n    pub struct ServerSpec {\n        #[serde(flatten)]\n        pub selector: Selector,\n        pub port: Port,\n        pub proxy_protocol: String,\n    }\n\n    /// References a pod spec's port by name or number.\n    #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]\n    #[serde(rename_all = \"camelCase\")]\n    pub enum Port {\n        Number(u16),\n        Name(String),\n    }\n\n    admission::rejects(|ns| Server {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: ServerSpec {\n            selector: Selector::Pod(api::labels::Selector::default()),\n            port: Port::Number(80.try_into().unwrap()),\n            proxy_protocol: \"garbanzo\".to_string(),\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_invalid_access_policy() {\n    admission::rejects(|ns| Server {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: ServerSpec {\n            selector: Selector::Pod(api::labels::Selector::default()),\n            port: Port::Number(80.try_into().unwrap()),\n            proxy_protocol: None,\n            access_policy: Some(\"foobar\".to_string()),\n        },\n    })\n    .await;\n}\n"
  },
  {
    "path": "policy-test/tests/admit_server_authorization.rs",
    "content": "use linkerd_policy_controller_k8s_api::{\n    self as api,\n    policy::server_authorization::{\n        Client, MeshTls, Network, Server, ServerAuthorization, ServerAuthorizationSpec,\n    },\n};\nuse linkerd_policy_test::admission;\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_valid() {\n    admission::accepts(|ns| ServerAuthorization {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: ServerAuthorizationSpec {\n            server: Server {\n                name: Some(\"test\".to_string()),\n                selector: None,\n            },\n            client: Client {\n                networks: Some(vec![\n                    Network {\n                        cidr: \"10.1.0.0/24\".parse().unwrap(),\n                        except: None,\n                    },\n                    Network {\n                        cidr: \"10.1.1.0/24\".parse().unwrap(),\n                        except: Some(vec![\"10.1.1.0/28\".parse().unwrap()]),\n                    },\n                ]),\n                unauthenticated: true,\n                mesh_tls: None,\n            },\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_except_whole_cidr() {\n    admission::rejects(|ns| ServerAuthorization {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: ServerAuthorizationSpec {\n            server: Server {\n                name: Some(\"test\".to_string()),\n                selector: None,\n            },\n            client: Client {\n                networks: Some(vec![Network {\n                    cidr: \"10.1.1.0/24\".parse().unwrap(),\n                    except: Some(vec![\"10.1.0.0/16\".parse().unwrap()]),\n                }]),\n                unauthenticated: true,\n                mesh_tls: None,\n            },\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_unauthenciated_and_mtls() {\n    admission::rejects(|ns| ServerAuthorization {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: ServerAuthorizationSpec {\n            server: Server {\n                name: Some(\"test\".to_string()),\n                selector: None,\n            },\n            client: Client {\n                unauthenticated: true,\n                mesh_tls: Some(MeshTls {\n                    identities: Some(vec![\"*\".to_string()]),\n                    ..Default::default()\n                }),\n                networks: None,\n            },\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_unauthenciated_tls_and_identities() {\n    admission::rejects(|ns| ServerAuthorization {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: ServerAuthorizationSpec {\n            server: Server {\n                name: Some(\"test\".to_string()),\n                selector: None,\n            },\n            client: Client {\n                mesh_tls: Some(MeshTls {\n                    unauthenticated_tls: true,\n                    identities: Some(vec![\"*\".to_string()]),\n                    ..Default::default()\n                }),\n                networks: None,\n                ..Default::default()\n            },\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_network_as_ip() {\n    admission::accepts(|ns| ServerAuthorization {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: ServerAuthorizationSpec {\n            server: Server {\n                name: Some(\"test\".to_string()),\n                selector: None,\n            },\n            client: Client {\n                networks: Some(vec![\n                    Network {\n                        cidr: \"10.1.0.2\".parse().unwrap(),\n                        except: None,\n                    },\n                    Network {\n                        cidr: \"10.1.1.0/24\".parse().unwrap(),\n                        except: Some(vec![\"10.1.1.3\".parse().unwrap()]),\n                    },\n                ]),\n                unauthenticated: true,\n                mesh_tls: None,\n            },\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_except_not_in_cidr() {\n    admission::rejects(|ns| ServerAuthorization {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: ServerAuthorizationSpec {\n            server: Server {\n                name: Some(\"test\".to_string()),\n                selector: None,\n            },\n            client: Client {\n                networks: Some(vec![Network {\n                    cidr: \"10.1.1.0/24\".parse().unwrap(),\n                    except: Some(vec![\"10.1.2.0/24\".parse().unwrap()]),\n                }]),\n                unauthenticated: true,\n                mesh_tls: None,\n            },\n        },\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_invalid_cidr() {\n    // Duplicate the CRD with relaxed validation so we can send an invalid CIDR value.\n    #[derive(\n        Clone,\n        Debug,\n        Default,\n        kube::CustomResource,\n        serde::Deserialize,\n        serde::Serialize,\n        schemars::JsonSchema,\n    )]\n    #[kube(\n        group = \"policy.linkerd.io\",\n        version = \"v1alpha1\",\n        kind = \"ServerAuthorization\",\n        namespaced\n    )]\n    #[serde(rename_all = \"camelCase\")]\n    pub struct ServerAuthorizationSpec {\n        pub server: Server,\n        pub client: Client,\n    }\n\n    #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]\n    #[serde(rename_all = \"camelCase\")]\n    pub struct Client {\n        pub networks: Option<Vec<Network>>,\n\n        #[serde(default)]\n        pub unauthenticated: bool,\n\n        #[serde(rename = \"meshTLS\")]\n        pub mesh_tls: Option<MeshTls>,\n    }\n\n    #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]\n    #[serde(rename_all = \"camelCase\")]\n    pub struct Network {\n        pub cidr: String,\n        pub except: Option<Vec<String>>,\n    }\n\n    admission::rejects(|ns| ServerAuthorization {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: ServerAuthorizationSpec {\n            server: Server {\n                name: Some(\"test\".to_string()),\n                selector: None,\n            },\n            client: Client {\n                networks: Some(vec![Network {\n                    cidr: \"bogus\".to_string(),\n                    except: None,\n                }]),\n                unauthenticated: true,\n                mesh_tls: None,\n            },\n        },\n    })\n    .await;\n}\n"
  },
  {
    "path": "policy-test/tests/admit_tcp_route.rs",
    "content": "#![cfg(feature = \"gateway-api-experimental\")]\n\nuse linkerd_policy_controller_k8s_api::{self as api, gateway};\nuse linkerd_policy_test::admission;\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_valid_egress_network() {\n    admission::accepts(|ns| gateway::TCPRoute {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns.clone()),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: gateway::TCPRouteSpec {\n            parent_refs: Some(vec![gateway::TCPRouteParentRefs {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: Some(\"EgressNetwork\".to_string()),\n                namespace: Some(ns.to_string()),\n                name: \"my-egress-net\".to_string(),\n                section_name: None,\n                port: Some(555),\n            }]),\n            rules: rules(1),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_egress_network_parent_with_no_port() {\n    admission::rejects(|ns| gateway::TCPRoute {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns.clone()),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: gateway::TCPRouteSpec {\n            parent_refs: Some(vec![gateway::TCPRouteParentRefs {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: Some(\"EgressNetwork\".to_string()),\n                namespace: Some(ns.to_string()),\n                name: \"my-egress-net\".to_string(),\n                section_name: None,\n                port: None,\n            }]),\n            rules: rules(1),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_if_more_than_one_rule() {\n    admission::rejects(|ns| gateway::TCPRoute {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns.clone()),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: gateway::TCPRouteSpec {\n            parent_refs: Some(vec![gateway::TCPRouteParentRefs {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: Some(\"EgressNetwork\".to_string()),\n                namespace: Some(ns.to_string()),\n                name: \"my-egress-net\".to_string(),\n                section_name: None,\n                port: Some(555),\n            }]),\n            rules: rules(2),\n        },\n        status: None,\n    })\n    .await;\n}\n\nfn rules(n: u16) -> Vec<gateway::TCPRouteRules> {\n    let mut rules = Vec::default();\n    for n in 1..=n {\n        rules.push(gateway::TCPRouteRules {\n            name: None,\n            backend_refs: Some(vec![gateway::TCPRouteRulesBackendRefs {\n                weight: None,\n                name: format!(\"default-{n}\"),\n                group: Some(\"policy.linkerd.ip\".to_string()),\n                namespace: Some(\"root\".to_string()),\n                port: None,\n                kind: Some(\"EgressNetwork\".to_string()),\n            }]),\n        });\n    }\n    rules\n}\n"
  },
  {
    "path": "policy-test/tests/admit_tls_route.rs",
    "content": "#![cfg(feature = \"gateway-api-experimental\")]\n\nuse linkerd_policy_controller_k8s_api::{self as api, gateway};\nuse linkerd_policy_test::admission;\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn accepts_valid_egress_network() {\n    admission::accepts(|ns| gateway::TLSRoute {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns.clone()),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: gateway::TLSRouteSpec {\n            hostnames: None,\n            parent_refs: Some(vec![gateway::TLSRouteParentRefs {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: Some(\"EgressNetwork\".to_string()),\n                namespace: Some(ns.to_string()),\n                name: \"my-egress-net\".to_string(),\n                section_name: None,\n                port: Some(555),\n            }]),\n            rules: rules(1),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_egress_network_parent_with_no_port() {\n    admission::rejects(|ns| gateway::TLSRoute {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns.clone()),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: gateway::TLSRouteSpec {\n            hostnames: None,\n            parent_refs: Some(vec![gateway::TLSRouteParentRefs {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: Some(\"EgressNetwork\".to_string()),\n                namespace: Some(ns.to_string()),\n                name: \"my-egress-net\".to_string(),\n                section_name: None,\n                port: None,\n            }]),\n            rules: rules(1),\n        },\n        status: None,\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn rejects_if_more_than_one_rule() {\n    admission::rejects(|ns| gateway::TLSRoute {\n        metadata: api::ObjectMeta {\n            namespace: Some(ns.clone()),\n            name: Some(\"test\".to_string()),\n            ..Default::default()\n        },\n        spec: gateway::TLSRouteSpec {\n            hostnames: None,\n            parent_refs: Some(vec![gateway::TLSRouteParentRefs {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: Some(\"EgressNetwork\".to_string()),\n                namespace: Some(ns.to_string()),\n                name: \"my-egress-net\".to_string(),\n                section_name: None,\n                port: Some(555),\n            }]),\n            rules: rules(2),\n        },\n        status: None,\n    })\n    .await;\n}\n\nfn rules(n: u16) -> Vec<gateway::TLSRouteRules> {\n    let mut rules = Vec::default();\n    for n in 1..=n {\n        rules.push(gateway::TLSRouteRules {\n            name: None,\n            backend_refs: Some(vec![gateway::TLSRouteRulesBackendRefs {\n                weight: None,\n                name: format!(\"default-{n}\"),\n                group: Some(\"policy.linkerd.ip\".to_string()),\n                namespace: Some(\"root\".to_string()),\n                port: None,\n                kind: Some(\"EgressNetwork\".to_string()),\n            }]),\n        });\n    }\n    rules\n}\n"
  },
  {
    "path": "policy-test/tests/e2e_appprotocol.rs",
    "content": "use futures::StreamExt;\nuse k8s_openapi::apimachinery::pkg::util::intstr::IntOrString;\nuse linkerd2_proxy_api::outbound::{proxy_protocol, OutboundPolicy, ProxyProtocol};\nuse linkerd_policy_controller_k8s_api::{self as k8s};\nuse linkerd_policy_test::{\n    await_condition, create, create_ready_pod, curl, endpoints_ready,\n    outbound_api::retry_watch_outbound_policy, test_route::TestParent, web, with_temp_ns,\n    LinkerdInject,\n};\nuse maplit::{btreemap, convert_args};\n\nconst OPAQUE_PORT: i32 = 81;\nconst HTTP1_PORT: i32 = 82;\nconst HTTP2_PORT: i32 = 83;\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn app_protocol() {\n    with_temp_ns(|client, ns| async move {\n        // Create the web pod and wait for it to be ready.\n        let (svc, _) = tokio::join!(\n            create(&client, service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        let (opaque_config, http1_config, http2_config) = tokio::join!(\n            policy(&client, &ns, svc.ip(), OPAQUE_PORT as u16),\n            policy(&client, &ns, svc.ip(), HTTP1_PORT as u16),\n            policy(&client, &ns, svc.ip(), HTTP2_PORT as u16),\n        );\n        assert!(matches!(\n            opaque_config.protocol,\n            Some(ProxyProtocol {\n                kind: Some(proxy_protocol::Kind::Opaque(_))\n            })\n        ));\n        assert!(matches!(\n            http1_config.protocol,\n            Some(ProxyProtocol {\n                kind: Some(proxy_protocol::Kind::Http1(_))\n            })\n        ));\n        assert!(matches!(\n            http2_config.protocol,\n            Some(ProxyProtocol {\n                kind: Some(proxy_protocol::Kind::Http2(_))\n            })\n        ));\n\n        let opaque_endpoint = format!(\"http://web:{OPAQUE_PORT}\");\n        let http1_endpoint = format!(\"http://web:{HTTP1_PORT}\");\n        let http2_endpoint = format!(\"http://web:{HTTP2_PORT}\");\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let (opaque, http1, http2) = tokio::join!(\n            curl.run(\"curl-opaque\", &opaque_endpoint, LinkerdInject::Enabled),\n            curl.run(\"curl-http1\", &http1_endpoint, LinkerdInject::Enabled),\n            curl.run(\"curl-http2\", &http2_endpoint, LinkerdInject::Enabled),\n        );\n        let (opaque_status, http1_status, http2_exit) = tokio::join!(\n            opaque.http_status_code(),\n            http1.http_status_code(),\n            // Server only supports HTTP/1, should result in failed exit code without a valid HTTP status\n            http2.exit_code(),\n        );\n        assert_eq!(\n            opaque_status, 204,\n            \"opaque request must be routed to valid backend\"\n        );\n        assert_eq!(\n            http1_status, 204,\n            \"http1 request must be routed to valid backend\"\n        );\n        assert_ne!(http2_exit, 0, \"http2 request must result in protocol error\");\n    })\n    .await;\n}\n\n// === helpers ===\n\npub fn service(ns: &str) -> k8s::api::core::v1::Service {\n    k8s::api::core::v1::Service {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(\"web\".to_string()),\n            ..Default::default()\n        },\n        spec: Some(k8s::api::core::v1::ServiceSpec {\n            type_: Some(\"ClusterIP\".to_string()),\n            selector: Some(convert_args!(btreemap!(\n                \"app\" => \"web\"\n            ))),\n            ports: Some(vec![\n                k8s::api::core::v1::ServicePort {\n                    name: Some(\"opaque\".to_string()),\n                    port: OPAQUE_PORT,\n                    target_port: Some(IntOrString::String(\"http\".to_string())),\n                    app_protocol: Some(\"linkerd.io/opaque\".to_string()),\n                    ..Default::default()\n                },\n                k8s::api::core::v1::ServicePort {\n                    name: Some(\"http1\".to_string()),\n                    port: HTTP1_PORT,\n                    target_port: Some(IntOrString::String(\"http\".to_string())),\n                    app_protocol: Some(\"http\".to_string()),\n                    ..Default::default()\n                },\n                k8s::api::core::v1::ServicePort {\n                    name: Some(\"http2\".to_string()),\n                    port: HTTP2_PORT,\n                    target_port: Some(IntOrString::String(\"http\".to_string())),\n                    app_protocol: Some(\"kubernetes.io/h2c\".to_string()),\n                    ..Default::default()\n                },\n            ]),\n            ..Default::default()\n        }),\n        ..Default::default()\n    }\n}\n\nasync fn policy(client: &kube::Client, ns: &str, ip: &str, port: u16) -> OutboundPolicy {\n    let mut rx = retry_watch_outbound_policy(client, ns, ip, port).await;\n    rx.next()\n        .await\n        .expect(\"watch must not fail\")\n        .expect(\"watch must return an initial config\")\n}\n"
  },
  {
    "path": "policy-test/tests/e2e_audit.rs",
    "content": "use kube::{Client, ResourceExt};\nuse linkerd_policy_controller_k8s_api as k8s;\nuse linkerd_policy_test::{\n    await_condition, create, create_ready_pod, curl, endpoints_ready, web, with_temp_ns,\n    LinkerdInject,\n};\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn server_audit() {\n    with_temp_ns(|client, ns| async move {\n        // Create a server with no policy that should block traffic to the associated pod\n        let srv = create(&client, web::server(&ns, None)).await;\n\n        // Create the web pod and wait for it to be ready.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        // All requests should fail\n        let curl = curl::Runner::init(&client, &ns).await;\n        let (injected, uninjected) = tokio::join!(\n            curl.run(\"curl-injected\", \"http://web\", LinkerdInject::Enabled),\n            curl.run(\"curl-uninjected\", \"http://web\", LinkerdInject::Disabled),\n        );\n        let (injected_status, uninjected_status) =\n            tokio::join!(injected.exit_code(), uninjected.exit_code());\n        assert_ne!(injected_status, 0, \"injected curl must fail\");\n        assert_ne!(uninjected_status, 0, \"uninjected curl must fail\");\n\n        // Patch the server with accessPolicy audit\n        let patch = serde_json::json!({\n            \"spec\": {\n                \"accessPolicy\": \"audit\",\n            }\n        });\n        let patch = k8s::Patch::Merge(patch);\n        let api = k8s::Api::<k8s::policy::Server>::namespaced(client.clone(), &ns);\n        api.patch(&srv.name_unchecked(), &k8s::PatchParams::default(), &patch)\n            .await\n            .expect(\"failed to patch server\");\n\n        // All requests should succeed\n        let (injected, uninjected) = tokio::join!(\n            curl.run(\"curl-audit-injected\", \"http://web\", LinkerdInject::Enabled),\n            curl.run(\n                \"curl-audit-uninjected\",\n                \"http://web\",\n                LinkerdInject::Disabled\n            ),\n        );\n        let (injected_status, uninjected_status) =\n            tokio::join!(injected.exit_code(), uninjected.exit_code());\n        assert_eq!(injected_status, 0, \"injected curl must contact web\");\n        assert_eq!(uninjected_status, 0, \"uninjected curl must contact web\");\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn ns_audit() {\n    with_temp_ns(|client, ns| async move {\n        change_access_policy(client.clone(), &ns, \"cluster-authenticated\").await;\n\n        // Create the web pod and wait for it to be ready.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        // Unmeshed requests should fail\n        let curl = curl::Runner::init(&client, &ns).await;\n        let (injected, uninjected) = tokio::join!(\n            curl.run(\"curl-injected\", \"http://web\", LinkerdInject::Enabled),\n            curl.run(\"curl-uninjected\", \"http://web\", LinkerdInject::Disabled),\n        );\n        let (injected_status, uninjected_status) =\n            tokio::join!(injected.exit_code(), uninjected.exit_code());\n        assert_eq!(injected_status, 0, \"injected curl must contact web\");\n        assert_ne!(uninjected_status, 0, \"uninjected curl must fail\");\n\n        change_access_policy(client.clone(), &ns, \"audit\").await;\n\n        // Recreate pod for it to pick the new default policy\n        let api = kube::Api::<k8s::api::core::v1::Pod>::namespaced(client.clone(), &ns);\n        kube::runtime::wait::delete::delete_and_finalize(\n            api,\n            \"web\",\n            &kube::api::DeleteParams::foreground(),\n        )\n        .await\n        .expect(\"web pod must be deleted\");\n\n        create_ready_pod(&client, web::pod(&ns)).await;\n\n        // All requests should work\n        let (injected, uninjected) = tokio::join!(\n            curl.run(\"curl-audit-injected\", \"http://web\", LinkerdInject::Enabled),\n            curl.run(\n                \"curl-audit-uninjected\",\n                \"http://web\",\n                LinkerdInject::Disabled\n            ),\n        );\n        let (injected_status, uninjected_status) =\n            tokio::join!(injected.exit_code(), uninjected.exit_code());\n        assert_eq!(injected_status, 0, \"injected curl must contact web\");\n        assert_eq!(uninjected_status, 0, \"uninject curl must contact web\");\n    })\n    .await;\n}\n\nasync fn change_access_policy(client: Client, ns: &str, policy: &str) {\n    let api = k8s::Api::<k8s::Namespace>::all(client.clone());\n    let patch = serde_json::json!({\n        \"metadata\": {\n            \"annotations\": {\n                \"config.linkerd.io/default-inbound-policy\": policy,\n            }\n        }\n    });\n    let patch = k8s::Patch::Merge(patch);\n    api.patch(ns, &k8s::PatchParams::default(), &patch)\n        .await\n        .expect(\"failed to patch namespace\");\n}\n"
  },
  {
    "path": "policy-test/tests/e2e_authorization_policy.rs",
    "content": "use kube::ResourceExt;\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s, gateway,\n    policy::{LocalTargetRef, NamespacedTargetRef},\n};\nuse linkerd_policy_test::{\n    await_condition, create, create_ready_pod, curl, endpoints_ready, web, with_temp_ns,\n    LinkerdInject,\n};\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn meshtls() {\n    with_temp_ns(|client, ns| async move {\n        // First create all of the policies we'll need so that the web pod\n        // starts up with the correct policy (to prevent races).\n        //\n        // The policy requires that all connections are authenticated with MeshTLS.\n        let (srv, all_mtls) = tokio::join!(\n            create(&client, web::server(&ns, None)),\n            create(&client, all_authenticated(&ns))\n        );\n        create(\n            &client,\n            authz_policy(\n                &ns,\n                \"web\",\n                LocalTargetRef::from_resource(&srv),\n                Some(NamespacedTargetRef::from_resource(&all_mtls)),\n            ),\n        )\n        .await;\n\n        // Create the web pod and wait for it to be ready.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let (injected, uninjected) = tokio::join!(\n            curl.run(\"curl-injected\", \"http://web\", LinkerdInject::Enabled),\n            curl.run(\"curl-uninjected\", \"http://web\", LinkerdInject::Disabled),\n        );\n        let (injected_status, uninjected_status) =\n            tokio::join!(injected.exit_code(), uninjected.exit_code());\n        assert_eq!(\n            injected_status, 0,\n            \"uninjected curl must fail to contact web\"\n        );\n        assert_ne!(uninjected_status, 0, \"injected curl must contact web\");\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn targets_route() {\n    with_temp_ns(|client, ns| async move {\n        // First create all of the policies we'll need so that the web pod\n        // starts up with the correct policy (to prevent races).\n        //\n        // The policy requires that all connections are authenticated with MeshTLS.\n        let (srv, all_mtls) = tokio::join!(\n            create(&client, web::server(&ns, None)),\n            create(&client, all_authenticated(&ns)),\n        );\n        // Create a route which matches the /allowed path.\n        let (root_route, _roux_route) = tokio::join!(\n            create(&client, http_route(\"root\", &ns, &srv.name_unchecked(), \"/\"),),\n            create(\n                &client,\n                http_route(\"roux\", &ns, &srv.name_unchecked(), \"/roux\")\n            )\n        );\n        // Create a policy which allows all authenticated clients\n        create(\n            &client,\n            authz_policy(\n                &ns,\n                \"root-authz\",\n                LocalTargetRef::from_resource(&root_route),\n                Some(NamespacedTargetRef::from_resource(&all_mtls)),\n            ),\n        )\n        .await;\n\n        // Create the web pod and wait for it to be ready.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        let curl = curl::Runner::init(&client, &ns).await;\n\n        let (allowed, no_route, unauth, no_authz) = tokio::join!(\n            curl.run(\"curl-allowed\", \"http://web/\", LinkerdInject::Enabled),\n            curl.run(\n                \"curl-no-route\",\n                \"http://web/noroute\",\n                LinkerdInject::Enabled\n            ),\n            curl.run(\"curl-unauth\", \"http://web/\", LinkerdInject::Disabled),\n            curl.run(\n                \"curl-route-without-authz\",\n                \"http://web/roux\",\n                LinkerdInject::Enabled\n            ),\n        );\n        let (allowed_status, no_route_status, unauth_status, no_authz_status) = tokio::join!(\n            allowed.http_status_code(),\n            no_route.http_status_code(),\n            unauth.http_status_code(),\n            no_authz.http_status_code(),\n        );\n        assert!(\n            allowed_status.is_success(),\n            \"curling allowed route must contact web\"\n        );\n        assert_eq!(\n            no_route_status,\n            hyper::StatusCode::NOT_FOUND,\n            \"curl which does not match route must not contact web\"\n        );\n        assert_eq!(\n            unauth_status,\n            hyper::StatusCode::FORBIDDEN,\n            \"curl which is not authenticated must not contact web\"\n        );\n        assert_eq!(\n            no_authz_status,\n            hyper::StatusCode::FORBIDDEN,\n            \"curl to route with no authorizations must not contact web\"\n        );\n\n        // Create a policy which allows all authenticated clients to access the server.\n        create(\n            &client,\n            authz_policy(\n                &ns,\n                \"server-authz\",\n                LocalTargetRef::from_resource(&srv),\n                Some(NamespacedTargetRef::from_resource(&all_mtls)),\n            ),\n        )\n        .await;\n\n        // Curl a route which doesn't have any authz, but its server does have\n        // an authz.\n        let route_with_server_authz_status = curl\n            .run(\n                \"curl-route-with-server-authz\",\n                \"http://web/roux\",\n                LinkerdInject::Enabled,\n            )\n            .await\n            .http_status_code()\n            .await;\n\n        assert!(\n            route_with_server_authz_status.is_success(),\n            \"curl to route with no authorizations on server with authorizations must contact web\"\n        );\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn targets_namespace() {\n    with_temp_ns(|client, ns| async move {\n        // First create all of the policies we'll need so that the web pod\n        // starts up with the correct policy (to prevent races).\n        //\n        // The policy requires that all connections are authenticated with MeshTLS.\n        let (_srv, all_mtls) = tokio::join!(\n            create(&client, web::server(&ns, None)),\n            create(&client, all_authenticated(&ns))\n        );\n        create(\n            &client,\n            authz_policy(\n                &ns,\n                \"web\",\n                LocalTargetRef {\n                    group: None,\n                    kind: \"Namespace\".to_string(),\n                    name: ns.clone(),\n                },\n                Some(NamespacedTargetRef::from_resource(&all_mtls)),\n            ),\n        )\n        .await;\n\n        // Create the web pod and wait for it to be ready.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let (injected, uninjected) = tokio::join!(\n            curl.run(\"curl-injected\", \"http://web\", LinkerdInject::Enabled),\n            curl.run(\"curl-uninjected\", \"http://web\", LinkerdInject::Disabled),\n        );\n        let (injected_status, uninjected_status) =\n            tokio::join!(injected.exit_code(), uninjected.exit_code());\n        assert_eq!(injected_status, 0, \"injected curl must contact web\");\n        assert_ne!(\n            uninjected_status, 0,\n            \"uninjected curl must fail to contact web\"\n        );\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn meshtls_namespace() {\n    with_temp_ns(|client, ns| async move {\n        // First create all of the policies we'll need so that the web pod\n        // starts up with the correct policy (to prevent races).\n        //\n        // The policy requires that all connections are authenticated with MeshTLS\n        // and come from service accounts in the given namespace.\n        let (srv, mtls_ns) = tokio::join!(\n            create(&client, web::server(&ns, None)),\n            create(&client, ns_authenticated(&ns))\n        );\n        create(\n            &client,\n            authz_policy(\n                &ns,\n                \"web\",\n                LocalTargetRef::from_resource(&srv),\n                Some(NamespacedTargetRef::from_resource(&mtls_ns)),\n            ),\n        )\n        .await;\n\n        // Create the web pod and wait for it to be ready.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let (injected, uninjected) = tokio::join!(\n            curl.run(\"curl-injected\", \"http://web\", LinkerdInject::Enabled),\n            curl.run(\"curl-uninjected\", \"http://web\", LinkerdInject::Disabled),\n        );\n        let (injected_status, uninjected_status) =\n            tokio::join!(injected.exit_code(), uninjected.exit_code());\n        assert_eq!(injected_status, 0, \"injected curl must contact web\");\n        assert_ne!(\n            uninjected_status, 0,\n            \"uninjected curl must fail to contact web\"\n        );\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn network() {\n    // In order to test the network policy, we need to create the client pod\n    // before creating the authorization policy. To avoid races, we do this by\n    // creating a `curl-lock` configmap that prevents curl from actually being\n    // executed. Once web is running with the correct policy, the configmap is\n    // deleted to unblock the curl pods.\n    with_temp_ns(|client, ns| async move {\n        let curl = curl::Runner::init(&client, &ns).await;\n        curl.create_lock().await;\n\n        // Create a curl pod and wait for it to get an IP.\n        let blessed = curl\n            .run(\"curl-blessed\", \"http://web\", LinkerdInject::Disabled)\n            .await;\n        let blessed_ip = blessed.ip().await;\n        tracing::debug!(curl.blessed.ip = %blessed_ip);\n\n        // Once we know the IP of the (blocked) pod, create an web\n        // authorization policy that permits connections from this pod.\n        let (srv, allow_ips) = tokio::join!(\n            create(&client, web::server(&ns, None)),\n            create(&client, allow_ips(&ns, Some(blessed_ip)))\n        );\n        create(\n            &client,\n            authz_policy(\n                &ns,\n                \"web\",\n                LocalTargetRef::from_resource(&srv),\n                Some(NamespacedTargetRef::from_resource(&allow_ips)),\n            ),\n        )\n        .await;\n\n        // Start web with the policy.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        // Once the web pod is ready, delete the `curl-lock` configmap to\n        // unblock curl from running.\n        curl.delete_lock().await;\n        tracing::info!(\"unblocked curl\");\n\n        // The blessed pod should be able to connect to the web pod.\n        let status = blessed.exit_code().await;\n        assert_eq!(status, 0, \"blessed curl pod must succeed\");\n\n        // Create another curl pod that is not included in the authorization. It\n        // should fail to connect to the web pod.\n        let status = curl\n            .run(\"curl-cursed\", \"http://web\", LinkerdInject::Disabled)\n            .await\n            .exit_code()\n            .await;\n        assert_ne!(status, 0, \"cursed curl pod must fail\");\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn both() {\n    // In order to test the network policy, we need to create the client pod\n    // before creating the authorization policy. To avoid races, we do this by\n    // creating a `curl-lock` configmap that prevents curl from actually being\n    // executed. Once web is running with the correct policy, the configmap is\n    // deleted to unblock the curl pods.\n    with_temp_ns(|client, ns| async move {\n        let curl = curl::Runner::init(&client, &ns).await;\n        curl.create_lock().await;\n\n        let (blessed_injected, blessed_uninjected) = tokio::join!(\n            curl.run(\n                \"curl-blessed-injected\",\n                \"http://web\",\n                LinkerdInject::Enabled,\n            ),\n            curl.run(\n                \"curl-blessed-uninjected\",\n                \"http://web\",\n                LinkerdInject::Disabled,\n            )\n        );\n        let (blessed_injected_ip, blessed_uninjected_ip) =\n            tokio::join!(blessed_injected.ip(), blessed_uninjected.ip(),);\n        tracing::debug!(curl.blessed.injected.ip = ?blessed_injected_ip);\n        tracing::debug!(curl.blessed.uninjected.ip = ?blessed_uninjected_ip);\n\n        // Once we know the IP of the (blocked) pod, create an web\n        // authorization policy that permits connections from this pod.\n        let (srv, allow_ips, all_mtls) = tokio::join!(\n            create(&client, web::server(&ns, None)),\n            create(\n                &client,\n                allow_ips(&ns, vec![blessed_injected_ip, blessed_uninjected_ip]),\n            ),\n            create(&client, all_authenticated(&ns))\n        );\n        create(\n            &client,\n            authz_policy(\n                &ns,\n                \"web\",\n                LocalTargetRef::from_resource(&srv),\n                vec![\n                    NamespacedTargetRef::from_resource(&allow_ips),\n                    NamespacedTargetRef::from_resource(&all_mtls),\n                ],\n            ),\n        )\n        .await;\n\n        // Start web with the policy.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        // Once the web pod is ready, delete the `curl-lock` configmap to\n        // unblock curl from running.\n        curl.delete_lock().await;\n        tracing::info!(\"unblocked curl\");\n\n        let (blessed_injected_status, blessed_uninjected_status) =\n            tokio::join!(blessed_injected.exit_code(), blessed_uninjected.exit_code());\n        // The blessed and injected pod should be able to connect to the web pod.\n        assert_eq!(\n            blessed_injected_status, 0,\n            \"blessed injected curl pod must succeed\"\n        );\n        // The blessed and uninjected pod should NOT be able to connect to the web pod.\n        assert_ne!(\n            blessed_uninjected_status, 0,\n            \"blessed uninjected curl pod must NOT succeed\"\n        );\n\n        let (cursed_injected, cursed_uninjected) = tokio::join!(\n            curl.run(\"curl-cursed-injected\", \"http://web\", LinkerdInject::Enabled,),\n            curl.run(\n                \"curl-cursed-uninjected\",\n                \"http://web\",\n                LinkerdInject::Disabled,\n            )\n        );\n        let (cursed_injected_status, cursed_uninjected_status) =\n            tokio::join!(cursed_injected.exit_code(), cursed_uninjected.exit_code(),);\n        assert_ne!(\n            cursed_injected_status, 0,\n            \"cursed injected curl pod must fail\"\n        );\n        assert_ne!(\n            cursed_uninjected_status, 0,\n            \"cursed uninjected curl pod must fail\"\n        );\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn either() {\n    // In order to test the network policy, we need to create the client pod\n    // before creating the authorization policy. To avoid races, we do this by\n    // creating a `curl-lock` configmap that prevents curl from actually being\n    // executed. Once web is running with the correct policy, the configmap is\n    // deleted to unblock the curl pods.\n    with_temp_ns(|client, ns| async move {\n        let curl = curl::Runner::init(&client, &ns).await;\n        curl.create_lock().await;\n\n        let (blessed_injected, blessed_uninjected) = tokio::join!(\n            curl.run(\n                \"curl-blessed-injected\",\n                \"http://web\",\n                LinkerdInject::Enabled,\n            ),\n            curl.run(\n                \"curl-blessed-uninjected\",\n                \"http://web\",\n                LinkerdInject::Disabled,\n            )\n        );\n        let (blessed_injected_ip, blessed_uninjected_ip) =\n            tokio::join!(blessed_injected.ip(), blessed_uninjected.ip());\n        tracing::debug!(curl.blessed.injected.ip = ?blessed_injected_ip);\n        tracing::debug!(curl.blessed.uninjected.ip = ?blessed_uninjected_ip);\n\n        // Once we know the IP of the (blocked) pod, create an web\n        // authorization policy that permits connections from this pod.\n        let (srv, allow_ips, all_mtls) = tokio::join!(\n            create(&client, web::server(&ns, None)),\n            create(&client, allow_ips(&ns, vec![blessed_uninjected_ip])),\n            create(&client, all_authenticated(&ns))\n        );\n        tokio::join!(\n            create(\n                &client,\n                authz_policy(\n                    &ns,\n                    \"web-from-ip\",\n                    LocalTargetRef::from_resource(&srv),\n                    vec![NamespacedTargetRef::from_resource(&allow_ips)],\n                ),\n            ),\n            create(\n                &client,\n                authz_policy(\n                    &ns,\n                    \"web-from-id\",\n                    LocalTargetRef::from_resource(&srv),\n                    vec![NamespacedTargetRef::from_resource(&all_mtls)],\n                ),\n            )\n        );\n\n        // Start web with the policy.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns)),\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        // Once the web pod is ready, delete the `curl-lock` configmap to\n        // unblock curl from running.\n        curl.delete_lock().await;\n        tracing::info!(\"unblocked curl\");\n\n        let (blessed_injected_status, blessed_uninjected_status) =\n            tokio::join!(blessed_injected.exit_code(), blessed_uninjected.exit_code());\n        // The blessed and injected pod should be able to connect to the web pod.\n        assert_eq!(\n            blessed_injected_status, 0,\n            \"blessed injected curl pod must succeed\"\n        );\n        // The blessed and uninjected pod should NOT be able to connect to the web pod.\n        assert_eq!(\n            blessed_uninjected_status, 0,\n            \"blessed uninjected curl pod must succeed\"\n        );\n\n        let (cursed_injected, cursed_uninjected) = tokio::join!(\n            curl.run(\"curl-cursed-injected\", \"http://web\", LinkerdInject::Enabled,),\n            curl.run(\n                \"curl-cursed-uninjected\",\n                \"http://web\",\n                LinkerdInject::Disabled,\n            ),\n        );\n        let (cursed_injected_status, cursed_uninjected_status) =\n            tokio::join!(cursed_injected.exit_code(), cursed_uninjected.exit_code());\n        assert_eq!(\n            cursed_injected_status, 0,\n            \"cursed injected curl pod must succeed\"\n        );\n        assert_ne!(\n            cursed_uninjected_status, 0,\n            \"cursed uninjected curl pod must fail\"\n        );\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn empty_authentications() {\n    with_temp_ns(|client, ns| async move {\n        // Create a policy that does not require any authentications.\n        let srv = create(&client, web::server(&ns, None)).await;\n        create(\n            &client,\n            authz_policy(&ns, \"web\", LocalTargetRef::from_resource(&srv), None),\n        )\n        .await;\n\n        // Create the web pod and wait for it to be ready.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        // All requests should work.\n        let curl = curl::Runner::init(&client, &ns).await;\n        let (injected, uninjected) = tokio::join!(\n            curl.run(\"curl-injected\", \"http://web\", LinkerdInject::Enabled),\n            curl.run(\"curl-uninjected\", \"http://web\", LinkerdInject::Disabled),\n        );\n        let (injected_status, uninjected_status) =\n            tokio::join!(injected.exit_code(), uninjected.exit_code());\n        assert_eq!(injected_status, 0, \"injected curl must contact web\");\n        assert_eq!(uninjected_status, 0, \"uninjected curl must contact web\");\n    })\n    .await;\n}\n\n// === helpers ===\n\nfn authz_policy(\n    ns: &str,\n    name: &str,\n    target: LocalTargetRef,\n    authns: impl IntoIterator<Item = NamespacedTargetRef>,\n) -> k8s::policy::AuthorizationPolicy {\n    k8s::policy::AuthorizationPolicy {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::AuthorizationPolicySpec {\n            target_ref: target,\n            required_authentication_refs: authns.into_iter().collect(),\n        },\n    }\n}\n\nfn all_authenticated(ns: &str) -> k8s::policy::MeshTLSAuthentication {\n    k8s::policy::MeshTLSAuthentication {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(\"all-authenticated\".to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::MeshTLSAuthenticationSpec {\n            identity_refs: None,\n            identities: Some(vec![\"*\".to_string()]),\n        },\n    }\n}\n\nfn ns_authenticated(ns: &str) -> k8s::policy::MeshTLSAuthentication {\n    k8s::policy::MeshTLSAuthentication {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(\"all-authenticated\".to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::MeshTLSAuthenticationSpec {\n            identity_refs: Some(vec![NamespacedTargetRef {\n                group: None,\n                kind: \"Namespace\".to_string(),\n                name: ns.to_string(),\n                namespace: None,\n            }]),\n            identities: None,\n        },\n    }\n}\n\nfn allow_ips(\n    ns: &str,\n    ips: impl IntoIterator<Item = std::net::IpAddr>,\n) -> k8s::policy::NetworkAuthentication {\n    k8s::policy::NetworkAuthentication {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(\"allow-pod\".to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::NetworkAuthenticationSpec {\n            networks: ips\n                .into_iter()\n                .map(|ip| k8s::policy::Network {\n                    cidr: ip.into(),\n                    except: None,\n                })\n                .collect(),\n        },\n    }\n}\n\nfn http_route(name: &str, ns: &str, server_name: &str, path: &str) -> k8s::policy::HttpRoute {\n    k8s::policy::HttpRoute {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::HttpRouteSpec {\n            parent_refs: Some(vec![gateway::HTTPRouteParentRefs {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: Some(\"Server\".to_string()),\n                namespace: Some(ns.to_string()),\n                name: server_name.to_string(),\n                section_name: None,\n                port: None,\n            }]),\n            hostnames: None,\n            rules: Some(vec![k8s::policy::httproute::HttpRouteRule {\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(path.to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    ..Default::default()\n                }]),\n                filters: None,\n                backend_refs: None,\n                timeouts: None,\n            }]),\n        },\n        status: None,\n    }\n}\n"
  },
  {
    "path": "policy-test/tests/e2e_egress_network.rs",
    "content": "use linkerd_policy_controller_k8s_api::{self as k8s, gateway};\nuse linkerd_policy_test::{\n    assert_status_accepted, await_condition, await_egress_net_status, await_gateway_route_status,\n    create, create_ready_pod, curl, endpoints_ready, web, with_temp_ns, LinkerdInject,\n};\n#[cfg(feature = \"gateway-api-experimental\")]\nuse linkerd_policy_test::{await_tcp_route_status, await_tls_route_status};\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn default_traffic_policy_http_allow() {\n    with_temp_ns(|client, ns| async move {\n        create(\n            &client,\n            k8s::policy::EgressNetwork {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"egress\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::EgressNetworkSpec {\n                    traffic_policy: k8s::policy::TrafficPolicy::Allow,\n                    networks: None,\n                },\n                status: None,\n            },\n        )\n        .await;\n        let status = await_egress_net_status(&client, &ns, \"egress\").await;\n        assert_status_accepted(status.conditions);\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let allowed = curl\n            .run(\n                \"curl-allowed\",\n                \"http://postman-echo.com/get\",\n                LinkerdInject::Enabled,\n            )\n            .await;\n\n        let allowed_status = allowed.http_status_code().await;\n        assert!(\n            allowed_status.is_success() || allowed_status.is_redirection(),\n            \"traffic should be allowed but got HTTP status code {allowed_status}\"\n        );\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn default_traffic_policy_http_deny() {\n    with_temp_ns(|client, ns| async move {\n        create(\n            &client,\n            k8s::policy::EgressNetwork {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"egress\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::EgressNetworkSpec {\n                    traffic_policy: k8s::policy::TrafficPolicy::Deny,\n                    networks: None,\n                },\n                status: None,\n            },\n        )\n        .await;\n        let status = await_egress_net_status(&client, &ns, \"egress\").await;\n        assert_status_accepted(status.conditions);\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let not_allowed = curl\n            .run(\n                \"curl-not-allowed\",\n                \"http://postman-echo.com/get\",\n                LinkerdInject::Enabled,\n            )\n            .await;\n\n        let not_allowed_status = not_allowed.http_status_code().await;\n        assert_eq!(not_allowed_status, 403, \"traffic should be blocked\");\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn default_traffic_policy_opaque_allow() {\n    with_temp_ns(|client, ns| async move {\n        create(\n            &client,\n            k8s::policy::EgressNetwork {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"egress\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::EgressNetworkSpec {\n                    traffic_policy: k8s::policy::TrafficPolicy::Allow,\n                    networks: None,\n                },\n                status: None,\n            },\n        )\n        .await;\n        let status = await_egress_net_status(&client, &ns, \"egress\").await;\n        assert_status_accepted(status.conditions);\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let allowed = curl\n            .run(\n                \"curl-allowed\",\n                \"https://postman-echo.com/get\",\n                LinkerdInject::Enabled,\n            )\n            .await;\n\n        let allowed_status = allowed.http_status_code().await;\n        assert!(\n            allowed_status.is_success() || allowed_status.is_redirection(),\n            \"traffic should be allowed but got HTTP status code {allowed_status}\"\n        );\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn default_traffic_policy_opaque_deny() {\n    with_temp_ns(|client, ns| async move {\n        create(\n            &client,\n            k8s::policy::EgressNetwork {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"egress\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::EgressNetworkSpec {\n                    traffic_policy: k8s::policy::TrafficPolicy::Deny,\n                    networks: None,\n                },\n                status: None,\n            },\n        )\n        .await;\n        let status = await_egress_net_status(&client, &ns, \"egress\").await;\n        assert_status_accepted(status.conditions);\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let not_allowed = curl\n            .run(\n                \"curl-not-allowed\",\n                \"https://postman-echo.com/get\",\n                LinkerdInject::Enabled,\n            )\n            .await;\n\n        let not_allowed_exit_code = not_allowed.exit_code().await;\n        assert_ne!(not_allowed_exit_code, 0, \"traffic should be blocked\");\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn explicit_allow_http_route() {\n    with_temp_ns(|client, ns| async move {\n        create(\n            &client,\n            k8s::policy::EgressNetwork {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"egress\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::EgressNetworkSpec {\n                    traffic_policy: k8s::policy::TrafficPolicy::Deny,\n                    networks: None,\n                },\n                status: None,\n            },\n        )\n        .await;\n        let status = await_egress_net_status(&client, &ns, \"egress\").await;\n        assert_status_accepted(status.conditions);\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let not_allowed_get = curl\n            .run(\n                \"curl-not-allowed-get\",\n                \"http://postman-echo.com/get\",\n                LinkerdInject::Enabled,\n            )\n            .await;\n\n        let not_allowed_get_status = not_allowed_get.http_status_code().await;\n        assert_eq!(not_allowed_get_status, 403, \"traffic should be blocked\");\n\n        // Now create an http route that will allow explicit hostname and explicit path\n        create(\n            &client,\n            gateway::HTTPRoute {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"http-route\".to_string()),\n                    ..Default::default()\n                },\n                spec: gateway::HTTPRouteSpec {\n                    parent_refs: Some(vec![gateway::HTTPRouteParentRefs {\n                        namespace: None,\n                        name: \"egress\".to_string(),\n                        port: Some(80),\n                        group: Some(\"policy.linkerd.io\".to_string()),\n                        kind: Some(\"EgressNetwork\".to_string()),\n                        section_name: None,\n                    }]),\n                    hostnames: None,\n                    rules: Some(vec![gateway::HTTPRouteRules {\n                        name: None,\n                        matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                            path: Some(gateway::HTTPRouteRulesMatchesPath {\n                                value: Some(\"/get\".to_string()),\n                                r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                            }),\n                            ..Default::default()\n                        }]),\n                        backend_refs: None,\n                        filters: None,\n                        ..Default::default()\n                    }]),\n                },\n                status: None,\n            },\n        )\n        .await;\n        await_gateway_route_status(&client, &ns, \"http-route\").await;\n\n        // traffic should be allowed for /get request\n        let allowed_get = curl\n            .run(\n                \"curl-allowed-get\",\n                \"http://postman-echo.com/get\",\n                LinkerdInject::Enabled,\n            )\n            .await;\n\n        let allowed_get_status = allowed_get.http_status_code().await;\n        assert!(\n            allowed_get_status.is_success() || allowed_get_status.is_redirection(),\n            \"traffic should be allowed but got HTTP status code {allowed_get_status}\"\n        );\n\n        // traffic should not be allowed for /ip request\n        let not_allowed_ip = curl\n            .run(\n                \"curl-not-allowed-ip\",\n                \"http://postman-echo.com/ip\",\n                LinkerdInject::Enabled,\n            )\n            .await;\n\n        let not_allowed_ip_status = not_allowed_ip.http_status_code().await;\n        assert_eq!(not_allowed_ip_status, 403, \"traffic should not be allowed\");\n    })\n    .await;\n}\n\n#[cfg(feature = \"gateway-api-experimental\")]\n#[tokio::test(flavor = \"current_thread\")]\nasync fn explicit_allow_tls_route() {\n    with_temp_ns(|client, ns| async move {\n        create(\n            &client,\n            k8s::policy::EgressNetwork {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"egress\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::EgressNetworkSpec {\n                    traffic_policy: k8s::policy::TrafficPolicy::Deny,\n                    networks: None,\n                },\n                status: None,\n            },\n        )\n        .await;\n        let status = await_egress_net_status(&client, &ns, \"egress\").await;\n        assert_status_accepted(status.conditions);\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let not_allowed_external = curl\n            .run(\n                \"not-allowed-external\",\n                \"https://postman-echo.com/get\",\n                LinkerdInject::Enabled,\n            )\n            .await;\n\n        let not_allowed_external_exit_code = not_allowed_external.exit_code().await;\n        assert_ne!(\n            not_allowed_external_exit_code, 0,\n            \"traffic should be blocked\"\n        );\n\n        // Now create a tls route that will allow explicit hostname and explicit path\n        create(\n            &client,\n            gateway::TLSRoute {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"tls-route\".to_string()),\n                    ..Default::default()\n                },\n                spec: gateway::TLSRouteSpec {\n                    parent_refs: Some(vec![gateway::TLSRouteParentRefs {\n                        namespace: None,\n                        name: \"egress\".to_string(),\n                        port: Some(443),\n                        group: Some(\"policy.linkerd.io\".to_string()),\n                        kind: Some(\"EgressNetwork\".to_string()),\n                        section_name: None,\n                    }]),\n                    hostnames: Some(vec![\"postman-echo.com\".to_string()]),\n                    rules: vec![gateway::TLSRouteRules {\n                        name: None,\n                        backend_refs: Some(vec![gateway::TLSRouteRulesBackendRefs {\n                            weight: None,\n                            namespace: None,\n                            name: \"egress\".to_string(),\n                            port: Some(443),\n                            group: Some(\"policy.linkerd.io\".to_string()),\n                            kind: Some(\"EgressNetwork\".to_string()),\n                        }]),\n                    }],\n                },\n                status: None,\n            },\n        )\n        .await;\n        await_tls_route_status(&client, &ns, \"tls-route\").await;\n\n        // External traffic should be allowed.\n        let allowed_external = curl\n            .run(\n                \"allowed-external\",\n                \"https://postman-echo.com/get\",\n                LinkerdInject::Enabled,\n            )\n            .await;\n\n        let allowed_external_status = allowed_external.http_status_code().await;\n        assert!(\n            allowed_external_status.is_success() || allowed_external_status.is_redirection(),\n            \"traffic should be allowed but got HTTP status code {allowed_external_status}\"\n        );\n\n        // traffic should not be allowed for google.com\n        let not_allowed_google = curl\n            .run(\n                \"curl-not-allowed-google\",\n                \"https://google.com/\",\n                LinkerdInject::Enabled,\n            )\n            .await;\n\n        let not_allowed_google_exit_code = not_allowed_google.exit_code().await;\n        assert_ne!(\n            not_allowed_google_exit_code, 0,\n            \"traffic should not be allowed\"\n        );\n    })\n    .await;\n}\n\n#[cfg(feature = \"gateway-api-experimental\")]\n#[tokio::test(flavor = \"current_thread\")]\nasync fn explicit_allow_tcp_route() {\n    with_temp_ns(|client, ns| async move {\n        create(\n            &client,\n            k8s::policy::EgressNetwork {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"egress\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::EgressNetworkSpec {\n                    traffic_policy: k8s::policy::TrafficPolicy::Deny,\n                    networks: None,\n                },\n                status: None,\n            },\n        )\n        .await;\n        let status = await_egress_net_status(&client, &ns, \"egress\").await;\n        assert_status_accepted(status.conditions);\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let not_allowed_external = curl\n            .run(\n                \"not-allowed-external\",\n                \"https://postman-echo.com/get\",\n                LinkerdInject::Enabled,\n            )\n            .await;\n\n        let not_allowed_external_exit_code = not_allowed_external.exit_code().await;\n        assert_ne!(\n            not_allowed_external_exit_code, 0,\n            \"traffic should be blocked\"\n        );\n\n        // Now create a tcp route that will allow explicit hostname and explicit path\n        create(\n            &client,\n            gateway::TCPRoute {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"tcp-route\".to_string()),\n                    ..Default::default()\n                },\n                spec: gateway::TCPRouteSpec {\n                    parent_refs: Some(vec![gateway::TCPRouteParentRefs {\n                        namespace: None,\n                        name: \"egress\".to_string(),\n                        port: Some(443),\n                        group: Some(\"policy.linkerd.io\".to_string()),\n                        kind: Some(\"EgressNetwork\".to_string()),\n                        section_name: None,\n                    }]),\n                    rules: vec![gateway::TCPRouteRules {\n                        name: None,\n                        backend_refs: Some(vec![gateway::TCPRouteRulesBackendRefs {\n                            weight: None,\n                            namespace: None,\n                            name: \"egress\".to_string(),\n                            port: Some(443),\n                            group: Some(\"policy.linkerd.io\".to_string()),\n                            kind: Some(\"EgressNetwork\".to_string()),\n                        }]),\n                    }],\n                },\n                status: None,\n            },\n        )\n        .await;\n        await_tcp_route_status(&client, &ns, \"tcp-route\").await;\n\n        // External traffic should be allowed on 443.\n        let allowed_external = curl\n            .run(\n                \"allowed-external\",\n                \"https://postman-echo.com/get\",\n                LinkerdInject::Enabled,\n            )\n            .await;\n\n        let allowed_external_status = allowed_external.http_status_code().await;\n        assert!(\n            allowed_external_status.is_success() || allowed_external_status.is_redirection(),\n            \"traffic should be allowed but got HTTP status code {allowed_external_status}\"\n        );\n\n        // External traffic should not be allowed on 80.\n        let not_allowed_google = curl\n            .run(\n                \"curl-not-allowed-google\",\n                \"http://postman-echo.com/get\",\n                LinkerdInject::Enabled,\n            )\n            .await;\n\n        let not_allowed_google_status = not_allowed_google.http_status_code().await;\n        assert_eq!(\n            not_allowed_google_status, 403,\n            \"traffic should not be allowed\"\n        );\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn routing_back_to_cluster_http_route() {\n    with_temp_ns(|client, ns| async move {\n        create(\n            &client,\n            k8s::policy::EgressNetwork {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"egress\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::EgressNetworkSpec {\n                    traffic_policy: k8s::policy::TrafficPolicy::Allow,\n                    networks: None,\n                },\n                status: None,\n            },\n        )\n        .await;\n        let status = await_egress_net_status(&client, &ns, \"egress\").await;\n        assert_status_accepted(status.conditions);\n\n        // Create the web pod and wait for it to be ready.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        // Now create an http route that will route requests\n        // back to the cluster if the request path is /get\n        // and will let the rest go through\n        create(\n            &client,\n            gateway::HTTPRoute {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"http-route\".to_string()),\n                    ..Default::default()\n                },\n                spec: gateway::HTTPRouteSpec {\n                    parent_refs: Some(vec![gateway::HTTPRouteParentRefs {\n                        namespace: None,\n                        name: \"egress\".to_string(),\n                        port: Some(80),\n                        group: Some(\"policy.linkerd.io\".to_string()),\n                        kind: Some(\"EgressNetwork\".to_string()),\n                        section_name: None,\n                    }]),\n                    hostnames: Some(vec![\"postman-echo.com\".to_string()]),\n                    rules: Some(vec![gateway::HTTPRouteRules {\n                        name: None,\n                        matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                            path: Some(gateway::HTTPRouteRulesMatchesPath {\n                                value: Some(\"/get\".to_string()),\n                                r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                            }),\n                            ..Default::default()\n                        }]),\n                        backend_refs: Some(vec![gateway::HTTPRouteRulesBackendRefs {\n                            weight: None,\n                            namespace: Some(ns.clone()),\n                            name: \"web\".to_string(),\n                            port: Some(80),\n                            group: None,\n                            kind: None,\n                            filters: None,\n                        }]),\n                        filters: None,\n                        ..Default::default()\n                    }]),\n                },\n                status: None,\n            },\n        )\n        .await;\n        await_gateway_route_status(&client, &ns, \"http-route\").await;\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let (in_cluster, out_of_cluster) = tokio::join!(\n            curl.run(\n                \"curl-in-cluster\",\n                \"http://postman-echo.com/get\",\n                LinkerdInject::Enabled\n            ),\n            curl.run(\n                \"curl-out-of-cluster\",\n                \"http://postman-echo.com/ip\",\n                LinkerdInject::Enabled\n            ),\n        );\n\n        let (in_cluster_status, out_of_cluster_status) = tokio::join!(\n            in_cluster.http_status_code(),\n            out_of_cluster.http_status_code(),\n        );\n\n        assert_eq!(in_cluster_status, 204); // in-cluster service returns 204\n        assert!(\n            out_of_cluster_status.is_success() || out_of_cluster_status.is_redirection(),\n            \"external service should be allowed but got HTTP status code {out_of_cluster_status}\"\n        );\n    })\n    .await;\n}\n\n#[cfg(feature = \"gateway-api-experimental\")]\n#[tokio::test(flavor = \"current_thread\")]\nasync fn routing_back_to_cluster_tls_route() {\n    with_temp_ns(|client, ns| async move {\n        create(\n            &client,\n            k8s::policy::EgressNetwork {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"egress\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::EgressNetworkSpec {\n                    traffic_policy: k8s::policy::TrafficPolicy::Allow,\n                    networks: None,\n                },\n                status: None,\n            },\n        )\n        .await;\n        let status = await_egress_net_status(&client, &ns, \"egress\").await;\n        assert_status_accepted(status.conditions);\n\n        // Create the web pod and wait for it to be ready.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        // Now create an tls route that will route requests\n        // to an in-cluster service based on SNI\n        create(\n            &client,\n            gateway::TLSRoute {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"tls-route\".to_string()),\n                    ..Default::default()\n                },\n                spec: gateway::TLSRouteSpec {\n                    parent_refs: Some(vec![gateway::TLSRouteParentRefs {\n                        namespace: None,\n                        name: \"egress\".to_string(),\n                        port: Some(443),\n                        group: Some(\"policy.linkerd.io\".to_string()),\n                        kind: Some(\"EgressNetwork\".to_string()),\n                        section_name: None,\n                    }]),\n                    hostnames: Some(vec![\"postman-echo.com\".to_string()]),\n                    rules: vec![gateway::TLSRouteRules {\n                        name: None,\n                        backend_refs: Some(vec![gateway::TLSRouteRulesBackendRefs {\n                            weight: None,\n                            namespace: Some(ns.clone()),\n                            name: \"web\".to_string(),\n                            port: Some(80),\n                            group: None,\n                            kind: None,\n                        }]),\n                    }],\n                },\n                status: None,\n            },\n        )\n        .await;\n        await_tls_route_status(&client, &ns, \"tls-route\").await;\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let (in_cluster, out_of_cluster) = tokio::join!(\n            curl.run(\n                \"curl-in-cluster\",\n                \"https://postman-echo.com/get\",\n                LinkerdInject::Enabled\n            ),\n            curl.run(\n                \"curl-out-of-cluster\",\n                \"https://google.com/not-there\",\n                LinkerdInject::Enabled\n            ),\n        );\n\n        let (in_cluster_exit_code, out_of_cluster_status) =\n            tokio::join!(in_cluster.exit_code(), out_of_cluster.http_status_code(),);\n\n        assert_ne!(in_cluster_exit_code, 0); // in-cluster service fails because it does not expect TLS\n        assert_eq!(out_of_cluster_status, 404); // external service returns 404 as this path does not exist\n    })\n    .await;\n}\n\n#[cfg(feature = \"gateway-api-experimental\")]\n#[tokio::test(flavor = \"current_thread\")]\nasync fn routing_back_to_cluster_tcp_route() {\n    with_temp_ns(|client, ns| async move {\n        create(\n            &client,\n            k8s::policy::EgressNetwork {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"egress\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::EgressNetworkSpec {\n                    traffic_policy: k8s::policy::TrafficPolicy::Allow,\n                    networks: None,\n                },\n                status: None,\n            },\n        )\n        .await;\n        let status = await_egress_net_status(&client, &ns, \"egress\").await;\n        assert_status_accepted(status.conditions);\n\n        // Create the web pod and wait for it to be ready.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        // Now create an tls route that will route requests\n        // to an in-cluster service based on SNI\n        create(\n            &client,\n            gateway::TCPRoute {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"tcp-route\".to_string()),\n                    ..Default::default()\n                },\n                spec: gateway::TCPRouteSpec {\n                    parent_refs: Some(vec![gateway::TCPRouteParentRefs {\n                        namespace: None,\n                        name: \"egress\".to_string(),\n                        port: Some(80),\n                        group: Some(\"policy.linkerd.io\".to_string()),\n                        kind: Some(\"EgressNetwork\".to_string()),\n                        section_name: None,\n                    }]),\n                    rules: vec![gateway::TCPRouteRules {\n                        name: None,\n                        backend_refs: Some(vec![gateway::TCPRouteRulesBackendRefs {\n                            weight: None,\n                            namespace: Some(ns.clone()),\n                            name: \"web\".to_string(),\n                            port: Some(80),\n                            group: None,\n                            kind: None,\n                        }]),\n                    }],\n                },\n                status: None,\n            },\n        )\n        .await;\n        await_tcp_route_status(&client, &ns, \"tcp-route\").await;\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let in_cluster = curl\n            .run(\n                \"curl-in-cluster\",\n                \"http://postman-echo.com/get\",\n                LinkerdInject::Enabled,\n            )\n            .await;\n\n        let in_cluster_status = in_cluster.http_status_code().await;\n\n        assert_eq!(in_cluster_status, 204); // in-cluster service returns 204\n    })\n    .await;\n}\n"
  },
  {
    "path": "policy-test/tests/e2e_failure_accrual.rs",
    "content": "use linkerd_policy_test::{\n    annotate_service, bb, create, create_ready_pod, curl, with_temp_ns, LinkerdInject,\n};\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn consecutive_failures() {\n    const MAX_FAILS: usize = 5;\n    with_temp_ns(|client, ns| async move {\n        // Create a bb service with one pod that always returns 500s.\n        let bad_pod = bb::Terminus::new(&ns)\n            .named(\"bb-bad\")\n            .percent_failure(100)\n            .to_pod();\n        let svc = {\n            let svc = bb::Terminus::service(&ns);\n            annotate_service(svc, maplit::btreemap!{\n                \"balancer.linkerd.io/failure-accrual\" => \"consecutive\".to_string(),\n                \"balancer.linkerd.io/failure-accrual-consecutive-max-failures\" => MAX_FAILS.to_string(),\n                // don't allow the failing pod to enter probation during the test.\n                \"balancer.linkerd.io/failure-accrual-consecutive-min-penalty\" => \"5m\".to_string(),\n                \"balancer.linkerd.io/failure-accrual-consecutive-max-penalty\" => \"10m\".to_string(),\n            })\n        };\n        tokio::join!(\n            create(&client, svc),\n            create_ready_pod(&client, bad_pod),\n        );\n\n        let curl = curl::Runner::init(&client, &ns)\n            .await\n            .run_execable(\"curl\", LinkerdInject::Enabled)\n            .await;\n\n        let url = format!(\"http://{}\", bb::Terminus::SERVICE_NAME);\n        for request in 0..MAX_FAILS * 2 {\n            tracing::info!(\"Sending request {request}...\");\n            let status = curl\n                .get(&url)\n                .await\n                .expect(\"curl command should succeed\");\n            tracing::info!(request, ?status);\n            if request < MAX_FAILS {\n                assert_eq!(status, hyper::StatusCode::INTERNAL_SERVER_ERROR);\n            } else {\n                // Once the circuit breaker has tripped, any in flight request\n                // will fail with a 504 due to failfast, and subsequent requests\n                // will fail with a 503 because the balancer has no available\n                // endpoints.\n                assert!(\n                    matches!(status, hyper::StatusCode::GATEWAY_TIMEOUT | hyper::StatusCode::SERVICE_UNAVAILABLE),\n                    \"expected 503 or 504, got {status:?}\"\n                );\n            }\n        }\n    })\n    .await;\n}\n"
  },
  {
    "path": "policy-test/tests/e2e_http_local_ratelimit_policy.rs",
    "content": "use k8s::policy::{NamespacedTargetRef, RateLimitPolicySpec};\nuse kube::api::LogParams;\nuse linkerd_policy_controller_k8s_api::{\n    self as k8s,\n    policy::{HttpLocalRateLimitPolicy, Limit, LocalTargetRef, Override},\n};\nuse linkerd_policy_test::{\n    await_condition, await_service_account, create, create_ready_pod, endpoints_ready, web,\n    with_temp_ns,\n};\nuse maplit::{btreemap, convert_args};\n\n#[tokio::test(flavor = \"current_thread\")]\n/// Tests reaching the rate limit for:\n/// - a client with a meshed identity\n/// - a client with a meshed identity that has an override\n/// - an unmeshed client\nasync fn ratelimit_identity_and_overrides() {\n    with_temp_ns(|client, ns| async move {\n        // create a server with a permissive access policy to not worry about auth\n        create(\n            &client,\n            web::server(&ns, Some(\"all-unauthenticated\".to_string())),\n        )\n        .await;\n\n        // create the pod \"web\" with its service\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        mk_ratelimit(&client, &ns, Some(70), Some(5), Some(10)).await;\n\n        tokio::join!(\n            create_service_account(&client, &ns, \"meshed-regular\"),\n            create_service_account(&client, &ns, \"meshed-id-1\"),\n            create_service_account(&client, &ns, \"unmeshed\"),\n        );\n\n        // all clients will send 20rps for 30s\n        tokio::join!(\n            create_fortio_pod(&client, &ns, \"meshed-regular\", true, 20, 30),\n            create_fortio_pod(&client, &ns, \"meshed-id-1\", true, 20, 30),\n            create_fortio_pod(&client, &ns, \"unmeshed\", false, 20, 30),\n        );\n\n        let (rl_meshed_regular, rl_meshed_id_1, rl_unmeshed) = tokio::join!(\n            fetch_fortio_rl(&client, &ns, \"meshed-regular\"),\n            fetch_fortio_rl(&client, &ns, \"meshed-id-1\"),\n            fetch_fortio_rl(&client, &ns, \"unmeshed\"),\n        );\n        tracing::info!(%rl_meshed_regular, %rl_meshed_id_1, %rl_unmeshed, \"Rate limit percentages\");\n\n        // for 20rps rate-limited at 5rps, we expect around 75% of requests to be rate limited\n        assert!(\n            (70..=80).contains(&rl_meshed_regular),\n            \"around 75% of meshed-regular's requests should be rate limited\",\n        );\n\n        // for 20rps rate-limited at 10rps, we expect around 50% of requests to be rate limited\n        assert!(\n            (45..=55).contains(&rl_meshed_id_1),\n            \"around 50% of meshed-id-1's requests should be rate limited\",\n        );\n\n        // for 20rps rate-limited at 5rps, we expect around 75% of requests to be rate limited\n        assert!(\n            (70..=80).contains(&rl_unmeshed),\n            \"around 75% of unmeshed's requests should be rate limited\",\n        );\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\n/// Tests reaching the total rate limit\nasync fn ratelimit_total() {\n    with_temp_ns(|client, ns| async move {\n        // create a server with a permissive access policy to not worry about auth\n        create(\n            &client,\n            web::server(&ns, Some(\"all-unauthenticated\".to_string())),\n        )\n        .await;\n\n        // create the pod \"web\" with its service\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        mk_ratelimit(&client, &ns, Some(70), None, None).await;\n\n        create_service_account(&client, &ns, \"meshed-id-1\").await;\n\n        // client will send 100rps for 30s\n        create_fortio_pod(&client, &ns, \"meshed-id-1\", true, 100, 30).await;\n\n        let rl_meshed_id_1 = fetch_fortio_rl(&client, &ns, \"meshed-id-1\").await;\n        tracing::info!(%rl_meshed_id_1, \"Rate limit percentage\");\n\n        // for 100rps rate-limited at 70rps, we expect around 30% of requests to be rate limited\n        assert!(\n            (25..=35).contains(&rl_meshed_id_1),\n            \"around 30% of meshed-id-1's requests should be rate limited\",\n        );\n    })\n    .await;\n}\n\n/// Makes a ratelimit policy \"rl\" with the given rates, where override_rps is the rate limit for\n/// the meshed-id-1 service account. It waits for the ratelimit to be accepted by the policy\n/// controller.\nasync fn mk_ratelimit(\n    client: &kube::Client,\n    ns: &str,\n    total_rps: Option<u32>,\n    identity_rps: Option<u32>,\n    override_rps: Option<u32>,\n) {\n    let total = total_rps.map(|rps| Limit {\n        requests_per_second: rps,\n    });\n    let identity = identity_rps.map(|rps| Limit {\n        requests_per_second: rps,\n    });\n    let overrides = override_rps.map(|rps| {\n        vec![Override {\n            requests_per_second: rps,\n            client_refs: vec![NamespacedTargetRef {\n                group: Some(\"core\".to_string()),\n                kind: \"ServiceAccount\".to_string(),\n                name: \"meshed-id-1\".to_string(),\n                namespace: Some(ns.to_string()),\n            }],\n        }]\n    });\n    create(\n        client,\n        HttpLocalRateLimitPolicy {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(\"rl\".to_string()),\n                ..Default::default()\n            },\n            spec: RateLimitPolicySpec {\n                target_ref: LocalTargetRef {\n                    group: Some(\"policy.linkerd.io\".to_string()),\n                    kind: \"Server\".to_string(),\n                    name: \"web\".to_string(),\n                },\n                total,\n                identity,\n                overrides,\n            },\n            status: None,\n        },\n    )\n    .await;\n\n    tracing::debug!(%ns, \"Waiting for ratelimit to be accepted\");\n    await_condition(client, ns, \"rl\", ratelimit_accepted).await;\n}\n\n// Wait for the ratelimit to be accepted by the policy controller\nfn ratelimit_accepted(obj: Option<&HttpLocalRateLimitPolicy>) -> bool {\n    if let Some(rl) = obj {\n        return rl\n            .status\n            .as_ref()\n            .map(|s| {\n                s.conditions\n                    .iter()\n                    .any(|c| c.type_ == \"Accepted\" && c.status == \"True\")\n            })\n            .unwrap_or(false);\n    }\n    false\n}\n\nasync fn create_service_account(client: &kube::Client, ns: &str, name: &str) {\n    create(\n        client,\n        k8s::api::core::v1::ServiceAccount {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(name.to_string()),\n                ..Default::default()\n            },\n            ..Default::default()\n        },\n    )\n    .await;\n    await_service_account(client, ns, name).await;\n}\n\n// Creates a fortio pod that sends requests to the \"web\" service\nasync fn create_fortio_pod(\n    client: &kube::Client,\n    ns: &str,\n    name: &str,\n    injected: bool,\n    rps: u32,\n    duration: u32,\n) {\n    let annotations = if injected {\n        Some(convert_args!(btreemap!(\n           \"linkerd.io/inject\" => \"enabled\",\n        )))\n    } else {\n        None\n    };\n\n    let pod = k8s::Pod {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            annotations,\n            labels: Some(convert_args!(btreemap!(\n                \"app\" => name,\n            ))),\n            ..Default::default()\n        },\n        spec: Some(k8s::PodSpec {\n            containers: vec![k8s::api::core::v1::Container {\n                name: \"fortio\".to_string(),\n                image: Some(\"fortio/fortio:latest\".to_string()),\n                args: Some(vec![\n                    \"load\".to_string(),\n                    \"-qps\".to_string(),\n                    rps.to_string(),\n                    \"-t\".to_string(),\n                    format!(\"{}s\", duration),\n                    \"-quiet\".to_string(),\n                    \"http://web\".to_string(),\n                ]),\n                ..Default::default()\n            }],\n            service_account_name: Some(name.to_string()),\n            ..Default::default()\n        }),\n        ..k8s::Pod::default()\n    };\n\n    create_ready_pod(client, pod).await;\n}\n\n/// Waits for the fortio pod to complete and parses the logs to extract the rate limit percentage.\nasync fn fetch_fortio_rl(client: &kube::Client, ns: &str, name: &str) -> u32 {\n    tracing::debug!(%ns, %name, \"Waiting to finish\");\n    await_condition(client, ns, name, is_fortio_container_terminated).await;\n\n    // log output should look something like \"Code 429 : 454 (75.7 %)\"\n    let pattern = r\"Code 429 : \\d+ \\((\\d+)\\.?\\d+? %\\)\";\n    let re = regex::Regex::new(pattern).unwrap();\n\n    let log_params = LogParams {\n        container: Some(\"fortio\".to_string()),\n        tail_lines: Some(2),\n        ..Default::default()\n    };\n    let api = kube::Api::<k8s::Pod>::namespaced(client.clone(), ns);\n    let result = api\n        .logs(name, &log_params)\n        .await\n        .expect(\"failed to fetch logs\")\n        .split('\\n')\n        .take(1)\n        .collect::<String>();\n\n    re.captures(&result)\n        .unwrap_or_else(|| panic!(\"failed to parse log: {result}\"))\n        .get(1)\n        .unwrap_or_else(|| panic!(\"failed to parse log: {result}\"))\n        .as_str()\n        .parse()\n        .expect(\"failed to parse rate limit percentage\")\n}\n\nfn is_fortio_container_terminated(pod: Option<&k8s::Pod>) -> bool {\n    let terminated = || -> Option<&k8s_openapi::api::core::v1::ContainerStateTerminated> {\n        pod?.status\n            .as_ref()?\n            .container_statuses\n            .as_ref()?\n            .iter()\n            .find(|cs| cs.name == \"fortio\")?\n            .state\n            .as_ref()?\n            .terminated\n            .as_ref()\n    };\n    terminated().is_some()\n}\n"
  },
  {
    "path": "policy-test/tests/e2e_http_routing.rs",
    "content": "use linkerd_policy_controller_k8s_api::{self as k8s, gateway};\nuse linkerd_policy_test::{\n    await_condition, create, create_ready_pod, curl, endpoints_ready, web, with_temp_ns,\n    LinkerdInject,\n};\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn path_based_routing() {\n    with_temp_ns(|client, ns| async move {\n        create(\n            &client,\n            k8s::policy::HttpRoute {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"web-route\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::HttpRouteSpec {\n                    parent_refs: Some(vec![k8s::gateway::HTTPRouteParentRefs {\n                        namespace: None,\n                        name: \"web\".to_string(),\n                        port: Some(80),\n                        group: Some(\"core\".to_string()),\n                        kind: Some(\"Service\".to_string()),\n                        section_name: None,\n                    }]),\n                    hostnames: None,\n                    rules: Some(vec![\n                        rule(\"/valid\".to_string(), \"web\".to_string()),\n                        rule(\"/invalid\".to_string(), \"foobar\".to_string()),\n                    ]),\n                },\n                status: None,\n            },\n        )\n        .await;\n\n        // Create the web pod and wait for it to be ready.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let (valid, invalid, notfound) = tokio::join!(\n            curl.run(\"curl-valid\", \"http://web/valid\", LinkerdInject::Enabled),\n            curl.run(\"curl-invalid\", \"http://web/invalid\", LinkerdInject::Enabled),\n            curl.run(\n                \"curl-notfound\",\n                \"http://web/notfound\",\n                LinkerdInject::Enabled\n            ),\n        );\n        let (valid_status, invalid_status, notfound_status) = tokio::join!(\n            valid.http_status_code(),\n            invalid.http_status_code(),\n            notfound.http_status_code()\n        );\n        assert_eq!(valid_status, 204, \"request must be routed to valid backend\");\n        assert_eq!(invalid_status, 500, \"invalid backend must produce 500\");\n        assert_eq!(\n            notfound_status, 404,\n            \"request not matching any rule must produce 404\"\n        )\n    })\n    .await;\n}\n\n// === helpers ===\n\nfn rule(path: String, backend: String) -> k8s::policy::httproute::HttpRouteRule {\n    k8s::policy::httproute::HttpRouteRule {\n        matches: Some(vec![gateway::HTTPRouteRulesMatches {\n            path: Some(gateway::HTTPRouteRulesMatchesPath {\n                value: Some(path),\n                r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n            }),\n            ..Default::default()\n        }]),\n        backend_refs: Some(vec![gateway::HTTPRouteRulesBackendRefs {\n            weight: None,\n            group: None,\n            kind: None,\n            name: backend,\n            namespace: None,\n            port: Some(80),\n            filters: None,\n        }]),\n        filters: None,\n        timeouts: None,\n    }\n}\n"
  },
  {
    "path": "policy-test/tests/e2e_server_authorization.rs",
    "content": "use linkerd_policy_controller_k8s_api::{\n    self as k8s, policy::server_authorization::Client as ClientAuthz, ResourceExt,\n};\nuse linkerd_policy_test::{\n    await_condition, create, create_ready_pod, curl, endpoints_ready, web, with_temp_ns,\n    LinkerdInject,\n};\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn meshtls() {\n    with_temp_ns(|client, ns| async move {\n        let srv = create(&client, web::server(&ns, None)).await;\n\n        create(\n            &client,\n            server_authz(\n                &ns,\n                \"web\",\n                &srv,\n                ClientAuthz {\n                    mesh_tls: Some(k8s::policy::server_authorization::MeshTls {\n                        identities: Some(vec![\"*\".to_string()]),\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                },\n            ),\n        )\n        .await;\n\n        // Create the web pod and wait for it to be ready.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        let curl = curl::Runner::init(&client, &ns).await;\n        let (injected, uninjected) = tokio::join!(\n            curl.run(\"curl-injected\", \"http://web\", LinkerdInject::Enabled),\n            curl.run(\"curl-uninjected\", \"http://web\", LinkerdInject::Disabled),\n        );\n        let (injected_status, uninjected_status) =\n            tokio::join!(injected.exit_code(), uninjected.exit_code());\n        assert_eq!(injected_status, 0, \"injected curl must succeed\");\n        assert_eq!(uninjected_status, 22, \"uninjected curl must fail\");\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn network() {\n    with_temp_ns(|client, ns| async move {\n        let curl = curl::Runner::init(&client, &ns).await;\n\n        // Create a lock that prevents `curl` pods from running. This allows us\n        // to create a curl pod so that its IP can be obtained to be used in an\n        // authorization policy.\n        curl.create_lock().await;\n\n        // Create a curl pod and wait for it to get an IP.\n        let blessed = curl\n            .run(\"curl-blessed\", \"http://web\", LinkerdInject::Disabled)\n            .await;\n        let blessed_ip = blessed.ip().await;\n        tracing::debug!(curl.blessed.ip = %blessed_ip);\n\n        // Once we know the IP of the (blocked) pod, create an web\n        // authorization policy that permits connections from this pod.\n        let srv = create(&client, web::server(&ns, None)).await;\n        create(\n            &client,\n            server_authz(\n                &ns,\n                \"web\",\n                &srv,\n                ClientAuthz {\n                    networks: Some(vec![k8s::policy::Network {\n                        cidr: blessed_ip.into(),\n                        except: None,\n                    }]),\n                    unauthenticated: true,\n                    ..Default::default()\n                },\n            ),\n        )\n        .await;\n\n        // Start web with the policy.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        // Once the web pod is ready, delete the `curl-lock` configmap to\n        // unblock curl from running.\n        curl.delete_lock().await;\n        tracing::info!(\"unblocked curl\");\n\n        // The blessed pod should be able to connect to the web pod.\n        let status = blessed.exit_code().await;\n        assert_eq!(status, 0, \"blessed curl must succeed\");\n\n        // Create another curl pod that is not included in the authorization. It\n        // should fail to connect to the web pod.\n        let status = curl\n            .run(\"curl-cursed\", \"http://web\", LinkerdInject::Disabled)\n            .await\n            .exit_code()\n            .await;\n        assert_eq!(status, 22, \"cursed curl must fail\");\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn both() {\n    with_temp_ns(|client, ns| async move {\n        let curl = curl::Runner::init(&client, &ns).await;\n\n        // Create a lock that prevents `curl` pods from running. This allows us\n        // to create a curl pod so that its IP can be obtained to be used in an\n        // authorization policy.\n        curl.create_lock().await;\n\n        let (blessed_injected, blessed_uninjected) = tokio::join!(\n            curl.run(\n                \"curl-blessed-injected\",\n                \"http://web\",\n                LinkerdInject::Enabled,\n            ),\n            curl.run(\n                \"curl-blessed-uninjected\",\n                \"http://web\",\n                LinkerdInject::Disabled,\n            )\n        );\n        let (blessed_injected_ip, blessed_uninjected_ip) =\n            tokio::join!(blessed_injected.ip(), blessed_uninjected.ip(),);\n        tracing::debug!(curl.blessed.injected.ip = ?blessed_injected_ip);\n        tracing::debug!(curl.blessed.uninjected.ip = ?blessed_uninjected_ip);\n\n        // Once we know the IP of the (blocked) pod, create an web\n        // authorization policy that permits connections from this pod.\n        let srv = create(&client, web::server(&ns, None)).await;\n        create(\n            &client,\n            server_authz(\n                &ns,\n                \"web\",\n                &srv,\n                ClientAuthz {\n                    networks: Some(vec![\n                        k8s::policy::Network {\n                            cidr: blessed_injected_ip.into(),\n                            except: None,\n                        },\n                        k8s::policy::Network {\n                            cidr: blessed_uninjected_ip.into(),\n                            except: None,\n                        },\n                    ]),\n                    mesh_tls: Some(k8s::policy::server_authorization::MeshTls {\n                        identities: Some(vec![\"*\".to_string()]),\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                },\n            ),\n        )\n        .await;\n\n        // Start web with the policy.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns))\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        // Once the web pod is ready, delete the `curl-lock` configmap to\n        // unblock curl from running.\n        curl.delete_lock().await;\n        tracing::info!(\"unblocked curl\");\n\n        let (blessed_injected_status, blessed_uninjected_status) =\n            tokio::join!(blessed_injected.exit_code(), blessed_uninjected.exit_code());\n        // The blessed and injected pod should be able to connect to the web pod.\n        assert_eq!(\n            blessed_injected_status, 0,\n            \"blessed injected curl must succeed\"\n        );\n        // The blessed and uninjected pod should NOT be able to connect to the web pod.\n        assert_eq!(\n            blessed_uninjected_status, 22,\n            \"blessed uninjected curl must fail\"\n        );\n\n        let (cursed_injected, cursed_uninjected) = tokio::join!(\n            curl.run(\"curl-cursed-injected\", \"http://web\", LinkerdInject::Enabled,),\n            curl.run(\n                \"curl-cursed-uninjected\",\n                \"http://web\",\n                LinkerdInject::Disabled,\n            )\n        );\n        let (cursed_injected_status, cursed_uninjected_status) =\n            tokio::join!(cursed_injected.exit_code(), cursed_uninjected.exit_code(),);\n        assert_eq!(cursed_injected_status, 22, \"cursed injected curl must fail\");\n        assert_eq!(\n            cursed_uninjected_status, 22,\n            \"cursed uninjected curl must fail\"\n        );\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn either() {\n    with_temp_ns(|client, ns| async move {\n        let curl = curl::Runner::init(&client, &ns).await;\n\n        tracing::info!(\"Blocking curl\");\n        curl.create_lock().await;\n\n        let (blessed_injected, blessed_uninjected) = tokio::join!(\n            curl.run(\n                \"curl-blessed-injected\",\n                \"http://web\",\n                LinkerdInject::Enabled,\n            ),\n            curl.run(\n                \"curl-blessed-uninjected\",\n                \"http://web\",\n                LinkerdInject::Disabled,\n            )\n        );\n        let (blessed_injected_ip, blessed_uninjected_ip) =\n            tokio::join!(blessed_injected.ip(), blessed_uninjected.ip());\n        tracing::debug!(curl.blessed.injected.ip = ?blessed_injected_ip);\n        tracing::debug!(curl.blessed.uninjected.ip = ?blessed_uninjected_ip);\n\n        // Once we know the IP of the (blocked) pod, create an web\n        // authorization policy that permits connections from this pod.\n        let srv = create(&client, web::server(&ns, None)).await;\n        tokio::join!(\n            create(\n                &client,\n                server_authz(\n                    &ns,\n                    \"web-from-ip\",\n                    &srv,\n                    ClientAuthz {\n                        unauthenticated: true,\n                        networks: Some(vec![\n                            k8s::policy::Network {\n                                cidr: blessed_injected_ip.into(),\n                                except: None,\n                            },\n                            k8s::policy::Network {\n                                cidr: blessed_uninjected_ip.into(),\n                                except: None,\n                            },\n                        ]),\n                        ..Default::default()\n                    },\n                )\n            ),\n            create(\n                &client,\n                server_authz(\n                    &ns,\n                    \"web-from-id\",\n                    &srv,\n                    ClientAuthz {\n                        mesh_tls: Some(k8s::policy::server_authorization::MeshTls {\n                            identities: Some(vec![\"*\".to_string()]),\n                            ..Default::default()\n                        }),\n                        ..Default::default()\n                    },\n                )\n            ),\n        );\n\n        // Start web with the policy.\n        tokio::join!(\n            create(&client, web::service(&ns)),\n            create_ready_pod(&client, web::pod(&ns)),\n        );\n\n        await_condition(&client, &ns, \"web\", endpoints_ready).await;\n\n        // Once the web pod is ready, delete the `curl-lock` configmap to\n        // unblock curl from running.\n        curl.delete_lock().await;\n        tracing::info!(\"unblocked curl\");\n\n        let (blessed_injected_status, blessed_uninjected_status) =\n            tokio::join!(blessed_injected.exit_code(), blessed_uninjected.exit_code());\n        assert_eq!(\n            blessed_injected_status, 0,\n            \"blessed injected curl must succeed\"\n        );\n        assert_eq!(\n            blessed_uninjected_status, 0,\n            \"blessed uninjected curl must succeed\"\n        );\n\n        let (cursed_injected, cursed_uninjected) = tokio::join!(\n            curl.run(\"curl-cursed-injected\", \"http://web\", LinkerdInject::Enabled,),\n            curl.run(\n                \"curl-cursed-uninjected\",\n                \"http://web\",\n                LinkerdInject::Disabled,\n            ),\n        );\n        let (cursed_injected_status, cursed_uninjected_status) =\n            tokio::join!(cursed_injected.exit_code(), cursed_uninjected.exit_code());\n        assert_eq!(\n            cursed_injected_status, 0,\n            \"cursed injected curl must succeed\"\n        );\n        assert_eq!(\n            cursed_uninjected_status, 22,\n            \"cursed uninjected curl must fail\"\n        );\n    })\n    .await;\n}\n\n// === helpers ===\n\nfn server_authz(\n    ns: &str,\n    name: &str,\n    target: &k8s::policy::Server,\n    client: ClientAuthz,\n) -> k8s::policy::ServerAuthorization {\n    k8s::policy::ServerAuthorization {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::ServerAuthorizationSpec {\n            server: k8s::policy::server_authorization::Server {\n                name: Some(target.name_unchecked()),\n                selector: None,\n            },\n            client,\n        },\n    }\n}\n"
  },
  {
    "path": "policy-test/tests/inbound_api.rs",
    "content": "use futures::prelude::*;\nuse kube::ResourceExt;\nuse linkerd_policy_controller_core::{Ipv4Net, Ipv6Net};\nuse linkerd_policy_controller_k8s_api::{self as k8s, gateway};\nuse linkerd_policy_test::{\n    assert_default_all_unauthenticated_labels, assert_is_default_all_unauthenticated,\n    assert_protocol_detect, await_condition, create, create_ready_pod, grpc, with_temp_ns,\n};\nuse maplit::{btreemap, convert_args, hashmap};\nuse tokio::time;\n\n/// Creates a pod, watches its policy, and updates policy resources that impact\n/// the watch.\n#[tokio::test(flavor = \"current_thread\")]\nasync fn server_with_server_authorization() {\n    with_temp_ns(|client, ns| async move {\n        // Create a pod that does nothing. It's injected with a proxy, so we can\n        // attach policies to its admin server.\n        let pod = create_ready_pod(&client, mk_pause(&ns, \"pause\")).await;\n\n        let mut rx = retry_watch_server(&client, &ns, &pod.name_unchecked()).await;\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        tracing::trace!(?config);\n        assert_is_default_all_unauthenticated!(config);\n        assert_protocol_detect!(config);\n\n        // Create a server that selects the pod's proxy admin server and ensure\n        // that the update now uses this server, which has no authorizations\n        let server = create(&client, mk_admin_server(&ns, \"linkerd-admin\", None)).await;\n        let config = next_config(&mut rx).await;\n        assert_eq!(\n            config.protocol,\n            Some(grpc::defaults::proxy_protocol_no_ratelimit())\n        );\n        assert_eq!(config.authorizations, vec![]);\n        assert_eq!(\n            config.labels,\n            convert_args!(hashmap!(\n                \"group\" => \"policy.linkerd.io\",\n                \"kind\" => \"server\",\n                \"name\" => \"linkerd-admin\"\n            )),\n        );\n\n        // Create a server authorization that refers to the `linkerd-admin`\n        // server (by name) and ensure that the update now reflects this\n        // authorization.\n        create(\n            &client,\n            k8s::policy::ServerAuthorization {\n                metadata: kube::api::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"all-admin\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::ServerAuthorizationSpec {\n                    server: k8s::policy::server_authorization::Server {\n                        name: Some(\"linkerd-admin\".to_string()),\n                        selector: None,\n                    },\n                    client: k8s::policy::server_authorization::Client {\n                        unauthenticated: true,\n                        ..k8s::policy::server_authorization::Client::default()\n                    },\n                },\n            },\n        )\n        .await;\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an updated config\");\n        tracing::trace!(?config);\n        assert_eq!(\n            config.protocol,\n            Some(grpc::defaults::proxy_protocol_no_ratelimit())\n        );\n        assert_eq!(\n            config.authorizations.first().unwrap().labels,\n            convert_args!(hashmap!(\n                \"group\" => \"policy.linkerd.io\",\n                \"kind\" => \"serverauthorization\",\n                \"name\" => \"all-admin\",\n            )),\n        );\n        assert_eq!(\n            *config\n                .authorizations\n                .first()\n                .unwrap()\n                .authentication\n                .as_ref()\n                .unwrap(),\n            grpc::inbound::Authn {\n                permit: Some(grpc::inbound::authn::Permit::Unauthenticated(\n                    grpc::inbound::authn::PermitUnauthenticated {}\n                )),\n            }\n        );\n        assert_eq!(\n            config.labels,\n            convert_args!(hashmap!(\n                \"group\" => \"policy.linkerd.io\",\n                \"kind\" => \"server\",\n                \"name\" => server.name_unchecked()\n            ))\n        );\n\n        // Delete the `Server` and ensure that the update reverts to the\n        // default.\n        kube::Api::<k8s::policy::Server>::namespaced(client.clone(), &ns)\n            .delete(\n                &server.name_unchecked(),\n                &kube::api::DeleteParams::default(),\n            )\n            .await\n            .expect(\"Server must be deleted\");\n        let config = next_config(&mut rx).await;\n        assert_is_default_all_unauthenticated!(config);\n        assert_protocol_detect!(config);\n    })\n    .await;\n}\n\n/// Creates a pod, watches its policy, and updates policy resources that impact\n/// the watch.\n#[tokio::test(flavor = \"current_thread\")]\nasync fn server_with_authorization_policy() {\n    with_temp_ns(|client, ns| async move {\n        // Create a pod that does nothing. It's injected with a proxy, so we can\n        // attach policies to its admin server.\n        let pod = create_ready_pod(&client, mk_pause(&ns, \"pause\")).await;\n\n        let mut rx = retry_watch_server(&client, &ns, &pod.name_unchecked()).await;\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        tracing::trace!(?config);\n        assert_is_default_all_unauthenticated!(config);\n        assert_protocol_detect!(config);\n\n        // Create a server that selects the pod's proxy admin server and ensure\n        // that the update now uses this server, which has no authorizations\n        let server = create(&client, mk_admin_server(&ns, \"linkerd-admin\", None)).await;\n        let config = next_config(&mut rx).await;\n        assert_eq!(\n            config.protocol,\n            Some(grpc::defaults::proxy_protocol_no_ratelimit())\n        );\n        assert_eq!(config.authorizations, vec![]);\n        assert_eq!(\n            config.labels,\n            convert_args!(hashmap!(\n                \"group\" => \"policy.linkerd.io\",\n                \"kind\" => \"server\",\n                \"name\" => server.name_unchecked()\n            ))\n        );\n\n        let all_nets = create(\n            &client,\n            k8s::policy::NetworkAuthentication {\n                metadata: kube::api::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"all-admin\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::NetworkAuthenticationSpec {\n                    networks: vec![\n                        k8s::policy::network_authentication::Network {\n                            cidr: Ipv4Net::default().into(),\n                            except: None,\n                        },\n                        k8s::policy::network_authentication::Network {\n                            cidr: Ipv6Net::default().into(),\n                            except: None,\n                        },\n                    ],\n                },\n            },\n        )\n        .await;\n\n        let authz_policy = create(\n            &client,\n            k8s::policy::AuthorizationPolicy {\n                metadata: kube::api::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"all-admin\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::AuthorizationPolicySpec {\n                    target_ref: k8s::policy::LocalTargetRef::from_resource(&server),\n                    required_authentication_refs: vec![\n                        k8s::policy::NamespacedTargetRef::from_resource(&all_nets),\n                    ],\n                },\n            },\n        )\n        .await;\n\n        let config = time::timeout(time::Duration::from_secs(10), next_config(&mut rx))\n            .await\n            .expect(\"watch must update within 10s\");\n\n        assert_eq!(\n            config.protocol,\n            Some(grpc::defaults::proxy_protocol_no_ratelimit())\n        );\n        assert_eq!(config.authorizations.len(), 1);\n        assert_eq!(\n            config.authorizations.first().unwrap().labels,\n            convert_args!(hashmap!(\n                \"group\" => \"policy.linkerd.io\",\n                \"kind\" => \"authorizationpolicy\",\n                \"name\" => authz_policy.name_unchecked(),\n            ))\n        );\n        assert_eq!(\n            *config\n                .authorizations\n                .first()\n                .unwrap()\n                .authentication\n                .as_ref()\n                .unwrap(),\n            grpc::inbound::Authn {\n                permit: Some(grpc::inbound::authn::Permit::Unauthenticated(\n                    grpc::inbound::authn::PermitUnauthenticated {}\n                )),\n            }\n        );\n        assert_eq!(\n            config.labels,\n            convert_args!(hashmap!(\n                \"group\" => \"policy.linkerd.io\",\n                \"kind\" => \"server\",\n                \"name\" => server.name_unchecked()\n            ))\n        );\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn servers_ordered_by_creation() {\n    with_temp_ns(|client, ns| async move {\n        // Create two servers in order.\n        let server0 = create(&client, mk_admin_server(&ns, \"linkerd-admin-0\", None)).await;\n        let server1 = create(&client, mk_admin_server(&ns, \"linkerd-admin-1\", None)).await;\n\n        // Then create a pod that we can discover policy for...\n        let pod = create_ready_pod(&client, mk_pause(&ns, \"pause\")).await;\n        let mut rx = retry_watch_server(&client, &ns, &pod.name_unchecked()).await;\n\n        // The first update should reflect the first server.\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        assert_eq!(\n            config.labels,\n            convert_args!(hashmap!(\n                \"group\" => \"policy.linkerd.io\",\n                \"kind\" => \"server\",\n                \"name\" => server0.metadata.name.as_deref().expect(\"server0 name\"),\n            )),\n        );\n\n        // Delete the first server and ensure that the update reverts to the\n        // second.\n        kube::Api::<k8s::policy::Server>::namespaced(client.clone(), &ns)\n            .delete(\n                server0.metadata.name.as_deref().expect(\"name\"),\n                &kube::api::DeleteParams::default(),\n            )\n            .await\n            .expect(\"Server must be deleted\");\n        let config = next_config(&mut rx).await;\n        assert_eq!(\n            config.labels,\n            convert_args!(hashmap!(\n                \"group\" => \"policy.linkerd.io\",\n                \"kind\" => \"server\",\n                \"name\" => server1.metadata.name.as_deref().expect(\"server1 name\"),\n            )),\n        );\n    })\n    .await\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn server_with_audit_policy() {\n    with_temp_ns(|client, ns| async move {\n        // Create a pod that does nothing. It's injected with a proxy, so we can\n        // attach policies to its admin server.\n        let pod = create_ready_pod(&client, mk_pause(&ns, \"pause\")).await;\n\n        let mut rx = retry_watch_server(&client, &ns, &pod.name_unchecked()).await;\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        tracing::trace!(?config);\n        assert_is_default_all_unauthenticated!(config);\n        assert_protocol_detect!(config);\n\n        // Create a server with audit access policy that selects the pod's proxy admin server and\n        // ensure that the update now uses this server, and an unauthenticated authorization is\n        // returned\n        let server = create(\n            &client,\n            mk_admin_server(&ns, \"linkerd-admin\", Some(\"audit\".to_string())),\n        )\n        .await;\n        let config = next_config(&mut rx).await;\n        assert_eq!(\n            config.protocol,\n            Some(grpc::defaults::proxy_protocol_no_ratelimit())\n        );\n        assert_eq!(config.authorizations.len(), 1);\n        assert_eq!(\n            config.authorizations.first().unwrap().labels,\n            convert_args!(hashmap!(\n                \"group\" => \"\",\n                \"kind\" => \"default\",\n                \"name\" => \"audit\",\n            ))\n        );\n        assert_eq!(\n            *config\n                .authorizations\n                .first()\n                .unwrap()\n                .authentication\n                .as_ref()\n                .unwrap(),\n            grpc::inbound::Authn {\n                permit: Some(grpc::inbound::authn::Permit::Unauthenticated(\n                    grpc::inbound::authn::PermitUnauthenticated {}\n                )),\n            }\n        );\n        assert_eq!(\n            config.labels,\n            convert_args!(hashmap!(\n                \"group\" => \"policy.linkerd.io\",\n                \"kind\" => \"server\",\n                \"name\" => server.name_unchecked()\n            ))\n        );\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn http_local_rate_limit_policy() {\n    with_temp_ns(|client, ns| async move {\n        // Create a pod that does nothing. It's injected with a proxy, so we can\n        // attach policies to its admin server.\n        let pod = create_ready_pod(&client, mk_pause(&ns, \"pause\")).await;\n\n        // Create a server with audit access policy that selects the pod's proxy admin server\n        let server = create(\n            &client,\n            mk_admin_server(&ns, \"linkerd-admin\", Some(\"audit\".to_string())),\n        )\n        .await;\n\n        // Create a rate-limit policy associated to the server\n        let rate_limit = create(\n            &client,\n            k8s::policy::ratelimit_policy::HttpLocalRateLimitPolicy {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.to_string()),\n                    name: Some(\"rl-0\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::ratelimit_policy::RateLimitPolicySpec {\n                    target_ref: k8s::policy::LocalTargetRef::from_resource(&server),\n                    total: Some(k8s::policy::ratelimit_policy::Limit {\n                        requests_per_second: 1000,\n                    }),\n                    identity: None,\n                    overrides: Some(vec![k8s::policy::ratelimit_policy::Override {\n                        requests_per_second: 200,\n                        client_refs: vec![k8s::policy::NamespacedTargetRef {\n                            group: None,\n                            kind: \"ServiceAccount\".to_string(),\n                            name: \"sa-0\".to_string(),\n                            namespace: None,\n                        }],\n                    }]),\n                },\n                status: None,\n            },\n        )\n        .await;\n\n        await_condition(\n            &client,\n            &ns,\n            &rate_limit.name_unchecked(),\n            |obj: Option<&k8s::policy::ratelimit_policy::HttpLocalRateLimitPolicy>| {\n                obj.as_ref().is_some_and(|obj| {\n                    obj.status.as_ref().is_some_and(|status| {\n                        status\n                            .conditions\n                            .iter()\n                            .any(|c| c.type_ == \"Accepted\" && c.status == \"True\")\n                    })\n                })\n            },\n        )\n        .await\n        .expect(\"rate limit must get a status\");\n\n        let client_id = format!(\"sa-0.{ns}.serviceaccount.identity.linkerd.cluster.local\");\n        let ratelimit_overrides = vec![(200, vec![client_id])];\n        let ratelimit =\n            grpc::defaults::http_local_ratelimit(\"rl-0\", Some(1000), None, ratelimit_overrides);\n        let protocol = grpc::defaults::proxy_protocol(Some(ratelimit));\n\n        let mut rx = retry_watch_server(&client, &ns, &pod.name_unchecked()).await;\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        tracing::trace!(?config);\n        assert_eq!(config.protocol, Some(protocol));\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn server_with_http_route() {\n    with_temp_ns(|client, ns| async move {\n        // Create a pod that does nothing. It's injected with a proxy, so we can\n        // attach policies to its admin server.\n        let pod = create_ready_pod(&client, mk_pause(&ns, \"pause\")).await;\n\n        let mut rx = retry_watch_server(&client, &ns, &pod.name_unchecked()).await;\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        tracing::trace!(?config);\n        assert_is_default_all_unauthenticated!(config);\n        assert_protocol_detect!(config);\n\n        // Create a server that selects the pod's proxy admin server and ensure\n        // that the update now uses this server, which has no authorizations\n        // and no routes.\n        let _server = create(&client, mk_admin_server(&ns, \"linkerd-admin\", None)).await;\n        let config = next_config(&mut rx).await;\n        assert_eq!(\n            config.protocol,\n            Some(grpc::defaults::proxy_protocol_no_ratelimit())\n        );\n        assert_eq!(config.authorizations, vec![]);\n        assert_eq!(\n            config.labels,\n            convert_args!(hashmap!(\n                \"group\" => \"policy.linkerd.io\",\n                \"kind\" => \"server\",\n                \"name\" => \"linkerd-admin\"\n            )),\n        );\n\n        // Create an http route that refers to the `linkerd-admin` server (by\n        // name) and ensure that the update now reflects this route.\n        let route = create(&client, mk_admin_route(ns.as_ref(), \"metrics-route\")).await;\n        let config = next_config(&mut rx).await;\n        let h1_route = http1_routes(&config).first().expect(\"must have route\");\n        let rule_match = h1_route\n            .rules\n            .first()\n            .expect(\"must have rule\")\n            .matches\n            .first()\n            .expect(\"must have match\");\n        // Route has no authorizations by default.\n        assert_eq!(h1_route.authorizations, Vec::default());\n        // Route has appropriate metadata.\n        assert_eq!(\n            h1_route\n                .metadata\n                .to_owned()\n                .expect(\"route must have metadata\"),\n            grpc::meta::Metadata {\n                kind: Some(grpc::meta::metadata::Kind::Resource(grpc::meta::Resource {\n                    group: \"policy.linkerd.io\".to_string(),\n                    kind: \"HTTPRoute\".to_string(),\n                    name: \"metrics-route\".to_string(),\n                    ..Default::default()\n                }))\n            }\n        );\n        // Route has path match.\n        assert_eq!(\n            rule_match\n                .path\n                .to_owned()\n                .expect(\"must have path match\")\n                .kind\n                .expect(\"must have kind\"),\n            grpc::http_route::path_match::Kind::Exact(\"/metrics\".to_string()),\n        );\n\n        // Create a network authentication and an authorization policy that\n        // refers to the `metrics-route` route (by name).\n        let all_nets = create(\n            &client,\n            k8s::policy::NetworkAuthentication {\n                metadata: kube::api::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"all-admin\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::NetworkAuthenticationSpec {\n                    networks: vec![\n                        k8s::policy::network_authentication::Network {\n                            cidr: Ipv4Net::default().into(),\n                            except: None,\n                        },\n                        k8s::policy::network_authentication::Network {\n                            cidr: Ipv6Net::default().into(),\n                            except: None,\n                        },\n                    ],\n                },\n            },\n        )\n        .await;\n        create(\n            &client,\n            k8s::policy::AuthorizationPolicy {\n                metadata: kube::api::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"all-admin\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::AuthorizationPolicySpec {\n                    target_ref: k8s::policy::LocalTargetRef::from_resource(&route),\n                    required_authentication_refs: vec![\n                        k8s::policy::NamespacedTargetRef::from_resource(&all_nets),\n                    ],\n                },\n            },\n        )\n        .await;\n\n        let config = next_config(&mut rx).await;\n        let http1 = if let grpc::inbound::proxy_protocol::Kind::Http1(http1) = config\n            .protocol\n            .expect(\"must have proxy protocol\")\n            .kind\n            .expect(\"must have kind\")\n        {\n            http1\n        } else {\n            panic!(\"proxy protocol must be HTTP1\")\n        };\n        let h1_route = http1.routes.first().expect(\"must have route\");\n        assert_eq!(h1_route.authorizations.len(), 1, \"must have authorizations\");\n\n        // Delete the `HttpRoute` and ensure that the update reverts to the\n        // default.\n        kube::Api::<k8s::policy::HttpRoute>::namespaced(client.clone(), &ns)\n            .delete(\"metrics-route\", &kube::api::DeleteParams::default())\n            .await\n            .expect(\"HttpRoute must be deleted\");\n        let config = next_config(&mut rx).await;\n        assert_eq!(\n            config.protocol,\n            Some(grpc::defaults::proxy_protocol_no_ratelimit())\n        );\n    })\n    .await\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn http_routes_ordered_by_creation() {\n    fn route_path(routes: &[grpc::inbound::HttpRoute], idx: usize) -> Option<&str> {\n        use grpc::http_route::path_match;\n        match routes\n            .get(idx)?\n            .rules\n            .first()?\n            .matches\n            .first()?\n            .path\n            .as_ref()?\n            .kind\n            .as_ref()?\n        {\n            path_match::Kind::Exact(ref path) => Some(path.as_ref()),\n            x => panic!(\"unexpected route match {x:?}\",),\n        }\n    }\n\n    with_temp_ns(|client, ns| async move {\n        // Create a pod that does nothing. It's injected with a proxy, so we can\n        // attach policies to its admin server.\n        let pod = create_ready_pod(&client, mk_pause(&ns, \"pause\")).await;\n\n        let mut rx = retry_watch_server(&client, &ns, &pod.name_unchecked()).await;\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        tracing::trace!(?config);\n        assert_is_default_all_unauthenticated!(config);\n        assert_protocol_detect!(config);\n\n        // Create a server that selects the pod's proxy admin server and ensure\n        // that the update now uses this server, which has no authorizations\n        // and no routes.\n        let _server = create(&client, mk_admin_server(&ns, \"linkerd-admin\", None)).await;\n        let config = next_config(&mut rx).await;\n        assert_eq!(\n            config.protocol,\n            Some(grpc::defaults::proxy_protocol_no_ratelimit())\n        );\n        assert_eq!(config.authorizations, vec![]);\n        assert_eq!(\n            config.labels,\n            convert_args!(hashmap!(\n                \"group\" => \"policy.linkerd.io\",\n                \"kind\" => \"server\",\n                \"name\" => \"linkerd-admin\"\n            )),\n        );\n\n        // Create several HTTPRoute resources referencing the admin server by\n        // name. These should be ordered by creation time. A different path\n        // match is used for each route so that they can be distinguished to\n        // ensure ordering.\n        create(\n            &client,\n            mk_admin_route_with_path(ns.as_ref(), \"d\", \"/metrics\"),\n        )\n        .await;\n        next_config(&mut rx).await;\n\n        // Creation timestamps in Kubernetes only have second precision, so we\n        // must wait a whole second between creating each of these routes in\n        // order for them to have different creation timestamps.\n        time::sleep(time::Duration::from_secs(1)).await;\n        create(\n            &client,\n            mk_admin_route_with_path(ns.as_ref(), \"a\", \"/ready\"),\n        )\n        .await;\n        next_config(&mut rx).await;\n\n        time::sleep(time::Duration::from_secs(1)).await;\n        create(\n            &client,\n            mk_admin_route_with_path(ns.as_ref(), \"c\", \"/shutdown\"),\n        )\n        .await;\n        next_config(&mut rx).await;\n\n        time::sleep(time::Duration::from_secs(1)).await;\n        create(\n            &client,\n            mk_admin_route_with_path(ns.as_ref(), \"b\", \"/proxy-log-level\"),\n        )\n        .await;\n        let config = next_config(&mut rx).await;\n        let routes = http1_routes(&config);\n\n        assert_eq!(routes.len(), 4);\n        assert_eq!(route_path(routes, 0), Some(\"/metrics\"));\n        assert_eq!(route_path(routes, 1), Some(\"/ready\"));\n        assert_eq!(route_path(routes, 2), Some(\"/shutdown\"));\n        assert_eq!(route_path(routes, 3), Some(\"/proxy-log-level\"));\n\n        // Delete one of the routes and ensure that the update maintains the\n        // same ordering.\n        kube::Api::<k8s::policy::HttpRoute>::namespaced(client.clone(), &ns)\n            .delete(\"c\", &kube::api::DeleteParams::default())\n            .await\n            .expect(\"HttpRoute must be deleted\");\n        let config = next_config(&mut rx).await;\n        let routes = http1_routes(&config);\n        assert_eq!(routes.len(), 3);\n        assert_eq!(route_path(routes, 0), Some(\"/metrics\"));\n        assert_eq!(route_path(routes, 1), Some(\"/ready\"));\n        assert_eq!(route_path(routes, 2), Some(\"/proxy-log-level\"));\n    })\n    .await\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn default_http_routes() {\n    with_temp_ns(|client, ns| async move {\n        // Create a pod that does nothing. It's injected with a proxy, so we can\n        // attach policies to its admin server.\n        let pod = create_ready_pod(&client, mk_pause(&ns, \"pause\")).await;\n\n        let mut rx = retry_watch_server(&client, &ns, &pod.name_unchecked()).await;\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        tracing::trace!(?config);\n        assert_is_default_all_unauthenticated!(config);\n        assert_protocol_detect!(config);\n\n        let routes = detect_routes(&config);\n        assert_eq!(routes.len(), 2);\n        let route_authzs = &routes[0].authorizations;\n        assert_eq!(route_authzs.len(), 0);\n    })\n    .await\n}\n\n/// Returns an `HttpRoute` resource in the provdied namespace and with the\n/// provided name, which attaches to the `linkerd-admin` `Server` resource and\n/// matches `GET` requests with the path `/metrics`.\nfn mk_admin_route(ns: &str, name: &str) -> k8s::policy::HttpRoute {\n    use k8s::policy::httproute as api;\n    api::HttpRoute {\n        metadata: kube::api::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: api::HttpRouteSpec {\n            parent_refs: Some(vec![gateway::HTTPRouteParentRefs {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: Some(\"Server\".to_string()),\n                namespace: None,\n                name: \"linkerd-admin\".to_string(),\n                section_name: None,\n                port: None,\n            }]),\n            hostnames: None,\n            rules: Some(vec![api::HttpRouteRule {\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/metrics\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    headers: None,\n                    query_params: None,\n                    method: Some(gateway::HTTPRouteRulesMatchesMethod::Get),\n                }]),\n                filters: None,\n                backend_refs: None,\n                timeouts: None,\n            }]),\n        },\n        status: None,\n    }\n}\n\n/// Returns an `HttpRoute` resource in the provdied namespace and with the\n/// provided name, which attaches to the `linkerd-admin` `Server` resource and\n/// matches `GET` requests with the provided path.\nfn mk_admin_route_with_path(ns: &str, name: &str, path: &str) -> k8s::policy::HttpRoute {\n    use k8s::policy::httproute as api;\n    api::HttpRoute {\n        metadata: kube::api::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: api::HttpRouteSpec {\n            parent_refs: Some(vec![gateway::HTTPRouteParentRefs {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: Some(\"Server\".to_string()),\n                namespace: None,\n                name: \"linkerd-admin\".to_string(),\n                section_name: None,\n                port: None,\n            }]),\n            hostnames: None,\n            rules: Some(vec![api::HttpRouteRule {\n                matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(path.to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                    }),\n                    headers: None,\n                    query_params: None,\n                    method: Some(gateway::HTTPRouteRulesMatchesMethod::Get),\n                }]),\n                filters: None,\n                backend_refs: None,\n                timeouts: None,\n            }]),\n        },\n        status: None,\n    }\n}\n\nfn mk_pause(ns: &str, name: &str) -> k8s::Pod {\n    k8s::Pod {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            annotations: Some(convert_args!(btreemap!(\n                \"linkerd.io/inject\" => \"enabled\",\n            ))),\n            ..Default::default()\n        },\n        spec: Some(k8s::PodSpec {\n            containers: vec![k8s::api::core::v1::Container {\n                name: \"pause\".to_string(),\n                image: Some(\"registry.k8s.io/pause:3.2\".to_string()),\n                ..Default::default()\n            }],\n            ..Default::default()\n        }),\n        ..k8s::Pod::default()\n    }\n}\n\nfn mk_admin_server(ns: &str, name: &str, access_policy: Option<String>) -> k8s::policy::Server {\n    k8s::policy::Server {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::ServerSpec {\n            selector: k8s::policy::server::Selector::Pod(k8s::labels::Selector::default()),\n            port: k8s::policy::server::Port::Number(4191.try_into().unwrap()),\n            proxy_protocol: Some(k8s::policy::server::ProxyProtocol::Http1),\n            access_policy,\n        },\n    }\n}\n\nasync fn retry_watch_server(\n    client: &kube::Client,\n    ns: &str,\n    pod_name: &str,\n) -> tonic::Streaming<grpc::inbound::Server> {\n    // Port-forward to the control plane and start watching the pod's admin\n    // server's policy and ensure that the first update uses the default\n    // policy.\n    let mut policy_api = grpc::InboundPolicyClient::port_forwarded(client).await;\n    loop {\n        match policy_api.watch_port(ns, pod_name, 4191).await {\n            Ok(rx) => return rx,\n            Err(error) => {\n                tracing::error!(?error, ns, pod_name, \"failed to watch policy for port 4191\");\n                time::sleep(time::Duration::from_secs(1)).await;\n            }\n        }\n    }\n}\n\nasync fn next_config(rx: &mut tonic::Streaming<grpc::inbound::Server>) -> grpc::inbound::Server {\n    let config = rx\n        .next()\n        .await\n        .expect(\"watch must not fail\")\n        .expect(\"watch must return an updated config\");\n    tracing::trace!(config = ?format_args!(\"{:#?}\", config));\n    config\n}\n\nfn detect_routes(config: &grpc::inbound::Server) -> &[grpc::inbound::HttpRoute] {\n    let kind = config\n        .protocol\n        .as_ref()\n        .expect(\"must have proxy protocol\")\n        .kind\n        .as_ref()\n        .expect(\"must have kind\");\n    let detect = if let grpc::inbound::proxy_protocol::Kind::Detect(ref detect) = kind {\n        detect\n    } else {\n        panic!(\"proxy protocol must be Detect; actually got:\\n{kind:#?}\")\n    };\n    &detect.http_routes[..]\n}\n\nfn http1_routes(config: &grpc::inbound::Server) -> &[grpc::inbound::HttpRoute] {\n    let kind = config\n        .protocol\n        .as_ref()\n        .expect(\"must have proxy protocol\")\n        .kind\n        .as_ref()\n        .expect(\"must have kind\");\n    let http1 = if let grpc::inbound::proxy_protocol::Kind::Http1(ref http1) = kind {\n        http1\n    } else {\n        panic!(\"proxy protocol must be HTTP1; actually got:\\n{kind:#?}\")\n    };\n    &http1.routes[..]\n}\n"
  },
  {
    "path": "policy-test/tests/inbound_api_external_workload.rs",
    "content": "use std::{num::NonZeroU16, time::Duration};\n\nuse futures::prelude::*;\nuse k8s::policy::LocalTargetRef;\nuse kube::ResourceExt;\nuse linkerd_policy_controller_core::{Ipv4Net, Ipv6Net};\nuse linkerd_policy_controller_k8s_api::{self as k8s, gateway};\nuse linkerd_policy_test::{\n    assert_default_all_unauthenticated_labels, assert_is_default_all_unauthenticated,\n    assert_protocol_detect_external, create, grpc, with_temp_ns,\n};\nuse maplit::{btreemap, convert_args, hashmap};\nuse tokio::time;\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn external_workload_srv_with_authorization_policy() {\n    with_temp_ns(|client, ns| async move {\n        // Create an external workload object.\n        let ext_workload = create(&client, mk_external_workload(&ns, \"wkld-1\")).await;\n\n        tracing::trace!(\n            external_workload = %ext_workload.name_any(),\n            ip = ?ext_workload.spec.workload_ips.as_ref().unwrap()[0]\n        );\n\n        let mut rx = retry_watch_server(&client, &ns, &ext_workload.name_any()).await;\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        tracing::trace!(?config);\n        assert_is_default_all_unauthenticated!(config);\n        assert_protocol_detect_external!(config);\n\n        // Create a server that selects the http port on the workload and ensure\n        // the update now uses this server (which has no authorizations)\n        let server = create(&client, mk_http_server(&ns, \"external-http\")).await;\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        assert_eq!(\n            config.protocol,\n            Some(grpc::defaults::proxy_protocol_external())\n        );\n        assert_eq!(config.authorizations, vec![]);\n        assert_eq!(\n            config.labels,\n            convert_args!(hashmap!(\n                \"group\" => \"policy.linkerd.io\",\n                \"kind\" => \"server\",\n                \"name\" => \"external-http\"\n            )),\n        );\n\n        // Create a server authorization that refers to the `linkerd-admin`\n        // server (by name) and ensure that the update now reflects this\n        // authorization.\n        create(\n            &client,\n            k8s::policy::AuthorizationPolicy {\n                metadata: kube::api::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"all-http\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::AuthorizationPolicySpec {\n                    target_ref: LocalTargetRef {\n                        group: Some(\"policy.linkerd.io\".to_string()),\n                        kind: \"server\".to_string(),\n                        name: server.name_any(),\n                    },\n                    required_authentication_refs: vec![],\n                },\n            },\n        )\n        .await;\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an updated config\");\n        tracing::trace!(?config);\n        assert_eq!(\n            config.protocol,\n            Some(grpc::defaults::proxy_protocol_external())\n        );\n        assert_eq!(\n            config.authorizations.first().unwrap().labels,\n            convert_args!(hashmap!(\n                \"group\" => \"policy.linkerd.io\",\n                \"kind\" => \"authorizationpolicy\",\n                \"name\" => \"all-http\",\n            )),\n        );\n        assert_eq!(\n            *config\n                .authorizations\n                .first()\n                .unwrap()\n                .authentication\n                .as_ref()\n                .unwrap(),\n            grpc::inbound::Authn {\n                permit: Some(grpc::inbound::authn::Permit::Unauthenticated(\n                    grpc::inbound::authn::PermitUnauthenticated {}\n                )),\n            }\n        );\n        assert_eq!(\n            config.labels,\n            convert_args!(hashmap!(\n                \"group\" => \"policy.linkerd.io\",\n                \"kind\" => \"server\",\n                \"name\" => server.name_unchecked()\n            ))\n        );\n\n        // Delete the `Server` and ensure that the update reverts to the\n        // default.\n        kube::Api::<k8s::policy::Server>::namespaced(client.clone(), &ns)\n            .delete(\n                &server.name_unchecked(),\n                &kube::api::DeleteParams::default(),\n            )\n            .await\n            .expect(\"Server must be deleted\");\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an updated config\");\n\n        assert_is_default_all_unauthenticated!(config);\n        assert_protocol_detect_external!(config);\n    })\n    .await\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn external_workload_srv_with_http_route() {\n    with_temp_ns(|client, ns| async move {\n        // Create an external workload object.\n        let ext_workload = create(&client, mk_external_workload(&ns, \"wkld-1\")).await;\n\n        tracing::trace!(\n            external_workload = %ext_workload.name_any(),\n            ip = ?ext_workload.spec.workload_ips.as_ref().unwrap()[0]\n        );\n\n        let mut rx = retry_watch_server(&client, &ns, &ext_workload.name_any()).await;\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        tracing::trace!(?config);\n        assert_is_default_all_unauthenticated!(config);\n        assert_protocol_detect_external!(config);\n\n        // Create a server that selects the http port on the workload and ensure\n        // the update now uses this server (which has no authorizations)\n        let server = create(&client, mk_http_server(&ns, \"external-http\")).await;\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        assert_eq!(\n            config.protocol,\n            Some(grpc::defaults::proxy_protocol_external())\n        );\n        assert_eq!(config.authorizations, vec![]);\n        assert_eq!(\n            config.labels,\n            convert_args!(hashmap!(\n                \"group\" => \"policy.linkerd.io\",\n                \"kind\" => \"server\",\n                \"name\" => \"external-http\"\n            )),\n        );\n\n        let created_route = {\n            use k8s::policy::httproute as api;\n            let http_route = api::HttpRoute {\n                metadata: kube::api::ObjectMeta {\n                    namespace: Some(ns.to_string()),\n                    name: Some(\"http-route\".to_string()),\n                    ..Default::default()\n                },\n                spec: api::HttpRouteSpec {\n                    parent_refs: Some(vec![gateway::HTTPRouteParentRefs {\n                        group: Some(\"policy.linkerd.io\".to_string()),\n                        kind: Some(\"Server\".to_string()),\n                        name: server.name_any(),\n                        namespace: None,\n                        section_name: None,\n                        port: None,\n                    }]),\n                    hostnames: None,\n                    rules: Some(vec![api::HttpRouteRule {\n                        matches: Some(vec![gateway::HTTPRouteRulesMatches {\n                            path: Some(gateway::HTTPRouteRulesMatchesPath {\n                                value: Some(\"/endpoint\".to_string()),\n                                r#type: Some(gateway::HTTPRouteRulesMatchesPathType::Exact),\n                            }),\n                            headers: None,\n                            query_params: None,\n                            method: Some(gateway::HTTPRouteRulesMatchesMethod::Get),\n                        }]),\n                        filters: None,\n                        backend_refs: None,\n                        timeouts: None,\n                    }]),\n                },\n                status: None,\n            };\n\n            create(&client, http_route).await\n        };\n\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        let kind = config\n            .protocol\n            .as_ref()\n            .expect(\"must have proxy protocol\")\n            .kind\n            .as_ref()\n            .expect(\"must have kind\");\n        let routes = if let grpc::inbound::proxy_protocol::Kind::Http1(ref http1) = kind {\n            &http1.routes[..]\n        } else {\n            panic!(\"proxy protocol must be 'Http1'; actually got:\\n{kind:#?}\");\n        };\n\n        assert_eq!(routes.len(), 1);\n        let route = routes.first().expect(\"must have route\");\n        // Route should have no authz policy by default\n        assert_eq!(route.authorizations, vec![]);\n        assert_eq!(\n            route.metadata.to_owned().expect(\"route must have metadata\"),\n            grpc::meta::Metadata {\n                kind: Some(grpc::meta::metadata::Kind::Resource(grpc::meta::Resource {\n                    group: \"policy.linkerd.io\".to_string(),\n                    kind: \"HTTPRoute\".to_string(),\n                    name: \"http-route\".to_string(),\n                    ..Default::default()\n                }))\n            }\n        );\n\n        // Route has path match\n        let rule_match = route\n            .rules\n            .first()\n            .expect(\"must have rule\")\n            .matches\n            .first()\n            .expect(\"must have match\");\n        assert_eq!(\n            rule_match\n                .path\n                .to_owned()\n                .expect(\"must have path match\")\n                .kind\n                .expect(\"must have kind\"),\n            grpc::http_route::path_match::Kind::Exact(\"/endpoint\".to_string())\n        );\n\n        // Create a network authn and a policy that refers to the route\n        let all_networks = create(\n            &client,\n            k8s::policy::NetworkAuthentication {\n                metadata: kube::api::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"all-net\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::NetworkAuthenticationSpec {\n                    networks: vec![\n                        k8s::policy::network_authentication::Network {\n                            cidr: Ipv4Net::default().into(),\n                            except: None,\n                        },\n                        k8s::policy::network_authentication::Network {\n                            cidr: Ipv6Net::default().into(),\n                            except: None,\n                        },\n                    ],\n                },\n            },\n        )\n        .await;\n        create(\n            &client,\n            k8s::policy::AuthorizationPolicy {\n                metadata: kube::api::ObjectMeta {\n                    namespace: Some(ns.clone()),\n                    name: Some(\"all-net\".to_string()),\n                    ..Default::default()\n                },\n                spec: k8s::policy::AuthorizationPolicySpec {\n                    target_ref: k8s::policy::LocalTargetRef::from_resource(&created_route),\n                    required_authentication_refs: vec![\n                        k8s::policy::NamespacedTargetRef::from_resource(&all_networks),\n                    ],\n                },\n            },\n        )\n        .await;\n\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        let http1 = if let grpc::inbound::proxy_protocol::Kind::Http1(http1) = config\n            .protocol\n            .expect(\"must have proxy protocol\")\n            .kind\n            .expect(\"must have kind\")\n        {\n            http1\n        } else {\n            panic!(\"proxy protocol must be HTTP1\");\n        };\n        let h1_route = http1.routes.first().expect(\"must have route\");\n        assert_eq!(h1_route.authorizations.len(), 1, \"must have authorizations\");\n\n        // Delete the `HttpRoute` and ensure that the update reverts to the\n        // default.\n        kube::Api::<k8s::policy::HttpRoute>::namespaced(client.clone(), &ns)\n            .delete(\"http-route\", &kube::api::DeleteParams::default())\n            .await\n            .expect(\"HttpRoute must be deleted\");\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        assert_eq!(\n            config.protocol,\n            Some(grpc::defaults::proxy_protocol_external())\n        );\n    })\n    .await;\n}\n#[tokio::test(flavor = \"current_thread\")]\nasync fn external_workload_default_http_route() {\n    with_temp_ns(|client, ns| async move {\n        // Create an external workload object.\n        let ext_workload = create(&client, mk_external_workload(&ns, \"wkld-1\")).await;\n\n        tracing::trace!(\n            external_workload = %ext_workload.name_any(),\n            ip = ?ext_workload.spec.workload_ips.as_ref().unwrap()[0]\n        );\n\n        let mut rx = retry_watch_server(&client, &ns, &ext_workload.name_any()).await;\n        let config = rx\n            .next()\n            .await\n            .expect(\"watch must not fail\")\n            .expect(\"watch must return an initial config\");\n        tracing::trace!(?config);\n        assert_is_default_all_unauthenticated!(config);\n        assert_protocol_detect_external!(config);\n\n        let kind = config\n            .protocol\n            .as_ref()\n            .expect(\"must have proxy protocol\")\n            .kind\n            .as_ref()\n            .expect(\"must have kind\");\n        let routes = if let grpc::inbound::proxy_protocol::Kind::Detect(ref detect) = kind {\n            &detect.http_routes[..]\n        } else {\n            panic!(\"proxy protocol must be 'Detect'; actually got:\\n{kind:#?}\");\n        };\n\n        assert_eq!(routes.len(), 1);\n        let route_authzs = &routes[0].authorizations;\n        assert_eq!(route_authzs.len(), 0);\n    })\n    .await\n}\n\nfn mk_external_workload(ns: &str, name: &str) -> k8s::external_workload::ExternalWorkload {\n    k8s::external_workload::ExternalWorkload {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.into()),\n            name: Some(name.into()),\n            labels: Some(convert_args!(btreemap!(\n                        \"app\" => \"ext\",\n            ))),\n            ..Default::default()\n        },\n        spec: k8s::external_workload::ExternalWorkloadSpec {\n            mesh_tls: k8s::external_workload::MeshTls {\n                identity: \"some-identity\".to_string(),\n                server_name: \"some-sni\".to_string(),\n            },\n            ports: Some(vec![k8s::external_workload::PortSpec {\n                name: Some(\"http\".into()),\n                port: NonZeroU16::new(80).unwrap(),\n                protocol: Default::default(),\n            }]),\n            workload_ips: Some(vec![k8s::external_workload::WorkloadIP {\n                ip: \"192.0.2.0\".to_string(),\n            }]),\n        },\n        status: None,\n    }\n}\n\nasync fn retry_watch_server(\n    client: &kube::Client,\n    ns: &str,\n    workload_name: &str,\n) -> tonic::Streaming<grpc::inbound::Server> {\n    let mut policy_api = grpc::InboundPolicyClient::port_forwarded(client).await;\n    loop {\n        match policy_api\n            .watch_port_for_external_workload(ns, workload_name, 80)\n            .await\n        {\n            Ok(rx) => return rx,\n            Err(error) => {\n                tracing::error!(\n                    ?error,\n                    ns,\n                    workload_name,\n                    \"failed to watch policy for port 80\"\n                );\n                time::sleep(Duration::from_secs(1)).await;\n            }\n        }\n    }\n}\n\nfn mk_http_server(ns: &str, name: &str) -> k8s::policy::Server {\n    k8s::policy::Server {\n        metadata: k8s::ObjectMeta {\n            namespace: Some(ns.to_string()),\n            name: Some(name.to_string()),\n            ..Default::default()\n        },\n        spec: k8s::policy::ServerSpec {\n            selector: k8s::policy::server::Selector::ExternalWorkload(\n                k8s::labels::Selector::default(),\n            ),\n            port: k8s::policy::server::Port::Name(\"http\".to_string()),\n            proxy_protocol: Some(k8s::policy::server::ProxyProtocol::Http1),\n            access_policy: None,\n        },\n    }\n}\n"
  },
  {
    "path": "policy-test/tests/inbound_http_route_status.rs",
    "content": "use kube::ResourceExt;\nuse linkerd_policy_controller_k8s_api::{self as k8s, gateway};\nuse linkerd_policy_test::{\n    await_condition, create, find_route_condition, mk_route, update, with_temp_ns,\n};\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn inbound_accepted_parent() {\n    with_temp_ns(|client, ns| async move {\n        // Create a test 'Server'\n        let server_name = \"test-accepted-server\";\n        let server = k8s::policy::Server {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(server_name.to_string()),\n                ..Default::default()\n            },\n            spec: k8s::policy::ServerSpec {\n                selector: k8s::policy::server::Selector::Pod(k8s::labels::Selector::from_iter(\n                    Some((\"app\", server_name)),\n                )),\n                port: k8s::policy::server::Port::Name(\"http\".to_string()),\n                proxy_protocol: Some(k8s::policy::server::ProxyProtocol::Http1),\n                access_policy: None,\n            },\n        };\n        let server = create(&client, server).await;\n        let srv_ref = vec![gateway::HTTPRouteParentRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"Server\".to_string()),\n            namespace: server.namespace(),\n            name: server.name_unchecked(),\n            section_name: None,\n            port: None,\n        }];\n\n        // Create a route that references the Server resource.\n        let _route = create(&client, mk_route(&ns, \"test-accepted-route\", Some(srv_ref))).await;\n        // Wait until route is updated with a status\n        let statuses = await_route_status(&client, &ns, \"test-accepted-route\")\n            .await\n            .parents;\n\n        let route_status = statuses\n            .clone()\n            .into_iter()\n            .find(|route_status| route_status.parent_ref.name == server_name)\n            .expect(\"must have at least one parent status\");\n\n        // Check status references to parent we have created\n        assert_eq!(\n            route_status.parent_ref.group.as_deref(),\n            Some(\"policy.linkerd.io\")\n        );\n        assert_eq!(route_status.parent_ref.kind.as_deref(), Some(\"Server\"));\n\n        // Check status is accepted with a status of 'True'\n        let cond = find_route_condition(&statuses, server_name)\n            .expect(\"must have at least one 'Accepted' condition for accepted server\");\n        assert_eq!(cond.status, \"True\");\n        assert_eq!(cond.reason, \"Accepted\")\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn inbound_multiple_parents() {\n    with_temp_ns(|client, ns| async move {\n        // Exercise accepted test with a valid, and an invalid parent reference\n        let srv_refs = vec![\n            gateway::HTTPRouteParentRefs {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: Some(\"Server\".to_string()),\n                namespace: Some(ns.clone()),\n                name: \"test-valid-server\".to_string(),\n                section_name: None,\n                port: None,\n            },\n            gateway::HTTPRouteParentRefs {\n                group: Some(\"policy.linkerd.io\".to_string()),\n                kind: Some(\"Server\".to_string()),\n                namespace: Some(ns.clone()),\n                name: \"test-invalid-server\".to_string(),\n                section_name: None,\n                port: None,\n            },\n        ];\n\n        // Create only one of the parents\n        let server = k8s::policy::Server {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(\"test-valid-server\".to_string()),\n                ..Default::default()\n            },\n            spec: k8s::policy::ServerSpec {\n                selector: k8s::policy::server::Selector::Pod(k8s::labels::Selector::from_iter(\n                    Some((\"app\", \"test-valid-server\")),\n                )),\n                port: k8s::policy::server::Port::Name(\"http\".to_string()),\n                proxy_protocol: Some(k8s::policy::server::ProxyProtocol::Http1),\n                access_policy: None,\n            },\n        };\n        let _server = create(&client, server).await;\n\n        // Create a route that references both parents.\n        let _route = create(\n            &client,\n            mk_route(&ns, \"test-multiple-parents-route\", Some(srv_refs)),\n        )\n        .await;\n        // Wait until route is updated with a status\n        let parent_status = await_route_status(&client, &ns, \"test-multiple-parents-route\")\n            .await\n            .parents;\n\n        // Find status for invalid parent and extract the condition\n        let invalid_cond = find_route_condition(&parent_status, \"test-invalid-server\")\n            .expect(\"must have at least one 'Accepted' condition set for invalid parent\");\n        // Route shouldn't be accepted\n        assert_eq!(invalid_cond.status, \"False\");\n        assert_eq!(invalid_cond.reason, \"NoMatchingParent\");\n\n        // Find status for valid parent and extract the condition\n        let valid_cond = find_route_condition(&parent_status, \"test-valid-server\")\n            .expect(\"must have at least one 'Accepted' condition set for valid parent\");\n        assert_eq!(valid_cond.status, \"True\");\n        assert_eq!(valid_cond.reason, \"Accepted\")\n    })\n    .await\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn inbound_no_parent_ref_patch() {\n    with_temp_ns(|client, ns| async move {\n        // Create a test 'Server'\n        let server_name = \"test-accepted-server\";\n        let server = k8s::policy::Server {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(server_name.to_string()),\n                ..Default::default()\n            },\n            spec: k8s::policy::ServerSpec {\n                selector: k8s::policy::server::Selector::Pod(k8s::labels::Selector::from_iter(\n                    Some((\"app\", server_name)),\n                )),\n                port: k8s::policy::server::Port::Name(\"http\".to_string()),\n                proxy_protocol: Some(k8s::policy::server::ProxyProtocol::Http1),\n                access_policy: None,\n            },\n        };\n        let server = create(&client, server).await;\n        let srv_ref = vec![gateway::HTTPRouteParentRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"Server\".to_string()),\n            namespace: server.namespace(),\n            name: server.name_unchecked(),\n            section_name: None,\n            port: None,\n        }];\n        // Create a route with a parent reference.\n        let route = create(\n            &client,\n            mk_route(&ns, \"test-no-parent-refs-route\", Some(srv_ref)),\n        )\n        .await;\n\n        // Status may not be set straight away. To account for that, wrap a\n        // status condition watcher in a timeout.\n        let status = await_route_status(&client, &ns, \"test-no-parent-refs-route\").await;\n        // If timeout has elapsed, then route did not receive a status patch\n        assert!(\n            status.parents.len() == 1,\n            \"HTTPRoute Status should have 1 parent status\"\n        );\n\n        // Update route to remove parent_refs\n        let _route = update(&client, mk_route(&ns, \"test-no-parent-refs-route\", None)).await;\n\n        // Wait for the status to be updated to contain no parent statuses.\n        await_condition::<k8s::policy::HttpRoute>(\n            &client,\n            &ns,\n            &route.name_unchecked(),\n            |obj: Option<&k8s::policy::HttpRoute>| -> bool {\n                obj.and_then(|route| route.status.as_ref())\n                    .is_some_and(|status| status.parents.is_empty())\n            },\n        )\n        .await\n        .expect(\"HTTPRoute Status should have no parent status\");\n    })\n    .await\n}\n\n#[tokio::test(flavor = \"current_thread\")]\n// Tests that inbound routes (routes attached to a `Server`) are properly\n// reconciled when the parentReference changes. Additionally, tests that routes\n// whose parentRefs do not exist are patched with an appropriate status.\nasync fn inbound_accepted_reconcile_no_parent() {\n    with_temp_ns(|client, ns| async move {\n        // Given a route with a nonexistent parentReference, we expect to have an\n        // 'Accepted' condition with 'False' as a status.\n        let server_name = \"test-reconcile-inbound-server\";\n        let srv_ref = vec![gateway::HTTPRouteParentRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"Server\".to_string()),\n            namespace: Some(ns.clone()),\n            name: server_name.to_string(),\n            section_name: None,\n            port: None,\n        }];\n        let _route = create(\n            &client,\n            mk_route(&ns, \"test-reconcile-inbound-route\", Some(srv_ref)),\n        )\n        .await;\n        let route_status = await_route_status(&client, &ns, \"test-reconcile-inbound-route\").await;\n        let cond = find_route_condition(&route_status.parents, server_name)\n            .expect(\"must have at least one 'Accepted' condition set for parent\");\n        // Test when parent ref does not exist we get Accepted { False }.\n        assert_eq!(cond.status, \"False\");\n        assert_eq!(cond.reason, \"NoMatchingParent\");\n\n        // Create the 'Server' that route references and expect it to be picked up\n        // by the index. Consequently, route will have its status reconciled.\n        let server = k8s::policy::Server {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(server_name.to_string()),\n                ..Default::default()\n            },\n            spec: k8s::policy::ServerSpec {\n                selector: k8s::policy::server::Selector::Pod(k8s::labels::Selector::from_iter(\n                    Some((\"app\", server_name)),\n                )),\n                port: k8s::policy::server::Port::Name(\"http\".to_string()),\n                proxy_protocol: Some(k8s::policy::server::ProxyProtocol::Http1),\n                access_policy: None,\n            },\n        };\n        create(&client, server).await;\n\n        // HTTPRoute may not be patched instantly, await the route condition\n        // status becoming accepted.\n        let _route_status = await_condition(\n            &client,\n            &ns,\n            \"test-reconcile-inbound-route\",\n            |obj: Option<&k8s::policy::httproute::HttpRoute>| -> bool {\n                tracing::trace!(?obj, \"got route status\");\n                let status = match obj.and_then(|route| route.status.as_ref()) {\n                    Some(status) => status,\n                    None => return false,\n                };\n                let cond = match find_route_condition(&status.parents, server_name) {\n                    Some(cond) => cond,\n                    None => return false,\n                };\n                cond.status == \"True\" && cond.reason == \"Accepted\"\n            },\n        )\n        .await\n        .expect(\"must fetch route\")\n        .status\n        .expect(\"route must contain a status representation\");\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn inbound_accepted_reconcile_parent_delete() {\n    with_temp_ns(|client, ns| async move {\n        // Attach a route to a Server and expect the route to be patched with an\n        // Accepted status.\n        let server_name = \"test-reconcile-delete-server\";\n        let server = k8s::policy::Server {\n            metadata: k8s::ObjectMeta {\n                namespace: Some(ns.to_string()),\n                name: Some(server_name.to_string()),\n                ..Default::default()\n            },\n            spec: k8s::policy::ServerSpec {\n                selector: k8s::policy::server::Selector::Pod(k8s::labels::Selector::from_iter(\n                    Some((\"app\", server_name)),\n                )),\n                port: k8s::policy::server::Port::Name(\"http\".to_string()),\n                proxy_protocol: Some(k8s::policy::server::ProxyProtocol::Http1),\n                access_policy: None,\n            },\n        };\n        create(&client, server).await;\n\n        // Create parentReference and route\n        let srv_ref = vec![gateway::HTTPRouteParentRefs {\n            group: Some(\"policy.linkerd.io\".to_string()),\n            kind: Some(\"Server\".to_string()),\n            namespace: Some(ns.clone()),\n            name: server_name.to_string(),\n            section_name: None,\n            port: None,\n        }];\n        let _route = create(\n            &client,\n            mk_route(&ns, \"test-reconcile-delete-route\", Some(srv_ref)),\n        )\n        .await;\n        let route_status = await_route_status(&client, &ns, \"test-reconcile-delete-route\").await;\n        let cond = find_route_condition(&route_status.parents, server_name)\n            .expect(\"must have at least one 'Accepted' condition\");\n        assert_eq!(cond.status, \"True\");\n        assert_eq!(cond.reason, \"Accepted\");\n\n        // Delete Server\n        let api: kube::Api<k8s::policy::Server> = kube::Api::namespaced(client.clone(), &ns);\n        api.delete(\n            \"test-reconcile-delete-server\",\n            &kube::api::DeleteParams::default(),\n        )\n        .await\n        .expect(\"API delete request failed\");\n\n        // HTTPRoute may not be patched instantly, await the route condition\n        // becoming NoMatchingParent.\n        let _route_status = await_condition(\n            &client,\n            &ns,\n            \"test-reconcile-delete-route\",\n            |obj: Option<&k8s::policy::httproute::HttpRoute>| -> bool {\n                tracing::trace!(?obj, \"got route status\");\n                let status = match obj.and_then(|route| route.status.as_ref()) {\n                    Some(status) => status,\n                    None => return false,\n                };\n                let cond = match find_route_condition(&status.parents, server_name) {\n                    Some(cond) => cond,\n                    None => return false,\n                };\n                cond.status == \"False\" && cond.reason == \"NoMatchingParent\"\n            },\n        )\n        .await\n        .expect(\"must fetch route\")\n        .status\n        .expect(\"route must contain a status representation\");\n    })\n    .await;\n}\n\n// Waits until an HttpRoute with the given namespace and name has a status set\n// on it, then returns the generic route status representation.\nasync fn await_route_status(\n    client: &kube::Client,\n    ns: &str,\n    name: &str,\n) -> gateway::HTTPRouteStatus {\n    use k8s::policy::httproute as api;\n    let route_status = await_condition(client, ns, name, |obj: Option<&api::HttpRoute>| -> bool {\n        obj.and_then(|route| route.status.as_ref()).is_some()\n    })\n    .await\n    .expect(\"must fetch route\")\n    .status\n    .expect(\"route must contain a status representation\");\n    tracing::trace!(?route_status, name, ns, \"got route status\");\n    route_status\n}\n"
  },
  {
    "path": "policy-test/tests/outbound_api.rs",
    "content": "use futures::{FutureExt, StreamExt};\nuse linkerd_policy_controller_k8s_api::{self as k8s, gateway, policy};\nuse linkerd_policy_test::{\n    assert_resource_meta, await_route_accepted, create, create_cluster_scoped,\n    delete_cluster_scoped, grpc,\n    outbound_api::{\n        assert_backend_matches_reference, assert_route_is_default, assert_singleton,\n        retry_watch_outbound_policy,\n    },\n    test_route::{TestParent, TestRoute},\n    update, with_temp_ns,\n};\nuse maplit::{btreemap, convert_args};\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn parent_does_not_exist() {\n    async fn test<P: TestParent + Send>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            let port = 4191;\n            // Some IP address in the cluster networks which we assume is not\n            // used.\n            let ip = \"10.8.255.255\";\n\n            let mut policy_api = grpc::OutboundPolicyClient::port_forwarded(&client).await;\n            let rsp: Result<tonic::Streaming<grpc::outbound::OutboundPolicy>, tonic::Status> =\n                policy_api.watch_ip(&ns, ip, port).await;\n\n            assert!(rsp.is_err());\n            assert_eq!(rsp.err().unwrap().code(), tonic::Code::NotFound);\n        })\n        .await\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn parent_with_no_routes() {\n    async fn test<P: TestParent, R: TestRoute>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n            route = %R::kind(&R::DynamicType::default())\n        );\n        with_temp_ns(|client, ns| async move {\n            let port = 4191;\n            // Create a parent with no routes.\n            // let parent = P::create_parent(&client.clone(), &ns).await;\n            let parent = create(&client, P::make_parent(&ns)).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service, gateway::HTTPRoute>().await;\n    test::<policy::EgressNetwork, gateway::HTTPRoute>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn route_with_no_rules() {\n    async fn test<P: TestParent, R: TestRoute>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n            route = %R::kind(&R::DynamicType::default())\n        );\n        with_temp_ns(|client, ns| async move {\n            tracing::debug!(\n                parent = %P::kind(&P::DynamicType::default()),\n                route = %R::kind(&R::DynamicType::default()),\n            );\n            let port = 4191;\n            let parent = create(&client, P::make_parent(&ns)).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            let route = create(\n                &client,\n                R::make_route(ns.clone(), vec![parent.obj_ref()], vec![]),\n            )\n            .await;\n            await_route_accepted(&client, &route).await;\n\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a route with no rules.\n            R::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                let rules = &R::rules_first_available(outbound_route);\n                assert!(route.meta_eq(R::extract_meta(outbound_route)));\n                assert!(rules.is_empty());\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service, gateway::HTTPRoute>().await;\n    test::<k8s::Service, policy::HttpRoute>().await;\n    test::<k8s::Service, gateway::GRPCRoute>().await;\n    test::<policy::EgressNetwork, policy::HttpRoute>().await;\n    test::<policy::EgressNetwork, gateway::HTTPRoute>().await;\n    test::<policy::EgressNetwork, gateway::GRPCRoute>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn routes_without_backends() {\n    async fn test<P: TestParent, R: TestRoute>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n            route = %R::kind(&R::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let port = 4191;\n            let parent = create(&client, P::make_parent(&ns)).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            // Create a route with one rule with no backends.\n            let route = create(\n                &client,\n                R::make_route(ns.clone(), vec![parent.obj_ref()], vec![vec![]]),\n            )\n            .await;\n            await_route_accepted(&client, &route).await;\n\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a route with the logical backend.\n            R::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                let rules = &R::rules_first_available(outbound_route);\n                assert!(route.meta_eq(R::extract_meta(outbound_route)));\n                let backends = assert_singleton(rules);\n                let backend = R::backend(*assert_singleton(backends));\n                assert_backend_matches_reference(backend, &parent.obj_ref(), port);\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service, gateway::HTTPRoute>().await;\n    test::<k8s::Service, policy::HttpRoute>().await;\n    test::<k8s::Service, gateway::GRPCRoute>().await;\n    test::<policy::EgressNetwork, policy::HttpRoute>().await;\n    test::<policy::EgressNetwork, gateway::HTTPRoute>().await;\n    test::<policy::EgressNetwork, gateway::GRPCRoute>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn routes_with_backend() {\n    async fn test<P: TestParent, R: TestRoute>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n            route = %R::kind(&R::DynamicType::default())\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let port = 4191;\n            let parent = create(&client, P::make_parent(&ns)).await;\n\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            let route = create(\n                &client,\n                R::make_route(\n                    ns,\n                    vec![parent.obj_ref()],\n                    vec![vec![backend.backend_ref(backend_port)]],\n                ),\n            )\n            .await;\n            await_route_accepted(&client, &route).await;\n\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a route with a backend with no filters.\n            R::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                let rules = &R::rules_random_available(outbound_route);\n                assert!(route.meta_eq(R::extract_meta(outbound_route)));\n                let backends = assert_singleton(rules);\n\n                let filters = R::backend_filters(*assert_singleton(backends));\n                assert!(filters.is_empty());\n\n                let outbound_backend = R::backend(*assert_singleton(backends));\n                assert_backend_matches_reference(\n                    outbound_backend,\n                    &backend.obj_ref(),\n                    backend_port,\n                );\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service, gateway::HTTPRoute>().await;\n    test::<k8s::Service, policy::HttpRoute>().await;\n    test::<k8s::Service, gateway::GRPCRoute>().await;\n    test::<policy::EgressNetwork, gateway::HTTPRoute>().await;\n    test::<policy::EgressNetwork, policy::HttpRoute>().await;\n    test::<policy::EgressNetwork, gateway::GRPCRoute>().await;\n    #[cfg(feature = \"gateway-api-experimental\")]\n    {\n        test::<k8s::Service, gateway::TLSRoute>().await;\n        test::<k8s::Service, gateway::TCPRoute>().await;\n        test::<policy::EgressNetwork, gateway::TLSRoute>().await;\n        test::<policy::EgressNetwork, gateway::TCPRoute>().await;\n    }\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn service_with_routes_with_cross_namespace_backend() {\n    async fn test<P: TestParent, R: TestRoute>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n            route = %R::kind(&R::DynamicType::default())\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let port = 4191;\n            let parent = create(&client, P::make_parent(&ns)).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            let backend_ns_name = format!(\"{ns}-backend\");\n            let backend_ns = create_cluster_scoped(\n                &client,\n                k8s::Namespace {\n                    metadata: k8s::ObjectMeta {\n                        name: Some(backend_ns_name.clone()),\n                        labels: Some(convert_args!(btreemap!(\n                            \"linkerd-policy-test\" => std::thread::current().name().unwrap_or(\"\"),\n                        ))),\n                        ..Default::default()\n                    },\n                    ..Default::default()\n                },\n            )\n            .await;\n\n            // Create a cross namespace backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&backend_ns_name) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n            let route = create(\n                &client,\n                R::make_route(\n                    ns,\n                    vec![parent.obj_ref()],\n                    vec![vec![backend.backend_ref(backend_port)]],\n                ),\n            )\n            .await;\n            await_route_accepted(&client, &route).await;\n\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a route with a backend with no filters.\n            R::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                let rules = &R::rules_random_available(outbound_route);\n                assert!(route.meta_eq(R::extract_meta(outbound_route)));\n                let backends = assert_singleton(rules);\n\n                let filters = R::backend_filters(*assert_singleton(backends));\n                assert!(filters.is_empty());\n\n                let outbound_backend = R::backend(*assert_singleton(backends));\n                assert_backend_matches_reference(\n                    outbound_backend,\n                    &backend.obj_ref(),\n                    backend_port,\n                );\n            });\n\n            delete_cluster_scoped(&client, backend_ns).await\n        })\n        .await\n    }\n\n    test::<k8s::Service, gateway::HTTPRoute>().await;\n    test::<k8s::Service, policy::HttpRoute>().await;\n    test::<k8s::Service, gateway::GRPCRoute>().await;\n    #[cfg(feature = \"gateway-api-experimental\")]\n    {\n        test::<k8s::Service, gateway::TLSRoute>().await;\n        test::<k8s::Service, gateway::TCPRoute>().await;\n    }\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn routes_with_invalid_backend() {\n    async fn test<P: TestParent, R: TestRoute>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n            route = %R::kind(&R::DynamicType::default())\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let port = 4191;\n            let parent = create(&client, P::make_parent(&ns)).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            let backend_port = 8888;\n            let mut backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n            backend.meta_mut().name = Some(\"invalid\".to_string());\n            let route = create(\n                &client,\n                R::make_route(\n                    ns,\n                    vec![parent.obj_ref()],\n                    vec![vec![backend.backend_ref(backend_port)]],\n                ),\n            )\n            .await;\n            await_route_accepted(&client, &route).await;\n\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a route with a backend with a failure filter.\n            R::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                let rules = &R::rules_random_available(outbound_route);\n                assert!(route.meta_eq(R::extract_meta(outbound_route)));\n                let backends = assert_singleton(rules);\n\n                let filters = R::backend_filters(*assert_singleton(backends));\n                let filter = assert_singleton(&filters);\n                assert!(R::is_failure_filter(filter));\n\n                let outbound_backend = R::backend(*assert_singleton(backends));\n                assert_backend_matches_reference(\n                    outbound_backend,\n                    &backend.obj_ref(),\n                    backend_port,\n                );\n            });\n        })\n        .await\n    }\n\n    test::<k8s::Service, gateway::HTTPRoute>().await;\n    test::<k8s::Service, policy::HttpRoute>().await;\n    test::<k8s::Service, gateway::GRPCRoute>().await;\n    test::<policy::EgressNetwork, gateway::HTTPRoute>().await;\n    test::<policy::EgressNetwork, policy::HttpRoute>().await;\n    test::<policy::EgressNetwork, gateway::GRPCRoute>().await;\n    #[cfg(feature = \"gateway-api-experimental\")]\n    {\n        test::<k8s::Service, gateway::TLSRoute>().await;\n        test::<k8s::Service, gateway::TCPRoute>().await;\n        test::<policy::EgressNetwork, gateway::TLSRoute>().await;\n        test::<policy::EgressNetwork, gateway::TCPRoute>().await;\n    }\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn multiple_routes() {\n    async fn test<P: TestParent, R: TestRoute>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n            route = %R::kind(&R::DynamicType::default())\n        );\n        with_temp_ns(|client, ns| async move {\n            tracing::debug!(\n                parent = %P::kind(&P::DynamicType::default()),\n                route = %R::kind(&R::DynamicType::default()),\n            );\n            // Create a parent\n            let port = 4191;\n            let parent = create(&client, P::make_parent(&ns)).await;\n\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            // Routes should be returned in sorted order by creation timestamp then\n            // name. To ensure that this test isn't timing dependant, routes should\n            // be created in alphabetical order.\n            let mut route_a = R::make_route(\n                ns.clone(),\n                vec![parent.obj_ref()],\n                vec![vec![backend.backend_ref(backend_port)]],\n            );\n            route_a.meta_mut().name = Some(\"a-route\".to_string());\n            let route_a = create(&client, route_a).await;\n            await_route_accepted(&client, &route_a).await;\n\n            // First route update.\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            let mut route_b = R::make_route(\n                ns.clone(),\n                vec![parent.obj_ref()],\n                vec![vec![backend.backend_ref(backend_port)]],\n            );\n            route_b.meta_mut().name = Some(\"b-route\".to_string());\n            let route_b = create(&client, route_b).await;\n            await_route_accepted(&client, &route_b).await;\n\n            // Second route update.\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            R::routes(&config, |routes| {\n                assert!(route_a.meta_eq(R::extract_meta(&routes[0])));\n                assert!(route_b.meta_eq(R::extract_meta(&routes[1])));\n            });\n        })\n        .await\n    }\n\n    test::<k8s::Service, gateway::HTTPRoute>().await;\n    test::<k8s::Service, policy::HttpRoute>().await;\n    test::<k8s::Service, gateway::GRPCRoute>().await;\n    test::<policy::EgressNetwork, gateway::HTTPRoute>().await;\n    test::<policy::EgressNetwork, policy::HttpRoute>().await;\n    test::<policy::EgressNetwork, gateway::GRPCRoute>().await;\n    #[cfg(feature = \"gateway-api-experimental\")]\n    {\n        test::<k8s::Service, gateway::TLSRoute>().await;\n        test::<policy::EgressNetwork, gateway::TLSRoute>().await;\n    }\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn opaque_service() {\n    async fn test<P: TestParent + Send>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            tracing::debug!(\n                parent = %P::kind(&P::DynamicType::default()),\n            );\n            // Create a parent\n            let port = 4191;\n            let mut parent = P::make_parent(&ns);\n            parent.meta_mut().annotations = Some(btreemap! {\n                \"config.linkerd.io/opaque-ports\".to_string() => port.to_string(),\n            });\n            let parent = create(&client, parent).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // Proxy protocol should be opaque.\n            match config.protocol.unwrap().kind.unwrap() {\n                grpc::outbound::proxy_protocol::Kind::Opaque(_) => {}\n                _ => panic!(\"proxy protocol must be Opaque\"),\n            };\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn route_with_no_port() {\n    async fn test<P: TestParent, R: TestRoute>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n            route = %R::kind(&R::DynamicType::default())\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let parent = create(&client, P::make_parent(&ns)).await;\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let port_a = 4191;\n            let port_b = 9999;\n\n            let mut rx_a = retry_watch_outbound_policy(&client, &ns, parent.ip(), port_a).await;\n            let config_a = rx_a\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config_a);\n\n            let mut rx_b = retry_watch_outbound_policy(&client, &ns, parent.ip(), port_b).await;\n            let config_b = rx_b\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config_b);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&config_a, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port_a);\n            });\n            gateway::HTTPRoute::routes(&config_b, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port_b);\n            });\n\n            // Create a route with no port in the parent_ref.\n            let mut parent_ref = parent.obj_ref();\n            parent_ref.port = None;\n            let route = create(\n                &client,\n                R::make_route(\n                    ns.clone(),\n                    vec![parent_ref],\n                    vec![vec![backend.backend_ref(backend_port)]],\n                ),\n            )\n            .await;\n            await_route_accepted(&client, &route).await;\n\n            let config_a = rx_a\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config_a);\n            assert_resource_meta(&config_a.metadata, parent.obj_ref(), port_a);\n\n            let config_b = rx_b\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config_b);\n            assert_resource_meta(&config_b.metadata, parent.obj_ref(), port_b);\n\n            // The route should apply to both ports.\n            R::routes(&config_a, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(R::extract_meta(outbound_route)));\n            });\n            R::routes(&config_b, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(R::extract_meta(outbound_route)));\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service, gateway::HTTPRoute>().await;\n    test::<k8s::Service, policy::HttpRoute>().await;\n    test::<k8s::Service, gateway::GRPCRoute>().await;\n    #[cfg(feature = \"gateway-api-experimental\")]\n    {\n        test::<k8s::Service, gateway::TLSRoute>().await;\n        test::<k8s::Service, gateway::TCPRoute>().await;\n    }\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn producer_route() {\n    async fn test<P: TestParent, R: TestRoute>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n            route = %R::kind(&R::DynamicType::default())\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let parent = create(&client, P::make_parent(&ns)).await;\n            let port = 4191;\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let mut producer_rx =\n                retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let producer_config = producer_rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?producer_config);\n            assert_resource_meta(&producer_config.metadata, parent.obj_ref(), port);\n\n            let mut consumer_rx =\n                retry_watch_outbound_policy(&client, \"consumer_ns\", parent.ip(), port).await;\n            let consumer_config = consumer_rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?consumer_config);\n            assert_resource_meta(&consumer_config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&producer_config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n            gateway::HTTPRoute::routes(&consumer_config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            // A route created in the same namespace as its parent service is called\n            // a producer route. It should be returned in outbound policy requests\n            // for that service from ALL namespaces.\n            let route = create(\n                &client,\n                R::make_route(\n                    ns.clone(),\n                    vec![parent.obj_ref()],\n                    vec![vec![backend.backend_ref(backend_port)]],\n                ),\n            )\n            .await;\n            await_route_accepted(&client, &route).await;\n\n            let producer_config = producer_rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?producer_config);\n            assert_resource_meta(&producer_config.metadata, parent.obj_ref(), port);\n\n            let consumer_config = consumer_rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?consumer_config);\n            assert_resource_meta(&consumer_config.metadata, parent.obj_ref(), port);\n\n            // The route should be returned in queries from the producer namespace.\n            R::routes(&producer_config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(R::extract_meta(outbound_route)));\n            });\n            // The route should be returned in queries from a consumer namespace.\n            R::routes(&consumer_config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(R::extract_meta(outbound_route)));\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service, gateway::HTTPRoute>().await;\n    test::<k8s::Service, policy::HttpRoute>().await;\n    test::<k8s::Service, gateway::GRPCRoute>().await;\n    #[cfg(feature = \"gateway-api-experimental\")]\n    {\n        test::<k8s::Service, gateway::TLSRoute>().await;\n        test::<k8s::Service, gateway::TCPRoute>().await;\n    }\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn pre_existing_producer_route() {\n    async fn test<P: TestParent, R: TestRoute>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n            route = %R::kind(&R::DynamicType::default())\n        );\n        // We test the scenario where outbound policy watches are initiated after\n        // a produce route already exists.\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let parent = create(&client, P::make_parent(&ns)).await;\n            let port = 4191;\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            // A route created in the same namespace as its parent service is called\n            // a producer route. It should be returned in outbound policy requests\n            // for that service from ALL namespaces.\n            let route = create(\n                &client,\n                R::make_route(\n                    ns.clone(),\n                    vec![parent.obj_ref()],\n                    vec![vec![backend.backend_ref(backend_port)]],\n                ),\n            )\n            .await;\n            await_route_accepted(&client, &route).await;\n\n            let mut producer_rx =\n                retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let producer_config = producer_rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?producer_config);\n            assert_resource_meta(&producer_config.metadata, parent.obj_ref(), port);\n\n            let mut consumer_rx =\n                retry_watch_outbound_policy(&client, \"consumer_ns\", parent.ip(), port).await;\n            let consumer_config = consumer_rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?consumer_config);\n            assert_resource_meta(&consumer_config.metadata, parent.obj_ref(), port);\n\n            // The route should be returned in queries from the producer namespace.\n            R::routes(&producer_config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(R::extract_meta(outbound_route)));\n            });\n            // The route should be returned in queries from a consumer namespace.\n            R::routes(&consumer_config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(R::extract_meta(outbound_route)));\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service, gateway::HTTPRoute>().await;\n    test::<k8s::Service, policy::HttpRoute>().await;\n    test::<k8s::Service, gateway::GRPCRoute>().await;\n    #[cfg(feature = \"gateway-api-experimental\")]\n    {\n        test::<k8s::Service, gateway::TLSRoute>().await;\n        test::<k8s::Service, gateway::TCPRoute>().await;\n    }\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn consumer_route() {\n    async fn test<P: TestParent, R: TestRoute>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n            route = %R::kind(&R::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let parent = create(&client, P::make_parent(&ns)).await;\n            let port = 4191;\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let consumer_ns_name = format!(\"{ns}-consumer\");\n            let consumer_ns = create_cluster_scoped(\n                &client,\n                k8s::Namespace {\n                    metadata: k8s::ObjectMeta {\n                        name: Some(consumer_ns_name.clone()),\n                        labels: Some(convert_args!(btreemap!(\n                            \"linkerd-policy-test\" => std::thread::current().name().unwrap_or(\"\"),\n                        ))),\n                        ..Default::default()\n                    },\n                    ..Default::default()\n                },\n            )\n            .await;\n\n            let mut producer_rx =\n                retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let producer_config = producer_rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?producer_config);\n            assert_resource_meta(&producer_config.metadata, parent.obj_ref(), port);\n\n            let mut consumer_rx =\n                retry_watch_outbound_policy(&client, &consumer_ns_name, parent.ip(), port).await;\n            let consumer_config = consumer_rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?consumer_config);\n            assert_resource_meta(&consumer_config.metadata, parent.obj_ref(), port);\n\n            let mut other_rx =\n                retry_watch_outbound_policy(&client, \"other_ns\", parent.ip(), port).await;\n            let other_config = other_rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?other_config);\n            assert_resource_meta(&other_config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&producer_config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n            gateway::HTTPRoute::routes(&consumer_config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n            gateway::HTTPRoute::routes(&other_config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            // A route created in a different namespace as its parent service is\n            // called a consumer route. It should be returned in outbound policy\n            // requests for that service ONLY when the request comes from the\n            // consumer namespace.\n            let route = create(\n                &client,\n                R::make_route(\n                    consumer_ns_name.clone(),\n                    vec![parent.obj_ref()],\n                    vec![vec![backend.backend_ref(backend_port)]],\n                ),\n            )\n            .await;\n            await_route_accepted(&client, &route).await;\n\n            // The route should NOT be returned in queries from the producer namespace.\n            // There should be a default route.\n            assert!(producer_rx.next().now_or_never().is_none());\n\n            // The route should be returned in queries from the same consumer\n            // namespace.\n            let consumer_config = consumer_rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?consumer_config);\n            assert_resource_meta(&consumer_config.metadata, parent.obj_ref(), port);\n\n            R::routes(&consumer_config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(R::extract_meta(outbound_route)));\n            });\n\n            // The route should NOT be returned in queries from a different consumer\n            // namespace.\n            assert!(other_rx.next().now_or_never().is_none());\n\n            delete_cluster_scoped(&client, consumer_ns).await;\n        })\n        .await;\n    }\n\n    test::<k8s::Service, gateway::HTTPRoute>().await;\n    test::<k8s::Service, policy::HttpRoute>().await;\n    test::<k8s::Service, gateway::GRPCRoute>().await;\n    #[cfg(feature = \"gateway-api-experimental\")]\n    {\n        test::<k8s::Service, gateway::TLSRoute>().await;\n        test::<k8s::Service, gateway::TCPRoute>().await;\n    }\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn route_reattachment() {\n    async fn test<P: TestParent, R: TestRoute>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n            route = %R::kind(&R::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let port = 4191;\n            let parent = create(&client, P::make_parent(&ns)).await;\n\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let mut route = create(\n                &client,\n                R::make_route(\n                    ns.clone(),\n                    vec![parent.obj_ref()],\n                    vec![vec![backend.backend_ref(backend_port)]],\n                ),\n            )\n            .await;\n            await_route_accepted(&client, &route).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // The route should be attached.\n            R::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(R::extract_meta(outbound_route)));\n            });\n\n            // Detatch route.\n            route.set_parent_name(\"other\".to_string());\n            update(&client, route.clone()).await;\n\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // The route should be unattached and the default route should be present.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            // Reattach route.\n            route.set_parent_name(parent.meta().name.clone().unwrap());\n            update(&client, route.clone()).await;\n\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // The route should be attached again.\n            R::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(R::extract_meta(outbound_route)));\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service, gateway::HTTPRoute>().await;\n    test::<k8s::Service, policy::HttpRoute>().await;\n    test::<k8s::Service, gateway::GRPCRoute>().await;\n    test::<policy::EgressNetwork, gateway::HTTPRoute>().await;\n    test::<policy::EgressNetwork, policy::HttpRoute>().await;\n    test::<policy::EgressNetwork, gateway::GRPCRoute>().await;\n    #[cfg(feature = \"gateway-api-experimental\")]\n    {\n        test::<k8s::Service, gateway::TLSRoute>().await;\n        test::<k8s::Service, gateway::TCPRoute>().await;\n        test::<policy::EgressNetwork, gateway::TLSRoute>().await;\n        test::<policy::EgressNetwork, gateway::TCPRoute>().await;\n    }\n}\n"
  },
  {
    "path": "policy-test/tests/outbound_api_app_protocol.rs",
    "content": "use futures::StreamExt;\nuse linkerd_policy_controller_k8s_api as k8s;\nuse linkerd_policy_controller_k8s_api::gateway;\nuse linkerd_policy_test::{\n    assert_resource_meta, await_route_accepted, create,\n    outbound_api::{\n        assert_route_is_default, assert_singleton, grpc_routes, http1_routes, http2_routes,\n        retry_watch_outbound_policy,\n    },\n    test_route::{TestParent, TestRoute},\n    with_temp_ns,\n};\n\n#[cfg(feature = \"gateway-api-experimental\")]\n#[tokio::test(flavor = \"current_thread\")]\nasync fn opaque_parent() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            let port = 4191;\n            // Create a parent with no routes.\n            let parent = create(\n                &client,\n                P::make_parent_with_protocol(&ns, Some(\"linkerd.io/opaque\".to_string())),\n            )\n            .await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            let routes = linkerd_policy_test::outbound_api::tcp_routes(&config);\n            let route = assert_singleton(routes);\n            assert_route_is_default::<gateway::TCPRoute>(route, &parent.obj_ref(), port);\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n}\n\n#[cfg(feature = \"gateway-api-experimental\")]\n#[tokio::test(flavor = \"current_thread\")]\nasync fn unknown_app_protocol_parent() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            let port = 4191;\n            // Create a parent with no routes.\n            let parent = create(\n                &client,\n                P::make_parent_with_protocol(&ns, Some(\"XMPP\".to_string())),\n            )\n            .await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            let routes = linkerd_policy_test::outbound_api::tcp_routes(&config);\n            let route = assert_singleton(routes);\n            assert_route_is_default::<gateway::TCPRoute>(route, &parent.obj_ref(), port);\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n}\n\n#[cfg(feature = \"gateway-api-experimental\")]\n#[tokio::test(flavor = \"current_thread\")]\nasync fn opaque_parent_with_tcp_route() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            let port = 4191;\n            // Create a parent with TCPRoute.\n            let parent = create(\n                &client,\n                P::make_parent_with_protocol(&ns, Some(\"linkerd.io/opaque\".to_string())),\n            )\n            .await;\n\n            let route = create(\n                &client,\n                gateway::TCPRoute::make_route(\n                    ns.clone(),\n                    vec![parent.obj_ref()],\n                    vec![vec![parent.backend_ref(port)]],\n                ),\n            )\n            .await;\n            await_route_accepted(&client, &route).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            gateway::TCPRoute::routes(&config, |routes| {\n                // Only the first TCPRoute should be returned in the config.\n                assert!(route.meta_eq(gateway::TCPRoute::extract_meta(&routes[0])));\n                assert_eq!(routes.len(), 1);\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn http1_parent() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            let port = 4191;\n            // Create a parent with no routes.\n            let parent = create(\n                &client,\n                P::make_parent_with_protocol(&ns, Some(\"http\".to_string())),\n            )\n            .await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            let routes = http1_routes(&config);\n            let route = assert_singleton(routes);\n            assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn http1_parent_with_http_route() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            let port = 4191;\n            // Create a parent with HTTPRoute.\n            let parent = create(\n                &client,\n                P::make_parent_with_protocol(&ns, Some(\"http\".to_string())),\n            )\n            .await;\n\n            let route = create(\n                &client,\n                gateway::HTTPRoute::make_route(\n                    ns.clone(),\n                    vec![parent.obj_ref()],\n                    vec![vec![parent.backend_ref(port)]],\n                ),\n            )\n            .await;\n            await_route_accepted(&client, &route).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            let routes = http1_routes(&config);\n            let outbound_route = assert_singleton(routes);\n            assert!(route.meta_eq(gateway::HTTPRoute::extract_meta(outbound_route)));\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn http2_parent() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            let port = 4191;\n            // Create a parent with no routes.\n            let parent = create(\n                &client,\n                P::make_parent_with_protocol(&ns, Some(\"kubernetes.io/h2c\".to_string())),\n            )\n            .await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            let routes = http2_routes(&config);\n            let route = assert_singleton(routes);\n            assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn http2_parent_with_http_route() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            let port = 4191;\n            // Create a parent with HTTPRoute.\n            let parent = create(\n                &client,\n                P::make_parent_with_protocol(&ns, Some(\"kubernetes.io/h2c\".to_string())),\n            )\n            .await;\n\n            let route = create(\n                &client,\n                gateway::HTTPRoute::make_route(\n                    ns.clone(),\n                    vec![parent.obj_ref()],\n                    vec![vec![parent.backend_ref(port)]],\n                ),\n            )\n            .await;\n            await_route_accepted(&client, &route).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            let routes = http2_routes(&config);\n            let outbound_route = assert_singleton(routes);\n            assert!(route.meta_eq(gateway::HTTPRoute::extract_meta(outbound_route)));\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn http2_parent_with_grpc_route() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            let port = 4191;\n            // Create a parent with GRPCRoute.\n            let parent = create(\n                &client,\n                P::make_parent_with_protocol(&ns, Some(\"kubernetes.io/h2c\".to_string())),\n            )\n            .await;\n\n            let route = create(\n                &client,\n                gateway::GRPCRoute::make_route(\n                    ns.clone(),\n                    vec![parent.obj_ref()],\n                    vec![vec![parent.backend_ref(port)]],\n                ),\n            )\n            .await;\n            await_route_accepted(&client, &route).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            let routes = grpc_routes(&config);\n            let outbound_route = assert_singleton(routes);\n            assert!(route.meta_eq(gateway::GRPCRoute::extract_meta(outbound_route)));\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n}\n"
  },
  {
    "path": "policy-test/tests/outbound_api_egress_network.rs",
    "content": "use futures::prelude::*;\nuse linkerd2_proxy_api::meta;\nuse linkerd_policy_test::{\n    assert_status_accepted, await_egress_net_status, create_egress_network, delete, grpc,\n    with_temp_ns,\n};\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn egress_switches_to_fallback() {\n    with_temp_ns(|client, ns| async move {\n        let egress_net = create_egress_network(&client, &ns, \"egress-net\").await;\n        let status = await_egress_net_status(&client, &ns, \"egress-net\").await;\n        assert_status_accepted(status.conditions);\n\n        let mut policy_api = grpc::OutboundPolicyClient::port_forwarded(&client).await;\n        let mut rsp = policy_api.watch_ip(&ns, \"1.1.1.1\", 80).await.unwrap();\n\n        let policy = rsp.next().await.unwrap().unwrap();\n        let meta = policy.metadata.unwrap();\n\n        let expected_meta = meta::Metadata {\n            kind: Some(meta::metadata::Kind::Resource(meta::Resource {\n                group: \"policy.linkerd.io\".to_string(),\n                port: 80,\n                kind: \"EgressNetwork\".to_string(),\n                name: \"egress-net\".to_string(),\n                namespace: ns.clone(),\n                section: \"\".to_string(),\n            })),\n        };\n\n        assert_eq!(meta, expected_meta);\n\n        delete(&client, egress_net).await;\n        assert!(rsp.next().await.is_none());\n\n        let mut rsp = policy_api.watch_ip(&ns, \"1.1.1.1\", 80).await.unwrap();\n\n        let policy = rsp.next().await.unwrap().unwrap();\n        let meta = policy.metadata.unwrap();\n        let expected_meta = meta::Metadata {\n            kind: Some(meta::metadata::Kind::Default(\"egress-fallback\".to_string())),\n        };\n        assert_eq!(meta, expected_meta);\n    })\n    .await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn fallback_switches_to_egress() {\n    with_temp_ns(|client, ns| async move {\n        let mut policy_api = grpc::OutboundPolicyClient::port_forwarded(&client).await;\n        let mut rsp = policy_api.watch_ip(&ns, \"1.1.1.1\", 80).await.unwrap();\n\n        let policy = rsp.next().await.unwrap().unwrap();\n        let meta = policy.metadata.unwrap();\n        let expected_meta = meta::Metadata {\n            kind: Some(meta::metadata::Kind::Default(\"egress-fallback\".to_string())),\n        };\n        assert_eq!(meta, expected_meta);\n\n        let _egress_net = create_egress_network(&client, &ns, \"egress-net\").await;\n        let status = await_egress_net_status(&client, &ns, \"egress-net\").await;\n        assert_status_accepted(status.conditions);\n\n        // stream should fall apart now\n        assert!(rsp.next().await.is_none());\n\n        // we should switch to an egress net now\n        let mut rsp = policy_api.watch_ip(&ns, \"1.1.1.1\", 80).await.unwrap();\n        let policy = rsp.next().await.unwrap().unwrap();\n        let meta = policy.metadata.unwrap();\n\n        let expected_meta = meta::Metadata {\n            kind: Some(meta::metadata::Kind::Resource(meta::Resource {\n                group: \"policy.linkerd.io\".to_string(),\n                port: 80,\n                kind: \"EgressNetwork\".to_string(),\n                name: \"egress-net\".to_string(),\n                namespace: ns.clone(),\n                section: \"\".to_string(),\n            })),\n        };\n\n        assert_eq!(meta, expected_meta);\n    })\n    .await;\n}\n"
  },
  {
    "path": "policy-test/tests/outbound_api_failure_accrual.rs",
    "content": "use std::time::Duration;\n\nuse futures::StreamExt;\nuse linkerd_policy_controller_k8s_api::{self as k8s, policy};\nuse linkerd_policy_test::{\n    assert_default_accrual_backoff, assert_resource_meta, create, grpc,\n    outbound_api::{\n        detect_failure_accrual, failure_accrual_consecutive, retry_watch_outbound_policy,\n    },\n    test_route::TestParent,\n    with_temp_ns,\n};\nuse maplit::btreemap;\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn consecutive_failure_accrual() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let port = 4191;\n            let mut parent = P::make_parent(&ns);\n            parent.meta_mut().annotations = Some(btreemap! {\n                \"balancer.linkerd.io/failure-accrual\".to_string() => \"consecutive\".to_string(),\n                \"balancer.linkerd.io/failure-accrual-consecutive-max-failures\".to_string() => \"8\".to_string(),\n                \"balancer.linkerd.io/failure-accrual-consecutive-min-penalty\".to_string() => \"10s\".to_string(),\n                \"balancer.linkerd.io/failure-accrual-consecutive-max-penalty\".to_string() => \"10m\".to_string(),\n                \"balancer.linkerd.io/failure-accrual-consecutive-jitter-ratio\".to_string() => \"1.0\".to_string(),\n            });\n            let parent = create(&client, parent).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            detect_failure_accrual(&config, |accrual| {\n                let consecutive = failure_accrual_consecutive(accrual);\n                assert_eq!(8, consecutive.max_failures);\n                assert_eq!(\n                    &grpc::outbound::ExponentialBackoff {\n                        min_backoff: Some(Duration::from_secs(10).try_into().unwrap()),\n                        max_backoff: Some(Duration::from_secs(600).try_into().unwrap()),\n                        jitter_ratio: 1.0_f32,\n                    },\n                    consecutive\n                        .backoff\n                        .as_ref()\n                        .expect(\"backoff must be configured\")\n                );\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn consecutive_failure_accrual_defaults_no_config() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a service configured to do consecutive failure accrual, but\n            // with no additional configuration\n            let port = 4191;\n            let mut parent = P::make_parent(&ns);\n            parent.meta_mut().annotations = Some(btreemap! {\n                \"balancer.linkerd.io/failure-accrual\".to_string() => \"consecutive\".to_string(),\n            });\n            let parent = create(&client, parent).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // Expect default max_failures and default backoff\n            detect_failure_accrual(&config, |accrual| {\n                let consecutive = failure_accrual_consecutive(accrual);\n                assert_eq!(7, consecutive.max_failures);\n                assert_default_accrual_backoff!(consecutive\n                    .backoff\n                    .as_ref()\n                    .expect(\"backoff must be configured\"));\n            });\n        })\n        .await\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn consecutive_failure_accrual_defaults_max_fails() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a service configured to do consecutive failure accrual with\n            // max number of failures and with default backoff\n            let port = 4191;\n            let mut parent = P::make_parent(&ns);\n            parent.meta_mut().annotations = Some(btreemap! {\n                \"balancer.linkerd.io/failure-accrual\".to_string() => \"consecutive\".to_string(),\n                \"balancer.linkerd.io/failure-accrual-consecutive-max-failures\".to_string() => \"8\".to_string(),\n            });\n            let parent = create(&client, parent).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // Expect default backoff and overridden max_failures\n            detect_failure_accrual(&config, |accrual| {\n                let consecutive = failure_accrual_consecutive(accrual);\n                assert_eq!(8, consecutive.max_failures);\n                assert_default_accrual_backoff!(consecutive\n                    .backoff\n                    .as_ref()\n                    .expect(\"backoff must be configured\"));\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn consecutive_failure_accrual_defaults_jitter() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a service configured to do consecutive failure accrual with\n            // max number of failures and with default backoff\n            let port = 4191;\n            let mut parent = P::make_parent(&ns);\n            parent.meta_mut().annotations = Some(btreemap! {\n                    \"balancer.linkerd.io/failure-accrual\".to_string() => \"consecutive\".to_string(),\n                    \"balancer.linkerd.io/failure-accrual-consecutive-jitter-ratio\".to_string() => \"1.0\".to_string(),\n            });\n            let parent = create(&client, parent).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // Expect defaults for everything except for the jitter ratio\n            detect_failure_accrual(&config, |accrual| {\n                let consecutive = failure_accrual_consecutive(accrual);\n                assert_eq!(7, consecutive.max_failures);\n                assert_eq!(\n                    &grpc::outbound::ExponentialBackoff {\n                        min_backoff: Some(Duration::from_secs(1).try_into().unwrap()),\n                        max_backoff: Some(Duration::from_secs(60).try_into().unwrap()),\n                        jitter_ratio: 1.0_f32,\n                    },\n                    consecutive\n                        .backoff\n                        .as_ref()\n                        .expect(\"backoff must be configured\")\n                );\n            });\n        })\n    .await;\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn default_failure_accrual() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create Service with consecutive failure accrual config for\n            // max_failures but no mode\n            let port = 4191;\n            let mut parent = P::make_parent(&ns);\n            parent.meta_mut().annotations = Some(btreemap! {\n                \"balancer.linkerd.io/failure-accrual-consecutive-max-failures\".to_string() => \"8\".to_string(),\n            });\n            let parent = create(&client, parent).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // Expect failure accrual config to be default (no failure accrual)\n            detect_failure_accrual(&config, |accrual| {\n                assert!(\n                    accrual.is_none(),\n                    \"consecutive failure accrual should not be configured for service\"\n                );\n            });\n        })\n    .await;\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n"
  },
  {
    "path": "policy-test/tests/outbound_api_grpc.rs",
    "content": "use futures::StreamExt;\nuse kube::Resource;\nuse linkerd2_proxy_api::{self as api, outbound};\nuse linkerd_policy_controller_k8s_api::{self as k8s, gateway, policy};\nuse linkerd_policy_test::{\n    assert_resource_meta, await_route_accepted, create,\n    outbound_api::{assert_route_is_default, assert_singleton, retry_watch_outbound_policy},\n    test_route::{TestParent, TestRoute},\n    with_temp_ns,\n};\nuse maplit::btreemap;\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn grpc_route_with_filters_service() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let port = 4191;\n            let parent = create(&client, P::make_parent(&ns)).await;\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            let mut route = gateway::GRPCRoute::make_route(\n                ns,\n                vec![parent.obj_ref()],\n                vec![vec![backend.backend_ref(backend_port)]],\n            );\n            for rule in route.spec.rules.iter_mut().flatten() {\n                rule.filters = Some(vec![gateway::GRPCRouteRulesFilters {\n                    request_header_modifier: Some(\n                        gateway::GRPCRouteRulesFiltersRequestHeaderModifier {\n                            set: Some(vec![\n                                gateway::GRPCRouteRulesFiltersRequestHeaderModifierSet {\n                                    name: \"set\".to_string(),\n                                    value: \"set-value\".to_string(),\n                                },\n                            ]),\n                            add: Some(vec![\n                                gateway::GRPCRouteRulesFiltersRequestHeaderModifierAdd {\n                                    name: \"add\".to_string(),\n                                    value: \"add-value\".to_string(),\n                                },\n                            ]),\n                            remove: Some(vec![\"remove\".to_string()]),\n                        },\n                    ),\n                    ..Default::default()\n                }]);\n            }\n            let route = create(&client, route).await;\n            await_route_accepted(&client, &route).await;\n\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a route with filters.\n            gateway::GRPCRoute::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(gateway::GRPCRoute::extract_meta(outbound_route)));\n                let rule = assert_singleton(&outbound_route.rules);\n                let filters = &rule.filters;\n                assert_eq!(\n                    *filters,\n                    vec![outbound::grpc_route::Filter {\n                        kind: Some(outbound::grpc_route::filter::Kind::RequestHeaderModifier(\n                            api::http_route::RequestHeaderModifier {\n                                add: Some(api::http_types::Headers {\n                                    headers: vec![api::http_types::headers::Header {\n                                        name: \"add\".to_string(),\n                                        value: \"add-value\".into(),\n                                    }]\n                                }),\n                                set: Some(api::http_types::Headers {\n                                    headers: vec![api::http_types::headers::Header {\n                                        name: \"set\".to_string(),\n                                        value: \"set-value\".into(),\n                                    }]\n                                }),\n                                remove: vec![\"remove\".to_string()],\n                            }\n                        ))\n                    }]\n                );\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn policy_grpc_route_with_backend_filters() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let port = 4191;\n            let parent = create(&client, P::make_parent(&ns)).await;\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            let mut route = gateway::GRPCRoute::make_route(\n                ns,\n                vec![parent.obj_ref()],\n                vec![vec![backend.backend_ref(backend_port)]],\n            );\n            for rule in route.spec.rules.iter_mut().flatten() {\n                for backend in rule.backend_refs.iter_mut().flatten() {\n                    backend.filters = Some(vec![gateway::GRPCRouteRulesBackendRefsFilters {\n                        request_header_modifier: Some(\n                            gateway::GRPCRouteRulesBackendRefsFiltersRequestHeaderModifier {\n                                set: Some(vec![gateway::GRPCRouteRulesBackendRefsFiltersRequestHeaderModifierSet {\n                                    name: \"set\".to_string(),\n                                    value: \"set-value\".to_string(),\n                                }]),\n                                add: Some(vec![gateway::GRPCRouteRulesBackendRefsFiltersRequestHeaderModifierAdd {\n                                    name: \"add\".to_string(),\n                                    value: \"add-value\".to_string(),\n                                }]),\n                                remove: Some(vec![\"remove\".to_string()]),\n                            },\n                        ),\n                        ..Default::default()\n                    }]);\n                }\n            }\n            let route = create(&client, route).await;\n            await_route_accepted(&client, &route).await;\n\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a route with backend filters.\n            gateway::GRPCRoute::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(gateway::GRPCRoute::extract_meta(outbound_route)));\n                let rules = gateway::GRPCRoute::rules_random_available(outbound_route);\n                let rule = assert_singleton(&rules);\n                let backend = assert_singleton(rule);\n                assert_eq!(\n                    backend.filters,\n                    vec![outbound::grpc_route::Filter {\n                        kind: Some(outbound::grpc_route::filter::Kind::RequestHeaderModifier(\n                            api::http_route::RequestHeaderModifier {\n                                add: Some(api::http_types::Headers {\n                                    headers: vec![api::http_types::headers::Header {\n                                        name: \"add\".to_string(),\n                                        value: \"add-value\".into(),\n                                    }]\n                                }),\n                                set: Some(api::http_types::Headers {\n                                    headers: vec![api::http_types::headers::Header {\n                                        name: \"set\".to_string(),\n                                        value: \"set-value\".into(),\n                                    }]\n                                }),\n                                remove: vec![\"remove\".to_string()],\n                            }\n                        ))\n                    }]\n                );\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn grpc_route_retries_and_timeouts() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let port = 4191;\n            let parent = create(&client, P::make_parent(&ns)).await;\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let mut route = gateway::GRPCRoute::make_route(\n                ns.clone(),\n                vec![parent.obj_ref()],\n                vec![vec![backend.backend_ref(backend_port)]],\n            );\n            route.meta_mut().annotations = Some(btreemap! {\n                \"retry.linkerd.io/grpc\".to_string() => \"internal\".to_string(),\n                \"timeout.linkerd.io/response\".to_string() => \"10s\".to_string(),\n            });\n            let route = create(&client, route).await;\n            await_route_accepted(&client, &route).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            gateway::GRPCRoute::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(gateway::GRPCRoute::extract_meta(outbound_route)));\n                let rule = assert_singleton(&outbound_route.rules);\n                let conditions = rule\n                    .retry\n                    .as_ref()\n                    .expect(\"retry config expected\")\n                    .conditions\n                    .as_ref()\n                    .expect(\"retry conditions expected\");\n                assert!(conditions.internal);\n                let timeout = rule\n                    .timeouts\n                    .as_ref()\n                    .expect(\"timeouts expected\")\n                    .response\n                    .as_ref()\n                    .expect(\"response timeout expected\");\n                assert_eq!(timeout.seconds, 10);\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn parent_retries_and_timeouts() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let mut parent = P::make_parent(&ns);\n            parent.meta_mut().annotations = Some(btreemap! {\n                \"retry.linkerd.io/grpc\".to_string() => \"internal\".to_string(),\n                \"timeout.linkerd.io/response\".to_string() => \"10s\".to_string(),\n            });\n            let parent = create(&client, parent).await;\n            let port = 4191;\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let mut route = gateway::GRPCRoute::make_route(\n                ns.clone(),\n                vec![parent.obj_ref()],\n                vec![vec![backend.backend_ref(backend_port)]],\n            );\n            route.meta_mut().annotations = Some(btreemap! {\n                // Route annotations override the retry config specified on the parent.\n                \"timeout.linkerd.io/request\".to_string() => \"5s\".to_string(),\n            });\n            let route = create(&client, route).await;\n            await_route_accepted(&client, &route).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            gateway::GRPCRoute::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(gateway::GRPCRoute::extract_meta(outbound_route)));\n                let rule = assert_singleton(&outbound_route.rules);\n\n                // Retry config inherited from the service.\n                let conditions = rule\n                    .retry\n                    .as_ref()\n                    .expect(\"retry config expected\")\n                    .conditions\n                    .as_ref()\n                    .expect(\"retry conditions expected\");\n                assert!(conditions.internal);\n\n                // Parent timeout config overridden by route timeout config.\n                let timeouts = rule.timeouts.as_ref().expect(\"timeouts expected\");\n                assert_eq!(timeouts.response, None);\n                let request_timeout = timeouts.request.as_ref().expect(\"request timeout expected\");\n                assert_eq!(request_timeout.seconds, 5);\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n"
  },
  {
    "path": "policy-test/tests/outbound_api_http.rs",
    "content": "use std::time::Duration;\n\nuse futures::StreamExt;\nuse linkerd2_proxy_api::{self as api, outbound};\nuse linkerd_policy_controller_k8s_api::{self as k8s, gateway, policy};\nuse linkerd_policy_test::{\n    assert_resource_meta, await_route_accepted, create,\n    outbound_api::{assert_route_is_default, assert_singleton, retry_watch_outbound_policy},\n    test_route::{TestParent, TestRoute},\n    with_temp_ns,\n};\nuse maplit::btreemap;\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn gateway_http_route_with_filters_service() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let port = 4191;\n            let parent = create(&client, P::make_parent(&ns)).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            let mut route =\n                gateway::HTTPRoute::make_route(ns, vec![parent.obj_ref()], vec![vec![]]);\n            for rule in route.spec.rules.iter_mut().flatten() {\n                rule.matches = Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::PathPrefix),\n                    }),\n                    ..Default::default()\n                }]);\n                rule.filters = Some(vec![\n                    gateway::HTTPRouteRulesFilters {\n                        request_header_modifier: Some(\n                            gateway::HTTPRouteRulesFiltersRequestHeaderModifier {\n                                set: Some(vec![\n                                    gateway::HTTPRouteRulesFiltersRequestHeaderModifierSet {\n                                        name: \"set\".to_string(),\n                                        value: \"set-value\".to_string(),\n                                    },\n                                ]),\n                                add: Some(vec![\n                                    gateway::HTTPRouteRulesFiltersRequestHeaderModifierAdd {\n                                        name: \"add\".to_string(),\n                                        value: \"add-value\".to_string(),\n                                    },\n                                ]),\n                                remove: Some(vec![\"remove\".to_string()]),\n                            },\n                        ),\n                        r#type: gateway::HTTPRouteRulesFiltersType::RequestHeaderModifier,\n                        ..Default::default()\n                    },\n                    gateway::HTTPRouteRulesFilters {\n                        request_redirect: Some(gateway::HTTPRouteRulesFiltersRequestRedirect {\n                            scheme: Some(gateway::HTTPRouteRulesFiltersRequestRedirectScheme::Http),\n                            hostname: Some(\"host\".to_string()),\n                            path: Some(gateway::HTTPRouteRulesFiltersRequestRedirectPath {\n                                replace_prefix_match: Some(\"/path\".to_string()),\n                                r#type: gateway::HTTPRouteRulesFiltersRequestRedirectPathType::ReplacePrefixMatch,\n                                ..Default::default()\n                            }),\n                            port: Some(5555),\n                            status_code: Some(302),\n                        }),\n                        r#type: gateway::HTTPRouteRulesFiltersType::RequestRedirect,\n                        ..Default::default()\n                    },\n                ]);\n            }\n            let route = create(&client, route).await;\n            await_route_accepted(&client, &route).await;\n\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a route with filters.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(gateway::HTTPRoute::extract_meta(outbound_route)));\n                let rule = assert_singleton(&outbound_route.rules);\n                let filters = &rule.filters;\n                assert_eq!(\n                    *filters,\n                    vec![\n                outbound::http_route::Filter {\n                    kind: Some(\n                        outbound::http_route::filter::Kind::RequestHeaderModifier(\n                            api::http_route::RequestHeaderModifier {\n                                add: Some(api::http_types::Headers {\n                                    headers: vec![api::http_types::headers::Header {\n                                        name: \"add\".to_string(),\n                                        value: \"add-value\".into(),\n                                    }]\n                                }),\n                                set: Some(api::http_types::Headers {\n                                    headers: vec![api::http_types::headers::Header {\n                                        name: \"set\".to_string(),\n                                        value: \"set-value\".into(),\n                                    }]\n                                }),\n                                remove: vec![\"remove\".to_string()],\n                            }\n                        )\n                    )\n                },\n                outbound::http_route::Filter {\n                    kind: Some(outbound::http_route::filter::Kind::Redirect(\n                        api::http_route::RequestRedirect {\n                            scheme: Some(api::http_types::Scheme {\n                                r#type: Some(api::http_types::scheme::Type::Registered(\n                                    api::http_types::scheme::Registered::Http.into(),\n                                ))\n                            }),\n                            host: \"host\".to_string(),\n                            path: Some(linkerd2_proxy_api::http_route::PathModifier {\n                                replace: Some(\n                                    linkerd2_proxy_api::http_route::path_modifier::Replace::Prefix(\n                                        \"/path\".to_string()\n                                    )\n                                )\n                            }),\n                            port: 5555,\n                            status: 302,\n                        }\n                    ))\n                }\n            ]\n                );\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn policy_http_route_with_filters_service() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let port = 4191;\n            let parent = create(&client, P::make_parent(&ns)).await;\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            let mut route = policy::HttpRoute::make_route(\n                ns,\n                vec![parent.obj_ref()],\n                vec![vec![backend.backend_ref(backend_port)]],\n            );\n            for rule in route.spec.rules.iter_mut().flatten() {\n                rule.matches = Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::PathPrefix),\n                    }),\n                    ..Default::default()\n                }]);\n                rule.filters = Some(vec![\n                    policy::httproute::HttpRouteFilter::RequestHeaderModifier {\n                        request_header_modifier:\n                            gateway::HTTPRouteRulesFiltersRequestHeaderModifier {\n                                set: Some(vec![\n                                    gateway::HTTPRouteRulesFiltersRequestHeaderModifierSet {\n                                        name: \"set\".to_string(),\n                                        value: \"set-value\".to_string(),\n                                    },\n                                ]),\n                                add: Some(vec![\n                                    gateway::HTTPRouteRulesFiltersRequestHeaderModifierAdd {\n                                        name: \"add\".to_string(),\n                                        value: \"add-value\".to_string(),\n                                    },\n                                ]),\n                                remove: Some(vec![\"remove\".to_string()]),\n                            },\n                    },\n                    policy::httproute::HttpRouteFilter::RequestRedirect {\n                        request_redirect: gateway::HTTPRouteRulesFiltersRequestRedirect {\n                            scheme: Some(gateway::HTTPRouteRulesFiltersRequestRedirectScheme::Http),\n                            hostname: Some(\"host\".to_string()),\n                            path: Some(gateway::HTTPRouteRulesFiltersRequestRedirectPath {\n                                replace_prefix_match: Some(\"/path\".to_string()),\n                                r#type: gateway::HTTPRouteRulesFiltersRequestRedirectPathType::ReplacePrefixMatch,\n                                ..Default::default()\n                            }),\n                            port: Some(5555),\n                            status_code: Some(302),\n                        },\n                    },\n                ]);\n            }\n            let route = create(&client, route).await;\n            await_route_accepted(&client, &route).await;\n\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a route with filters.\n            policy::HttpRoute::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(policy::HttpRoute::extract_meta(outbound_route)));\n                let rule = assert_singleton(&outbound_route.rules);\n                let filters = &rule.filters;\n                assert_eq!(\n                    *filters,\n                    vec![\n                outbound::http_route::Filter {\n                    kind: Some(\n                        outbound::http_route::filter::Kind::RequestHeaderModifier(\n                            api::http_route::RequestHeaderModifier {\n                                add: Some(api::http_types::Headers {\n                                    headers: vec![api::http_types::headers::Header {\n                                        name: \"add\".to_string(),\n                                        value: \"add-value\".into(),\n                                    }]\n                                }),\n                                set: Some(api::http_types::Headers {\n                                    headers: vec![api::http_types::headers::Header {\n                                        name: \"set\".to_string(),\n                                        value: \"set-value\".into(),\n                                    }]\n                                }),\n                                remove: vec![\"remove\".to_string()],\n                            }\n                        )\n                    )\n                },\n                outbound::http_route::Filter {\n                    kind: Some(outbound::http_route::filter::Kind::Redirect(\n                        api::http_route::RequestRedirect {\n                            scheme: Some(api::http_types::Scheme {\n                                r#type: Some(api::http_types::scheme::Type::Registered(\n                                    api::http_types::scheme::Registered::Http.into(),\n                                ))\n                            }),\n                            host: \"host\".to_string(),\n                            path: Some(linkerd2_proxy_api::http_route::PathModifier {\n                                replace: Some(\n                                    linkerd2_proxy_api::http_route::path_modifier::Replace::Prefix(\n                                        \"/path\".to_string()\n                                    )\n                                )\n                            }),\n                            port: 5555,\n                            status: 302,\n                        }\n                    ))\n                }\n            ]\n                );\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn gateway_http_route_with_backend_filters() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let port = 4191;\n            let parent = create(&client, P::make_parent(&ns)).await;\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            let mut route = gateway::HTTPRoute::make_route(\n                ns,\n                vec![parent.obj_ref()],\n                vec![vec![backend.backend_ref(backend_port)]],\n            );\n            for rule in route.spec.rules.iter_mut().flatten() {\n                rule.matches = Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::PathPrefix),\n                    }),\n                    ..Default::default()\n                }]);\n                for backend in rule.backend_refs.iter_mut().flatten() {\n                    backend.filters = Some(vec![\n                        gateway::HTTPRouteRulesBackendRefsFilters {\n                            request_header_modifier: Some(\n                                gateway::HTTPRouteRulesBackendRefsFiltersRequestHeaderModifier {\n                                    set: Some(vec![\n                                        gateway::HTTPRouteRulesBackendRefsFiltersRequestHeaderModifierSet {\n                                            name: \"set\".to_string(),\n                                            value: \"set-value\".to_string(),\n                                        },\n                                    ]),\n                                    add: Some(vec![\n                                        gateway::HTTPRouteRulesBackendRefsFiltersRequestHeaderModifierAdd {\n                                            name: \"add\".to_string(),\n                                            value: \"add-value\".to_string(),\n                                        },\n                                    ]),\n                                    remove: Some(vec![\"remove\".to_string()]),\n                                },\n                            ),\n                            r#type: gateway::HTTPRouteRulesBackendRefsFiltersType::RequestHeaderModifier,\n                            ..Default::default()\n                        },\n                        gateway::HTTPRouteRulesBackendRefsFilters {\n                            request_redirect: Some(gateway::HTTPRouteRulesBackendRefsFiltersRequestRedirect {\n                                scheme: Some(gateway::HTTPRouteRulesBackendRefsFiltersRequestRedirectScheme::Http),\n                                hostname: Some(\"host\".to_string()),\n                                path: Some(gateway::HTTPRouteRulesBackendRefsFiltersRequestRedirectPath {\n                                    replace_prefix_match: Some(\"/path\".to_string()),\n                                    r#type: gateway::HTTPRouteRulesBackendRefsFiltersRequestRedirectPathType::ReplacePrefixMatch,\n                                    ..Default::default()\n                                }),\n                                port: Some(5555),\n                                status_code: Some(302),\n                            }),\n                            r#type: gateway::HTTPRouteRulesBackendRefsFiltersType::RequestRedirect,\n                            ..Default::default()\n                        },\n                    ]);\n                }\n            }\n            let route = create(&client, route).await;\n            await_route_accepted(&client, &route).await;\n\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a route with backend filters.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(gateway::HTTPRoute::extract_meta(outbound_route)));\n                let rules = gateway::HTTPRoute::rules_random_available(outbound_route);\n                let rule = assert_singleton(&rules);\n                let backend = assert_singleton(rule);\n                assert_eq!(\n                    backend.filters,\n                    vec![\n                outbound::http_route::Filter {\n                    kind: Some(\n                        outbound::http_route::filter::Kind::RequestHeaderModifier(\n                            api::http_route::RequestHeaderModifier {\n                                add: Some(api::http_types::Headers {\n                                    headers: vec![api::http_types::headers::Header {\n                                        name: \"add\".to_string(),\n                                        value: \"add-value\".into(),\n                                    }]\n                                }),\n                                set: Some(api::http_types::Headers {\n                                    headers: vec![api::http_types::headers::Header {\n                                        name: \"set\".to_string(),\n                                        value: \"set-value\".into(),\n                                    }]\n                                }),\n                                remove: vec![\"remove\".to_string()],\n                            }\n                        )\n                    )\n                },\n                outbound::http_route::Filter {\n                    kind: Some(outbound::http_route::filter::Kind::Redirect(\n                        api::http_route::RequestRedirect {\n                            scheme: Some(api::http_types::Scheme {\n                                r#type: Some(api::http_types::scheme::Type::Registered(\n                                    api::http_types::scheme::Registered::Http.into(),\n                                ))\n                            }),\n                            host: \"host\".to_string(),\n                            path: Some(linkerd2_proxy_api::http_route::PathModifier {\n                                replace: Some(\n                                    linkerd2_proxy_api::http_route::path_modifier::Replace::Prefix(\n                                        \"/path\".to_string()\n                                    )\n                                )\n                            }),\n                            port: 5555,\n                            status: 302,\n                        }\n                    ))\n                }\n            ]\n                );\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn policy_http_route_with_backend_filters() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let port = 4191;\n            let parent = create(&client, P::make_parent(&ns)).await;\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            let mut route = policy::HttpRoute::make_route(\n                ns,\n                vec![parent.obj_ref()],\n                vec![vec![backend.backend_ref(backend_port)]],\n            );\n            for rule in route.spec.rules.iter_mut().flatten() {\n                rule.matches = Some(vec![gateway::HTTPRouteRulesMatches {\n                    path: Some(gateway::HTTPRouteRulesMatchesPath {\n                        value: Some(\"/foo\".to_string()),\n                        r#type: Some(gateway::HTTPRouteRulesMatchesPathType::PathPrefix),\n                    }),\n                    ..Default::default()\n                }]);\n                for backend in rule.backend_refs.iter_mut().flatten() {\n                    backend.filters = Some(vec![\n                        gateway::HTTPRouteRulesBackendRefsFilters {\n                            request_header_modifier: Some(gateway::HTTPRouteRulesBackendRefsFiltersRequestHeaderModifier {\n                                set: Some(vec![gateway::HTTPRouteRulesBackendRefsFiltersRequestHeaderModifierSet {\n                                    name: \"set\".to_string(),\n                                    value: \"set-value\".to_string(),\n                                }]),\n                                add: Some(vec![gateway::HTTPRouteRulesBackendRefsFiltersRequestHeaderModifierAdd {\n                                    name: \"add\".to_string(),\n                                    value: \"add-value\".to_string(),\n                                }]),\n                                remove: Some(vec![\"remove\".to_string()]),\n                            }),\n                            ..Default::default()\n                        },\n                        gateway::HTTPRouteRulesBackendRefsFilters {\n                            request_redirect: Some(gateway::HTTPRouteRulesBackendRefsFiltersRequestRedirect {\n                                scheme: Some(gateway::HTTPRouteRulesBackendRefsFiltersRequestRedirectScheme::Http),\n                                hostname: Some(\"host\".to_string()),\n                                path: Some(gateway::HTTPRouteRulesBackendRefsFiltersRequestRedirectPath {\n                                    replace_prefix_match: Some(\"/path\".to_string()),\n                                    r#type: gateway::HTTPRouteRulesBackendRefsFiltersRequestRedirectPathType::ReplacePrefixMatch,\n                                    ..Default::default()\n                                }),\n                                port: Some(5555),\n                                status_code: Some(302),\n                            }),\n                            ..Default::default()\n                        },\n                    ]);\n                }\n            }\n            let route = create(&client, route).await;\n            await_route_accepted(&client, &route).await;\n\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a route with backend filters.\n            policy::HttpRoute::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(policy::HttpRoute::extract_meta(outbound_route)));\n                let rules = policy::HttpRoute::rules_random_available(outbound_route);\n                let rule = assert_singleton(&rules);\n                let backend = assert_singleton(rule);\n                assert_eq!(\n                    backend.filters,\n                    vec![\n                outbound::http_route::Filter {\n                    kind: Some(\n                        outbound::http_route::filter::Kind::RequestHeaderModifier(\n                            api::http_route::RequestHeaderModifier {\n                                add: Some(api::http_types::Headers {\n                                    headers: vec![api::http_types::headers::Header {\n                                        name: \"add\".to_string(),\n                                        value: \"add-value\".into(),\n                                    }]\n                                }),\n                                set: Some(api::http_types::Headers {\n                                    headers: vec![api::http_types::headers::Header {\n                                        name: \"set\".to_string(),\n                                        value: \"set-value\".into(),\n                                    }]\n                                }),\n                                remove: vec![\"remove\".to_string()],\n                            }\n                        )\n                    )\n                },\n                outbound::http_route::Filter {\n                    kind: Some(outbound::http_route::filter::Kind::Redirect(\n                        api::http_route::RequestRedirect {\n                            scheme: Some(api::http_types::Scheme {\n                                r#type: Some(api::http_types::scheme::Type::Registered(\n                                    api::http_types::scheme::Registered::Http.into(),\n                                ))\n                            }),\n                            host: \"host\".to_string(),\n                            path: Some(linkerd2_proxy_api::http_route::PathModifier {\n                                replace: Some(\n                                    linkerd2_proxy_api::http_route::path_modifier::Replace::Prefix(\n                                        \"/path\".to_string()\n                                    )\n                                )\n                            }),\n                            port: 5555,\n                            status: 302,\n                        }\n                    ))\n                }\n            ]\n                );\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn http_route_retries_and_timeouts() {\n    async fn test<P: TestParent, R: TestRoute<Route = outbound::HttpRoute>>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n            route = %R::kind(&R::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let parent = create(&client, P::make_parent(&ns)).await;\n            let port = 4191;\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let mut route = R::make_route(\n                ns.clone(),\n                vec![parent.obj_ref()],\n                vec![vec![backend.backend_ref(backend_port)]],\n            );\n            route.meta_mut().annotations = Some(btreemap! {\n                \"retry.linkerd.io/http\".to_string() => \"5xx\".to_string(),\n                \"timeout.linkerd.io/response\".to_string() => \"10s\".to_string(),\n            });\n            let route = create(&client, route).await;\n            await_route_accepted(&client, &route).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            R::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(policy::HttpRoute::extract_meta(outbound_route)));\n                let rule = assert_singleton(&outbound_route.rules);\n                let conditions = rule\n                    .retry\n                    .as_ref()\n                    .expect(\"retry config expected\")\n                    .conditions\n                    .as_ref()\n                    .expect(\"retry conditions expected\");\n                let status_range = assert_singleton(&conditions.status_ranges);\n                assert_eq!(status_range.start, 500);\n                assert_eq!(status_range.end, 599);\n                let timeout = rule\n                    .timeouts\n                    .as_ref()\n                    .expect(\"timeouts expected\")\n                    .response\n                    .as_ref()\n                    .expect(\"response timeout expected\");\n                assert_eq!(timeout.seconds, 10);\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service, gateway::HTTPRoute>().await;\n    test::<k8s::Service, policy::HttpRoute>().await;\n    test::<policy::EgressNetwork, gateway::HTTPRoute>().await;\n    test::<policy::EgressNetwork, policy::HttpRoute>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn http_route_linkerd_timeouts() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let parent = create(&client, P::make_parent(&ns)).await;\n            let port = 4191;\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let route = policy::HttpRoute {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.to_string()),\n                    name: Some(\"foo-route\".to_string()),\n                    ..Default::default()\n                },\n                spec: policy::HttpRouteSpec {\n                    parent_refs: Some(vec![parent.obj_ref()]),\n                    hostnames: None,\n                    rules: Some(vec![policy::httproute::HttpRouteRule {\n                        matches: Some(vec![]),\n                        filters: None,\n                        timeouts: Some(policy::httproute::HttpRouteTimeouts {\n                            request: Some(Duration::from_secs(5).into()),\n                            backend_request: None,\n                        }),\n                        backend_refs: Some(vec![backend.backend_ref(backend_port)]),\n                    }]),\n                },\n                status: None,\n            };\n\n            let route = create(&client, route).await;\n            await_route_accepted(&client, &route).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            policy::HttpRoute::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(policy::HttpRoute::extract_meta(outbound_route)));\n                let rule = assert_singleton(&outbound_route.rules);\n                let timeout = rule\n                    .timeouts\n                    .as_ref()\n                    .expect(\"timeouts expected\")\n                    .request\n                    .as_ref()\n                    .expect(\"request timeout expected\");\n                assert_eq!(timeout.seconds, 5);\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n\n// The timeout field on HTTPRoute is only available in Gateway API v1.2+.\n#[cfg(feature = \"gateway-api-experimental\")]\n#[tokio::test(flavor = \"current_thread\")]\nasync fn http_route_gateway_timeouts() {\n    async fn test<P: TestParent>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let parent = create(&client, P::make_parent(&ns)).await;\n            let port = 4191;\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let route = gateway::HTTPRoute {\n                metadata: k8s::ObjectMeta {\n                    namespace: Some(ns.to_string()),\n                    name: Some(\"foo-route\".to_string()),\n                    ..Default::default()\n                },\n                spec: gateway::HTTPRouteSpec {\n                    parent_refs: Some(vec![parent.obj_ref()]),\n                    hostnames: None,\n                    rules: Some(vec![gateway::HTTPRouteRules {\n                        name: None,\n                        matches: Some(vec![]),\n                        filters: None,\n                        timeouts: Some(gateway::HTTPRouteRulesTimeouts {\n                            request: Some(\"5s\".to_string()),\n                            backend_request: None,\n                        }),\n                        retry: None,\n                        backend_refs: Some(vec![backend.backend_ref(backend_port)]),\n                        session_persistence: None,\n                    }]),\n                },\n                status: None,\n            };\n\n            let route = create(&client, route).await;\n            await_route_accepted(&client, &route).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(gateway::HTTPRoute::extract_meta(outbound_route)));\n                let rule = assert_singleton(&outbound_route.rules);\n                let timeout = rule\n                    .timeouts\n                    .as_ref()\n                    .expect(\"timeouts expected\")\n                    .request\n                    .as_ref()\n                    .expect(\"request timeout expected\");\n                assert_eq!(timeout.seconds, 5);\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service>().await;\n    test::<policy::EgressNetwork>().await;\n}\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn parent_retries_and_timeouts() {\n    async fn test<P: TestParent, R: TestRoute<Route = outbound::HttpRoute>>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n            route = %R::kind(&R::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let mut parent = P::make_parent(&ns);\n            parent.meta_mut().annotations = Some(btreemap! {\n                \"retry.linkerd.io/http\".to_string() => \"5xx\".to_string(),\n                \"timeout.linkerd.io/response\".to_string() => \"10s\".to_string(),\n            });\n            let parent = create(&client, parent).await;\n            let port = 4191;\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let mut route = R::make_route(\n                ns.clone(),\n                vec![parent.obj_ref()],\n                vec![vec![backend.backend_ref(backend_port)]],\n            );\n            route.meta_mut().annotations = Some(btreemap! {\n                // Route annotations override the retry config specified on the parent.\n                \"timeout.linkerd.io/request\".to_string() => \"5s\".to_string(),\n            });\n            let route = create(&client, route).await;\n            await_route_accepted(&client, &route).await;\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            R::routes(&config, |routes| {\n                let outbound_route = routes.first().expect(\"route must exist\");\n                assert!(route.meta_eq(policy::HttpRoute::extract_meta(outbound_route)));\n                let rule = assert_singleton(&outbound_route.rules);\n                let conditions = rule\n                    .retry\n                    .as_ref()\n                    .expect(\"retry config expected\")\n                    .conditions\n                    .as_ref()\n                    .expect(\"retry conditions expected\");\n                let status_range = assert_singleton(&conditions.status_ranges);\n                // Retry config inherited from the service.\n                assert_eq!(status_range.start, 500);\n                assert_eq!(status_range.end, 599);\n                let timeouts = rule.timeouts.as_ref().expect(\"timeouts expected\");\n                // Parent timeout config overridden by route timeout config.\n                assert_eq!(timeouts.response, None);\n                let request_timeout = timeouts.request.as_ref().expect(\"request timeout expected\");\n                assert_eq!(request_timeout.seconds, 5);\n            });\n        })\n        .await;\n    }\n\n    test::<k8s::Service, gateway::HTTPRoute>().await;\n    test::<k8s::Service, policy::HttpRoute>().await;\n    test::<policy::EgressNetwork, gateway::HTTPRoute>().await;\n    test::<policy::EgressNetwork, policy::HttpRoute>().await;\n}\n"
  },
  {
    "path": "policy-test/tests/outbound_api_tcp.rs",
    "content": "#![cfg(feature = \"gateway-api-experimental\")]\n\nuse futures::StreamExt;\nuse linkerd_policy_controller_k8s_api::{self as k8s, gateway, policy};\nuse linkerd_policy_test::{\n    assert_resource_meta, await_route_accepted, create,\n    outbound_api::{assert_route_is_default, assert_singleton, retry_watch_outbound_policy},\n    test_route::{TestParent, TestRoute},\n    with_temp_ns,\n};\n\n#[tokio::test(flavor = \"current_thread\")]\nasync fn multiple_tcp_routes() {\n    async fn test<P: TestParent, R: TestRoute>() {\n        tracing::debug!(\n            parent = %P::kind(&P::DynamicType::default()),\n            route = %R::kind(&R::DynamicType::default()),\n        );\n        with_temp_ns(|client, ns| async move {\n            // Create a parent\n            let port = 4191;\n            let parent = create(&client, P::make_parent(&ns)).await;\n\n            // Create a backend\n            let backend_port = 8888;\n            let backend = match P::make_backend(&ns) {\n                Some(b) => create(&client, b).await,\n                None => parent.clone(),\n            };\n\n            let mut rx = retry_watch_outbound_policy(&client, &ns, parent.ip(), port).await;\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an initial config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            // There should be a default route.\n            gateway::HTTPRoute::routes(&config, |routes| {\n                let route = assert_singleton(routes);\n                assert_route_is_default::<gateway::HTTPRoute>(route, &parent.obj_ref(), port);\n            });\n\n            // Routes should be returned in sorted order by creation timestamp then\n            // name. To ensure that this test isn't timing dependant, routes should\n            // be created in alphabetical order.\n            let mut route_a = R::make_route(\n                ns.clone(),\n                vec![parent.obj_ref()],\n                vec![vec![backend.backend_ref(backend_port)]],\n            );\n            route_a.meta_mut().name = Some(\"a-route\".to_string());\n            let route_a = create(&client, route_a).await;\n            await_route_accepted(&client, &route_a).await;\n\n            // First route update.\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            let mut route_b = R::make_route(\n                ns.clone(),\n                vec![parent.obj_ref()],\n                vec![vec![backend.backend_ref(backend_port)]],\n            );\n            route_b.meta_mut().name = Some(\"b-route\".to_string());\n            let route_b = create(&client, route_b).await;\n            await_route_accepted(&client, &route_b).await;\n\n            // Second route update.\n            let config = rx\n                .next()\n                .await\n                .expect(\"watch must not fail\")\n                .expect(\"watch must return an updated config\");\n            tracing::trace!(?config);\n\n            assert_resource_meta(&config.metadata, parent.obj_ref(), port);\n\n            R::routes(&config, |routes| {\n                // Only the first TCPRoute should be returned in the config.\n                assert!(route_a.meta_eq(R::extract_meta(&routes[0])));\n                assert_eq!(routes.len(), 1);\n            });\n        })\n        .await\n    }\n\n    test::<k8s::Service, gateway::TCPRoute>().await;\n    test::<policy::EgressNetwork, gateway::TCPRoute>().await;\n}\n"
  },
  {
    "path": "policy-test/tests/outbound_http_route_status.rs",
    "content": "// use k8s::Condition;\n// use k8s_gateway_api::{ParentReference, RouteParentStatus, RouteStatus};\n// use k8s_openapi::chrono::Utc;\n// use kube::ResourceExt;\n// use linkerd_policy_controller_core::POLICY_CONTROLLER_NAME;\n// use linkerd_policy_controller_k8s_api as k8s;\n// use linkerd_policy_test::{\n//     await_condition, await_route_status, create, find_route_condition, mk_route, with_temp_ns,\n// };\n\n// #[tokio::test(flavor = \"current_thread\")]\n// async fn accepted_parent() {\n//     with_temp_ns(|client, ns| async move {\n//         // Create a parent Service\n//         let svc_name = \"test-service\";\n//         let svc = k8s::Service {\n//             metadata: k8s::ObjectMeta {\n//                 namespace: Some(ns.clone()),\n//                 name: Some(svc_name.to_string()),\n//                 ..Default::default()\n//             },\n//             spec: Some(k8s::ServiceSpec {\n//                 type_: Some(\"ClusterIP\".to_string()),\n//                 ports: Some(vec![k8s::ServicePort {\n//                     port: 80,\n//                     ..Default::default()\n//                 }]),\n//                 ..Default::default()\n//             }),\n//             ..k8s::Service::default()\n//         };\n//         let svc = create(&client, svc).await;\n//         let svc_ref = vec![k8s::policy::httproute::ParentReference {\n//             group: Some(\"core\".to_string()),\n//             kind: Some(\"Service\".to_string()),\n//             namespace: svc.namespace(),\n//             name: svc.name_unchecked(),\n//             section_name: None,\n//             port: Some(80),\n//         }];\n\n//         // Create a route that references the Service resource.\n//         let _route = create(&client, mk_route(&ns, \"test-route\", Some(svc_ref))).await;\n//         // Wait until route is updated with a status\n//         let statuses = await_route_status(&client, &ns, \"test-route\").await.parents;\n\n//         let route_status = statuses\n//             .clone()\n//             .into_iter()\n//             .find(|route_status| route_status.parent_ref.name == svc_name)\n//             .expect(\"must have at least one parent status\");\n\n//         // Check status references to parent we have created\n//         assert_eq!(route_status.parent_ref.group.as_deref(), Some(\"core\"));\n//         assert_eq!(route_status.parent_ref.kind.as_deref(), Some(\"Service\"));\n\n//         // Check status is accepted with a status of 'True'\n//         let cond = find_route_condition(&statuses, svc_name)\n//             .expect(\"must have at least one 'Accepted' condition for accepted servuce\");\n//         assert_eq!(cond.status, \"True\");\n//         assert_eq!(cond.reason, \"Accepted\")\n//     })\n//     .await;\n// }\n\n// #[tokio::test(flavor = \"current_thread\")]\n// async fn no_cluster_ip() {\n//     with_temp_ns(|client, ns| async move {\n//         // Create a parent Service\n//         let svc = k8s::Service {\n//             metadata: k8s::ObjectMeta {\n//                 namespace: Some(ns.clone()),\n//                 name: Some(\"test-service\".to_string()),\n//                 ..Default::default()\n//             },\n//             spec: Some(k8s::ServiceSpec {\n//                 cluster_ip: Some(\"None\".to_string()),\n//                 type_: Some(\"ClusterIP\".to_string()),\n//                 ports: Some(vec![k8s::ServicePort {\n//                     port: 80,\n//                     ..Default::default()\n//                 }]),\n//                 ..Default::default()\n//             }),\n//             ..k8s::Service::default()\n//         };\n//         let svc = create(&client, svc).await;\n//         let svc_ref = vec![k8s::policy::httproute::ParentReference {\n//             group: Some(\"core\".to_string()),\n//             kind: Some(\"Service\".to_string()),\n//             namespace: svc.namespace(),\n//             name: svc.name_unchecked(),\n//             section_name: None,\n//             port: Some(80),\n//         }];\n\n//         // Create a route that references the Service resource.\n//         let _route = create(&client, mk_route(&ns, \"test-route\", Some(svc_ref))).await;\n//         // Wait until route is updated with a status\n//         let status = await_route_status(&client, &ns, \"test-route\").await;\n//         let cond = find_route_condition(&status.parents, \"test-service\")\n//             .expect(\"must have at least one 'Accepted' condition set for parent\");\n//         // Parent with no ClusterIP should not match.\n//         assert_eq!(cond.status, \"False\");\n//         assert_eq!(cond.reason, \"NoMatchingParent\");\n//     })\n//     .await;\n// }\n\n// #[tokio::test(flavor = \"current_thread\")]\n// async fn external_name() {\n//     with_temp_ns(|client, ns| async move {\n//         // Create a parent Service\n//         let svc = k8s::Service {\n//             metadata: k8s::ObjectMeta {\n//                 namespace: Some(ns.clone()),\n//                 name: Some(\"test-service\".to_string()),\n//                 ..Default::default()\n//             },\n//             spec: Some(k8s::ServiceSpec {\n//                 type_: Some(\"ExternalName\".to_string()),\n//                 external_name: Some(\"linkerd.io\".to_string()),\n//                 ports: Some(vec![k8s::ServicePort {\n//                     port: 80,\n//                     ..Default::default()\n//                 }]),\n//                 ..Default::default()\n//             }),\n//             ..k8s::Service::default()\n//         };\n//         let svc = create(&client, svc).await;\n//         let svc_ref = vec![k8s::policy::httproute::ParentReference {\n//             group: Some(\"core\".to_string()),\n//             kind: Some(\"Service\".to_string()),\n//             namespace: svc.namespace(),\n//             name: svc.name_unchecked(),\n//             section_name: None,\n//             port: Some(80),\n//         }];\n\n//         // Create a route that references the Service resource.\n//         let _route = create(&client, mk_route(&ns, \"test-route\", Some(svc_ref))).await;\n//         // Wait until route is updated with a status\n//         let status = await_route_status(&client, &ns, \"test-route\").await;\n//         let cond = find_route_condition(&status.parents, \"test-service\")\n//             .expect(\"must have at least one 'Accepted' condition set for parent\");\n//         // Parent with ExternalName should not match.\n//         assert_eq!(cond.status, \"False\");\n//         assert_eq!(cond.reason, \"NoMatchingParent\");\n//     })\n//     .await;\n// }\n\n// #[tokio::test(flavor = \"current_thread\")]\n// async fn multiple_statuses() {\n//     with_temp_ns(|client, ns| async move {\n//         // Create a parent Service\n//         let svc_name = \"test-service\";\n//         let svc = k8s::Service {\n//             metadata: k8s::ObjectMeta {\n//                 namespace: Some(ns.clone()),\n//                 name: Some(svc_name.to_string()),\n//                 ..Default::default()\n//             },\n//             spec: Some(k8s::ServiceSpec {\n//                 type_: Some(\"ClusterIP\".to_string()),\n//                 ports: Some(vec![k8s::ServicePort {\n//                     port: 80,\n//                     ..Default::default()\n//                 }]),\n//                 ..Default::default()\n//             }),\n//             ..k8s::Service::default()\n//         };\n//         let svc = create(&client, svc).await;\n//         let svc_ref = vec![k8s::policy::httproute::ParentReference {\n//             group: Some(\"core\".to_string()),\n//             kind: Some(\"Service\".to_string()),\n//             namespace: svc.namespace(),\n//             name: svc.name_unchecked(),\n//             section_name: None,\n//             port: Some(80),\n//         }];\n\n//         // Create a route that references the Service resource.\n//         let _route = create(&client, mk_route(&ns, \"test-route\", Some(svc_ref))).await;\n\n//         // Patch a status onto the HttpRoute.\n//         let value = serde_json::json!({\n//             \"apiVersion\": \"policy.linkerd.io\",\n//                 \"kind\": \"HTTPRoute\",\n//                 \"name\": \"test-route\",\n//                 \"status\": k8s::policy::httproute::HttpRouteStatus {\n//                     inner: RouteStatus {\n//                         parents: vec![RouteParentStatus {\n//                             conditions: vec![Condition {\n//                                 last_transition_time: k8s::Time(Utc::now()),\n//                                 message: \"\".to_string(),\n//                                 observed_generation: None,\n//                                 reason: \"Accepted\".to_string(),\n//                                 status: \"True\".to_string(),\n//                                 type_: \"Accepted\".to_string(),\n//                             }],\n//                             controller_name: \"someone/else\".to_string(),\n//                             parent_ref: ParentReference {\n//                                 group: Some(\"gateway.networking.k8s.io\".to_string()),\n//                                 name: \"foo\".to_string(),\n//                                 kind: Some(\"Gateway\".to_string()),\n//                                 namespace: Some(\"bar\".to_string()),\n//                                 port: None,\n//                                 section_name: None,\n//                             },\n//                         }],\n//                     },\n//                 },\n//         });\n//         let patch = k8s::Patch::Merge(value);\n//         let patch_params = k8s::PatchParams::apply(\"someone/else\");\n//         let api = k8s::Api::<k8s::policy::HttpRoute>::namespaced(client.clone(), &ns);\n//         api.patch_status(\"test-route\", &patch_params, &patch)\n//             .await\n//             .expect(\"failed to patch status\");\n\n//         await_condition(\n//             &client,\n//             &ns,\n//             \"test-route\",\n//             |obj: Option<&k8s::policy::HttpRoute>| -> bool {\n//                 obj.and_then(|route| route.status.as_ref())\n//                     .map(|status| {\n//                         let statuses = &status.parents;\n\n//                         let other_status_found = statuses\n//                             .iter()\n//                             .any(|route_status| route_status.controller_name == \"someone/else\");\n\n//                         let linkerd_status_found = statuses.iter().any(|route_status| {\n//                             route_status.controller_name == POLICY_CONTROLLER_NAME\n//                         });\n\n//                         other_status_found && linkerd_status_found\n//                     })\n//                     .unwrap_or(false)\n//             },\n//         )\n//         .await\n//         .expect(\"must have both statuses\");\n//     })\n//     .await;\n// }\n"
  },
  {
    "path": "proto/common/net.proto",
    "content": "syntax = \"proto3\";\n\npackage linkerd2.common.net;\n\noption go_package = \"github.com/linkerd/linkerd2/controller/gen/common/net\";\n\nmessage IPAddress {\n  oneof ip {\n    fixed32 ipv4 = 1;\n    IPv6 ipv6 = 2;\n  }\n}\n\nmessage IPv6 {\n  fixed64 first = 1; // hextets 1-4\n  fixed64 last = 2; // hextets 5-8\n}\n\nmessage TcpAddress {\n  IPAddress ip = 1;\n  uint32 port = 2;\n}\n\n"
  },
  {
    "path": "proto/google/protobuf/duration.proto",
    "content": "// Protocol Buffers - Google's data interchange format\n// Copyright 2008 Google Inc.  All rights reserved.\n// https://developers.google.com/protocol-buffers/\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//     * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//     * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nsyntax = \"proto3\";\n\npackage google.protobuf;\n\noption csharp_namespace = \"Google.Protobuf.WellKnownTypes\";\noption cc_enable_arenas = true;\noption go_package = \"github.com/golang/protobuf/ptypes/duration\";\noption java_package = \"com.google.protobuf\";\noption java_outer_classname = \"DurationProto\";\noption java_multiple_files = true;\noption objc_class_prefix = \"GPB\";\n\n// A Duration represents a signed, fixed-length span of time represented\n// as a count of seconds and fractions of seconds at nanosecond\n// resolution. It is independent of any calendar and concepts like \"day\"\n// or \"month\". It is related to Timestamp in that the difference between\n// two Timestamp values is a Duration and it can be added or subtracted\n// from a Timestamp. Range is approximately +-10,000 years.\n//\n// # Examples\n//\n// Example 1: Compute Duration from two Timestamps in pseudo code.\n//\n//     Timestamp start = ...;\n//     Timestamp end = ...;\n//     Duration duration = ...;\n//\n//     duration.seconds = end.seconds - start.seconds;\n//     duration.nanos = end.nanos - start.nanos;\n//\n//     if (duration.seconds < 0 && duration.nanos > 0) {\n//       duration.seconds += 1;\n//       duration.nanos -= 1000000000;\n//     } else if (durations.seconds > 0 && duration.nanos < 0) {\n//       duration.seconds -= 1;\n//       duration.nanos += 1000000000;\n//     }\n//\n// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.\n//\n//     Timestamp start = ...;\n//     Duration duration = ...;\n//     Timestamp end = ...;\n//\n//     end.seconds = start.seconds + duration.seconds;\n//     end.nanos = start.nanos + duration.nanos;\n//\n//     if (end.nanos < 0) {\n//       end.seconds -= 1;\n//       end.nanos += 1000000000;\n//     } else if (end.nanos >= 1000000000) {\n//       end.seconds += 1;\n//       end.nanos -= 1000000000;\n//     }\n//\n// Example 3: Compute Duration from datetime.timedelta in Python.\n//\n//     td = datetime.timedelta(days=3, minutes=10)\n//     duration = Duration()\n//     duration.FromTimedelta(td)\n//\n// # JSON Mapping\n//\n// In JSON format, the Duration type is encoded as a string rather than an\n// object, where the string ends in the suffix \"s\" (indicating seconds) and\n// is preceded by the number of seconds, with nanoseconds expressed as\n// fractional seconds. For example, 3 seconds with 0 nanoseconds should be\n// encoded in JSON format as \"3s\", while 3 seconds and 1 nanosecond should\n// be expressed in JSON format as \"3.000000001s\", and 3 seconds and 1\n// microsecond should be expressed in JSON format as \"3.000001s\".\n//\n//\nmessage Duration {\n\n  // Signed seconds of the span of time. Must be from -315,576,000,000\n  // to +315,576,000,000 inclusive. Note: these bounds are computed from:\n  // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years\n  int64 seconds = 1;\n\n  // Signed fractions of a second at nanosecond resolution of the span\n  // of time. Durations less than one second are represented with a 0\n  // `seconds` field and a positive or negative `nanos` field. For durations\n  // of one second or more, a non-zero value for the `nanos` field must be\n  // of the same sign as the `seconds` field. Must be from -999,999,999\n  // to +999,999,999 inclusive.\n  int32 nanos = 2;\n}\n"
  },
  {
    "path": "proxy-identity/main.go",
    "content": "package main\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/rand\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n\t\"k8s.io/apimachinery/pkg/util/validation/field\"\n)\n\nconst (\n\tenvDir          = \"LINKERD2_PROXY_IDENTITY_DIR\"\n\tenvLocalName    = \"LINKERD2_PROXY_IDENTITY_LOCAL_NAME\"\n\tenvTrustAnchors = \"LINKERD2_PROXY_IDENTITY_TRUST_ANCHORS\"\n)\n\nfunc main() {\n\tdir := os.Getenv(envDir)\n\tkeyPath, csrPath, err := checkEndEntityDir(dir)\n\tif err != nil {\n\t\tlog.Fatalf(\"Invalid end-entity directory: %s\", err)\n\t}\n\n\tif _, err := loadVerifier(os.Getenv(envTrustAnchors)); err != nil {\n\t\tlog.Fatalf(\"Failed to load trust anchors: %s\", err)\n\t}\n\n\tkey, err := generateAndStoreKey(keyPath)\n\tif err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n\n\tname := os.Getenv(envLocalName)\n\tif _, err := generateAndStoreCSR(csrPath, name, key); err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n\n\trunProxy()\n}\n\nfunc loadVerifier(pem string) (verify x509.VerifyOptions, err error) {\n\tif pem == \"\" {\n\t\terr = fmt.Errorf(\"'%s' must be set\", envTrustAnchors)\n\t\treturn\n\t}\n\n\tverify.Roots, err = tls.DecodePEMCertPool(pem)\n\treturn\n}\n\n// checkEndEntityDir checks that the provided directory path exists and is\n// suitable to write key material to, returning the key and CSR paths.\n//\n// If the directory does not exist, we assume that the directory was specified\n// incorrectly and return an error. In practice this directory should be tmpfs\n// so that credentials are not written to disk, so we do not want to create new\n// directories here.\n//\n// If the key and/or CSR paths refer to existing files, it will be logged and\n// the credentials will be recreated.\nfunc checkEndEntityDir(dir string) (string, string, error) {\n\tif dir == \"\" {\n\t\treturn \"\", \"\", errors.New(\"no end entity directory specified\")\n\t}\n\n\ts, err := os.Stat(dir)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tif !s.IsDir() {\n\t\treturn \"\", \"\", fmt.Errorf(\"not a directory: %s\", dir)\n\t}\n\n\tkeyPath := filepath.Join(dir, \"key.p8\")\n\tif err = checkNotExists(keyPath); err != nil {\n\t\tlog.Infof(\"Found pre-existing key: %s\", keyPath)\n\t}\n\n\tcsrPath := filepath.Join(dir, \"csr.der\")\n\tif err = checkNotExists(csrPath); err != nil {\n\t\tlog.Infof(\"Found pre-existing CSR: %s\", csrPath)\n\t}\n\n\treturn keyPath, csrPath, nil\n}\n\nfunc checkNotExists(p string) (err error) {\n\t_, err = os.Stat(p)\n\tif err == nil {\n\t\terr = fmt.Errorf(\"already exists: %s\", p)\n\t} else if os.IsNotExist(err) {\n\t\terr = nil\n\t}\n\treturn\n}\n\nfunc generateAndStoreKey(p string) (key *ecdsa.PrivateKey, err error) {\n\t// Generate a private key and store it read-only. This is written to the\n\t// file-system so that the proxy may read this key at startup. The\n\t// destination path should generally be tmpfs so that the key material is\n\t// not written to disk.\n\tkey, err = tls.GenerateKey()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tpemb := tls.EncodePrivateKeyP8(key)\n\terr = os.WriteFile(p, pemb, 0600)\n\treturn\n}\n\nfunc generateAndStoreCSR(p, id string, key *ecdsa.PrivateKey) ([]byte, error) {\n\tif id == \"\" {\n\t\treturn nil, errors.New(\"a non-empty identity is required\")\n\t}\n\n\tif err := validation.IsFullyQualifiedDomainName(field.NewPath(\"\"), id).ToAggregate(); err != nil {\n\t\treturn nil, fmt.Errorf(\"%s a fully qualified DNS name is required\", id)\n\t}\n\n\tcsr := x509.CertificateRequest{\n\t\tSubject:  pkix.Name{CommonName: id},\n\t\tDNSNames: []string{id},\n\t}\n\tcsrb, err := x509.CreateCertificateRequest(rand.Reader, &csr, key)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create CSR: %w\", err)\n\t}\n\n\tif err = os.WriteFile(p, csrb, 0600); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to write CSR: %w\", err)\n\t}\n\n\treturn csrb, nil\n}\n"
  },
  {
    "path": "proxy-identity/run_proxy_unix.go",
    "content": "//go:build !windows\n\npackage main\n\nimport (\n\t\"os\"\n\t\"syscall\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nfunc runProxy() {\n\t// The input arguments are static.\n\t//nolint:gosec\n\terr := syscall.Exec(\"/usr/lib/linkerd/linkerd2-proxy\", []string{}, os.Environ())\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to run proxy: %s\", err)\n\t}\n}\n"
  },
  {
    "path": "proxy-identity/run_proxy_windows.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\t\"os/exec\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nfunc runProxy() {\n\tpath := \"C:\\\\linkerd\\\\linkerd2-proxy.exe\"\n\tcmd := exec.Command(path)\n\tcmd.Env = os.Environ()\n\tcmd.Stdin = os.Stdin\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\terr := cmd.Run()\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to run proxy: %s\", err)\n\t}\n}\n"
  },
  {
    "path": "proxy-runtime.yml",
    "content": "contents:\n  repositories:\n    - https://packages.wolfi.dev/os\n  keyring:\n    - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub\n  packages:\n    - ca-certificates-bundle\n    - glibc\n    - iptables\n    - ip6tables\n    - libnetfilter_conntrack\n    - libnfnetlink\n    - libmnl\n    - libgcc\n    - nftables-slim\n    - libcap-utils\narchs:\n  - x86_64\n  - aarch64\npaths:\n- path: /run\n  type: directory\n  permissions: 0o755\naccounts:\n  users:\n    - username: nonroot\n      uid: 65532\n    - username: nobody\n      uid: 65534\n  run-as: 65532\nwork-dir: /home/nonroot\n"
  },
  {
    "path": "rust-toolchain.toml",
    "content": "[toolchain]\nchannel = \"1.90.0\"\n"
  },
  {
    "path": "test/cli/cli_install_static_test.go",
    "content": "package test\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\nvar (\n\tTestHelper *testutil.TestHelper\n)\n\nfunc TestMain(m *testing.M) {\n\n\t// Read the flags and create a new test helper\n\texit := func(code int, msg string) {\n\t\tfmt.Fprintln(os.Stderr, msg)\n\t\tos.Exit(code)\n\t}\n\n\tlinkerd := flag.String(\"linkerd\", \"\", \"path to the linkerd binary to test\")\n\trunTests := flag.Bool(\"cli-tests\", false, \"must be provided to run the cli tests\")\n\tflag.Parse()\n\n\tif !*runTests {\n\t\texit(0, \"cli tests not enabled: enable with -cli-tests\")\n\t}\n\n\tif *linkerd == \"\" {\n\t\texit(1, \"-linkerd flag is required\")\n\t}\n\n\tTestHelper = testutil.NewGenericTestHelper(*linkerd, \"\", \"l5d\", \"linkerd-viz\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", false, false, false, false, false, false, *http.DefaultClient, testutil.KubernetesHelper{})\n\tos.Exit(m.Run())\n}\n\nfunc TestCliInstall(t *testing.T) {\n\t_, err := TestHelper.LinkerdRun(\"install\", \"--ignore-cluster\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install' command failed\", err)\n\t}\n}\n"
  },
  {
    "path": "test/fuzzing/README.md",
    "content": "# Fuzzing Linkerd\n\nThe scripting setup for fuzzing is used by [google/oss-fuzz] which\nperforms continuous fuzzing for the Linkerd project.\n\nThe fuzzing configuration for Linkerd is located in the [linkerd2\nproject directory][of-l2] which handles the docker build and execution of the\nfuzzers.\n\n## Running locally\n\nInstructions for running the fuzzers locally can be found in the oss-fuzz\n[docs].\n\nThis will require cloning the [google/oss-fuzz] repository locally and running\nthe commands outlined in the instructions.\n\n### oss-fuzz File Setup\n\n- [Dockerfile] provides the necessary environment for running the\n  fuzzer; the main thing being the `oss-fuzz-base` image which provides the\n  `compile_go_fuzzer` funtions seen in this directory's `build.sh`.\n- [build.sh] is responsible for calling the fuzzing functions for each\n  fuzzer in the linkerd2 project.\n\n<!-- refs -->\n[google/oss-fuzz]: https://github.com/google/oss-fuzz\n[of-l2]: https://github.com/google/oss-fuzz/tree/master/projects/linkerd2\n[docs]: https://google.github.io/oss-fuzz/getting-started/new-project-guide/#testing-locally\n[Dockerfile]: https://github.com/google/oss-fuzz/blob/master/projects/linkerd2/Dockerfile\n[build.sh]: https://github.com/google/oss-fuzz/blob/master/projects/linkerd2/build.sh\n"
  },
  {
    "path": "test/fuzzing/fuzzers.go",
    "content": "package fuzzing\n\nimport (\n\tfuzz \"github.com/AdaLogics/go-fuzz-headers\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/util\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// FuzzParsePorts fuzzes the ParsePorts function.\nfunc FuzzParsePorts(data []byte) int {\n\t_ = util.ParsePorts(string(data))\n\treturn 1\n}\n\n// FuzzParseContainerOpaquePorts fuzzes the ParseContainerOpaquePorts function.\nfunc FuzzParseContainerOpaquePorts(data []byte) int {\n\tf := fuzz.NewConsumer(data)\n\n\tqtyOfContainers, err := f.GetInt()\n\tif err != nil {\n\t\treturn 0\n\t}\n\tqtyOfContainers %= 20\n\n\tcontainers := make([]corev1.Container, 0)\n\tfor i := 0; i < qtyOfContainers; i++ {\n\t\tnewContainer := corev1.Container{}\n\t\terr = f.GenerateStruct(&newContainer)\n\t\tif err != nil {\n\t\t\treturn 0\n\t\t}\n\t\tcontainers = append(containers, newContainer)\n\t}\n\toverride, err := f.GetString()\n\tif err != nil {\n\t\treturn 0\n\t}\n\t_ = util.ParseContainerOpaquePorts(override, util.GetNamedPorts(containers))\n\treturn 1\n}\n\n// FuzzHealthCheck fuzzes the HealthCheck method for the healthchecker.\nfunc FuzzHealthCheck(data []byte) int {\n\tf := fuzz.NewConsumer(data)\n\toptions := &healthcheck.Options{}\n\terr := f.GenerateStruct(options)\n\tif err != nil {\n\t\treturn 0\n\t}\n\t_ = healthcheck.NewHealthChecker([]healthcheck.CategoryID{healthcheck.KubernetesAPIChecks}, options)\n\treturn 1\n}\n"
  },
  {
    "path": "test/integration/deep/appprotocol/appprotocol_test.go",
    "content": "package appprotocol\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n\tv1 \"k8s.io/api/core/v1\"\n)\n\nvar TestHelper *testutil.TestHelper\n\nvar appProtocolClientTemplate = template.Must(template.New(\"appprotocol_client.yaml\").ParseFiles(\"testdata/appprotocol_client.yaml\"))\n\nvar (\n\topaqueApp = \"opaque\"\n\topaqueSC  = \"slow-cooker-opaque\"\n\thttp1App  = \"http1\"\n\thttp1SC   = \"slow-cooker-http1\"\n)\n\ntype testCase struct {\n\tname      string\n\tappName   string\n\tappChecks []check\n\tscName    string\n\tscChecks  []check\n}\n\ntype check func(metrics, ns string) error\n\nfunc checks(c ...check) []check { return c }\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until control plane is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)\n\tos.Exit(m.Run())\n}\n\n// clientTemplateArgs is a struct that contains the arguments to be supplied\n// to the deployment template appprotocol_client.yaml.\ntype clientTemplateArgs struct {\n\tServiceCookerOpaqueTargetHost string\n\tServiceCookerHttp1TargetHost  string\n}\n\nfunc serviceName(n string) string {\n\treturn fmt.Sprintf(\"svc-%s\", n)\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\nfunc TestAppProtocolCalledByServiceTarget(t *testing.T) {\n\tctx := context.Background()\n\tTestHelper.WithDataPlaneNamespace(ctx, \"appprotocol-called-by-service-name-test\", map[string]string{}, t, func(t *testing.T, appProtocolNs string) {\n\t\tchecks := func(c ...check) []check { return c }\n\n\t\tif err := deployApplications(appProtocolNs); err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"failed to deploy applications\", err)\n\t\t}\n\t\twaitForAppDeploymentReady(t, appProtocolNs)\n\n\t\ttmplArgs := clientTemplateArgs{\n\t\t\tServiceCookerOpaqueTargetHost: serviceName(opaqueApp),\n\t\t\tServiceCookerHttp1TargetHost:  serviceName(http1App),\n\t\t}\n\t\tif err := deployTemplate(appProtocolNs, appProtocolClientTemplate, tmplArgs); err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"failed to deploy client pods\", err)\n\t\t}\n\t\twaitForClientDeploymentReady(t, appProtocolNs)\n\n\t\trunTests(ctx, t, appProtocolNs, []testCase{\n\t\t\t{\n\t\t\t\tname:   \"calling a meshed service when appProtocol is linkerd.io/opaque on receiving service\",\n\t\t\t\tscName: opaqueSC,\n\t\t\t\tscChecks: checks(\n\t\t\t\t\thasNoOutboundHTTPRequest,\n\t\t\t\t\thasOutboundTCPWithTLSAndAuthority,\n\t\t\t\t),\n\t\t\t\tappName:   opaqueApp,\n\t\t\t\tappChecks: checks(hasInboundTCPTrafficWithTLS),\n\t\t\t},\n\t\t})\n\t\trunTests(ctx, t, appProtocolNs, []testCase{\n\t\t\t{\n\t\t\t\tname:   \"calling a meshed service when appProtocol is http on receiving service\",\n\t\t\t\tscName: http1SC,\n\t\t\t\tscChecks: checks(\n\t\t\t\t\thasOutboundHTTPRequestWithTLS,\n\t\t\t\t\thasOutboundTCPWithTLSAndAuthority,\n\t\t\t\t),\n\t\t\t\tappName:   http1App,\n\t\t\t\tappChecks: checks(hasInboundTCPTrafficWithTLS),\n\t\t\t},\n\t\t})\n\t})\n}\n\nfunc TestAppProtocolCalledByPodTarget(t *testing.T) {\n\tctx := context.Background()\n\tTestHelper.WithDataPlaneNamespace(ctx, \"appprotocol-called-by-pod-ip-test\", map[string]string{}, t, func(t *testing.T, appProtocolNs string) {\n\n\t\tif err := deployApplications(appProtocolNs); err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"failed to deploy applications\", err)\n\t\t}\n\t\twaitForAppDeploymentReady(t, appProtocolNs)\n\n\t\ttmplArgs, err := templateArgsPodIP(ctx, appProtocolNs)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"failed to fetch pod IPs\", err)\n\t\t}\n\n\t\tif err := deployTemplate(appProtocolNs, appProtocolClientTemplate, tmplArgs); err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"failed to deploy client pods\", err)\n\t\t}\n\t\twaitForClientDeploymentReady(t, appProtocolNs)\n\n\t\trunTests(ctx, t, appProtocolNs, []testCase{\n\t\t\t{\n\t\t\t\tname:   \"calling a meshed service when appProtocol is linkerd.io/opaque on receiving service\",\n\t\t\t\tscName: opaqueSC,\n\t\t\t\tscChecks: checks(\n\t\t\t\t\t// We call pods directly, so annotation on a service is ignored.\n\t\t\t\t\thasOutboundHTTPRequestWithTLS,\n\t\t\t\t\t// No authority here, because we are calling the pod directly.\n\t\t\t\t\thasOutboundTCPWithTLSAndNoAuthority,\n\t\t\t\t),\n\t\t\t\tappName:   opaqueApp,\n\t\t\t\tappChecks: checks(hasInboundTCPTrafficWithTLS),\n\t\t\t},\n\t\t})\n\t\trunTests(ctx, t, appProtocolNs, []testCase{\n\t\t\t{\n\t\t\t\tname:   \"calling a meshed service when appProtocol is http on receiving service\",\n\t\t\t\tscName: http1SC,\n\t\t\t\tscChecks: checks(\n\t\t\t\t\t// We call pods directly, so annotation on a service is ignored.\n\t\t\t\t\thasOutboundHTTPRequestWithTLS,\n\t\t\t\t\t// No authority here, because we are calling the pod directly.\n\t\t\t\t\thasOutboundTCPWithTLSAndNoAuthority,\n\t\t\t\t),\n\t\t\t\tappName:   http1App,\n\t\t\t\tappChecks: checks(hasInboundTCPTrafficWithTLS),\n\t\t\t},\n\t\t})\n\t})\n}\n\nfunc waitForAppDeploymentReady(t *testing.T, appProtocolNs string) {\n\tTestHelper.WaitRollout(t, map[string]testutil.DeploySpec{\n\t\topaqueApp: {\n\t\t\tNamespace: appProtocolNs,\n\t\t\tReplicas:  1,\n\t\t},\n\t\thttp1App: {\n\t\t\tNamespace: appProtocolNs,\n\t\t\tReplicas:  1,\n\t\t},\n\t})\n}\n\nfunc waitForClientDeploymentReady(t *testing.T, appProtocolNs string) {\n\tTestHelper.WaitRollout(t, map[string]testutil.DeploySpec{\n\t\topaqueSC: {\n\t\t\tNamespace: appProtocolNs,\n\t\t\tReplicas:  1,\n\t\t},\n\t\thttp1SC: {\n\t\t\tNamespace: appProtocolNs,\n\t\t\tReplicas:  1,\n\t\t},\n\t})\n}\n\nfunc templateArgsPodIP(ctx context.Context, ns string) (clientTemplateArgs, error) {\n\topaquePodIP, err := getPodIPByAppLabel(ctx, ns, opaqueApp)\n\tif err != nil {\n\t\treturn clientTemplateArgs{}, fmt.Errorf(\"failed to fetch pod IP for %q: %w\", opaqueApp, err)\n\t}\n\thttp1PodIP, err := getPodIPByAppLabel(ctx, ns, http1App)\n\tif err != nil {\n\t\treturn clientTemplateArgs{}, fmt.Errorf(\"failed to fetch pod IP for %q: %w\", http1App, err)\n\t}\n\treturn clientTemplateArgs{\n\t\tServiceCookerOpaqueTargetHost: opaquePodIP,\n\t\tServiceCookerHttp1TargetHost:  http1PodIP,\n\t}, nil\n}\n\nfunc runTests(ctx context.Context, t *testing.T, ns string, tcs []testCase) {\n\tt.Helper()\n\tfor _, tc := range tcs {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\terr := testutil.RetryFor(30*time.Second, func() error {\n\t\t\t\tif err := checkPodMetrics(ctx, ns, tc.scName, tc.scChecks); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to check metrics for client pod: %w\", err)\n\t\t\t\t}\n\t\t\t\tif tc.appName == \"\" {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tif err := checkPodMetrics(ctx, ns, tc.appName, tc.appChecks); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to check metrics for app pod: %w\", err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected metric for pod\", \"unexpected metric for pod: %s\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc checkPodMetrics(ctx context.Context, appProtocolNs string, podAppLabel string, checks []check) error {\n\tpods, err := TestHelper.GetPods(ctx, appProtocolNs, map[string]string{\"app\": podAppLabel})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting pods for label 'app: %q': %w\", podAppLabel, err)\n\t}\n\tif len(pods) == 0 {\n\t\treturn fmt.Errorf(\"no pods found for label 'app: %q'\", podAppLabel)\n\t}\n\tmetrics, err := getPodMetrics(pods[0], appProtocolNs)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting metrics for pod %q: %w\", pods[0].Name, err)\n\t}\n\tfor _, check := range checks {\n\t\tif err := check(metrics, appProtocolNs); err != nil {\n\t\t\treturn fmt.Errorf(\"validation of pod metrics failed: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc deployApplications(ns string) error {\n\tout, err := TestHelper.Kubectl(\"\", \"apply\", \"-f\", \"testdata/appprotocol_application.yaml\", \"-n\", ns)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed apply deployment file %q: %w\", out, err)\n\t}\n\treturn nil\n}\n\nfunc deployTemplate(ns string, tmpl *template.Template, templateArgs interface{}) error {\n\tbb := &bytes.Buffer{}\n\tif err := tmpl.Execute(bb, templateArgs); err != nil {\n\t\treturn fmt.Errorf(\"failed to write deployment template: %w\", err)\n\t}\n\tout, err := TestHelper.KubectlApply(bb.String(), ns)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed apply deployment file %q: %w\", out, err)\n\t}\n\treturn nil\n}\n\nfunc getPodMetrics(pod v1.Pod, ns string) (string, error) {\n\tpodName := fmt.Sprintf(\"pod/%s\", pod.Name)\n\tcmd := []string{\"diagnostics\", \"proxy-metrics\", \"--namespace\", ns, podName}\n\tmetrics, err := TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn metrics, nil\n}\n\nfunc getPodIPByAppLabel(ctx context.Context, ns string, app string) (string, error) {\n\tlabels := map[string]string{\"app\": app}\n\tpods, err := TestHelper.GetPods(ctx, ns, labels)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get pod by labels %v: %w\", labels, err)\n\t}\n\tif len(pods) == 0 {\n\t\treturn \"\", fmt.Errorf(\"no pods found for labels %v\", labels)\n\t}\n\treturn pods[0].Status.PodIP, nil\n}\n"
  },
  {
    "path": "test/integration/deep/appprotocol/assertions.go",
    "content": "package appprotocol\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/linkerd/linkerd2/testutil/prommatch\"\n)\n\nvar (\n\tauthorityRE = regexp.MustCompile(`[a-zA-Z0-9\\-]+\\.[a-zA-Z0-9\\-]+\\.svc\\.cluster\\.local:[0-9]+`)\n)\n\n// hasNoOutboundHTTPRequest returns error if there is any\n// series matching request_total{direction=\"outbound\"}\nfunc hasNoOutboundHTTPRequest(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\"request_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\": prommatch.Equals(\"outbound\"),\n\t\t})\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check of against the provided metrics: %w\", err)\n\t}\n\tif ok {\n\t\treturn fmt.Errorf(\"expected not to find HTTP outbound requests \\n%s\", metrics)\n\t}\n\treturn nil\n}\n\n// hasOutboundHTTPRequestWithTLS checks there is a series matching:\n//\n//\trequest_total{\n//\t  direction=\"outbound\",\n//\t  target_addr=~\"[0-9\\.]+:[0-9]+\",\n//\t  target_ip=~\"[0-9.]+\",\n//\t  tls=\"true\",\n//\t  dst_namespace=\"default.${ns}.serviceaccount.identity.linkerd.cluster.local\",\n//\t  dst_serviceaccount=\"default\"\n//\t}\nfunc hasOutboundHTTPRequestWithTLS(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\"request_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\":          prommatch.Equals(\"outbound\"),\n\t\t\t\"tls\":                prommatch.Equals(\"true\"),\n\t\t\t\"server_id\":          prommatch.Equals(fmt.Sprintf(\"default.%s.serviceaccount.identity.linkerd.cluster.local\", ns)),\n\t\t\t\"dst_namespace\":      prommatch.Equals(ns),\n\t\t\t\"dst_serviceaccount\": prommatch.Equals(\"default\"),\n\t\t},\n\t\tprommatch.TargetAddrLabels(),\n\t\tprommatch.HasPositiveValue())\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check of against the provided metrics: %w\", err)\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"expected to find HTTP TLS outbound requests \\n%s\", metrics)\n\t}\n\treturn nil\n}\n\n// hasInboundTCPTrafficWithTLS checks there is a series matching:\n//\n//\ttcp_open_total{\n//\t  direction=\"inbound\",\n//\t  peer=\"src\",\n//\t  tls=\"true\",\n//\t  client_id=\"default.${ns}.serviceaccount.identity.linkerd.cluster.local\",\n//\t  srv_kind=\"default\",\n//\t  srv_name=\"all-unauthenticated\",\n//\t  target_addr=~\"[0-9\\.]+:[0-9]+\",\n//\t  target_ip=~\"[0-9\\.]+\"\n//\t}\nfunc hasInboundTCPTrafficWithTLS(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\n\t\t\"tcp_open_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\": prommatch.Equals(\"inbound\"),\n\t\t\t\"peer\":      prommatch.Equals(\"src\"),\n\t\t\t\"tls\":       prommatch.Equals(\"true\"),\n\t\t\t\"client_id\": prommatch.Equals(fmt.Sprintf(\"default.%s.serviceaccount.identity.linkerd.cluster.local\", ns)),\n\t\t\t\"srv_kind\":  prommatch.Equals(\"default\"),\n\t\t\t\"srv_name\":  prommatch.Equals(\"all-unauthenticated\"),\n\t\t},\n\t\tprommatch.TargetAddrLabels(),\n\t\tprommatch.HasPositiveValue(),\n\t)\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check of against the provided metrics: %w\", err)\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"failed to find expected metric for inbound TLS TCP traffic\\n%s\", metrics)\n\t}\n\treturn nil\n}\n\n// hasOutboundTCPWithTLSAndAuthority checks there is a series matching:\n//\n//\ttcp_open_total{\n//\t  direction=\"outbound\",\n//\t  peer=\"dst\",\n//\t  tls=\"true\",\n//\t  target_addr=~\"[0-9\\.]+:[0-9]+\",\n//\t  authority=~\"[a-zA-Z\\-]+\\.[a-zA-Z\\-]+\\.svc\\.cluster\\.local:[0-9]+\"\n//\t}\nfunc hasOutboundTCPWithTLSAndAuthority(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\"tcp_open_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\": prommatch.Equals(\"outbound\"),\n\t\t\t\"peer\":      prommatch.Equals(\"dst\"),\n\t\t\t\"tls\":       prommatch.Equals(\"true\"),\n\t\t\t\"authority\": prommatch.Like(authorityRE),\n\t\t},\n\t\tprommatch.TargetAddrLabels(),\n\t\tprommatch.HasPositiveValue())\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check against the provided metrics: %w\", err)\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"failed to find expected metric for outbound TLS TCP traffic\\n%s\", metrics)\n\t}\n\treturn nil\n}\n\n// hasOutboundTCPWithTLSAndNoAuthority checks there is a series matching:\n//\n//\ttcp_open_total{\n//\t  direction=\"outbound\",\n//\t  peer=\"dst\",\n//\t  tls=\"true\",\n//\t  target_addr=~\"[0-9\\.]+:[0-9]+\",\n//\t  authority=\"\"\n//\t}\nfunc hasOutboundTCPWithTLSAndNoAuthority(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\"tcp_open_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\": prommatch.Equals(\"outbound\"),\n\t\t\t\"peer\":      prommatch.Equals(\"dst\"),\n\t\t\t\"tls\":       prommatch.Equals(\"true\"),\n\t\t\t\"authority\": prommatch.Absent(),\n\t\t},\n\t\tprommatch.TargetAddrLabels(),\n\t\tprommatch.HasPositiveValue())\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check of against the provided metrics: %w\", err)\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"failed to find expected metric for outbound TLS TCP traffic\\n%s\", metrics)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "test/integration/deep/appprotocol/testdata/appprotocol_application.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: opaque\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: opaque\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n      labels:\n        app: opaque\n    spec:\n      containers:\n      - name: app\n        image: buoyantio/bb:v0.0.7\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=opaque\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc-opaque\n  labels:\n    app: svc-opaque\nspec:\n  selector:\n    app: opaque\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n    appProtocol: linkerd.io/opaque\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: http1\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: http1\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n      labels:\n        app: http1\n    spec:\n      containers:\n      - name: app\n        image: buoyantio/bb:v0.0.7\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=http1\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc-http1\n  labels:\n    app: svc-http1\nspec:\n  selector:\n    app: http1\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n    appProtocol: http"
  },
  {
    "path": "test/integration/deep/appprotocol/testdata/appprotocol_client.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: slow-cooker-opaque\nspec:\n  selector:\n    matchLabels:\n      app: slow-cooker-opaque\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: \"enabled\"\n      labels:\n        app: slow-cooker-opaque\n    spec:\n      containers:\n      - name: slow-cooker-opaque\n        image: buoyantio/slow_cooker:1.3.0\n        args:\n        - -qps=1\n        - -metric-addr=0.0.0.0:9999\n        - http://{{ .ServiceCookerOpaqueTargetHost}}:8080\n        ports:\n        - containerPort: 9999\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: slow-cooker-http1\nspec:\n  selector:\n    matchLabels:\n      app: slow-cooker-http1\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: \"enabled\"\n      labels:\n        app: slow-cooker-http1\n    spec:\n      containers:\n      - name: slow-cooker-opaque\n        image: buoyantio/slow_cooker:1.3.0\n        args:\n        - -qps=1\n        - -metric-addr=0.0.0.0:9999\n        - http://{{ .ServiceCookerHttp1TargetHost}}:8080\n        ports:\n        - containerPort: 9999"
  },
  {
    "path": "test/integration/deep/dualstack/dualstack_test.go",
    "content": "package dualstack\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\ntype IP struct {\n\tIP string `json:\"ip\"`\n}\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until control plane is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)\n\tos.Exit(m.Run())\n}\n\n// TestDualStack creates an injected pod that starts two servers, one listening\n// on the IPv4 wildcard address and serving the string \"IPv4\", and another\n// listening on the IPv6 wildcard address and serving the string \"IPv6\". They\n// are fronted by a DualStack Service. We test that we can reach those two IPs\n// directly, and that making a request to the service's FQDN always hits the\n// IPv6 endpoint.\nfunc TestDualStack(t *testing.T) {\n\tif !TestHelper.DualStack() {\n\t\tt.Skip(\"Skipping DualStack test\")\n\t}\n\n\tTestHelper.WithDataPlaneNamespace(context.Background(), \"dualstack-test\", map[string]string{}, t, func(t *testing.T, ns string) {\n\t\tout, err := TestHelper.Kubectl(\"\",\n\t\t\t\"create\", \"configmap\", \"go-app\",\n\t\t\t\"--from-file=main.go=testdata/ipfamilies-server.go\",\n\t\t\t\"-n\", ns,\n\t\t)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v\\noutput:\\n%s\", err, out)\n\t\t}\n\n\t\tout, err = TestHelper.Kubectl(\"\",\n\t\t\t\"apply\", \"-f\", \"testdata/ipfamilies-server-client.yml\",\n\t\t\t\"-n\", ns,\n\t\t)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v\\noutput:\\n%s\", err, out)\n\t\t}\n\n\t\tcheckPods(t, ns, \"ipfamilies-server\")\n\t\tcheckPods(t, ns, \"client\")\n\n\t\tvar clientIPv6, serverIPv4, serverIPv6 string\n\n\t\tt.Run(\"Retrieve pod IPs\", func(t *testing.T) {\n\t\t\tcmd := []string{\n\t\t\t\t\"get\", \"po\",\n\t\t\t\t\"-o\", \"jsonpath='{.items[*].status.podIPs}'\",\n\t\t\t\t\"-n\", ns,\n\t\t\t}\n\n\t\t\tout, err = TestHelper.Kubectl(\"\", append(cmd, \"-l\", \"app=server\")...)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v\\noutput:\\n%s\", err, out)\n\t\t\t}\n\n\t\t\tvar IPs []IP\n\t\t\tout = strings.Trim(out, \"'\")\n\t\t\tif err = json.Unmarshal([]byte(out), &IPs); err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"error unmarshaling JSON\", \"error unmarshaling JSON '%s': %s\", out, err)\n\t\t\t}\n\t\t\tif len(IPs) != 2 {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected number of IPs\", \"expected 2 IPs, got %s\", fmt.Sprint(len(IPs)))\n\t\t\t}\n\t\t\tserverIPv4 = IPs[0].IP\n\t\t\tserverIPv6 = IPs[1].IP\n\n\t\t\tout, err = TestHelper.Kubectl(\"\", append(cmd, \"-l\", \"app=client\")...)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v\\noutput:\\n%s\", err, out)\n\t\t\t}\n\n\t\t\tout = strings.Trim(out, \"'\")\n\t\t\tif err = json.Unmarshal([]byte(out), &IPs); err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"error unmarshaling JSON\", \"error unmarshaling JSON '%s': %s\", out, err)\n\t\t\t}\n\t\t\tif len(IPs) != 2 {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected number of IPs\", \"expected 2 IPs, got %s\", fmt.Sprint(len(IPs)))\n\t\t\t}\n\t\t\tclientIPv6 = IPs[1].IP\n\t\t})\n\n\t\tt.Run(\"Apply policy\", func(t *testing.T) {\n\t\t\tfile, err := os.Open(\"testdata/ipfamilies-policy.yml\")\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tdefer file.Close()\n\t\t\tmanifest, err := io.ReadAll(file)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tin := strings.ReplaceAll(string(manifest), \"{IPv6}\", clientIPv6)\n\t\t\tout, err = TestHelper.KubectlApply(in, ns)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v\\noutput:\\n%s\", err, out)\n\t\t\t}\n\t\t})\n\n\t\ttime.Sleep(10 * time.Second)\n\n\t\tt.Run(\"Hit IPv4 addr directly\", func(t *testing.T) {\n\t\t\tout, err = TestHelper.Kubectl(\"\",\n\t\t\t\t\"exec\", \"deploy/client\",\n\t\t\t\t\"-c\", \"curl\",\n\t\t\t\t\"-n\", ns,\n\t\t\t\t\"--\",\n\t\t\t\t\"curl\", \"-s\", \"-S\", \"--stderr\", \"-\", \"http://\"+serverIPv4+\":8080\",\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v\\noutput:\\n%s\", err, out)\n\t\t\t}\n\t\t\tif out != \"IPv4\\n\" {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected output\", \"expected 'IPv4', received '%s'\", out)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"Hit IPv6 addr directly\", func(t *testing.T) {\n\t\t\tout, err = TestHelper.Kubectl(\"\",\n\t\t\t\t\"exec\", \"deploy/client\",\n\t\t\t\t\"-c\", \"curl\",\n\t\t\t\t\"-n\", ns,\n\t\t\t\t\"--\",\n\t\t\t\t\"curl\", \"-s\", \"-S\", \"--stderr\", \"-\", \"http://[\"+serverIPv6+\"]:8080\",\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v\\noutput:\\n%s\", err, out)\n\t\t\t}\n\t\t\tif out != \"IPv6\\n\" {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"expected 'IPv6'\", \"received '%s'\", out)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"Hit FQDN directly (should always resolve to IPv6)\", func(t *testing.T) {\n\t\t\tfor i := 0; i < 10; i++ {\n\t\t\t\tout, err = TestHelper.Kubectl(\"\",\n\t\t\t\t\t\"exec\", \"deploy/client\",\n\t\t\t\t\t\"-c\", \"curl\",\n\t\t\t\t\t\"-n\", ns,\n\t\t\t\t\t\"--\",\n\t\t\t\t\t\"curl\", \"-s\", \"-S\", \"--stderr\", \"-\", \"http://ipfamilies-server:8080\",\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v\\noutput:\\n%s\", err, out)\n\t\t\t\t}\n\t\t\t\tif out != \"IPv6\\n\" {\n\t\t\t\t\ttestutil.AnnotatedFatalf(t, \"expected 'IPv6'\", \"received '%s'\", out)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t})\n}\n\nfunc checkPods(t *testing.T, ns, pod string) {\n\tt.Helper()\n\n\tif err := TestHelper.CheckPods(context.Background(), ns, pod, 1); err != nil {\n\t\t//nolint:errorlint\n\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t} else {\n\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/integration/deep/dualstack/testdata/ipfamilies-policy.yml",
    "content": "apiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  name: ipfamilies\nspec:\n  podSelector:\n    matchLabels:\n      app.kubernetes.io/name: ipfamilies-server\n  port: http\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  name: ipfamilies\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: ipfamilies\n  requiredAuthenticationRefs:\n    - name: ipfamilies\n      kind: NetworkAuthentication\n      group: policy.linkerd.io\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  name: ipfamilies\nspec:\n  networks:\n  - cidr: {IPv6}/128\n"
  },
  {
    "path": "test/integration/deep/dualstack/testdata/ipfamilies-server-client.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: ipfamilies-server\nspec:\n  selector:\n    matchLabels:\n      app: server\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n      labels:\n        app: server\n    spec:\n      containers:\n      - image: ghcr.io/alpeb/family-server:v1\n        name: ipfamilies-server\n        ports:\n        - containerPort: 8080\n          name: http\n          protocol: TCP\n        command: [\"/bin/sh\"]\n        args:\n        - -c\n        - 'go run /go/src/app/main.go'\n        readinessProbe:\n          httpGet:\n            path: /\n            port: http\n        volumeMounts:\n        - name: go-app\n          mountPath: /go/src/app\n      volumes:\n      - name: go-app\n        configMap:\n          name: go-app\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: ipfamilies-server\nspec:\n  ipFamilies:\n  - IPv4\n  - IPv6\n  ipFamilyPolicy: RequireDualStack\n  ports:\n  - name: http\n    port: 8080\n    protocol: TCP\n    targetPort: http\n  selector:\n    app: server\n  type: ClusterIP\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: client\nspec:\n  selector:\n    matchLabels:\n      app: client\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n      labels:\n        app: client\n    spec:\n      containers:\n      - name: curl\n        image: curlimages/curl\n        command: [ \"sh\", \"-c\", \"--\" ]\n        args: [ \"sleep infinity\" ]\n"
  },
  {
    "path": "test/integration/deep/dualstack/testdata/ipfamilies-server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n)\n\ntype (\n\tipv4Handler struct{}\n\tipv6Handler struct{}\n)\n\nfunc (ipv4Handler) ServeHTTP(w http.ResponseWriter, _ *http.Request) {\n\tfmt.Fprintf(w, \"IPv4\\n\")\n}\n\nfunc (ipv6Handler) ServeHTTP(w http.ResponseWriter, _ *http.Request) {\n\tfmt.Fprintf(w, \"IPv6\\n\")\n}\n\nfunc main() {\n\tlog.Print(\"Server started\")\n\n\tgo func() {\n\t\tln, err := net.Listen(\"tcp4\", \"0.0.0.0:8080\")\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tsrv := &http.Server{Handler: ipv4Handler{}}\n\t\tlog.Fatal(srv.Serve(ln))\n\t}()\n\n\tln, err := net.Listen(\"tcp6\", \"[::]:8080\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tsrv := &http.Server{Handler: ipv6Handler{}}\n\tlog.Fatal(srv.Serve(ln))\n}\n"
  },
  {
    "path": "test/integration/deep/egress/egress_test.go",
    "content": "package egress\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\n//////////////////////\n///   TEST SETUP   ///\n//////////////////////\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until control plane is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)\n\tos.Exit(m.Run())\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\nfunc TestEgressHttp(t *testing.T) {\n\tctx := context.Background()\n\tout, err := TestHelper.LinkerdRun(\"inject\", \"testdata/proxy.yaml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"unexpected error\", err)\n\t}\n\n\tTestHelper.WithDataPlaneNamespace(ctx, \"egress-test\", map[string]string{}, t, func(t *testing.T, ns string) {\n\n\t\tout, err = TestHelper.KubectlApply(out, ns)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v output:\\n%s\", err, out)\n\t\t}\n\n\t\terr = TestHelper.CheckPods(ctx, ns, \"egress-test\", 1)\n\t\tif err != nil {\n\t\t\t//nolint:errorlint\n\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t} else {\n\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t}\n\t\t}\n\n\t\ttestCase := func(url, methodToUse string) {\n\t\t\ttestName := fmt.Sprintf(\"Can use egress to send %s request to (%s)\", methodToUse, url)\n\t\t\tt.Run(testName, func(t *testing.T) {\n\t\t\t\tcmd := []string{\n\t\t\t\t\t\"-n\", ns, \"exec\", \"deploy/egress-test\", \"-c\", \"http-egress\",\n\t\t\t\t\t\"--\", \"curl\", \"-sko\", \"/dev/null\", \"-w\", \"%{http_code}\", \"-X\", methodToUse, url,\n\t\t\t\t}\n\t\t\t\tout, err := TestHelper.Kubectl(\"\", cmd...)\n\t\t\t\tif err != nil {\n\t\t\t\t\ttestutil.AnnotatedFatalf(t, fmt.Sprintf(\"failed to exec %s\", cmd), \"failed to exec %s: %s (%s)\", cmd, err, out)\n\t\t\t\t}\n\n\t\t\t\tvar status int\n\t\t\t\t_, err = fmt.Sscanf(out, \"%d\", &status)\n\t\t\t\tif err != nil {\n\t\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to parse status code\", \"failed to parse status code (%s): %s\", out, err)\n\t\t\t\t}\n\n\t\t\t\tif status < 100 || status >= 500 {\n\t\t\t\t\ttestutil.Fatalf(t, \"got HTTP error code: %d\\n\", status)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\tsupportedProtocols := []string{\"http\", \"https\"}\n\t\tmethods := []string{\"GET\", \"POST\"}\n\t\tfor _, protocolToUse := range supportedProtocols {\n\t\t\tfor _, methodToUse := range methods {\n\t\t\t\tserviceName := fmt.Sprintf(\"%s://www.linkerd.io\", protocolToUse)\n\t\t\t\ttestCase(serviceName, methodToUse)\n\t\t\t}\n\t\t}\n\n\t\t// Test egress for a domain with fewer than 3 segments.\n\t\ttestCase(\"http://linkerd.io\", \"GET\")\n\n\t})\n}\n"
  },
  {
    "path": "test/integration/deep/egress/testdata/proxy.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: egress-test\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: egress-test\n  template:\n    metadata:\n      labels:\n        app: egress-test\n    spec:\n      containers:\n      - name: http-egress\n        image: cr.l5d.io/linkerd/debug:edge-20.9.2\n"
  },
  {
    "path": "test/integration/deep/endpoints/endpoints_test.go",
    "content": "package endpoints\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\nvar TestHelper *testutil.TestHelper\n\ntype testCase struct {\n\tname       string\n\tauthority  string\n\texpectedRE string\n\tns         string\n}\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until control plane is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)\n\tos.Exit(m.Run())\n}\n\nfunc TestGoodEndpoints(t *testing.T) {\n\tctx := context.Background()\n\tcontrolNs := TestHelper.GetLinkerdNamespace()\n\n\tTestHelper.WithDataPlaneNamespace(ctx, \"endpoints-test\", map[string]string{}, t, func(t *testing.T, ns string) {\n\t\tout, err := TestHelper.Kubectl(\"\", \"apply\", \"-f\", \"testdata/nginx.yaml\", \"-n\", ns)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v output:\\n%s\", err, out)\n\t\t}\n\n\t\terr = TestHelper.CheckPods(ctx, ns, \"nginx\", 1)\n\t\tif err != nil {\n\t\t\t//nolint:errorlint\n\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t} else {\n\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t}\n\t\t}\n\n\t\tendpointCases := createTestCaseTable(controlNs, ns)\n\t\tfor _, endpointCase := range endpointCases {\n\t\t\ttestName := fmt.Sprintf(\"expect endpoints created for %s\", endpointCase.name)\n\n\t\t\tt.Run(testName, func(t *testing.T) {\n\t\t\t\terr = testutil.RetryFor(5*time.Second, func() error {\n\t\t\t\t\tout, err = TestHelper.LinkerdRun(\"diagnostics\", \"endpoints\", endpointCase.authority, \"-ojson\")\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to get endpoints for %s: %w\", endpointCase.authority, err)\n\t\t\t\t\t}\n\n\t\t\t\t\tre := regexp.MustCompile(endpointCase.expectedRE)\n\t\t\t\t\tif !re.MatchString(out) {\n\t\t\t\t\t\treturn fmt.Errorf(\"endpoint data does not match pattern\\nexpected output:\\n%s\\nactual:\\n%s\", endpointCase.expectedRE, out)\n\t\t\t\t\t}\n\n\t\t\t\t\tmatches := re.FindStringSubmatch(out)\n\t\t\t\t\tif len(matches) < 2 {\n\t\t\t\t\t\treturn fmt.Errorf(\"invalid endpoint data\\nexpected: \\n%s\\nactual: \\n%s\", endpointCase.expectedRE, out)\n\t\t\t\t\t}\n\n\t\t\t\t\tnamespaceMatch := matches[1]\n\t\t\t\t\tif namespaceMatch != endpointCase.ns {\n\t\t\t\t\t\treturn fmt.Errorf(\"endpoint namespace does not match\\nexpected: %s, actual: %s\", endpointCase.ns, namespaceMatch)\n\t\t\t\t\t}\n\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\ttestutil.AnnotatedErrorf(t, \"unexpected error\", \"unexpected error: %v\", err)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t})\n}\n\n// TODO: when #3004 gets fixed, add a negative test for mismatched ports\nfunc TestBadEndpoints(t *testing.T) {\n\t_, stderr, err := TestHelper.PipeToLinkerdRun(\"\", \"diagnostics\", \"endpoints\", \"foo\")\n\tif err == nil {\n\t\ttestutil.AnnotatedFatalf(t, \"was expecting an error\", \"was expecting an error: %v\", err)\n\t}\n\tstderrOut := strings.Split(stderr, \"\\n\")\n\tif len(stderrOut) == 0 {\n\t\ttestutil.AnnotatedFatalf(t, \"unexpected output\", \"unexpected output: %s\", stderr)\n\t}\n\tif stderrOut[0] != \"Destination API error: Invalid authority: foo\" {\n\t\ttestutil.AnnotatedErrorf(t, \"unexpected error string\", \"unexpected error string: %s\", stderrOut[0])\n\t}\n}\n\nfunc createTestCaseTable(controlNs, endpointNs string) []testCase {\n\treturn []testCase{\n\t\t{\n\t\t\tname:      \"linkerd-dst\",\n\t\t\tauthority: fmt.Sprintf(\"linkerd-dst.%s.svc.cluster.local:8086\", controlNs),\n\t\t\texpectedRE: `\\[\n  \\{\n    \"namespace\": \"(\\S*)\",\n    \"ip\": \"[a-f0-9.:]+\",\n    \"port\": 8086,\n    \"pod\": \"linkerd-destination\\-[a-f0-9]+\\-[a-z0-9]+\",\n    \"service\": \"linkerd-dst\\.\\S*\",\n    \"weight\": \\d+,\n    \"http2\": \\{(?s).*\\},\n    \"labels\": \\{(?s).*\\}\n  \\}\n\\]`,\n\t\t\tns: controlNs,\n\t\t},\n\t\t{\n\t\t\tname:      \"linkerd-identity\",\n\t\t\tauthority: fmt.Sprintf(\"linkerd-identity.%s.svc.cluster.local:8080\", controlNs),\n\t\t\texpectedRE: `\\[\n  \\{\n    \"namespace\": \"(\\S*)\",\n    \"ip\": \"[a-f0-9.:]+\",\n    \"port\": 8080,\n    \"pod\": \"linkerd-identity\\-[a-f0-9]+\\-[a-z0-9]+\",\n    \"service\": \"linkerd-identity\\.\\S*\",\n    \"weight\": \\d+,\n    \"http2\": \\{(?s).*\\},\n    \"labels\": \\{(?s).*\\}\n  \\}\n\\]`,\n\t\t\tns: controlNs,\n\t\t},\n\t\t{\n\t\t\tname:      \"linkerd-proxy-injector\",\n\t\t\tauthority: fmt.Sprintf(\"linkerd-proxy-injector.%s.svc.cluster.local:443\", controlNs),\n\t\t\texpectedRE: `\\[\n  \\{\n    \"namespace\": \"(\\S*)\",\n    \"ip\": \"[a-f0-9.:]+\",\n    \"port\": 8443,\n    \"pod\": \"linkerd-proxy-injector-[a-f0-9]+\\-[a-z0-9]+\",\n    \"service\": \"linkerd-proxy-injector\\.\\S*\",\n    \"weight\": \\d+,\n    \"http2\": \\{(?s).*\\},\n    \"labels\": \\{(?s).*\\}\n  \\}\n\\]`,\n\t\t\tns: controlNs,\n\t\t},\n\t\t{\n\t\t\tname:      \"nginx\",\n\t\t\tauthority: fmt.Sprintf(\"nginx.%s.svc.cluster.local:8080\", endpointNs),\n\t\t\texpectedRE: `\\[\n  \\{\n    \"namespace\": \"(\\S*)\",\n    \"ip\": \"[a-f0-9.:]+\",\n    \"port\": 8080,\n    \"pod\": \"nginx-[a-f0-9]+\\-[a-z0-9]+\",\n    \"service\": \"nginx\\.\\S*\",\n    \"weight\": \\d+,\n    \"http2\": \\{(?s).*\\},\n    \"labels\": \\{(?s).*\\}\n  \\}\n\\]`,\n\t\t\tns: endpointNs,\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "test/integration/deep/endpoints/testdata/nginx.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx\n  labels:\n    app: nginx\nspec:\n  selector:\n    matchLabels:\n      app: nginx\n  replicas: 1\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: \"enabled\"\n      labels:\n        app: nginx\n    spec:\n      containers:\n        - name: nginx\n          image: nginx:alpine\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: nginx\nspec:\n  ports:\n  - name: service\n    port: 8080\n  selector:\n    app: nginx \n"
  },
  {
    "path": "test/integration/deep/hostname/assertions.go",
    "content": "package hostname\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/linkerd/linkerd2/testutil/prommatch\"\n)\n\nvar (\n\tauthorityRE = regexp.MustCompile(`[a-zA-Z0-9\\-]+\\.[a-zA-Z0-9\\-]+\\.svc\\.cluster\\.local:[0-9]+`)\n\thostnameRE  = regexp.MustCompile(`[a-zA-Z0-9\\-]+`)\n)\n\n// hasOutboundHTTPRequestWithHostname checks there is a series matching:\n//\n//\trequest_total{\n//\t  route_namespace=\"\",\n//\t  route_name=\"http\",\n//\t  route_kind=\"default\",\n//\t  route_group=\"\",\n//\t  hostname=~\"[a-zA-Z0-9]+\"\n//\t}\nfunc hasOutboundHTTPRequestWithHostname(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\"outbound_http_route_request_duration_seconds_count\",\n\t\tprommatch.Labels{\n\t\t\t\"route_namespace\": prommatch.Equals(\"\"),\n\t\t\t\"route_name\":      prommatch.Equals(\"http\"),\n\t\t\t\"route_kind\":      prommatch.Equals(\"default\"),\n\t\t\t\"route_group\":     prommatch.Equals(\"\"),\n\t\t\t\"hostname\":        prommatch.Like(hostnameRE),\n\t\t},\n\t\tprommatch.HasPositiveValue())\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check of against the provided metrics: %w\", err)\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"expected to find HTTP hostname outbound requests \\n%s\", metrics)\n\t}\n\treturn nil\n}\n\n// hasOutboundHTTPRequestWithoutHostname checks there is a series matching:\n//\n//\trequest_total{\n//\t  route_namespace=\"\",\n//\t  route_name=\"http\",\n//\t  route_kind=\"default\",\n//\t  route_group=\"\",\n//\t  hostname=\"\"\n//\t}\nfunc hasOutboundHTTPRequestWithoutHostname(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\"outbound_http_route_request_duration_seconds_count\",\n\t\tprommatch.Labels{\n\t\t\t\"route_namespace\": prommatch.Equals(\"\"),\n\t\t\t\"route_name\":      prommatch.Equals(\"http\"),\n\t\t\t\"route_kind\":      prommatch.Equals(\"default\"),\n\t\t\t\"route_group\":     prommatch.Equals(\"\"),\n\t\t\t\"hostname\":        prommatch.Equals(\"\"),\n\t\t},\n\t\tprommatch.HasPositiveValue())\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check of against the provided metrics: %w\", err)\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"expected to find HTTP outbound requests \\n%s\", metrics)\n\t}\n\treturn nil\n}\n\n// hasInboundTCPTrafficWithTLS checks there is a series matching:\n//\n//\ttcp_open_total{\n//\t  direction=\"inbound\",\n//\t  peer=\"src\",\n//\t  tls=\"true\",\n//\t  client_id=\"default.${ns}.serviceaccount.identity.linkerd.cluster.local\",\n//\t  srv_kind=\"default\",\n//\t  srv_name=\"all-unauthenticated\",\n//\t  target_addr=~\"[0-9\\.]+:[0-9]+\",\n//\t  target_ip=~\"[0-9\\.]+\"\n//\t}\nfunc hasInboundTCPTrafficWithTLS(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\n\t\t\"tcp_open_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\": prommatch.Equals(\"inbound\"),\n\t\t\t\"peer\":      prommatch.Equals(\"src\"),\n\t\t\t\"tls\":       prommatch.Equals(\"true\"),\n\t\t\t\"client_id\": prommatch.Equals(fmt.Sprintf(\"default.%s.serviceaccount.identity.linkerd.cluster.local\", ns)),\n\t\t\t\"srv_kind\":  prommatch.Equals(\"default\"),\n\t\t\t\"srv_name\":  prommatch.Equals(\"all-unauthenticated\"),\n\t\t},\n\t\tprommatch.TargetAddrLabels(),\n\t\tprommatch.HasPositiveValue(),\n\t)\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check of against the provided metrics: %w\", err)\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"failed to find expected metric for inbound TLS TCP traffic\\n%s\", metrics)\n\t}\n\treturn nil\n}\n\n// hasOutboundTCPWithTLSAndAuthority checks there is a series matching:\n//\n//\ttcp_open_total{\n//\t  direction=\"outbound\",\n//\t  peer=\"dst\",\n//\t  tls=\"true\",\n//\t  target_addr=~\"[0-9\\.]+:[0-9]+\",\n//\t  authority=~\"[a-zA-Z\\-]+\\.[a-zA-Z\\-]+\\.svc\\.cluster\\.local:[0-9]+\"\n//\t}\nfunc hasOutboundTCPWithTLSAndAuthority(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\"tcp_open_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\": prommatch.Equals(\"outbound\"),\n\t\t\t\"peer\":      prommatch.Equals(\"dst\"),\n\t\t\t\"tls\":       prommatch.Equals(\"true\"),\n\t\t\t\"authority\": prommatch.Like(authorityRE),\n\t\t},\n\t\tprommatch.TargetAddrLabels(),\n\t\tprommatch.HasPositiveValue())\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check against the provided metrics: %w\", err)\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"failed to find expected metric for outbound TLS TCP traffic\\n%s\", metrics)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "test/integration/deep/hostname/hostname_test.go",
    "content": "package hostname\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n\tv1 \"k8s.io/api/core/v1\"\n)\n\nvar TestHelper *testutil.TestHelper\n\nvar hostnameClientTemplate = template.Must(template.New(\"hostname_client.yaml\").ParseFiles(\"testdata/hostname_client.yaml\"))\n\nvar (\n\tdisabledApp = \"disabled\"\n\tdisabledSC  = \"slow-cooker-disabled\"\n\tenabledApp  = \"enabled\"\n\tenabledSC   = \"slow-cooker-enabled\"\n)\n\ntype testCase struct {\n\tname      string\n\tappName   string\n\tappChecks []check\n\tscName    string\n\tscChecks  []check\n}\n\ntype check func(metrics, ns string) error\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until control plane is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)\n\tos.Exit(m.Run())\n}\n\n// clientTemplateArgs is a struct that contains the arguments to be supplied\n// to the deployment template hostname_client.yaml.\ntype clientTemplateArgs struct {\n\tServiceCookerDisabledTargetHost string\n\tServiceCookerEnabledTargetHost  string\n}\n\nfunc serviceName(n string) string {\n\treturn fmt.Sprintf(\"svc-%s\", n)\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\nfunc TestHostnameCalledByServiceTarget(t *testing.T) {\n\tctx := context.Background()\n\tTestHelper.WithDataPlaneNamespace(ctx, \"hostname-called-by-service-name-test\", map[string]string{}, t, func(t *testing.T, hostnameNs string) {\n\t\tchecks := func(c ...check) []check { return c }\n\n\t\tif err := deployApplications(hostnameNs); err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"failed to deploy applications\", err)\n\t\t}\n\t\twaitForAppDeploymentReady(t, hostnameNs)\n\n\t\ttmplArgs := clientTemplateArgs{\n\t\t\tServiceCookerDisabledTargetHost: serviceName(disabledApp),\n\t\t\tServiceCookerEnabledTargetHost:  serviceName(enabledApp),\n\t\t}\n\t\tif err := deployTemplate(hostnameNs, hostnameClientTemplate, tmplArgs); err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"failed to deploy client pods\", err)\n\t\t}\n\t\twaitForClientDeploymentReady(t, hostnameNs)\n\n\t\trunTests(ctx, t, hostnameNs, []testCase{\n\t\t\t{\n\t\t\t\tname:   \"calling a meshed service with hostname metrics disabled\",\n\t\t\t\tscName: disabledSC,\n\t\t\t\tscChecks: checks(\n\t\t\t\t\thasOutboundHTTPRequestWithoutHostname,\n\t\t\t\t\thasOutboundTCPWithTLSAndAuthority,\n\t\t\t\t),\n\t\t\t\tappName:   disabledApp,\n\t\t\t\tappChecks: checks(hasInboundTCPTrafficWithTLS),\n\t\t\t},\n\t\t})\n\t\trunTests(ctx, t, hostnameNs, []testCase{\n\t\t\t{\n\t\t\t\tname:   \"calling a meshed service with hostname metrics enabled\",\n\t\t\t\tscName: enabledSC,\n\t\t\t\tscChecks: checks(\n\t\t\t\t\thasOutboundHTTPRequestWithHostname,\n\t\t\t\t\thasOutboundTCPWithTLSAndAuthority,\n\t\t\t\t),\n\t\t\t\tappName:   enabledApp,\n\t\t\t\tappChecks: checks(hasInboundTCPTrafficWithTLS),\n\t\t\t},\n\t\t})\n\t})\n}\n\nfunc waitForAppDeploymentReady(t *testing.T, hostnameNs string) {\n\tTestHelper.WaitRollout(t, map[string]testutil.DeploySpec{\n\t\tdisabledApp: {\n\t\t\tNamespace: hostnameNs,\n\t\t\tReplicas:  1,\n\t\t},\n\t\tenabledApp: {\n\t\t\tNamespace: hostnameNs,\n\t\t\tReplicas:  1,\n\t\t},\n\t})\n}\n\nfunc waitForClientDeploymentReady(t *testing.T, hostnameNs string) {\n\tTestHelper.WaitRollout(t, map[string]testutil.DeploySpec{\n\t\tdisabledSC: {\n\t\t\tNamespace: hostnameNs,\n\t\t\tReplicas:  1,\n\t\t},\n\t\tenabledSC: {\n\t\t\tNamespace: hostnameNs,\n\t\t\tReplicas:  1,\n\t\t},\n\t})\n}\n\nfunc runTests(ctx context.Context, t *testing.T, ns string, tcs []testCase) {\n\tt.Helper()\n\tfor _, tc := range tcs {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\terr := testutil.RetryFor(30*time.Second, func() error {\n\t\t\t\tif err := checkPodMetrics(ctx, ns, tc.scName, tc.scChecks); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to check metrics for client pod: %w\", err)\n\t\t\t\t}\n\t\t\t\tif tc.appName == \"\" {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tif err := checkPodMetrics(ctx, ns, tc.appName, tc.appChecks); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to check metrics for app pod: %w\", err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected metric for pod\", \"unexpected metric for pod: %s\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc checkPodMetrics(ctx context.Context, hostnameNs string, podAppLabel string, checks []check) error {\n\tpods, err := TestHelper.GetPods(ctx, hostnameNs, map[string]string{\"app\": podAppLabel})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting pods for label 'app: %q': %w\", podAppLabel, err)\n\t}\n\tif len(pods) == 0 {\n\t\treturn fmt.Errorf(\"no pods found for label 'app: %q'\", podAppLabel)\n\t}\n\tmetrics, err := getPodMetrics(pods[0], hostnameNs)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting metrics for pod %q: %w\", pods[0].Name, err)\n\t}\n\tfor _, check := range checks {\n\t\tif err := check(metrics, hostnameNs); err != nil {\n\t\t\treturn fmt.Errorf(\"validation of pod metrics failed: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc deployApplications(ns string) error {\n\tout, err := TestHelper.Kubectl(\"\", \"apply\", \"-f\", \"testdata/hostname_application.yaml\", \"-n\", ns)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed apply deployment file %q: %w\", out, err)\n\t}\n\treturn nil\n}\n\nfunc deployTemplate(ns string, tmpl *template.Template, templateArgs interface{}) error {\n\tbb := &bytes.Buffer{}\n\tif err := tmpl.Execute(bb, templateArgs); err != nil {\n\t\treturn fmt.Errorf(\"failed to write deployment template: %w\", err)\n\t}\n\tout, err := TestHelper.KubectlApply(bb.String(), ns)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed apply deployment file %q: %w\", out, err)\n\t}\n\treturn nil\n}\n\nfunc getPodMetrics(pod v1.Pod, ns string) (string, error) {\n\tpodName := fmt.Sprintf(\"pod/%s\", pod.Name)\n\tcmd := []string{\"diagnostics\", \"proxy-metrics\", \"--namespace\", ns, podName}\n\tmetrics, err := TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn metrics, nil\n}\n"
  },
  {
    "path": "test/integration/deep/hostname/testdata/hostname_application.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: disabled\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: disabled\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n      labels:\n        app: disabled\n    spec:\n      containers:\n      - name: app\n        image: buoyantio/bb:v0.0.7\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=disabled\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc-disabled\n  labels:\n    app: svc-disabled\nspec:\n  selector:\n    app: disabled\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: enabled\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: enabled\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n      labels:\n        app: enabled\n    spec:\n      containers:\n      - name: app\n        image: buoyantio/bb:v0.0.7\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=enabled\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc-enabled\n  labels:\n    app: svc-enabled\nspec:\n  selector:\n    app: enabled\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080"
  },
  {
    "path": "test/integration/deep/hostname/testdata/hostname_client.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: slow-cooker-disabled\nspec:\n  selector:\n    matchLabels:\n      app: slow-cooker-disabled\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: \"enabled\"\n      labels:\n        app: slow-cooker-disabled\n    spec:\n      containers:\n      - name: slow-cooker-opdisabledque\n        image: buoyantio/slow_cooker:1.3.0\n        args:\n        - -qps=1\n        - -metric-addr=0.0.0.0:9999\n        - http://{{ .ServiceCookerDisabledTargetHost}}:8080\n        ports:\n        - containerPort: 9999\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: slow-cooker-enabled\nspec:\n  selector:\n    matchLabels:\n      app: slow-cooker-enabled\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: \"enabled\"\n        config.linkerd.io/proxy-metrics-hostname-labels: \"true\"\n      labels:\n        app: slow-cooker-enabled\n    spec:\n      containers:\n      - name: slow-cooker-opaque\n        image: buoyantio/slow_cooker:1.3.0\n        args:\n        - -qps=1\n        - -metric-addr=0.0.0.0:9999\n        - http://{{ .ServiceCookerEnabledTargetHost}}:8080\n        ports:\n        - containerPort: 9999"
  },
  {
    "path": "test/integration/deep/install_test.go",
    "content": "package deeptest\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\n//////////////////////\n///   TEST SETUP   ///\n//////////////////////\n\nvar (\n\tTestHelper *testutil.TestHelper\n)\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\tos.Exit(m.Run())\n}\n\nfunc TestInstallCalico(t *testing.T) {\n\tif !TestHelper.CNI() {\n\t\treturn\n\t}\n\n\t// install Calico as per instructions on\n\t// https://k3d.io/v5.8.3/usage/advanced/calico/#1-create-the-cluster-without-flannel\n\t// there's a lot of waiting involved here as the various components come up, so the easiest\n\t// is to retry the steps until they succeed\n\tvar out string\n\terr := testutil.RetryFor(time.Minute, func() error {\n\t\tvar err error\n\t\tout, err = TestHelper.Kubectl(\"\", []string{\"apply\", \"-f\", \"https://raw.githubusercontent.com/projectcalico/calico/v3.31.0/manifests/tigera-operator.yaml\"}...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tdeploys := map[string]testutil.DeploySpec{\n\t\t\t\"tigera-operator\": {\n\t\t\t\tNamespace: \"tigera-operator\",\n\t\t\t\tReplicas:  1,\n\t\t\t},\n\t\t}\n\t\tTestHelper.WaitRollout(t, deploys)\n\n\t\tout, err = TestHelper.Kubectl(\"\", []string{\"apply\", \"-f\", \"https://raw.githubusercontent.com/projectcalico/calico/v3.31.0/manifests/custom-resources.yaml\"}...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, system := range []string{\"apiserver\", \"calico\", \"goldmane\", \"ippools\", \"whisker\"} {\n\t\t\tout, err = TestHelper.Kubectl(\"\", \"wait\", \"--for=condition=available\", \"--timeout=120s\", \"tigerastatus\", system)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to install calico\", \"failed to install calico: %s: %s\", err, out)\n\t}\n}\n\nfunc TestInstallCNIPlugin(t *testing.T) {\n\tif !TestHelper.CNI() {\n\t\treturn\n\t}\n\n\t// install the CNI plugin in the cluster\n\tvar (\n\t\tcmd  = \"install-cni\"\n\t\targs = []string{\n\t\t\t\"--use-wait-flag\",\n\t\t\t\"--cni-log-level=debug\",\n\t\t\t// For Flannel (k3d's default CNI) the following settings are required.\n\t\t\t// For Calico the default ones are fine.\n\t\t\t// \"--dest-cni-net-dir=/var/lib/rancher/k3s/agent/etc/cni/net.d\",\n\t\t\t// \"--dest-cni-bin-dir=/bin\",\n\t\t}\n\t)\n\n\texec := append([]string{cmd}, args...)\n\tout, err := TestHelper.LinkerdRun(exec...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install-cni' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApply(out, \"\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\t// perform a linkerd check with --linkerd-cni-enabled\n\ttimeout := time.Minute\n\terr = testutil.RetryFor(timeout, func() error {\n\t\tout, err = TestHelper.LinkerdRun(\"check\", \"--pre\", \"--linkerd-cni-enabled\", \"--wait=5m\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"'linkerd check' command timed-out (%s)\", timeout), err)\n\t}\n}\n\n// TestInstall will install the linkerd control plane to be used in the rest of\n// the deep suite tests.\nfunc TestInstall(t *testing.T) {\n\terr := TestHelper.InstallGatewayAPI()\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"failed to install gateway-api\", err)\n\t}\n\n\t// Install CRDs\n\tcmd := []string{\n\t\t\"install\",\n\t\t\"--crds\",\n\t\t\"--controller-log-level\", \"debug\",\n\t\t\"--set\", fmt.Sprintf(\"proxy.image.version=%s\", TestHelper.GetVersion()),\n\t\t\"--set\", \"heartbeatSchedule=1 2 3 4 5\",\n\t}\n\n\t// Pipe cmd & args to `linkerd`\n\tout, err := TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithArgs(out)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\t// Install control-plane\n\tcmd = []string{\n\t\t\"install\",\n\t\t\"--controller-log-level\", \"debug\",\n\t\t\"--set\", fmt.Sprintf(\"proxy.image.version=%s\", TestHelper.GetVersion()),\n\t\t\"--set\", \"heartbeatSchedule=1 2 3 4 5\",\n\t}\n\n\t// If testing deep suite with CNI, set --cni-enabled to true\n\tif TestHelper.CNI() {\n\t\tcmd = append(cmd, \"--linkerd-cni-enabled\")\n\t}\n\n\tif TestHelper.NativeSidecar() {\n\t\tcmd = append(cmd, \"--set\", \"proxy.nativeSidecar=true\")\n\t}\n\n\tif TestHelper.DualStack() {\n\t\tcmd = append(cmd, \"--set\", \"disableIPv6=false\")\n\t}\n\n\t// Pipe cmd & args to `linkerd`\n\tout, err = TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithArgs(out)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\tout, err = TestHelper.LinkerdRun(\"check\", \"--wait=3m\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'linkerd check' command failed\",\n\t\t\t\"'linkerd check' command failed\\n%s\", out)\n\t}\n}\n"
  },
  {
    "path": "test/integration/deep/kind-dualstack.yml",
    "content": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  ipFamily: dual\n"
  },
  {
    "path": "test/integration/deep/kind-ipv6.yml",
    "content": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  ipFamily: ipv6\n"
  },
  {
    "path": "test/integration/deep/localhost/localhost_test.go",
    "content": "package localhost\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"os\"\n\t\"regexp\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n\t\"github.com/linkerd/linkerd2/testutil/prommatch\"\n)\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until control plane is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)\n\tos.Exit(m.Run())\n}\n\nvar (\n\tnginxPodRE  = regexp.MustCompile(`nginx.*`)\n\tnginxLabels = prommatch.Labels{\n\t\t\"direction\":             prommatch.Equals(\"outbound\"),\n\t\t\"authority\":             prommatch.Equals(\"nginx.linkerd-localhost-server-test.svc.cluster.local:8080\"),\n\t\t\"dst_deployment\":        prommatch.Equals(\"nginx\"),\n\t\t\"dst_namespace\":         prommatch.Equals(\"linkerd-localhost-server-test\"),\n\t\t\"dst_pod\":               prommatch.Like(nginxPodRE),\n\t\t\"dst_pod_template_hash\": prommatch.Any(),\n\t\t\"dst_service\":           prommatch.Equals(\"nginx\"),\n\t\t\"dst_serviceaccount\":    prommatch.Equals(\"default\"),\n\t}\n\trequestsToNGINXMatcher = prommatch.NewMatcher(\"request_total\",\n\t\tnginxLabels,\n\t\tprommatch.HasPositiveValue(),\n\t)\n\tfailedResponsesFromNGINXMatcher = prommatch.NewMatcher(\"response_total\",\n\t\tnginxLabels,\n\t\tprommatch.Labels{\n\t\t\t\"classification\": prommatch.Equals(\"failure\"),\n\t\t},\n\t\tprommatch.HasPositiveValue(),\n\t)\n\tsuccessResponsesMatcher = prommatch.NewMatcher(\"response_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\":      prommatch.Equals(\"outbound\"),\n\t\t\t\"classification\": prommatch.Equals(\"success\"),\n\t\t},\n\t\tprommatch.HasPositiveValue(),\n\t)\n\ttcpOpenMatcher = prommatch.NewMatcher(\"tcp_open_connections\",\n\t\tnginxLabels,\n\t\tprommatch.HasValueOf(0),\n\t)\n)\n\n// TestLocalhostServer creates an nginx deployment which listens on localhost\n// and a slow-cooker which attempts to send traffic to the nginx.  Since\n// slow-cooker should not be able to connect to nginx's localhost address,\n// these requests should fail.\nfunc TestLocalhostServer(t *testing.T) {\n\tctx := context.Background()\n\tnginx, err := TestHelper.LinkerdRun(\"inject\", \"testdata/nginx.yaml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"unexpected error\", err)\n\t}\n\tslowcooker, err := TestHelper.LinkerdRun(\"inject\", \"testdata/slow-cooker.yaml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"unexpected error\", err)\n\t}\n\n\tTestHelper.WithDataPlaneNamespace(ctx, \"localhost-server-test\", map[string]string{}, t, func(t *testing.T, ns string) {\n\n\t\tout, err := TestHelper.KubectlApply(nginx, ns)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v output:\\n%s\", err, out)\n\t\t}\n\t\tout, err = TestHelper.KubectlApply(slowcooker, ns)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v output:\\n%s\", err, out)\n\t\t}\n\n\t\tfor _, deploy := range []string{\"nginx\", \"slow-cooker\"} {\n\t\t\terr = TestHelper.CheckPods(ctx, ns, deploy, 1)\n\t\t\tif err != nil {\n\t\t\t\t//nolint:errorlint\n\t\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t\t} else {\n\t\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\terr = testutil.RetryFor(50*time.Second, func() error {\n\t\t\t// Use a short time window so that transient errors at startup\n\t\t\t// fall out of the window.\n\t\t\tmetrics, err := TestHelper.LinkerdRun(\"diagnostics\", \"proxy-metrics\", \"-n\", ns, \"deploy/slow-cooker\")\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected diagnostics error\",\n\t\t\t\t\t\"unexpected diagnostics error: %s\\n%s\", err, out)\n\t\t\t}\n\n\t\t\tm := prommatch.Suite{}.\n\t\t\t\tMustContain(\"requests from slowcooker to nginx\", requestsToNGINXMatcher).\n\t\t\t\tMustContain(\"failed responses returned to slowcooker from nginx\", failedResponsesFromNGINXMatcher).\n\t\t\t\tMustNotContain(\"success responses returned to slowcooker from nginx\", successResponsesMatcher).\n\t\t\t\tMustContain(\"zero open tcp connections to nginx\", tcpOpenMatcher)\n\n\t\t\tif err := m.CheckString(metrics); err != nil {\n\t\t\t\treturn fmt.Errorf(\"metrics check failed: %w\", err)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected stat output\", \"unexpected stat output: %v\", err)\n\t\t}\n\t})\n}\n\n// TestLocalhostRouting creates a pod with two containers: nginx and curl, and\n// tests traffic can be successfully routed when packets stay local. It will\n// test that the pod can send a request to itself successfully via its pod IP\n// (concrete address). And it will also test that a pod can send a request to\n// itself via its service IP (logical address).\nfunc TestLocalhostRouting(t *testing.T) {\n\tctx := context.Background()\n\tnginx, err := TestHelper.LinkerdRun(\"inject\", \"testdata/nginx-and-curl.yaml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"unexpected error\", err)\n\t}\n\n\tTestHelper.WithDataPlaneNamespace(ctx, \"localhost-routing-test\", map[string]string{}, t, func(t *testing.T, ns string) {\n\t\tout, err := TestHelper.KubectlApply(nginx, ns)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v output:\\n%s\", err, out)\n\t\t}\n\n\t\terr = TestHelper.CheckPods(ctx, ns, \"nginx\", 1)\n\t\tif err != nil {\n\t\t\t//nolint:errorlint\n\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t} else {\n\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t}\n\t\t}\n\n\t\tpods, err := TestHelper.GetPodsForDeployment(ctx, ns, \"nginx\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v\", err)\n\t\t}\n\n\t\tpodName := pods[0].ObjectMeta.Name\n\t\texecCommand := []string{\"exec\", \"-n\", ns, podName, \"-c\", \"curl\", \"--\", \"curl\", \"-w\", \"%{http_code}\", \"-so\", \"/dev/null\"}\n\t\tt.Run(\"Route to Concrete Address Over Loopback\", func(t *testing.T) {\n\t\t\tpodIP := pods[0].Status.PodIP\n\t\t\tif podIP == \"\" {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: no IP address found for %s/%s\", ns, podName)\n\t\t\t}\n\n\t\t\taddr, err := netip.ParseAddr(podIP)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"Invalid IP\", \"Invalid IP '%s': %s\", podIP, err)\n\t\t\t}\n\t\t\tif addr.Is6() {\n\t\t\t\tpodIP = fmt.Sprintf(\"[%s]\", podIP)\n\t\t\t}\n\t\t\turl := fmt.Sprintf(\"http://%s:80\", podIP)\n\n\t\t\tstatusCode, err := TestHelper.Kubectl(\"\", append(execCommand, url)...)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error received when calling 'kubectl exec'\", \"unexpected error received when calling 'kubectl exec': %v\", err)\n\t\t\t}\n\n\t\t\tif statusCode != \"200\" {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected http status code received\", \"unexpected http status code received: expected: '200', got: '%s'\", statusCode)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"Route to Logical Address Over Loopback\", func(t *testing.T) {\n\t\t\tstatusCode, err := TestHelper.Kubectl(\"\", append(execCommand, \"http://nginx-svc:80\")...)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error received when calling 'kubectl exec'\", \"unexpected error received when calling 'kubectl exec': %v\", err)\n\t\t\t}\n\n\t\t\tif statusCode != \"200\" {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected http status code received\", \"unexpected http status code received: expected: '200', got: '%s'\", statusCode)\n\t\t\t}\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "test/integration/deep/localhost/testdata/nginx-and-curl.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx\n  labels:\n    app: nginx\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n      - name: nginx\n        image: nginx:1.25.5\n        ports:\n        - containerPort: 80\n      - name: curl\n        image: curlimages/curl\n        command: [\"sleep\", \"infinite\"]\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: nginx-svc\nspec:\n  selector:\n    app: nginx\n  ports:\n    - name: http\n      protocol: TCP\n      port: 80\n"
  },
  {
    "path": "test/integration/deep/localhost/testdata/nginx.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: nginx-config\ndata:\n nginx.conf: |-\n    events {}\n    http {\n        server {\n          listen 127.0.0.1:8080;\n            location / {\n                return 200;\n            }\n        }\n    }\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx\n  labels:\n    app: nginx\nspec:\n  selector:\n    matchLabels:\n      app: nginx\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n        - name: nginx\n          image: nginx:alpine\n          volumeMounts:\n            - name: nginx-config\n              mountPath: /etc/nginx/nginx.conf\n              subPath: nginx.conf\n      volumes:\n        - name: nginx-config\n          configMap:\n            name: nginx-config\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: nginx\nspec:\n  ports:\n  - name: service\n    port: 8080\n  selector:\n    app: nginx"
  },
  {
    "path": "test/integration/deep/localhost/testdata/slow-cooker.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: slow-cooker\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: slow-cooker\n  template:\n    metadata:\n      labels:\n        app: slow-cooker\n    spec:\n      containers:\n      - name: slow-cooker\n        image: buoyantio/slow_cooker:1.3.0\n        command:\n        - \"/bin/sh\"\n        args:\n        - \"-c\"\n        - |\n          sleep 15 # wait for pods to start\n          /slow_cooker/slow_cooker -metric-addr 0.0.0.0:9999 http://nginx:8080\n        ports:\n        - containerPort: 9999\n"
  },
  {
    "path": "test/integration/deep/norelay/norelay_test.go",
    "content": "package norelay\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until control plane is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)\n\tos.Exit(m.Run())\n}\n\n// TestNoRelay verifies that hitting the proxy's outbound port doesn't result\n// in an open relay, by trying to leverage the l5d-dst-override header in an\n// ingress proxy.\nfunc TestNoRelay(t *testing.T) {\n\tctx := context.Background()\n\tdeployments := getDeployments(t)\n\tTestHelper.WithDataPlaneNamespace(ctx, \"norelay-test\", map[string]string{}, t, func(t *testing.T, ns string) {\n\t\tfor name, res := range deployments {\n\t\t\tout, err := TestHelper.KubectlApply(res, ns)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\",\n\t\t\t\t\t\"unexpected error with deployment %s: %v output:\\n%s\",\n\t\t\t\t\tname, err, out,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tfor name := range deployments {\n\t\t\terr := TestHelper.CheckPods(ctx, ns, name, 1)\n\t\t\tif err != nil {\n\t\t\t\t//nolint:errorlint\n\t\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t\t} else {\n\t\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\trelayIPPort := getPodIPPort(t, ns, \"app=server-relay\", 4140)\n\t\to, err := TestHelper.Kubectl(\n\t\t\t\"\", \"-n\", ns, \"exec\", \"deploy/client\",\n\t\t\t\"--\", \"curl\", \"-f\", \"-H\", \"l5d-dst-override: server-hello.\"+ns+\".svc.cluster.local:8080\", \"http://\"+relayIPPort,\n\t\t)\n\t\tif err == nil || err.Error() != \"exit status 22\" {\n\t\t\ttestutil.AnnotatedFatalf(t, \"no error or unexpected error returned\",\n\t\t\t\t\"no error or unexpected error returned: %s\\n%s\", o, err)\n\t\t}\n\t})\n}\n\n// TestRelay validates the previous test by running the same scenario but\n// forcing an open relay by changing the value of\n// LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS from 127.0.0.1:4140,[::1]:4140 to\n// 0.0.0.0:4140, which is not possible without manually changing the injected\n// proxy yaml\n//\n// We don't care if this behavior breaks--it's not a supported configuration.\n// However, this test is oddly useful in finding bugs in ingress-mode proxy\n// configurations, so we keep it around. ¯\\_(ツ)_/¯\nfunc TestRelay(t *testing.T) {\n\tctx := context.Background()\n\tdeployments := getDeployments(t)\n\n\t// account for both when IPv6 is enabled or disabled\n\tdeployments[\"server-relay\"] = strings.ReplaceAll(\n\t\tdeployments[\"server-relay\"],\n\t\t\"127.0.0.1:4140,[::1]:4140\",\n\t\t\"'[::]:4140'\",\n\t)\n\tdeployments[\"server-relay\"] = strings.ReplaceAll(\n\t\tdeployments[\"server-relay\"],\n\t\t\"127.0.0.1:4140\",\n\t\t\"0.0.0.0:4140\",\n\t)\n\n\tTestHelper.WithDataPlaneNamespace(ctx, \"relay-test\", map[string]string{}, t, func(t *testing.T, ns string) {\n\t\tfor name, res := range deployments {\n\t\t\tout, err := TestHelper.KubectlApply(res, ns)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\",\n\t\t\t\t\t\"unexpected error with deployment %s: %v output:\\n%s\",\n\t\t\t\t\tname, err, out,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\twaitForPods(t, ctx, ns, deployments)\n\t\trelayIPPort := getPodIPPort(t, ns, \"app=server-relay\", 4140)\n\n\t\t// Send a request to the outbound proxy port with a header that should route internally.\n\t\to, err := TestHelper.Kubectl(\n\t\t\t\"\", \"-n\", ns, \"exec\", \"deploy/client\",\n\t\t\t\"--\", \"curl\", \"-fsv\", \"-H\", \"l5d-dst-override: server-hello.\"+ns+\".svc.cluster.local:8080\", \"http://\"+relayIPPort,\n\t\t)\n\t\tif err != nil {\n\t\t\tlog, err := TestHelper.Kubectl(\n\t\t\t\t\"\", \"logs\",\n\t\t\t\t\"-n\", ns,\n\t\t\t\t\"-l\", \"app=server-relay\",\n\t\t\t\t\"-c\", \"linkerd-proxy\",\n\t\t\t\t\"--tail=1000\",\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tlog = fmt.Sprintf(\"failed to retrieve server-relay logs: %s\", err)\n\t\t\t}\n\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error returned\",\n\t\t\t\t\"unexpected error returned: %s\\n%s\\n---\\n%s\", o, err, log)\n\t\t}\n\t\tif !strings.Contains(o, \"HELLO-FROM-SERVER\") {\n\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected response returned\",\n\t\t\t\t\"unexpected response returned: %s\", o)\n\t\t}\n\t})\n}\n\nfunc getDeployments(t *testing.T) map[string]string {\n\tdeploys := make(map[string]string)\n\tvar err error\n\n\t// server-hello is injected normally\n\tdeploys[\"server-hello\"], err = TestHelper.LinkerdRun(\"inject\", \"testdata/server-hello.yml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"unexpected error\", err)\n\t}\n\n\t// server-relay is injected in ingress mode, manually\n\tdeploys[\"server-relay\"], err = TestHelper.LinkerdRun(\n\t\t\"inject\", \"--manual\", \"--ingress\",\n\t\t\"--proxy-log-level=linkerd=debug,info\",\n\t\t\"testdata/server-relay.yml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"unexpected error\", err)\n\t}\n\n\t// client is not injected\n\tdeploys[\"client\"], err = testutil.ReadFile(\"testdata/client.yml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to read 'client.yml'\",\n\t\t\t\"failed to read 'client.yml': %s\", err)\n\t}\n\n\treturn deploys\n}\n\nfunc getPodIPPort(t *testing.T, ns, selector string, port int) string {\n\tt.Helper()\n\tip, err := TestHelper.Kubectl(\n\t\t\"\", \"-n\", ns, \"get\", \"po\", \"-l\", selector,\n\t\t\"-o\", \"jsonpath='{range .items[*]}{@.status.podIP}{end}'\",\n\t)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to retrieve pod IP\",\n\t\t\t\"failed to retrieve pod IP: %s\", err)\n\t}\n\tip = strings.Trim(ip, \"'\")\n\tparsedIP := net.ParseIP(ip)\n\tif parsedIP == nil {\n\t\ttestutil.AnnotatedFatalf(t, \"invalid pod IP\",\n\t\t\t\"invalid pod IP: %s\", err)\n\t}\n\tif parsedIP.To4() != nil {\n\t\treturn fmt.Sprintf(\"%s:%d\", ip, port)\n\t}\n\treturn fmt.Sprintf(\"[%s]:%d\", ip, port)\n}\n\nfunc waitForPods(t *testing.T, ctx context.Context, ns string, deployments map[string]string) {\n\tt.Helper()\n\tfor name := range deployments {\n\t\terr := TestHelper.CheckPods(ctx, ns, name, 1)\n\t\tif err != nil {\n\t\t\t//nolint:errorlint\n\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t} else {\n\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/integration/deep/norelay/testdata/client.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: client\nspec:\n  selector:\n    matchLabels:\n      app: client\n  template:\n    metadata:\n      labels:\n        app: client\n    spec:\n      containers:\n      - name: curl\n        image: curlimages/curl\n        command: [ \"sh\", \"-c\", \"--\" ]\n        args: [ \"while true; do sleep 10; done;\" ]\n"
  },
  {
    "path": "test/integration/deep/norelay/testdata/server-hello.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: server-hello\nspec:\n  selector:\n    matchLabels:\n      app: server-hello\n  template:\n    metadata:\n      labels:\n        app: server-hello\n    spec:\n      containers:\n      - name: app\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=HELLO-FROM-SERVER\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: server-hello\nspec:\n  selector:\n    app: server-hello\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n"
  },
  {
    "path": "test/integration/deep/norelay/testdata/server-relay.yml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: server-relay\n  labels:\n    app: server-relay\nspec:\n  selector:\n    matchLabels:\n      app: server-relay\n  template:\n    metadata:\n      labels:\n        app: server-relay\n    spec:\n      containers:\n      - name: app\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=HELLO-FROM-RELAY\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: server-relay\nspec:\n  selector:\n    app: server-relay\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n"
  },
  {
    "path": "test/integration/deep/opaqueports/assertions.go",
    "content": "package opaqueports\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/linkerd/linkerd2/testutil/prommatch\"\n)\n\nvar (\n\tauthorityRE = regexp.MustCompile(`[a-zA-Z\\-]+\\.[a-zA-Z\\-]+\\.svc\\.cluster\\.local:[0-9]+`)\n)\n\n// hasNoOutboundHTTPRequest returns error if there is any\n// series matching request_total{direction=\"outbound\"}\nfunc hasNoOutboundHTTPRequest(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\"request_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\": prommatch.Equals(\"outbound\"),\n\t\t})\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check of against the provided metrics: %w\", err)\n\t}\n\tif ok {\n\t\treturn fmt.Errorf(\"expected not to find HTTP outbound requests \\n%s\", metrics)\n\t}\n\treturn nil\n}\n\n// hasOutboundHTTPRequestWithTLS checks there is a series matching:\n//\n//\trequest_total{\n//\t  direction=\"outbound\",\n//\t  target_addr=~\"[0-9\\.]+:[0-9]+\",\n//\t  target_ip=~\"[0-9.]+\",\n//\t  tls=\"true\",\n//\t  dst_namespace=\"default.${ns}.serviceaccount.identity.linkerd.cluster.local\",\n//\t  dst_serviceaccount=\"default\"\n//\t}\nfunc hasOutboundHTTPRequestWithTLS(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\"request_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\":          prommatch.Equals(\"outbound\"),\n\t\t\t\"tls\":                prommatch.Equals(\"true\"),\n\t\t\t\"server_id\":          prommatch.Equals(fmt.Sprintf(\"default.%s.serviceaccount.identity.linkerd.cluster.local\", ns)),\n\t\t\t\"dst_namespace\":      prommatch.Equals(ns),\n\t\t\t\"dst_serviceaccount\": prommatch.Equals(\"default\"),\n\t\t},\n\t\tprommatch.TargetAddrLabels(),\n\t\tprommatch.HasPositiveValue())\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check of against the provided metrics: %w\", err)\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"expected to find HTTP TLS outbound requests \\n%s\", metrics)\n\t}\n\treturn nil\n}\n\n// hasOutboundHTTPRequestNoTLS checks there is a series matching:\n//\n//\trequest_total{\n//\t  direction=\"outbound\",\n//\t  target_addr=~\"[0-9\\.]+:[0-9]+\",\n//\t  target_ip=~\"[0-9.]+\",\n//\t  tls=\"no_identity\",\n//\t  no_tls_reason=\"not_provided_by_service_discovery\",\n//\t  dst_namespace=\"default.${ns}.serviceaccount.identity.linkerd.cluster.local\",\n//\t  dst_serviceaccount=\"default\"\n//\t}\nfunc hasOutboundHTTPRequestNoTLS(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\"request_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\":          prommatch.Equals(\"outbound\"),\n\t\t\t\"tls\":                prommatch.Equals(\"no_identity\"),\n\t\t\t\"no_tls_reason\":      prommatch.Equals(\"not_provided_by_service_discovery\"),\n\t\t\t\"dst_namespace\":      prommatch.Equals(ns),\n\t\t\t\"dst_serviceaccount\": prommatch.Equals(\"default\"),\n\t\t},\n\t\tprommatch.TargetAddrLabels(),\n\t\tprommatch.HasPositiveValue())\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check of against the provided metrics: %w\", err)\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"expected to find HTTP non-TLS outbound requests\\n%s\", metrics)\n\t}\n\treturn nil\n}\n\n// hasInboundTCPTrafficWithTLS checks there is a series matching:\n//\n//\ttcp_open_total{\n//\t  direction=\"inbound\",\n//\t  peer=\"src\",\n//\t  tls=\"true\",\n//\t  client_id=\"default.${ns}.serviceaccount.identity.linkerd.cluster.local\",\n//\t  srv_kind=\"default\",\n//\t  srv_name=\"all-unauthenticated\",\n//\t  target_addr=~\"[0-9\\.]+:[0-9]+\",\n//\t  target_ip=~\"[0-9\\.]+\"\n//\t}\nfunc hasInboundTCPTrafficWithTLS(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\n\t\t\"tcp_open_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\": prommatch.Equals(\"inbound\"),\n\t\t\t\"peer\":      prommatch.Equals(\"src\"),\n\t\t\t\"tls\":       prommatch.Equals(\"true\"),\n\t\t\t\"client_id\": prommatch.Equals(fmt.Sprintf(\"default.%s.serviceaccount.identity.linkerd.cluster.local\", ns)),\n\t\t\t\"srv_kind\":  prommatch.Equals(\"default\"),\n\t\t\t\"srv_name\":  prommatch.Equals(\"all-unauthenticated\"),\n\t\t},\n\t\tprommatch.TargetAddrLabels(),\n\t\tprommatch.HasPositiveValue(),\n\t)\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check of against the provided metrics: %w\", err)\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"failed to find expected metric for inbound TLS TCP traffic\\n%s\", metrics)\n\t}\n\treturn nil\n}\n\n// hasOutboundTCPWithAuthorityAndNoTLS checks there is a series matching:\n//\n//\ttcp_open_total{\n//\t  direction=\"outbound\",\n//\t  peer=\"dst\",\n//\t  tls=\"no_identity\",\n//\t  no_tls_reason=\"not_provided_by_service_discovery\",\n//\t  authority=~\"[a-zA-Z\\-]+\\.[a-zA-Z\\-]+\\.svc\\.cluster\\.local:[0-9]+\"\n//\t}\nfunc hasOutboundTCPWithAuthorityAndNoTLS(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\"tcp_open_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\":     prommatch.Equals(\"outbound\"),\n\t\t\t\"peer\":          prommatch.Equals(\"dst\"),\n\t\t\t\"tls\":           prommatch.Equals(\"no_identity\"),\n\t\t\t\"no_tls_reason\": prommatch.Equals(\"not_provided_by_service_discovery\"),\n\t\t\t\"authority\":     prommatch.Like(authorityRE),\n\t\t},\n\t\tprommatch.HasPositiveValue())\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check of against the provided metrics: %w\", err)\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"failed to find expected metric for outbound non-TLS TCP traffic\\n%s\", metrics)\n\t}\n\treturn nil\n}\n\n// hasOutboundTCPWithNoTLSAndNoAuthority checks there is a series matching:\n//\n//\ttcp_open_total{\n//\t  direction=\"outbound\",\n//\t  peer=\"dst\",\n//\t  tls=\"no_identity\",\n//\t  no_tls_reason=\"not_provided_by_service_discovery\",\n//\t  authority=\"\"\n//\t}\nfunc hasOutboundTCPWithNoTLSAndNoAuthority(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\"tcp_open_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\":     prommatch.Equals(\"outbound\"),\n\t\t\t\"peer\":          prommatch.Equals(\"dst\"),\n\t\t\t\"tls\":           prommatch.Equals(\"no_identity\"),\n\t\t\t\"no_tls_reason\": prommatch.Equals(\"not_provided_by_service_discovery\"),\n\t\t\t\"authority\":     prommatch.Absent(),\n\t\t})\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check of against the provided metrics: %w\", err)\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"failed to find expected metric for outbound non-TLS TCP traffic\\n%s\", metrics)\n\t}\n\treturn nil\n}\n\n// hasOutboundTCPWithTLSAndAuthority checks there is a series matching:\n//\n//\ttcp_open_total{\n//\t  direction=\"outbound\",\n//\t  peer=\"dst\",\n//\t  tls=\"true\",\n//\t  target_addr=~\"[0-9\\.]+:[0-9]+\",\n//\t  authority=~\"[a-zA-Z\\-]+\\.[a-zA-Z\\-]+\\.svc\\.cluster\\.local:[0-9]+\"\n//\t}\nfunc hasOutboundTCPWithTLSAndAuthority(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\"tcp_open_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\": prommatch.Equals(\"outbound\"),\n\t\t\t\"peer\":      prommatch.Equals(\"dst\"),\n\t\t\t\"tls\":       prommatch.Equals(\"true\"),\n\t\t\t\"authority\": prommatch.Like(authorityRE),\n\t\t},\n\t\tprommatch.TargetAddrLabels(),\n\t\tprommatch.HasPositiveValue())\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check against the provided metrics: %w\", err)\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"failed to find expected metric for outbound TLS TCP traffic\\n%s\", metrics)\n\t}\n\treturn nil\n}\n\n// hasOutboundTCPWithTLSAndNoAuthority checks there is a series matching:\n//\n//\ttcp_open_total{\n//\t  direction=\"outbound\",\n//\t  peer=\"dst\",\n//\t  tls=\"true\",\n//\t  target_addr=~\"[0-9\\.]+:[0-9]+\",\n//\t  authority=\"\"\n//\t}\nfunc hasOutboundTCPWithTLSAndNoAuthority(metrics, ns string) error {\n\tm := prommatch.NewMatcher(\"tcp_open_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\": prommatch.Equals(\"outbound\"),\n\t\t\t\"peer\":      prommatch.Equals(\"dst\"),\n\t\t\t\"tls\":       prommatch.Equals(\"true\"),\n\t\t\t\"authority\": prommatch.Absent(),\n\t\t},\n\t\tprommatch.TargetAddrLabels(),\n\t\tprommatch.HasPositiveValue())\n\tok, err := m.HasMatchInString(metrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run a check of against the provided metrics: %w\", err)\n\t}\n\tif !ok {\n\t\treturn fmt.Errorf(\"failed to find expected metric for outbound TLS TCP traffic\\n%s\", metrics)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "test/integration/deep/opaqueports/opaque_ports_test.go",
    "content": "package opaqueports\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n\tv1 \"k8s.io/api/core/v1\"\n)\n\nvar TestHelper *testutil.TestHelper\n\nvar opaquePortsClientTemplate = template.Must(template.New(\"opaque_ports_client.yaml\").ParseFiles(\"testdata/opaque_ports_client.yaml\"))\n\nvar (\n\topaquePodApp         = \"opaque-pod\"\n\topaquePodSC          = \"slow-cooker-opaque-pod\"\n\topaqueSvcApp         = \"opaque-service\"\n\topaqueSvcSC          = \"slow-cooker-opaque-service\"\n\topaqueUnmeshedSvcApp = \"opaque-unmeshed\"\n\topaqueUnmeshedSvcPod = \"opaque-unmeshed-svc\"\n\topaqueUnmeshedSvcSC  = \"slow-cooker-opaque-unmeshed-svc\"\n)\n\ntype testCase struct {\n\tname      string\n\tappName   string\n\tappChecks []check\n\tscName    string\n\tscChecks  []check\n}\n\ntype check func(metrics, ns string) error\n\nfunc checks(c ...check) []check { return c }\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until control plane is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)\n\tos.Exit(m.Run())\n}\n\n// clientTemplateArgs is a struct that contains the arguments to be supplied\n// to the deployment template opaque_ports_client.yaml.\ntype clientTemplateArgs struct {\n\tServiceCookerOpaqueServiceTargetHost     string\n\tServiceCookerOpaquePodTargetHost         string\n\tServiceCookerOpaqueUnmeshedSVCTargetHost string\n}\n\nfunc serviceName(n string) string {\n\treturn fmt.Sprintf(\"svc-%s\", n)\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\nfunc TestOpaquePortsCalledByServiceTarget(t *testing.T) {\n\tctx := context.Background()\n\tTestHelper.WithDataPlaneNamespace(ctx, \"opaque-ports-called-by-service-name-test\", map[string]string{}, t, func(t *testing.T, opaquePortsNs string) {\n\t\tchecks := func(c ...check) []check { return c }\n\n\t\tif err := deployApplications(opaquePortsNs); err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"failed to deploy applications\", err)\n\t\t}\n\t\twaitForAppDeploymentReady(t, opaquePortsNs)\n\n\t\ttmplArgs := clientTemplateArgs{\n\t\t\tServiceCookerOpaqueServiceTargetHost:     serviceName(opaqueSvcApp),\n\t\t\tServiceCookerOpaquePodTargetHost:         serviceName(opaquePodApp),\n\t\t\tServiceCookerOpaqueUnmeshedSVCTargetHost: serviceName(opaqueUnmeshedSvcApp),\n\t\t}\n\t\tif err := deployTemplate(opaquePortsNs, opaquePortsClientTemplate, tmplArgs); err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"failed to deploy client pods\", err)\n\t\t}\n\t\twaitForClientDeploymentReady(t, opaquePortsNs)\n\n\t\trunTests(ctx, t, opaquePortsNs, []testCase{\n\t\t\t{\n\t\t\t\tname:   \"calling a meshed service when opaque annotation is on receiving pod\",\n\t\t\t\tscName: opaquePodSC,\n\t\t\t\tscChecks: checks(\n\t\t\t\t\thasNoOutboundHTTPRequest,\n\t\t\t\t\thasOutboundTCPWithTLSAndNoAuthority,\n\t\t\t\t),\n\t\t\t\tappName:   opaquePodApp,\n\t\t\t\tappChecks: checks(hasInboundTCPTrafficWithTLS),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"calling a meshed service when opaque annotation is on receiving service\",\n\t\t\t\tscName: opaqueSvcSC,\n\t\t\t\tscChecks: checks(\n\t\t\t\t\thasNoOutboundHTTPRequest,\n\t\t\t\t\thasOutboundTCPWithTLSAndAuthority,\n\t\t\t\t),\n\t\t\t\tappName:   opaqueSvcApp,\n\t\t\t\tappChecks: checks(hasInboundTCPTrafficWithTLS),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"calling an unmeshed service when opaque annotation is on service\",\n\t\t\t\tscName: opaqueUnmeshedSvcSC,\n\t\t\t\tscChecks: checks(\n\t\t\t\t\thasNoOutboundHTTPRequest,\n\t\t\t\t\thasOutboundTCPWithAuthorityAndNoTLS,\n\t\t\t\t),\n\t\t\t},\n\t\t})\n\t})\n}\n\nfunc TestOpaquePortsCalledByPodTarget(t *testing.T) {\n\tctx := context.Background()\n\tTestHelper.WithDataPlaneNamespace(ctx, \"opaque-ports-called-by-pod-ip-test\", map[string]string{}, t, func(t *testing.T, opaquePortsNs string) {\n\n\t\tif err := deployApplications(opaquePortsNs); err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"failed to deploy applications\", err)\n\t\t}\n\t\twaitForAppDeploymentReady(t, opaquePortsNs)\n\n\t\ttmplArgs, err := templateArgsPodIP(ctx, opaquePortsNs)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"failed to fetch pod IPs\", err)\n\t\t}\n\n\t\tif err := deployTemplate(opaquePortsNs, opaquePortsClientTemplate, tmplArgs); err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"failed to deploy client pods\", err)\n\t\t}\n\t\twaitForClientDeploymentReady(t, opaquePortsNs)\n\n\t\trunTests(ctx, t, opaquePortsNs, []testCase{\n\t\t\t{\n\t\t\t\tname:   \"calling a meshed service when opaque annotation is on receiving pod\",\n\t\t\t\tscName: opaquePodSC,\n\t\t\t\tscChecks: checks(\n\t\t\t\t\thasNoOutboundHTTPRequest,\n\t\t\t\t\thasOutboundTCPWithTLSAndNoAuthority,\n\t\t\t\t),\n\t\t\t\tappName:   opaquePodApp,\n\t\t\t\tappChecks: checks(hasInboundTCPTrafficWithTLS),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"calling a meshed service when opaque annotation is on receiving service\",\n\t\t\t\tscName: opaqueSvcSC,\n\t\t\t\tscChecks: checks(\n\t\t\t\t\t// We call pods directly, so annotation on a service is ignored.\n\t\t\t\t\thasOutboundHTTPRequestWithTLS,\n\t\t\t\t\t// No authority here, because we are calling the pod directly.\n\t\t\t\t\thasOutboundTCPWithTLSAndNoAuthority,\n\t\t\t\t),\n\t\t\t\tappName:   opaqueSvcApp,\n\t\t\t\tappChecks: checks(hasInboundTCPTrafficWithTLS),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"calling an unmeshed service\",\n\t\t\t\tscName: opaqueUnmeshedSvcSC,\n\t\t\t\tscChecks: checks(\n\t\t\t\t\t// We call pods directly, so annotation on a service is ignored.\n\t\t\t\t\thasOutboundHTTPRequestNoTLS,\n\t\t\t\t\t// No authority here, because we are calling the pod directly.\n\t\t\t\t\thasOutboundTCPWithNoTLSAndNoAuthority,\n\t\t\t\t),\n\t\t\t},\n\t\t})\n\t})\n}\n\nfunc waitForAppDeploymentReady(t *testing.T, opaquePortsNs string) {\n\tTestHelper.WaitRollout(t, map[string]testutil.DeploySpec{\n\t\topaquePodApp: {\n\t\t\tNamespace: opaquePortsNs,\n\t\t\tReplicas:  1,\n\t\t},\n\t\topaqueSvcApp: {\n\t\t\tNamespace: opaquePortsNs,\n\t\t\tReplicas:  1,\n\t\t},\n\t\topaqueUnmeshedSvcPod: {\n\t\t\tNamespace: opaquePortsNs,\n\t\t\tReplicas:  1,\n\t\t},\n\t})\n}\n\nfunc waitForClientDeploymentReady(t *testing.T, opaquePortsNs string) {\n\tTestHelper.WaitRollout(t, map[string]testutil.DeploySpec{\n\t\topaquePodSC: {\n\t\t\tNamespace: opaquePortsNs,\n\t\t\tReplicas:  1,\n\t\t},\n\t\topaqueSvcSC: {\n\t\t\tNamespace: opaquePortsNs,\n\t\t\tReplicas:  1,\n\t\t},\n\t\topaqueUnmeshedSvcSC: {\n\t\t\tNamespace: opaquePortsNs,\n\t\t\tReplicas:  1,\n\t\t},\n\t})\n}\n\nfunc templateArgsPodIP(ctx context.Context, ns string) (clientTemplateArgs, error) {\n\topaquePodSCPodIP, err := getPodIPByAppLabel(ctx, ns, opaquePodApp)\n\tif err != nil {\n\t\treturn clientTemplateArgs{}, fmt.Errorf(\"failed to fetch pod IP for %q: %w\", opaquePodApp, err)\n\t}\n\topaqueSvcSCPodIP, err := getPodIPByAppLabel(ctx, ns, opaqueSvcApp)\n\tif err != nil {\n\t\treturn clientTemplateArgs{}, fmt.Errorf(\"failed to fetch pod IP for %q: %w\", opaqueSvcApp, err)\n\t}\n\topaqueUnmeshedSvcPodIP, err := getPodIPByAppLabel(ctx, ns, opaqueUnmeshedSvcPod)\n\tif err != nil {\n\t\treturn clientTemplateArgs{}, fmt.Errorf(\"failed to fetch pod IP for %q: %w\", opaqueUnmeshedSvcPod, err)\n\t}\n\treturn clientTemplateArgs{\n\t\tServiceCookerOpaquePodTargetHost:         opaquePodSCPodIP,\n\t\tServiceCookerOpaqueServiceTargetHost:     opaqueSvcSCPodIP,\n\t\tServiceCookerOpaqueUnmeshedSVCTargetHost: opaqueUnmeshedSvcPodIP,\n\t}, nil\n}\n\nfunc runTests(ctx context.Context, t *testing.T, ns string, tcs []testCase) {\n\tt.Helper()\n\tfor _, tc := range tcs {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\terr := testutil.RetryFor(30*time.Second, func() error {\n\t\t\t\tif err := checkPodMetrics(ctx, ns, tc.scName, tc.scChecks); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to check metrics for client pod: %w\", err)\n\t\t\t\t}\n\t\t\t\tif tc.appName == \"\" {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tif err := checkPodMetrics(ctx, ns, tc.appName, tc.appChecks); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to check metrics for app pod: %w\", err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected metric for pod\", \"unexpected metric for pod: %s\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc checkPodMetrics(ctx context.Context, opaquePortsNs string, podAppLabel string, checks []check) error {\n\tpods, err := TestHelper.GetPods(ctx, opaquePortsNs, map[string]string{\"app\": podAppLabel})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting pods for label 'app: %q': %w\", podAppLabel, err)\n\t}\n\tif len(pods) == 0 {\n\t\treturn fmt.Errorf(\"no pods found for label 'app: %q'\", podAppLabel)\n\t}\n\tmetrics, err := getPodMetrics(pods[0], opaquePortsNs)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting metrics for pod %q: %w\", pods[0].Name, err)\n\t}\n\tfor _, check := range checks {\n\t\tif err := check(metrics, opaquePortsNs); err != nil {\n\t\t\treturn fmt.Errorf(\"validation of pod metrics failed: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc deployApplications(ns string) error {\n\tout, err := TestHelper.Kubectl(\"\", \"apply\", \"-f\", \"testdata/opaque_ports_application.yaml\", \"-n\", ns)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed apply deployment file %q: %w\", out, err)\n\t}\n\treturn nil\n}\n\nfunc deployTemplate(ns string, tmpl *template.Template, templateArgs interface{}) error {\n\tbb := &bytes.Buffer{}\n\tif err := tmpl.Execute(bb, templateArgs); err != nil {\n\t\treturn fmt.Errorf(\"failed to write deployment template: %w\", err)\n\t}\n\tout, err := TestHelper.KubectlApply(bb.String(), ns)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed apply deployment file %q: %w\", out, err)\n\t}\n\treturn nil\n}\n\nfunc getPodMetrics(pod v1.Pod, ns string) (string, error) {\n\tpodName := fmt.Sprintf(\"pod/%s\", pod.Name)\n\tcmd := []string{\"diagnostics\", \"proxy-metrics\", \"--namespace\", ns, podName}\n\tmetrics, err := TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn metrics, nil\n}\n\nfunc getPodIPByAppLabel(ctx context.Context, ns string, app string) (string, error) {\n\tlabels := map[string]string{\"app\": app}\n\tpods, err := TestHelper.GetPods(ctx, ns, labels)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get pod by labels %v: %w\", labels, err)\n\t}\n\tif len(pods) == 0 {\n\t\treturn \"\", fmt.Errorf(\"no pods found for labels %v\", labels)\n\t}\n\treturn pods[0].Status.PodIP, nil\n}\n"
  },
  {
    "path": "test/integration/deep/opaqueports/testdata/opaque_ports_application.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: opaque-pod\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: opaque-pod\n  template:\n    metadata:\n      labels:\n        app: opaque-pod\n      annotations:\n        linkerd.io/inject: \"enabled\"\n        config.linkerd.io/opaque-ports: \"8080\"\n    spec:\n      containers:\n      - name: app\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=opaque-pod\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc-opaque-pod\n  labels:\n    app: svc-opaque-pod\nspec:\n  selector:\n    app: opaque-pod\n  clusterIP: None\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: opaque-service\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: opaque-service\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: \"enabled\"\n      labels:\n        app: opaque-service\n    spec:\n      containers:\n      - name: app\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=opaque-service\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc-opaque-service\n  labels:\n    app: svc-opaque-service\n  annotations:\n    config.linkerd.io/opaque-ports: \"8080\"\nspec:\n  selector:\n    app: opaque-service\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: opaque-unmeshed-svc\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: opaque-unmeshed-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: disabled\n      labels:\n        app: opaque-unmeshed-svc\n    spec:\n      containers:\n      - name: app\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=opaque-unmeshed-svc\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: svc-opaque-unmeshed\n  labels:\n    app: svc-opaque-unmeshed\n  annotations:\n    config.linkerd.io/opaque-ports: \"8080\"\nspec:\n  selector:\n    app: opaque-unmeshed-svc\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n"
  },
  {
    "path": "test/integration/deep/opaqueports/testdata/opaque_ports_client.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: slow-cooker-opaque-service\nspec:\n  selector:\n    matchLabels:\n      app: slow-cooker-opaque-service\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: \"enabled\"\n      labels:\n        app: slow-cooker-opaque-service\n    spec:\n      containers:\n      - name: slow-cooker-opaque-service\n        image: buoyantio/slow_cooker:1.3.0\n        args:\n        - -qps=1\n        - -metric-addr=0.0.0.0:9999\n        - http://{{ .ServiceCookerOpaqueServiceTargetHost}}:8080\n        ports:\n        - containerPort: 9999\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: slow-cooker-opaque-pod\nspec:\n  selector:\n    matchLabels:\n      app: slow-cooker-opaque-pod\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: \"enabled\"\n      labels:\n        app: slow-cooker-opaque-pod\n    spec:\n      containers:\n      - name: slow-cooker-opaque-pod\n        image: buoyantio/slow_cooker:1.3.0\n        args:\n        - -qps=1\n        - -metric-addr=0.0.0.0:9999\n        - http://{{ .ServiceCookerOpaquePodTargetHost}}:8080\n        ports:\n        - containerPort: 9999\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: slow-cooker-opaque-unmeshed-svc\nspec:\n  selector:\n    matchLabels:\n      app: slow-cooker-opaque-unmeshed-svc\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: \"enabled\"\n      labels:\n        app: slow-cooker-opaque-unmeshed-svc\n    spec:\n      containers:\n      - name: slow-cooker-opaque-unmeshed-svc\n        image: buoyantio/slow_cooker:1.3.0\n        args:\n        - -qps=1\n        - -metric-addr=0.0.0.0:9999\n        - http://{{ .ServiceCookerOpaqueUnmeshedSVCTargetHost}}:8080\n        ports:\n        - containerPort: 9999\n"
  },
  {
    "path": "test/integration/deep/serviceaccounts/serviceaccounts_test.go",
    "content": "package serviceaccounts\n\nimport (\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until control plane is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)\n\tos.Exit(m.Run())\n}\n\n// namesMatch checks if all the expectedServiceAccountNames are present in the given list,\n// The passed argument list is allowed to contain extra members.\nfunc namesMatch(names []string) bool {\n\tfor _, expectedname := range healthcheck.ExpectedServiceAccountNames {\n\t\tfound := false\n\t\tfor _, name := range names {\n\t\t\tif expectedname == name {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc TestServiceAccountsMatch(t *testing.T) {\n\texpectedNames := healthcheck.ExpectedServiceAccountNames\n\n\tres, err := TestHelper.Kubectl(\"\",\n\t\t\"-n\", TestHelper.GetLinkerdNamespace(),\n\t\t\"get\", \"serviceaccounts\",\n\t\t\"--output\", \"name\",\n\t)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"Error retrieving list of service accounts\",\n\t\t\t\"error retrieving list of service accounts: %s\", err)\n\t}\n\tnames := strings.Split(strings.TrimSpace(res), \"\\n\")\n\tvar saNames []string\n\tfor _, name := range names {\n\t\tsaNames = append(saNames, strings.TrimPrefix(name, \"serviceaccount/\"))\n\t}\n\t// disregard `default` and `linkerd-heartbeat`\n\tif len(saNames) < len(expectedNames) || !namesMatch(saNames) {\n\t\ttestutil.Fatalf(t, \"the service account list doesn't match the expected list: %s\", expectedNames)\n\t}\n}\n"
  },
  {
    "path": "test/integration/deep/skipports/skip_ports_test.go",
    "content": "package skipports\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n\t\"github.com/linkerd/linkerd2/testutil/prommatch\"\n)\n\nvar TestHelper *testutil.TestHelper\n\nvar (\n\tskipPortsNs          = \"skip-ports-test\"\n\temojivotoDeployments = []string{\"emoji\", \"vote-bot\", \"voting\", \"web\"}\n)\n\nfunc secureRequestMatcher(dst string) *prommatch.Matcher {\n\treturn prommatch.NewMatcher(\"request_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\":   prommatch.Equals(\"outbound\"),\n\t\t\t\"tls\":         prommatch.Equals(\"true\"),\n\t\t\t\"dst_service\": prommatch.Equals(dst),\n\t\t})\n}\n\nfunc insecureRequestMatcher(dst string) *prommatch.Matcher {\n\treturn prommatch.NewMatcher(\"request_total\",\n\t\tprommatch.Labels{\n\t\t\t\"direction\":   prommatch.Equals(\"outbound\"),\n\t\t\t\"tls\":         prommatch.Equals(\"no_identity\"),\n\t\t\t\"dst_service\": prommatch.Equals(dst),\n\t\t})\n}\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until control plane is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)\n\tos.Exit(m.Run())\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\nfunc TestSkipInboundPorts(t *testing.T) {\n\n\tif os.Getenv(\"RUN_ARM_TEST\") != \"\" {\n\t\tt.Skip(\"Skipping Skip Inbound Ports test. TODO: Build multi-arch emojivoto\")\n\t}\n\n\tctx := context.Background()\n\tTestHelper.WithDataPlaneNamespace(ctx, skipPortsNs, nil, t, func(t *testing.T, ns string) {\n\t\tout, err := TestHelper.LinkerdRun(\"inject\", \"--manual\", \"testdata/skip_ports_application.yaml\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"'linkerd inject' command failed\", err)\n\t\t}\n\t\tout, err = TestHelper.KubectlApply(out, ns)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t\t}\n\n\t\t// Check all emojivoto deployments are up and running\n\t\tfor _, deploy := range emojivotoDeployments {\n\t\t\tif err := TestHelper.CheckPods(ctx, ns, deploy, 1); err != nil {\n\t\t\t\t//nolint:errorlint\n\t\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t\t} else {\n\t\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tt.Run(\"check webapp metrics\", func(t *testing.T) {\n\t\t\t// Wait for slow-cookers to start sending requests by using a short\n\t\t\t// time window through RetryFor.\n\t\t\terr := testutil.RetryFor(30*time.Second, func() error {\n\t\t\t\tpods, err := TestHelper.GetPods(ctx, ns, map[string]string{\"app\": \"web-svc\"})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error getting pods\\n%w\", err)\n\t\t\t\t}\n\n\t\t\t\tpodName := fmt.Sprintf(\"pod/%s\", pods[0].Name)\n\t\t\t\tcmd := []string{\"diagnostics\", \"proxy-metrics\", \"--namespace\", ns, podName}\n\n\t\t\t\tmetrics, err := TestHelper.LinkerdRun(cmd...)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error getting metrics for pod\\n%w\", err)\n\t\t\t\t}\n\t\t\t\ts := prommatch.Suite{}.\n\t\t\t\t\tMustContain(\"secure requests to emoji-svc\", secureRequestMatcher(\"emoji-svc\")).\n\t\t\t\t\tMustContain(\"insecure requests to voting-svc\", insecureRequestMatcher(\"voting-svc\")).\n\t\t\t\t\tMustNotContain(\"insecure requests to emoji-svc\", insecureRequestMatcher(\"emoji-svc\")).\n\t\t\t\t\tMustNotContain(\"secure requests to voting-svc\", secureRequestMatcher(\"voting-svc\"))\n\t\t\t\tif err := s.CheckString(metrics); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error matching metrics\\n%w\", err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v\", err)\n\t\t\t}\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "test/integration/deep/skipports/testdata/skip_ports_application.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: emoji\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: voting\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: web\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: emoji-svc\nspec:\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n  - name: prom\n    port: 8801\n    targetPort: 8801\n  selector:\n    app: emoji-svc\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: voting-svc\nspec:\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n  - name: prom\n    port: 8801\n    targetPort: 8801\n  selector:\n    app: voting-svc\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: web-svc\nspec:\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n  selector:\n    app: web-svc\n  type: ClusterIP\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: emoji\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v11\n  name: emoji\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: emoji-svc\n      version: v11\n  template:\n    metadata:\n      labels:\n        app: emoji-svc\n        version: v11\n    spec:\n      containers:\n      - env:\n        - name: GRPC_PORT\n          value: \"8080\"\n        - name: PROM_PORT\n          value: \"8801\"\n        image: docker.l5d.io/buoyantio/emojivoto-emoji-svc:v11\n        name: emoji-svc\n        ports:\n        - containerPort: 8080\n          name: grpc\n        - containerPort: 8801\n          name: prom\n        resources:\n          requests:\n            cpu: 100m\n      serviceAccountName: emoji\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: vote-bot\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v11\n  name: vote-bot\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: vote-bot\n      version: v11\n  template:\n    metadata:\n      labels:\n        app: vote-bot\n        version: v11\n    spec:\n      containers:\n      - command:\n        - emojivoto-vote-bot\n        env:\n        - name: WEB_HOST\n          value: web-svc:80\n        image: docker.l5d.io/buoyantio/emojivoto-web:v11\n        name: vote-bot\n        resources:\n          requests:\n            cpu: 10m\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: voting\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v11\n  name: voting\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: voting-svc\n      version: v11\n  template:\n    metadata:\n      annotations:\n        config.linkerd.io/skip-inbound-ports: \"8080\"\n      labels:\n        app: voting-svc\n        version: v11\n    spec:\n      containers:\n      - env:\n        - name: GRPC_PORT\n          value: \"8080\"\n        - name: PROM_PORT\n          value: \"8801\"\n        image: docker.l5d.io/buoyantio/emojivoto-voting-svc:v11\n        name: voting-svc\n        ports:\n        - containerPort: 8080\n          name: grpc\n        - containerPort: 8801\n          name: prom\n        resources:\n          requests:\n            cpu: 100m\n      serviceAccountName: voting\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: web\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v11\n  name: web\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n      version: v11\n  template:\n    metadata:\n      labels:\n        app: web-svc\n        version: v11\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"8080\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: docker.l5d.io/buoyantio/emojivoto-web:v11\n        name: web-svc\n        ports:\n        - containerPort: 8080\n          name: http\n        resources:\n          requests:\n            cpu: 100m\n      serviceAccountName: web\n"
  },
  {
    "path": "test/integration/external/externalissuer/external_issuer_test.go",
    "content": "package externalissuer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\nvar TestHelper *testutil.TestHelper\n\nconst (\n\tTestAppBackendDeploymentName = \"backend\"\n\tTestAppNamespaceSuffix       = \"external-issuer-app-test\"\n)\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until viz extension pods are running\n\tTestHelper.WaitUntilDeployReady(testutil.ExternalVizDeployReplicas)\n\tos.Exit(m.Run())\n}\n\nfunc TestExternalIssuer(t *testing.T) {\n\tctx := context.Background()\n\tTestHelper.WithDataPlaneNamespace(ctx, TestAppNamespaceSuffix, map[string]string{}, t, func(t *testing.T, testNamespace string) {\n\t\tverifyInstallApp(ctx, t)\n\t\tverifyAppWorksBeforeCertRotation(t)\n\t\tverifyRotateExternalCerts(ctx, t)\n\t\tverifyIdentityServiceReloadsIssuerCert(t)\n\t\tensureNewCSRSAreServed()\n\t\tverifyAppWorksAfterCertRotation(t)\n\t})\n}\n\nfunc verifyInstallApp(ctx context.Context, t *testing.T) {\n\tout, err := TestHelper.LinkerdRun(\"inject\", \"--manual\", \"testdata/external_issuer_application.yaml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd inject' command failed\", err)\n\t}\n\n\tprefixedNs := TestHelper.GetTestNamespace(TestAppNamespaceSuffix)\n\tout, err = TestHelper.KubectlApply(out, prefixedNs)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\", \"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\tif err := TestHelper.CheckPods(ctx, prefixedNs, TestAppBackendDeploymentName, 1); err != nil {\n\t\t//nolint:errorlint\n\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t} else {\n\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t}\n\t}\n\n\tif err := TestHelper.CheckPods(ctx, prefixedNs, \"slow-cooker\", 1); err != nil {\n\t\t//nolint:errorlint\n\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t} else {\n\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t}\n\t}\n}\n\nfunc checkAppWoks(t *testing.T, timeout time.Duration) error {\n\treturn testutil.RetryFor(timeout, func() error {\n\t\targs := []string{\"viz\", \"stat\", \"deploy\", \"-n\", TestHelper.GetTestNamespace(TestAppNamespaceSuffix), \"--from\", \"deploy/slow-cooker\", \"-t\", \"1m\"}\n\t\tout, err := TestHelper.LinkerdRun(args...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trowStats, err := testutil.ParseRows(out, 1, 8)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tstat := rowStats[TestAppBackendDeploymentName]\n\t\tif stat.Success != \"100.00%\" {\n\t\t\tt.Fatalf(\"Expected no errors in test app but got [%s] success rate\", stat.Success)\n\t\t}\n\t\treturn nil\n\t})\n\n}\n\nfunc verifyAppWorksBeforeCertRotation(t *testing.T) {\n\ttimeout := 40 * time.Second\n\terr := checkAppWoks(t, timeout)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"timed-out while ensuring test app works (before cert rotation) (%s)\", timeout), err)\n\t}\n}\n\nfunc verifyRotateExternalCerts(ctx context.Context, t *testing.T) {\n\t// We rotate the certificates here by simply grabbing\n\t// the key and cert values from the temporary secret we have\n\t// created\n\tsecretWithUpdatedData, err := TestHelper.KubernetesHelper.GetSecret(ctx, TestHelper.GetLinkerdNamespace(), k8s.IdentityIssuerSecretName+\"-new\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to fetch new secret data resource\", \"failed to fetch new secret data resource: %s\", err)\n\t}\n\n\troots := secretWithUpdatedData.Data[k8s.IdentityIssuerTrustAnchorsNameExternal]\n\tcrt := secretWithUpdatedData.Data[corev1.TLSCertKey]\n\tkey := secretWithUpdatedData.Data[corev1.TLSPrivateKeyKey]\n\n\tif err = TestHelper.CreateTLSSecret(k8s.IdentityIssuerSecretName, string(roots), string(crt), string(key)); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to update linkerd-identity-issuer resource\", \"failed to update linkerd-identity-issuer resource: %s\", err)\n\t}\n}\n\nfunc verifyIdentityServiceReloadsIssuerCert(t *testing.T) {\n\t// check that the identity service has received an IssuerUpdated event\n\ttimeout := 90 * time.Second\n\terr := testutil.RetryFor(timeout, func() error {\n\t\tout, err := TestHelper.Kubectl(\"\",\n\t\t\t\"--namespace\", TestHelper.GetLinkerdNamespace(),\n\t\t\t\"get\", \"events\", \"--field-selector\", \"reason=IssuerUpdated\", \"-ojson\",\n\t\t)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedErrorf(t, \"'kubectl get events' command failed\", \"'kubectl get events' command failed with %s\\n%s\", err, out)\n\t\t}\n\n\t\tevents, err := testutil.ParseEvents(out)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif len(events) != 1 {\n\t\t\treturn fmt.Errorf(\"expected just one event but got %d\", len(events))\n\t\t}\n\n\t\texpectedEventMessage := \"Updated identity issuer\"\n\t\tissuerUpdatedEvent := events[0]\n\n\t\tif issuerUpdatedEvent.Message != expectedEventMessage {\n\t\t\treturn fmt.Errorf(\"expected event message [%s] but got [%s]\", expectedEventMessage, issuerUpdatedEvent.Message)\n\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"timed-out verifying identity svc reloads issuer cert (%s)\", timeout), err)\n\t}\n\n}\n\nfunc ensureNewCSRSAreServed() {\n\t// this is to ensure new certs have been issued by the identity service.\n\t// we know that this will happen because the issuance lifetime is set to 15s.\n\t// Possible improvement is to provide a more deterministic way of checking that.\n\t// Perhaps we can emit k8s events when the identity service processed a CSR.\n\ttime.Sleep(20 * time.Second)\n}\n\nfunc verifyAppWorksAfterCertRotation(t *testing.T) {\n\ttimeout := 40 * time.Second\n\terr := checkAppWoks(t, timeout)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"timed-out ensuring test app works (after cert rotation) (%s)\", timeout), err)\n\t}\n}\n"
  },
  {
    "path": "test/integration/external/externalissuer/testdata/external_issuer_application.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: backend\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: backend\n  template:\n    metadata:\n      labels:\n        app: backend\n    spec:\n      containers:\n      - name: backend\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=backend\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: backend-svc\nspec:\n  selector:\n    app: backend\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: slow-cooker\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: slow-cooker\n  template:\n    metadata:\n      labels:\n        app: slow-cooker\n    spec:\n      containers:\n      - name: slow-cooker\n        image: buoyantio/slow_cooker:1.3.0\n        command:\n        - \"/bin/sh\"\n        args:\n        - \"-c\"\n        - |\n          sleep 5 # wait for pods to start\n          /slow_cooker/slow_cooker http://backend-svc:8080\n        ports:\n        - containerPort: 9999\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: slow-cooker\nspec:\n  selector:\n    app: slow-cooker\n  ports:\n  - name: metrics\n    port: 9999\n    targetPort: 9999\n"
  },
  {
    "path": "test/integration/external/externalresources/rabbitmq_test.go",
    "content": "package externalresources\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until control plane pods are running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)\n\tos.Exit(m.Run())\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\nfunc TestRabbitMQDeploy(t *testing.T) {\n\tctx := context.Background()\n\tTestHelper.WithDataPlaneNamespace(ctx, \"rabbitmq-test\", map[string]string{}, t, func(t *testing.T, testNamespace string) {\n\t\tout, err := TestHelper.LinkerdRun(\"inject\", \"--manual\", \"testdata/rabbitmq-server.yaml\")\n\t\t// inject rabbitmq server\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'linkerd inject' command failed\", \"'linkerd inject' command failed: %s\", err)\n\t\t}\n\t\t// deploy rabbitmq server\n\t\t_, err = TestHelper.KubectlApply(out, testNamespace)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"kubectl apply command failed\", \"'kubectl apply' command failed: %s\", err)\n\t\t}\n\t\tif err := TestHelper.CheckPods(ctx, testNamespace, \"rabbitmq\", 1); err != nil {\n\t\t\t//nolint:errorlint\n\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out %s\", rce)\n\t\t\t} else {\n\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out %s\", err)\n\t\t\t}\n\t\t}\n\t\t// inject rabbitmq-client\n\t\tstdout, err := TestHelper.LinkerdRun(\"inject\", \"--manual\", \"testdata/rabbitmq-client.yaml\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'linkerd inject' command failed\", \"'linkerd inject' command failed: %s\", err)\n\t\t}\n\t\t// deploy rabbitmq client\n\t\t_, err = TestHelper.KubectlApply(stdout, testNamespace)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"kubectl apply command failed\", \"'kubectl apply' command failed: %s\", err)\n\t\t}\n\t\tif err := TestHelper.CheckPods(ctx, testNamespace, \"rabbitmq-client\", 1); err != nil {\n\t\t\t//nolint:errorlint\n\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out %s\", rce)\n\t\t\t} else {\n\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out %s\", err)\n\t\t\t}\n\t\t}\n\t\t// Verify client output\n\t\tgolden := \"check.rabbitmq.golden\"\n\t\ttimeout := 50 * time.Second\n\t\terr = testutil.RetryFor(timeout, func() error {\n\t\t\tout, err := TestHelper.Kubectl(\"\", \"-n\", testNamespace, \"logs\", \"-lapp=rabbitmq-client\", \"-crabbitmq-client\")\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"'kubectl logs -l app=rabbitmq-client -c rabbitmq-client' command failed\\n%w\", err)\n\t\t\t}\n\t\t\terr = TestHelper.ValidateOutput(out, golden)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"received unexpected output\\n%w\", err)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"'kubectl logs' command timed-out (%s)\", timeout), err)\n\t\t}\n\n\t})\n\n}\n"
  },
  {
    "path": "test/integration/external/externalresources/testdata/check.rabbitmq.golden",
    "content": "[OK] Create message queue MQTestQueue\n[OK] Published message. Message count: 1\n[OK] Consumed message. Message count: 0\n"
  },
  {
    "path": "test/integration/external/externalresources/testdata/rabbitmq-client.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: rabbitmq-client\n  name: rabbitmq-client\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: rabbitmq-client\n  template:\n    metadata:\n      labels:\n        app: rabbitmq-client\n    spec:\n      containers:\n        - image: ghcr.io/barkardk/rabbitmq-client:1.2.3\n          name: rabbitmq-client\n          env:\n            - name: RABBITMQ_AMQP_CONN_STR\n              value: \"amqp://guest:guest@rabbitmq:5672/\"\n          command:\n            - \"/bin/sh\"\n          args:\n            - \"-c\"\n            - |\n              sleep 20 # wait for pods to start\n              ./mq_test\n      restartPolicy: Always\n\n"
  },
  {
    "path": "test/integration/external/externalresources/testdata/rabbitmq-server.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: rabbitmq\n  name: rabbitmq\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: rabbitmq\n  template:\n    metadata:\n      labels:\n        app: rabbitmq\n    spec:\n      containers:\n        - image: rabbitmq:3.8.12-rc.3-management\n          name: rabbitmq\n          readinessProbe:\n            exec:\n              command: [\"rabbitmq-diagnostics\", \"ping\"]\n            initialDelaySeconds: 20\n            periodSeconds: 60\n            timeoutSeconds: 10\n          ports:\n            - containerPort: 15672\n            - containerPort: 5672\n\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app: rabbitmq\n  name: rabbitmq\nspec:\n  ports:\n    - name: http\n      port: 15672\n      protocol: TCP\n      targetPort: 15672\n    - name: amqp\n      port: 5672\n      protocol: TCP\n      targetPort: 5672\n  selector:\n    app: rabbitmq\n  type: ClusterIP\n"
  },
  {
    "path": "test/integration/external/install_test.go",
    "content": "package externaltest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\n//////////////////////\n///   TEST SETUP   ///\n//////////////////////\n\nvar (\n\tTestHelper *testutil.TestHelper\n)\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\tos.Exit(m.Run())\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\n// TestInstallLinkerd will install the linkerd control plane to be used in the rest of\n// the deep suite tests\nfunc TestInstallLinkerd(t *testing.T) {\n\terr := TestHelper.InstallGatewayAPI()\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"failed to install gateway-api\", err)\n\t}\n\n\terr = TestHelper.CreateControlPlaneNamespaceIfNotExists(context.Background(), TestHelper.GetLinkerdNamespace())\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, fmt.Sprintf(\"failed to create %s namespace\", TestHelper.GetLinkerdNamespace()),\n\t\t\t\"failed to create %s namespace: %s\", TestHelper.GetLinkerdNamespace(), err)\n\t}\n\n\tidentity := fmt.Sprintf(\"identity.%s.%s\", TestHelper.GetLinkerdNamespace(), TestHelper.GetClusterDomain())\n\n\troot, err := tls.GenerateRootCAWithDefaults(identity)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"error generating root CA\", err)\n\t}\n\n\t// instead of passing the roots and key around we generate\n\t// two secrets here. The second one will be used in the\n\t// external_issuer_test to update the first one and trigger\n\t// cert rotation in the identity service. That allows us\n\t// to generated the certs on the fly and use custom domain.\n\tif err = TestHelper.CreateTLSSecret(\n\t\tk8s.IdentityIssuerSecretName,\n\t\troot.Cred.Crt.EncodeCertificatePEM(),\n\t\troot.Cred.Crt.EncodeCertificatePEM(),\n\t\troot.Cred.EncodePrivateKeyPEM()); err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"error creating TLS secret\", err)\n\t}\n\n\tcrt2, err := root.GenerateCA(identity, -1)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"error generating CA\", err)\n\t}\n\n\tif err = TestHelper.CreateTLSSecret(\n\t\tk8s.IdentityIssuerSecretName+\"-new\",\n\t\troot.Cred.Crt.EncodeCertificatePEM(),\n\t\tcrt2.Cred.EncodeCertificatePEM(),\n\t\tcrt2.Cred.EncodePrivateKeyPEM()); err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"error creating TLS secret (-new)\", err)\n\t}\n\n\t// Install CRDs\n\tcmd := []string{\n\t\t\"install\",\n\t\t\"--crds\",\n\t\t\"--controller-log-level\", \"debug\",\n\t\t\"--set\", fmt.Sprintf(\"proxy.image.version=%s\", TestHelper.GetVersion()),\n\t\t\"--set\", \"heartbeatSchedule=1 2 3 4 5\",\n\t\t\"--identity-issuance-lifetime=15s\",\n\t\t\"--identity-external-issuer=true\",\n\t}\n\n\t// Pipe cmd & args to `linkerd`\n\tout, err := TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install --crds' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithArgs(out)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\t// Install control-plane with a short cert lifetime to put some pressure on\n\t// the CSR request, response code path.\n\tcmd = []string{\n\t\t\"install\",\n\t\t\"--controller-log-level\", \"debug\",\n\t\t\"--set\", fmt.Sprintf(\"proxy.image.version=%s\", TestHelper.GetVersion()),\n\t\t\"--set\", \"heartbeatSchedule=1 2 3 4 5\",\n\t\t\"--identity-issuance-lifetime=15s\",\n\t\t\"--identity-external-issuer=true\",\n\t}\n\n\t// Pipe cmd & args to `linkerd`\n\tout, err = TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithArgs(out)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\tTestHelper.WaitRollout(t, testutil.LinkerdDeployReplicasEdge)\n}\n\n// TestInstallViz will install the viz extension to be used by the rest of the\n// tests in the viz suite\nfunc TestInstallViz(t *testing.T) {\n\t// Install external prometheus\n\tout, err := TestHelper.LinkerdRun(\"inject\", \"testdata/external_prometheus.yaml\", \"--manual\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'linkerd inject' command failed\", \"'linkerd inject' command failed: %s\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApply(out, \"\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"kubectl apply command failed\\n%s\", out)\n\t}\n\n\tcmd := []string{\n\t\t\"viz\",\n\t\t\"install\",\n\t\t\"--set\", fmt.Sprintf(\"namespace=%s\", TestHelper.GetVizNamespace()),\n\t\t\"--set\", \"prometheusUrl=http://prometheus.external-prometheus.svc.cluster.local:9090\",\n\t\t\"--set\", \"prometheus.enabled=false\",\n\t}\n\n\tout, err = TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd viz install' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithArgs(out)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\texpectedDeployments := make(map[string]testutil.DeploySpec, len(testutil.LinkerdVizDeployReplicas))\n\tfor k, v := range testutil.LinkerdVizDeployReplicas {\n\t\tif k == \"prometheus\" {\n\t\t\tv = testutil.DeploySpec{Namespace: \"external-prometheus\", Replicas: 1}\n\t\t}\n\n\t\texpectedDeployments[k] = v\n\t}\n\tTestHelper.WaitRollout(t, expectedDeployments)\n\n}\n\nfunc TestCheckVizWithExternalPrometheus(t *testing.T) {\n\tif err := TestHelper.TestCheck(); err != nil {\n\t\tt.Fatalf(\"'linkerd check' command failed: %s\", err)\n\t}\n}\n"
  },
  {
    "path": "test/integration/external/stat/stat_test.go",
    "content": "package get\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\n//////////////////////\n///   TEST SETUP   ///\n//////////////////////\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until viz extension pods are running\n\tTestHelper.WaitUntilDeployReady(testutil.ExternalVizDeployReplicas)\n\tos.Exit(m.Run())\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\n// These tests retry for up to 20 seconds, since each call to \"linkerd stat\"\n// generates traffic to the components in the linkerd namespace, and we're\n// testing that those components are properly reporting stats. It's ok if the\n// first few attempts fail due to missing stats, since the requests from those\n// failed attempts will eventually be recorded in the stats that we're\n// requesting, and the test will pass.\nfunc TestCliStatForLinkerdNamespace(t *testing.T) {\n\tctx := context.Background()\n\tvar prometheusPod, prometheusNamespace, prometheusDeployment, metricsPod string\n\t// Get Metrics Pod\n\tpods, err := TestHelper.GetPodNamesForDeployment(ctx, TestHelper.GetVizNamespace(), \"metrics-api\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to get pods for metrics-api\",\n\t\t\t\"failed to get pods for metrics-api: %s\", err)\n\t}\n\tif len(pods) != 1 {\n\t\ttestutil.Fatalf(t, \"expected 1 pod for metrics-api, got %d\", len(pods))\n\t}\n\tmetricsPod = pods[0]\n\n\t// Retrieve Prometheus pod details\n\tprometheusNamespace = \"external-prometheus\"\n\tprometheusDeployment = \"prometheus\"\n\n\tpods, err = TestHelper.GetPodNamesForDeployment(ctx, prometheusNamespace, prometheusDeployment)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to get pods for prometheus\",\n\t\t\t\"failed to get pods for prometheus: %s\", err)\n\t}\n\tif len(pods) != 1 {\n\t\ttestutil.Fatalf(t, \"expected 1 pod for prometheus, got %d\", len(pods))\n\t}\n\tprometheusPod = pods[0]\n\n\ttestCases := []struct {\n\t\targs         []string\n\t\texpectedRows map[string]string\n\t\tstatus       string\n\t}{\n\t\t{\n\t\t\targs: []string{\"viz\", \"stat\", \"deploy\", \"-n\", TestHelper.GetLinkerdNamespace()},\n\t\t\texpectedRows: map[string]string{\n\t\t\t\t\"linkerd-destination\":    \"1/1\",\n\t\t\t\t\"linkerd-identity\":       \"1/1\",\n\t\t\t\t\"linkerd-proxy-injector\": \"1/1\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\targs: []string{\"viz\", \"stat\", \"ns\", TestHelper.GetLinkerdNamespace()},\n\t\t\texpectedRows: map[string]string{\n\t\t\t\tTestHelper.GetLinkerdNamespace(): \"3/3\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\targs: []string{\"viz\", \"stat\", fmt.Sprintf(\"po/%s\", prometheusPod), \"-n\", prometheusNamespace, \"--from\", fmt.Sprintf(\"po/%s\", metricsPod), \"--from-namespace\", TestHelper.GetVizNamespace()},\n\t\t\texpectedRows: map[string]string{\n\t\t\t\tprometheusPod: \"1/1\",\n\t\t\t},\n\t\t\tstatus: \"Running\",\n\t\t},\n\t\t{\n\t\t\targs: []string{\"viz\", \"stat\", \"deploy\", \"-n\", TestHelper.GetVizNamespace(), \"--to\", fmt.Sprintf(\"po/%s\", prometheusPod), \"--to-namespace\", prometheusNamespace},\n\t\t\texpectedRows: map[string]string{\n\t\t\t\t\"metrics-api\": \"1/1\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\targs: []string{\"viz\", \"stat\", \"deploy\", \"-n\", TestHelper.GetVizNamespace(), \"--to\", fmt.Sprintf(\"svc/%s\", prometheusDeployment), \"--to-namespace\", prometheusNamespace},\n\t\t\texpectedRows: map[string]string{\n\t\t\t\t\"metrics-api\": \"1/1\",\n\t\t\t},\n\t\t},\n\t}\n\n\tif !TestHelper.ExternalPrometheus() {\n\t\ttestCases = append(testCases, []struct {\n\t\t\targs         []string\n\t\t\texpectedRows map[string]string\n\t\t\tstatus       string\n\t\t}{\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"deploy\", \"-n\", TestHelper.GetVizNamespace()},\n\t\t\t\texpectedRows: map[string]string{\n\t\t\t\t\t\"metrics-api\":  \"1/1\",\n\t\t\t\t\t\"tap\":          \"1/1\",\n\t\t\t\t\t\"web\":          \"1/1\",\n\t\t\t\t\t\"tap-injector\": \"1/1\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"ns\", TestHelper.GetVizNamespace()},\n\t\t\t\texpectedRows: map[string]string{\n\t\t\t\t\tTestHelper.GetVizNamespace(): \"4/4\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"svc\", \"prometheus\", \"-n\", prometheusNamespace, \"--from\", \"deploy/metrics-api\", \"--from-namespace\", TestHelper.GetVizNamespace()},\n\n\t\t\t\texpectedRows: map[string]string{\n\t\t\t\t\t\"prometheus\": \"-\",\n\t\t\t\t},\n\t\t\t},\n\t\t}...,\n\t\t)\n\t} else {\n\t\ttestCases = append(testCases, []struct {\n\t\t\targs         []string\n\t\t\texpectedRows map[string]string\n\t\t\tstatus       string\n\t\t}{\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"deploy\", \"-n\", TestHelper.GetVizNamespace()},\n\t\t\t\texpectedRows: map[string]string{\n\t\t\t\t\t\"metrics-api\":  \"1/1\",\n\t\t\t\t\t\"tap\":          \"1/1\",\n\t\t\t\t\t\"web\":          \"1/1\",\n\t\t\t\t\t\"tap-injector\": \"1/1\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"ns\", TestHelper.GetVizNamespace()},\n\t\t\t\texpectedRows: map[string]string{\n\t\t\t\t\tTestHelper.GetVizNamespace(): \"4/4\",\n\t\t\t\t},\n\t\t\t},\n\t\t}...,\n\t\t)\n\t}\n\n\t// Apply a sample application\n\tTestHelper.WithDataPlaneNamespace(ctx, \"stat-test\", map[string]string{}, t, func(t *testing.T, prefixedNs string) {\n\t\tout, err := TestHelper.LinkerdRun(\"inject\", \"--manual\", \"./testdata/application.yaml\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"'linkerd inject' command failed\", err)\n\t\t}\n\n\t\tout, err = TestHelper.KubectlApply(out, prefixedNs)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t\t}\n\n\t\t// wait for deployments to start\n\t\tfor _, deploy := range []string{\"backend\", \"failing\", \"slow-cooker\"} {\n\t\t\tif err := TestHelper.CheckPods(ctx, prefixedNs, deploy, 1); err != nil {\n\t\t\t\t//nolint:errorlint\n\t\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t\t} else {\n\t\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttestCases = append(testCases, []struct {\n\t\t\targs         []string\n\t\t\texpectedRows map[string]string\n\t\t\tstatus       string\n\t\t}{\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"svc\", \"-n\", prefixedNs},\n\t\t\t\texpectedRows: map[string]string{\n\t\t\t\t\t\"backend-svc\": \"-\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"svc/backend-svc\", \"-n\", prefixedNs, \"--from\", \"deploy/slow-cooker\"},\n\t\t\t\texpectedRows: map[string]string{\n\t\t\t\t\t\"backend-svc\": \"-\",\n\t\t\t\t},\n\t\t\t},\n\t\t}...,\n\t\t)\n\n\t\tfor _, tt := range testCases {\n\t\t\ttt := tt // pin\n\t\t\ttimeout := 20 * time.Second\n\t\t\tt.Run(\"linkerd \"+strings.Join(tt.args, \" \"), func(t *testing.T) {\n\t\t\t\terr := testutil.RetryFor(timeout, func() error {\n\t\t\t\t\t// Use a short time window so that transient errors at startup\n\t\t\t\t\t// fall out of the window.\n\t\t\t\t\ttt.args = append(tt.args, \"-t\", \"30s\")\n\t\t\t\t\tout, err := TestHelper.LinkerdRun(tt.args...)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected stat error\",\n\t\t\t\t\t\t\t\"unexpected stat error: %s\\n%s\", err, out)\n\t\t\t\t\t}\n\n\t\t\t\t\texpectedColumnCount := 8\n\t\t\t\t\tif tt.status != \"\" {\n\t\t\t\t\t\texpectedColumnCount++\n\t\t\t\t\t}\n\t\t\t\t\trowStats, err := testutil.ParseRows(out, len(tt.expectedRows), expectedColumnCount)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\tfor name, meshed := range tt.expectedRows {\n\t\t\t\t\t\tif err := validateRowStats(name, meshed, tt.status, rowStats); err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"timed-out checking stats (%s)\", timeout), err)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc validateRowStats(name, expectedMeshCount, expectedStatus string, rowStats map[string]*testutil.RowStat) error {\n\tstat, ok := rowStats[name]\n\tif !ok {\n\t\treturn fmt.Errorf(\"no stats found for [%s]\", name)\n\t}\n\n\tif stat.Status != expectedStatus {\n\t\treturn fmt.Errorf(\"expected status '%s' for '%s', got '%s'\",\n\t\t\texpectedStatus, name, stat.Status)\n\t}\n\n\tif stat.Meshed != expectedMeshCount {\n\t\treturn fmt.Errorf(\"expected mesh count [%s] for [%s], got [%s]\",\n\t\t\texpectedMeshCount, name, stat.Meshed)\n\t}\n\n\texpectedSuccessRate := \"100.00%\"\n\tif stat.Success != expectedSuccessRate {\n\t\treturn fmt.Errorf(\"expected success rate [%s] for [%s], got [%s]\",\n\t\t\texpectedSuccessRate, name, stat.Success)\n\t}\n\n\tif !strings.HasSuffix(stat.Rps, \"rps\") {\n\t\treturn fmt.Errorf(\"unexpected rps for [%s], got [%s]\",\n\t\t\tname, stat.Rps)\n\t}\n\n\tif !strings.HasSuffix(stat.P50Latency, \"ms\") {\n\t\treturn fmt.Errorf(\"unexpected p50 latency for [%s], got [%s]\",\n\t\t\tname, stat.P50Latency)\n\t}\n\n\tif !strings.HasSuffix(stat.P95Latency, \"ms\") {\n\t\treturn fmt.Errorf(\"unexpected p95 latency for [%s], got [%s]\",\n\t\t\tname, stat.P95Latency)\n\t}\n\n\tif !strings.HasSuffix(stat.P99Latency, \"ms\") {\n\t\treturn fmt.Errorf(\"unexpected p99 latency for [%s], got [%s]\",\n\t\t\tname, stat.P99Latency)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "test/integration/external/stat/testdata/application.yaml",
    "content": "# Two backend pods, one always failing\n# and another one returning OK response\n# Slowcooker is used to generate traffic\n# that will be routed via traffic split\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: backend\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: backend\n  template:\n    metadata:\n      labels:\n        app: backend\n    spec:\n      containers:\n      - name: backend\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=backend1\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: backend-svc\nspec:\n  selector:\n    app: backend\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: failing\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: failing\n  template:\n    metadata:\n      labels:\n        app: failing\n    spec:\n      containers:\n      - name: failing\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=failing\"\n        - \"--percent-failure=100\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: failing-svc\nspec:\n  selector:\n    app: failing\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: slow-cooker\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: slow-cooker\n  template:\n    metadata:\n      labels:\n        app: slow-cooker\n    spec:\n      containers:\n      - name: slow-cooker\n        image: buoyantio/slow_cooker:1.3.0\n        command:\n        - \"/bin/sh\"\n        args:\n        - \"-c\"\n        - |\n          sleep 5 # wait for pods to start\n          /slow_cooker/slow_cooker http://backend-svc:8080\n        ports:\n        - containerPort: 9999\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: slow-cooker\nspec:\n  selector:\n    app: slow-cooker\n  ports:\n  - name: metrics\n    port: 9999\n    targetPort: 9999\n"
  },
  {
    "path": "test/integration/external/testdata/external_prometheus.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: external-prometheus\n  labels:\n    test.linkerd.io/is-test-data-plane: \"true\"\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: prometheus\n  labels:\n    test.linkerd.io/is-test-data-plane: \"true\"\nrules:\n- apiGroups: [\"\"]\n  resources: [\"nodes\", \"nodes/proxy\", \"pods\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: prometheus\n  labels:\n    test.linkerd.io/is-test-data-plane: \"true\"\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: prometheus\nsubjects:\n- kind: ServiceAccount\n  name: prometheus\n  namespace: external-prometheus\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: prometheus\n  namespace: external-prometheus\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: prometheus-config\n  namespace: external-prometheus\ndata:\n  prometheus.yml: |-\n    global:\n      evaluation_interval: 10s\n      scrape_interval: 10s\n      scrape_timeout: 10s\n\n    rule_files:\n    - /etc/prometheus/*_rules.yml\n    - /etc/prometheus/*_rules.yaml\n\n    scrape_configs:\n    - job_name: 'prometheus'\n      static_configs:\n      - targets: ['localhost:9090']\n\n    - job_name: 'grafana'\n      kubernetes_sd_configs:\n      - role: pod\n        namespaces:\n          names: ['grafana']\n      relabel_configs:\n      - source_labels:\n        - __meta_kubernetes_pod_container_name\n        action: keep\n        regex: ^grafana$\n\n    #  Required for: https://grafana.com/grafana/dashboards/315\n    - job_name: 'kubernetes-nodes-cadvisor'\n      scheme: https\n      tls_config:\n        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n        insecure_skip_verify: true\n      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token\n      kubernetes_sd_configs:\n      - role: node\n      relabel_configs:\n      - action: labelmap\n        regex: __meta_kubernetes_node_label_(.+)\n      - target_label: __address__\n        replacement: kubernetes.default.svc:443\n      - source_labels: [__meta_kubernetes_node_name]\n        regex: (.+)\n        target_label: __metrics_path__\n        replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor\n      metric_relabel_configs:\n      - source_labels: [__name__]\n        regex: '(container|machine)_(cpu|memory|network|fs)_(.+)'\n        action: keep\n      - source_labels: [__name__]\n        regex: 'container_memory_failures_total' # unneeded large metric\n        action: drop\n\n    - job_name: 'linkerd-controller'\n      kubernetes_sd_configs:\n      - role: pod\n        namespaces:\n          names: ['linkerd']\n      relabel_configs:\n      - source_labels:\n        - __meta_kubernetes_pod_label_linkerd_io_control_plane_component\n        - __meta_kubernetes_pod_container_port_name\n        action: keep\n        regex: (.*);admin$\n      - source_labels: [__meta_kubernetes_pod_container_name]\n        action: replace\n        target_label: component\n\n    - job_name: 'linkerd-service-mirror'\n      kubernetes_sd_configs:\n      - role: pod\n      relabel_configs:\n      - source_labels:\n        - __meta_kubernetes_pod_label_linkerd_io_control_plane_component\n        - __meta_kubernetes_pod_container_port_name\n        action: keep\n        regex: linkerd-service-mirror;admin$\n      - source_labels: [__meta_kubernetes_pod_container_name]\n        action: replace\n        target_label: component\n\n    - job_name: 'linkerd-proxy'\n      kubernetes_sd_configs:\n      - role: pod\n      relabel_configs:\n      - source_labels:\n        - __meta_kubernetes_pod_container_name\n        - __meta_kubernetes_pod_container_port_name\n        - __meta_kubernetes_pod_label_linkerd_io_control_plane_ns\n        action: keep\n        regex: ^linkerd-proxy;linkerd-admin;linkerd$\n      - source_labels: [__meta_kubernetes_namespace]\n        action: replace\n        target_label: namespace\n      - source_labels: [__meta_kubernetes_pod_name]\n        action: replace\n        target_label: pod\n      # special case k8s' \"job\" label, to not interfere with prometheus' \"job\"\n      # label\n      # __meta_kubernetes_pod_label_linkerd_io_proxy_job=foo =>\n      # k8s_job=foo\n      - source_labels: [__meta_kubernetes_pod_label_linkerd_io_proxy_job]\n        action: replace\n        target_label: k8s_job\n      # drop __meta_kubernetes_pod_label_linkerd_io_proxy_job\n      - action: labeldrop\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_job\n      # __meta_kubernetes_pod_label_linkerd_io_proxy_deployment=foo =>\n      # deployment=foo\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)\n      # drop all labels that we just made copies of in the previous labelmap\n      - action: labeldrop\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)\n      # __meta_kubernetes_pod_label_linkerd_io_foo=bar =>\n      # foo=bar\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_linkerd_io_(.+)\n      # Copy all pod labels to tmp labels\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_(.+)\n        replacement: __tmp_pod_label_$1\n      # Take `linkerd_io_` prefixed labels and copy them without the prefix\n      - action: labelmap\n        regex: __tmp_pod_label_linkerd_io_(.+)\n        replacement:  __tmp_pod_label_$1\n      # Drop the `linkerd_io_` originals\n      - action: labeldrop\n        regex: __tmp_pod_label_linkerd_io_(.+)\n      # Copy tmp labels into real labels\n      - action: labelmap\n        regex: __tmp_pod_label_(.+)\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: prometheus\n  namespace: external-prometheus\nspec:\n  type: ClusterIP\n  selector:\n    app: prometheus\n  ports:\n  - name: admin\n    port: 9090\n    targetPort: 9090\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: prometheus\n  namespace: external-prometheus\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: prometheus\n  template:\n    metadata:\n      labels:\n        app: prometheus\n        linkerd.io/inject: \"enabled\"\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      securityContext:\n        fsGroup: 65534\n      containers:\n      - args:\n        - --config.file=/etc/prometheus/prometheus.yml\n        - --log.level=info\n        - --storage.tsdb.path=/data\n        - --storage.tsdb.retention.time=6h\n        image: prom/prometheus:v2.19.3\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /-/healthy\n            port: 9090\n          initialDelaySeconds: 30\n          timeoutSeconds: 30\n        name: prometheus\n        ports:\n        - containerPort: 9090\n          name: admin\n        readinessProbe:\n          httpGet:\n            path: /-/ready\n            port: 9090\n          initialDelaySeconds: 30\n          timeoutSeconds: 30\n        securityContext:\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n        volumeMounts:\n        - mountPath: /data\n          name: data\n        - mountPath: /etc/prometheus/prometheus.yml\n          name: prometheus-config\n          subPath: prometheus.yml\n          readOnly: true\n      serviceAccountName: prometheus\n      volumes:\n      - name: data\n        emptyDir: {}\n      - configMap:\n          name: prometheus-config\n        name: prometheus-config\n"
  },
  {
    "path": "test/integration/install/inject/inject_test.go",
    "content": "package inject\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\t\"k8s.io/apimachinery/pkg/runtime/serializer/json\"\n)\n\nconst (\n\topaquePorts       = \"11211\"\n\tmanualOpaquePorts = \"22122\"\n)\n\n//////////////////////\n///   TEST SETUP   ///\n//////////////////////\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until control plane pods are running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)\n\tos.Exit(m.Run())\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\nfunc parseDeployment(yamlString string) (*appsv1.Deployment, error) {\n\ts := json.NewYAMLSerializer(json.DefaultMetaFactory, scheme.Scheme,\n\t\tscheme.Scheme)\n\tvar deploy appsv1.Deployment\n\t_, _, err := s.Decode([]byte(yamlString), nil, &deploy)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &deploy, nil\n}\n\nfunc TestInjectManualParams(t *testing.T) {\n\treg := \"cr.l5d.io/linkerd\"\n\tif override := os.Getenv(flags.EnvOverrideDockerRegistry); override != \"\" {\n\t\treg = override\n\t}\n\n\tinjectionValidator := testutil.InjectValidator{\n\t\tNoInitContainer:        TestHelper.CNI(),\n\t\tVersion:                \"proxy-version\",\n\t\tImage:                  reg + \"/proxy-image\",\n\t\tImagePullPolicy:        \"Never\",\n\t\tControlPort:            123,\n\t\tSkipInboundPorts:       \"234,345\",\n\t\tSkipOutboundPorts:      \"456,567\",\n\t\tInboundPort:            678,\n\t\tAdminPort:              789,\n\t\tOutboundPort:           890,\n\t\tCPURequest:             \"10m\",\n\t\tMemoryRequest:          \"10Mi\",\n\t\tCPULimit:               \"20m\",\n\t\tMemoryLimit:            \"20Mi\",\n\t\tUID:                    1337,\n\t\tLogLevel:               \"off\",\n\t\tEnableExternalProfiles: true,\n\t}\n\n\tfor _, nativeSidecar := range []bool{false, true} {\n\t\tt.Run(fmt.Sprintf(\"Testing manual injection with nativeSidecar=%v\", nativeSidecar), func(t *testing.T) {\n\t\t\tinjectionValidator.NativeSidecar = nativeSidecar\n\n\t\t\tflags, _ := injectionValidator.GetFlagsAndAnnotations()\n\n\t\t\t// TODO: test config.linkerd.io/proxy-version\n\t\t\tcmd := append([]string{\"inject\",\n\t\t\t\t\"--manual\",\n\t\t\t}, flags...)\n\n\t\t\tcmd = append(cmd, \"testdata/inject_test.yaml\")\n\n\t\t\tout, err := TestHelper.LinkerdRun(cmd...)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"unexpected error\", err)\n\t\t\t}\n\n\t\t\tdeploy, err := parseDeployment(out)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed parsing deployment\", \"failed parsing deployment\\n%s\", err.Error())\n\t\t\t}\n\n\t\t\terr = injectionValidator.ValidatePod(&deploy.Spec.Template.Spec)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"received unexpected output\", \"received unexpected output\\n%s\", err.Error())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestInjectAutoParams(t *testing.T) {\n\tinjectYAML, err := testutil.ReadFile(\"testdata/inject_test.yaml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to read inject test file\", \"failed to read inject test file: %s\", err)\n\t}\n\n\tinjectNS := \"inj-auto-params-test\"\n\tdeployName := \"inject-test-terminus-auto\"\n\n\tctx := context.Background()\n\n\tinjectionValidator := testutil.InjectValidator{\n\t\tNoInitContainer:         TestHelper.CNI() || TestHelper.Calico(),\n\t\tAutoInject:              true,\n\t\tAdminPort:               8888,\n\t\tControlPort:             8881,\n\t\tEnableExternalProfiles:  true,\n\t\tEnableDebug:             true,\n\t\tImagePullPolicy:         \"Never\",\n\t\tInboundPort:             8882,\n\t\tOutboundPort:            8883,\n\t\tCPULimit:                \"160m\",\n\t\tCPURequest:              \"150m\",\n\t\tMemoryLimit:             \"150Mi\",\n\t\tMemoryRequest:           \"100Mi\",\n\t\tEphemeralStorageLimit:   \"50Mi\",\n\t\tEphemeralStorageRequest: \"10Mi\",\n\t\tImage:                   \"proxy-image\",\n\t\tLogLevel:                \"proxy-log-level\",\n\t\tUID:                     10,\n\t\tVersion:                 \"proxy-version\",\n\t\tRequireIdentityOnPorts:  \"8884,8885\",\n\t\tOpaquePorts:             \"8888,8889\",\n\t\tOutboundConnectTimeout:  \"888ms\",\n\t\tInboundConnectTimeout:   \"999ms\",\n\t\tSkipOutboundPorts:       \"1111,2222,3333\",\n\t\tSkipInboundPorts:        \"4444,5555,6666\",\n\t\tWaitBeforeExitSeconds:   10,\n\t}\n\n\tfor _, nativeSidecar := range []bool{false, true} {\n\t\tTestHelper.WithDataPlaneNamespace(ctx, fmt.Sprintf(\"%s-%v\", injectNS, nativeSidecar), map[string]string{}, t, func(t *testing.T, ns string) {\n\t\t\tt.Run(fmt.Sprintf(\"Testing injection via webhook with nativeSidecar=%v\", nativeSidecar), func(t *testing.T) {\n\t\t\t\tinjectionValidator.NativeSidecar = nativeSidecar\n\t\t\t\t// proxy.nativeSidecar and waitBeforeExitSeconds cannot be used simultaneously\n\t\t\t\tif nativeSidecar {\n\t\t\t\t\tinjectionValidator.WaitBeforeExitSeconds = 0\n\t\t\t\t}\n\n\t\t\t\t_, annotations := injectionValidator.GetFlagsAndAnnotations()\n\n\t\t\t\tpatchedYAML, err := testutil.PatchDeploy(injectYAML, deployName, annotations)\n\t\t\t\tif err != nil {\n\t\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to patch inject test YAML\",\n\t\t\t\t\t\t\"failed to patch inject test YAML in namespace %s for deploy/%s: %s\", ns, deployName, err)\n\t\t\t\t}\n\n\t\t\t\to, err := TestHelper.Kubectl(patchedYAML, \"--namespace\", ns, \"create\", \"-f\", \"-\")\n\t\t\t\tif err != nil {\n\t\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to create deployment\", \"failed to create deploy/%s in namespace %s for  %s: %s\", deployName, ns, err, o)\n\t\t\t\t}\n\n\t\t\t\tvar pod *v1.Pod\n\t\t\t\terr = testutil.RetryFor(30*time.Second, func() error {\n\t\t\t\t\tpods, err := TestHelper.GetPodsForDeployment(ctx, ns, deployName)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to get pods for namespace %s\", ns)\n\t\t\t\t\t}\n\n\t\t\t\t\tfor _, p := range pods {\n\t\t\t\t\t\tp := p // pin\n\t\t\t\t\t\tcreator, ok := p.Annotations[k8s.CreatedByAnnotation]\n\t\t\t\t\t\tif ok && strings.Contains(creator, \"proxy-injector\") {\n\t\t\t\t\t\t\tpod = &p\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 pod == nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to find auto injected pod for deployment %s\", deployName)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\n\t\t\t\tif err != nil {\n\t\t\t\t\ttestutil.AnnotatedFatal(t, \"failed to find autoinjected pod: \", err.Error())\n\t\t\t\t}\n\n\t\t\t\tif err := injectionValidator.ValidatePod(&pod.Spec); err != nil {\n\t\t\t\t\ttestutil.AnnotatedFatal(t, \"failed to validate auto injection\", err.Error())\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc TestInjectAutoNamespaceOverrideAnnotations(t *testing.T) {\n\t// Check for Namespace level override of proxy Configurations\n\tinjectYAML, err := testutil.ReadFile(\"testdata/inject_test.yaml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to read inject test file\", \"failed to read inject test file: %s\", err)\n\t}\n\n\tinjectNS := \"inj-ns-override-test\"\n\tdeployName := \"inject-test-terminus\"\n\tnsProxyMemReq := \"50Mi\"\n\tnsProxyCPUReq := \"200m\"\n\n\t// Namespace level proxy configuration override\n\tnsAnnotations := map[string]string{\n\t\tk8s.ProxyInjectAnnotation:        k8s.ProxyInjectEnabled,\n\t\tk8s.ProxyCPURequestAnnotation:    nsProxyCPUReq,\n\t\tk8s.ProxyMemoryRequestAnnotation: nsProxyMemReq,\n\t}\n\n\tctx := context.Background()\n\tTestHelper.WithDataPlaneNamespace(ctx, injectNS, nsAnnotations, t, func(t *testing.T, ns string) {\n\t\t// patch injectYAML with unique name and pod annotations\n\t\t// Pod Level proxy configuration override\n\t\tpodProxyCPUReq := \"600m\"\n\t\tpodAnnotations := map[string]string{\n\t\t\tk8s.ProxyCPURequestAnnotation: podProxyCPUReq,\n\t\t}\n\n\t\tpatchedYAML, err := testutil.PatchDeploy(injectYAML, deployName, podAnnotations)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to patch inject test YAML\",\n\t\t\t\t\"failed to patch inject test YAML in namespace %s for deploy/%s: %s\", ns, deployName, err)\n\t\t}\n\n\t\to, err := TestHelper.Kubectl(patchedYAML, \"--namespace\", ns, \"create\", \"-f\", \"-\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to create deployment\", \"failed to create deploy/%s in namespace %s for  %s: %s\", deployName, ns, err, o)\n\t\t}\n\n\t\to, err = TestHelper.Kubectl(\"\", \"--namespace\", ns, \"wait\", \"--for=condition=available\", \"--timeout=120s\", \"deploy/\"+deployName)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, fmt.Sprintf(\"failed to wait for condition=available for deploy/%s in namespace %s\", deployName, ns),\n\t\t\t\t\"failed to wait for condition=available for deploy/%s in namespace %s: %s: %s\", deployName, ns, err, o)\n\t\t}\n\n\t\tpods, err := TestHelper.GetPodsForDeployment(ctx, ns, deployName)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, fmt.Sprintf(\"failed to get pods for namespace %s\", ns),\n\t\t\t\t\"failed to get pods for namespace %s: %s\", ns, err)\n\t\t}\n\n\t\tproxyContainer := testutil.GetProxyContainer(pods[0].Spec)\n\n\t\t// Match the pod configuration with the namespace level overrides\n\t\tif proxyContainer.Resources.Requests[\"memory\"] != resource.MustParse(nsProxyMemReq) {\n\t\t\ttestutil.Fatalf(t, \"proxy memory resource request failed to match with namespace level override\")\n\t\t}\n\n\t\t// Match with proxy level override\n\t\tif proxyContainer.Resources.Requests[\"cpu\"] != resource.MustParse(podProxyCPUReq) {\n\t\t\ttestutil.Fatalf(t, \"proxy cpu resource request failed to match with pod level override\")\n\t\t}\n\t})\n}\n\nfunc TestInjectAutoPod(t *testing.T) {\n\tpodsYAML, err := testutil.ReadFile(\"testdata/pods.yaml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to read inject test file\",\n\t\t\t\"failed to read inject test file: %s\", err)\n\t}\n\n\tinjectNS := \"inject-pod-test\"\n\tpodName := \"inject-pod-test-terminus\"\n\topaquePodName := \"inject-opaque-pod-test-terminus\"\n\tnsAnnotations := map[string]string{\n\t\tk8s.ProxyInjectAnnotation:      k8s.ProxyInjectEnabled,\n\t\tk8s.ProxyOpaquePortsAnnotation: opaquePorts,\n\t}\n\n\ttruthy := true\n\tfalsy := false\n\tinitUser := int64(65534)\n\tinitGroup := int64(65534)\n\tseccompProfile := &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}\n\texpectedInitContainer := v1.Container{\n\t\tName:    k8s.InitContainerName,\n\t\tCommand: []string{\"/usr/lib/linkerd/linkerd2-proxy-init\"},\n\t\tArgs: []string{\n\t\t\t\"--firewall-bin-path\", \"iptables-nft\",\n\t\t\t\"--firewall-save-bin-path\", \"iptables-nft-save\",\n\t\t\t\"--ipv6=false\",\n\t\t\t\"--incoming-proxy-port\", \"4143\",\n\t\t\t\"--outgoing-proxy-port\", \"4140\",\n\t\t\t\"--proxy-uid\", \"2102\",\n\t\t\t// 1234,5678 were added at install time in `install_test.go`'s helmOverridesEdge()\n\t\t\t\"--inbound-ports-to-ignore\", \"4190,4191,1234,5678\",\n\t\t\t\"--outbound-ports-to-ignore\", \"4567,4568\",\n\t\t},\n\t\tVolumeMounts: []v1.VolumeMount{\n\t\t\t{\n\t\t\t\tName:      \"linkerd-proxy-init-xtables-lock\",\n\t\t\t\tReadOnly:  false,\n\t\t\t\tMountPath: \"/run\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tReadOnly:  true,\n\t\t\t\tMountPath: \"/var/run/secrets/kubernetes.io/serviceaccount\",\n\t\t\t},\n\t\t},\n\t\tTerminationMessagePath: \"/dev/termination-log\",\n\t\tImagePullPolicy:        \"IfNotPresent\",\n\t\tSecurityContext: &v1.SecurityContext{\n\t\t\tCapabilities: &v1.Capabilities{\n\t\t\t\tAdd: []v1.Capability{v1.Capability(\"NET_ADMIN\"), v1.Capability(\"NET_RAW\")},\n\t\t\t},\n\t\t\tPrivileged:               &falsy,\n\t\t\tRunAsNonRoot:             &truthy,\n\t\t\tAllowPrivilegeEscalation: &falsy,\n\t\t\tReadOnlyRootFilesystem:   &truthy,\n\t\t\tRunAsUser:                &initUser,\n\t\t\tRunAsGroup:               &initGroup,\n\t\t\tSeccompProfile:           seccompProfile,\n\t\t},\n\t\tTerminationMessagePolicy: v1.TerminationMessagePolicy(\"FallbackToLogsOnError\"),\n\t}\n\n\tctx := context.Background()\n\n\tTestHelper.WithDataPlaneNamespace(ctx, injectNS, nsAnnotations, t, func(t *testing.T, ns string) {\n\t\to, err := TestHelper.Kubectl(podsYAML, \"--namespace\", ns, \"create\", \"-f\", \"-\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to create pods\",\n\t\t\t\t\"failed to create pods in namespace %s for %s: %s\", ns, err, o)\n\t\t}\n\n\t\to, err = TestHelper.Kubectl(\"\", \"--namespace\", ns, \"wait\", \"--for=condition=initialized\", \"--timeout=120s\", \"pod/\"+podName)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to wait for condition=initialized\",\n\t\t\t\t\"failed to wait for condition=initialized for pod/%s in namespace %s: %s: %s\", podName, ns, err, o)\n\t\t}\n\n\t\t// Check that pods with no annotation inherit from the namespace.\n\t\tpods, err := TestHelper.GetPods(ctx, ns, map[string]string{\"app\": podName})\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to get pods\", \"failed to get pods for namespace %s: %s\", ns, err)\n\t\t}\n\t\tif len(pods) != 1 {\n\t\t\ttestutil.Fatalf(t, \"wrong number of pods returned for namespace %s: %d\", ns, len(pods))\n\t\t}\n\t\tannotation, ok := pods[0].Annotations[k8s.ProxyOpaquePortsAnnotation]\n\t\tif !ok {\n\t\t\ttestutil.Fatalf(t, \"pod in namespace %s did not inherit opaque ports annotation\", ns)\n\t\t}\n\t\tif annotation != opaquePorts {\n\t\t\ttestutil.Fatalf(t, \"expected pod in namespace %s to have %s opaque ports, but it had %s\", ns, opaquePorts, annotation)\n\t\t}\n\n\t\t// Check that pods with an annotation do not inherit from the\n\t\t// namespace.\n\t\topaquePods, err := TestHelper.GetPods(ctx, ns, map[string]string{\"app\": opaquePodName})\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to get pods\", \"failed to get pods for namespace %s: %s\", ns, err)\n\t\t}\n\t\tif len(opaquePods) != 1 {\n\t\t\ttestutil.Fatalf(t, \"wrong number of pods returned for namespace %s: %d\", ns, len(opaquePods))\n\t\t}\n\t\tannotation = opaquePods[0].Annotations[k8s.ProxyOpaquePortsAnnotation]\n\t\tif annotation != manualOpaquePorts {\n\t\t\ttestutil.Fatalf(t, \"expected pod in namespace %s to have %s opaque ports, but it had %s\", ns, manualOpaquePorts, annotation)\n\t\t}\n\n\t\tif proxyContainer := testutil.GetProxyContainer(pods[0].Spec); proxyContainer == nil {\n\t\t\ttestutil.Fatalf(t, \"pod in namespace %s wasn't injected with the proxy container\", ns)\n\t\t}\n\n\t\tif !TestHelper.CNI() {\n\t\t\tinitContainers := pods[0].Spec.InitContainers\n\t\t\tif len(initContainers) == 0 {\n\t\t\t\ttestutil.Fatalf(t, \"pod in namespace %s wasn't injected with the init container\", ns)\n\t\t\t}\n\t\t\tinitContainer := initContainers[0]\n\t\t\tif mounts := initContainer.VolumeMounts; len(mounts) == 0 {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"init container doesn't have volume mounts\", \"init container doesn't have volume mounts: %#v\", initContainer)\n\t\t\t}\n\t\t\t// Removed token volume name from comparison because it contains a random string\n\t\t\tinitContainer.VolumeMounts[1].Name = \"\"\n\t\t\t// Expect the init container image to use the proxy container image\n\t\t\texpectedInitContainer.Image = testutil.GetProxyContainer(pods[0].Spec).Image\n\t\t\tif diff := deep.Equal(expectedInitContainer, initContainer); diff != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"malformed init container\", \"malformed init container:\\n%v\", diff)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestInjectDisabledAutoPod(t *testing.T) {\n\tpodsYAML, err := testutil.ReadFile(\"testdata/pods.yaml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to read inject test file\",\n\t\t\t\"failed to read inject test file: %s\", err)\n\t}\n\n\tns := \"inject-disabled-pod-test\"\n\tpodName := \"inject-pod-test-terminus\"\n\topaquePodName := \"inject-opaque-pod-test-terminus\"\n\tnsAnnotations := map[string]string{\n\t\tk8s.ProxyInjectAnnotation:      k8s.ProxyInjectDisabled,\n\t\tk8s.ProxyOpaquePortsAnnotation: opaquePorts,\n\t}\n\tctx := context.Background()\n\tTestHelper.WithDataPlaneNamespace(ctx, ns, nsAnnotations, t, func(t *testing.T, ns string) {\n\t\to, err := TestHelper.Kubectl(podsYAML, \"--namespace\", ns, \"create\", \"-f\", \"-\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to create pods\",\n\t\t\t\t\"failed to create pods in namespace %s for %s: %s\", ns, err, o)\n\t\t}\n\n\t\to, err = TestHelper.Kubectl(\"\", \"--namespace\", ns, \"wait\", \"--for=condition=initialized\", \"--timeout=120s\", \"pod/\"+podName)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to wait for condition=initialized\",\n\t\t\t\t\"failed to wait for condition=initialized for pod/%s in namespace %s: %s: %s\", podName, ns, err, o)\n\t\t}\n\n\t\t// Check that pods with no annotation inherit from the namespace.\n\t\tpods, err := TestHelper.GetPods(ctx, ns, map[string]string{\"app\": podName})\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to get pods\", \"failed to get pods for namespace %s: %s\", ns, err)\n\t\t}\n\t\tif len(pods) != 1 {\n\t\t\ttestutil.Fatalf(t, \"wrong number of pods returned for namespace %s: %d\", ns, len(pods))\n\t\t}\n\t\tannotation, ok := pods[0].Annotations[k8s.ProxyOpaquePortsAnnotation]\n\t\tif !ok {\n\t\t\ttestutil.Fatalf(t, \"pod in namespace %s did not inherit opaque ports annotation\", ns)\n\t\t}\n\t\tif annotation != opaquePorts {\n\t\t\ttestutil.Fatalf(t, \"expected pod in namespace %s to have %s opaque ports, but it had %s\", ns, opaquePorts, annotation)\n\t\t}\n\n\t\t// Check that pods with an annotation do not inherit from the\n\t\t// namespace.\n\t\topaquePods, err := TestHelper.GetPods(ctx, ns, map[string]string{\"app\": opaquePodName})\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to get pods\", \"failed to get pods for namespace %s: %s\", ns, err)\n\t\t}\n\t\tif len(opaquePods) != 1 {\n\t\t\ttestutil.Fatalf(t, \"wrong number of pods returned for namespace %s: %d\", ns, len(opaquePods))\n\t\t}\n\t\tannotation = opaquePods[0].Annotations[k8s.ProxyOpaquePortsAnnotation]\n\t\tif annotation != manualOpaquePorts {\n\t\t\ttestutil.Fatalf(t, \"expected pod in namespace %s to have %s opaque ports, but it had %s\", ns, manualOpaquePorts, annotation)\n\t\t}\n\n\t\tif proxyContainer := testutil.GetProxyContainer(pods[0].Spec); proxyContainer != nil {\n\t\t\ttestutil.Fatalf(t, \"pod in namespace %s should not have been injected\", ns)\n\t\t}\n\t})\n}\n\nfunc TestInjectService(t *testing.T) {\n\tservicesYAML, err := testutil.ReadFile(\"testdata/services.yaml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to read inject test file\",\n\t\t\t\"failed to read inject test file: %s\", err)\n\t}\n\n\tns := \"inject-service-test\"\n\tserviceName := \"service-test\"\n\topaqueServiceName := \"opaque-service-test\"\n\tnsAnnotations := map[string]string{\n\t\tk8s.ProxyInjectAnnotation:      k8s.ProxyInjectEnabled,\n\t\tk8s.ProxyOpaquePortsAnnotation: opaquePorts,\n\t}\n\tctx := context.Background()\n\tTestHelper.WithDataPlaneNamespace(ctx, ns, nsAnnotations, t, func(t *testing.T, ns string) {\n\t\to, err := TestHelper.Kubectl(servicesYAML, \"--namespace\", ns, \"create\", \"-f\", \"-\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to create services\",\n\t\t\t\t\"failed to create services in namespace %s for %s: %s\", ns, err, o)\n\t\t}\n\n\t\t// Check that the service with no annotation inherits from the namespace.\n\t\tservice, err := TestHelper.GetService(ctx, ns, serviceName)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to get service\", \"failed to get service for namespace %s: %s\", ns, err)\n\t\t}\n\t\tannotation, ok := service.Annotations[k8s.ProxyOpaquePortsAnnotation]\n\t\tif !ok {\n\t\t\ttestutil.Fatalf(t, \"pod in namespace %s did not inherit opaque ports annotation\", ns)\n\t\t}\n\t\tif annotation != opaquePorts {\n\t\t\ttestutil.Fatalf(t, \"expected pod in namespace %s to have %s opaque ports, but it had %s\", ns, opaquePorts, annotation)\n\t\t}\n\n\t\t// Check that the service with no annotation did not inherit from the namespace.\n\t\tservice, err = TestHelper.GetService(ctx, ns, opaqueServiceName)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to get service\", \"failed to get service for namespace %s: %s\", ns, err)\n\t\t}\n\t\tannotation = service.Annotations[k8s.ProxyOpaquePortsAnnotation]\n\t\tif annotation != manualOpaquePorts {\n\t\t\ttestutil.Fatalf(t, \"expected service in namespace %s to have %s opaque ports, but it had %s\", ns, manualOpaquePorts, annotation)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "test/integration/install/inject/testdata/inject_test.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: inject-test-terminus\nspec:\n  selector:\n    matchLabels:\n      app: inject-test-terminus\n  template:\n    metadata:\n      labels:\n        app: inject-test-terminus\n    spec:\n      containers:\n      - name: bb-terminus\n        image: buoyantio/bb:v0.0.6\n        args: [\"terminus\", \"--grpc-server-port\", \"9090\", \"--response-text\", \"BANANA\"]\n        ports:\n        - containerPort: 9090\n"
  },
  {
    "path": "test/integration/install/inject/testdata/pods.yaml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  name: inject-pod-test-terminus\n  labels:\n    app: inject-pod-test-terminus\nspec:\n  containers:\n  - name: bb-terminus\n    image: buoyantio/bb:v0.0.6\n    args: [\"terminus\", \"--grpc-server-port\", \"9090\", \"--response-text\", \"BANANA\"]\n    ports:\n    - containerPort: 9090\n---\napiVersion: v1\nkind: Pod\nmetadata:\n  name: inject-opaque-pod-test-terminus\n  annotations:\n    config.linkerd.io/opaque-ports: \"22122\"\n  labels:\n    app: inject-opaque-pod-test-terminus\nspec:\n  containers:\n  - name: bb-terminus\n    image: buoyantio/bb:v0.0.6\n    args: [\"terminus\", \"--grpc-server-port\", \"9090\", \"--response-text\", \"BANANA\"]\n    ports:\n    - containerPort: 9090\n"
  },
  {
    "path": "test/integration/install/inject/testdata/services.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: service-test\nspec:\n  selector:\n    app: svc\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  annotations:\n    config.linkerd.io/opaque-ports: \"22122\"\n  name: opaque-service-test\nspec:\n  selector:\n    app: svc\n  ports:\n  - port: 22122\n    targetPort: 22122\n"
  },
  {
    "path": "test/integration/install/install_test.go",
    "content": "package test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n\t\"github.com/linkerd/linkerd2/pkg/tree\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\n//////////////////////\n///   TEST SETUP   ///\n//////////////////////\n\nvar (\n\tTestHelper *testutil.TestHelper\n)\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\tos.Exit(m.Run())\n}\n\nvar (\n\tconfigMapUID string\n\n\thelmTLSCerts *tls.CA\n\n\tlinkerdSvcEdge = []testutil.Service{\n\t\t{Namespace: \"linkerd\", Name: \"linkerd-dst\"},\n\t\t{Namespace: \"linkerd\", Name: \"linkerd-identity\"},\n\n\t\t{Namespace: \"linkerd\", Name: \"linkerd-dst-headless\"},\n\t\t{Namespace: \"linkerd\", Name: \"linkerd-identity-headless\"},\n\t}\n\n\t// Override in case edge starts to deviate from stable service-wise\n\tlinkerdSvcStable = linkerdSvcEdge\n\n\t// skippedInboundPorts lists some ports to be marked as skipped, which will\n\t// be verified in test/integration/inject\n\tskippedInboundPorts  = \"1234,5678\"\n\tskippedOutboundPorts = \"1234,5678\"\n\tvizExtensionName     = \"viz\"\n)\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\n// Tests are executed in serial in the order defined\n// Later tests depend on the success of earlier tests\n\nfunc TestVersionPreInstall(t *testing.T) {\n\tversion := \"unavailable\"\n\tif TestHelper.UpgradeFromVersion() != \"\" {\n\t\tversion = TestHelper.UpgradeFromVersion()\n\t}\n\n\terr := TestHelper.CheckVersion(version)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"Version command failed\", \"Version command failed\\n%s\", err.Error())\n\t}\n}\n\nfunc TestCheckPreInstall(t *testing.T) {\n\tif TestHelper.ExternalIssuer() {\n\t\tt.Skip(\"Skipping pre-install check for external issuer test\")\n\t}\n\n\tif TestHelper.UpgradeFromVersion() != \"\" {\n\t\tt.Skip(\"Skipping pre-install check for upgrade test\")\n\t}\n\n\tif err := TestHelper.TestCheckPre(); err != nil {\n\t\tt.Fatalf(\"'linkerd check --pre' command failed: %s\", err)\n\t}\n}\n\nfunc TestUpgradeTestAppWorksBeforeUpgrade(t *testing.T) {\n\tif TestHelper.UpgradeFromVersion() != \"\" {\n\t\tctx := context.Background()\n\t\t// make sure app is running\n\t\ttestAppNamespace := \"upgrade-test\"\n\t\tfor _, deploy := range []string{\"emoji\", \"voting\", \"web\"} {\n\t\t\tif err := TestHelper.CheckPods(ctx, testAppNamespace, deploy, 1); err != nil {\n\t\t\t\t//nolint:errorlint\n\t\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t\t} else {\n\t\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif err := testutil.ExerciseTestAppEndpoint(\"/api/list\", testAppNamespace, TestHelper); err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"error exercising test app endpoint before upgrade\",\n\t\t\t\t\"error exercising test app endpoint before upgrade %s\", err)\n\t\t}\n\t} else {\n\t\tt.Skip(\"Skipping for non upgrade test\")\n\t}\n}\n\nfunc TestRetrieveUidPreUpgrade(t *testing.T) {\n\tif TestHelper.UpgradeFromVersion() != \"\" {\n\t\tvar err error\n\t\tconfigMapUID, err = TestHelper.KubernetesHelper.GetConfigUID(context.Background(), TestHelper.GetLinkerdNamespace())\n\t\tif err != nil || configMapUID == \"\" {\n\t\t\ttestutil.AnnotatedFatalf(t, \"error retrieving linkerd-config's uid\",\n\t\t\t\t\"error retrieving linkerd-config's uid: %s\", err)\n\t\t}\n\t}\n}\n\nfunc TestInstallOrUpgradeCli(t *testing.T) {\n\tif TestHelper.GetHelmReleaseName() != \"\" {\n\t\treturn\n\t}\n\n\tvar (\n\t\tcmd  = \"install\"\n\t\targs = []string{\n\t\t\t\"--controller-log-level\", \"debug\",\n\t\t\t\"--set\", fmt.Sprintf(\"proxy.image.version=%s\", TestHelper.GetVersion()),\n\t\t\t\"--skip-inbound-ports\", skippedInboundPorts,\n\t\t\t\"--set\", \"heartbeatSchedule=1 2 3 4 5\",\n\t\t}\n\t\tvizCmd  = []string{\"viz\", \"install\"}\n\t\tvizArgs = []string{\n\t\t\t\"--set\", fmt.Sprintf(\"namespace=%s\", TestHelper.GetVizNamespace()),\n\t\t}\n\t)\n\n\tif TestHelper.GetClusterDomain() != \"cluster.local\" {\n\t\targs = append(args, \"--cluster-domain\", TestHelper.GetClusterDomain())\n\t\tvizArgs = append(vizArgs, \"--set\", fmt.Sprintf(\"clusterDomain=%s\", TestHelper.GetClusterDomain()))\n\t}\n\n\tif policy := TestHelper.DefaultInboundPolicy(); policy != \"\" {\n\t\targs = append(args, \"--set\", \"proxy.defaultInboundPolicy=\"+policy)\n\t}\n\n\tif TestHelper.UpgradeFromVersion() != \"\" {\n\n\t\tcmd = \"upgrade\"\n\t\t// upgrade CRDs and then control-plane\n\t\tout, err := TestHelper.LinkerdRun(cmd, \"--crds\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"'linkerd upgrade config' command failed\", err)\n\t\t}\n\n\t\t// apply CRDs\n\t\t// Limit the pruning only to known resources\n\t\t// that we intend to be delete in this stage to prevent it\n\t\t// from deleting other resources that have the\n\t\t// label\n\t\tout, err = TestHelper.KubectlApplyWithArgs(out, []string{\n\t\t\t\"--prune\",\n\t\t\t\"-l\", \"linkerd.io/control-plane-ns=linkerd\",\n\t\t\t\"--prune-allowlist\", \"rbac.authorization.k8s.io/v1/clusterrole\",\n\t\t\t\"--prune-allowlist\", \"rbac.authorization.k8s.io/v1/clusterrolebinding\",\n\t\t\t\"--prune-allowlist\", \"apiregistration.k8s.io/v1/apiservice\",\n\t\t}...)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"kubectl apply command failed\\n%s\", out)\n\t\t}\n\n\t\t// prepare for upgrade of control-plane\n\t\tedge, err := regexp.Match(`(edge)-([0-9]+\\.[0-9]+\\.[0-9]+)`, []byte(TestHelper.UpgradeFromVersion()))\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"could not match regex\", err)\n\t\t}\n\n\t\tif edge {\n\t\t\targs = append(args, []string{\"--set\", fmt.Sprintf(\"proxyInit.ignoreOutboundPorts=%s\", strings.Replace(skippedOutboundPorts, \",\", \"\\\\,\", 1))}...)\n\t\t} else {\n\t\t\targs = append(args, []string{\"--skip-outbound-ports\", skippedOutboundPorts}...)\n\t\t}\n\t} else {\n\t\terr := TestHelper.InstallGatewayAPI()\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"failed to install gateway-api\", err)\n\t\t}\n\n\t\t// install CRDs first\n\t\texec := append([]string{cmd}, append(args, \"--crds\")...)\n\t\tout, err := TestHelper.LinkerdRun(exec...)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"'linkerd install' command failed\", err)\n\t\t}\n\t\tout, err = TestHelper.KubectlApply(out, \"\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"kubectl apply command failed\\n%s\", out)\n\t\t}\n\t}\n\n\texec := append([]string{cmd}, args...)\n\tout, err := TestHelper.LinkerdRun(exec...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install' command failed\", err)\n\t}\n\n\t// test `linkerd upgrade --from-manifests`\n\tif TestHelper.UpgradeFromVersion() != \"\" {\n\t\tkubeArgs := append([]string{\"--namespace\", TestHelper.GetLinkerdNamespace(), \"get\"}, \"configmaps\", \"-oyaml\")\n\t\tconfigManifests, err := TestHelper.Kubectl(\"\", kubeArgs...)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl get' command failed\",\n\t\t\t\t\"'kubectl get' command failed with %s\\n%s\\n%s\", err, configManifests, kubeArgs)\n\t\t}\n\n\t\tkubeArgs = append([]string{\"--namespace\", TestHelper.GetLinkerdNamespace(), \"get\"}, \"secrets\", \"-oyaml\")\n\t\tsecretManifests, err := TestHelper.Kubectl(\"\", kubeArgs...)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl get' command failed\",\n\t\t\t\t\"'kubectl get' command failed with %s\\n%s\\n%s\", err, secretManifests, kubeArgs)\n\t\t}\n\n\t\tmanifests := configManifests + \"---\\n\" + secretManifests\n\n\t\texec = append(exec, \"--from-manifests\", \"-\")\n\t\tupgradeFromManifests, stderr, err := TestHelper.PipeToLinkerdRun(manifests, exec...)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'linkerd upgrade --from-manifests' command failed\",\n\t\t\t\t\"'linkerd upgrade --from-manifests' command failed with %s\\n%s\\n%s\\n%s\", err, stderr, upgradeFromManifests, manifests)\n\t\t}\n\n\t\tif out != upgradeFromManifests {\n\t\t\t// retry in case it's just a discrepancy in the heartbeat cron schedule\n\t\t\texec := append([]string{cmd}, args...)\n\t\t\tout, err := TestHelper.LinkerdRun(exec...)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, fmt.Sprintf(\"command failed: %v\", exec),\n\t\t\t\t\t\"command failed: %v\\n%s\\n%s\", exec, out, stderr)\n\t\t\t}\n\n\t\t\tif out != upgradeFromManifests {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"manifest upgrade differs from k8s upgrade\",\n\t\t\t\t\t\"manifest upgrade differs from k8s upgrade.\\nk8s upgrade:\\n%s\\nmanifest upgrade:\\n%s\", out, upgradeFromManifests)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Limit the pruning only to known resources\n\t// that we intend to be delete in this stage to prevent it\n\t// from deleting other resources that have the\n\t// label\n\tcmdOut, err := TestHelper.KubectlApplyWithArgs(out, []string{\n\t\t\"--prune\",\n\t\t\"-l\", \"linkerd.io/control-plane-ns=linkerd\",\n\t\t\"--prune-allowlist\", \"apps/v1/deployment\",\n\t\t\"--prune-allowlist\", \"core/v1/service\",\n\t\t\"--prune-allowlist\", \"core/v1/configmap\",\n\t}...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", cmdOut)\n\t}\n\n\tTestHelper.WaitRollout(t, testutil.LinkerdDeployReplicasEdge)\n\n\t// It is necessary to clone LinkerdVizDeployReplicas so that we do not\n\t// mutate its original value.\n\texpectedDeployments := make(map[string]testutil.DeploySpec)\n\tfor k, v := range testutil.LinkerdVizDeployReplicas {\n\t\texpectedDeployments[k] = v\n\t}\n\n\t// Install Linkerd Viz Extension\n\tif TestHelper.UpgradeFromVersion() != \"\" {\n\t\texec = append(vizCmd, vizArgs...)\n\t\tout, err = TestHelper.LinkerdRun(exec...)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"'linkerd viz install' command failed\", err)\n\t\t}\n\n\t\tout, err = TestHelper.KubectlApplyWithArgs(out, []string{\n\t\t\t\"--prune\",\n\t\t\t\"-l\", \"linkerd.io/extension=viz\",\n\t\t}...)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t\t}\n\n\t\tTestHelper.WaitRollout(t, expectedDeployments)\n\t}\n\n\t// Install Linkerd Viz Extension\n\texec = append(vizCmd, vizArgs...)\n\tout, err = TestHelper.LinkerdRun(exec...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd viz install' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithArgs(out, []string{\n\t\t\"--prune\",\n\t\t\"-l\", \"linkerd.io/extension=viz\",\n\t}...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\tTestHelper.WaitRollout(t, expectedDeployments)\n\n}\n\n// helmInstallFlags returns the flags required for the `helm install` command,\n// both for the linkerd-control-plane and the linkerd-viz charts\nfunc helmInstallFlags(root *tls.CA) ([]string, []string) {\n\tcoreArgs := []string{\n\t\t\"--set\", \"controllerLogLevel=debug\",\n\t\t\"--set\", \"linkerdVersion=\" + TestHelper.UpgradeHelmFromVersion(),\n\t\t\"--set\", \"proxy.image.version=\" + TestHelper.UpgradeHelmFromVersion(),\n\t\t\"--set\", \"identityTrustDomain=cluster.local\",\n\t\t\"--set\", \"identityTrustAnchorsPEM=\" + root.Cred.Crt.EncodeCertificatePEM(),\n\t\t\"--set\", \"identity.issuer.tls.crtPEM=\" + root.Cred.Crt.EncodeCertificatePEM(),\n\t\t\"--set\", \"identity.issuer.tls.keyPEM=\" + root.Cred.EncodePrivateKeyPEM(),\n\t\t\"--set\", \"identity.issuer.crtExpiry=\" + root.Cred.Crt.Certificate.NotAfter.Format(time.RFC3339),\n\t}\n\tvizArgs := []string{\n\t\t\"--namespace\", TestHelper.GetVizNamespace(),\n\t\t\"--create-namespace\",\n\t\t\"--set\", \"linkerdVersion=\" + TestHelper.UpgradeHelmFromVersion(),\n\t}\n\treturn coreArgs, vizArgs\n}\n\n// helmUpgradeFlags returns the flags required for the `helm upgrade` command,\n// both for the linkerd-control-plane and the linkerd-viz charts\nfunc helmUpgradeFlags(root *tls.CA) ([]string, []string) {\n\tskippedInboundPortsEscaped := strings.Replace(skippedInboundPorts, \",\", \"\\\\,\", 1)\n\tcoreArgs := []string{\n\t\t\"--set\", \"controllerLogLevel=debug\",\n\t\t\"--set\", \"linkerdVersion=\" + TestHelper.GetVersion(),\n\t\t// these ports will get verified in test/integration/inject\n\t\t\"--set\", \"proxyInit.ignoreInboundPorts=\" + skippedInboundPortsEscaped,\n\t\t\"--set\", \"identityTrustAnchorsPEM=\" + root.Cred.Crt.EncodeCertificatePEM(),\n\t\t\"--set\", \"identity.issuer.tls.crtPEM=\" + root.Cred.Crt.EncodeCertificatePEM(),\n\t\t\"--set\", \"identity.issuer.tls.keyPEM=\" + root.Cred.EncodePrivateKeyPEM(),\n\t}\n\tvizArgs := []string{\n\t\t\"--namespace\", TestHelper.GetVizNamespace(),\n\t\t\"--set\", \"linkerdVersion=\" + TestHelper.GetVersion(),\n\t}\n\n\tif override := os.Getenv(flags.EnvOverrideDockerRegistry); override != \"\" {\n\t\tcoreArgs = append(coreArgs,\n\t\t\t\"--set\", \"proxy.image.name=\"+cmd.RegistryOverride(\"cr.l5d.io/linkerd/proxy\", override),\n\t\t\t\"--set\", \"controllerImage=\"+cmd.RegistryOverride(\"cr.l5d.io/linkerd/controller\", override),\n\t\t\t\"--set\", \"debugContainer.image.name=\"+cmd.RegistryOverride(\"cr.l5d.io/linkerd/debug\", override),\n\t\t)\n\t\tvizArgs = append(vizArgs,\n\t\t\t\"--set\", \"metricsAPI.image.registry=\"+override,\n\t\t\t\"--set\", \"tap.image.registry=\"+override,\n\t\t\t\"--set\", \"tapInjector.image.registry=\"+override,\n\t\t\t\"--set\", \"dashboard.image.registry=\"+override,\n\t\t)\n\t}\n\n\treturn coreArgs, vizArgs\n}\n\nfunc TestInstallHelm(t *testing.T) {\n\tif TestHelper.GetHelmReleaseName() == \"\" {\n\t\treturn\n\t}\n\n\tcn := fmt.Sprintf(\"identity.%s.cluster.local\", TestHelper.GetLinkerdNamespace())\n\tvar err error\n\thelmTLSCerts, err = tls.GenerateRootCAWithDefaults(cn)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to generate root certificate for identity\",\n\t\t\t\"failed to generate root certificate for identity: %s\", err)\n\t}\n\n\targs, vizArgs := helmInstallFlags(helmTLSCerts)\n\n\treleaseName := TestHelper.GetHelmReleaseName() + \"-crds\"\n\tif stdout, stderr, err := TestHelper.HelmInstall(\"linkerd/linkerd-crds\", releaseName, args...); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'helm install' command failed\",\n\t\t\t\"'helm install' command failed\\n%s\\n%s\", stdout, stderr)\n\t}\n\n\treleaseName = TestHelper.GetHelmReleaseName() + \"-control-plane\"\n\tif stdout, stderr, err := TestHelper.HelmInstall(\"linkerd/linkerd-control-plane\", releaseName, args...); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'helm install' command failed\",\n\t\t\t\"'helm install' command failed\\n%s\\n%s\", stdout, stderr)\n\t}\n\tTestHelper.WaitRollout(t, testutil.LinkerdDeployReplicasEdge)\n\n\treleaseName = TestHelper.GetHelmReleaseName() + \"-l5d-viz\"\n\tif stdout, stderr, err := TestHelper.HelmCmdPlain(\"install\", \"linkerd/linkerd-viz\", releaseName, vizArgs...); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'helm install' command failed\",\n\t\t\t\"'helm install' command failed\\n%s\\n%s\", stdout, stderr)\n\t}\n\n\tTestHelper.WaitRollout(t, testutil.LinkerdVizDeployReplicas)\n}\n\nfunc TestControlPlaneResourcesPostInstall(t *testing.T) {\n\texpectedServices := linkerdSvcEdge\n\texpectedDeployments := testutil.LinkerdDeployReplicasEdge\n\tif !TestHelper.ExternalPrometheus() {\n\t\tvizServices := []testutil.Service{\n\t\t\t{Namespace: \"linkerd-viz\", Name: \"web\"},\n\t\t\t{Namespace: \"linkerd-viz\", Name: \"tap\"},\n\t\t\t{Namespace: \"linkerd-viz\", Name: \"prometheus\"},\n\t\t}\n\t\texpectedServices = append(expectedServices, vizServices...)\n\t\texpectedDeployments[\"prometheus\"] = testutil.DeploySpec{Namespace: \"linkerd-viz\", Replicas: 1}\n\t}\n\n\t// Upgrade Case\n\tif TestHelper.UpgradeHelmFromVersion() != \"\" {\n\t\texpectedServices = linkerdSvcStable\n\t\texpectedDeployments = testutil.LinkerdDeployReplicasStable\n\t}\n\ttestutil.TestResourcesPostInstall(TestHelper.GetLinkerdNamespace(), expectedServices, expectedDeployments, TestHelper, t)\n}\n\nfunc TestUpgradeHelm(t *testing.T) {\n\tif TestHelper.UpgradeHelmFromVersion() == \"\" {\n\t\tt.Skip(\"Skipping as this is not a helm upgrade test\")\n\t}\n\n\targs := []string{\n\t\t// implicit as at least one value is set manually: \"--reset-values\",\n\t\t// (see https://medium.com/@kcatstack/understand-helm-upgrade-flags-reset-values-reuse-values-6e58ac8f127e )\n\n\t\t// Also ensure that the CPU requests are fairly small (<100m) in order\n\t\t// to avoid squeeze-out of other pods in CI tests.\n\n\t\t\"--set\", \"proxy.resources.cpu.limit=200m\",\n\t\t\"--set\", \"proxy.resources.cpu.request=20m\",\n\t\t\"--set\", \"proxy.resources.memory.limit=200Mi\",\n\t\t\"--set\", \"proxy.resources.memory.request=100Mi\",\n\t\t// actually sets the value for the controller pod\n\t\t\"--set\", \"destinationProxyResources.cpu.limit=1020m\",\n\t\t\"--set\", \"destinationProxyResources.memory.request=102Mi\",\n\t\t\"--set\", \"identityProxyResources.cpu.limit=1040m\",\n\t\t\"--set\", \"identityProxyResources.memory.request=104Mi\",\n\t\t\"--set\", \"proxyInjectorProxyResources.cpu.limit=1060m\",\n\t\t\"--set\", \"proxyInjectorProxyResources.memory.request=106Mi\",\n\t\t\"--atomic\",\n\t\t\"--timeout\", \"5m\",\n\t\t\"--wait\",\n\t}\n\textraArgs, vizArgs := helmUpgradeFlags(helmTLSCerts)\n\targs = append(args, extraArgs...)\n\treleaseName := TestHelper.GetHelmReleaseName() + \"-crds\"\n\tif stdout, stderr, err := TestHelper.HelmUpgrade(TestHelper.GetHelmCharts()+\"/linkerd-crds\", releaseName, args...); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'helm upgrade' command failed\",\n\t\t\t\"'helm upgrade' command failed\\n%s\\n%s\", stdout, stderr)\n\t}\n\n\treleaseName = TestHelper.GetHelmReleaseName() + \"-control-plane\"\n\tif stdout, stderr, err := TestHelper.HelmUpgrade(TestHelper.GetHelmCharts()+\"/linkerd-control-plane\", releaseName, args...); err != nil {\n\t\tTestHelper.WaitRollout(t, testutil.LinkerdDeployReplicasEdge)\n\t\ttestutil.AnnotatedFatalf(t, \"'helm upgrade' command failed\",\n\t\t\t\"'helm upgrade' command failed\\n%s\\n%s\", stdout, stderr)\n\t}\n\tTestHelper.WaitRollout(t, testutil.LinkerdVizDeployReplicas)\n\n\tvizChart := TestHelper.GetLinkerdVizHelmChart()\n\treleaseName = TestHelper.GetHelmReleaseName() + \"-l5d-viz\"\n\tif stdout, stderr, err := TestHelper.HelmCmdPlain(\"upgrade\", vizChart, releaseName, vizArgs...); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'helm upgrade' command failed\",\n\t\t\t\"'helm upgrade' command failed\\n%s\\n%s\", stdout, stderr)\n\t}\n\n\tTestHelper.WaitRollout(t, testutil.LinkerdVizDeployReplicas)\n\n\tTestHelper.AddInstalledExtension(vizExtensionName)\n}\n\nfunc TestRetrieveUidPostUpgrade(t *testing.T) {\n\tif TestHelper.UpgradeFromVersion() != \"\" {\n\t\tnewConfigMapUID, err := TestHelper.KubernetesHelper.GetConfigUID(context.Background(), TestHelper.GetLinkerdNamespace())\n\t\tif err != nil || newConfigMapUID == \"\" {\n\t\t\ttestutil.AnnotatedFatalf(t, \"error retrieving linkerd-config's uid\",\n\t\t\t\t\"error retrieving linkerd-config's uid: %s\", err)\n\t\t}\n\t\tif configMapUID != newConfigMapUID {\n\t\t\ttestutil.AnnotatedFatalf(t, \"linkerd-config's uid after upgrade doesn't match its value before the upgrade\",\n\t\t\t\t\"linkerd-config's uid after upgrade [%s] doesn't match its value before the upgrade [%s]\",\n\t\t\t\tnewConfigMapUID, configMapUID,\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestOverridesSecret(t *testing.T) {\n\n\tif TestHelper.GetHelmReleaseName() != \"\" {\n\t\tt.Skip(\"Skipping as this is a helm test where linkerd-config-overrides is absent\")\n\t}\n\n\tconfigOverridesSecret, err := TestHelper.KubernetesHelper.GetSecret(context.Background(), TestHelper.GetLinkerdNamespace(), \"linkerd-config-overrides\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"could not retrieve linkerd-config-overrides\",\n\t\t\t\"could not retrieve linkerd-config-overrides\\n%s\", err)\n\t}\n\n\toverrides := configOverridesSecret.Data[\"linkerd-config-overrides\"]\n\toverridesTree, err := tree.BytesToTree(overrides)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"could not retrieve linkerd-config-overrides\",\n\t\t\t\"could not retrieve linkerd-config-overrides\\n%s\", err)\n\t}\n\n\t// Check for fields that were added during install\n\ttestCases := []struct {\n\t\tpath  []string\n\t\tvalue string\n\t}{\n\t\t{\n\t\t\t[]string{\"controllerLogLevel\"},\n\t\t\t\"debug\",\n\t\t},\n\t\t{\n\t\t\t[]string{\"proxyInit\", \"ignoreInboundPorts\"},\n\t\t\tskippedInboundPorts,\n\t\t},\n\t}\n\n\t// Check for fields that were added during upgrade\n\tif TestHelper.UpgradeFromVersion() != \"\" {\n\t\ttestCases = append(testCases, []struct {\n\t\t\tpath  []string\n\t\t\tvalue string\n\t\t}{\n\t\t\t{\n\t\t\t\t[]string{\"proxyInit\", \"ignoreOutboundPorts\"},\n\t\t\t\tskippedOutboundPorts,\n\t\t\t},\n\t\t}...)\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%s: %s\", strings.Join(tc.path, \"/\"), tc.value), func(t *testing.T) {\n\t\t\tfinalValue, err := overridesTree.GetString(tc.path...)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"could not perform tree.GetString\",\n\t\t\t\t\t\"could not perform tree.GetString\\n%s\", err)\n\t\t\t}\n\n\t\t\tif tc.value != finalValue {\n\t\t\t\ttestutil.AnnotatedFatalf(t, fmt.Sprintf(\"Values at path %s do not match\", strings.Join(tc.path, \"/\")),\n\t\t\t\t\t\"Expected value at [%s] to be [%s] but received [%s]\",\n\t\t\t\t\tstrings.Join(tc.path, \"/\"), tc.value, finalValue)\n\t\t\t}\n\t\t})\n\t}\n\n\textractValue := func(t *testing.T, path ...string) string {\n\t\tval, err := overridesTree.GetString(path...)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"error calling overridesTree.GetString()\",\n\t\t\t\t\"error calling overridesTree.GetString(): %s\", err)\n\t\t\treturn \"\"\n\n\t\t}\n\t\treturn val\n\t}\n\n\tt.Run(\"Check if any unknown fields sneaked in\", func(t *testing.T) {\n\t\tknownKeys := tree.Tree{\n\t\t\t\"controllerLogLevel\": \"debug\",\n\t\t\t\"heartbeatSchedule\":  \"1 2 3 4 5\",\n\t\t\t\"identity\": tree.Tree{\n\t\t\t\t\"issuer\": tree.Tree{},\n\t\t\t},\n\t\t\t\"identityTrustAnchorsPEM\": extractValue(t, \"identityTrustAnchorsPEM\"),\n\t\t\t\"proxyInit\": tree.Tree{\n\t\t\t\t\"ignoreInboundPorts\": skippedInboundPorts,\n\t\t\t},\n\t\t\t\"proxy\": tree.Tree{\n\t\t\t\t\"image\": tree.Tree{\n\t\t\t\t\t\"version\": TestHelper.GetVersion(),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tif reg := os.Getenv(flags.EnvOverrideDockerRegistry); reg != \"\" {\n\t\t\tknownKeys[\"controllerImage\"] = reg + \"/controller\"\n\t\t\tknownKeys[\"debugContainer\"] = tree.Tree{\n\t\t\t\t\"image\": tree.Tree{\n\t\t\t\t\t\"name\": reg + \"/debug\",\n\t\t\t\t},\n\t\t\t}\n\t\t\tknownKeys[\"proxy\"] = tree.Tree{\n\t\t\t\t\"image\": tree.Tree{\n\t\t\t\t\t\"name\":    reg + \"/proxy\",\n\t\t\t\t\t\"version\": TestHelper.GetVersion(),\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\t// Check for fields that were added during upgrade\n\t\tif TestHelper.UpgradeFromVersion() != \"\" {\n\t\t\tknownKeys[\"proxyInit\"].(tree.Tree)[\"ignoreOutboundPorts\"] = skippedOutboundPorts\n\t\t}\n\n\t\tif TestHelper.GetClusterDomain() != \"cluster.local\" {\n\t\t\tknownKeys[\"clusterDomain\"] = TestHelper.GetClusterDomain()\n\t\t}\n\n\t\tif TestHelper.ExternalIssuer() {\n\t\t\tknownKeys[\"identity\"].(tree.Tree)[\"issuer\"].(tree.Tree)[\"issuanceLifetime\"] = \"15s\"\n\t\t\tknownKeys[\"identity\"].(tree.Tree)[\"issuer\"].(tree.Tree)[\"scheme\"] = \"kubernetes.io/tls\"\n\t\t} else {\n\t\t\tknownKeys[\"identity\"].(tree.Tree)[\"issuer\"].(tree.Tree)[\"tls\"] = tree.Tree{\n\t\t\t\t\"crtPEM\": extractValue(t, \"identity\", \"issuer\", \"tls\", \"crtPEM\"),\n\t\t\t\t\"keyPEM\": extractValue(t, \"identity\", \"issuer\", \"tls\", \"keyPEM\"),\n\t\t\t}\n\t\t}\n\n\t\tif TestHelper.CNI() {\n\t\t\tknownKeys[\"cniEnabled\"] = true\n\t\t}\n\n\t\tif policy := TestHelper.DefaultInboundPolicy(); policy != \"\" {\n\t\t\tknownKeys[\"proxy\"].(tree.Tree)[\"defaultInboundPolicy\"] = policy\n\t\t}\n\n\t\t// Check if the keys in overridesTree match with knownKeys\n\t\tif diff := deep.Equal(overridesTree.String(), knownKeys.String()); diff != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"Overrides and knownKeys are different\", \"%+v\", diff)\n\t\t}\n\t})\n}\n\ntype expectedData struct {\n\tpod        string\n\tcpuLimit   string\n\tcpuRequest string\n\tmemLimit   string\n\tmemRequest string\n}\n\nvar expectedResources = []expectedData{\n\t{\n\t\tpod:        \"linkerd-destination\",\n\t\tcpuLimit:   \"1020m\",\n\t\tcpuRequest: \"20m\",\n\t\tmemLimit:   \"200Mi\",\n\t\tmemRequest: \"102Mi\",\n\t},\n\t{\n\t\tpod:        \"linkerd-identity\",\n\t\tcpuLimit:   \"1040m\",\n\t\tcpuRequest: \"20m\",\n\t\tmemLimit:   \"200Mi\",\n\t\tmemRequest: \"104Mi\",\n\t},\n\t{\n\t\tpod:        \"linkerd-proxy-injector\",\n\t\tcpuLimit:   \"1060m\",\n\t\tcpuRequest: \"20m\",\n\t\tmemLimit:   \"200Mi\",\n\t\tmemRequest: \"106Mi\",\n\t},\n}\n\nfunc TestComponentProxyResources(t *testing.T) {\n\tif TestHelper.UpgradeHelmFromVersion() == \"\" {\n\t\tt.Skip(\"Skipping as this is not a helm upgrade test\")\n\t}\n\n\tfor _, expected := range expectedResources {\n\t\tresourceReqs, err := TestHelper.GetResources(context.Background(), \"linkerd-proxy\", expected.pod, TestHelper.GetLinkerdNamespace())\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"setting proxy resources failed\", \"Error retrieving resource requirements for %s: %s\", expected.pod, err)\n\t\t}\n\n\t\tcpuLimitStr := resourceReqs.Limits.Cpu().String()\n\t\tif cpuLimitStr != expected.cpuLimit {\n\t\t\ttestutil.AnnotatedFatalf(t, \"setting proxy resources failed\", \"unexpected %s CPU limit: expected %s, was %s\", expected.pod, expected.cpuLimit, cpuLimitStr)\n\t\t}\n\t\tcpuRequestStr := resourceReqs.Requests.Cpu().String()\n\t\tif cpuRequestStr != expected.cpuRequest {\n\t\t\ttestutil.AnnotatedFatalf(t, \"setting proxy resources failed\", \"unexpected %s CPU request: expected %s, was %s\", expected.pod, expected.cpuRequest, cpuRequestStr)\n\t\t}\n\t\tmemLimitStr := resourceReqs.Limits.Memory().String()\n\t\tif memLimitStr != expected.memLimit {\n\t\t\ttestutil.AnnotatedFatalf(t, \"setting proxy resources failed\", \"unexpected %s memory limit: expected %s, was %s\", expected.pod, expected.memLimit, memLimitStr)\n\t\t}\n\t\tmemRequestStr := resourceReqs.Requests.Memory().String()\n\t\tif memRequestStr != expected.memRequest {\n\t\t\ttestutil.AnnotatedFatalf(t, \"setting proxy resources failed\", \"unexpected %s memory request: expected %s, was %s\", expected.pod, expected.memRequest, memRequestStr)\n\t\t}\n\t}\n}\n\nfunc TestVersionPostInstall(t *testing.T) {\n\terr := TestHelper.CheckVersion(TestHelper.GetVersion())\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"Version command failed\",\n\t\t\t\"Version command failed\\n%s\", err.Error())\n\t}\n}\n\nfunc TestCheckPostInstall(t *testing.T) {\n\tif err := TestHelper.TestCheckProxy(TestHelper.GetVersion(), TestHelper.GetLinkerdNamespace()); err != nil {\n\t\tt.Fatalf(\"'linkerd check --proxy' command failed: %s\", err)\n\t}\n}\n\nfunc TestUpgradeTestAppWorksAfterUpgrade(t *testing.T) {\n\tif TestHelper.UpgradeFromVersion() != \"\" {\n\t\ttestAppNamespace := \"upgrade-test\"\n\t\tif err := testutil.ExerciseTestAppEndpoint(\"/api/vote?choice=:policeman:\", testAppNamespace, TestHelper); err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"error exercising test app endpoint after upgrade\",\n\t\t\t\t\"error exercising test app endpoint after upgrade %s\", err)\n\t\t}\n\t} else {\n\t\tt.Skip(\"Skipping for non upgrade test\")\n\t}\n}\n\nfunc TestRestarts(t *testing.T) {\n\texpectedDeployments := testutil.LinkerdDeployReplicasEdge\n\tif !TestHelper.ExternalPrometheus() {\n\t\texpectedDeployments[\"prometheus\"] = testutil.DeploySpec{Namespace: \"linkerd-viz\", Replicas: 1}\n\t}\n\tfor deploy, spec := range expectedDeployments {\n\t\tif err := TestHelper.CheckPods(context.Background(), spec.Namespace, deploy, spec.Replicas); err != nil {\n\t\t\t//nolint:errorlint\n\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t} else {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"CheckPods timed-out\", err)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/integration/install/smoke/install_smoke_test.go",
    "content": "package smoke\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\n//////////////////////\n///   TEST SETUP   ///\n//////////////////////\n\nvar (\n\tTestHelper *testutil.TestHelper\n)\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until control plane pods are running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)\n\tos.Exit(m.Run())\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\nfunc TestSmoke(t *testing.T) {\n\tctx := context.Background()\n\tTestHelper.WithDataPlaneNamespace(ctx, \"smoke-test\", map[string]string{}, t, func(t *testing.T, ns string) {\n\t\tcmd := []string{\"inject\", \"testdata/smoke_test.yaml\"}\n\t\tout, injectReport, err := TestHelper.PipeToLinkerdRun(\"\", cmd...)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'linkerd inject' command failed\",\n\t\t\t\t\"'linkerd inject' command failed: %s\\n%s\", err, out)\n\t\t}\n\n\t\terr = TestHelper.ValidateOutput(injectReport, \"inject.report.golden\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"received unexpected output\",\n\t\t\t\t\"received unexpected output\\n%s\", err.Error())\n\t\t}\n\n\t\tout, err = TestHelper.KubectlApply(out, ns)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t\t}\n\n\t\t// Wait for pods to in smoke-test deployment to come up\n\t\tfor _, deploy := range []string{\"smoke-test-terminus\", \"smoke-test-gateway\"} {\n\t\t\tif err := TestHelper.CheckPods(ctx, ns, deploy, 1); err != nil {\n\t\t\t\t//nolint:errorlint\n\t\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t\t} else {\n\t\t\t\t\ttestutil.AnnotatedFatal(t, \"CheckPods timed-out\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Test 'linkerd check --proxy' with the current image version\n\t\tcmd = []string{\"check\", \"--proxy\", \"--expected-version\", TestHelper.GetVersion(), \"--namespace\", ns, \"--wait=5m\"}\n\t\texpected := getCheckOutput(t, \"check.proxy.golden\", TestHelper.GetLinkerdNamespace())\n\n\t\t// Use a short time window for check tests to get rid of transient\n\t\t// errors\n\t\ttimeout := 5 * time.Minute\n\t\terr = testutil.RetryFor(timeout, func() error {\n\t\t\tout, err := TestHelper.LinkerdRun(cmd...)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"'linkerd check' command failed\\n%w\\n%s\", err, out)\n\t\t\t}\n\n\t\t\tif !strings.Contains(out, expected) {\n\t\t\t\treturn fmt.Errorf(\n\t\t\t\t\t\"Expected:\\n%s\\nActual:\\n%s\", expected, out)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"'linkerd check' command timed-out (%s)\", timeout), err)\n\t\t}\n\n\t\t// Test traffic from smoke-test client to server\n\t\turl, err := TestHelper.URLFor(ctx, ns, \"smoke-test-gateway\", 8080)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to get URL\",\n\t\t\t\t\"failed to get URL: %s\", err)\n\t\t}\n\n\t\toutput, err := TestHelper.HTTPGetURL(url)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\",\n\t\t\t\t\"unexpected error: %v %s\", err, output)\n\t\t}\n\n\t\texpectedStringInPayload := \"\\\"payload\\\":\\\"BANANA\\\"\"\n\t\tif !strings.Contains(output, expectedStringInPayload) {\n\t\t\ttestutil.AnnotatedFatalf(t, \"application response doesn't contain the expected response\",\n\t\t\t\t\"expected application response to contain string [%s], but it was [%s]\",\n\t\t\t\texpectedStringInPayload, output)\n\t\t}\n\t})\n\n}\n\nfunc getCheckOutput(t *testing.T, goldenFile string, namespace string) string {\n\tpods, err := TestHelper.KubernetesHelper.GetPods(context.Background(), namespace, nil)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"failed to retrieve pods: %s\", err), err)\n\t}\n\n\tproxyVersionErr := \"\"\n\terr = healthcheck.CheckProxyVersionsUpToDate(pods, version.Channels{})\n\tif err != nil {\n\t\tproxyVersionErr = err.Error()\n\t}\n\n\ttpl := template.Must(template.ParseFiles(\"testdata\" + \"/\" + goldenFile))\n\tvars := struct {\n\t\tProxyVersionErr string\n\t\tHintURL         string\n\t}{\n\t\tproxyVersionErr,\n\t\thealthcheck.HintBaseURL(TestHelper.GetVersion()),\n\t}\n\n\tvar expected bytes.Buffer\n\tif err := tpl.Execute(&expected, vars); err != nil {\n\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"failed to parse %s template: %s\", goldenFile, err), err)\n\t}\n\n\treturn expected.String()\n}\n"
  },
  {
    "path": "test/integration/install/smoke/testdata/check.proxy.golden",
    "content": "kubernetes-api\n--------------\n√ can initialize the client\n√ can query the Kubernetes API\n\nkubernetes-version\n------------------\n√ is running the minimum Kubernetes API version\n\nlinkerd-existence\n-----------------\n√ 'linkerd-config' config map exists\n√ heartbeat ServiceAccount exist\n√ control plane replica sets are ready\n√ no unschedulable pods\n√ control plane pods are ready\n√ cluster networks contains all node podCIDRs\n√ cluster networks contains all pods\n√ cluster networks contains all services\n\nlinkerd-config\n--------------\n√ control plane Namespace exists\n√ control plane ClusterRoles exist\n√ control plane ClusterRoleBindings exist\n√ control plane ServiceAccounts exist\n√ control plane CustomResourceDefinitions exist\n√ control plane MutatingWebhookConfigurations exist\n√ control plane ValidatingWebhookConfigurations exist\n√ proxy-init container runs as root user if docker container runtime is used\n\nlinkerd-identity\n----------------\n√ certificate config is valid\n√ trust anchors are using supported crypto algorithm\n√ trust anchors are within their validity period\n√ trust anchors are valid for at least 60 days\n√ issuer cert is using supported crypto algorithm\n√ issuer cert is within its validity period\n√ issuer cert is valid for at least 60 days\n√ issuer cert is issued by the trust anchor\n\nlinkerd-webhooks-and-apisvc-tls\n-------------------------------\n√ proxy-injector webhook has valid cert\n√ proxy-injector cert is valid for at least 60 days\n√ sp-validator webhook has valid cert\n√ sp-validator cert is valid for at least 60 days\n√ policy-validator webhook has valid cert\n√ policy-validator cert is valid for at least 60 days\n\nlinkerd-identity-data-plane\n---------------------------\n√ data plane proxies certificate match CA\n\nlinkerd-version\n---------------\n√ can determine the latest version\n√ cli is up-to-date\n\nlinkerd-control-plane-proxy\n---------------------------\n√ control plane proxies are healthy\n√ control plane proxies are up-to-date\n√ control plane proxies and cli versions match\n\nlinkerd-data-plane\n------------------\n√ data plane namespace exists\n√ data plane proxies are ready\n√ data plane is up-to-date\n√ data plane and cli versions match\n√ data plane pod labels are configured correctly\n√ data plane service labels are configured correctly\n√ data plane service annotations are configured correctly\n√ opaque ports are properly annotated\n"
  },
  {
    "path": "test/integration/install/smoke/testdata/inject.report.golden",
    "content": "\ndeployment \"smoke-test-terminus\" injected\nservice \"smoke-test-terminus-svc\" skipped\nserver \"smoke-test-terminus\" skipped\nserverauthorization \"smoke-test-terminus\" skipped\ndeployment \"smoke-test-gateway\" injected\nservice \"smoke-test-gateway-svc\" skipped\nserver \"smoke-test-proxy-admin\" skipped\nserverauthorization \"smoke-test-proxy-admin\" skipped\n\n"
  },
  {
    "path": "test/integration/install/smoke/testdata/smoke_test.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: smoke-test-terminus\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: smoke-test-terminus\n  template:\n    metadata:\n      labels:\n        app: smoke-test-terminus\n    spec:\n      containers:\n      - name: http-to-grpc\n        image: buoyantio/bb:v0.0.6\n        args: [\"terminus\", \"--grpc-server-port\", \"9090\", \"--response-text\", \"BANANA\"]\n        ports:\n        - containerPort: 9090\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: smoke-test-terminus-svc\nspec:\n  selector:\n    app: smoke-test-terminus\n  ports:\n  - name: grpc\n    port: 9090\n    targetPort: 9090\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  name: smoke-test-terminus\nspec:\n  podSelector:\n    matchLabels:\n      app: smoke-test-terminus\n  port: 9090\n  proxyProtocol: gRPC\n---\napiVersion: policy.linkerd.io/v1beta1\nkind: ServerAuthorization\nmetadata:\n  name: smoke-test-terminus\nspec:\n  server:\n    name: smoke-test-terminus\n  client:\n    meshTLS:\n      serviceAccounts:\n      - name: default\n        namespace: linkerd-smoke-test\n      - name: default\n        namespace: linkerd-smoke-test-manual\n      - name: default\n        namespace: linkerd-smoke-test-ann\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: smoke-test-gateway\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: smoke-test-gateway\n  template:\n    metadata:\n      labels:\n        app: smoke-test-gateway\n    spec:\n      containers:\n      - name: http-to-grpc\n        image: buoyantio/bb:v0.0.6\n        args: [\"point-to-point-channel\", \"--grpc-downstream-server\", \"smoke-test-terminus-svc:9090\", \"--h1-server-port\", \"8080\"]\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: smoke-test-gateway-svc\nspec:\n  selector:\n    app: smoke-test-gateway\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  name: smoke-test-proxy-admin\nspec:\n  podSelector:\n    matchExpressions:\n    - key: app\n      operator: In\n      values:\n      - smoke-test-terminus\n      - smoke-test-gateway\n  port: linkerd-admin\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1beta1\nkind: ServerAuthorization\nmetadata:\n  name: smoke-test-proxy-admin\nspec:\n  server:\n    name: smoke-test-proxy-admin\n  client:\n    # for kubelet probes\n    unauthenticated: true\n"
  },
  {
    "path": "test/integration/install/testdata/check.proxy.golden",
    "content": "kubernetes-api\n--------------\n√ can initialize the client\n√ can query the Kubernetes API\n\nkubernetes-version\n------------------\n√ is running the minimum Kubernetes API version\n\nlinkerd-existence\n-----------------\n√ 'linkerd-config' config map exists\n√ heartbeat ServiceAccount exist\n√ control plane replica sets are ready\n√ no unschedulable pods\n√ control plane pods are ready\n√ cluster networks contains all node podCIDRs\n√ cluster networks contains all pods\n√ cluster networks contains all services\n\nlinkerd-config\n--------------\n√ control plane Namespace exists\n√ control plane ClusterRoles exist\n√ control plane ClusterRoleBindings exist\n√ control plane ServiceAccounts exist\n√ control plane CustomResourceDefinitions exist\n√ control plane MutatingWebhookConfigurations exist\n√ control plane ValidatingWebhookConfigurations exist\n√ proxy-init container runs as root user if docker container runtime is used\n\nlinkerd-identity\n----------------\n√ certificate config is valid\n√ trust anchors are using supported crypto algorithm\n√ trust anchors are within their validity period\n√ trust anchors are valid for at least 60 days\n√ issuer cert is using supported crypto algorithm\n√ issuer cert is within its validity period\n√ issuer cert is valid for at least 60 days\n√ issuer cert is issued by the trust anchor\n\nlinkerd-webhooks-and-apisvc-tls\n-------------------------------\n√ proxy-injector webhook has valid cert\n√ proxy-injector cert is valid for at least 60 days\n√ sp-validator webhook has valid cert\n√ sp-validator cert is valid for at least 60 days\n√ policy-validator webhook has valid cert\n√ policy-validator cert is valid for at least 60 days\n\nlinkerd-identity-data-plane\n---------------------------\n√ data plane proxies certificate match CA\n\nlinkerd-version\n---------------\n√ can determine the latest version\n√ cli is up-to-date\n\nlinkerd-control-plane-proxy\n---------------------------\n√ control plane proxies are healthy\n√ control plane proxies are up-to-date\n√ control plane proxies and cli versions match\n\nlinkerd-data-plane\n------------------\n√ data plane namespace exists\n√ data plane proxies are ready\n√ data plane is up-to-date\n√ data plane and cli versions match\n√ data plane pod labels are configured correctly\n√ data plane service labels are configured correctly\n√ data plane service annotations are configured correctly\n√ opaque ports are properly annotated\n"
  },
  {
    "path": "test/integration/install/testdata/inject.report.golden",
    "content": "\ndeployment \"smoke-test-terminus\" injected\nservice \"smoke-test-terminus-svc\" skipped\nserver \"smoke-test-terminus\" skipped\nserverauthorization \"smoke-test-terminus\" skipped\ndeployment \"smoke-test-gateway\" injected\nservice \"smoke-test-gateway-svc\" skipped\nserver \"smoke-test-proxy-admin\" skipped\nserverauthorization \"smoke-test-proxy-admin\" skipped\n\n"
  },
  {
    "path": "test/integration/install/testdata/smoke_test.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: smoke-test-terminus\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: smoke-test-terminus\n  template:\n    metadata:\n      labels:\n        app: smoke-test-terminus\n    spec:\n      containers:\n      - name: http-to-grpc\n        image: buoyantio/bb:v0.0.6\n        args: [\"terminus\", \"--grpc-server-port\", \"9090\", \"--response-text\", \"BANANA\"]\n        ports:\n        - containerPort: 9090\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: smoke-test-terminus-svc\nspec:\n  selector:\n    app: smoke-test-terminus\n  ports:\n  - name: grpc\n    port: 9090\n    targetPort: 9090\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  name: smoke-test-terminus\nspec:\n  podSelector:\n    matchLabels:\n      app: smoke-test-terminus\n  port: 9090\n  proxyProtocol: gRPC\n---\napiVersion: policy.linkerd.io/v1beta1\nkind: ServerAuthorization\nmetadata:\n  name: smoke-test-terminus\nspec:\n  server:\n    name: smoke-test-terminus\n  client:\n    meshTLS:\n      serviceAccounts:\n      - name: default\n        namespace: linkerd-smoke-test\n      - name: default\n        namespace: linkerd-smoke-test-manual\n      - name: default\n        namespace: linkerd-smoke-test-ann\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: smoke-test-gateway\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: smoke-test-gateway\n  template:\n    metadata:\n      labels:\n        app: smoke-test-gateway\n    spec:\n      containers:\n      - name: http-to-grpc\n        image: buoyantio/bb:v0.0.6\n        args: [\"point-to-point-channel\", \"--grpc-downstream-server\", \"smoke-test-terminus-svc:9090\", \"--h1-server-port\", \"8080\"]\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: smoke-test-gateway-svc\nspec:\n  selector:\n    app: smoke-test-gateway\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  name: smoke-test-proxy-admin\nspec:\n  podSelector:\n    matchExpressions:\n    - key: app\n      operator: In\n      values:\n      - smoke-test-terminus\n      - smoke-test-gateway\n  port: linkerd-admin\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1beta1\nkind: ServerAuthorization\nmetadata:\n  name: smoke-test-proxy-admin\nspec:\n  server:\n    name: smoke-test-proxy-admin\n  client:\n    # for kubelet probes\n    unauthenticated: true\n"
  },
  {
    "path": "test/integration/install/testdata/upgrade_test.yaml",
    "content": "---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: emoji\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: voting\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: web\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  creationTimestamp: null\n  name: emoji\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: emoji-svc\n  strategy: {}\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: emoji-svc\n    spec:\n      serviceAccountName: emoji\n      containers:\n      - env:\n        - name: GRPC_PORT\n          value: \"8080\"\n        image: buoyantio/emojivoto-emoji-svc:v10\n        name: emoji-svc\n        ports:\n        - containerPort: 8080\n          name: grpc\n        resources:\n          requests:\n            cpu: 100m\nstatus: {}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: emoji-svc\nspec:\n  selector:\n    app: emoji-svc\n  clusterIP: None\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  creationTimestamp: null\n  name: voting\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: voting-svc\n  strategy: {}\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: voting-svc\n    spec:\n      serviceAccountName: voting\n      containers:\n      - env:\n        - name: GRPC_PORT\n          value: \"8080\"\n        image: buoyantio/emojivoto-voting-svc:v10\n        name: voting-svc\n        ports:\n        - containerPort: 8080\n          name: grpc\n        resources:\n          requests:\n            cpu: 100m\nstatus: {}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: voting-svc\nspec:\n  selector:\n    app: voting-svc\n  clusterIP: None\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  creationTimestamp: null\n  name: web\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  strategy: {}\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: web-svc\n    spec:\n      serviceAccountName: web\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"8080\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 8080\n          name: http\n        resources:\n          requests:\n            cpu: 100m\nstatus: {}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: web-svc\nspec:\n  type: LoadBalancer\n  selector:\n    app: web-svc\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n---\n"
  },
  {
    "path": "test/integration/install/uninstall/uninstall_test.go",
    "content": "package uninstall\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\tif !TestHelper.Uninstall() {\n\t\tfmt.Fprintln(os.Stderr, \"Uninstall test disabled\")\n\t\tos.Exit(0)\n\t}\n\tos.Exit(m.Run())\n}\n\nfunc TestInstall(t *testing.T) {\n\terr := TestHelper.InstallGatewayAPI()\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"failed to install gateway-api\", err)\n\t}\n\n\targs := []string{\n\t\t\"install\",\n\t\t\"--crds\",\n\t\t\"--controller-log-level\", \"debug\",\n\t\t\"--proxy-log-level\", \"warn,linkerd=debug\",\n\t\t\"--set\", fmt.Sprintf(\"proxy.image.version=%s\", TestHelper.GetVersion()),\n\t}\n\n\tout, err := TestHelper.LinkerdRun(args...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install --crds' command failed\", err)\n\t}\n\tout, err = TestHelper.KubectlApply(out, \"\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\targs = []string{\n\t\t\"install\",\n\t\t\"--controller-log-level\", \"debug\",\n\t\t\"--proxy-log-level\", \"warn,linkerd=debug\",\n\t\t\"--set\", fmt.Sprintf(\"proxy.image.version=%s\", TestHelper.GetVersion()),\n\t}\n\n\tout, err = TestHelper.LinkerdRun(args...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApply(out, \"\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\tvar (\n\t\tvizCmd  = []string{\"viz\", \"install\"}\n\t\tvizArgs = []string{\n\t\t\t\"--set\", fmt.Sprintf(\"namespace=%s\", TestHelper.GetVizNamespace()), \"--wait\", \"5m\"}\n\t)\n\n\t// Install Linkerd Viz Extension\n\texec := append(vizCmd, vizArgs...)\n\tout, err = TestHelper.LinkerdRun(exec...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd viz install' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApply(out, \"\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n}\n\nfunc TestResourcesPostInstall(t *testing.T) {\n\tctx := context.Background()\n\t// Tests Namespace\n\terr := TestHelper.CheckIfNamespaceExists(ctx, TestHelper.GetLinkerdNamespace())\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"received unexpected output\",\n\t\t\t\"received unexpected output\\n%s\", err.Error())\n\t}\n\n\t// Tests Pods and Deployments\n\n\texpectedDeployments := testutil.LinkerdDeployReplicasEdge\n\tif !TestHelper.ExternalPrometheus() {\n\t\texpectedDeployments[\"prometheus\"] = testutil.DeploySpec{Namespace: \"linkerd-viz\", Replicas: 1}\n\t}\n\t// Upgrade Case\n\tif TestHelper.UpgradeHelmFromVersion() != \"\" {\n\t\texpectedDeployments = testutil.LinkerdDeployReplicasStable\n\t}\n\tfor deploy, spec := range expectedDeployments {\n\t\tif err := TestHelper.CheckPods(ctx, spec.Namespace, deploy, spec.Replicas); err != nil {\n\t\t\t//nolint:errorlint\n\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t} else {\n\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestUninstall(t *testing.T) {\n\tvar (\n\t\tvizCmd = []string{\"viz\", \"uninstall\"}\n\t)\n\n\t// Uninstall Linkerd Viz Extension\n\tout, err := TestHelper.LinkerdRun(vizCmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd viz uninstall' command failed\", err)\n\t}\n\n\targs := []string{\"delete\", \"-f\", \"-\"}\n\tout, err = TestHelper.Kubectl(out, args...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl delete' command failed\",\n\t\t\t\"'kubectl delete' command failed\\n%s\", out)\n\t}\n\n\targs = []string{\"uninstall\"}\n\tout, err = TestHelper.LinkerdRun(args...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install' command failed\", err)\n\t}\n\n\targs = []string{\"delete\", \"-f\", \"-\"}\n\tout, err = TestHelper.Kubectl(out, args...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl delete' command failed\",\n\t\t\t\"'kubectl delete' command failed\\n%s\", out)\n\t}\n}\n\nfunc TestCheckPostUninstall(t *testing.T) {\n\tif err := TestHelper.TestCheckPre(); err != nil {\n\t\tt.Fatalf(\"'linkerd check' command failed: %s\", err)\n\t}\n}\n"
  },
  {
    "path": "test/integration/multicluster/install_test.go",
    "content": "package multiclustertest\n\nimport (\n\t\"bytes\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"math/big\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\tmcHealthcheck \"github.com/linkerd/linkerd2/multicluster/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\n//////////////////////\n///   TEST SETUP   ///\n//////////////////////\n\nvar (\n\tTestHelper     *testutil.TestHelper\n\tcontexts       map[string]string\n\ttestDataDiffer testutil.TestDataDiffer\n)\n\ntype (\n\tmulticlusterCerts struct {\n\t\tca         []byte\n\t\tissuerCert []byte\n\t\tissuerKey  []byte\n\t}\n)\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\tos.Exit(m.Run())\n}\n\n// TestInstall will install the linkerd control plane to be used in the rest of\n// the deep suite tests.\nfunc TestInstall(t *testing.T) {\n\t// Create temporary directory to create shared trust anchor and issuer\n\t// certificates\n\ttmpDir, err := os.MkdirTemp(\"\", \"multicluster-certs\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"failed to create temp dir\", err)\n\t}\n\n\tdefer os.RemoveAll(tmpDir)\n\n\t// Generate CA certificate\n\tcerts, err := createMulticlusterCertificates()\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"failed to create multicluster certificates\", err)\n\t}\n\n\t// First, write CA to file\n\trootPath := fmt.Sprintf(\"%s/%s\", tmpDir, \"ca.crt\")\n\t// Write file with numberic mode 0400 -- u=r\n\tif err = os.WriteFile(rootPath, certs.ca, 0400); err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"failed to create CA certificate\", err)\n\t}\n\n\t// Second, write issuer key and cert to files\n\tissuerCertPath := fmt.Sprintf(\"%s/%s\", tmpDir, \"issuer.crt\")\n\tissuerKeyPath := fmt.Sprintf(\"%s/%s\", tmpDir, \"issuer.key\")\n\n\tif err = os.WriteFile(issuerCertPath, certs.issuerCert, 0400); err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"failed to create issuer certificate\", err)\n\t}\n\n\tif err = os.WriteFile(issuerKeyPath, certs.issuerKey, 0400); err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"failed to create issuer key\", err)\n\t}\n\n\t// Install CRDs\n\tcmd := []string{\n\t\t\"install\",\n\t\t\"--crds\",\n\t\t\"--controller-log-level\", \"debug\",\n\t\t\"--set\", fmt.Sprintf(\"proxy.image.version=%s\", TestHelper.GetVersion()),\n\t\t\"--set\", \"heartbeatSchedule=1 2 3 4 5\",\n\t\t\"--identity-trust-anchors-file\", rootPath,\n\t\t\"--identity-issuer-certificate-file\", issuerCertPath,\n\t\t\"--identity-issuer-key-file\", issuerKeyPath,\n\t}\n\n\t// Global state to keep track of clusters\n\tcontexts = TestHelper.GetMulticlusterContexts()\n\tfor _, ctx := range contexts {\n\t\t// Pipe cmd & args to `linkerd`\n\t\tcmd := append([]string{\"--context=\" + ctx}, cmd...)\n\t\tout, err := TestHelper.LinkerdRun(cmd...)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"'linkerd install' command failed\", err)\n\t\t}\n\t\t// Apply manifest from stdin\n\t\tout, err = TestHelper.KubectlApplyWithContext(out, ctx, \"-f\", \"-\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t\t}\n\t}\n\n\t// Install control-plane\n\tcmd = []string{\n\t\t\"install\",\n\t\t\"--controller-log-level\", \"debug\",\n\t\t\"--set\", fmt.Sprintf(\"proxy.image.version=%s\", TestHelper.GetVersion()),\n\t\t\"--set\", \"heartbeatSchedule=1 2 3 4 5\",\n\t\t\"--identity-trust-anchors-file\", rootPath,\n\t\t\"--identity-issuer-certificate-file\", issuerCertPath,\n\t\t\"--identity-issuer-key-file\", issuerKeyPath,\n\t}\n\n\t// Global state to keep track of clusters\n\tcontexts = TestHelper.GetMulticlusterContexts()\n\tfor _, ctx := range contexts {\n\t\t// Pipe cmd & args to `linkerd`\n\t\tcmd := append([]string{\"--context=\" + ctx}, cmd...)\n\t\tout, err := TestHelper.LinkerdRun(cmd...)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"'linkerd install' command failed\", err)\n\t\t}\n\t\t// Apply manifest from stdin\n\t\tout, err = TestHelper.KubectlApplyWithContext(out, ctx, \"-f\", \"-\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t\t}\n\t}\n\n}\n\nfunc TestInstallMulticluster(t *testing.T) {\n\tfor k, ctx := range contexts {\n\t\tvar out string\n\t\tvar err error\n\t\targs := []string{\"--context=\" + ctx, \"multicluster\", \"install\",\n\t\t\t\"--set\", \"localServiceMirror.excludedAnnotations=evil.linkerd/*\\\\,evil\",\n\t\t\t\"--set\", \"localServiceMirror.excludedLabels=evil.linkerd/*\\\\,evil\",\n\t\t}\n\n\t\t// Source context should be installed without a gateway\n\t\tif k == testutil.SourceContextKey {\n\t\t\targs = append(args, \"--gateway=false\")\n\t\t\tif TestHelper.GetMulticlusterManageControllers() {\n\t\t\t\targs = append(\n\t\t\t\t\targs,\n\t\t\t\t\t\"--set\", \"controllers[0].link.ref.name=target\",\n\t\t\t\t\t\"--set\", \"controllers[0].logFormat=json\",\n\t\t\t\t\t\"--set\", \"controllers[0].logLevel=debug\",\n\t\t\t\t\t\"--set\", \"controllers[0].enableHeadlessServices=true\",\n\t\t\t\t)\n\t\t\t}\n\t\t} else if TestHelper.GetMulticlusterManageControllers() {\n\t\t\targs = append(\n\t\t\t\targs,\n\t\t\t\t\"--set\", \"controllers[0].link.ref.name=source\",\n\t\t\t\t\"--set\", \"controllers[0].gateway.enabled=false\",\n\t\t\t\t\"--set\", \"controllers[0].logFormat=json\",\n\t\t\t\t\"--set\", \"controllers[0].logLevel=debug\",\n\t\t\t)\n\t\t}\n\n\t\tout, err = TestHelper.LinkerdRun(args...)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"'linkerd multicluster install' command failed\", err)\n\t\t}\n\n\t\tout, err = TestHelper.KubectlApplyWithContext(out, ctx, \"-f\", \"-\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t\t}\n\t}\n\n\t// Wait for gateways to come up in target cluster\n\tTestHelper.WaitRolloutWithContext(t, testutil.MulticlusterDeployReplicas, contexts[testutil.TargetContextKey])\n\n\tTestHelper.AddInstalledExtension(\"multicluster\")\n}\n\nfunc TestMulticlusterResourcesPostInstall(t *testing.T) {\n\tmulticlusterSvcs := []testutil.Service{\n\t\t{Namespace: \"linkerd-multicluster\", Name: \"linkerd-gateway\"},\n\t}\n\n\tTestHelper.SwitchContext(contexts[testutil.TargetContextKey])\n\ttestutil.TestResourcesPostInstall(TestHelper.GetMulticlusterNamespace(), multiclusterSvcs, testutil.MulticlusterDeployReplicas, TestHelper, t)\n}\n\nfunc TestLinkClusters(t *testing.T) {\n\t// Get the control plane node IP, this is used to communicate with the\n\t// API Server address.\n\t// k3s runs an API server on the control plane node, the docker\n\t// container IP suffices for a connection between containers to happen\n\t// since they run on a shared network.\n\tlbCmd := []string{\n\t\t\"get\", \"node\",\n\t\t\"-n\", \" -l=node-role.kubernetes.io/control-plane=true\",\n\t\t\"-o\", \"go-template={{ (index (index .items 0).status.addresses 0).address }}\",\n\t}\n\n\t// Link target cluster to source\n\t// * source cluster should support headless services\n\tlinkName := \"target\"\n\tlbIP, err := TestHelper.KubectlWithContext(\"\", contexts[testutil.TargetContextKey], lbCmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl get' command failed\",\n\t\t\t\"'kubectl get' command failed\\n%s\", lbIP)\n\t}\n\n\tlinkCmd := []string{\n\t\t\"--context=\" + contexts[testutil.TargetContextKey],\n\t\t\"--cluster-name\", linkName,\n\t\t\"--api-server-address\", fmt.Sprintf(\"https://%s:6443\", lbIP),\n\t\t\"multicluster\",\n\t\t\"--excluded-annotations\", \"evil.linkerd/*,evil\",\n\t\t\"--excluded-labels\", \"evil.linkerd/*,evil\",\n\t}\n\tif TestHelper.GetMulticlusterManageControllers() {\n\t\tlinkCmd = append(\n\t\t\tlinkCmd,\n\t\t\t\"link-gen\",\n\t\t)\n\t} else {\n\t\tlinkCmd = append(\n\t\t\tlinkCmd,\n\t\t\t\"link\",\n\t\t\t\"--set\", \"enableHeadlessServices=true\",\n\t\t\t\"--log-format\", \"json\",\n\t\t\t\"--log-level\", \"debug\",\n\t\t)\n\t}\n\n\tout, err := TestHelper.LinkerdRun(linkCmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'linkerd multicluster link' command failed\", \"'linkerd multicluster link' command failed: %s\\n%s\", out, err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithContext(out, contexts[testutil.SourceContextKey], \"-f\", \"-\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\t// Link source cluster to target\n\t// * source cluster does not have a gateway, so the link will reflect that\n\tlinkName = \"source\"\n\tlbIP, err = TestHelper.KubectlWithContext(\"\", contexts[testutil.SourceContextKey], lbCmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl get' command failed\",\n\t\t\t\"'kubectl get' command failed\\n%s\", lbIP)\n\t}\n\n\tlinkCmd = []string{\n\t\t\"--context=\" + contexts[testutil.SourceContextKey],\n\t\t\"--cluster-name\", linkName, \"--gateway=false\",\n\t\t\"--api-server-address\", fmt.Sprintf(\"https://%s:6443\", lbIP),\n\t\t\"multicluster\",\n\t}\n\tif TestHelper.GetMulticlusterManageControllers() {\n\t\tlinkCmd = append(\n\t\t\tlinkCmd,\n\t\t\t\"link-gen\",\n\t\t)\n\t} else {\n\t\tlinkCmd = append(\n\t\t\tlinkCmd,\n\t\t\t\"link\",\n\t\t\t\"--log-format\", \"json\",\n\t\t\t\"--log-level\", \"debug\",\n\t\t)\n\t}\n\n\tout, err = TestHelper.LinkerdRun(linkCmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'linkerd multicluster link' command failed\", \"'linkerd multicluster link' command failed: %s\\n%s\", out, err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithContext(out, contexts[testutil.TargetContextKey], \"-f\", \"-\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n}\n\nfunc TestCheckMulticluster(t *testing.T) {\n\t// Run `linkerd check` for both clusters, expect multicluster checks to be\n\t// run and pass successfully\n\tfor _, ctx := range contexts {\n\t\t// First, switch context to make sure we check pods in the cluster we're\n\t\t// supposed to be checking them in. This will rebuild the clientset\n\t\tif err := TestHelper.SwitchContext(ctx); err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to rebuild helper clientset with new context\", \"failed to rebuild helper clientset with new context [%s]: %v\", ctx, err)\n\t\t}\n\n\t\terr := TestHelper.TestCheckWith([]healthcheck.CategoryID{mcHealthcheck.LinkerdMulticlusterExtensionCheck}, \"--context\", ctx)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"'linkerd check' command failed: %s\", err)\n\t\t}\n\t}\n\n\t// Check resources after link were created successfully in source cluster (e.g.\n\t// secrets)\n\tt.Run(\"Outputs resources that allow service-mirror controllers to connect to target cluster\", func(t *testing.T) {\n\t\tif err := TestHelper.SwitchContext(contexts[testutil.TargetContextKey]); err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t,\n\t\t\t\t\"failed to rebuild helper clientset with new context\",\n\t\t\t\t\"failed to rebuild helper clientset with new context [%s]: %v\",\n\t\t\t\tcontexts[testutil.TargetContextKey], err)\n\t\t}\n\t\tname := \"foo\"\n\t\tout, err := TestHelper.LinkerdRun(\"mc\", \"allow\", \"--service-account-name\", name)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t,\n\t\t\t\t\"failed to execute 'mc allow' command\",\n\t\t\t\t\"failed to execute 'mc allow' command %s\\n%s\",\n\t\t\t\terr.Error(), out)\n\t\t}\n\t\tparams := map[string]string{\n\t\t\t\"Version\":     TestHelper.GetVersion(),\n\t\t\t\"AccountName\": name,\n\t\t}\n\t\tif err = testDataDiffer.DiffTestYAMLTemplate(\"allow.golden\", out, params); err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t,\n\t\t\t\t\"received unexpected output\",\n\t\t\t\t\"received unexpected output\\n%s\",\n\t\t\t\terr.Error())\n\t\t}\n\t})\n}\n\n//////////////////////\n///   CERT UTILS   ///\n//////////////////////\n\nfunc createMulticlusterCertificates() (multiclusterCerts, error) {\n\tcaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\tif err != nil {\n\t\treturn multiclusterCerts{}, err\n\t}\n\n\tvar serialNumber int64 = 1\n\tcaTemplate := createCertificateTemplate(\"root.linkerd.cluster.local\", big.NewInt(serialNumber))\n\tcaTemplate.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign\n\t// Create self-signed CA. Pass in its own pub key and its own private key\n\tcaDerBytes, err := x509.CreateCertificate(rand.Reader, &caTemplate, &caTemplate, &caKey.PublicKey, caKey)\n\tif err != nil {\n\t\treturn multiclusterCerts{}, err\n\t}\n\n\t// Increment serial number to generate next certificate (issuer)\n\tserialNumber++\n\tissuerDerBytes, issuerECKey, err := createIssuerCertificate(serialNumber, &caTemplate, caKey)\n\tif err != nil {\n\t\treturn multiclusterCerts{}, err\n\t}\n\n\t// Convert keypairs to DER encoding. We don't care about the CA key so we\n\t// only encode (to export) the issuer keys\n\tissuerDerKey, err := x509.MarshalECPrivateKey(issuerECKey)\n\tif err != nil {\n\t\treturn multiclusterCerts{}, err\n\t}\n\n\t// Finally, get strings from der blocks\n\t// we don't care about CA's private key, it won't be written to a file\n\tca, _, err := tryDerToPem(caDerBytes, []byte{})\n\tif err != nil {\n\t\treturn multiclusterCerts{}, err\n\t}\n\n\tissuer, issuerKey, err := tryDerToPem(issuerDerBytes, issuerDerKey)\n\tif err != nil {\n\t\treturn multiclusterCerts{}, err\n\t}\n\n\treturn multiclusterCerts{\n\t\tca:         ca,\n\t\tissuerCert: issuer,\n\t\tissuerKey:  issuerKey,\n\t}, nil\n}\n\n// createCertificateTemplate will bootstrap a certificate based on the arguments\n// passed in, with a validty of 24h\nfunc createCertificateTemplate(subjectCommonName string, serialNumber *big.Int) x509.Certificate {\n\treturn x509.Certificate{\n\t\tSerialNumber:          serialNumber,\n\t\tSubject:               pkix.Name{CommonName: subjectCommonName},\n\t\tNotBefore:             time.Now(),\n\t\tNotAfter:              time.Now().Add(time.Hour * 24),\n\t\tKeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign,\n\t\tBasicConstraintsValid: true,\n\t\tMaxPathLen:            0,\n\t\tIsCA:                  true,\n\t}\n}\n\n// createIssuerCertificate accepts a serial number, a CA template and the CA's\n// key and it creates and signs an intermediate certificate. The function\n// returns the certificate in DER encoding along with its keypair\nfunc createIssuerCertificate(serialNumber int64, caTemplate *x509.Certificate, caKey *ecdsa.PrivateKey) ([]byte, *ecdsa.PrivateKey, error) {\n\t// Generate keypair first\n\tkey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\tif err != nil {\n\t\treturn []byte{}, nil, err\n\t}\n\n\t// Create issuer template\n\ttemplate := createCertificateTemplate(\"identity.linkerd.cluster.local\", big.NewInt(serialNumber))\n\ttemplate.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign\n\n\t// Create issuer certificate signed by CA, we pass in parent template and\n\t// parent key\n\tderBytes, err := x509.CreateCertificate(rand.Reader, &template, caTemplate, &key.PublicKey, caKey)\n\tif err != nil {\n\t\treturn []byte{}, nil, err\n\t}\n\n\treturn derBytes, key, nil\n}\n\n// tryDerToPem converts a DER encoded byte block and a DER encoded ECDSA keypair\n// to a PEM encoded block\nfunc tryDerToPem(derBlock []byte, key []byte) ([]byte, []byte, error) {\n\tcertOut := &bytes.Buffer{}\n\tcertPemBlock := pem.Block{Type: \"CERTIFICATE\", Bytes: derBlock}\n\tif err := pem.Encode(certOut, &certPemBlock); err != nil {\n\t\treturn []byte{}, []byte{}, err\n\t}\n\n\tif len(key) == 0 {\n\t\treturn certOut.Bytes(), []byte{}, nil\n\t}\n\n\tkeyOut := &bytes.Buffer{}\n\tkeyPemBlock := pem.Block{Type: \"EC PRIVATE KEY\", Bytes: key}\n\tif err := pem.Encode(keyOut, &keyPemBlock); err != nil {\n\t\treturn []byte{}, []byte{}, err\n\t}\n\n\treturn certOut.Bytes(), keyOut.Bytes(), nil\n}\n"
  },
  {
    "path": "test/integration/multicluster/multicluster-traffic/federated_test.go",
    "content": "package multiclustertraffic\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n)\n\n// TestFederatedService deploys emojivoto to two clusters and has the web-svc\n// in both clusters join a federated service. It creates a vote-bot in the\n// source cluster which sends traffic to the federated service and then checks\n// the logs of the web-svc in both clusters. If it has successfully issued\n// requests, then we'll see log messages.\n//\n// We verify that the federated service exists and has no endpoints in the\n// source cluster.\nfunc TestFederatedService(t *testing.T) {\n\tif err := TestHelper.SwitchContext(contexts[testutil.TargetContextKey]); err != nil {\n\t\ttestutil.AnnotatedFatalf(t,\n\t\t\t\"failed to rebuild helper clientset with new context\",\n\t\t\t\"failed to rebuild helper clientset with new context [%s]: %v\",\n\t\t\tcontexts[testutil.TargetContextKey], err)\n\t}\n\tctx := context.Background()\n\t// Create emojivoto in target cluster, to be deleted at the end of the test.\n\tannotations := map[string]string{\n\t\t// \"config.linkerd.io/proxy-log-level\": \"linkerd=debug,info\",\n\t}\n\tTestHelper.WithDataPlaneNamespace(ctx, \"emojivoto-federated\", annotations, t, func(t *testing.T, ns string) {\n\t\tt.Run(\"Deploy resources in source and target clusters\", func(t *testing.T) {\n\t\t\t// Deploy federated-client in source-cluster\n\t\t\to, err := TestHelper.KubectlWithContext(\"\", contexts[testutil.SourceContextKey], \"create\", \"ns\", ns)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to create ns\", \"failed to create ns: %s\\n%s\", err, o)\n\t\t\t}\n\t\t\to, err = TestHelper.KubectlApplyWithContext(\"\", contexts[testutil.SourceContextKey], \"--namespace\", ns, \"-f\", \"testdata/federated-client.yml\")\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to install federated-client\", \"failed to installfederated-client: %s\\n%s\", err, o)\n\t\t\t}\n\n\t\t\t// Deploy emojivoto in both clusters\n\t\t\tfor _, ctx := range contexts {\n\t\t\t\tout, err := TestHelper.KubectlApplyWithContext(\"\", ctx, \"--namespace\", ns, \"-f\", \"testdata/emojivoto-no-bot.yml\")\n\t\t\t\tif err != nil {\n\t\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to install emojivoto\", \"failed to install emojivoto: %s\\n%s\", err, out)\n\t\t\t\t}\n\n\t\t\t\t// Label the service to join the federated service and add\n\t\t\t\t// labels to the service so we can ensure they are copied\n\t\t\t\t// correctly to the federated service.\n\t\t\t\ttimeout := time.Minute\n\t\t\t\terr = testutil.RetryFor(timeout, func() error {\n\t\t\t\t\tfor _, label := range []string{\n\t\t\t\t\t\t\"mirror.linkerd.io/federated=member\",\n\t\t\t\t\t\t\"evil.linkerd/a=b\",\n\t\t\t\t\t\t\"evil=yes\",\n\t\t\t\t\t\t\"good.linkerd/c=d\",\n\t\t\t\t\t\t\"good=yes\",\n\t\t\t\t\t} {\n\t\t\t\t\t\tout, err = TestHelper.KubectlWithContext(\"\", ctx, \"--namespace\", ns, \"label\", \"service/web-svc\", label)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tout, err = TestHelper.KubectlWithContext(\"\", ctx, \"--namespace\", ns, \"label\", \"service/web-svc\", \"test-context=\"+ctx)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to label web-svc\", \"%s\\n%s\", err, out)\n\t\t\t\t}\n\n\t\t\t\t// Add annotations to the service so we can ensure they are\n\t\t\t\t// copied correctly to the federated service.\n\t\t\t\terr = testutil.RetryFor(timeout, func() error {\n\t\t\t\t\tfor _, annotation := range []string{\n\t\t\t\t\t\t\"evil.linkerd/a=b\",\n\t\t\t\t\t\t\"evil=yes\",\n\t\t\t\t\t\t\"good.linkerd/c=d\",\n\t\t\t\t\t\t\"good=yes\",\n\t\t\t\t\t} {\n\t\t\t\t\t\tout, err = TestHelper.KubectlWithContext(\"\", ctx, \"--namespace\", ns, \"annotate\", \"service/web-svc\", annotation)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tout, err = TestHelper.KubectlWithContext(\"\", ctx, \"--namespace\", ns, \"annotate\", \"service/web-svc\", \"test-context=\"+ctx)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to annotate web-svc\", \"%s\\n%s\", err, out)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"Wait until target workloads are ready\", func(t *testing.T) {\n\t\t\t// Wait until client is up and running in source cluster\n\t\t\tvoteBotDeployReplica := map[string]testutil.DeploySpec{\"vote-bot\": {Namespace: ns, Replicas: 1}}\n\t\t\tTestHelper.WaitRolloutWithContext(t, voteBotDeployReplica, contexts[testutil.SourceContextKey])\n\n\t\t\t// Wait until services and replicas are up and running.\n\t\t\temojiDeployReplicas := map[string]testutil.DeploySpec{\n\t\t\t\t\"web\":    {Namespace: ns, Replicas: 1},\n\t\t\t\t\"emoji\":  {Namespace: ns, Replicas: 1},\n\t\t\t\t\"voting\": {Namespace: ns, Replicas: 1},\n\t\t\t}\n\t\t\tfor _, ctx := range contexts {\n\t\t\t\tTestHelper.WaitRolloutWithContext(t, emojiDeployReplicas, ctx)\n\t\t\t}\n\n\t\t})\n\n\t\ttimeout := 3 * time.Minute\n\t\tt.Run(\"Ensure federated service exists and has no endpoints\", func(t *testing.T) {\n\t\t\terr := TestHelper.SwitchContext(contexts[testutil.SourceContextKey])\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"failed to switch contexts\", err)\n\t\t\t}\n\t\t\terr = testutil.RetryFor(timeout, func() error {\n\t\t\t\tsvc, err := TestHelper.GetService(ctx, ns, \"web-svc-federated\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tremoteDiscovery, found := svc.Annotations[k8s.RemoteDiscoveryAnnotation]\n\t\t\t\tif !found {\n\t\t\t\t\treturn fmt.Errorf(\"federated service missing annotation: %s\", k8s.RemoteDiscoveryLabel)\n\t\t\t\t}\n\t\t\t\tif remoteDiscovery != \"web-svc@target\" {\n\t\t\t\t\treturn fmt.Errorf(\"federated service remote discovery was %s, expected %s\", remoteDiscovery, \"web-svc@target\")\n\t\t\t\t}\n\t\t\t\tlocalDiscovery, found := svc.Annotations[k8s.LocalDiscoveryAnnotation]\n\t\t\t\tif !found {\n\t\t\t\t\treturn fmt.Errorf(\"federated service missing annotation: %s\", k8s.LocalDiscoveryAnnotation)\n\t\t\t\t}\n\t\t\t\tif localDiscovery != \"web-svc\" {\n\t\t\t\t\treturn fmt.Errorf(\"federated service local discovery was %s, expected %s\", localDiscovery, \"web-svc\")\n\t\t\t\t}\n\n\t\t\t\t// Metadata should be copied from the source cluster's service\n\t\t\t\t// because that Link is older.\n\t\t\t\ttestAnnotation, found := svc.Annotations[\"test-context\"]\n\t\t\t\tif !found {\n\t\t\t\t\treturn errors.New(\"federated service missing annotation: test-context\")\n\t\t\t\t}\n\t\t\t\tif testAnnotation != contexts[testutil.SourceContextKey] {\n\t\t\t\t\treturn fmt.Errorf(\"federated service test-context was %s, expected %s\", testAnnotation, contexts[testutil.SourceContextKey])\n\t\t\t\t}\n\t\t\t\ttestLabel, found := svc.Labels[\"test-context\"]\n\t\t\t\tif !found {\n\t\t\t\t\treturn errors.New(\"federated service missing label: test-context\")\n\t\t\t\t}\n\t\t\t\tif testLabel != contexts[testutil.SourceContextKey] {\n\t\t\t\t\treturn fmt.Errorf(\"federated service test-context was %s, expected %s\", testLabel, contexts[testutil.SourceContextKey])\n\t\t\t\t}\n\n\t\t\t\t_, err = TestHelper.GetEndpoints(ctx, ns, \"web-svc-federated\")\n\t\t\t\tif err == nil {\n\t\t\t\t\treturn errors.New(\"federated service should not have endpoints\")\n\t\t\t\t}\n\t\t\t\tif !kerrors.IsNotFound(err) {\n\t\t\t\t\treturn fmt.Errorf(\"failed to retrieve federated service endpoints: %w\", err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"timed-out verifying federated service\", err)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"Check if federated service has correct metadata\", func(t *testing.T) {\n\t\t\ttimeout := time.Minute\n\t\t\terr := testutil.RetryFor(timeout, func() error {\n\t\t\t\terr := CheckAnnotation(contexts[testutil.SourceContextKey], ns, \"web-svc-federated\", \"evil\\\\.linkerd/a\", \"\") // Should be excluded.\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\terr = CheckAnnotation(contexts[testutil.SourceContextKey], ns, \"web-svc-federated\", \"good\", \"yes\") // Should be included.\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\terr = CheckAnnotation(contexts[testutil.SourceContextKey], ns, \"web-svc-federated\", \"good\\\\.linkerd/c\", \"d\") // Should be included.\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\terr = CheckLabel(contexts[testutil.SourceContextKey], ns, \"web-svc-federated\", \"evil\", \"\") // Should be excluded.\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\terr = CheckLabel(contexts[testutil.SourceContextKey], ns, \"web-svc-federated\", \"evil\\\\.linkerd/a\", \"\") // Should be excluded.\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\terr = CheckLabel(contexts[testutil.SourceContextKey], ns, \"web-svc-federated\", \"good\", \"yes\") // Should be included.\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\terr = CheckLabel(contexts[testutil.SourceContextKey], ns, \"web-svc-federated\", \"good\\\\.linkerd/c\", \"d\") // Should be included.\n\t\t\t\treturn err\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"incorrect service metadata\", \"incorrect service metadata: %s\", err)\n\t\t\t}\n\t\t})\n\n\t\tfor _, ctx := range contexts {\n\t\t\terr := testutil.RetryFor(timeout, func() error {\n\t\t\t\tout, err := TestHelper.KubectlWithContext(\"\",\n\t\t\t\t\tctx,\n\t\t\t\t\t\"--namespace\", ns,\n\t\t\t\t\t\"logs\",\n\t\t\t\t\t\"--selector\", \"app=web-svc\",\n\t\t\t\t\t\"--container\", \"web-svc\",\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"%w\\n%s\", err, out)\n\t\t\t\t}\n\t\t\t\t// Check for expected error messages\n\t\t\t\tfor _, row := range strings.Split(out, \"\\n\") {\n\t\t\t\t\tif strings.Contains(row, \" /api/vote?choice=:doughnut: \") {\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn fmt.Errorf(\"web-svc logs in %s cluster do not include voting errors\\n%s\", ctx, out)\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"timed-out waiting for traffic in %s cluster (%s)\", ctx, timeout), err)\n\t\t\t}\n\t\t}\n\n\t\t// We update the ports on both members services and assert that the ports\n\t\t// are copied from the older Link.\n\t\tt.Run(\"Update federated service ports\", func(t *testing.T) {\n\t\t\tfor _, ctx := range contexts {\n\t\t\t\tout, err := TestHelper.KubectlWithContext(\"\",\n\t\t\t\t\tctx,\n\t\t\t\t\t\"--namespace\", ns,\n\t\t\t\t\t\"patch\",\n\t\t\t\t\t\"service\",\n\t\t\t\t\t\"web-svc\",\n\t\t\t\t\t\"--type\", \"merge\",\n\t\t\t\t\t\"--patch\", fmt.Sprintf(`{\"spec\": {\"ports\": [{\"name\": \"http-%s\", \"port\": 80, \"targetPort\": 80}]}}`, ctx),\n\t\t\t\t)\n\t\t\t\tif err != nil {\n\t\t\t\t\ttestutil.AnnotatedFatal(t, \"failed to update ports\", out)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\terr := TestHelper.SwitchContext(contexts[testutil.SourceContextKey])\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"failed to switch contexts\", err)\n\t\t\t}\n\t\t\terr = testutil.RetryFor(timeout, func() error {\n\t\t\t\tsvc, err := TestHelper.GetService(ctx, ns, \"web-svc-federated\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif svc.Spec.Ports[0].Name != \"http-\"+contexts[testutil.SourceContextKey] {\n\t\t\t\t\treturn fmt.Errorf(\"federated service port name was %s, expected %s\", svc.Spec.Ports[0].Name, \"http-\"+contexts[testutil.SourceContextKey])\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"timed-out verifying federated service ports\", err)\n\t\t\t}\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "test/integration/multicluster/multicluster-traffic/mc_traffic_test.go",
    "content": "package multiclustertraffic\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tmcHealthcheck \"github.com/linkerd/linkerd2/multicluster/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n\t\"github.com/linkerd/linkerd2/testutil/prommatch\"\n)\n\nvar (\n\tTestHelper *testutil.TestHelper\n\ttargetCtx  string\n\tsourceCtx  string\n\tcontexts   map[string]string\n)\n\nvar (\n\tnginxTargetLabels = prommatch.Labels{\n\t\t\"direction\":            prommatch.Equals(\"outbound\"),\n\t\t\"tls\":                  prommatch.Equals(\"true\"),\n\t\t\"server_id\":            prommatch.Equals(\"default.linkerd-multicluster-statefulset.serviceaccount.identity.linkerd.cluster.local\"),\n\t\t\"dst_control_plane_ns\": prommatch.Equals(\"linkerd\"),\n\t\t\"dst_namespace\":        prommatch.Equals(\"linkerd-multicluster-statefulset\"),\n\t\t\"dst_pod\":              prommatch.Equals(\"nginx-statefulset-0\"),\n\t\t\"dst_serviceaccount\":   prommatch.Equals(\"default\"),\n\t\t\"dst_statefulset\":      prommatch.Equals(\"nginx-statefulset\"),\n\t}\n\n\ttcpConnMatcher = prommatch.NewMatcher(\"tcp_open_total\",\n\t\tprommatch.Labels{\n\t\t\t\"peer\": prommatch.Equals(\"dst\"),\n\t\t},\n\t\tprommatch.TargetAddrLabels(),\n\t\tnginxTargetLabels,\n\t\tprommatch.HasPositiveValue(),\n\t)\n\thttpReqMatcher = prommatch.NewMatcher(\"request_total\",\n\t\tprommatch.TargetAddrLabels(),\n\t\tnginxTargetLabels,\n\t\tprommatch.HasPositiveValue(),\n\t)\n)\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Before starting, initialize contexts\n\tcontexts = TestHelper.GetMulticlusterContexts()\n\tsourceCtx = contexts[testutil.SourceContextKey]\n\ttargetCtx = contexts[testutil.TargetContextKey]\n\t// Then, re-build clientset with source cluster context instead of context\n\t// inferred from environment.\n\tif err := TestHelper.SwitchContext(sourceCtx); err != nil {\n\t\tout := fmt.Sprintf(\"Error running test: failed to switch Kubernetes client to context [%s]: %s\\n\", sourceCtx, err)\n\t\tos.Stderr.Write([]byte(out))\n\t\tos.Exit(1)\n\t}\n\t// Block until gateway & service mirror deploys are running successfully in\n\t// source cluster.\n\tif TestHelper.GetMulticlusterManageControllers() {\n\t\tTestHelper.WaitUntilDeployReady(map[string]testutil.DeploySpec{\n\t\t\t\"controller-target\": {Namespace: \"linkerd-multicluster\", Replicas: 1},\n\t\t})\n\t} else {\n\t\tTestHelper.WaitUntilDeployReady(map[string]testutil.DeploySpec{\n\t\t\t\"linkerd-service-mirror-target\": {Namespace: \"linkerd-multicluster\", Replicas: 1},\n\t\t})\n\t}\n\tos.Exit(m.Run())\n}\n\n// TestGateways tests the `linkerd multicluster gateways` command by installing\n// three emojivoto services in target cluster and asserting the output against\n// the source cluster.\nfunc TestGateways(t *testing.T) {\n\tt.Run(\"install resources in target cluster\", func(t *testing.T) {\n\t\t// Create namespace in source cluster\n\t\tout, err := TestHelper.KubectlWithContext(\"\", contexts[testutil.SourceContextKey], \"create\", \"namespace\", \"linkerd-nginx-gateway-deploy\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to create namespace\", \"failed to create namespace 'linkerd-nginx-gateway-deploy': %s\\n%s\", err, out)\n\t\t}\n\n\t\tout, err = TestHelper.KubectlApplyWithContext(\"\", contexts[testutil.TargetContextKey], \"-n\", \"linkerd-nginx-gateway-deploy\", \"-f\", \"testdata/nginx-gateway-deploy.yaml\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to install nginx deploy\", \"failed to install nginx deploy: %s\\n%s\", err, out)\n\t\t}\n\n\t\t// Wait for workloads to spin up in target cluster. These workloads will have\n\t\t// their services mirrored in the source cluster. The test will check whether\n\t\t// the gateway keeps track of the mirror services.\n\t\ttgtWorkloadRollouts := map[string]testutil.DeploySpec{\n\t\t\t\"nginx-deploy\": {Namespace: \"linkerd-nginx-gateway-deploy\", Replicas: 1},\n\t\t}\n\t\tTestHelper.WaitRolloutWithContext(t, tgtWorkloadRollouts, contexts[testutil.TargetContextKey])\n\t})\n\n\ttimeout := time.Minute\n\terr := testutil.RetryFor(timeout, func() error {\n\t\tout, err := TestHelper.LinkerdRun(\"--context=\"+contexts[testutil.SourceContextKey], \"multicluster\", \"gateways\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trows := strings.Split(out, \"\\n\")\n\t\tif len(rows) < 2 {\n\t\t\treturn errors.New(\"response is empty\")\n\t\t}\n\t\tfields := strings.Fields(rows[1])\n\t\tif len(fields) < 4 {\n\t\t\treturn fmt.Errorf(\"unexpected number of columns: %d\", len(fields))\n\t\t}\n\t\tif fields[0] != \"target\" {\n\t\t\treturn fmt.Errorf(\"unexpected target cluster name: %s\", fields[0])\n\t\t}\n\t\tif fields[1] != \"True\" {\n\t\t\treturn errors.New(\"target cluster is not alive\")\n\t\t}\n\t\tif fields[2] != \"1\" {\n\t\t\treturn fmt.Errorf(\"invalid NUM_SVC: %s\", fields[2])\n\t\t}\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"'linkerd multicluster gateways' command timed-out (%s)\", timeout), err)\n\t}\n}\n\n// TestCheckGatewayAfterRepairEndpoints calls `linkerd mc check` again after 1 minute,\n// so that the RepairEndpoints event has already been processed, making sure\n// that resyncing didn't break things.\nfunc TestCheckGatewayAfterRepairEndpoints(t *testing.T) {\n\t// Re-build the clientset with the source context\n\tif err := TestHelper.SwitchContext(contexts[testutil.SourceContextKey]); err != nil {\n\t\ttestutil.AnnotatedFatalf(t,\n\t\t\t\"failed to rebuild helper clientset with new context\",\n\t\t\t\"failed to rebuild helper clientset with new context [%s]: %v\",\n\t\t\tcontexts[testutil.SourceContextKey], err)\n\t}\n\ttime.Sleep(time.Minute + 5*time.Second)\n\terr := TestHelper.TestCheckWith([]healthcheck.CategoryID{mcHealthcheck.LinkerdMulticlusterExtensionCheck}, \"--context\", contexts[testutil.SourceContextKey])\n\tif err != nil {\n\t\tt.Fatalf(\"'linkerd check' command failed: %s\", err)\n\t}\n}\n\n// TestTargetTraffic inspects the target cluster's web-svc pod to see if the\n// source cluster's vote-bot has been able to hit it with requests. If it has\n// successfully issued requests, then we'll see log messages.\n//\n// TODO it may be clearer to invoke `linkerd diagnostics proxy-metrics` to check whether we see\n// connections from the gateway pod to the web-svc?\nfunc TestTargetTraffic(t *testing.T) {\n\tif err := TestHelper.SwitchContext(contexts[testutil.TargetContextKey]); err != nil {\n\t\ttestutil.AnnotatedFatalf(t,\n\t\t\t\"failed to rebuild helper clientset with new context\",\n\t\t\t\"failed to rebuild helper clientset with new context [%s]: %v\",\n\t\t\tcontexts[testutil.TargetContextKey], err)\n\t}\n\n\tctx := context.Background()\n\t// Create emojivoto in target cluster, to be deleted at the end of the test.\n\tannotations := map[string]string{\n\t\t// \"config.linkerd.io/proxy-log-level\": \"linkerd=debug,info\",\n\t}\n\tTestHelper.WithDataPlaneNamespace(ctx, \"emojivoto\", annotations, t, func(t *testing.T, ns string) {\n\t\tt.Run(\"Deploy resources in source and target clusters\", func(t *testing.T) {\n\t\t\t// Deploy vote-bot client in source-cluster\n\t\t\to, err := TestHelper.KubectlWithContext(\"\", contexts[testutil.SourceContextKey], \"create\", \"ns\", ns)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to create ns\", \"failed to create ns: %s\\n%s\", err, o)\n\t\t\t}\n\t\t\to, err = TestHelper.KubectlApplyWithContext(\"\", contexts[testutil.SourceContextKey], \"--namespace\", ns, \"-f\", \"testdata/vote-bot.yml\")\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to install vote-bot\", \"failed to install vote-bot: %s\\n%s\", err, o)\n\t\t\t}\n\n\t\t\tout, err := TestHelper.KubectlApplyWithContext(\"\", contexts[testutil.TargetContextKey], \"--namespace\", ns, \"-f\", \"testdata/emojivoto-no-bot.yml\")\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to install emojivoto\", \"failed to install emojivoto: %s\\n%s\", err, out)\n\t\t\t}\n\n\t\t\ttimeout := time.Minute\n\t\t\terr = testutil.RetryFor(timeout, func() error {\n\t\t\t\tfor _, label := range []string{\n\t\t\t\t\t\"mirror.linkerd.io/exported=true\",\n\t\t\t\t\t\"evil.linkerd/a=b\",\n\t\t\t\t\t\"evil=yes\",\n\t\t\t\t\t\"good.linkerd/c=d\",\n\t\t\t\t\t\"good=yes\",\n\t\t\t\t} {\n\t\t\t\t\tout, err = TestHelper.KubectlWithContext(\"\", contexts[testutil.TargetContextKey], \"--namespace\", ns, \"label\", \"service/web-svc\", label)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to label web-svc\", \"%s\\n%s\", err, out)\n\t\t\t}\n\n\t\t\terr = testutil.RetryFor(timeout, func() error {\n\t\t\t\tfor _, annotation := range []string{\n\t\t\t\t\t\"evil.linkerd/a=b\",\n\t\t\t\t\t\"evil=yes\",\n\t\t\t\t\t\"good.linkerd/c=d\",\n\t\t\t\t\t\"good=yes\",\n\t\t\t\t} {\n\t\t\t\t\tout, err = TestHelper.KubectlWithContext(\"\", contexts[testutil.TargetContextKey], \"--namespace\", ns, \"annotate\", \"service/web-svc\", annotation)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to annotate web-svc\", \"%s\\n%s\", err, out)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"Check if mirror service has correct metadata\", func(t *testing.T) {\n\t\t\ttimeout := time.Minute\n\t\t\terr := testutil.RetryFor(timeout, func() error {\n\t\t\t\terr := CheckAnnotation(contexts[testutil.SourceContextKey], ns, \"web-svc-target\", \"good\", \"yes\") // Should be included.\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\terr = CheckAnnotation(contexts[testutil.SourceContextKey], ns, \"web-svc-target\", \"good\\\\.linkerd/c\", \"d\") // Should be included.\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\terr = CheckAnnotation(contexts[testutil.SourceContextKey], ns, \"web-svc-target\", \"evil\", \"\") // Should be excluded.\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\terr = CheckAnnotation(contexts[testutil.SourceContextKey], ns, \"web-svc-target\", \"evil\\\\.linkerd/a\", \"\") // Should be excluded.\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\terr = CheckLabel(contexts[testutil.SourceContextKey], ns, \"web-svc-target\", \"good\", \"yes\") // Should be included.\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\terr = CheckLabel(contexts[testutil.SourceContextKey], ns, \"web-svc-target\", \"good\\\\.linkerd/c\", \"d\") // Should be included.\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\terr = CheckLabel(contexts[testutil.SourceContextKey], ns, \"web-svc-target\", \"evil\", \"\") // Should be excluded.\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\terr = CheckLabel(contexts[testutil.SourceContextKey], ns, \"web-svc-target\", \"evil\\\\.linkerd/a\", \"\") // Should be excluded.\n\t\t\t\treturn err\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"incorrect service metadata\", \"incorrect service metadata: %s\", err)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"Wait until target workloads are ready\", func(t *testing.T) {\n\t\t\t// Wait until client is up and running in source cluster\n\t\t\tvoteBotDeployReplica := map[string]testutil.DeploySpec{\"vote-bot\": {Namespace: ns, Replicas: 1}}\n\t\t\tTestHelper.WaitRolloutWithContext(t, voteBotDeployReplica, contexts[testutil.SourceContextKey])\n\n\t\t\t// Wait until \"target\" services and replicas are up and running.\n\t\t\temojiDeployReplicas := map[string]testutil.DeploySpec{\n\t\t\t\t\"web\":    {Namespace: ns, Replicas: 1},\n\t\t\t\t\"emoji\":  {Namespace: ns, Replicas: 1},\n\t\t\t\t\"voting\": {Namespace: ns, Replicas: 1},\n\t\t\t}\n\t\t\tTestHelper.WaitRolloutWithContext(t, emojiDeployReplicas, targetCtx)\n\t\t})\n\n\t\ttimeout := time.Minute\n\t\terr := testutil.RetryFor(timeout, func() error {\n\t\t\tout, err := TestHelper.KubectlWithContext(\"\",\n\t\t\t\ttargetCtx,\n\t\t\t\t\"--namespace\", ns,\n\t\t\t\t\"logs\",\n\t\t\t\t\"--selector\", \"app=web-svc\",\n\t\t\t\t\"--container\", \"web-svc\",\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"%w\\n%s\", err, out)\n\t\t\t}\n\t\t\t// Check for expected error messages\n\t\t\tfor _, row := range strings.Split(out, \"\\n\") {\n\t\t\t\tif strings.Contains(row, \" /api/vote?choice=:doughnut: \") {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"web-svc logs in target cluster do not include voting errors\\n%s\", out)\n\t\t})\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"'linkerd multicluster gateways' command timed-out (%s)\", timeout), err)\n\t\t}\n\t})\n}\n\n// TestMulticlusterStatefulSetTargetTraffic will test that a statefulset can be\n// mirrored from a target cluster to a source cluster. The test deploys two\n// workloads: a slow cooker (as a client) in the src, and an nginx statefulset in\n// (as a server) in the tgt. The slow-cooker is configured to send traffic to an\n// nginx endpoint mirror (nginx-statefulset-0). The traffic should be received\n// by the nginx pod in the tgt. To assert this, we get proxy metrics from the\n// gateway to make sure our connections from the source cluster were routed\n// correctly.\nfunc TestMulticlusterStatefulSetTargetTraffic(t *testing.T) {\n\tif err := TestHelper.SwitchContext(contexts[testutil.TargetContextKey]); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to rebuild helper clientset with new context\", \"failed to rebuild helper clientset with new context [%s]: %v\", contexts[testutil.TargetContextKey], err)\n\t}\n\n\tctx := context.Background()\n\t// Create 'multicluster-statefulset' namespace in target cluster, to be deleted at the end of the test.\n\tTestHelper.WithDataPlaneNamespace(ctx, \"multicluster-statefulset\", map[string]string{}, t, func(t *testing.T, ns string) {\n\t\tt.Run(\"Deploy resources in source and target clusters\", func(t *testing.T) {\n\t\t\t// Create slow-cooker client in source cluster\n\t\t\tout, err := TestHelper.KubectlApplyWithContext(\"\", contexts[testutil.SourceContextKey], \"-f\", \"testdata/slow-cooker.yml\")\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to install slow-cooker\", \"failed to install slow-cooker: %s\\ngot: %s\", err, out)\n\t\t\t}\n\n\t\t\t// Create statefulset deployment in target cluster\n\t\t\tout, err = TestHelper.KubectlApplyWithContext(\"\", contexts[testutil.TargetContextKey], \"-f\", \"testdata/nginx-ss.yml\")\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to install nginx-ss\", \"failed to install nginx-ss: %s\\n%s\", err, out)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"Wait until workloads are ready\", func(t *testing.T) {\n\t\t\t// Wait until client is up and running in source cluster\n\t\t\tscDeployReplica := map[string]testutil.DeploySpec{\"slow-cooker\": {Namespace: ns, Replicas: 1}}\n\t\t\tTestHelper.WaitRolloutWithContext(t, scDeployReplica, contexts[testutil.SourceContextKey])\n\n\t\t\t// Wait until \"target\" statefulset is up and running.\n\t\t\tnginxSpec := testutil.DeploySpec{Namespace: ns, Replicas: 1}\n\t\t\to, err := TestHelper.KubectlWithContext(\"\", contexts[testutil.TargetContextKey], \"--namespace=\"+nginxSpec.Namespace, \"rollout\", \"status\", \"--timeout=5m\", \"statefulset/nginx-statefulset\")\n\t\t\tif err != nil {\n\t\t\t\toEvt, _ := TestHelper.KubectlWithContext(\"\", contexts[testutil.TargetContextKey], \"--namespace=\"+nginxSpec.Namespace, \"get\", \"event\", \"--field-selector\", \"involvedObject.name=nginx-statefulset\")\n\t\t\t\ttestutil.AnnotatedFatalf(t,\n\t\t\t\t\tfmt.Sprintf(\"failed to wait rollout of deploy/%s\", \"nginx-statefulset\"),\n\t\t\t\t\t\"failed to wait for rollout of deploy/%s: %s: %s\\nEvents:\\n%s\", \"nginx-statefulset\", err, o, oEvt)\n\t\t\t}\n\t\t})\n\n\t\t_, err := TestHelper.KubectlWithContext(\"\", contexts[testutil.TargetContextKey], \"--namespace=\"+ns, \"label\", \"svc\", \"nginx-statefulset-svc\", k8s.DefaultExportedServiceSelector+\"=true\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"failed to label nginx-statefulset-svc service\", err)\n\t\t}\n\n\t\tdgCmd := []string{\"--context=\" + targetCtx, \"diagnostics\", \"proxy-metrics\", \"--namespace\",\n\t\t\t\"linkerd-multicluster\", \"deploy/linkerd-gateway\"}\n\t\tt.Run(\"expect open outbound TCP connection from gateway to nginx\", func(t *testing.T) {\n\t\t\t// Use a short time window so that slow-cooker can warm-up and send\n\t\t\t// requests.\n\t\t\terr := testutil.RetryFor(1*time.Minute, func() error {\n\t\t\t\t// Check gateway metrics\n\t\t\t\tmetrics, err := TestHelper.LinkerdRun(dgCmd...)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to get metrics for gateway deployment: %w\", err)\n\t\t\t\t}\n\n\t\t\t\ts := prommatch.Suite{}.\n\t\t\t\t\tMustContain(\"TCP connection from gateway to nginx\", tcpConnMatcher).\n\t\t\t\t\tMustContain(\"HTTP requests from gateway to nginx\", httpReqMatcher)\n\n\t\t\t\tif err := s.CheckString(metrics); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"invalid metrics for gateway deployment: %w\", err)\n\t\t\t\t}\n\n\t\t\t\treturn nil\n\t\t\t})\n\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\", \"unexpected error: %v\", err)\n\t\t\t}\n\t\t})\n\t})\n}\n\nfunc TestSourceResourcesAreCleaned(t *testing.T) {\n\tif err := TestHelper.SwitchContext(contexts[testutil.SourceContextKey]); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to rebuild helper clientset with new context\", \"failed to rebuild helper clientset with new context [%s]: %v\", contexts[testutil.SourceContextKey], err)\n\t}\n\n\tctx := context.Background()\n\tif err := TestHelper.DeleteNamespaceIfExists(ctx, \"linkerd-multicluster-statefulset\"); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, fmt.Sprintf(\"failed to delete %s namespace\", \"linkerd-multicluster-statefulset\"),\n\t\t\t\"failed to delete %s namespace: %s\", \"linkerd-multicluster-statefulset\", err)\n\t}\n\n\tif err := TestHelper.DeleteNamespaceIfExists(ctx, \"linkerd-emojivoto\"); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, fmt.Sprintf(\"failed to delete %s namespace\", \"linkerd-emojivoto\"),\n\t\t\t\"failed to delete %s namespace: %s\", \"linkerd-emojivoto\", err)\n\t}\n\n\tif err := TestHelper.DeleteNamespaceIfExists(ctx, \"linkerd-nginx-gateway-deploy\"); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, fmt.Sprintf(\"failed to delete %s namespace\", \"linkerd-nginx-gateway-deploy\"),\n\t\t\t\"failed to delete %s namespace: %s\", \"linkerd-nginx-gateway-deploy\", err)\n\t}\n}\n\n// At the end of the test, we have one resource left to clean 'linkerd-nginx-gateway-deploy',\n// so we just switch the context again and delete its corresponding namespace.\nfunc TestTargetResourcesAreCleaned(t *testing.T) {\n\tif err := TestHelper.SwitchContext(contexts[testutil.TargetContextKey]); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to rebuild helper clientset with new context\", \"failed to rebuild helper clientset with new context [%s]: %v\", contexts[testutil.TargetContextKey], err)\n\t}\n\n\tctx := context.Background()\n\tif err := TestHelper.DeleteNamespaceIfExists(ctx, \"linkerd-nginx-gateway-deploy\"); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, fmt.Sprintf(\"failed to delete %s namespace\", \"linkerd-nginx-gateway-deploy\"),\n\t\t\t\"failed to delete %s namespace: %s\", \"linkerd-nginx-gateway-deploy\", err)\n\t}\n}\n\nfunc CheckAnnotation(context, ns, svc, annotation, expected string) error {\n\tout, err := TestHelper.KubectlWithContext(\"\", context, \"--namespace\", ns, \"get\", \"service\", svc, fmt.Sprintf(\"-ojsonpath={.metadata.annotations.%s}\", annotation))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to get annotation %s on service %s: %w\", annotation, svc, err)\n\t}\n\tif out != expected {\n\t\treturn fmt.Errorf(\"Expected annotation %s to be %s, got %s\", annotation, expected, out)\n\t}\n\treturn nil\n}\n\nfunc CheckLabel(context, ns, svc, label, expected string) error {\n\tout, err := TestHelper.KubectlWithContext(\"\", context, \"--namespace\", ns, \"get\", \"service\", svc, fmt.Sprintf(\"-ojsonpath={.metadata.labels.%s}\", label))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to get label %s on service %s: %w\", label, svc, err)\n\t}\n\tif out != expected {\n\t\treturn fmt.Errorf(\"Expected label %s to be %s, got %s\", label, expected, out)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "test/integration/multicluster/multicluster-traffic/pod_to_pod_test.go",
    "content": "package multiclustertraffic\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n)\n\n// TestPodToPodTraffic inspects the target cluster's web-svc pod to see if the\n// source cluster's vote-bot has been able to hit it with requests. If it has\n// successfully issued requests, then we'll see log messages.\n//\n// We verify that the service has been mirrored in remote discovery mode by\n// checking that it had no endpoints in the source cluster.\nfunc TestPodToPodTraffic(t *testing.T) {\n\tif err := TestHelper.SwitchContext(contexts[testutil.TargetContextKey]); err != nil {\n\t\ttestutil.AnnotatedFatalf(t,\n\t\t\t\"failed to rebuild helper clientset with new context\",\n\t\t\t\"failed to rebuild helper clientset with new context [%s]: %v\",\n\t\t\tcontexts[testutil.TargetContextKey], err)\n\t}\n\n\tctx := context.Background()\n\t// Create emojivoto in target cluster, to be deleted at the end of the test.\n\tannotations := map[string]string{\n\t\t// \"config.linkerd.io/proxy-log-level\": \"linkerd=debug,info\",\n\t}\n\tTestHelper.WithDataPlaneNamespace(ctx, \"emojivoto-p2p\", annotations, t, func(t *testing.T, ns string) {\n\t\tt.Run(\"Deploy resources in source and target clusters\", func(t *testing.T) {\n\t\t\t// Deploy vote-bot client in source-cluster\n\t\t\to, err := TestHelper.KubectlWithContext(\"\", contexts[testutil.SourceContextKey], \"create\", \"ns\", ns)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to create ns\", \"failed to create ns: %s\\n%s\", err, o)\n\t\t\t}\n\t\t\to, err = TestHelper.KubectlApplyWithContext(\"\", contexts[testutil.SourceContextKey], \"--namespace\", ns, \"-f\", \"testdata/vote-bot.yml\")\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to install vote-bot\", \"failed to install vote-bot: %s\\n%s\", err, o)\n\t\t\t}\n\n\t\t\tout, err := TestHelper.KubectlApplyWithContext(\"\", contexts[testutil.TargetContextKey], \"--namespace\", ns, \"-f\", \"testdata/emojivoto-no-bot.yml\")\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to install emojivoto\", \"failed to install emojivoto: %s\\n%s\", err, out)\n\t\t\t}\n\n\t\t\ttimeout := time.Minute\n\t\t\terr = testutil.RetryFor(timeout, func() error {\n\t\t\t\tout, err = TestHelper.KubectlWithContext(\"\", contexts[testutil.TargetContextKey], \"--namespace\", ns, \"label\", \"service/web-svc\", \"mirror.linkerd.io/exported=remote-discovery\")\n\t\t\t\treturn err\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to label web-svc\", \"%s\\n%s\", err, out)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"Wait until target workloads are ready\", func(t *testing.T) {\n\t\t\t// Wait until client is up and running in source cluster\n\t\t\tvoteBotDeployReplica := map[string]testutil.DeploySpec{\"vote-bot\": {Namespace: ns, Replicas: 1}}\n\t\t\tTestHelper.WaitRolloutWithContext(t, voteBotDeployReplica, contexts[testutil.SourceContextKey])\n\n\t\t\t// Wait until \"target\" services and replicas are up and running.\n\t\t\temojiDeployReplicas := map[string]testutil.DeploySpec{\n\t\t\t\t\"web\":    {Namespace: ns, Replicas: 1},\n\t\t\t\t\"emoji\":  {Namespace: ns, Replicas: 1},\n\t\t\t\t\"voting\": {Namespace: ns, Replicas: 1},\n\t\t\t}\n\t\t\tTestHelper.WaitRolloutWithContext(t, emojiDeployReplicas, targetCtx)\n\t\t})\n\n\t\ttimeout := time.Minute\n\t\tt.Run(\"Ensure mirror service exists and has no endpoints\", func(t *testing.T) {\n\t\t\terr := TestHelper.SwitchContext(contexts[testutil.SourceContextKey])\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"failed to switch contexts\", err)\n\t\t\t}\n\t\t\terr = testutil.RetryFor(timeout, func() error {\n\t\t\t\tsvc, err := TestHelper.GetService(ctx, ns, \"web-svc-target\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tremoteDiscovery, found := svc.Labels[k8s.RemoteDiscoveryLabel]\n\t\t\t\tif !found {\n\t\t\t\t\ttestutil.AnnotatedFatal(t, \"mirror service missing label\", \"mirror service missing label: \"+k8s.RemoteDiscoveryLabel)\n\t\t\t\t}\n\t\t\t\tif remoteDiscovery != \"target\" {\n\t\t\t\t\ttestutil.AnnotatedFatal(t, \"mirror service has incorrect remote discovery\", fmt.Sprintf(\"mirror service remote discovery was %s, expected %s\", remoteDiscovery, \"target\"))\n\t\t\t\t}\n\t\t\t\tremoteService, found := svc.Labels[k8s.RemoteServiceLabel]\n\t\t\t\tif !found {\n\t\t\t\t\ttestutil.AnnotatedFatal(t, \"mirror service missing label\", \"mirror service missing label: \"+k8s.RemoteServiceLabel)\n\t\t\t\t}\n\t\t\t\tif remoteService != \"web-svc\" {\n\t\t\t\t\ttestutil.AnnotatedFatal(t, \"mirror service has incorrect remote service\", fmt.Sprintf(\"mirror service remote service was %s, expected %s\", remoteService, \"web-svc\"))\n\t\t\t\t}\n\t\t\t\t_, err = TestHelper.GetEndpoints(ctx, ns, \"web-svc-target\")\n\t\t\t\tif err == nil {\n\t\t\t\t\ttestutil.AnnotatedFatal(t, \"mirror service should not have endpoints\", \"mirror service should not have endpoints\")\n\t\t\t\t}\n\t\t\t\tif !kerrors.IsNotFound(err) {\n\t\t\t\t\ttestutil.AnnotatedFatal(t, \"failed to retrieve mirror service endpoints\", err.Error())\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"timed-out verifying mirror service\", err)\n\t\t\t}\n\t\t})\n\n\t\terr := testutil.RetryFor(timeout, func() error {\n\t\t\tout, err := TestHelper.KubectlWithContext(\"\",\n\t\t\t\ttargetCtx,\n\t\t\t\t\"--namespace\", ns,\n\t\t\t\t\"logs\",\n\t\t\t\t\"--selector\", \"app=web-svc\",\n\t\t\t\t\"--container\", \"web-svc\",\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"%w\\n%s\", err, out)\n\t\t\t}\n\t\t\t// Check for expected error messages\n\t\t\tfor _, row := range strings.Split(out, \"\\n\") {\n\t\t\t\tif strings.Contains(row, \" /api/vote?choice=:doughnut: \") {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"web-svc logs in target cluster do not include voting errors\\n%s\", out)\n\t\t})\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"timed-out waiting for traffic (%s)\", timeout), err)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "test/integration/multicluster/multicluster-traffic/testdata/emojivoto-no-bot.yml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: emoji\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: voting\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: web\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: emoji-svc\nspec:\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n  - name: prom\n    port: 8801\n    targetPort: 8801\n  selector:\n    app: emoji-svc\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: voting-svc\nspec:\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n  - name: prom\n    port: 8801\n    targetPort: 8801\n  selector:\n    app: voting-svc\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: web-svc\nspec:\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n  selector:\n    app: web-svc\n  type: ClusterIP\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: emoji\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v10\n  name: emoji\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: emoji-svc\n      version: v10\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n      labels:\n        app: emoji-svc\n        version: v10\n    spec:\n      containers:\n      - env:\n        - name: GRPC_PORT\n          value: \"8080\"\n        - name: PROM_PORT\n          value: \"8801\"\n        image: buoyantio/emojivoto-emoji-svc:v10\n        name: emoji-svc\n        ports:\n        - containerPort: 8080\n          name: grpc\n        - containerPort: 8801\n          name: prom\n        resources:\n          requests:\n            cpu: 100m\n      serviceAccountName: emoji\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: voting\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v10\n  name: voting\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: voting-svc\n      version: v10\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n      labels:\n        app: voting-svc\n        version: v10\n    spec:\n      containers:\n      - env:\n        - name: GRPC_PORT\n          value: \"8080\"\n        - name: PROM_PORT\n          value: \"8801\"\n        image: buoyantio/emojivoto-voting-svc:v10\n        name: voting-svc\n        ports:\n        - containerPort: 8080\n          name: grpc\n        - containerPort: 8801\n          name: prom\n        resources:\n          requests:\n            cpu: 100m\n      serviceAccountName: voting\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: web\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v10\n  name: web\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n      version: v10\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n      labels:\n        app: web-svc\n        version: v10\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"8080\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 8080\n          name: http\n        resources:\n          requests:\n            cpu: 100m\n      serviceAccountName: web\n"
  },
  {
    "path": "test/integration/multicluster/multicluster-traffic/testdata/federated-client.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: vote-bot\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v10\n  name: vote-bot\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: vote-bot\n      version: v10\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n      labels:\n        app: vote-bot\n        version: v10\n    spec:\n      containers:\n      - command:\n        - emojivoto-vote-bot\n        env:\n        - name: WEB_HOST\n          value: web-svc-federated:80\n        image: buoyantio/emojivoto-web:v10\n        name: vote-bot\n        resources:\n          requests:\n            cpu: 10m\n"
  },
  {
    "path": "test/integration/multicluster/multicluster-traffic/testdata/nginx-gateway-deploy.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    test.linkerd.io/is-test-data-plane: \"true\"\n  name: linkerd-nginx-gateway-deploy\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: nginx-svc\n  namespace: linkerd-nginx-gateway-deploy\n  labels:\n    mirror.linkerd.io/exported: \"true\"\nspec:\n  selector:\n    app: nginx-deploy\n  sessionAffinity: None\n  type: ClusterIP\n  ports:\n  - protocol: TCP\n    port: 80\nstatus:\n  loadBalancer: {}\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx-deploy\nspec:\n  selector:\n    matchLabels:\n      app: nginx-deploy\n  replicas: 1\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: \"enabled\"\n      labels:\n        app: nginx-deploy\n    spec:\n      containers:\n      - name: nginx\n        image: nginx\n\n"
  },
  {
    "path": "test/integration/multicluster/multicluster-traffic/testdata/nginx-ss.yml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: nginx-statefulset-svc\n  namespace: linkerd-multicluster-statefulset\nspec:\n  clusterIP: None\n  clusterIPs:\n  - None\n  selector:\n    app: nginx-set\n  sessionAffinity: None\n  type: ClusterIP\n  ports:\n  - name: http\n    protocol: TCP\n    port: 80\n---\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: nginx-statefulset\n  namespace: linkerd-multicluster-statefulset\nspec:\n  serviceName: nginx-statefulset-svc\n  selector:\n    matchLabels:\n      app: nginx-set\n  replicas: 1\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n      labels:\n        app: nginx-set\n    spec:\n      containers:\n      - name: nginx\n        image: nginx\n---\n"
  },
  {
    "path": "test/integration/multicluster/multicluster-traffic/testdata/slow-cooker.yml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    test.linkerd.io/is-test-data-plane: \"true\"\n  name: linkerd-multicluster-statefulset\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: slow-cooker\n  namespace: linkerd-multicluster-statefulset\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: slow-cooker\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n      labels:\n        app: slow-cooker\n    spec:\n      containers:\n      - name: slow-cooker\n        image: buoyantio/slow_cooker:1.3.0\n        args:\n        - -qps=1\n        - -metric-addr=0.0.0.0:9999\n        - http://nginx-statefulset-0.nginx-statefulset-svc-target.linkerd-multicluster-statefulset.svc.cluster.local:80\n        ports:\n        - containerPort: 9999\n"
  },
  {
    "path": "test/integration/multicluster/multicluster-traffic/testdata/vote-bot.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: vote-bot\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v10\n  name: vote-bot\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: vote-bot\n      version: v10\n  template:\n    metadata:\n      annotations:\n        linkerd.io/inject: enabled\n      labels:\n        app: vote-bot\n        version: v10\n    spec:\n      containers:\n      - command:\n        - emojivoto-vote-bot\n        env:\n        - name: WEB_HOST\n          value: web-svc-target:80\n        image: buoyantio/emojivoto-web:v10\n        name: vote-bot\n        resources:\n          requests:\n            cpu: 10m\n"
  },
  {
    "path": "test/integration/multicluster/testdata/allow.golden",
    "content": "kind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n    pod-security.kubernetes.io/enforce: privileged\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ .AccountName }}\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/cli {{ .Version }}\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"batch\"]\n  resources: [\"jobs\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"discovery.k8s.io\"]\n  resources: [\"endpointslices\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"policy.linkerd.io\"]\n  resources: [\"servers\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\"]\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ .AccountName }}\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/cli {{ .Version }}\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: {{ .AccountName }}-token\n  namespace: linkerd-multicluster\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    kubernetes.io/service-account.name: {{ .AccountName }}\n    linkerd.io/created-by: linkerd/cli {{ .Version }}\ntype: kubernetes.io/service-account-token\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: {{ .AccountName }}\n  labels:\n    linkerd.io/extension: multicluster\n  annotations:\n    linkerd.io/created-by: linkerd/cli {{ .Version }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ .AccountName }}\nsubjects:\n  - kind: ServiceAccount\n    name: {{ .AccountName }}\n    namespace: linkerd-multicluster\n"
  },
  {
    "path": "test/integration/rsa-ca/install_rsa_ca_test.go",
    "content": "package rsaca\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\n//////////////////////\n///   TEST SETUP   ///\n//////////////////////\n\nvar (\n\tTestHelper *testutil.TestHelper\n)\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\tos.Exit(m.Run())\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\nfunc TestRsaCa(t *testing.T) {\n\terr := TestHelper.InstallGatewayAPI()\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"failed to install gateway-api\", err)\n\t}\n\n\terr = TestHelper.CreateControlPlaneNamespaceIfNotExists(context.Background(), TestHelper.GetLinkerdNamespace())\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, fmt.Sprintf(\"failed to create %s namespace\", TestHelper.GetLinkerdNamespace()),\n\t\t\t\"failed to create %s namespace: %s\", TestHelper.GetLinkerdNamespace(), err)\n\t}\n\n\t// Install CRDs\n\tcmd := []string{\n\t\t\"install\",\n\t\t\"--crds\",\n\t\t\"--controller-log-level\", \"debug\",\n\t\t\"--set\", fmt.Sprintf(\"proxy.image.version=%s\", TestHelper.GetVersion()),\n\t\t\"--set\", \"heartbeatSchedule=1 2 3 4 5\",\n\t}\n\n\t// Pipe cmd & args to `linkerd`\n\tout, err := TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install --crds' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithArgs(out)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\t// Install control-plane with a RSA CA which has issued an ECDSA issuer certificate\n\tcmd = []string{\n\t\t\"install\",\n\t\t\"--controller-log-level\", \"debug\",\n\t\t\"--set\", fmt.Sprintf(\"proxy.image.version=%s\", TestHelper.GetVersion()),\n\t\t\"--identity-trust-anchors-file\", \"testdata/ca.crt\",\n\t\t\"--identity-issuer-certificate-file\", \"testdata/issuer.crt\",\n\t\t\"--identity-issuer-key-file\", \"testdata/issuer.key\",\n\t}\n\n\t// Pipe cmd & args to `linkerd`\n\tout, err = TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithArgs(out)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\tTestHelper.WaitRollout(t, testutil.LinkerdDeployReplicasEdge)\n}\n"
  },
  {
    "path": "test/integration/rsa-ca/testdata/ca.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDGTCCAgGgAwIBAgIQWo14Dx0kQlRe11eECx1kLDANBgkqhkiG9w0BAQsFADAl\nMSMwIQYDVQQDExpyb290LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAeFw0yMjA3MTIy\nMjM4NDhaFw0zMjA3MDkyMjM4NDhaMCUxIzAhBgNVBAMTGnJvb3QubGlua2VyZC5j\nbHVzdGVyLmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1cNW\n64sk7nRqF5IZOv/FTzl1PE+4v09pbcIN/21NbEcd3aBPwlbMJHuUgAuxvw5Kq/g2\niIIJTb9X1ohy19eiO32ZfinRKJYIL3vf79zKepANvvlWxR+u/H77KajVPj3mpql2\nBVzMWlmThIJBzc7Huch/hjhP+/hQ/7mE1G+dnxwGlMhsb4EvxRZzJnIXP0u0/Lpz\ncK0dQ6iKX/l46s5Y0IJibiZ8GboZTrq9TOp+cN3jFrPO29F4tnjqF9cBgG1lO4b9\n/oCVPaJpIlrmqmqVKzS2S62lYxV9KvxPrUMWNW/xoej035hH74+dSJHZhn5HybxG\nTsZ0ZV5w2vpWse9sNwIDAQABo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/\nBAgwBgEB/wIBATAdBgNVHQ4EFgQU1YCd8Jwk+twWuamPccLOjX8zwZEwDQYJKoZI\nhvcNAQELBQADggEBAAFJ1u8RPyaCDzIHZm7p9p2uGzw+yvVF6JkW+4ZXzSCennlm\nYaT78pj9P2ZpE1s0i8UL8Qss/FKdaknFxkeMBWJKS21OdsQVJVy0zGBhbRwYWHWq\nCsxBw/THB8EHYh92tpwb5SYTFBKEsFCnxDgql5JMdFLsuWSIKl9WXFluxUJPlJB/\n1NBheB1BPyHYQIPF6jl+d5DJx7WyS7Pa/rYTzdKsERM+cbtiZYWl3h84FGboXR1C\n7pfsBxJGrO7+3IRFkUxKtZtkKY2ibJ0oJVgPRop610IAuwH/WdathNwQhuzdkMjB\n0DHSkgBoskZlzniPcNl/wOBB+yvC9nlWRSXf2bQ=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/integration/rsa-ca/testdata/ca.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA1cNW64sk7nRqF5IZOv/FTzl1PE+4v09pbcIN/21NbEcd3aBP\nwlbMJHuUgAuxvw5Kq/g2iIIJTb9X1ohy19eiO32ZfinRKJYIL3vf79zKepANvvlW\nxR+u/H77KajVPj3mpql2BVzMWlmThIJBzc7Huch/hjhP+/hQ/7mE1G+dnxwGlMhs\nb4EvxRZzJnIXP0u0/LpzcK0dQ6iKX/l46s5Y0IJibiZ8GboZTrq9TOp+cN3jFrPO\n29F4tnjqF9cBgG1lO4b9/oCVPaJpIlrmqmqVKzS2S62lYxV9KvxPrUMWNW/xoej0\n35hH74+dSJHZhn5HybxGTsZ0ZV5w2vpWse9sNwIDAQABAoIBADkvNIV2h76yrd74\nWn+KBMKY4F/uA8JKAC44h34ZQ2j/7WFojW2zwpDP7n4Cot41eIxgrlX+U3bVBS6C\n+hX7vY6knvc9QJLW8AGj5dhI/HGlL8gy859wRmONpKsUW1d3P8i99LCijphs9iWw\nouHnu05b8KF7VwpU93YxrvMVmNkDLRy7l5AIfDNlJuhLBqabbxWtMAmIOJE+c4B4\nJnCZmzRrjRluN++O4VAJwMY138kYGGpzhD6oPWA/1q0IJDfNMS1Wl3eKkhXdORV4\n93VxbCUp6j4sKj+rX/S3CtQ56gzCgxyBCINrc8ZaXdklcGkuMJOYoQ4TdgGMIaOp\nWKSJXsECgYEA5ECpMiH6jjMD9uKXnFxf7AfrjUoiAQ4Zp3HPx9m2mzTuxmuALllY\nuq5zZpG0aMkOgh1m5Io5ysvfnuHI6OktG6by/5dJYVGywdwQVT8qpswxn/kEV5Fe\nG2E293SWd465QFKu/6D0N71/qTGzfuHpqjleSKTwuIRNQ1674tysGpkCgYEA77/B\ntoqAq49dn0nXV2jCsydxifGfWNCWAFM2f7OU6SZt6hY54SViWY94kH8wKAKxoJnO\nIbc25c4i+dY5xEBR+HlOIGxxS2UkSz5RY1vROcu8mRiRs4JRHa8I7cFWQp7SB3mR\n2K1D4L4Ipkw0e3zxOV54NNGpSNwwJUC91bJOT08CgYEA0rhbO1whKxwv4cHpA8JI\nD+hz7tlssRqqVmp8z1zP91OTyHzANonnn1ikUyHasw5CpZ6tOfneRrmWteBuEZAL\nQ8cJ+Spa5Ux+Qfh+36RUJO7INY64EnuyrIZAL41jx/ZsUdTDmF2oeLkqXTH0KwQ3\nKx6RS3FyhnYlujeAL31YKakCgYBjvdIYYDyxox5fA3hcPBAsOm+o5OXXLEgLcJMO\nw6Zi7QLzTTXdLhFhkyekbdWZ/6zoVLSGIFPtfTnd2LsFo4C2r7jKEnN722MjDpUL\nkgpUUidvReJv3PpkMAq15yY85xgX1gLQMx03Jbgxfyiia1Nr+5pk1wjnb/tztCCG\nA/1CTwKBgEXtlnAUDL23HlnBs7TzqkGuk03M+hubm83m4ZEFnnCMChWS+xl0bZ6O\nlHMV/4tcMTNC/hA0agdyDWKkESTm1ve0zd7RSFOABsRtDhIWr3R7HARzf8t+VKPL\nsg5lQZ+41hr4f3rg48fZz4xw66AbIfQktP4AnUH+V5zshzxqJlnn\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "test/integration/rsa-ca/testdata/issuer.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIICczCCAVugAwIBAgIQHI4iWZqqY9YGKn6LnMIMqzANBgkqhkiG9w0BAQsFADAl\nMSMwIQYDVQQDExpyb290LmxpbmtlcmQuY2x1c3Rlci5sb2NhbDAeFw0yMjA4MDEx\nODE5MzVaFw0zMjA3MjkxODE5MzVaMCkxJzAlBgNVBAMTHmlkZW50aXR5Lmxpbmtl\ncmQuY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABI8FK0Bc\nb40+TKUIjLpr/k/AmBlp367W8xsW1TZ60TEO0OXBpGYLdNvuOh5vIOTG/Xn9OSmZ\niHXHZ7ikpeJzVNqjZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/\nAgEAMB0GA1UdDgQWBBSMHJIetxtiZ/yBCF9pDUOhZ+GbUjAfBgNVHSMEGDAWgBTV\ngJ3wnCT63Ba5qY9xws6NfzPBkTANBgkqhkiG9w0BAQsFAAOCAQEAehCuAwQoZYek\nbTvlJJICoWG99bg/bouZYpNHo3bSoLw4EmM0hTGPSIiajLv2kwnRQOWnQ/SgUvQM\nw+EGFWtI7x8uRdYkX7go2hrt28Pr/aTYcKhnBoDl/4mo0341SFSG7FwpvoEqWulD\nZ354tptDShoHXZAQciZDDr2cBtabszNn3aUXWuMsPXi/cSMgwErD3yalidAHVGMM\nrq60AUhVgGgBED/yzbNzgaHqGbsLEDYgmn7SEWla5dalm/b+3UInm3VLucU0bAS0\nLE2O6P+vqMJZTwH0d7rfQYd5FpFlLR4UrsdjKTzVPV+rP1S0reEKctM+4exqM+Yr\nomqyVHdmJA==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/integration/rsa-ca/testdata/issuer.key",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIGbPfosDdsWQD5U/2SiIP46WEc1Z8YiW0gVIpf7fKvHJoAoGCCqGSM49\nAwEHoUQDQgAEjwUrQFxvjT5MpQiMumv+T8CYGWnfrtbzGxbVNnrRMQ7Q5cGkZgt0\n2+46Hm8g5Mb9ef05KZmIdcdnuKSl4nNU2g==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "test/integration/tracing/install_test.go",
    "content": "package deeptest\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\n//////////////////////\n///   TEST SETUP   ///\n//////////////////////\n\nvar (\n\tTestHelper *testutil.TestHelper\n)\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\tos.Exit(m.Run())\n}\n\n// TestInstall will install the linkerd control plane to be used in the rest of\n// the tracing suite tests.\nfunc TestInstall(t *testing.T) {\n\terr := TestHelper.InstallGatewayAPI()\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"failed to install gateway-api\", err)\n\t}\n\n\t// Install CRDs\n\tcmd := []string{\n\t\t\"install\",\n\t\t\"--crds\",\n\t\t\"--controller-log-level\", \"debug\",\n\t\t\"--set\", fmt.Sprintf(\"proxy.image.version=%s\", TestHelper.GetVersion()),\n\t\t\"--set\", \"heartbeatSchedule=1 2 3 4 5\",\n\t\t\"--values\", \"testdata/tracing-values.yaml\",\n\t}\n\n\t// Pipe cmd & args to `linkerd`\n\tout, err := TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithArgs(out)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\t// Install control-plane\n\tcmd = []string{\n\t\t\"install\",\n\t\t\"--controller-log-level\", \"debug\",\n\t\t\"--set\", fmt.Sprintf(\"proxy.image.version=%s\", TestHelper.GetVersion()),\n\t\t\"--set\", \"heartbeatSchedule=1 2 3 4 5\",\n\t\t\"--values\", \"testdata/tracing-values.yaml\",\n\t}\n\n\t// Pipe cmd & args to `linkerd`\n\tout, err = TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithArgs(out)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\tout, err = TestHelper.LinkerdRun(\"check\", \"--wait=3m\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'linkerd check' command failed\",\n\t\t\t\"'linkerd check' command failed\\n%s\", out)\n\t}\n}\n"
  },
  {
    "path": "test/integration/tracing/testdata/tracing-values.yaml",
    "content": "proxy:\n  tracing:\n    enabled: true\n    collector:\n      endpoint: otel-collector-opentelemetry-collector.tracing:4317\n      meshIdentity:\n        serviceAccountName: otel-collector-opentelemetry-collector\n        namespace: tracing\n"
  },
  {
    "path": "test/integration/tracing/tracing/testdata/emojivoto.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: tracing-test\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: emoji\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: voting\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: web\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: emoji-svc\nspec:\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n  - name: prom\n    port: 8801\n    targetPort: 8801\n  selector:\n    app: emoji-svc\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: voting-svc\nspec:\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n  - name: prom\n    port: 8801\n    targetPort: 8801\n  selector:\n    app: voting-svc\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: web-svc\nspec:\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n  selector:\n    app: web-svc\n  type: ClusterIP\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: emoji\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v11\n  name: emoji\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: emoji-svc\n      version: v11\n  template:\n    metadata:\n      labels:\n        app: emoji-svc\n        version: v11\n      annotations:\n        linkerd.io/inject: enabled\n    spec:\n      containers:\n      - env:\n        - name: GRPC_PORT\n          value: \"8080\"\n        - name: PROM_PORT\n          value: \"8801\"\n        - name: OC_AGENT_HOST\n          value: \"collector.linkerd-jaeger:55678\"\n        image: docker.l5d.io/buoyantio/emojivoto-emoji-svc:v11\n        name: emoji-svc\n        ports:\n        - containerPort: 8080\n          name: grpc\n        - containerPort: 8801\n          name: prom\n        resources:\n          requests:\n            cpu: 100m\n      serviceAccountName: emoji\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: vote-bot\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v11\n  name: vote-bot\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: vote-bot\n      version: v11\n  template:\n    metadata:\n      labels:\n        app: vote-bot\n        version: v11\n      annotations:\n        linkerd.io/inject: enabled\n    spec:\n      containers:\n      - command:\n        - emojivoto-vote-bot\n        env:\n        - name: WEB_HOST\n          value: web-svc:80\n        - name: OC_AGENT_HOST\n          value: \"collector.linkerd-jaeger:55678\"\n        image: docker.l5d.io/buoyantio/emojivoto-web:v11\n        name: vote-bot\n        resources:\n          requests:\n            cpu: 10m\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: voting\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v11\n  name: voting\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: voting-svc\n      version: v11\n  template:\n    metadata:\n      labels:\n        app: voting-svc\n        version: v11\n      annotations:\n        linkerd.io/inject: enabled\n    spec:\n      containers:\n      - env:\n        - name: GRPC_PORT\n          value: \"8080\"\n        - name: PROM_PORT\n          value: \"8801\"\n        - name: OC_AGENT_HOST\n          value: \"collector.linkerd-jaeger:55678\"\n        image: docker.l5d.io/buoyantio/emojivoto-voting-svc:v11\n        name: voting-svc\n        ports:\n        - containerPort: 8080\n          name: grpc\n        - containerPort: 8801\n          name: prom\n        resources:\n          requests:\n            cpu: 100m\n      serviceAccountName: voting\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: web\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v11\n  name: web\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n      version: v11\n  template:\n    metadata:\n      labels:\n        app: web-svc\n        version: v11\n      annotations:\n        linkerd.io/inject: enabled\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"8080\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        - name: OC_AGENT_HOST\n          value: \"collector.linkerd-jaeger:55678\"\n        image: docker.l5d.io/buoyantio/emojivoto-web:v11\n        name: web-svc\n        ports:\n        - containerPort: 8080\n          name: http\n        resources:\n          requests:\n            cpu: 100m\n      serviceAccountName: web\n"
  },
  {
    "path": "test/integration/tracing/tracing/testdata/jaeger-aio-values.yaml",
    "content": "provisionDataStore:\n  cassandra: false\nallInOne:\n  enabled: true\nstorage:\n  type: memory\nagent:\n  enabled: false\ncollector:\n  enabled: false\nquery:\n  enabled: false\n"
  },
  {
    "path": "test/integration/tracing/tracing/testdata/otel-values.yaml",
    "content": "mode: deployment\nimage:\n  repository: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-k8s\ncommand:\n  name: otelcol-k8s\nconfig:\n  exporters:\n    debug: { }\n    otlp/jaeger:\n      endpoint: http://jaeger-collector.tracing:4317\n      tls:\n        insecure: true\n  service:\n    pipelines:\n      traces:\n        exporters:\n          - debug\n          - otlp/jaeger\n"
  },
  {
    "path": "test/integration/tracing/tracing/testdata/tracing.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: tracing\n  annotations:\n    linkerd.io/inject: enabled\n"
  },
  {
    "path": "test/integration/tracing/tracing/tracing_test.go",
    "content": "package tracing\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\ntype (\n\ttraces struct {\n\t\tData []trace `json:\"data\"`\n\t}\n\n\ttrace struct {\n\t\tProcesses map[string]process `json:\"processes\"`\n\t}\n\n\tprocess struct {\n\t\tServiceName string `json:\"serviceName\"`\n\t}\n)\n\n//////////////////////\n///   TEST SETUP   ///\n//////////////////////\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until viz extension is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)\n\tos.Exit(m.Run())\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\nfunc TestTracing(t *testing.T) {\n\tctx := context.Background()\n\n\ttracingNs := \"tracing\"\n\tinstallTracing(t, tracingNs)\n\n\tTestHelper.WithDataPlaneNamespace(ctx, \"tracing-test\", map[string]string{}, t, func(t *testing.T, namespace string) {\n\t\temojivotoYaml, err := testutil.ReadFile(\"testdata/emojivoto.yaml\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to read emojivoto yaml\",\n\t\t\t\t\"failed to read emojivoto yaml\\n%s\\n\", err)\n\t\t}\n\n\t\tout, err := TestHelper.KubectlApply(emojivotoYaml, namespace)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t\t}\n\n\t\t// wait for deployments to start\n\t\tfor _, deploy := range []struct {\n\t\t\tns   string\n\t\t\tname string\n\t\t}{\n\t\t\t{ns: namespace, name: \"vote-bot\"},\n\t\t\t{ns: namespace, name: \"web\"},\n\t\t\t{ns: namespace, name: \"emoji\"},\n\t\t\t{ns: namespace, name: \"voting\"},\n\t\t\t{ns: tracingNs, name: \"otel-collector-opentelemetry-collector\"},\n\t\t\t{ns: tracingNs, name: \"jaeger\"},\n\t\t} {\n\t\t\tif err := TestHelper.CheckPods(ctx, deploy.ns, deploy.name, 1); err != nil {\n\t\t\t\tvar rce *testutil.RestartCountError\n\t\t\t\tif errors.As(err, &rce) {\n\t\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tt.Run(\"expect full trace\", func(t *testing.T) {\n\t\t\ttimeout := 1 * time.Minute\n\t\t\terr = testutil.RetryFor(timeout, func() error {\n\t\t\t\turl, err := TestHelper.URLFor(ctx, tracingNs, \"jaeger\", 16686)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\ttracesJSON, err := TestHelper.HTTPGetURL(url + \"/api/traces?lookback=1h&service=linkerd-proxy\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\ttraces := traces{}\n\n\t\t\t\terr = json.Unmarshal([]byte(tracesJSON), &traces)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tif !hasTraceWithProcess(&traces, \"linkerd-proxy\") {\n\t\t\t\t\treturn noProxyTraceFound{}\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"timed-out checking trace (%s)\", timeout), err)\n\t\t\t}\n\t\t})\n\t})\n}\n\nfunc installTracing(t *testing.T, namespace string) {\n\ttracingYaml, err := testutil.ReadFile(\"testdata/tracing.yaml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to read tracing yaml\",\n\t\t\t\"failed to read emojivoto yaml\\n%s\\n\", err)\n\t}\n\n\tout, err := TestHelper.KubectlApply(tracingYaml, namespace)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\tstdout, stderr, err := TestHelper.HelmRun(\"repo\", \"add\", \"jaegertracing\", \"https://jaegertracing.github.io/helm-charts\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to add jaeger repository\", \"failed to add jaeger repository\\n%s\\n------\\n%s\\n\", stdout, stderr)\n\t}\n\tstdout, stderr, err = TestHelper.HelmRun(\"repo\", \"add\", \"open-telemetry\", \"https://open-telemetry.github.io/opentelemetry-helm-charts\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to add OpenTelemetry repository\", \"failed to add OpenTelemetry repository\\n%s\\n------\\n%s\\n\", stdout, stderr)\n\t}\n\tstdout, stderr, err = TestHelper.HelmRun(\"install\", \"jaeger\", \"jaegertracing/jaeger\", \"--namespace=tracing\", \"--values=testdata/jaeger-aio-values.yaml\", \"--version=3.4.1\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to install jaeger\", \"failed to install jaeger\\n%s\\n------\\n%s\\n\", stdout, stderr)\n\t}\n\tstdout, stderr, err = TestHelper.HelmRun(\"install\", \"otel-collector\", \"open-telemetry/opentelemetry-collector\", \"--namespace=tracing\", \"--values=testdata/otel-values.yaml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to install OpenTelemetry\", \"failed to install OpenTelemetry\\n%s\\n------\\n%s\\n\", stdout, stderr)\n\t}\n}\n\nfunc hasTraceWithProcess(traces *traces, ps string) bool {\n\tfor _, trace := range traces.Data {\n\t\tfor _, process := range trace.Processes {\n\t\t\tif process.ServiceName == ps {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\ntype noProxyTraceFound struct{}\n\nfunc (e noProxyTraceFound) Error() string {\n\treturn \"no trace found with processes: linkerd-proxy\"\n}\n"
  },
  {
    "path": "test/integration/upgrade-edge/testdata/emoji.yaml",
    "content": "---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: emoji\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: voting\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: web\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  creationTimestamp: null\n  name: emoji\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: emoji-svc\n  strategy: {}\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: emoji-svc\n    spec:\n      serviceAccountName: emoji\n      containers:\n      - env:\n        - name: GRPC_PORT\n          value: \"8080\"\n        image: buoyantio/emojivoto-emoji-svc:v10\n        name: emoji-svc\n        ports:\n        - containerPort: 8080\n          name: grpc\n        resources:\n          requests:\n            cpu: 100m\nstatus: {}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: emoji-svc\nspec:\n  selector:\n    app: emoji-svc\n  clusterIP: None\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  creationTimestamp: null\n  name: voting\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: voting-svc\n  strategy: {}\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: voting-svc\n    spec:\n      serviceAccountName: voting\n      containers:\n      - env:\n        - name: GRPC_PORT\n          value: \"8080\"\n        image: buoyantio/emojivoto-voting-svc:v10\n        name: voting-svc\n        ports:\n        - containerPort: 8080\n          name: grpc\n        resources:\n          requests:\n            cpu: 100m\nstatus: {}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: voting-svc\nspec:\n  selector:\n    app: voting-svc\n  clusterIP: None\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  creationTimestamp: null\n  name: web\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n  strategy: {}\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: web-svc\n    spec:\n      serviceAccountName: web\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"8080\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: buoyantio/emojivoto-web:v10\n        name: web-svc\n        ports:\n        - containerPort: 8080\n          name: http\n        resources:\n          requests:\n            cpu: 100m\nstatus: {}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: web-svc\nspec:\n  type: LoadBalancer\n  selector:\n    app: web-svc\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n---\n"
  },
  {
    "path": "test/integration/upgrade-edge/upgrade_edge_test.go",
    "content": "package edgeupgradetest\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/tree\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\nvar (\n\tTestHelper *testutil.TestHelper\n\n\tconfigMapUID string\n\n\tlinkerdSvcEdge = []testutil.Service{\n\t\t{Namespace: \"linkerd\", Name: \"linkerd-dst\"},\n\t\t{Namespace: \"linkerd\", Name: \"linkerd-identity\"},\n\n\t\t{Namespace: \"linkerd\", Name: \"linkerd-dst-headless\"},\n\t\t{Namespace: \"linkerd\", Name: \"linkerd-identity-headless\"},\n\t}\n\n\t// skippedInboundPorts lists some ports to be marked as skipped, which will\n\t// be verified in test/integration/inject\n\tskippedInboundPorts    = \"1234,5678\"\n\tskippedOutboundPorts   = \"1234,5678\"\n\tlinkerdBaseEdgeVersion string\n)\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\tos.Exit(m.Run())\n}\n\nfunc TestInstallResourcesPreUpgrade(t *testing.T) {\n\tversions, err := TestHelper.GetReleaseChannelVersions()\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"failed to get the latest release channels versions\", err)\n\t}\n\tlinkerdBaseEdgeVersion = versions[\"edge\"]\n\n\ttmpDir, err := os.MkdirTemp(\"\", \"upgrade-cli\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"failed to create temp dir\", err)\n\t}\n\tdefer os.RemoveAll(tmpDir)\n\n\tcliPath := fmt.Sprintf(\"%s/linkerd2-cli-%s-%s-%s\", tmpDir, linkerdBaseEdgeVersion, runtime.GOOS, runtime.GOARCH)\n\tif err := TestHelper.DownloadCLIBinary(cliPath, linkerdBaseEdgeVersion); err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"failed to fetch cli executable\", err)\n\t}\n\n\t// Nest all pre-upgrade tests here so they can install and check resources\n\t// using the latest edge CLI\n\tt.Run(fmt.Sprintf(\"installing Linkerd %s control plane\", linkerdBaseEdgeVersion), func(t *testing.T) {\n\t\terr := TestHelper.InstallGatewayAPI()\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"failed to install gateway-api\", err)\n\t\t}\n\n\t\tout, err := TestHelper.CmdRun(cliPath, \"install\", \"--crds\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'linkerd install --crds' command failed\", \"'linkerd install --crds' command failed:\\n%v\", err)\n\t\t}\n\n\t\tout, err = TestHelper.KubectlApply(out, \"\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t\t}\n\n\t\twaitArgs := []string{\n\t\t\t\"wait\", \"--for\", \"condition=established\", \"--timeout=60s\", \"crd\",\n\t\t\t\"authorizationpolicies.policy.linkerd.io\",\n\t\t\t\"meshtlsauthentications.policy.linkerd.io\",\n\t\t\t\"networkauthentications.policy.linkerd.io\",\n\t\t\t\"servers.policy.linkerd.io\",\n\t\t\t\"serverauthorizations.policy.linkerd.io\",\n\t\t}\n\t\tif _, err := TestHelper.Kubectl(\"\", waitArgs...); err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl wait crd' command failed\", \"'kubectl wait crd' command failed:\\n%v\", err)\n\t\t}\n\n\t\tout, err = TestHelper.CmdRun(cliPath,\n\t\t\t\"install\",\n\t\t\t\"--controller-log-level=debug\",\n\t\t\t\"--set=proxyInit.ignoreInboundPorts=1234\\\\,5678\",\n\t\t)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'linkerd install' command failed\", \"'linkerd install' command failed:\\n%v\", err)\n\t\t}\n\n\t\tout, err = TestHelper.KubectlApply(out, \"\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t\t}\n\n\t\tTestHelper.WaitRollout(t, testutil.LinkerdDeployReplicasEdge)\n\t})\n\n\t// TestInstallViz will install the viz extension to be used by the rest of the\n\t// tests in the viz suite\n\tt.Run(fmt.Sprintf(\"installing Linkerd %s viz extension\", linkerdBaseEdgeVersion), func(t *testing.T) {\n\t\tout, err := TestHelper.CmdRun(cliPath, \"viz\", \"install\", \"--set\", fmt.Sprintf(\"namespace=%s\", TestHelper.GetVizNamespace()))\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"'linkerd viz install' command failed\", err)\n\t\t}\n\n\t\tout, err = TestHelper.KubectlApplyWithArgs(out)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t\t}\n\n\t\tTestHelper.WaitRollout(t, testutil.LinkerdVizDeployReplicas)\n\t\tTestHelper.AddInstalledExtension(\"viz\")\n\n\t})\n\n\t// Check client and server versions are what we expect them to be\n\tt.Run(fmt.Sprintf(\"check version is %s pre-upgrade\", linkerdBaseEdgeVersion), func(t *testing.T) {\n\t\tout, err := TestHelper.CmdRun(cliPath, \"version\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'linkerd version' command failed\", \"'linkerd version' command failed\\n%s\", err.Error())\n\t\t}\n\n\t\tif !strings.Contains(out, fmt.Sprintf(\"Client version: %s\", linkerdBaseEdgeVersion)) {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'linkerd version' command failed\", \"'linkerd version' command failed\\nexpected client version: %s, got: %s\", linkerdBaseEdgeVersion, out)\n\t\t}\n\t\tif !strings.Contains(out, fmt.Sprintf(\"Server version: %s\", linkerdBaseEdgeVersion)) {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'linkerd version' command failed\", \"'linkerd version' command failed\\nexpected server version: %s, got: %s\", linkerdBaseEdgeVersion, out)\n\t\t}\n\t})\n}\n\nfunc TestUpgradeTestAppWorksBeforeUpgrade(t *testing.T) {\n\tctx := context.Background()\n\ttestAppNamespace := \"upgrade-test\"\n\n\t// create namespace, and install test app\n\tif err := TestHelper.CreateDataPlaneNamespaceIfNotExists(ctx, testAppNamespace, map[string]string{k8s.ProxyInjectAnnotation: \"enabled\"}); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to create namespace\", \"failed to create namespace %s: %s\", testAppNamespace, err)\n\t}\n\n\tif _, err := TestHelper.Kubectl(\"\", \"apply\", \"-f\", \"./testdata/emoji.yaml\", \"-n\", testAppNamespace); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' failed\", \"'kubectl apply' failed: %s\", err)\n\t}\n\n\t// make sure app is running\n\tfor _, deploy := range []string{\"emoji\", \"voting\", \"web\"} {\n\t\tif err := TestHelper.CheckPods(ctx, testAppNamespace, deploy, 1); err != nil {\n\t\t\tvar rce *testutil.RestartCountError\n\t\t\tif errors.As(err, &rce) {\n\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t} else {\n\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif err := testutil.ExerciseTestAppEndpoint(\"/api/list\", testAppNamespace, TestHelper); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"error exercising test app endpoint before upgrade\",\n\t\t\t\"error exercising test app endpoint before upgrade %s\", err)\n\t}\n}\n\nfunc TestRetrieveUidPreUpgrade(t *testing.T) {\n\tvar err error\n\tconfigMapUID, err = TestHelper.KubernetesHelper.GetConfigUID(context.Background(), TestHelper.GetLinkerdNamespace())\n\tif err != nil || configMapUID == \"\" {\n\t\ttestutil.AnnotatedFatalf(t, \"error retrieving linkerd-config's uid\",\n\t\t\t\"error retrieving linkerd-config's uid: %s\", err)\n\t}\n}\n\nfunc TestUpgradeCli(t *testing.T) {\n\tcmd := \"upgrade\"\n\targs := []string{\n\t\t\"--controller-log-level\", \"debug\",\n\t\t\"--set\", \"proxyInit.ignoreInboundPorts=1234\\\\,5678\",\n\t\t\"--set\", \"heartbeatSchedule=1 2 3 4 5\",\n\t\t\"--set\", \"proxyInit.ignoreOutboundPorts=1234\\\\,5678\",\n\t}\n\n\t// Upgrade CRDs.\n\texec := append([]string{cmd}, append(args, \"--crds\")...)\n\tout, err := TestHelper.LinkerdRun(exec...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd upgrade --crds' command failed\", err)\n\t}\n\tcmdOut, err := TestHelper.KubectlApply(out, \"\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", cmdOut)\n\t}\n\n\t// Upgrade control plane.\n\texec = append([]string{cmd}, args...)\n\tout, err = TestHelper.LinkerdRun(exec...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd upgrade' command failed\", err)\n\t}\n\n\t// Limit the pruning only to known resources\n\t// that we intend to be delete in this stage to prevent it\n\t// from deleting other resources that have the\n\t// label\n\tcmdOut, err = TestHelper.KubectlApplyWithArgs(out, []string{\n\t\t\"--prune\",\n\t\t\"-l\", \"linkerd.io/control-plane-ns=linkerd\",\n\t\t\"--prune-allowlist\", \"apps/v1/deployment\",\n\t\t\"--prune-allowlist\", \"core/v1/service\",\n\t\t\"--prune-allowlist\", \"core/v1/configmap\",\n\t}...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", cmdOut)\n\t}\n\n\tTestHelper.WaitRollout(t, testutil.LinkerdDeployReplicasEdge)\n\n\t// It is necessary to clone LinkerdVizDeployReplicas so that we do not\n\t// mutate its original value.\n\texpectedDeployments := make(map[string]testutil.DeploySpec)\n\tfor k, v := range testutil.LinkerdVizDeployReplicas {\n\t\texpectedDeployments[k] = v\n\t}\n\n\t// Install Linkerd Viz Extension\n\tvizCmd := []string{\n\t\t\"viz\",\n\t\t\"install\",\n\t\t\"--set\", fmt.Sprintf(\"namespace=%s\", TestHelper.GetVizNamespace()),\n\t}\n\tout, err = TestHelper.LinkerdRun(vizCmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd viz install' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithArgs(out, []string{\n\t\t\"--prune\",\n\t\t\"-l\", \"linkerd.io/extension=viz\",\n\t}...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\tTestHelper.WaitRollout(t, expectedDeployments)\n\n}\n\nfunc TestControlPlaneResourcesPostInstall(t *testing.T) {\n\texpectedDeployments := testutil.LinkerdDeployReplicasEdge\n\texpectedServices := linkerdSvcEdge\n\tvizServices := []testutil.Service{\n\t\t{Namespace: \"linkerd-viz\", Name: \"web\"},\n\t\t{Namespace: \"linkerd-viz\", Name: \"tap\"},\n\t\t{Namespace: \"linkerd-viz\", Name: \"prometheus\"},\n\t}\n\texpectedServices = append(expectedServices, vizServices...)\n\texpectedDeployments[\"prometheus\"] = testutil.DeploySpec{Namespace: \"linkerd-viz\", Replicas: 1}\n\n\ttestutil.TestResourcesPostInstall(TestHelper.GetLinkerdNamespace(), expectedServices, expectedDeployments, TestHelper, t)\n}\n\nfunc TestRetrieveUidPostUpgrade(t *testing.T) {\n\tnewConfigMapUID, err := TestHelper.KubernetesHelper.GetConfigUID(context.Background(), TestHelper.GetLinkerdNamespace())\n\tif err != nil || newConfigMapUID == \"\" {\n\t\ttestutil.AnnotatedFatalf(t, \"error retrieving linkerd-config's uid\",\n\t\t\t\"error retrieving linkerd-config's uid: %s\", err)\n\t}\n\tif configMapUID != newConfigMapUID {\n\t\ttestutil.AnnotatedFatalf(t, \"linkerd-config's uid after upgrade doesn't match its value before the upgrade\",\n\t\t\t\"linkerd-config's uid after upgrade [%s] doesn't match its value before the upgrade [%s]\",\n\t\t\tnewConfigMapUID, configMapUID,\n\t\t)\n\t}\n}\n\nfunc TestOverridesSecret(t *testing.T) {\n\tconfigOverridesSecret, err := TestHelper.KubernetesHelper.GetSecret(context.Background(), TestHelper.GetLinkerdNamespace(), \"linkerd-config-overrides\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"could not retrieve linkerd-config-overrides\",\n\t\t\t\"could not retrieve linkerd-config-overrides\\n%s\", err)\n\t}\n\n\toverrides := configOverridesSecret.Data[\"linkerd-config-overrides\"]\n\toverridesTree, err := tree.BytesToTree(overrides)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"could not retrieve linkerd-config-overrides\",\n\t\t\t\"could not retrieve linkerd-config-overrides\\n%s\", err)\n\t}\n\n\t// Check for fields that were added during install\n\ttestCases := []struct {\n\t\tpath  []string\n\t\tvalue string\n\t}{\n\t\t{\n\t\t\t[]string{\"controllerLogLevel\"},\n\t\t\t\"debug\",\n\t\t},\n\t\t{\n\t\t\t[]string{\"proxyInit\", \"ignoreInboundPorts\"},\n\t\t\tskippedInboundPorts,\n\t\t},\n\t\t{\n\t\t\t[]string{\"proxyInit\", \"ignoreOutboundPorts\"},\n\t\t\tskippedOutboundPorts,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%s: %s\", strings.Join(tc.path, \"/\"), tc.value), func(t *testing.T) {\n\t\t\tfinalValue, err := overridesTree.GetString(tc.path...)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"could not perform tree.GetString\",\n\t\t\t\t\t\"could not perform tree.GetString\\n%s\", err)\n\t\t\t}\n\n\t\t\tif tc.value != finalValue {\n\t\t\t\ttestutil.AnnotatedFatalf(t, fmt.Sprintf(\"Values at path %s do not match\", strings.Join(tc.path, \"/\")),\n\t\t\t\t\t\"Expected value at [%s] to be [%s] but received [%s]\",\n\t\t\t\t\tstrings.Join(tc.path, \"/\"), tc.value, finalValue)\n\t\t\t}\n\t\t})\n\t}\n\n\textractValue := func(t *testing.T, path ...string) string {\n\t\tval, err := overridesTree.GetString(path...)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"error calling overridesTree.GetString()\",\n\t\t\t\t\"error calling overridesTree.GetString(): %s\", err)\n\t\t\treturn \"\"\n\n\t\t}\n\t\treturn val\n\t}\n\n\tt.Run(\"Check if any unknown fields snuck in\", func(t *testing.T) {\n\t\tknownKeys := tree.Tree{\n\t\t\t\"controllerLogLevel\": \"debug\",\n\t\t\t\"heartbeatSchedule\":  \"1 2 3 4 5\",\n\t\t\t\"identity\": tree.Tree{\n\t\t\t\t\"issuer\": tree.Tree{\n\t\t\t\t\t\"tls\": tree.Tree{\n\t\t\t\t\t\t\"crtPEM\": extractValue(t, \"identity\", \"issuer\", \"tls\", \"crtPEM\"),\n\t\t\t\t\t\t\"keyPEM\": extractValue(t, \"identity\", \"issuer\", \"tls\", \"keyPEM\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"identityTrustAnchorsPEM\": extractValue(t, \"identityTrustAnchorsPEM\"),\n\t\t\t\"proxyInit\": tree.Tree{\n\t\t\t\t\"ignoreInboundPorts\":  skippedInboundPorts,\n\t\t\t\t\"ignoreOutboundPorts\": skippedOutboundPorts,\n\t\t\t},\n\t\t}\n\n\t\tif reg := os.Getenv(flags.EnvOverrideDockerRegistry); reg != \"\" {\n\t\t\tknownKeys[\"controllerImage\"] = reg + \"/controller\"\n\t\t\tknownKeys[\"debugContainer\"] = tree.Tree{\n\t\t\t\t\"image\": tree.Tree{\n\t\t\t\t\t\"name\": reg + \"/debug\",\n\t\t\t\t},\n\t\t\t}\n\t\t\tknownKeys[\"proxy\"] = tree.Tree{\n\t\t\t\t\"image\": tree.Tree{\n\t\t\t\t\t\"name\": reg + \"/proxy\",\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\t// Check if the keys in overridesTree match with knownKeys\n\t\tif diff := deep.Equal(overridesTree.String(), knownKeys.String()); diff != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"Overrides and knownKeys are different\", \"%+v\", diff)\n\t\t}\n\t})\n}\n\nfunc TestVersionPostInstall(t *testing.T) {\n\terr := TestHelper.CheckVersion(TestHelper.GetVersion())\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"Version command failed\",\n\t\t\t\"Version command failed\\n%s\", err.Error())\n\t}\n}\n\nfunc TestCheckProxyPostUpgrade(t *testing.T) {\n\tif err := TestHelper.TestCheckProxy(TestHelper.GetVersion(), TestHelper.GetLinkerdNamespace()); err != nil {\n\t\tt.Fatalf(\"'linkerd check --proxy' command failed: %s\", err)\n\t}\n}\n\nfunc TestUpgradeTestAppWorksAfterUpgrade(t *testing.T) {\n\ttestAppNamespace := \"upgrade-test\"\n\n\t// Restart pods after upgrade to make sure they're re-injected with the\n\t// latest proxy\n\tif _, err := TestHelper.Kubectl(\"\", \"rollout\", \"restart\", \"deploy\", \"-n\", testAppNamespace); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl rollout' failed\", \"'kubectl rollout' failed: %s\", err)\n\t}\n\n\t// make sure app is running before proceeding\n\tctx := context.Background()\n\tfor _, deploy := range []string{\"emoji\", \"voting\", \"web\"} {\n\t\tif err := TestHelper.CheckPods(ctx, testAppNamespace, deploy, 1); err != nil {\n\t\t\tvar rce *testutil.RestartCountError\n\t\t\tif errors.As(err, &rce) {\n\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t} else {\n\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif err := testutil.ExerciseTestAppEndpoint(\"/api/vote?choice=:policeman:\", testAppNamespace, TestHelper); err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"error exercising test app endpoint after upgrade\",\n\t\t\t\"error exercising test app endpoint after upgrade %s\", err)\n\t}\n}\n"
  },
  {
    "path": "test/integration/viz/edges/edges_test.go",
    "content": "package edges\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\t\"text/template\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until viz extension is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdVizDeployReplicas)\n\tos.Exit(m.Run())\n}\n\n// TestEdges requires that there has been traffic recently between linkerd-web\n// and linkerd-controller for edges to have been registered, which is the\n// case when running this test in the context of the other integration tests.\n\nfunc TestEdges(t *testing.T) {\n\tns := TestHelper.GetLinkerdNamespace()\n\tpromNs := TestHelper.GetVizNamespace()\n\tvars := struct {\n\t\tNs     string\n\t\tPromNs string\n\t}{ns, promNs}\n\n\tvar b bytes.Buffer\n\ttpl := template.Must(template.ParseFiles(\"testdata/linkerd_edges.golden\"))\n\tif err := tpl.Execute(&b, vars); err != nil {\n\t\tt.Fatalf(\"failed to parse linkerd_edges.golden template: %s\", err)\n\t}\n\n\ttimeout := 50 * time.Second\n\tcmd := []string{\n\t\t\"edges\",\n\t\t\"-n\", ns,\n\t\t\"deploy\",\n\t\t\"-ojson\",\n\t}\n\tr := regexp.MustCompile(b.String())\n\terr := testutil.RetryFor(timeout, func() error {\n\t\tout, err := TestHelper.LinkerdRun(cmd...)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tpods, err := TestHelper.Kubectl(\"\", []string{\"get\", \"pods\", \"-A\"}...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !r.MatchString(out) {\n\t\t\tt.Errorf(\"Expected output:\\n%s\\nactual:\\n%s\\nAll pods: %s\", b.String(), out, pods)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\ttestutil.AnnotatedError(t, fmt.Sprintf(\"timed-out checking edges (%s)\", timeout), err)\n\t}\n}\n\n// TestDirectEdges deploys a terminus and then generates a load generator which\n// sends traffic directly to the pod ip of the terminus pod.\nfunc TestDirectEdges(t *testing.T) {\n\n\tctx := context.Background()\n\t// setup\n\tTestHelper.WithDataPlaneNamespace(ctx, \"direct-edges-test\", map[string]string{}, t, func(t *testing.T, testNamespace string) {\n\n\t\t// inject terminus\n\n\t\tout, err := TestHelper.LinkerdRun(\"inject\", \"--manual\", \"testdata/terminus.yaml\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'linkerd inject' command failed\", \"'linkerd inject' command failed: %s\", err)\n\t\t}\n\n\t\t// deploy terminus\n\n\t\tout, err = TestHelper.KubectlApply(out, testNamespace)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"kubectl apply command failed\", \"kubectl apply command failed\\n%s\", out)\n\t\t}\n\n\t\tif err := TestHelper.CheckPods(ctx, testNamespace, \"terminus\", 1); err != nil {\n\t\t\t//nolint:errorlint\n\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t} else {\n\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t}\n\t\t}\n\n\t\t// get terminus pod ip\n\n\t\tip, err := TestHelper.Kubectl(\"\", \"-n\", testNamespace, \"get\", \"pod\", \"-ojsonpath=\\\"{.items[*].status.podIP}\\\"\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedError(t, \"'kubectl get pod' command failed\", err)\n\t\t}\n\t\tip = strings.Trim(ip, \"\\\"\") // strip quotes\n\n\t\tb, err := os.ReadFile(\"testdata/slow-cooker.yaml\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedError(t, \"error reading file slow-cooker.yaml\", err)\n\t\t}\n\n\t\tslowcooker := string(b)\n\t\tslowcooker = strings.ReplaceAll(slowcooker, \"___TERMINUS_POD_IP___\", ip)\n\n\t\t// inject slow cooker\n\n\t\tout, stderr, err := TestHelper.PipeToLinkerdRun(slowcooker, \"inject\", \"--manual\", \"-\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'linkerd 'inject' command failed\", \"'linkerd %s' command failed with %s: %s\\n\", \"inject\", err.Error(), stderr)\n\t\t}\n\n\t\t// deploy slow cooker\n\n\t\tout, err = TestHelper.KubectlApply(out, testNamespace)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"kubectl apply command failed\", \"kubectl apply command failed\\n%s\", out)\n\t\t}\n\n\t\tif err := TestHelper.CheckPods(ctx, testNamespace, \"slow-cooker\", 1); err != nil {\n\t\t\t//nolint:errorlint\n\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t} else {\n\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t}\n\t\t}\n\n\t\t// check edges\n\t\ttimeout := 50 * time.Second\n\t\ttestDataPath := \"testdata\"\n\t\terr = testutil.RetryFor(timeout, func() error {\n\t\t\tout, err = TestHelper.LinkerdRun(\"-n\", testNamespace, \"-o\", \"json\", \"viz\", \"edges\", \"deploy\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\ttpl := template.Must(template.ParseFiles(testDataPath + \"/direct_edges.golden\"))\n\t\t\tvars := struct {\n\t\t\t\tNs    string\n\t\t\t\tVizNs string\n\t\t\t}{\n\t\t\t\ttestNamespace,\n\t\t\t\tTestHelper.GetVizNamespace(),\n\t\t\t}\n\t\t\tvar buf bytes.Buffer\n\t\t\tif err := tpl.Execute(&buf, vars); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to parse direct_edges.golden template: %w\", err)\n\t\t\t}\n\n\t\t\tpods, err := TestHelper.Kubectl(\"\", []string{\"get\", \"pods\", \"-A\"}...)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tr := regexp.MustCompile(buf.String())\n\t\t\tif !r.MatchString(out) {\n\t\t\t\treturn fmt.Errorf(\"Expected output:\\n%s\\nactual:\\n%s\\nAll pods: %s\", buf.String(), out, pods)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedError(t, fmt.Sprintf(\"timed-out checking edges (%s)\", timeout), err)\n\t\t}\n\t})\n\n}\n"
  },
  {
    "path": "test/integration/viz/edges/testdata/direct_edges.golden",
    "content": "\\[\n  \\{\n    \"src\": \"slow-cooker\",\n    \"src_namespace\": \"{{.Ns}}\",\n    \"dst\": \"terminus\",\n    \"dst_namespace\": \"{{.Ns}}\",\n    \"client_id\": \"default.{{.Ns}}\",\n    \"server_id\": \"default.{{.Ns}}\",\n    \"no_tls_reason\": \"\"\n  \\},\n  \\{\n    \"src\": \"prometheus\",\n    \"src_namespace\": \"{{.VizNs}}\",\n    \"dst\": \"slow-cooker\",\n    \"dst_namespace\": \"{{.Ns}}\",\n    \"client_id\": \"prometheus.{{.VizNs}}\",\n    \"server_id\": \"default.{{.Ns}}\",\n    \"no_tls_reason\": \"\"\n  \\},\n  \\{\n    \"src\": \"prometheus\",\n    \"src_namespace\": \"{{.VizNs}}\",\n    \"dst\": \"terminus\",\n    \"dst_namespace\": \"{{.Ns}}\",\n    \"client_id\": \"prometheus.{{.VizNs}}\",\n    \"server_id\": \"default.{{.Ns}}\",\n    \"no_tls_reason\": \"\"\n  \\}\n\\]\n"
  },
  {
    "path": "test/integration/viz/edges/testdata/linkerd_edges.golden",
    "content": "\\[\n  \\{\n    \"src\": \"prometheus\",\n    \"src_namespace\": \"{{.PromNs}}\",\n    \"dst\": \"linkerd\\-destination\",\n    \"dst_namespace\": \"{{.Ns}}\",\n    \"client_id\": \"prometheus.{{.PromNs}}\",\n    \"server_id\": \"linkerd\\-destination.{{.Ns}}\",\n    \"no_tls_reason\": \"\"\n  \\},\n  \\{\n    \"src\": \"prometheus\",\n    \"src_namespace\": \"{{.PromNs}}\",\n    \"dst\": \"linkerd\\-identity\",\n    \"dst_namespace\": \"{{.Ns}}\",\n    \"client_id\": \"prometheus.{{.PromNs}}\",\n    \"server_id\": \"linkerd\\-identity.{{.Ns}}\",\n    \"no_tls_reason\": \"\"\n  \\},\n  \\{\n    \"src\": \"prometheus\",\n    \"src_namespace\": \"{{.PromNs}}\",\n    \"dst\": \"linkerd\\-proxy\\-injector\",\n    \"dst_namespace\": \"{{.Ns}}\",\n    \"client_id\": \"prometheus.{{.PromNs}}\",\n    \"server_id\": \"linkerd\\-proxy\\-injector.{{.Ns}}\",\n    \"no_tls_reason\": \"\"\n  \\}\n\\]\n"
  },
  {
    "path": "test/integration/viz/edges/testdata/slow-cooker.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: slow-cooker\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: slow-cooker\n  template:\n    metadata:\n      labels:\n        app: slow-cooker\n    spec:\n      containers:\n      - name: slow-cooker\n        image: buoyantio/slow_cooker:1.3.0\n        command:\n        - \"/bin/sh\"\n        args:\n        - \"-c\"\n        - |\n          sleep 15 # wait for pods to start\n          /slow_cooker/slow_cooker -metric-addr 0.0.0.0:9999 http://___TERMINUS_POD_IP___:8080\n        ports:\n        - containerPort: 9999\n"
  },
  {
    "path": "test/integration/viz/edges/testdata/terminus.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: terminus\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: terminus\n  template:\n    metadata:\n      labels:\n        app: terminus\n    spec:\n      containers:\n      - name: terminus\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=pong\"\n        ports:\n        - containerPort: 8080\n"
  },
  {
    "path": "test/integration/viz/install_test.go",
    "content": "package viztest\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\n//////////////////////\n///   TEST SETUP   ///\n//////////////////////\n\nvar (\n\tTestHelper *testutil.TestHelper\n)\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\tos.Exit(m.Run())\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\n// TestInstallLinkerd will install the linkerd control plane to be used in the rest of\n// the deep suite tests\nfunc TestInstallLinkerd(t *testing.T) {\n\terr := TestHelper.InstallGatewayAPI()\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"failed to install gateway-api\", err)\n\t}\n\n\t// Install CRDs\n\tcmd := []string{\n\t\t\"install\",\n\t\t\"--crds\",\n\t\t\"--controller-log-level\", \"debug\",\n\t\t\"--set\", fmt.Sprintf(\"proxy.image.version=%s\", TestHelper.GetVersion()),\n\t\t\"--set\", \"heartbeatSchedule=1 2 3 4 5\",\n\t}\n\n\t// Pipe cmd & args to `linkerd`\n\tout, err := TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install --crds' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithArgs(out)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\t// Install control-plane\n\tcmd = []string{\n\t\t\"install\",\n\t\t\"--controller-log-level\", \"debug\",\n\t\t\"--set\", fmt.Sprintf(\"proxy.image.version=%s\", TestHelper.GetVersion()),\n\t\t\"--set\", \"heartbeatSchedule=1 2 3 4 5\",\n\t}\n\n\t// Pipe cmd & args to `linkerd`\n\tout, err = TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd install' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithArgs(out)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\tTestHelper.WaitRollout(t, testutil.LinkerdDeployReplicasEdge)\n}\n\n// TestInstallVizHA tests a dry run of installing viz in HA mode.\nfunc TestInstallVizHA(t *testing.T) {\n\tcmd := []string{\n\t\t\"viz\",\n\t\t\"install\",\n\t\t\"--set\", fmt.Sprintf(\"namespace=%s\", TestHelper.GetVizNamespace()),\n\t\t\"--ha\",\n\t}\n\n\tif TestHelper.NativeSidecar() {\n\t\tcmd = append(cmd, \"--set\", \"proxy.nativeSidecar=true\")\n\t}\n\n\tout, err := TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd viz install' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithArgs(out, \"--dry-run\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n}\n\n// TestInstallViz will install the viz extension to be used by the rest of the\n// tests in the viz suite\nfunc TestInstallViz(t *testing.T) {\n\tcmd := []string{\n\t\t\"viz\",\n\t\t\"install\",\n\t\t\"--set\", fmt.Sprintf(\"namespace=%s\", TestHelper.GetVizNamespace()),\n\t}\n\n\tout, err := TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd viz install' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApplyWithArgs(out)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\tTestHelper.WaitRollout(t, testutil.LinkerdVizDeployReplicas)\n\n}\n\nfunc TestCheckViz(t *testing.T) {\n\tif err := TestHelper.TestCheck(); err != nil {\n\t\tt.Fatalf(\"'linkerd check' command failed: %s\", err)\n\t}\n}\n\nfunc TestDashboard(t *testing.T) {\n\tdashboardPort := 52237\n\tdashboardURL := fmt.Sprintf(\"http://localhost:%d\", dashboardPort)\n\n\toutputStream, err := TestHelper.LinkerdRunStream(\"viz\", \"dashboard\", \"-p\",\n\t\tstrconv.Itoa(dashboardPort), \"--show\", \"url\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"error running command\",\n\t\t\t\"error running command:\\n%s\", err)\n\t}\n\tdefer outputStream.Stop()\n\n\toutputLines, err := outputStream.ReadUntil(4, 1*time.Minute)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"error running command\",\n\t\t\t\"error running command:\\n%s\", err)\n\t}\n\n\toutput := strings.Join(outputLines, \"\")\n\tif !strings.Contains(output, dashboardURL) {\n\t\ttestutil.AnnotatedFatalf(t,\n\t\t\t\"dashboard command failed\", \"Expected url [%s] not present\", dashboardURL)\n\t}\n\n\tresp, err := TestHelper.HTTPGetURL(dashboardURL + \"/api/version\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\",\n\t\t\t\"unexpected error: %v\", err)\n\t}\n\n\tif !strings.Contains(resp, TestHelper.GetVersion()) {\n\t\ttestutil.AnnotatedFatalf(t, \"dashboard command failed; response doesn't contain expected version\",\n\t\t\t\"dashboard command failed. Expected response [%s] to contain version [%s]\",\n\t\t\tresp, TestHelper.GetVersion())\n\t}\n}\n"
  },
  {
    "path": "test/integration/viz/policy/policy_test.go",
    "content": "package policy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until viz extension is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdVizDeployReplicas)\n\tos.Exit(m.Run())\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\nfunc TestPolicy(t *testing.T) {\n\tctx := context.Background()\n\n\t// Test authorization stats\n\tTestHelper.WithDataPlaneNamespace(ctx, \"stat-authz-test\", map[string]string{}, t, func(t *testing.T, prefixedNs string) {\n\t\temojivotoYaml, err := testutil.ReadFile(\"testdata/emojivoto.yaml\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to read emojivoto yaml\",\n\t\t\t\t\"failed to read emojivoto yaml\\n%s\\n\", err)\n\t\t}\n\t\temojivotoYaml = strings.ReplaceAll(emojivotoYaml, \"___NS___\", prefixedNs)\n\t\tout, stderr, err := TestHelper.PipeToLinkerdRun(emojivotoYaml, \"inject\", \"-\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'linkerd inject' command failed\",\n\t\t\t\t\"'linkerd inject' command failed\\n%s\\n%s\", out, stderr)\n\t\t}\n\n\t\tout, err = TestHelper.KubectlApply(out, prefixedNs)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to apply emojivoto resources\",\n\t\t\t\t\"failed to apply emojivoto resources: %s\\n %s\", err, out)\n\t\t}\n\n\t\temojivotoPolicy, err := testutil.ReadFile(\"testdata/emoji-policy.yaml\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to read emoji-policy yaml\",\n\t\t\t\t\"failed to read emoji-policy yaml\\n%s\\n\", err)\n\t\t}\n\n\t\tout, err = TestHelper.KubectlApply(emojivotoPolicy, prefixedNs)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"failed to apply emojivoto policy resources\",\n\t\t\t\t\"failed to apply emojivoto policy resources: %s\\n %s\", err, out)\n\t\t}\n\n\t\t// wait for deployments to start\n\t\tfor _, deploy := range []string{\"web\", \"emoji\", \"vote-bot\", \"voting\"} {\n\t\t\tif err := TestHelper.CheckPods(ctx, prefixedNs, deploy, 1); err != nil {\n\t\t\t\t//nolint:errorlint\n\t\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t\t} else {\n\t\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttestCases := []struct {\n\t\t\targs         []string\n\t\t\texpectedRows []string\n\t\t\tisServer     bool\n\t\t}{\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"srv\", \"-n\", prefixedNs},\n\t\t\t\texpectedRows: []string{\n\t\t\t\t\t\"emoji-grpc\",\n\t\t\t\t\t\"voting-grpc\",\n\t\t\t\t\t\"web-http\",\n\t\t\t\t},\n\t\t\t\tisServer: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"srv/emoji-grpc\", \"-n\", prefixedNs},\n\t\t\t\texpectedRows: []string{\n\t\t\t\t\t\"emoji-grpc\",\n\t\t\t\t},\n\t\t\t\tisServer: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"saz\", \"-n\", prefixedNs},\n\t\t\t\texpectedRows: []string{\n\t\t\t\t\t\"emoji-grpc\",\n\t\t\t\t\t\"voting-grpc\",\n\t\t\t\t\t\"web-public\",\n\t\t\t\t},\n\t\t\t\tisServer: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"saz/emoji-grpc\", \"-n\", prefixedNs},\n\t\t\t\texpectedRows: []string{\n\t\t\t\t\t\"emoji-grpc\",\n\t\t\t\t},\n\t\t\t\tisServer: false,\n\t\t\t},\n\t\t}\n\n\t\tfor _, tt := range testCases {\n\t\t\ttt := tt // pin\n\t\t\ttimeout := 3 * time.Minute\n\t\t\tt.Run(\"linkerd \"+strings.Join(tt.args, \" \"), func(t *testing.T) {\n\t\t\t\terr := testutil.RetryFor(timeout, func() error {\n\t\t\t\t\t// Use a short time window so that transient errors at startup\n\t\t\t\t\t// fall out of the window.\n\t\t\t\t\ttt.args = append(tt.args, \"-t\", \"30s\")\n\t\t\t\t\tout, err := TestHelper.LinkerdRun(tt.args...)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected stat error\",\n\t\t\t\t\t\t\t\"unexpected stat error: %s\\n%s\", err, out)\n\t\t\t\t\t}\n\n\t\t\t\t\tvar expectedColumnCount int\n\t\t\t\t\tif tt.isServer {\n\t\t\t\t\t\texpectedColumnCount = 8\n\t\t\t\t\t} else {\n\t\t\t\t\t\texpectedColumnCount = 6\n\t\t\t\t\t}\n\n\t\t\t\t\trowStats, err := ParseAuthzRows(out, len(tt.expectedRows), expectedColumnCount, tt.isServer)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\tfor _, name := range tt.expectedRows {\n\t\t\t\t\t\tif err := validateAuthzRows(name, rowStats, tt.isServer); err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"timed-out checking policy stats (%s)\", timeout), err)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\ntype noSuccess struct{ name string }\n\nfunc (e noSuccess) Error() string {\n\treturn fmt.Sprintf(\"no success rate reported for %s\", e.name)\n}\n\nfunc validateAuthzRows(name string, rowStats map[string]*testutil.RowStat, isServer bool) error {\n\tstat, ok := rowStats[name]\n\tif !ok {\n\t\treturn fmt.Errorf(\"No stats found for [%s]\", name)\n\t}\n\n\t// Check for suffix only, as the value will not be 100% always with\n\t// the normal emojivoto sample\n\tif stat.Success == \"-\" {\n\t\treturn noSuccess{name}\n\t}\n\tif !strings.HasSuffix(stat.Success, \"%\") {\n\t\treturn fmt.Errorf(\"Unexpected success rate for [%s], got [%s]\",\n\t\t\tname, stat.Success)\n\t}\n\n\tif isServer {\n\t\tif !strings.HasSuffix(stat.UnauthorizedRPS, \"rps\") {\n\t\t\treturn fmt.Errorf(\"Unexpected Unauthorized RPS for [%s], got [%s]\",\n\t\t\t\tname, stat.UnauthorizedRPS)\n\t\t}\n\t}\n\n\tif !strings.HasSuffix(stat.Rps, \"rps\") {\n\t\treturn fmt.Errorf(\"Unexpected rps for [%s], got [%s]\",\n\t\t\tname, stat.Rps)\n\t}\n\n\tif !strings.HasSuffix(stat.P50Latency, \"ms\") {\n\t\treturn fmt.Errorf(\"Unexpected p50 latency for [%s], got [%s]\",\n\t\t\tname, stat.P50Latency)\n\t}\n\n\tif !strings.HasSuffix(stat.P95Latency, \"ms\") {\n\t\treturn fmt.Errorf(\"Unexpected p95 latency for [%s], got [%s]\",\n\t\t\tname, stat.P95Latency)\n\t}\n\n\tif !strings.HasSuffix(stat.P99Latency, \"ms\") {\n\t\treturn fmt.Errorf(\"Unexpected p99 latency for [%s], got [%s]\",\n\t\t\tname, stat.P99Latency)\n\t}\n\n\tif isServer {\n\t\t_, err := strconv.Atoi(stat.TCPOpenConnections)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Error parsing number of TCP connections [%s]: %w\", stat.TCPOpenConnections, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ParseAuthzRows parses the output of linkerd stat on a policy resource\nfunc ParseAuthzRows(out string, expectedRowCount, expectedColumnCount int, isServer bool) (map[string]*testutil.RowStat, error) {\n\trows, err := testutil.CheckRowCount(out, expectedRowCount)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trowStats := make(map[string]*testutil.RowStat)\n\tfor _, row := range rows {\n\t\tfields := strings.Fields(row)\n\n\t\tif len(fields) != expectedColumnCount {\n\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\"Expected [%d] columns in stat output, got [%d]; full output:\\n%s\",\n\t\t\t\texpectedColumnCount, len(fields), row)\n\t\t}\n\n\t\ti := 0\n\t\trowStats[fields[0]] = &testutil.RowStat{\n\t\t\tName: fields[0],\n\t\t}\n\n\t\tif isServer {\n\t\t\trowStats[fields[0]].UnauthorizedRPS = fields[1+i]\n\t\t\trowStats[fields[0]].Success = fields[2+i]\n\t\t\trowStats[fields[0]].Rps = fields[3+i]\n\t\t\trowStats[fields[0]].P50Latency = fields[4+i]\n\t\t\trowStats[fields[0]].P95Latency = fields[5+i]\n\t\t\trowStats[fields[0]].P99Latency = fields[6+i]\n\t\t\trowStats[fields[0]].TCPOpenConnections = fields[7+i]\n\t\t} else {\n\t\t\trowStats[fields[0]].Success = fields[1+i]\n\t\t\trowStats[fields[0]].Rps = fields[2+i]\n\t\t\trowStats[fields[0]].P50Latency = fields[3+i]\n\t\t\trowStats[fields[0]].P95Latency = fields[4+i]\n\t\t\trowStats[fields[0]].P99Latency = fields[5+i]\n\t\t}\n\n\t}\n\n\treturn rowStats, nil\n}\n"
  },
  {
    "path": "test/integration/viz/policy/testdata/emoji-policy.yaml",
    "content": "---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  name: emoji-grpc\n  labels:\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/name: emoji\n    app.kubernetes.io/version: v11\nspec:\n  podSelector:\n    matchLabels:\n      app: emoji-svc\n  port: grpc\n  proxyProtocol: gRPC\n---\napiVersion: policy.linkerd.io/v1beta1\nkind: ServerAuthorization\nmetadata:\n  name: emoji-grpc\n  labels:\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/name: emoji\n    app.kubernetes.io/version: v11\nspec:\n  # Allow all authenticated clients to access the (read-only) emoji service.\n  server:\n    name: emoji-grpc\n  client:\n    meshTLS:\n      identities:\n        - \"*.linkerd-stat-authz-test.serviceaccount.identity.linkerd.cluster.local\"\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  name: voting-grpc\n  labels:\n    app: voting-svc\nspec:\n  podSelector:\n    matchLabels:\n      app: voting-svc\n  port: grpc\n  proxyProtocol: gRPC\n---\napiVersion: policy.linkerd.io/v1beta1\nkind: ServerAuthorization\nmetadata:\n  name: voting-grpc\n  labels:\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/name: voting\n    app.kubernetes.io/version: v11\nspec:\n  server:\n    name: voting-grpc\n  # The voting service only allows requests from the web service.\n  client:\n    meshTLS:\n      serviceAccounts:\n        - name: web\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  name: web-http\n  labels:\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/name: web\n    app.kubernetes.io/version: v11\nspec:\n  podSelector:\n    matchLabels:\n      app: web-svc\n  port: http\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1beta1\nkind: ServerAuthorization\nmetadata:\n  name: web-public\n  labels:\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/name: web\n    app.kubernetes.io/version: v11\nspec:\n  server:\n    name: web-http\n  # Allow all clients to access the web HTTP port without regard for\n  # authentication. If unauthenticated connections are permitted, there is no\n  # need to describe authenticated clients.\n  client:\n    unauthenticated: true\n    networks:\n      - cidr: 0.0.0.0/0\n      - cidr: ::/0\n"
  },
  {
    "path": "test/integration/viz/policy/testdata/emojivoto.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: emoji\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: voting\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: web\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: emoji-svc\nspec:\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n  - name: prom\n    port: 8801\n    targetPort: 8801\n  selector:\n    app: emoji-svc\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: voting-svc\nspec:\n  ports:\n  - name: grpc\n    port: 8080\n    targetPort: 8080\n  - name: prom\n    port: 8801\n    targetPort: 8801\n  selector:\n    app: voting-svc\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: web-svc\nspec:\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n  selector:\n    app: web-svc\n  type: ClusterIP\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: emoji\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v11\n  name: emoji\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: emoji-svc\n      version: v11\n  template:\n    metadata:\n      labels:\n        app: emoji-svc\n        version: v11\n    spec:\n      containers:\n      - env:\n        - name: GRPC_PORT\n          value: \"8080\"\n        - name: PROM_PORT\n          value: \"8801\"\n        image: docker.l5d.io/buoyantio/emojivoto-emoji-svc:v11\n        name: emoji-svc\n        ports:\n        - containerPort: 8080\n          name: grpc\n        - containerPort: 8801\n          name: prom\n        resources:\n          requests:\n            cpu: 100m\n      serviceAccountName: emoji\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: vote-bot\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v11\n  name: vote-bot\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: vote-bot\n      version: v11\n  template:\n    metadata:\n      labels:\n        app: vote-bot\n        version: v11\n    spec:\n      containers:\n      - command:\n        - emojivoto-vote-bot\n        env:\n        - name: WEB_HOST\n          value: web-svc.___NS___:80\n        image: docker.l5d.io/buoyantio/emojivoto-web:v11\n        name: vote-bot\n        resources:\n          requests:\n            cpu: 10m\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: voting\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v11\n  name: voting\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: voting-svc\n      version: v11\n  template:\n    metadata:\n      labels:\n        app: voting-svc\n        version: v11\n    spec:\n      containers:\n      - env:\n        - name: GRPC_PORT\n          value: \"8080\"\n        - name: PROM_PORT\n          value: \"8801\"\n        image: docker.l5d.io/buoyantio/emojivoto-voting-svc:v11\n        name: voting-svc\n        ports:\n        - containerPort: 8080\n          name: grpc\n        - containerPort: 8801\n          name: prom\n        resources:\n          requests:\n            cpu: 100m\n      serviceAccountName: voting\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: web\n    app.kubernetes.io/part-of: emojivoto\n    app.kubernetes.io/version: v11\n  name: web\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: web-svc\n      version: v11\n  template:\n    metadata:\n      labels:\n        app: web-svc\n        version: v11\n    spec:\n      containers:\n      - env:\n        - name: WEB_PORT\n          value: \"8080\"\n        - name: EMOJISVC_HOST\n          value: emoji-svc.___NS___:8080\n        - name: VOTINGSVC_HOST\n          value: voting-svc.___NS___:8080\n        - name: INDEX_BUNDLE\n          value: dist/index_bundle.js\n        image: docker.l5d.io/buoyantio/emojivoto-web:v11\n        name: web-svc\n        ports:\n        - containerPort: 8080\n          name: http\n        resources:\n          requests:\n            cpu: 100m\n      serviceAccountName: web\n"
  },
  {
    "path": "test/integration/viz/routes/routes_test.go",
    "content": "package get\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\n//////////////////////\n///   TEST SETUP   ///\n//////////////////////\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until viz extension is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdVizDeployReplicas)\n\tos.Exit(m.Run())\n}\n\ntype testCase struct {\n\ts string\n\tc int\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\n// TestRoutes exercises the \"linkerd routes\" command, validating the\n// installation and output of ServiceProfiles for both the control-plane and\n// smoke test.\nfunc TestRoutes(t *testing.T) {\n\t// control-plane routes\n\tcmd := []string{\"viz\", \"routes\", \"--namespace\", TestHelper.GetLinkerdNamespace(), \"deploy\"}\n\tout, err := TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd routes' command failed\", err)\n\t}\n\n\trouteStrings := []testCase{\n\t\t{\"linkerd-destination\", 1},\n\t\t{\"linkerd-identity\", 3},\n\t\t{\"linkerd-proxy-injector\", 2},\n\t}\n\n\tfor _, r := range routeStrings {\n\t\tcount := strings.Count(out, r.s)\n\t\tif count != r.c {\n\t\t\ttestutil.AnnotatedFatalf(t, fmt.Sprintf(\"expected %d occurrences of \\\"%s\\\", got %d\", r.c, r.s, count),\n\t\t\t\t\"expected %d occurrences of \\\"%s\\\", got %d\\n%s\", r.c, r.s, count, out)\n\t\t}\n\t}\n\n\t// viz routes\n\tcmd = []string{\"viz\", \"routes\", \"--namespace\", TestHelper.GetVizNamespace(), \"deploy\"}\n\tout, err = TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd routes' command failed\", err)\n\t}\n\n\tvizRouteStrings := []testCase{\n\t\t{\"metrics-api\", 9},\n\t\t{\"tap\", 4},\n\t\t{\"tap-injector\", 2},\n\t\t{\"web\", 2},\n\t}\n\n\tif !TestHelper.ExternalPrometheus() {\n\t\tvizRouteStrings = append(vizRouteStrings, testCase{\n\t\t\t\"prometheus\",\n\t\t\t5,\n\t\t})\n\t}\n\tfor _, r := range vizRouteStrings {\n\t\tcount := strings.Count(out, r.s)\n\t\tif count != r.c {\n\t\t\ttestutil.AnnotatedFatalf(t, fmt.Sprintf(\"expected %d occurrences of \\\"%s\\\", got %d\", r.c, r.s, count),\n\t\t\t\t\"expected %d occurrences of \\\"%s\\\", got %d\\n%s\", r.c, r.s, count, out)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/integration/viz/serviceprofiles/serviceprofiles_test.go",
    "content": "package serviceprofiles\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\t\"github.com/linkerd/linkerd2/testutil\"\n\tcmd2 \"github.com/linkerd/linkerd2/viz/cmd\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nvar TestHelper *testutil.TestHelper\n\ntype testCase struct {\n\targs           []string\n\tdeployName     string\n\texpectedRoutes []string\n\tnamespace      string\n\tsourceName     string\n\tspName         string\n}\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until viz extension is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdVizDeployReplicas)\n\tos.Exit(m.Run())\n}\n\nfunc TestServiceProfiles(t *testing.T) {\n\tctx := context.Background()\n\tTestHelper.WithDataPlaneNamespace(ctx, \"serviceprofile-test\", map[string]string{}, t, func(t *testing.T, ns string) {\n\t\tt.Run(\"service profiles\", testProfiles)\n\t\tt.Run(\"service profiles metrics\", testMetrics)\n\t})\n}\n\nfunc testProfiles(t *testing.T) {\n\tctx := context.Background()\n\ttestNamespace := TestHelper.GetTestNamespace(\"serviceprofile-test\")\n\tout, err := TestHelper.LinkerdRun(\"inject\", \"--manual\", \"testdata/tap_application.yaml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd inject' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApply(out, testNamespace)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\t// wait for deployments to start\n\tfor _, deploy := range []string{\"t1\", \"t2\", \"t3\", \"gateway\"} {\n\t\tif err := TestHelper.CheckPods(ctx, testNamespace, deploy, 1); err != nil {\n\t\t\t//nolint:errorlint\n\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t} else {\n\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tsourceName:     \"tap\",\n\t\t\tnamespace:      testNamespace,\n\t\t\tdeployName:     \"deployment/t1\",\n\t\t\tspName:         \"t1-svc\",\n\t\t\texpectedRoutes: []string{\"POST /buoyantio.bb.TheService/theFunction\", \"[DEFAULT]\"},\n\t\t},\n\t\t{\n\t\t\tsourceName:     \"open-api\",\n\t\t\tnamespace:      testNamespace,\n\t\t\tspName:         \"t3-svc\",\n\t\t\tdeployName:     \"deployment/t3\",\n\t\t\texpectedRoutes: []string{\"DELETE /testpath\", \"GET /testpath\", \"PATCH /testpath\", \"POST /testpath\", \"[DEFAULT]\"},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(tc.sourceName, func(t *testing.T) {\n\t\t\troutes, err := getRoutes(tc.deployName, tc.namespace, []string{})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"'linkerd routes' command failed\",\n\t\t\t\t\t\"'linkerd routes' command failed: %s\\n\", err)\n\t\t\t}\n\n\t\t\tinitialExpectedRoutes := []string{\"[DEFAULT]\"}\n\n\t\t\tassertExpectedRoutes(initialExpectedRoutes, routes, t)\n\n\t\t\tsourceFlag := fmt.Sprintf(\"--%s\", tc.sourceName)\n\t\t\tcmd := []string{\"profile\", \"--namespace\", tc.namespace, tc.spName, sourceFlag}\n\t\t\tif tc.sourceName == \"tap\" {\n\t\t\t\tcmd = append([]string{\"viz\"}, cmd...)\n\t\t\t\ttc.args = []string{\n\t\t\t\t\ttc.deployName,\n\t\t\t\t\t\"--tap-route-limit\",\n\t\t\t\t\t\"1\",\n\t\t\t\t\t\"--tap-duration\",\n\t\t\t\t\t\"25s\",\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif tc.sourceName == \"open-api\" {\n\t\t\t\ttc.args = []string{\n\t\t\t\t\t\"testdata/t3.swagger\",\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcmd = append(cmd, tc.args...)\n\t\t\tout, err := TestHelper.LinkerdRun(cmd...)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"'linkerd %s' command failed\", cmd), err)\n\t\t\t}\n\n\t\t\t_, err = TestHelper.KubectlApply(out, tc.namespace)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\t\"'kubectl apply' command failed:\\n%s\", err)\n\t\t\t}\n\n\t\t\troutes, err = getRoutes(tc.deployName, tc.namespace, []string{})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"'linkerd routes' command failed\",\n\t\t\t\t\t\"'linkerd routes' command failed: %s\\n\", err)\n\t\t\t}\n\n\t\t\tassertExpectedRoutes(tc.expectedRoutes, routes, t)\n\t\t})\n\t}\n}\n\nfunc testMetrics(t *testing.T) {\n\tvar (\n\t\ttestNamespace        = TestHelper.GetTestNamespace(\"serviceprofile-test\")\n\t\ttestSP               = \"world-svc\"\n\t\ttestDownstreamDeploy = \"deployment/world\"\n\t\ttestUpstreamDeploy   = \"deployment/hello\"\n\t\ttestYAML             = \"testdata/hello_world.yaml\"\n\t)\n\n\tout, err := TestHelper.LinkerdRun(\"inject\", \"--manual\", testYAML)\n\tif err != nil {\n\t\ttestutil.AnnotatedError(t, \"'linkerd inject' command failed\", err)\n\t}\n\n\tout, err = TestHelper.KubectlApply(out, testNamespace)\n\tif err != nil {\n\t\ttestutil.AnnotatedErrorf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t}\n\n\tcmd := []string{\n\t\t\"profile\",\n\t\t\"--namespace\",\n\t\ttestNamespace,\n\t\t\"--open-api\",\n\t\t\"testdata/world.swagger\",\n\t\ttestSP,\n\t}\n\n\tout, err = TestHelper.LinkerdRun(cmd...)\n\tif err != nil {\n\t\ttestutil.AnnotatedError(t, fmt.Sprintf(\"'linkerd %s' command failed\", cmd), err)\n\t}\n\n\t_, err = TestHelper.KubectlApply(out, testNamespace)\n\tif err != nil {\n\t\ttestutil.AnnotatedErrorf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed\\n%s\", err)\n\t}\n\n\tassertRouteStat(testUpstreamDeploy, testNamespace, testDownstreamDeploy, t, func(stat *cmd2.JSONRouteStats) error {\n\t\tif !(*stat.ActualSuccess > 0.00 && *stat.ActualSuccess < 100.00) {\n\t\t\treturn fmt.Errorf(\"expected Actual Success to be greater than 0%% and less than 100%% due to pre-seeded failure rate. But got %0.2f\", *stat.ActualSuccess)\n\t\t}\n\t\treturn nil\n\t})\n\n\tprofile := &sp.ServiceProfile{}\n\n\t// Grab the output and convert it to a service profile object for modification\n\terr = yaml.Unmarshal([]byte(out), profile)\n\tif err != nil {\n\t\ttestutil.AnnotatedErrorf(t, \"unable to unmarshal YAML\",\n\t\t\t\"unable to unmarshal YAML: %s\", err)\n\t}\n\n\t// introduce retry in the service profile\n\tfor _, route := range profile.Spec.Routes {\n\t\tif route.Name == \"GET /testpath\" {\n\t\t\troute.IsRetryable = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tbytes, err := yaml.Marshal(profile)\n\tif err != nil {\n\t\ttestutil.AnnotatedErrorf(t, \"error marshalling service profile\",\n\t\t\t\"error marshalling service profile: %s\", bytes)\n\t}\n\n\tout, err = TestHelper.KubectlApply(string(bytes), testNamespace)\n\tif err != nil {\n\t\ttestutil.AnnotatedErrorf(t, \"'kubectl apply' command failed\",\n\t\t\t\"'kubectl apply' command failed:\\n%s :%s\", err, out)\n\t}\n\n\tassertRouteStat(testUpstreamDeploy, testNamespace, testDownstreamDeploy, t, func(stat *cmd2.JSONRouteStats) error {\n\t\tif *stat.EffectiveSuccess < 0.95 {\n\t\t\treturn fmt.Errorf(\"expected Effective Success to be at least 95%% with retries enabled. But got %.2f\", *stat.EffectiveSuccess)\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc assertRouteStat(upstream, namespace, downstream string, t *testing.T, assertFn func(stat *cmd2.JSONRouteStats) error) {\n\tconst routePath = \"GET /testpath\"\n\ttimeout := 2 * time.Minute\n\terr := testutil.RetryFor(timeout, func() error {\n\t\troutes, err := getRoutes(upstream, namespace, []string{\"--to\", downstream})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"'linkerd routes' command failed: %w\", err)\n\t\t}\n\n\t\tvar testRoute *cmd2.JSONRouteStats\n\t\tassertExpectedRoutes([]string{routePath, \"[DEFAULT]\"}, routes, t)\n\n\t\tfor _, route := range routes {\n\t\t\tif route.Route == routePath {\n\t\t\t\ttestRoute = route\n\t\t\t}\n\t\t}\n\n\t\tif testRoute == nil {\n\t\t\treturn errors.New(\"expected test route not to be nil\")\n\t\t}\n\n\t\treturn assertFn(testRoute)\n\t})\n\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"timed-out asserting route stat (%s)\", timeout), err)\n\t}\n}\n\nfunc assertExpectedRoutes(expected []string, actual []*cmd2.JSONRouteStats, t *testing.T) {\n\n\tif len(expected) != len(actual) {\n\t\ttestutil.Errorf(t, \"mismatch routes count. Expected %d, Actual %d\", len(expected), len(actual))\n\t}\n\n\tfor _, expectedRoute := range expected {\n\t\tcontainsRoute := false\n\t\tfor _, actualRoute := range actual {\n\t\t\tif actualRoute.Route == expectedRoute {\n\t\t\t\tcontainsRoute = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !containsRoute {\n\t\t\tsb := strings.Builder{}\n\t\t\tfor _, route := range actual {\n\t\t\t\tsb.WriteString(fmt.Sprintf(\"%s \", route.Route))\n\t\t\t}\n\t\t\ttestutil.Errorf(t, \"expected route %s not found in %+v\", expectedRoute, sb.String())\n\t\t}\n\t}\n}\n\nfunc getRoutes(deployName, namespace string, additionalArgs []string) ([]*cmd2.JSONRouteStats, error) {\n\tcmd := []string{\"viz\", \"routes\", \"--namespace\", namespace, deployName}\n\n\tif len(additionalArgs) > 0 {\n\t\tcmd = append(cmd, additionalArgs...)\n\t}\n\n\tcmd = append(cmd, \"--output\", \"json\")\n\tvar results map[string][]*cmd2.JSONRouteStats\n\terr := testutil.RetryFor(2*time.Minute, func() error {\n\t\tout, err := TestHelper.LinkerdRun(cmd...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := yaml.Unmarshal([]byte(out), &results); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif _, ok := results[deployName]; ok {\n\t\t\treturn nil\n\t\t}\n\n\t\tkeys := []string{}\n\t\tfor k := range results {\n\t\t\tkeys = append(keys, k)\n\t\t}\n\t\treturn fmt.Errorf(\"could not retrieve route info for %s; found [%s]\", deployName, strings.Join(keys, \", \"))\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn results[deployName], nil\n\n}\n"
  },
  {
    "path": "test/integration/viz/serviceprofiles/testdata/hello_world.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: hello\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: hello\n  template:\n    metadata:\n      labels:\n        app: hello\n    spec:\n      containers:\n      - name: hello\n        image: buoyantio/helloworld:0.1.7\n        args:\n        - \"-addr=:8888\"\n        - \"-text=Hello\"\n        - \"-target=world-svc:8889/testpath\"\n        ports:\n        - name: service\n          containerPort: 8888\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: hello-svc\nspec:\n  selector:\n    app: hello\n  ports:\n  - name: http\n    port: 8888\n    targetPort: 8888\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: world\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: world\n  template:\n    metadata:\n      labels:\n        app: world\n    spec:\n      containers:\n      - name: world\n        image: buoyantio/helloworld:0.1.7\n        args:\n        - \"-addr=:8889\"\n        - \"-text=World\"\n        - \"-failure-rate=0.5\"\n        ports:\n        - name: service\n          containerPort: 8889\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: world-svc\nspec:\n  selector:\n    app: world\n  ports:\n  - name: http\n    port: 8889\n    targetPort: 8889\n---\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: hello-slow-cooker\nspec:\n  template:\n    metadata:\n      labels:\n        app: hello-slow-cooker\n    spec:\n      containers:\n      - name: hello-slow-cooker\n        image: buoyantio/slow_cooker:1.3.0\n        command:\n        - \"/bin/sh\"\n        args:\n        - \"-c\"\n        - |\n          sleep 15 # wait for pods to start\n          /slow_cooker/slow_cooker -metric-addr 0.0.0.0:9998 http://hello-svc:8888/testpath\n      restartPolicy: OnFailure\n"
  },
  {
    "path": "test/integration/viz/serviceprofiles/testdata/t3.swagger",
    "content": "openapi: '3.0.1'\npaths:\n  /testpath:\n    get: {}\n    post: {}\n    delete: {}\n    patch: {}\n"
  },
  {
    "path": "test/integration/viz/serviceprofiles/testdata/tap_application.yaml",
    "content": "# /slow_cooker/slow_cooker --http-> gateway --grpc-> t1\n#                              --grpc-> t2 always-error\n#                              --http-> t3\n#\n\n### t1 terminates gRPC requests\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: t1\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: t1\n  template:\n    metadata:\n      labels:\n        app: t1\n    spec:\n      containers:\n      - name: t1\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--grpc-server-port=9090\"\n        - \"--response-text=t1\"\n        ports:\n        - containerPort: 9090\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: t1-svc\nspec:\n  selector:\n    app: t1\n  ports:\n  - name: grpc\n    port: 9090\n    targetPort: 9090\n\n### t2 terminates gRPC requests and always fails\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: t2\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: t2\n  template:\n    metadata:\n      labels:\n        app: t2\n    spec:\n      containers:\n      - name: t2\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--grpc-server-port=9090\"\n        - \"--response-text=t2\"\n        - \"--percent-failure=100\"\n        ports:\n        - containerPort: 9090\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: t2-svc\nspec:\n  selector:\n    app: t2\n  ports:\n  - name: grpc\n    port: 9090\n    targetPort: 9090\n\n# t3 terminates HTTP/1.1 requests\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: t3\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: t3\n  template:\n    metadata:\n      labels:\n        app: t3\n    spec:\n      containers:\n      - name: t3\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--percent-failure=50\"\n        - \"--response-text=t3\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: t3-svc\nspec:\n  selector:\n    app: t3\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n\n### gateway broadcasts requests to t1, t2, and t3\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: gateway\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: gateway\n  template:\n    metadata:\n      labels:\n        app: gateway\n    spec:\n      containers:\n      - name: gateway\n        image: buoyantio/bb:v0.0.6\n        args:\n        - broadcast-channel\n        - \"--h1-server-port=8080\"\n        - \"--grpc-downstream-server=t1-svc:9090\"\n        - \"--grpc-downstream-server=t2-svc:9090\"\n        - \"--h1-downstream-server=http://t3-svc:8080/testpath\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: gateway-svc\nspec:\n  selector:\n    app: gateway\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n\n### slow-cooker sends requests to the gateway\n---\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: slow-cooker\nspec:\n  template:\n    metadata:\n      labels:\n        app: slow-cooker\n    spec:\n      containers:\n      - name: slow-cooker\n        image: buoyantio/slow_cooker:1.3.0\n        command:\n        - \"/bin/sh\"\n        args:\n        - \"-c\"\n        - |\n          sleep 15 # wait for pods to start\n          /slow_cooker/slow_cooker -metric-addr 0.0.0.0:9999 http://gateway-svc:8080\n        ports:\n        - containerPort: 9999\n      restartPolicy: OnFailure\n"
  },
  {
    "path": "test/integration/viz/serviceprofiles/testdata/world.swagger",
    "content": "openapi: '3.0.1'\npaths:\n  /testpath:\n    get: {}\n"
  },
  {
    "path": "test/integration/viz/stat/stat_test.go",
    "content": "package get\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\n//////////////////////\n///   TEST SETUP   ///\n//////////////////////\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until viz extension is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdVizDeployReplicas)\n\tos.Exit(m.Run())\n}\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\n// These tests retry for up to 20 seconds, since each call to \"linkerd stat\"\n// generates traffic to the components in the linkerd namespace, and we're\n// testing that those components are properly reporting stats. It's ok if the\n// first few attempts fail due to missing stats, since the requests from those\n// failed attempts will eventually be recorded in the stats that we're\n// requesting, and the test will pass.\nfunc TestCliStatForLinkerdNamespace(t *testing.T) {\n\tctx := context.Background()\n\tvar prometheusPod, prometheusNamespace, prometheusDeployment, metricsPod string\n\t// Get Metrics Pod\n\tpods, err := TestHelper.GetPodNamesForDeployment(ctx, TestHelper.GetVizNamespace(), \"metrics-api\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to get pods for metrics-api\",\n\t\t\t\"failed to get pods for metrics-api: %s\", err)\n\t}\n\tif len(pods) != 1 {\n\t\ttestutil.Fatalf(t, \"expected 1 pod for metrics-api, got %d\", len(pods))\n\t}\n\tmetricsPod = pods[0]\n\n\t// Retrieve Prometheus pod details\n\tprometheusNamespace = TestHelper.GetVizNamespace()\n\tprometheusDeployment = \"prometheus\"\n\n\tpods, err = TestHelper.GetPodNamesForDeployment(ctx, prometheusNamespace, prometheusDeployment)\n\tif err != nil {\n\t\ttestutil.AnnotatedFatalf(t, \"failed to get pods for prometheus\",\n\t\t\t\"failed to get pods for prometheus: %s\", err)\n\t}\n\tif len(pods) != 1 {\n\t\ttestutil.Fatalf(t, \"expected 1 pod for prometheus, got %d\", len(pods))\n\t}\n\tprometheusPod = pods[0]\n\n\ttestCases := []struct {\n\t\targs         []string\n\t\texpectedRows map[string]string\n\t\tstatus       string\n\t}{\n\t\t{\n\t\t\targs: []string{\"viz\", \"stat\", \"deploy\", \"-n\", TestHelper.GetLinkerdNamespace()},\n\t\t\texpectedRows: map[string]string{\n\t\t\t\t\"linkerd-destination\":    \"1/1\",\n\t\t\t\t\"linkerd-identity\":       \"1/1\",\n\t\t\t\t\"linkerd-proxy-injector\": \"1/1\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\targs: []string{\"viz\", \"stat\", \"ns\", TestHelper.GetLinkerdNamespace()},\n\t\t\texpectedRows: map[string]string{\n\t\t\t\tTestHelper.GetLinkerdNamespace(): \"3/3\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\targs: []string{\"viz\", \"stat\", fmt.Sprintf(\"po/%s\", prometheusPod), \"-n\", prometheusNamespace, \"--from\", fmt.Sprintf(\"po/%s\", metricsPod), \"--from-namespace\", TestHelper.GetVizNamespace()},\n\t\t\texpectedRows: map[string]string{\n\t\t\t\tprometheusPod: \"1/1\",\n\t\t\t},\n\t\t\tstatus: \"Running\",\n\t\t},\n\t\t{\n\t\t\targs: []string{\"viz\", \"stat\", \"deploy\", \"-n\", TestHelper.GetVizNamespace(), \"--to\", fmt.Sprintf(\"po/%s\", prometheusPod), \"--to-namespace\", prometheusNamespace},\n\t\t\texpectedRows: map[string]string{\n\t\t\t\t\"metrics-api\": \"1/1\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\targs: []string{\"viz\", \"stat\", \"deploy\", \"-n\", TestHelper.GetVizNamespace(), \"--to\", fmt.Sprintf(\"svc/%s\", prometheusDeployment), \"--to-namespace\", prometheusNamespace},\n\t\t\texpectedRows: map[string]string{\n\t\t\t\t\"metrics-api\": \"1/1\",\n\t\t\t},\n\t\t},\n\t}\n\n\tif !TestHelper.ExternalPrometheus() {\n\t\ttestCases = append(testCases, []struct {\n\t\t\targs         []string\n\t\t\texpectedRows map[string]string\n\t\t\tstatus       string\n\t\t}{\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"deploy\", \"-n\", TestHelper.GetVizNamespace()},\n\t\t\t\texpectedRows: map[string]string{\n\t\t\t\t\t\"metrics-api\":  \"1/1\",\n\t\t\t\t\t\"prometheus\":   \"1/1\",\n\t\t\t\t\t\"tap\":          \"1/1\",\n\t\t\t\t\t\"web\":          \"1/1\",\n\t\t\t\t\t\"tap-injector\": \"1/1\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"ns\", TestHelper.GetVizNamespace()},\n\t\t\t\texpectedRows: map[string]string{\n\t\t\t\t\tTestHelper.GetVizNamespace(): \"5/5\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"svc\", \"prometheus\", \"-n\", TestHelper.GetVizNamespace(), \"--from\", \"deploy/metrics-api\", \"--from-namespace\", TestHelper.GetVizNamespace()},\n\t\t\t\texpectedRows: map[string]string{\n\t\t\t\t\t\"prometheus\": \"-\",\n\t\t\t\t},\n\t\t\t},\n\t\t}...,\n\t\t)\n\t} else {\n\t\ttestCases = append(testCases, []struct {\n\t\t\targs         []string\n\t\t\texpectedRows map[string]string\n\t\t\tstatus       string\n\t\t}{\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"deploy\", \"-n\", TestHelper.GetVizNamespace()},\n\t\t\t\texpectedRows: map[string]string{\n\t\t\t\t\t\"metrics-api\":  \"1/1\",\n\t\t\t\t\t\"tap\":          \"1/1\",\n\t\t\t\t\t\"web\":          \"1/1\",\n\t\t\t\t\t\"tap-injector\": \"1/1\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"ns\", TestHelper.GetVizNamespace()},\n\t\t\t\texpectedRows: map[string]string{\n\t\t\t\t\tTestHelper.GetVizNamespace(): \"4/4\",\n\t\t\t\t},\n\t\t\t},\n\t\t}...,\n\t\t)\n\t}\n\n\t// Apply a sample application\n\tTestHelper.WithDataPlaneNamespace(ctx, \"stat-test\", map[string]string{}, t, func(t *testing.T, prefixedNs string) {\n\t\tout, err := TestHelper.LinkerdRun(\"inject\", \"--manual\", \"../trafficsplit/testdata/application.yaml\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"'linkerd inject' command failed\", err)\n\t\t}\n\n\t\tout, err = TestHelper.KubectlApply(out, prefixedNs)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t\t}\n\n\t\t// wait for deployments to start\n\t\tfor _, deploy := range []string{\"backend\", \"failing\", \"slow-cooker\"} {\n\t\t\tif err := TestHelper.CheckPods(ctx, prefixedNs, deploy, 1); err != nil {\n\t\t\t\t//nolint:errorlint\n\t\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t\t} else {\n\t\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttestCases = append(testCases, []struct {\n\t\t\targs         []string\n\t\t\texpectedRows map[string]string\n\t\t\tstatus       string\n\t\t}{\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"svc\", \"-n\", prefixedNs},\n\t\t\t\texpectedRows: map[string]string{\n\t\t\t\t\t\"backend-svc\": \"-\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\targs: []string{\"viz\", \"stat\", \"svc/backend-svc\", \"-n\", prefixedNs, \"--from\", \"deploy/slow-cooker\"},\n\t\t\t\texpectedRows: map[string]string{\n\t\t\t\t\t\"backend-svc\": \"-\",\n\t\t\t\t},\n\t\t\t},\n\t\t}...,\n\t\t)\n\n\t\tfor _, tt := range testCases {\n\t\t\ttt := tt // pin\n\t\t\ttimeout := 20 * time.Second\n\t\t\tt.Run(\"linkerd \"+strings.Join(tt.args, \" \"), func(t *testing.T) {\n\t\t\t\terr := testutil.RetryFor(timeout, func() error {\n\t\t\t\t\t// Use a short time window so that transient errors at startup\n\t\t\t\t\t// fall out of the window.\n\t\t\t\t\ttt.args = append(tt.args, \"-t\", \"30s\")\n\t\t\t\t\tout, err := TestHelper.LinkerdRun(tt.args...)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected stat error\",\n\t\t\t\t\t\t\t\"unexpected stat error: %s\\n%s\", err, out)\n\t\t\t\t\t}\n\n\t\t\t\t\texpectedColumnCount := 8\n\t\t\t\t\tif tt.status != \"\" {\n\t\t\t\t\t\texpectedColumnCount++\n\t\t\t\t\t}\n\t\t\t\t\trowStats, err := testutil.ParseRows(out, len(tt.expectedRows), expectedColumnCount)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\tfor name, meshed := range tt.expectedRows {\n\t\t\t\t\t\tif err := validateRowStats(name, meshed, tt.status, rowStats); err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"timed-out checking stats (%s)\", timeout), err)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n\nfunc validateRowStats(name, expectedMeshCount, expectedStatus string, rowStats map[string]*testutil.RowStat) error {\n\tstat, ok := rowStats[name]\n\tif !ok {\n\t\treturn fmt.Errorf(\"No stats found for [%s]\", name)\n\t}\n\n\tif stat.Status != expectedStatus {\n\t\treturn fmt.Errorf(\"Expected status '%s' for '%s', got '%s'\",\n\t\t\texpectedStatus, name, stat.Status)\n\t}\n\n\tif stat.Meshed != expectedMeshCount {\n\t\treturn fmt.Errorf(\"Expected mesh count [%s] for [%s], got [%s]\",\n\t\t\texpectedMeshCount, name, stat.Meshed)\n\t}\n\n\texpectedSuccessRate := \"100.00%\"\n\tif stat.Success != expectedSuccessRate {\n\t\treturn fmt.Errorf(\"Expected success rate [%s] for [%s], got [%s]\",\n\t\t\texpectedSuccessRate, name, stat.Success)\n\t}\n\n\tif !strings.HasSuffix(stat.Rps, \"rps\") {\n\t\treturn fmt.Errorf(\"Unexpected rps for [%s], got [%s]\",\n\t\t\tname, stat.Rps)\n\t}\n\n\tif !strings.HasSuffix(stat.P50Latency, \"ms\") {\n\t\treturn fmt.Errorf(\"Unexpected p50 latency for [%s], got [%s]\",\n\t\t\tname, stat.P50Latency)\n\t}\n\n\tif !strings.HasSuffix(stat.P95Latency, \"ms\") {\n\t\treturn fmt.Errorf(\"Unexpected p95 latency for [%s], got [%s]\",\n\t\t\tname, stat.P95Latency)\n\t}\n\n\tif !strings.HasSuffix(stat.P99Latency, \"ms\") {\n\t\treturn fmt.Errorf(\"Unexpected p99 latency for [%s], got [%s]\",\n\t\t\tname, stat.P99Latency)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "test/integration/viz/tap/tap_test.go",
    "content": "package tap\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\n//////////////////////\n///   TEST SETUP   ///\n//////////////////////\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until viz extension is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdVizDeployReplicas)\n\tos.Exit(m.Run())\n}\n\nvar (\n\texpectedT1 = testutil.TapEvent{\n\t\tMethod:     \"POST\",\n\t\tAuthority:  \"t1-svc:9090\",\n\t\tPath:       \"/buoyantio.bb.TheService/theFunction\",\n\t\tHTTPStatus: \"200\",\n\t\tGrpcStatus: \"OK\",\n\t\tTLS:        \"true\",\n\t\tLineCount:  3,\n\t}\n\n\texpectedT2 = testutil.TapEvent{\n\t\tMethod:     \"POST\",\n\t\tAuthority:  \"t2-svc:9090\",\n\t\tPath:       \"/buoyantio.bb.TheService/theFunction\",\n\t\tHTTPStatus: \"200\",\n\t\tGrpcStatus: \"Unknown\",\n\t\tTLS:        \"true\",\n\t\tLineCount:  3,\n\t}\n\n\texpectedT3 = testutil.TapEvent{\n\t\tMethod:     \"POST\",\n\t\tAuthority:  \"t3-svc:8080\",\n\t\tPath:       \"/\",\n\t\tHTTPStatus: \"200\",\n\t\tGrpcStatus: \"\",\n\t\tTLS:        \"true\",\n\t\tLineCount:  3,\n\t}\n\n\texpectedGateway = testutil.TapEvent{\n\t\tMethod:     \"GET\",\n\t\tAuthority:  \"gateway-svc:8080\",\n\t\tPath:       \"/\",\n\t\tHTTPStatus: \"500\",\n\t\tGrpcStatus: \"\",\n\t\tTLS:        \"true\",\n\t\tLineCount:  3,\n\t}\n)\n\n//////////////////////\n/// TEST EXECUTION ///\n//////////////////////\n\nfunc TestCliTap(t *testing.T) {\n\tout, err := TestHelper.LinkerdRun(\"inject\", \"--manual\", \"testdata/tap_application.yaml\")\n\tif err != nil {\n\t\ttestutil.AnnotatedFatal(t, \"'linkerd inject' command failed\", err)\n\t}\n\n\tctx := context.Background()\n\tTestHelper.WithDataPlaneNamespace(ctx, \"tap-test\", map[string]string{}, t, func(t *testing.T, prefixedNs string) {\n\n\t\tout, err = TestHelper.KubectlApply(out, prefixedNs)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t\t}\n\n\t\t// wait for deployments to start\n\t\tfor _, deploy := range []string{\"t1\", \"t2\", \"t3\", \"gateway\"} {\n\t\t\tif err := TestHelper.CheckPods(ctx, prefixedNs, deploy, 1); err != nil {\n\t\t\t\t//nolint:errorlint\n\t\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t\t} else {\n\t\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tt.Run(\"tap a deployment\", func(t *testing.T) {\n\t\t\tevents, err := testutil.Tap(\"deploy/t1\", TestHelper, \"--namespace\", prefixedNs)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"tap failed\", err)\n\t\t\t}\n\n\t\t\terr = testutil.ValidateExpected(events, expectedT1)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"validating tap failed\", err)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"tap a deployment using context namespace\", func(t *testing.T) {\n\t\t\tout, err := TestHelper.Kubectl(\"\", \"config\", \"set-context\", \"--namespace=\"+prefixedNs, \"--current\")\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\",\n\t\t\t\t\t\"unexpected error: %v output:\\n%s\", err, out)\n\t\t\t}\n\n\t\t\tevents, err := testutil.Tap(\"deploy/t1\", TestHelper)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"tap failed using context namespace\", err)\n\t\t\t}\n\n\t\t\terr = testutil.ValidateExpected(events, expectedT1)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"validating tap failed using context namespace\", err)\n\t\t\t}\n\n\t\t\tout, err = TestHelper.Kubectl(\"\", \"config\", \"set-context\", \"--namespace=default\", \"--current\")\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\",\n\t\t\t\t\t\"unexpected error: %v output:\\n%s\", err, out)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"tap a disabled deployment\", func(t *testing.T) {\n\t\t\tout, stderr, err := TestHelper.PipeToLinkerdRun(\"\", \"viz\", \"tap\", \"deploy/t4\", \"--namespace\", prefixedNs)\n\t\t\tif out != \"\" {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected output\",\n\t\t\t\t\t\"unexpected output: %s\", out)\n\t\t\t}\n\t\t\tif err == nil {\n\t\t\t\ttestutil.Fatal(t, \"expected an error, got none\")\n\t\t\t}\n\t\t\tif stderr == \"\" {\n\t\t\t\ttestutil.Fatal(t, \"expected an error, got none\")\n\t\t\t}\n\t\t\texpectedErr := `no pods to tap for type=\"deployment\" name=\"t4\"\n1 pods found with tap disabled via the viz.linkerd.io/disable-tap annotation:`\n\t\t\tsplit := strings.Split(stderr, \"\\n\")\n\t\t\tactualErr := strings.Join(split[:2], \"\\n\")\n\t\t\tif actualErr != expectedErr {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"unexpected error\",\n\t\t\t\t\t\"expected [%s], got: %s\", expectedErr, actualErr)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"tap a service call\", func(t *testing.T) {\n\t\t\tevents, err := testutil.Tap(\"deploy/gateway\", TestHelper, \"--to\", \"svc/t2-svc\", \"--namespace\", prefixedNs)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"failed tapping a service call\", err)\n\t\t\t}\n\n\t\t\terr = testutil.ValidateExpected(events, expectedT2)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"failed validating tapping a service call\", err)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"tap a pod\", func(t *testing.T) {\n\t\t\tdeploy := \"t3\"\n\t\t\tpods, err := TestHelper.GetPodNamesForDeployment(ctx, prefixedNs, deploy)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to get pods for deployment t3\",\n\t\t\t\t\t\"failed to get pods for deployment [%s]\\n%s\", deploy, err)\n\t\t\t}\n\n\t\t\tif len(pods) != 1 {\n\t\t\t\ttestutil.Fatalf(t, \"expected exactly one pod for deployment [%s], got:\\n%v\", deploy, pods)\n\t\t\t}\n\n\t\t\tevents, err := testutil.Tap(\"pod/\"+pods[0], TestHelper, \"--namespace\", prefixedNs)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"error tapping pod\", err)\n\t\t\t}\n\n\t\t\terr = testutil.ValidateExpected(events, expectedT3)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"error validating pod tap\", err)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"filter tap events by method\", func(t *testing.T) {\n\t\t\tevents, err := testutil.Tap(\"deploy/gateway\", TestHelper, \"--namespace\", prefixedNs, \"--method\", \"GET\")\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"error filtering tap events by method\", err)\n\t\t\t}\n\n\t\t\terr = testutil.ValidateExpected(events, expectedGateway)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"error validating filtered tap events by method\", err)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"filter tap events by authority\", func(t *testing.T) {\n\t\t\tevents, err := testutil.Tap(\"deploy/gateway\", TestHelper, \"--namespace\", prefixedNs, \"--authority\", \"t1-svc:9090\")\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"error filtering tap events by authority\", err)\n\t\t\t}\n\n\t\t\terr = testutil.ValidateExpected(events, expectedT1)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, \"error validating filtered tap events by authority\", err)\n\t\t\t}\n\t\t})\n\t})\n\n}\n"
  },
  {
    "path": "test/integration/viz/tap/testdata/tap_application.yaml",
    "content": "# /slow_cooker/slow_cooker --http-> gateway --grpc-> t1\n#                              --grpc-> t2 always-error\n#                              --http-> t3\n#                              --http-> t4 tap-disabled\n#\n\n### t1 terminates gRPC requests\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: t1\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: t1\n  template:\n    metadata:\n      labels:\n        app: t1\n    spec:\n      containers:\n      - name: t1\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--grpc-server-port=9090\"\n        - \"--response-text=t1\"\n        ports:\n        - containerPort: 9090\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: t1-svc\nspec:\n  selector:\n    app: t1\n  ports:\n  - name: grpc\n    port: 9090\n    targetPort: 9090\n\n### t2 terminates gRPC requests and always fails\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: t2\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: t2\n  template:\n    metadata:\n      labels:\n        app: t2\n    spec:\n      containers:\n      - name: t2\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--grpc-server-port=9090\"\n        - \"--response-text=t2\"\n        - \"--percent-failure=100\"\n        ports:\n        - containerPort: 9090\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: t2-svc\nspec:\n  selector:\n    app: t2\n  ports:\n  - name: grpc\n    port: 9090\n    targetPort: 9090\n\n# t3 terminates HTTP/1.1 requests\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: t3\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: t3\n  template:\n    metadata:\n      labels:\n        app: t3\n    spec:\n      containers:\n      - name: t3\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=t3\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: t3-svc\nspec:\n  selector:\n    app: t3\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n\n# t4 terminates HTTP/1.1 requests, but tap is disabled\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: t4\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: t4\n  template:\n    metadata:\n      annotations:\n        viz.linkerd.io/disable-tap: \"true\"\n      labels:\n        app: t4\n    spec:\n      containers:\n      - name: t4\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=t4\"\n        ports:\n        - containerPort: 8080\n\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: t4-svc\nspec:\n  selector:\n    app: t4\n  ports:\n  - name: http\n    port: 8080\n\n### gateway broadcasts requests to t1, t2, t3 and t4\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: gateway\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: gateway\n  template:\n    metadata:\n      labels:\n        app: gateway\n    spec:\n      containers:\n      - name: gateway\n        image: buoyantio/bb:v0.0.6\n        args:\n        - broadcast-channel\n        - \"--h1-server-port=8080\"\n        - \"--grpc-downstream-server=t1-svc:9090\"\n        - \"--grpc-downstream-server=t2-svc:9090\"\n        - \"--h1-downstream-server=http://t3-svc:8080\"\n        - \"--h1-downstream-server=http://t4-svc:8080\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: gateway-svc\nspec:\n  selector:\n    app: gateway\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n\n### slow-cooker sends requests to the gateway\n---\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: slow-cooker\nspec:\n  template:\n    metadata:\n      labels:\n        app: slow-cooker\n    spec:\n      containers:\n      - name: slow-cooker\n        image: buoyantio/slow_cooker:1.3.0\n        command:\n        - \"/bin/sh\"\n        args:\n        - \"-c\"\n        - |\n          sleep 15 # wait for pods to start\n          /slow_cooker/slow_cooker -metric-addr 0.0.0.0:9999 http://gateway-svc:8080\n        ports:\n        - containerPort: 9999\n      restartPolicy: OnFailure\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: slow-cooker\nspec:\n  selector:\n    app: slow-cooker\n  ports:\n  - name: metrics\n    port: 9999\n    targetPort: 9999\n"
  },
  {
    "path": "test/integration/viz/trafficsplit/testdata/application.yaml",
    "content": "# Two backend pods, one always failing\n# and another one returning OK response\n# Slowcooker is used to generate traffic\n# that will be routed via traffic split\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: backend\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: backend\n  template:\n    metadata:\n      labels:\n        app: backend\n    spec:\n      containers:\n      - name: backend\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=backend1\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: backend-svc\nspec:\n  selector:\n    app: backend\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: failing\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: failing\n  template:\n    metadata:\n      labels:\n        app: failing\n    spec:\n      containers:\n      - name: failing\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=failing\"\n        - \"--percent-failure=100\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: failing-svc\nspec:\n  selector:\n    app: failing\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: slow-cooker\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: slow-cooker\n  template:\n    metadata:\n      labels:\n        app: slow-cooker\n    spec:\n      containers:\n      - name: slow-cooker\n        image: buoyantio/slow_cooker:1.3.0\n        command:\n        - \"/bin/sh\"\n        args:\n        - \"-c\"\n        - |\n          sleep 5 # wait for pods to start\n          /slow_cooker/slow_cooker http://backend-svc:8080\n        ports:\n        - containerPort: 9999\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: slow-cooker\nspec:\n  selector:\n    app: slow-cooker\n  ports:\n  - name: metrics\n    port: 9999\n    targetPort: 9999\n"
  },
  {
    "path": "test/integration/viz/trafficsplit/testdata/applications-at-diff-ports.yaml",
    "content": "# Two backend pods, one always failing\n# and another one returning OK response\n# Slowcooker is used to generate traffic\n# that will be routed via traffic split\n\n# Two backends serve at different ports i.e\n# 8080 and 8081.\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: backend\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: backend\n  template:\n    metadata:\n      labels:\n        app: backend\n    spec:\n      containers:\n      - name: backend\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8080\"\n        - \"--response-text=backend1\"\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: backend-svc\nspec:\n  selector:\n    app: backend\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: failing\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: failing\n  template:\n    metadata:\n      labels:\n        app: failing\n    spec:\n      containers:\n      - name: failing\n        image: buoyantio/bb:v0.0.6\n        args:\n        - terminus\n        - \"--h1-server-port=8081\"\n        - \"--response-text=failing\"\n        - \"--percent-failure=100\"\n        ports:\n        - containerPort: 8081\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: failing-svc\nspec:\n  selector:\n    app: failing\n  ports:\n  - name: http\n    port: 8081\n    targetPort: 8081\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: slow-cooker\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: slow-cooker\n  template:\n    metadata:\n      labels:\n        app: slow-cooker\n    spec:\n      containers:\n      - name: slow-cooker\n        image: buoyantio/slow_cooker:1.3.0\n        command:\n        - \"/bin/sh\"\n        args:\n        - \"-c\"\n        - |\n          sleep 5 # wait for pods to start\n          /slow_cooker/slow_cooker http://backend-svc:8080\n        ports:\n        - containerPort: 9999\n"
  },
  {
    "path": "test/integration/viz/trafficsplit/trafficsplit_test.go",
    "content": "package trafficsplit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\nvar TestHelper *testutil.TestHelper\n\nfunc TestMain(m *testing.M) {\n\tTestHelper = testutil.NewTestHelper()\n\t// Block test execution until viz extension is running\n\tTestHelper.WaitUntilDeployReady(testutil.LinkerdVizDeployReplicas)\n\tos.Exit(m.Run())\n}\n\nfunc parseStatRows(out string, expectedRowCount, expectedColumnCount int) ([]*testutil.RowStat, error) {\n\trows, err := testutil.CheckRowCount(out, expectedRowCount)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar statRows []*testutil.RowStat\n\n\tfor _, row := range rows {\n\t\tfields := strings.Fields(row)\n\n\t\tif len(fields) != expectedColumnCount {\n\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\"Expected [%d] columns in stat output, got [%d]; full output:\\n%s\",\n\t\t\t\texpectedColumnCount, len(fields), row)\n\t\t}\n\n\t\trow := &testutil.RowStat{\n\t\t\tName:               fields[0],\n\t\t\tMeshed:             fields[1],\n\t\t\tSuccess:            fields[2],\n\t\t\tRps:                fields[3],\n\t\t\tP50Latency:         fields[4],\n\t\t\tP95Latency:         fields[5],\n\t\t\tP99Latency:         fields[6],\n\t\t\tTCPOpenConnections: fields[7],\n\t\t}\n\n\t\tstatRows = append(statRows, row)\n\n\t}\n\treturn statRows, nil\n}\n\nfunc TestServiceProfileDstOverrides(t *testing.T) {\n\tctx := context.Background()\n\tTestHelper.WithDataPlaneNamespace(ctx, \"trafficsplit-test-sp\", map[string]string{}, t, func(t *testing.T, prefixedNs string) {\n\t\t// First, create a `ServiceProfile`.\n\t\tinitialSP := `---\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: backend-svc.linkerd-trafficsplit-test-sp.svc.cluster.local\nspec:\n  dstOverrides:\n    - authority: backend-svc.linkerd-trafficsplit-test-sp.svc.cluster.local\n      weight: 1\n    - authority: failing-svc.linkerd-trafficsplit-test-sp.svc.cluster.local:8081\n      weight: 0\n...`\n\t\tout, err := TestHelper.KubectlApply(initialSP, prefixedNs)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"Failed to apply ServiceProfile\",\n\t\t\t\t\"Failed to apply ServiceProfile\\n%s\", out)\n\t\t}\n\n\t\t// Deploy an application that will route traffic to the `backend-svc`.\n\t\tout, err = TestHelper.LinkerdRun(\"inject\", \"--manual\",\n\t\t\t\"--proxy-log-level=linkerd=debug,linkerd_service_profiles::client=trace,info\",\n\t\t\t\"testdata/applications-at-diff-ports.yaml\")\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatal(t, \"'linkerd inject' command failed\", err)\n\t\t}\n\t\tout, err = TestHelper.KubectlApply(out, prefixedNs)\n\t\tif err != nil {\n\t\t\ttestutil.AnnotatedFatalf(t, \"'kubectl apply' command failed\",\n\t\t\t\t\"'kubectl apply' command failed\\n%s\", out)\n\t\t}\n\t\t// Wait for all of its pods to be ready.\n\t\tfor _, deploy := range []string{\"backend\", \"failing\", \"slow-cooker\"} {\n\t\t\tif err := TestHelper.CheckPods(ctx, prefixedNs, deploy, 1); err != nil {\n\t\t\t\t//nolint:errorlint\n\t\t\t\tif rce, ok := err.(*testutil.RestartCountError); ok {\n\t\t\t\t\ttestutil.AnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t\t} else {\n\t\t\t\t\ttestutil.AnnotatedError(t, \"CheckPods timed-out\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tt.Run(\"ensure traffic is sent to one backend only\", func(t *testing.T) {\n\t\t\ttimeout := 40 * time.Second\n\t\t\texpectedRows := []*testutil.RowStat{\n\t\t\t\t{\n\t\t\t\t\tName:               \"backend\",\n\t\t\t\t\tMeshed:             \"1/1\",\n\t\t\t\t\tSuccess:            \"100.00%\",\n\t\t\t\t\tTCPOpenConnections: \"1\",\n\t\t\t\t},\n\t\t\t}\n\t\t\terr := testutil.RetryFor(timeout, func() error {\n\t\t\t\tout, err := TestHelper.LinkerdRun(\"viz\", \"stat\", \"deploy\", \"--namespace\", prefixedNs,\n\t\t\t\t\t\"--from\", \"deploy/slow-cooker\", \"-t\", \"30s\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\trows, err := parseStatRows(out, 1, 8)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif err := validateRowStats(expectedRows, rows); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"timed-out ensuring traffic is sent to one backend only (%s)\", timeout), err)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"update traffic split resource with equal weights\", func(t *testing.T) {\n\t\t\tupdatedSP := `---\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: backend-svc.linkerd-trafficsplit-test-sp.svc.cluster.local\nspec:\n  dstOverrides:\n    - authority: backend-svc.linkerd-trafficsplit-test-sp.svc.cluster.local\n      weight: 1\n    - authority: failing-svc.linkerd-trafficsplit-test-sp.svc.cluster.local:8081\n      weight: 1\n...`\n\t\t\tout, err := TestHelper.KubectlApply(updatedSP, prefixedNs)\n\t\t\tif err != nil {\n\t\t\t\ttestutil.AnnotatedFatalf(t, \"failed to update ServiceProfile\",\n\t\t\t\t\t\"failed to update ServiceProfile: %s\\n %s\", err, out)\n\t\t\t}\n\t\t})\n\n\t\tt.Run(\"ensure traffic is sent to both backends\", func(t *testing.T) {\n\t\t\ttimeout := 40 * time.Second\n\t\t\texpectedRows := []*testutil.RowStat{\n\t\t\t\t{\n\t\t\t\t\tName:               \"backend\",\n\t\t\t\t\tMeshed:             \"1/1\",\n\t\t\t\t\tSuccess:            \"100.00%\",\n\t\t\t\t\tTCPOpenConnections: \"1\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:               \"failing\",\n\t\t\t\t\tMeshed:             \"1/1\",\n\t\t\t\t\tSuccess:            \"0.00%\",\n\t\t\t\t\tTCPOpenConnections: \"1\",\n\t\t\t\t},\n\t\t\t}\n\t\t\terr := testutil.RetryFor(timeout, func() error {\n\t\t\t\tout, err := TestHelper.LinkerdRun(\"viz\", \"stat\", \"deploy\", \"-n\", prefixedNs,\n\t\t\t\t\t\"--from\", \"deploy/slow-cooker\", \"-t\", \"30s\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\trows, err := parseStatRows(out, 2, 8)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif err := validateRowStats(expectedRows, rows); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tout, lerr := TestHelper.LinkerdRun(\"viz\", \"stat\", \"deploy\", \"-n\", prefixedNs,\n\t\t\t\t\t\"--from\", \"deploy/slow-cooker\", \"-t\", \"30s\")\n\t\t\t\tif lerr == nil {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"----------- linkerd viz stat\\n\")\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"%s\", out)\n\t\t\t\t}\n\t\t\t\tout, lerr = TestHelper.Kubectl(\"\", \"logs\", \"--tail=1000\", \"-n\", prefixedNs,\n\t\t\t\t\t\"-l\", \"app=slow-cooker\", \"-c\", \"linkerd-proxy\")\n\t\t\t\tif lerr != nil {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"Failed to run kubectl logs: %s\", lerr)\n\t\t\t\t} else {\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"----------- kubectl logs\\n\")\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"%s\", out)\n\t\t\t\t}\n\t\t\t\ttestutil.AnnotatedFatal(t, fmt.Sprintf(\"timed-out ensuring traffic is sent to both backends (%s)\", timeout), err)\n\t\t\t}\n\t\t})\n\t})\n}\n\nfunc validateRowStats(expectedRowStats, actualRowStats []*testutil.RowStat) error {\n\tif len(expectedRowStats) != len(actualRowStats) {\n\t\treturn fmt.Errorf(\"Expected number of rows to be %d, but found %d\", len(expectedRowStats), len(actualRowStats))\n\t}\n\tfor i := 0; i < len(expectedRowStats); i++ {\n\t\terr := compareRowStat(expectedRowStats[i], actualRowStats[i])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc compareRowStat(expectedRow, actualRow *testutil.RowStat) error {\n\tif actualRow.Name != expectedRow.Name {\n\t\treturn fmt.Errorf(\"Expected name to be '%s', got '%s'\",\n\t\t\texpectedRow.Name, actualRow.Name)\n\t}\n\tif actualRow.Meshed != expectedRow.Meshed {\n\t\treturn fmt.Errorf(\"Expected meshed to be '%s', got '%s'\",\n\t\t\texpectedRow.Meshed, actualRow.Meshed)\n\t}\n\tif !strings.HasSuffix(actualRow.Rps, \"rps\") {\n\t\treturn fmt.Errorf(\"Unexpected rps for [%s], got [%s]\",\n\t\t\tactualRow.Name, actualRow.Rps)\n\t}\n\tif !strings.HasSuffix(actualRow.P50Latency, \"ms\") {\n\t\treturn fmt.Errorf(\"Unexpected p50 latency for [%s], got [%s]\",\n\t\t\tactualRow.Name, actualRow.P50Latency)\n\t}\n\tif !strings.HasSuffix(actualRow.P95Latency, \"ms\") {\n\t\treturn fmt.Errorf(\"Unexpected p95 latency for [%s], got [%s]\",\n\t\t\tactualRow.Name, actualRow.P95Latency)\n\t}\n\tif !strings.HasSuffix(actualRow.P99Latency, \"ms\") {\n\t\treturn fmt.Errorf(\"Unexpected p99 latency for [%s], got [%s]\",\n\t\t\tactualRow.Name, actualRow.P99Latency)\n\t}\n\tif actualRow.Success != expectedRow.Success {\n\t\treturn fmt.Errorf(\"Expected success to be '%s', got '%s'\",\n\t\t\texpectedRow.Success, actualRow.Success)\n\t}\n\tif actualRow.TCPOpenConnections != expectedRow.TCPOpenConnections {\n\t\treturn fmt.Errorf(\"Expected tcp to be '%s', got '%s'\",\n\t\t\texpectedRow.TCPOpenConnections, actualRow.TCPOpenConnections)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "testutil/annotations.go",
    "content": "package testutil\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n)\n\nconst (\n\tenvFlag  = \"GH_ANNOTATION\"\n\trootPath = \"/linkerd2/\"\n)\n\ntype level int\n\nconst (\n\terr level = iota\n\twarn\n)\n\nfunc (l level) String() string {\n\tswitch l {\n\tcase err:\n\t\treturn \"error\"\n\tcase warn:\n\t\treturn \"warning\"\n\t}\n\tpanic(fmt.Sprintf(\"invalid level: %d\", l))\n}\n\nfunc echoAnnotation(t *testing.T, l level, args ...interface{}) {\n\tif _, ok := os.LookupEnv(envFlag); ok {\n\t\t_, fileName, fileLine, ok := runtime.Caller(3)\n\t\tif !ok {\n\t\t\tpanic(\"Couldn't recover runtime info\")\n\t\t}\n\t\tfileName = fileName[strings.LastIndex(fileName, rootPath)+len(rootPath):]\n\t\t// In case of coming from `t.Run(testName, ...)`, only take the first part\n\t\t// of the name; the following parts might not be as generic\n\t\tparts := strings.Split(t.Name(), \"/\")\n\t\ttestName := parts[0]\n\t\tfor _, arg := range args {\n\t\t\tmsg := fmt.Sprintf(\"%s - %s\", testName, arg)\n\t\t\tfmt.Printf(\"::%s file=%s,line=%d::%s\\n\", l, fileName, fileLine, msg)\n\t\t}\n\t}\n}\n\nfunc echoAnnotationErr(t *testing.T, args ...interface{}) {\n\techoAnnotation(t, err, args...)\n}\n\nfunc echoAnnotationWarn(t *testing.T, args ...interface{}) {\n\techoAnnotation(t, warn, args...)\n}\n\n// Error is a wrapper around t.Error()\n// args are passed to t.Error(args) and each arg will be sent to stdout formatted\n// as a GitHub annotation when the envFlag environment variable is set\nfunc Error(t *testing.T, args ...interface{}) {\n\tt.Helper()\n\techoAnnotationErr(t, args...)\n\tt.Error(args...)\n}\n\n// AnnotatedError is similar to Error() but it also admits a msg string that\n// will be used as the GitHub annotation\nfunc AnnotatedError(t *testing.T, msg string, args ...interface{}) {\n\tt.Helper()\n\techoAnnotationErr(t, msg)\n\tt.Error(args...)\n}\n\n// Errorf is a wrapper around t.Errorf()\n// format and args are passed to t.Errorf(format, args) and the formatted\n// message will be sent to stdout as a GitHub annotation when the envFlag\n// environment variable is set\nfunc Errorf(t *testing.T, format string, args ...interface{}) {\n\tt.Helper()\n\techoAnnotationErr(t, fmt.Sprintf(format, args...))\n\tt.Errorf(format, args...)\n}\n\n// AnnotatedErrorf is similar to Errorf() but it also admits a msg string that\n// will be used as the GitHub annotation\nfunc AnnotatedErrorf(t *testing.T, msg, format string, args ...interface{}) {\n\tt.Helper()\n\techoAnnotationErr(t, msg)\n\tt.Errorf(format, args...)\n}\n\n// Fatal is a wrapper around t.Fatal()\n// args are passed to t.Fatal(args) and each arg will be sent to stdout formatted\n// as a GitHub annotation when the envFlag environment variable is set\nfunc Fatal(t *testing.T, args ...interface{}) {\n\tt.Helper()\n\techoAnnotationErr(t, args)\n\tt.Fatal(args...)\n}\n\n// AnnotatedFatal is similar to Fatal() but it also admits a msg string that\n// will be used as the GitHub annotation\nfunc AnnotatedFatal(t *testing.T, msg string, args ...interface{}) {\n\tt.Helper()\n\techoAnnotationErr(t, msg)\n\tt.Fatal(args...)\n}\n\n// Fatalf is a wrapper around t.Errorf()\n// format and args are passed to t.Fatalf(format, args) and the formatted\n// message will be sent to stdout as a GitHub annotation when the envFlag\n// environment variable is set\nfunc Fatalf(t *testing.T, format string, args ...interface{}) {\n\tt.Helper()\n\techoAnnotationErr(t, fmt.Sprintf(format, args...))\n\tt.Fatalf(format, args...)\n}\n\n// AnnotatedFatalf is similar to Fatalf() but it also admits a msg string that\n// will be used as the GitHub annotation\nfunc AnnotatedFatalf(t *testing.T, msg, format string, args ...interface{}) {\n\tt.Helper()\n\techoAnnotationErr(t, msg)\n\tt.Fatalf(format, args...)\n}\n\n// AnnotatedWarn is a wrapper around t.Log() but it also admits a msg string that\n// will be used as the GitHub warning annotation\nfunc AnnotatedWarn(t *testing.T, msg string, args ...interface{}) {\n\tt.Helper()\n\techoAnnotationWarn(t, msg)\n\tt.Log(args...)\n}\n"
  },
  {
    "path": "testutil/annotations_test.go",
    "content": "package testutil\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestMain(m *testing.M) {\n\tos.Setenv(envFlag, \"true\")\n\tos.Exit(m.Run())\n}\n\nfunc redirectStdout(t *testing.T) (*os.File, chan string) {\n\torigStdout := os.Stdout\n\tnewStdout, w, pipeErr := os.Pipe()\n\tif pipeErr != nil {\n\t\tt.Fatalf(\"error creating os.Pipe(): %s\", pipeErr)\n\t}\n\tos.Stdout = w\n\n\t// retrieve the payload sent to newStdout in a separate goroutine\n\t// to avoid blocking\n\toutC := make(chan string)\n\tgo func() {\n\t\tvar buf bytes.Buffer\n\t\tio.Copy(&buf, newStdout)\n\t\toutC <- buf.String()\n\t}()\n\n\treturn origStdout, outC\n}\n\nfunc restoreStdout(outC chan string, origStdout *os.File) string {\n\tos.Stdout.Close()\n\tout := <-outC\n\tos.Stdout = origStdout\n\treturn out\n}\n\nfunc TestError(t *testing.T) {\n\tmsg := \"This is an error\"\n\n\t// redirect stdout temporarily to catch the GitHub annotation output\n\torigStdout, outC := redirectStdout(t)\n\tError(&testing.T{}, msg)\n\tout := restoreStdout(outC, origStdout)\n\n\tif !strings.HasSuffix(strings.TrimSpace(out), \"testutil/annotations_test.go,line=48:: - This is an error\") {\n\t\tt.Fatalf(\"unexpected stdout content: %s\", out)\n\t}\n}\n\nfunc TestAnnotatedErrorf(t *testing.T) {\n\tmsgFormat := \"This is a detailed error: %s\"\n\tstr := \"foobar\"\n\tmsgDesc := \"This is a generic error\"\n\n\t// redirect stdout temporarily to catch the GitHub annotation output\n\torigStdout, outC := redirectStdout(t)\n\tAnnotatedErrorf(&testing.T{}, msgDesc, msgFormat, str)\n\tout := restoreStdout(outC, origStdout)\n\n\tif !strings.HasSuffix(strings.TrimSpace(out), \"testutil/annotations_test.go,line=63:: - This is a generic error\") {\n\t\tt.Fatalf(\"unexpected stdout content: %s\", out)\n\t}\n}\n"
  },
  {
    "path": "testutil/doc.go",
    "content": "/*\nPackage testutil provides helpers for running the linkerd integration tests.\n\nAll helpers are defined as functions on the TestHelper struct, which you should\ninstantiate once per test, using the NewTestHelper function. Since that function\nalso parses command line flags, it should be called as part of your test's\nTestMain function. For example:\n\n\tpackage mytest\n\n\timport (\n\t\t\"os\"\n\t\t\"testing\"\n\n\t\t\"github.com/linkerd/linkerd2/testutil\"\n\t)\n\n\tvar TestHelper *util.TestHelper\n\n\tfunc TestMain(m *testing.M) {\n\t  TestHelper = util.NewTestHelper()\n\t  os.Exit(m.Run())\n\t}\n\n\tfunc TestMyTest(t *testing.T) {\n\t\t// add test code here\n\t}\n\nCalling NewTestHelper adds the following command line flags:\n\n\t-linkerd string\n\t\tpath to the linkerd binary to test\n\t-linkerd-namespace string\n\t\tthe namespace where linkerd is installed (default \"linkerd\")\n\t-k8s-context string\n\t\tthe kubernetes context associated with the test cluster (default \"\")\n\t-integration-tests\n\t\tmust be provided to run the integration tests\n\nNote that the -integration-tests flag must be set when running tests, so that\nthe tests aren't inadvertently executed when unit tests for the project are run.\n\nTestHelper embeds KubernetesHelper, so all functions defined on KubernetesHelper\nare also available to instances of TestHelper. See the individual function\ndefinitions for details on how to use each helper in tests.\n*/\npackage testutil\n"
  },
  {
    "path": "testutil/inject.go",
    "content": "package testutil\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\tjsonpatch \"github.com/evanphx/json-patch\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nfunc applyPatch(in string, patchJSON []byte) (string, error) {\n\tpatch, err := jsonpatch.DecodePatch(patchJSON)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tjson, err := yaml.YAMLToJSON([]byte(in))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tpatched, err := patch.Apply(json)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(patched), nil\n}\n\n// PatchDeploy patches a manifest by applying annotations\nfunc PatchDeploy(in string, name string, annotations map[string]string) (string, error) {\n\tops := []string{\n\t\tfmt.Sprintf(`{\"op\": \"replace\", \"path\": \"/metadata/name\", \"value\": \"%s\"}`, name),\n\t\tfmt.Sprintf(`{\"op\": \"replace\", \"path\": \"/spec/selector/matchLabels/app\", \"value\": \"%s\"}`, name),\n\t\tfmt.Sprintf(`{\"op\": \"replace\", \"path\": \"/spec/template/metadata/labels/app\", \"value\": \"%s\"}`, name),\n\t}\n\n\tif len(annotations) > 0 {\n\t\tops = append(ops, `{\"op\": \"add\", \"path\": \"/spec/template/metadata/annotations\", \"value\": {}}`)\n\t\tfor k, v := range annotations {\n\t\t\tops = append(ops,\n\t\t\t\tfmt.Sprintf(`{\"op\": \"add\", \"path\": \"/spec/template/metadata/annotations/%s\", \"value\": \"%s\"}`, strings.ReplaceAll(k, \"/\", \"~1\"), v),\n\t\t\t)\n\t\t}\n\t}\n\n\tpatchJSON := []byte(fmt.Sprintf(\"[%s]\", strings.Join(ops, \",\")))\n\n\treturn applyPatch(in, patchJSON)\n}\n\n// GetProxyContainer get the proxy containers\nfunc GetProxyContainer(spec v1.PodSpec) *v1.Container {\n\tfor _, c := range append(spec.InitContainers, spec.Containers...) {\n\t\tcontainer := c\n\t\tif container.Name == k8s.ProxyContainerName {\n\t\t\treturn &container\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "testutil/inject_validator.go",
    "content": "package testutil\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n)\n\nconst enabled = \"true\"\n\n// InjectValidator is used as a helper to generate\n// correct injector flags and annotations and verify\n// injected pods\ntype InjectValidator struct {\n\tNoInitContainer         bool\n\tNativeSidecar           bool\n\tAutoInject              bool\n\tAdminPort               int\n\tControlPort             int\n\tEnableDebug             bool\n\tEnableExternalProfiles  bool\n\tImagePullPolicy         string\n\tInboundPort             int\n\tOutboundPort            int\n\tCPULimit                string\n\tEphemeralStorageLimit   string\n\tCPURequest              string\n\tMemoryLimit             string\n\tMemoryRequest           string\n\tEphemeralStorageRequest string\n\tImage                   string\n\tLogLevel                string\n\tLogFormat               string\n\tUID                     int\n\tGID                     int\n\tVersion                 string\n\tRequireIdentityOnPorts  string\n\tSkipOutboundPorts       string\n\tOpaquePorts             string\n\tSkipInboundPorts        string\n\tOutboundConnectTimeout  string\n\tInboundConnectTimeout   string\n\tWaitBeforeExitSeconds   int\n\tSkipSubnets             string\n\tShutdownGracePeriod     string\n}\n\nfunc (iv *InjectValidator) getContainer(pod *v1.PodSpec, name string, isInit bool) *v1.Container {\n\tcontainers := pod.Containers\n\tif isInit {\n\t\tcontainers = pod.InitContainers\n\t}\n\tfor _, container := range containers {\n\t\tif container.Name == name {\n\t\t\treturn &container\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (iv *InjectValidator) getPodContainer(pod *v1.PodSpec) *v1.Container {\n\tvar containers []v1.Container\n\tif iv.NativeSidecar {\n\t\tcontainers = pod.InitContainers\n\t} else {\n\t\tcontainers = pod.Containers\n\t}\n\tfor _, container := range containers {\n\t\tif container.Name == k8s.ProxyContainerName {\n\t\t\treturn &container\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (iv *InjectValidator) validateEnvVar(container *v1.Container, envName, expectedValue string) error {\n\tfor _, env := range container.Env {\n\t\tif env.Name == envName {\n\t\t\tif env.Value == expectedValue {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"env: %s, expected: %s, actual %s\", envName, expectedValue, env.Value)\n\t\t}\n\n\t}\n\treturn fmt.Errorf(\"cannot find env: %s\", envName)\n}\n\nfunc (iv *InjectValidator) validatePort(container *v1.Container, portName string, expectedValue int) error {\n\tfor _, port := range container.Ports {\n\t\tif port.Name == portName {\n\t\t\tif port.ContainerPort == int32(expectedValue) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"port: %s, expected: %d, actual %d\", portName, expectedValue, port.ContainerPort)\n\t\t}\n\n\t}\n\treturn fmt.Errorf(\"cannot find port: %s\", portName)\n}\n\nfunc (iv *InjectValidator) validateDebugContainer(pod *v1.PodSpec) error {\n\tif iv.EnableDebug {\n\t\tproxyContainer := iv.getContainer(pod, k8s.DebugContainerName, false)\n\t\tif proxyContainer == nil {\n\t\t\treturn fmt.Errorf(\"container %s missing\", k8s.DebugContainerName)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (iv *InjectValidator) validateProxyContainer(pod *v1.PodSpec) error {\n\tproxyContainer := iv.getPodContainer(pod)\n\tif proxyContainer == nil {\n\t\treturn fmt.Errorf(\"proxy container %s missing\", k8s.ProxyContainerName)\n\t}\n\n\tif iv.AdminPort != 0 {\n\t\tif err := iv.validateEnvVar(proxyContainer, \"LINKERD2_PROXY_ADMIN_LISTEN_ADDR\", fmt.Sprintf(\"0.0.0.0:%d\", iv.AdminPort)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif proxyContainer.LivenessProbe.HTTPGet.Port.IntVal != int32(iv.AdminPort) {\n\t\t\treturn fmt.Errorf(\"livenessProbe: expected: %d, actual %d\", iv.AdminPort, proxyContainer.LivenessProbe.HTTPGet.Port.IntVal)\n\t\t}\n\t\tif proxyContainer.ReadinessProbe.HTTPGet.Port.IntVal != int32(iv.AdminPort) {\n\t\t\treturn fmt.Errorf(\"readinessProbe: expected: %d, actual %d\", iv.AdminPort, proxyContainer.LivenessProbe.HTTPGet.Port.IntVal)\n\t\t}\n\n\t\tif err := iv.validatePort(proxyContainer, k8s.ProxyAdminPortName, iv.AdminPort); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.ControlPort != 0 {\n\t\tif err := iv.validateEnvVar(proxyContainer, \"LINKERD2_PROXY_CONTROL_LISTEN_ADDR\", fmt.Sprintf(\"0.0.0.0:%d\", iv.ControlPort)); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.EnableExternalProfiles {\n\t\tif err := iv.validateEnvVar(proxyContainer, \"LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES\", \".\"); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.ImagePullPolicy != \"\" {\n\t\tif string(proxyContainer.ImagePullPolicy) != iv.ImagePullPolicy {\n\t\t\treturn fmt.Errorf(\"pullPolicy: expected: %s, actual %s\", iv.ImagePullPolicy, string(proxyContainer.ImagePullPolicy))\n\t\t}\n\t}\n\n\tif iv.InboundPort != 0 {\n\t\tif err := iv.validateEnvVar(proxyContainer, \"LINKERD2_PROXY_INBOUND_LISTEN_ADDR\", fmt.Sprintf(\"0.0.0.0:%d\", iv.InboundPort)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif proxyContainer.LivenessProbe.HTTPGet.Port.IntVal != int32(iv.AdminPort) {\n\t\t\treturn fmt.Errorf(\"livenessProbe: expected: %d, actual %d\", iv.AdminPort, proxyContainer.LivenessProbe.HTTPGet.Port.IntVal)\n\t\t}\n\t\tif proxyContainer.ReadinessProbe.HTTPGet.Port.IntVal != int32(iv.AdminPort) {\n\t\t\treturn fmt.Errorf(\"readinessProbe: expected: %d, actual %d\", iv.AdminPort, proxyContainer.LivenessProbe.HTTPGet.Port.IntVal)\n\t\t}\n\t\tif err := iv.validatePort(proxyContainer, k8s.ProxyPortName, iv.InboundPort); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.OutboundPort != 0 {\n\t\tif err := iv.validateEnvVar(\n\t\t\tproxyContainer,\n\t\t\t\"LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS\",\n\t\t\tfmt.Sprintf(\"127.0.0.1:%d\", iv.OutboundPort),\n\t\t); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.CPULimit != \"\" {\n\t\tlimit := resource.MustParse(iv.CPULimit)\n\t\tif proxyContainer.Resources.Limits.Cpu() != nil {\n\t\t\tif !proxyContainer.Resources.Limits.Cpu().Equal(limit) {\n\t\t\t\treturn fmt.Errorf(\"CpuLimit: expected %v, actual %v\", &limit, proxyContainer.Resources.Limits.Cpu())\n\t\t\t}\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"CpuLimit: expected %v, but none\", &limit)\n\t\t}\n\n\t}\n\n\tif iv.CPURequest != \"\" {\n\t\trequest := resource.MustParse(iv.CPURequest)\n\t\tif proxyContainer.Resources.Requests.Cpu() != nil {\n\t\t\tif !proxyContainer.Resources.Requests.Cpu().Equal(request) {\n\t\t\t\treturn fmt.Errorf(\"CpuRequest: expected %v, actual %v\", &request, proxyContainer.Resources.Requests.Cpu())\n\t\t\t}\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"CpuRequest: expected %v, but none\", &request)\n\t\t}\n\t}\n\n\tif iv.MemoryLimit != \"\" {\n\t\tlimit := resource.MustParse(iv.MemoryLimit)\n\t\tif proxyContainer.Resources.Limits.Memory() != nil {\n\t\t\tif !proxyContainer.Resources.Limits.Memory().Equal(limit) {\n\t\t\t\treturn fmt.Errorf(\"MemLimit: expected %v, actual %v\", &limit, proxyContainer.Resources.Limits.Memory())\n\t\t\t}\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"MemLimit: expected %v, but none\", &limit)\n\t\t}\n\t}\n\n\tif iv.MemoryRequest != \"\" {\n\t\trequest := resource.MustParse(iv.MemoryRequest)\n\t\tif proxyContainer.Resources.Requests.Memory() != nil {\n\t\t\tif !proxyContainer.Resources.Requests.Memory().Equal(request) {\n\t\t\t\treturn fmt.Errorf(\"MemRequest: expected %v, actual %v\", &request, proxyContainer.Resources.Requests.Memory())\n\t\t\t}\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"MemRequest: expected %v, but none\", &request)\n\t\t}\n\t}\n\n\tif iv.EphemeralStorageLimit != \"\" {\n\t\tlimit := resource.MustParse(iv.EphemeralStorageLimit)\n\t\tif proxyContainer.Resources.Limits.StorageEphemeral() != nil {\n\t\t\tif !proxyContainer.Resources.Limits.StorageEphemeral().Equal(limit) {\n\t\t\t\treturn fmt.Errorf(\"EphemeralStorageLimit: expected %v, actual %v\", &limit, proxyContainer.Resources.Limits.StorageEphemeral())\n\t\t\t}\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"EphemeralStorageLimit: expected %v, but none\", &limit)\n\t\t}\n\t}\n\n\tif iv.EphemeralStorageRequest != \"\" {\n\t\trequest := resource.MustParse(iv.EphemeralStorageRequest)\n\t\tif proxyContainer.Resources.Requests.StorageEphemeral() != nil {\n\t\t\tif !proxyContainer.Resources.Requests.StorageEphemeral().Equal(request) {\n\t\t\t\treturn fmt.Errorf(\"EphemeralStorageRequest: expected %v, actual %v\", &request, proxyContainer.Resources.Requests.StorageEphemeral())\n\t\t\t}\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"EphemeralStorageRequest: expected %v, but none\", &request)\n\t\t}\n\t}\n\n\tif iv.Image != \"\" || iv.Version != \"\" {\n\t\timage := strings.Split(proxyContainer.Image, \":\")\n\n\t\tif len(image) != 2 {\n\t\t\treturn fmt.Errorf(\"invalid proxy container image string: %s\", proxyContainer.Image)\n\t\t}\n\n\t\tif iv.Image != \"\" {\n\t\t\tif image[0] != iv.Image {\n\t\t\t\treturn fmt.Errorf(\"proxyImage: expected %s, actual %s\", iv.Image, image[0])\n\t\t\t}\n\t\t}\n\n\t\tif iv.Version != \"\" {\n\t\t\tif image[1] != iv.Version {\n\t\t\t\treturn fmt.Errorf(\"proxyImageVersion: expected %s, actual %s\", iv.Version, image[1])\n\t\t\t}\n\t\t}\n\t}\n\n\tif iv.LogLevel != \"\" {\n\t\texpectedLogLevel := fmt.Sprintf(\"%s,[{headers}]=off,[{request}]=off\", iv.LogLevel)\n\t\tif err := iv.validateEnvVar(proxyContainer, \"LINKERD2_PROXY_LOG\", expectedLogLevel); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.LogFormat != \"\" {\n\t\tif err := iv.validateEnvVar(proxyContainer, \"LINKERD2_PROXY_LOG_FORMAT\", iv.LogFormat); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.UID != 0 {\n\t\tif proxyContainer.SecurityContext.RunAsUser == nil {\n\t\t\treturn fmt.Errorf(\"no RunAsUser specified\")\n\t\t}\n\t\tif *proxyContainer.SecurityContext.RunAsUser != int64(iv.UID) {\n\t\t\treturn fmt.Errorf(\"runAsUser: expected %d, actual %d\", iv.UID, *proxyContainer.SecurityContext.RunAsUser)\n\t\t}\n\t}\n\n\tif iv.GID != 0 {\n\t\tif proxyContainer.SecurityContext.RunAsGroup == nil {\n\t\t\treturn fmt.Errorf(\"no RunAsGroup specified\")\n\t\t}\n\t\tif *proxyContainer.SecurityContext.RunAsGroup != int64(iv.GID) {\n\t\t\treturn fmt.Errorf(\"runAsGroup: expected %d, actual %d\", iv.GID, *proxyContainer.SecurityContext.RunAsGroup)\n\t\t}\n\t}\n\n\tif iv.RequireIdentityOnPorts != \"\" {\n\t\tif err := iv.validateEnvVar(proxyContainer, \"LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_IDENTITY\", iv.RequireIdentityOnPorts); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.OpaquePorts != \"\" {\n\t\tif err := iv.validateEnvVar(proxyContainer, \"LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION\", iv.OpaquePorts); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.OutboundConnectTimeout != \"\" {\n\t\tif err := iv.validateEnvVar(proxyContainer, \"LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT\", iv.OutboundConnectTimeout); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.OutboundConnectTimeout != \"\" {\n\t\tif err := iv.validateEnvVar(proxyContainer, \"LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT\", iv.InboundConnectTimeout); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.WaitBeforeExitSeconds != 0 {\n\t\texpectedCmd := fmt.Sprintf(\"/bin/sleep,%d\", iv.WaitBeforeExitSeconds)\n\t\tactual := strings.Join(proxyContainer.Lifecycle.PreStop.Exec.Command, \",\")\n\t\tif expectedCmd != strings.Join(proxyContainer.Lifecycle.PreStop.Exec.Command, \",\") {\n\t\t\treturn fmt.Errorf(\"preStopHook: expected %s, actual %s\", expectedCmd, actual)\n\t\t}\n\t}\n\n\tif iv.ShutdownGracePeriod != \"\" {\n\t\tif err := iv.validateEnvVar(proxyContainer, \"LINKERD2_PROXY_SHUTDOWN_GRACE_PERIOD\", iv.ShutdownGracePeriod); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (iv *InjectValidator) validateInitContainer(pod *v1.PodSpec) error {\n\tif iv.NoInitContainer {\n\t\treturn nil\n\t}\n\tinitContainer := iv.getContainer(pod, k8s.InitContainerName, true)\n\tif initContainer == nil {\n\t\treturn fmt.Errorf(\"container %s missing\", k8s.InitContainerName)\n\t}\n\n\tif iv.InboundPort != 0 {\n\t\tif err := iv.validateArg(initContainer, \"--incoming-proxy-port\", strconv.Itoa(iv.InboundPort)); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.OutboundPort != 0 {\n\t\tif err := iv.validateArg(initContainer, \"--proxy-uid\", strconv.Itoa(iv.UID)); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.UID != 0 {\n\t\tif err := iv.validateArg(initContainer, \"--outgoing-proxy-port\", strconv.Itoa(iv.OutboundPort)); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.SkipInboundPorts != \"\" {\n\t\texpectedPorts := fmt.Sprintf(\"%d,%d,%s\", iv.ControlPort, iv.AdminPort, iv.SkipInboundPorts)\n\t\tif err := iv.validateArg(initContainer, \"--inbound-ports-to-ignore\", expectedPorts); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.SkipOutboundPorts != \"\" {\n\t\tif err := iv.validateArg(initContainer, \"--outbound-ports-to-ignore\", iv.SkipOutboundPorts); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif iv.SkipSubnets != \"\" {\n\t\tif err := iv.validateArg(initContainer, \"--skip-subnets\", iv.SkipSubnets); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (iv *InjectValidator) validateArg(container *v1.Container, argName, expectedValue string) error {\n\tfor i, arg := range container.Args {\n\t\tif arg == argName {\n\t\t\tif len(container.Args) < i+2 {\n\t\t\t\treturn fmt.Errorf(\"No value for arg %s\", argName)\n\t\t\t}\n\t\t\tif container.Args[i+1] != expectedValue {\n\t\t\t\treturn fmt.Errorf(\"container arg %s expected: %s, actual %s\", argName, expectedValue, container.Args[i+1])\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn fmt.Errorf(\"Could not find arg: %s\", argName)\n\n}\n\n// ValidatePod validates that the pod had been configured\n// according by the injector correctly\nfunc (iv *InjectValidator) ValidatePod(pod *v1.PodSpec) error {\n\n\tif err := iv.validateProxyContainer(pod); err != nil {\n\t\treturn err\n\t}\n\n\tif err := iv.validateInitContainer(pod); err != nil {\n\t\treturn err\n\t}\n\n\tif err := iv.validateDebugContainer(pod); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// GetFlagsAndAnnotations retrieves the injector config flags and annotations\n// based on the options provided\nfunc (iv *InjectValidator) GetFlagsAndAnnotations() ([]string, map[string]string) {\n\tannotations := make(map[string]string)\n\tvar flags []string\n\n\t// native sidecar annotation and flag are set explicitly to avoid relying on defaults that might change\n\tif iv.NativeSidecar {\n\t\tannotations[k8s.ProxyEnableNativeSidecarAnnotationBeta] = enabled\n\t\tflags = append(flags, \"--native-sidecar\")\n\t} else {\n\t\tannotations[k8s.ProxyEnableNativeSidecarAnnotationBeta] = \"false\"\n\t\tflags = append(flags, \"--native-sidecar=false\")\n\t}\n\n\tif iv.AutoInject {\n\t\tannotations[k8s.ProxyInjectAnnotation] = k8s.ProxyInjectEnabled\n\t}\n\n\tif iv.AdminPort != 0 {\n\t\tannotations[k8s.ProxyAdminPortAnnotation] = strconv.Itoa(iv.AdminPort)\n\t\tflags = append(flags, fmt.Sprintf(\"--admin-port=%s\", strconv.Itoa(iv.AdminPort)))\n\t}\n\n\tif iv.ControlPort != 0 {\n\t\tannotations[k8s.ProxyControlPortAnnotation] = strconv.Itoa(iv.ControlPort)\n\t\tflags = append(flags, fmt.Sprintf(\"--control-port=%s\", strconv.Itoa(iv.ControlPort)))\n\t}\n\n\tif iv.EnableDebug {\n\t\tannotations[k8s.ProxyEnableDebugAnnotation] = enabled\n\t\tflags = append(flags, \"--enable-debug-sidecar\")\n\t}\n\n\tif iv.EnableExternalProfiles {\n\t\tannotations[k8s.ProxyEnableExternalProfilesAnnotation] = enabled\n\t\tflags = append(flags, \"--enable-external-profiles\")\n\t}\n\n\tif iv.ImagePullPolicy != \"\" {\n\t\tannotations[k8s.ProxyImagePullPolicyAnnotation] = iv.ImagePullPolicy\n\t\tflags = append(flags, fmt.Sprintf(\"--image-pull-policy=%s\", iv.ImagePullPolicy))\n\t}\n\n\tif iv.InboundPort != 0 {\n\t\tannotations[k8s.ProxyInboundPortAnnotation] = strconv.Itoa(iv.InboundPort)\n\t\tflags = append(flags, fmt.Sprintf(\"--inbound-port=%s\", strconv.Itoa(iv.InboundPort)))\n\t}\n\n\tif iv.OutboundPort != 0 {\n\t\tannotations[k8s.ProxyOutboundPortAnnotation] = strconv.Itoa(iv.OutboundPort)\n\t\tflags = append(flags, fmt.Sprintf(\"--outbound-port=%s\", strconv.Itoa(iv.OutboundPort)))\n\t}\n\n\tif iv.CPULimit != \"\" {\n\t\tannotations[k8s.ProxyCPULimitAnnotation] = iv.CPULimit\n\t\tflags = append(flags, fmt.Sprintf(\"--proxy-cpu-limit=%s\", iv.CPULimit))\n\t}\n\n\tif iv.CPURequest != \"\" {\n\t\tannotations[k8s.ProxyCPURequestAnnotation] = iv.CPURequest\n\t\tflags = append(flags, fmt.Sprintf(\"--proxy-cpu-request=%s\", iv.CPURequest))\n\t}\n\n\tif iv.MemoryLimit != \"\" {\n\t\tannotations[k8s.ProxyMemoryLimitAnnotation] = iv.MemoryLimit\n\t\tflags = append(flags, fmt.Sprintf(\"--proxy-memory-limit=%s\", iv.MemoryLimit))\n\t}\n\n\tif iv.EphemeralStorageLimit != \"\" {\n\t\tannotations[k8s.ProxyEphemeralStorageLimitAnnotation] = iv.EphemeralStorageLimit\n\t\t// TODO - `inject` does not support the `--set` flag, so we can't add this\n\t\t// flags = append(flags, \"--set\", fmt.Sprintf(\"proxy.resources.ephemeral-storage.limit=%s\", iv.EphemeralStorageLimit))\n\t}\n\n\tif iv.MemoryRequest != \"\" {\n\t\tannotations[k8s.ProxyMemoryRequestAnnotation] = iv.MemoryRequest\n\t\tflags = append(flags, fmt.Sprintf(\"--proxy-memory-request=%s\", iv.MemoryRequest))\n\t}\n\n\tif iv.EphemeralStorageRequest != \"\" {\n\t\tannotations[k8s.ProxyEphemeralStorageRequestAnnotation] = iv.EphemeralStorageRequest\n\t\t// TODO - `inject` does not support the `--set` flag, so we can't add this\n\t\t// flags = append(flags, \"--set\", fmt.Sprintf(\"proxy.resources.ephemeral-storage.request=%s\", iv.EphemeralStorageRequest))\n\t}\n\n\tif iv.Image != \"\" {\n\t\tannotations[k8s.ProxyImageAnnotation] = iv.Image\n\t\tflags = append(flags, fmt.Sprintf(\"--proxy-image=%s\", iv.Image))\n\t}\n\n\tif iv.LogLevel != \"\" {\n\t\tannotations[k8s.ProxyLogLevelAnnotation] = iv.LogLevel\n\t\tflags = append(flags, fmt.Sprintf(\"--proxy-log-level=%s\", iv.LogLevel))\n\t}\n\n\tif iv.LogFormat != \"\" {\n\t\tannotations[k8s.ProxyLogFormatAnnotation] = iv.LogFormat\n\t}\n\n\tif iv.UID != 0 {\n\t\tannotations[k8s.ProxyUIDAnnotation] = strconv.Itoa(iv.UID)\n\t\tflags = append(flags, fmt.Sprintf(\"--proxy-uid=%s\", strconv.Itoa(iv.UID)))\n\t}\n\n\tif iv.GID != 0 {\n\t\tannotations[k8s.ProxyGIDAnnotation] = strconv.Itoa(iv.GID)\n\t\tflags = append(flags, fmt.Sprintf(\"--proxy-gid=%s\", strconv.Itoa(iv.GID)))\n\t}\n\n\tif iv.Version != \"\" {\n\t\tannotations[k8s.ProxyVersionOverrideAnnotation] = iv.Version\n\t\tflags = append(flags, fmt.Sprintf(\"--proxy-version=%s\", iv.Version))\n\t}\n\n\tif iv.RequireIdentityOnPorts != \"\" {\n\t\tannotations[k8s.ProxyRequireIdentityOnInboundPortsAnnotation] = iv.RequireIdentityOnPorts\n\t\tflags = append(flags, fmt.Sprintf(\"--require-identity-on-inbound-ports =%s\", iv.RequireIdentityOnPorts))\n\t}\n\n\tif iv.SkipInboundPorts != \"\" {\n\t\tannotations[k8s.ProxyIgnoreInboundPortsAnnotation] = iv.SkipInboundPorts\n\t\tflags = append(flags, fmt.Sprintf(\"--skip-inbound-ports=%s\", iv.SkipInboundPorts))\n\t}\n\n\tif iv.OpaquePorts != \"\" {\n\t\tannotations[k8s.ProxyOpaquePortsAnnotation] = iv.OpaquePorts\n\t}\n\n\tif iv.SkipOutboundPorts != \"\" {\n\t\tannotations[k8s.ProxyIgnoreOutboundPortsAnnotation] = iv.SkipOutboundPorts\n\t\tflags = append(flags, fmt.Sprintf(\"--skip-outbound-ports=%s\", iv.SkipOutboundPorts))\n\t}\n\n\tif iv.OutboundConnectTimeout != \"\" {\n\t\tannotations[k8s.ProxyOutboundConnectTimeout] = iv.OutboundConnectTimeout\n\t}\n\n\tif iv.InboundConnectTimeout != \"\" {\n\t\tannotations[k8s.ProxyInboundConnectTimeout] = iv.InboundConnectTimeout\n\t}\n\n\tif iv.WaitBeforeExitSeconds != 0 {\n\t\tannotations[k8s.ProxyWaitBeforeExitSecondsAnnotation] = strconv.Itoa(iv.WaitBeforeExitSeconds)\n\t\tflags = append(flags, fmt.Sprintf(\"--wait-before-exit-secondst=%s\", strconv.Itoa(iv.WaitBeforeExitSeconds)))\n\n\t}\n\n\tif iv.SkipSubnets != \"\" {\n\t\tannotations[k8s.ProxySkipSubnetsAnnotation] = iv.SkipSubnets\n\t\tflags = append(flags, fmt.Sprintf(\"--skip-subnets=%s\", iv.SkipSubnets))\n\t}\n\n\tif iv.ShutdownGracePeriod != \"\" {\n\t\tannotations[k8s.ProxyShutdownGracePeriodAnnotation] = iv.ShutdownGracePeriod\n\t\tflags = append(flags, fmt.Sprintf(\"--shutdown-grace-period=%s\", iv.ShutdownGracePeriod))\n\t}\n\n\treturn flags, annotations\n}\n"
  },
  {
    "path": "testutil/install.go",
    "content": "package testutil\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n)\n\n// TestResourcesPostInstall tests resources post control plane installation\nfunc TestResourcesPostInstall(namespace string, services []Service, deploys map[string]DeploySpec, h *TestHelper, t *testing.T) {\n\tt.Helper()\n\tctx := context.Background()\n\t// Tests Namespace\n\terr := h.CheckIfNamespaceExists(ctx, namespace)\n\tif err != nil {\n\t\tAnnotatedFatalf(t, \"received unexpected output\",\n\t\t\t\"received unexpected output\\n%s\", err)\n\t}\n\n\t// Tests Services\n\tfor _, svc := range services {\n\t\tif err := h.CheckService(ctx, svc.Namespace, svc.Name); err != nil {\n\t\t\tAnnotatedErrorf(t, fmt.Sprintf(\"error validating service [%s/%s]\", svc.Namespace, svc.Name),\n\t\t\t\t\"error validating service [%s/%s]:\\n%s\", svc.Namespace, svc.Name, err)\n\t\t}\n\t}\n\n\t// Tests Pods and Deployments\n\tfor deploy, spec := range deploys {\n\t\tif err := h.CheckPods(ctx, spec.Namespace, deploy, spec.Replicas); err != nil {\n\t\t\t//nolint:errorlint\n\t\t\tif rce, ok := err.(*RestartCountError); ok {\n\t\t\t\tAnnotatedWarn(t, \"CheckPods timed-out\", rce)\n\t\t\t} else {\n\t\t\t\tAnnotatedFatal(t, \"CheckPods timed-out\", err)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ExerciseTestAppEndpoint tests if the emojivoto service is reachable\nfunc ExerciseTestAppEndpoint(endpoint, namespace string, h *TestHelper) error {\n\ttestAppURL, err := h.URLFor(context.Background(), namespace, \"web\", 8080)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor i := 0; i < 30; i++ {\n\t\t_, err := h.HTTPGetURL(testAppURL + endpoint)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "testutil/kubernetes_helper.go",
    "content": "package testutil\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\n\t// Loads the GCP auth plugin\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth/gcp\"\n)\n\n// KubernetesHelper provides Kubernetes-related test helpers. It connects to the\n// Kubernetes API using the environment's configured kubeconfig file.\ntype KubernetesHelper struct {\n\tk8sContext string\n\tclientset  *kubernetes.Clientset\n\tretryFor   func(time.Duration, func() error) error\n}\n\n// RestartCountError is returned by CheckPods() whenever a pod has restarted exactly one time.\n// Consumers should log this type of error instead of failing the test.\n// This is to alleviate CI flakiness stemming from a containerd bug.\n// See https://github.com/kubernetes/kubernetes/issues/89064\n// See https://github.com/containerd/containerd/issues/4068\ntype RestartCountError struct {\n\tmsg string\n}\n\nfunc (e *RestartCountError) Error() string {\n\treturn e.msg\n}\n\n// NewKubernetesHelper creates a new instance of KubernetesHelper.\nfunc NewKubernetesHelper(k8sContext string, retryFor func(time.Duration, func() error) error) (*KubernetesHelper, error) {\n\trules := clientcmd.NewDefaultClientConfigLoadingRules()\n\toverrides := &clientcmd.ConfigOverrides{CurrentContext: k8sContext}\n\tkubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)\n\tconfig, err := kubeConfig.ClientConfig()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tclientset, err := kubernetes.NewForConfig(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &KubernetesHelper{\n\t\tclientset:  clientset,\n\t\tk8sContext: k8sContext,\n\t\tretryFor:   retryFor,\n\t}, nil\n}\n\n// CheckIfNamespaceExists checks if a namespace exists.\nfunc (h *KubernetesHelper) CheckIfNamespaceExists(ctx context.Context, namespace string) error {\n\t_, err := h.clientset.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{})\n\treturn err\n}\n\n// SwitchContext will re-build the clientset with the given context. Useful when\n// testing multiple clusters at the same time\nfunc (h *KubernetesHelper) SwitchContext(ctx string) error {\n\trules := clientcmd.NewDefaultClientConfigLoadingRules()\n\toverrides := &clientcmd.ConfigOverrides{CurrentContext: ctx}\n\tkubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)\n\tconfig, err := kubeConfig.ClientConfig()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclientset, err := kubernetes.NewForConfig(config)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\th.clientset = clientset\n\treturn nil\n}\n\n// GetSecret retrieves a Kubernetes Secret\nfunc (h *KubernetesHelper) GetSecret(ctx context.Context, namespace, name string) (*corev1.Secret, error) {\n\treturn h.clientset.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{})\n}\n\nfunc (h *KubernetesHelper) createNamespaceIfNotExists(ctx context.Context, namespace string, annotations, labels map[string]string) error {\n\terr := h.CheckIfNamespaceExists(ctx, namespace)\n\n\tif err != nil {\n\t\tns := &corev1.Namespace{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tLabels:      labels,\n\t\t\t\tAnnotations: annotations,\n\t\t\t\tName:        namespace,\n\t\t\t},\n\t\t}\n\t\t_, err = h.clientset.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// DeleteNamespaceIfExists attempts to delete the given namespace,\n// using the K8s API directly\nfunc (h *KubernetesHelper) DeleteNamespaceIfExists(ctx context.Context, namespace string) error {\n\terr := h.clientset.CoreV1().Namespaces().Delete(ctx, namespace, metav1.DeleteOptions{})\n\n\tif err != nil && !kerrors.IsNotFound(err) {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// CreateControlPlaneNamespaceIfNotExists creates linkerd control plane namespace.\nfunc (h *KubernetesHelper) CreateControlPlaneNamespaceIfNotExists(ctx context.Context, namespace string) error {\n\treturn h.createNamespaceIfNotExists(ctx, namespace, nil, nil)\n}\n\n// CreateDataPlaneNamespaceIfNotExists creates a dataplane namespace if it does not already exist,\n// with a test.linkerd.io/is-test-data-plane label for easier cleanup afterwards\nfunc (h *KubernetesHelper) CreateDataPlaneNamespaceIfNotExists(ctx context.Context, namespace string, annotations map[string]string) error {\n\treturn h.createNamespaceIfNotExists(ctx, namespace, annotations, map[string]string{\"test.linkerd.io/is-test-data-plane\": \"true\"})\n}\n\n// KubectlApply applies a given configuration string in a namespace. If the\n// namespace does not exist, it creates it first. If no namespace is provided,\n// it does not specify the `--namespace` flag.\nfunc (h *KubernetesHelper) KubectlApply(stdin string, namespace string) (string, error) {\n\targs := []string{\"apply\", \"-f\", \"-\"}\n\tif namespace != \"\" {\n\t\targs = append(args, \"--namespace\", namespace)\n\t}\n\n\treturn h.Kubectl(stdin, args...)\n}\n\n// KubectlApplyWithArgs applies a given configuration string with the passed\n// flags\nfunc (h *KubernetesHelper) KubectlApplyWithArgs(stdin string, cmdArgs ...string) (string, error) {\n\targs := []string{\"apply\"}\n\targs = append(args, cmdArgs...)\n\targs = append(args, \"-f\", \"-\")\n\treturn h.Kubectl(stdin, args...)\n}\n\n// Kubectl executes an arbitrary Kubectl command\nfunc (h *KubernetesHelper) Kubectl(stdin string, arg ...string) (string, error) {\n\twithContext := append([]string{\"--context=\" + h.k8sContext}, arg...)\n\tcmd := exec.Command(\"kubectl\", withContext...)\n\tcmd.Stdin = strings.NewReader(stdin)\n\tout, err := cmd.CombinedOutput()\n\treturn string(out), err\n}\n\n// KubectlApplyWithContext applies a given configuration with the given flags\nfunc (h *KubernetesHelper) KubectlApplyWithContext(stdin string, context string, arg ...string) (string, error) {\n\targs := append([]string{\"apply\"}, arg...)\n\treturn h.KubectlWithContext(stdin, context, args...)\n}\n\n// KubectlWithContext will call the kubectl binary with any optional arguments\n// provided and an arbitrary, given context. Useful when working with k8s\n// resources in a multi-cluster context. Optionally, stdin can be piped to\n// kubectl.\nfunc (h *KubernetesHelper) KubectlWithContext(stdin string, context string, arg ...string) (string, error) {\n\twithContext := append([]string{\"--context=\" + context}, arg...)\n\tcmd := exec.Command(\"kubectl\", withContext...)\n\tcmd.Stdin = strings.NewReader(stdin)\n\tout, err := cmd.CombinedOutput()\n\treturn string(out), err\n}\n\n// GetConfigUID returns the uid associated to the linkerd-config ConfigMap resource\n// in the given namespace\nfunc (h *KubernetesHelper) GetConfigUID(ctx context.Context, namespace string) (string, error) {\n\tcm, err := h.clientset.CoreV1().ConfigMaps(namespace).Get(ctx, k8s.ConfigConfigMapName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(cm.GetUID()), nil\n}\n\n// GetResources returns the resource limits and requests set on a deployment\n// of the set name in the given namespace\nfunc (h *KubernetesHelper) GetResources(ctx context.Context, containerName, deploymentName, namespace string) (corev1.ResourceRequirements, error) {\n\tdep, err := h.clientset.AppsV1().Deployments(namespace).Get(ctx, deploymentName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn corev1.ResourceRequirements{}, err\n\t}\n\n\tfor _, container := range dep.Spec.Template.Spec.Containers {\n\t\tif container.Name == containerName {\n\t\t\treturn container.Resources, nil\n\t\t}\n\t}\n\treturn corev1.ResourceRequirements{}, fmt.Errorf(\"container %s not found in deployment %s in namespace %s\", containerName, deploymentName, namespace)\n}\n\n// CheckPods checks that a deployment in a namespace contains the expected\n// number of pods in the Running state, and that no pods have been restarted.\nfunc (h *KubernetesHelper) CheckPods(ctx context.Context, namespace string, deploymentName string, replicas int) error {\n\tvar checkedPods []corev1.Pod\n\n\terr := h.retryFor(60*time.Minute, func() error {\n\t\tcheckedPods = []corev1.Pod{}\n\t\tpods, err := h.GetPodsForDeployment(ctx, namespace, deploymentName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tvar deploymentReplicas int\n\t\tfor _, pod := range pods {\n\t\t\tcheckedPods = append(checkedPods, pod)\n\n\t\t\tdeploymentReplicas++\n\t\t\tif pod.Status.Phase != \"Running\" {\n\t\t\t\treturn fmt.Errorf(\"Pod [%s] in namespace [%s] is not running\",\n\t\t\t\t\tpod.Name, pod.Namespace)\n\t\t\t}\n\t\t\tfor _, container := range pod.Status.ContainerStatuses {\n\t\t\t\tif !container.Ready {\n\t\t\t\t\treturn fmt.Errorf(\"Container [%s] in pod [%s] in namespace [%s] is not running\",\n\t\t\t\t\t\tcontainer.Name, pod.Name, pod.Namespace)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif deploymentReplicas != replicas {\n\t\t\treturn fmt.Errorf(\"Expected there to be [%d] pods in deployment [%s] in namespace [%s], but found [%d]\",\n\t\t\t\treplicas, deploymentName, namespace, deploymentReplicas)\n\t\t}\n\n\t\treturn nil\n\t})\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, pod := range checkedPods {\n\t\tfor _, status := range append(pod.Status.ContainerStatuses, pod.Status.InitContainerStatuses...) {\n\t\t\terrStr := fmt.Sprintf(\"Container [%s] in pod [%s] in namespace [%s] has restart count [%d]\",\n\t\t\t\tstatus.Name, pod.Name, pod.Namespace, status.RestartCount)\n\t\t\tif status.RestartCount == 1 {\n\t\t\t\treturn &RestartCountError{errStr}\n\t\t\t}\n\t\t\tif status.RestartCount > 1 {\n\t\t\t\treturn errors.New(errStr)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// CheckService checks that a service exists in a namespace.\nfunc (h *KubernetesHelper) CheckService(ctx context.Context, namespace string, serviceName string) error {\n\treturn h.retryFor(10*time.Second, func() error {\n\t\t_, err := h.clientset.CoreV1().Services(namespace).Get(ctx, serviceName, metav1.GetOptions{})\n\t\treturn err\n\t})\n}\n\n// GetService gets a service that exists in a namespace.\nfunc (h *KubernetesHelper) GetService(ctx context.Context, namespace string, serviceName string) (*corev1.Service, error) {\n\tservice, err := h.clientset.CoreV1().Services(namespace).Get(ctx, serviceName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn service, nil\n}\n\n// GetEndpoints gets endpoints that exist in a namespace.\nfunc (h *KubernetesHelper) GetEndpoints(ctx context.Context, namespace string, serviceName string) (*corev1.Endpoints, error) {\n\tep, err := h.clientset.CoreV1().Endpoints(namespace).Get(ctx, serviceName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn ep, nil\n}\n\n// GetPods returns all pods with the given labels\nfunc (h *KubernetesHelper) GetPods(ctx context.Context, namespace string, podLabels map[string]string) ([]corev1.Pod, error) {\n\tpodList, err := h.clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{\n\t\tLabelSelector: labels.Set(podLabels).AsSelector().String(),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn podList.Items, nil\n}\n\n// GetPodsForDeployment returns all pods for the given deployment\nfunc (h *KubernetesHelper) GetPodsForDeployment(ctx context.Context, namespace string, deploymentName string) ([]corev1.Pod, error) {\n\tdeploy, err := h.clientset.AppsV1().Deployments(namespace).Get(ctx, deploymentName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn h.GetPods(ctx, namespace, deploy.Spec.Selector.MatchLabels)\n}\n\n// GetPodNamesForDeployment returns all pod names for the given deployment\nfunc (h *KubernetesHelper) GetPodNamesForDeployment(ctx context.Context, namespace string, deploymentName string) ([]string, error) {\n\tpodList, err := h.GetPodsForDeployment(ctx, namespace, deploymentName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpods := make([]string, 0)\n\tfor _, pod := range podList {\n\t\tpods = append(pods, pod.Name)\n\t}\n\n\treturn pods, nil\n}\n\n// ParseNamespacedResource extracts a namespace and resource name from a string\n// that's in the format namespace/resource. If the strings is in a different\n// format it returns an error.\nfunc (h *KubernetesHelper) ParseNamespacedResource(resource string) (string, string, error) {\n\tr := regexp.MustCompile(`^(.+)\\/(.+)$`)\n\tmatches := r.FindAllStringSubmatch(resource, 2)\n\tif len(matches) == 0 {\n\t\treturn \"\", \"\", fmt.Errorf(\"string [%s] didn't contain expected format for namespace/resource, extracted: %v\", resource, matches)\n\t}\n\treturn matches[0][1], matches[0][2], nil\n}\n\n// URLFor creates a kubernetes port-forward, runs it, and returns the URL that\n// tests can use for access to the given deployment. Note that the port-forward\n// remains running for the duration of the test.\nfunc (h *KubernetesHelper) URLFor(ctx context.Context, namespace, deployName string, remotePort int) (string, error) {\n\tk8sAPI, err := k8s.NewAPI(\"\", h.k8sContext, \"\", []string{}, 0)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tpf, err := k8s.NewPortForward(ctx, k8sAPI, namespace, deployName, \"localhost\", 0, remotePort, false)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif err = pf.Init(); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn pf.URLFor(\"\"), nil\n}\n\n// WaitRollout blocks until all the given deployments have been completely\n// rolled out (and their pods are ready)\nfunc (h *KubernetesHelper) WaitRollout(t *testing.T, deploys map[string]DeploySpec) {\n\tt.Helper()\n\t// Use default context\n\th.WaitRolloutWithContext(t, deploys, h.k8sContext)\n}\n\n// WaitRolloutWithContext blocks until all the given deployments in a provided\n// k8s context have been completely rolled out (and their pods are ready)\nfunc (h *KubernetesHelper) WaitRolloutWithContext(t *testing.T, deploys map[string]DeploySpec, context string) {\n\tt.Helper()\n\tfor deploy, deploySpec := range deploys {\n\t\tstat, err := h.KubectlWithContext(\"\", context, \"--namespace=\"+deploySpec.Namespace,\n\t\t\t\"rollout\", \"status\", \"--timeout=5m\", \"deploy/\"+deploy)\n\t\tif err != nil {\n\t\t\tdesc, _ := h.KubectlWithContext(\"\", context, \"--namespace=\"+deploySpec.Namespace,\n\t\t\t\t\"describe\", \"po\")\n\t\t\tAnnotatedFatalf(t,\n\t\t\t\tfmt.Sprintf(\"failed to wait rollout of deploy/%s\", deploy),\n\t\t\t\t\"failed to wait for rollout of deploy/%s: %s: %s\\n---\\n%s\", deploy, err, stat, desc)\n\t\t}\n\t}\n}\n\n// WaitUntilDeployReady will block and wait until all given deploys have been\n// rolled out and their pods are in a 'ready' status. The difference between\n// this and WaitRollout is that WaitUntilDeployReady uses CheckPods underneath,\n// instead of relying on the 'rollout' command. WaitUntilDeployReady will also\n// retry for a long period time. This function is used to block tests from\n// running until the control plane and extensions are ready.\nfunc (h *KubernetesHelper) WaitUntilDeployReady(deploys map[string]DeploySpec) {\n\tctx := context.Background()\n\tfor deploy, spec := range deploys {\n\t\tif err := h.CheckPods(ctx, spec.Namespace, deploy, 1); err != nil {\n\t\t\tvar out string\n\t\t\t//nolint:errorlint\n\t\t\tif rce, ok := err.(*RestartCountError); ok {\n\t\t\t\tout = fmt.Sprintf(\"Error running test: failed to wait for deploy/%s to become 'ready', too many restarts (%v)\\n\", deploy, rce)\n\t\t\t} else {\n\t\t\t\tout = fmt.Sprintf(\"Error running test: failed to wait for deploy/%s to become 'ready', timed out waiting for condition\\n\", deploy)\n\t\t\t}\n\t\t\tos.Stderr.Write([]byte(out))\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "testutil/prommatch/common.go",
    "content": "package prommatch\n\nimport \"regexp\"\n\nvar (\n\tportRe = regexp.MustCompile(`\\d+`)\n)\n\n// TargetAddrLabels match series with proper target_addr, target_port, and target_ip.\nfunc TargetAddrLabels() Labels {\n\treturn Labels{\n\t\t\"target_addr\": IsAddr(),\n\t\t\"target_ip\":   IsIP(),\n\t\t\"target_port\": Like(portRe),\n\t}\n}\n"
  },
  {
    "path": "testutil/prommatch/prommatch.go",
    "content": "// Package prommatch provides means of checking whether a prometheus metrics\n// contain a specific series.\n//\n// It tries to give a similar look and feel as time series in PromQL.\n// So where in PromQL one would write this:\n//\n//\trequest_total{direction=\"outbound\", target_port=~\"8\\d\\d\\d\"} 30\n//\n// In prommatch, one can write this:\n//\n//\tportRE := regex.MustCompile(`^8\\d\\d\\d$`)\n//\tprommatch.NewMatcher(\"request_total\", prommatch.Labels{\n//\t\t\"direction\": prommatch.Equals(\"outbound\"),\n//\t\t\"target_port\": prommatch.Like(portRE),\n//\t})\npackage prommatch\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"regexp\"\n\n\tdto \"github.com/prometheus/client_model/go\"\n\t\"github.com/prometheus/common/expfmt\"\n\t\"github.com/prometheus/common/model\"\n)\n\n// Expression can match or reject one time series.\ntype Expression interface {\n\tmatches(sp *model.Sample) bool\n}\n\ntype funcMatcher func(sp *model.Sample) bool\n\nfunc (fc funcMatcher) matches(sp *model.Sample) bool {\n\treturn fc(sp)\n}\n\n// NewMatcher will match series name (exactly) and all the additional matchers.\nfunc NewMatcher(name string, ms ...Expression) *Matcher {\n\treturn &Matcher{\n\t\texpressions: append([]Expression{hasName(name)}, ms...),\n\t}\n}\n\n// Matcher contains a list of expressions, which will be checked against each series.\ntype Matcher struct {\n\texpressions []Expression\n}\n\n// HasMatchInString will return:\n// - true, if the provided metrics have a series which matches all expressions,\n// - false, if none of the series matches,\n// - error, if the provided string is not valid Prometheus metrics,\nfunc (e *Matcher) HasMatchInString(s string) (bool, error) {\n\tv, err := extractSamplesVectorFromString(s)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to parse input string as vector of samples: %w\", err)\n\t}\n\treturn e.hasMatchInVector(v), nil\n}\n\nfunc (e *Matcher) hasMatchInVector(v model.Vector) bool {\n\tfor _, s := range v {\n\t\tif e.sampleMatches(s) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (e *Matcher) sampleMatches(s *model.Sample) bool {\n\tfor _, m := range e.expressions {\n\t\tif !m.matches(s) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// LabelMatcher can match or reject a label's value.\ntype LabelMatcher func(string) bool\n\n// Labels is used for selecting series with matching labels.\ntype Labels map[string]LabelMatcher\n\n// Make sure Labels implement Expression.\nvar _ Expression = Labels{}\n\nfunc (l Labels) matches(s *model.Sample) bool {\n\tfor k, m := range l {\n\t\tlabelValue := s.Metric[model.LabelName(k)]\n\t\tif !m(string(labelValue)) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// Equals is when you want label value to have an exact value.\nfunc Equals(expected string) LabelMatcher {\n\treturn func(s string) bool {\n\t\treturn expected == s\n\t}\n}\n\n// Like is when you want label value to match a regular expression.\nfunc Like(re *regexp.Regexp) LabelMatcher {\n\treturn func(s string) bool {\n\t\treturn re.MatchString(s)\n\t}\n}\n\n// Absent is when you want to match series MISSING a specific label.\nfunc Absent() LabelMatcher {\n\treturn func(s string) bool {\n\t\treturn s == \"\"\n\t}\n}\n\n// Any is when you want to select a series which has a certain label, but don't care about the value.\nfunc Any() LabelMatcher {\n\treturn func(s string) bool {\n\t\treturn s != \"\"\n\t}\n}\n\n// HasValueLike is used for selecting time series based on value.\nfunc HasValueLike(f func(float64) bool) Expression {\n\treturn funcMatcher(func(sp *model.Sample) bool {\n\t\treturn f(float64(sp.Value))\n\t})\n}\n\n// HasValueOf is used for selecting time series based on a specific value.\nfunc HasValueOf(f float64) Expression {\n\treturn funcMatcher(func(sp *model.Sample) bool {\n\t\treturn f == float64(sp.Value)\n\t})\n}\n\n// HasPositiveValue is used to select time series with a positive value.\nfunc HasPositiveValue() Expression {\n\treturn HasValueLike(func(f float64) bool {\n\t\treturn f > 0\n\t})\n}\n\n// IsAddr is used to check if the value is an IP:port combo, where IP can be\n// an IPv4 or an IPv6\nfunc IsAddr() LabelMatcher {\n\treturn func(s string) bool {\n\t\tif _, err := netip.ParseAddrPort(s); err != nil {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}\n}\n\n// IsIP use used to check if the value is an IPv4 or IPv6\nfunc IsIP() LabelMatcher {\n\treturn func(s string) bool {\n\t\treturn net.ParseIP(s) != nil\n\t}\n}\n\nfunc hasName(metricName string) Expression {\n\treturn funcMatcher(func(sp *model.Sample) bool {\n\t\treturn sp.Metric[model.MetricNameLabel] == model.LabelValue(metricName)\n\t})\n}\n\nfunc extractSamplesVectorFromString(s string) (model.Vector, error) {\n\tbb := bytes.NewBufferString(s)\n\n\tp := expfmt.NewTextParser(model.LegacyValidation)\n\tmetricFamilies, err := p.TextToMetricFamilies(bb)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse input as metrics: %w\", err)\n\t}\n\tvar mfs []*dto.MetricFamily\n\tfor _, m := range metricFamilies {\n\t\tmfs = append(mfs, m)\n\t}\n\tv, err := expfmt.ExtractSamples(&expfmt.DecodeOptions{}, mfs...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to extract samples from input: %w\", err)\n\t}\n\treturn v, nil\n}\n"
  },
  {
    "path": "testutil/prommatch/prommatch_test.go",
    "content": "package prommatch\n\nimport (\n\t_ \"embed\"\n\t\"regexp\"\n\t\"testing\"\n)\n\n//go:embed testdata/sampleMetrics.txt\nvar sampleMetrics string\n\nfunc TestMatchingString(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tmatcher *Matcher\n\t\tresult  bool\n\t}{\n\t\t{\n\t\t\tname:    \"match by name\",\n\t\t\tmatcher: NewMatcher(\"process_cpu_seconds_total\"),\n\t\t\tresult:  true,\n\t\t},\n\t\t{\n\t\t\tname:    \"match by name (no results)\",\n\t\t\tmatcher: NewMatcher(\"process_cpu_seconds_total_xxx\"),\n\t\t\tresult:  false,\n\t\t},\n\t\t{\n\t\t\tname: \"match by label\",\n\t\t\tmatcher: NewMatcher(\n\t\t\t\t\"inbound_http_authz_allow_total\",\n\t\t\t\tLabels{\n\t\t\t\t\t\"authz_kind\": Equals(\"default\"),\n\t\t\t\t}),\n\t\t\tresult: true,\n\t\t},\n\t\t{\n\t\t\tname: \"match by label (no results, no such value)\",\n\t\t\tmatcher: NewMatcher(\n\t\t\t\t\"inbound_http_authz_allow_total\",\n\t\t\t\tLabels{\n\t\t\t\t\t\"authz_kind\": Equals(\"default_xxx\"),\n\t\t\t\t}),\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\tname: \"match by label (no results, no such labels)\",\n\t\t\tmatcher: NewMatcher(\n\t\t\t\t\"inbound_http_authz_allow_total\",\n\t\t\t\tLabels{\n\t\t\t\t\t\"authz_kind_xxx\": Equals(\"default\"),\n\t\t\t\t}),\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\tname: \"match by label regex and name\",\n\t\t\tmatcher: NewMatcher(\n\t\t\t\t\"control_response_latency_ms_sum\",\n\t\t\t\tLabels{\n\t\t\t\t\t\"addr\": Like(regexp.MustCompile(`linkerd-identity-headless.linkerd.svc.cluster.local:[\\d]{4}`)),\n\t\t\t\t}),\n\t\t\tresult: true,\n\t\t},\n\t\t{\n\t\t\tname: \"match by label regex and name (no results)\",\n\t\t\tmatcher: NewMatcher(\n\t\t\t\t\"control_response_latency_ms_sum\",\n\t\t\t\tLabels{\n\t\t\t\t\t\"addr\": Like(regexp.MustCompile(`linkerd-identity-headless.linkerd.svc.cluster.local:[\\d]{5}`)),\n\t\t\t\t}),\n\t\t\tresult: false,\n\t\t},\n\t\t{\n\t\t\tname: \"match histogram bucket\",\n\t\t\tmatcher: NewMatcher(\n\t\t\t\t\"control_response_latency_ms_bucket\",\n\t\t\t\tLabels{\n\t\t\t\t\t\"le\": Equals(\"2\"),\n\t\t\t\t}),\n\t\t\tresult: true,\n\t\t},\n\t\t{\n\t\t\tname: \"match histogram bucket\",\n\t\t\tmatcher: NewMatcher(\n\t\t\t\t\"control_response_latency_ms_bucket\",\n\t\t\t\tLabels{\n\t\t\t\t\t\"le\": Equals(\"2\"),\n\t\t\t\t},\n\t\t\t\tHasPositiveValue()),\n\t\t\tresult: true,\n\t\t},\n\t\t{\n\t\t\tname: \"match by value (no results)\",\n\t\t\tmatcher: NewMatcher(\n\t\t\t\t\"control_response_latency_ms_bucket\",\n\t\t\t\tLabels{\n\t\t\t\t\t\"le\": Equals(\"0\"),\n\t\t\t\t},\n\t\t\t\tHasPositiveValue(),\n\t\t\t),\n\t\t\tresult: false,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tok, err := tc.matcher.HasMatchInString(sampleMetrics)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t}\n\t\t\tif ok != tc.result {\n\t\t\t\tt.Fatalf(\"Expected %v, got %v\", tc.result, ok)\n\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "testutil/prommatch/suite.go",
    "content": "package prommatch\n\nimport (\n\t\"fmt\"\n)\n\ntype matcherAndMessage struct {\n\tmatcher *Matcher\n\tmessage string\n\tpresent bool\n}\n\n// Suite is a list of matchers and messages to check against a string of metrics.\ntype Suite []matcherAndMessage\n\n// MustContain a series which will match the provided matcher.\nfunc (ms Suite) MustContain(message string, m *Matcher) Suite {\n\treturn append(ms, matcherAndMessage{\n\t\tmatcher: m,\n\t\tmessage: message,\n\t\tpresent: true,\n\t})\n}\n\n// MustNotContain a series which will match the provided matcher.\nfunc (ms Suite) MustNotContain(message string, m *Matcher) Suite {\n\treturn append(ms, matcherAndMessage{\n\t\tmatcher: m,\n\t\tmessage: message,\n\t\tpresent: false,\n\t})\n}\n\n// CheckString will run each assertion in the suite against the provided metrics.\nfunc (ms Suite) CheckString(metrics string) error {\n\tfor _, m := range ms {\n\t\tok, err := m.matcher.HasMatchInString(metrics)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to run a check of against the provided metrics: %w\", err)\n\t\t}\n\t\tif m.present && !ok {\n\t\t\treturn fmt.Errorf(\"expected to find %s\\n%s\", m.message, metrics)\n\t\t}\n\t\tif !m.present && ok {\n\t\t\treturn fmt.Errorf(\"expected not to find %s\\n%s\", m.message, metrics)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "testutil/prommatch/testdata/sampleMetrics.txt",
    "content": "#\n# POD slow-cooker-opaque-pod-mjqsw (1 of 1)\n#\n# HELP inbound_http_authz_allow_total The total number of inbound HTTP requests that were authorized\n# TYPE inbound_http_authz_allow_total counter\ninbound_http_authz_allow_total{target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\"} 30\n# HELP identity_cert_expiration_timestamp_seconds Time when the this proxy's current mTLS identity certificate will expire (in seconds since the UNIX epoch).\n# TYPE identity_cert_expiration_timestamp_seconds gauge\nidentity_cert_expiration_timestamp_seconds 1651053543\n# HELP identity_cert_refresh_count The total number of times this proxy's mTLS identity certificate has been refreshed by the Identity service.\n# TYPE identity_cert_refresh_count counter\nidentity_cert_refresh_count 1\n# HELP request_total Total count of HTTP requests.\n# TYPE request_total counter\nrequest_total{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\"} 30\n# HELP response_latency_ms Elapsed times between a request's headers being received and its response stream completing\n# TYPE response_latency_ms histogram\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"1\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"2\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"3\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"4\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"5\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"10\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"20\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"30\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"40\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"50\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"100\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"200\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"300\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"400\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"500\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"1000\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"2000\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"3000\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"4000\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"5000\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"10000\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"20000\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"30000\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"40000\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"50000\"} 29\nresponse_latency_ms_bucket{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",le=\"+Inf\"} 29\nresponse_latency_ms_count{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\"} 29\nresponse_latency_ms_sum{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\"} 0\n# HELP response_total Total count of HTTP responses.\n# TYPE response_total counter\nresponse_total{direction=\"inbound\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",authz_kind=\"default\",authz_name=\"all-unauthenticated\",status_code=\"200\",classification=\"success\"} 29\n# HELP control_request_total Total count of HTTP requests.\n# TYPE control_request_total counter\ncontrol_request_total{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"} 1\ncontrol_request_total{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\"} 2\ncontrol_request_total{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\"} 1\n# HELP control_response_latency_ms Elapsed times between a request's headers being received and its response stream completing\n# TYPE control_response_latency_ms histogram\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"1\"} 0\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"2\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"3\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"4\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"5\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"10\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"20\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"30\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"40\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"50\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"100\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"200\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"300\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"400\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"500\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"1000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"2000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"3000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"4000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"5000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"10000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"20000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"30000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"40000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"50000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"+Inf\"} 1\ncontrol_response_latency_ms_count{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\"} 1\ncontrol_response_latency_ms_sum{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"1\"} 0\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"2\"} 0\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"3\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"4\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"5\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"10\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"20\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"30\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"40\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"50\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"100\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"200\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"300\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"400\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"500\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"1000\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"2000\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"3000\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"4000\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"5000\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"10000\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"20000\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"30000\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"40000\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"50000\"} 2\ncontrol_response_latency_ms_bucket{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"+Inf\"} 2\ncontrol_response_latency_ms_count{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\"} 2\ncontrol_response_latency_ms_sum{addr=\"linkerd-policy.linkerd.svc.cluster.local:8090\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\"} 6\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"1\"} 0\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"2\"} 0\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"3\"} 0\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"4\"} 0\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"5\"} 0\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"10\"} 0\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"20\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"30\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"40\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"50\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"100\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"200\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"300\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"400\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"500\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"1000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"2000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"3000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"4000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"5000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"10000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"20000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"30000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"40000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"50000\"} 1\ncontrol_response_latency_ms_bucket{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",le=\"+Inf\"} 1\ncontrol_response_latency_ms_count{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\"} 1\ncontrol_response_latency_ms_sum{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\"} 12\n# HELP control_response_total Total count of HTTP responses.\n# TYPE control_response_total counter\ncontrol_response_total{addr=\"linkerd-dst-headless.linkerd.svc.cluster.local:8086\",tls=\"true\",server_id=\"linkerd-destination.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",classification=\"success\"} 1\ncontrol_response_total{addr=\"linkerd-identity-headless.linkerd.svc.cluster.local:8080\",tls=\"true\",server_id=\"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",status_code=\"200\",classification=\"success\",grpc_status=\"0\"} 1\n# HELP tcp_open_total Total count of opened connections\n# TYPE tcp_open_total counter\ntcp_open_total{direction=\"outbound\",peer=\"dst\",target_addr=\"10.42.0.122:8080\",target_ip=\"10.42.0.122\",target_port=\"8080\",tls=\"true\",server_id=\"default.linkerd-opaque-ports-test.serviceaccount.identity.linkerd.cluster.local\",dst_control_plane_ns=\"linkerd\",dst_deployment=\"opaque-pod\",dst_namespace=\"linkerd-opaque-ports-test\",dst_pod=\"opaque-pod-dcf789f5d-sfx7v\",dst_pod_template_hash=\"dcf789f5d\",dst_serviceaccount=\"default\"} 1\ntcp_open_total{direction=\"outbound\",peer=\"src\",target_addr=\"10.42.0.122:8080\",target_ip=\"10.42.0.122\",target_port=\"8080\",tls=\"no_identity\",no_tls_reason=\"loopback\"} 1\ntcp_open_total{direction=\"inbound\",peer=\"src\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\"} 30\n# HELP tcp_open_connections Number of currently-open connections\n# TYPE tcp_open_connections gauge\ntcp_open_connections{direction=\"outbound\",peer=\"dst\",target_addr=\"10.42.0.122:8080\",target_ip=\"10.42.0.122\",target_port=\"8080\",tls=\"true\",server_id=\"default.linkerd-opaque-ports-test.serviceaccount.identity.linkerd.cluster.local\",dst_control_plane_ns=\"linkerd\",dst_deployment=\"opaque-pod\",dst_namespace=\"linkerd-opaque-ports-test\",dst_pod=\"opaque-pod-dcf789f5d-sfx7v\",dst_pod_template_hash=\"dcf789f5d\",dst_serviceaccount=\"default\"} 1\ntcp_open_connections{direction=\"outbound\",peer=\"src\",target_addr=\"10.42.0.122:8080\",target_ip=\"10.42.0.122\",target_port=\"8080\",tls=\"no_identity\",no_tls_reason=\"loopback\"} 1\ntcp_open_connections{direction=\"inbound\",peer=\"src\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\"} 1\n# HELP tcp_read_bytes_total Total count of bytes read from peers\n# TYPE tcp_read_bytes_total counter\ntcp_read_bytes_total{direction=\"outbound\",peer=\"dst\",target_addr=\"10.42.0.122:8080\",target_ip=\"10.42.0.122\",target_port=\"8080\",tls=\"true\",server_id=\"default.linkerd-opaque-ports-test.serviceaccount.identity.linkerd.cluster.local\",dst_control_plane_ns=\"linkerd\",dst_deployment=\"opaque-pod\",dst_namespace=\"linkerd-opaque-ports-test\",dst_pod=\"opaque-pod-dcf789f5d-sfx7v\",dst_pod_template_hash=\"dcf789f5d\",dst_serviceaccount=\"default\"} 8323\ntcp_read_bytes_total{direction=\"outbound\",peer=\"src\",target_addr=\"10.42.0.122:8080\",target_ip=\"10.42.0.122\",target_port=\"8080\",tls=\"no_identity\",no_tls_reason=\"loopback\"} 3763\ntcp_read_bytes_total{direction=\"inbound\",peer=\"src\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\"} 3073\n# HELP tcp_write_bytes_total Total count of bytes written to peers\n# TYPE tcp_write_bytes_total counter\ntcp_write_bytes_total{direction=\"outbound\",peer=\"dst\",target_addr=\"10.42.0.122:8080\",target_ip=\"10.42.0.122\",target_port=\"8080\",tls=\"true\",server_id=\"default.linkerd-opaque-ports-test.serviceaccount.identity.linkerd.cluster.local\",dst_control_plane_ns=\"linkerd\",dst_deployment=\"opaque-pod\",dst_namespace=\"linkerd-opaque-ports-test\",dst_pod=\"opaque-pod-dcf789f5d-sfx7v\",dst_pod_template_hash=\"dcf789f5d\",dst_serviceaccount=\"default\"} 3763\ntcp_write_bytes_total{direction=\"outbound\",peer=\"src\",target_addr=\"10.42.0.122:8080\",target_ip=\"10.42.0.122\",target_port=\"8080\",tls=\"no_identity\",no_tls_reason=\"loopback\"} 8323\ntcp_write_bytes_total{direction=\"inbound\",peer=\"src\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\"} 63256\n# HELP tcp_close_total Total count of closed connections\n# TYPE tcp_close_total counter\ntcp_close_total{direction=\"inbound\",peer=\"src\",target_addr=\"0.0.0.0:4191\",target_ip=\"0.0.0.0\",target_port=\"4191\",tls=\"no_identity\",no_tls_reason=\"no_tls_from_remote\",srv_kind=\"default\",srv_name=\"all-unauthenticated\",errno=\"\"} 29\n# HELP opencensus_span_export_streams Total count of opened span export streams\n# TYPE opencensus_span_export_streams counter\nopencensus_span_export_streams 0\n# HELP opencensus_span_export_requests Total count of span export request messages\n# TYPE opencensus_span_export_requests counter\nopencensus_span_export_requests 0\n# HELP opencensus_span_exports Total count of spans exported\n# TYPE opencensus_span_exports counter\nopencensus_span_exports 0\n# HELP stack_create_total Total number of services created\n# TYPE stack_create_total counter\nstack_create_total{direction=\"outbound\",protocol=\"tcp\",name=\"balancer\"} 0\nstack_create_total{direction=\"outbound\",protocol=\"http\",name=\"logical\"} 0\nstack_create_total{direction=\"outbound\",protocol=\"tcp\",name=\"logical\"} 0\nstack_create_total{direction=\"inbound\",protocol=\"tcp\",name=\"gateway\"} 0\nstack_create_total{direction=\"outbound\",protocol=\"tcp\",name=\"server\"} 1\nstack_create_total{direction=\"outbound\",protocol=\"http\",name=\"balancer\"} 0\nstack_create_total{direction=\"inbound\",protocol=\"http\",name=\"gateway\"} 0\nstack_create_total{direction=\"inbound\",protocol=\"http\",name=\"logical\"} 0\nstack_create_total{direction=\"outbound\",protocol=\"http\",name=\"balance.endpoint\"} 0\n# HELP stack_drop_total Total number of services dropped\n# TYPE stack_drop_total counter\nstack_drop_total{direction=\"outbound\",protocol=\"tcp\",name=\"balancer\"} 0\nstack_drop_total{direction=\"outbound\",protocol=\"http\",name=\"logical\"} 0\nstack_drop_total{direction=\"outbound\",protocol=\"tcp\",name=\"logical\"} 0\nstack_drop_total{direction=\"inbound\",protocol=\"tcp\",name=\"gateway\"} 0\nstack_drop_total{direction=\"outbound\",protocol=\"tcp\",name=\"server\"} 0\nstack_drop_total{direction=\"outbound\",protocol=\"http\",name=\"balancer\"} 0\nstack_drop_total{direction=\"inbound\",protocol=\"http\",name=\"gateway\"} 0\nstack_drop_total{direction=\"inbound\",protocol=\"http\",name=\"logical\"} 0\nstack_drop_total{direction=\"outbound\",protocol=\"http\",name=\"balance.endpoint\"} 0\n# HELP stack_poll_total Total number of stack polls\n# TYPE stack_poll_total counter\nstack_poll_total{direction=\"outbound\",protocol=\"tcp\",name=\"balancer\",ready=\"true\"} 0\nstack_poll_total{direction=\"outbound\",protocol=\"http\",name=\"logical\",ready=\"true\"} 0\nstack_poll_total{direction=\"outbound\",protocol=\"tcp\",name=\"logical\",ready=\"true\"} 0\nstack_poll_total{direction=\"inbound\",protocol=\"tcp\",name=\"gateway\",ready=\"true\"} 0\nstack_poll_total{direction=\"outbound\",protocol=\"tcp\",name=\"server\",ready=\"true\"} 1\nstack_poll_total{direction=\"outbound\",protocol=\"http\",name=\"balancer\",ready=\"true\"} 0\nstack_poll_total{direction=\"inbound\",protocol=\"http\",name=\"gateway\",ready=\"true\"} 0\nstack_poll_total{direction=\"inbound\",protocol=\"http\",name=\"logical\",ready=\"true\"} 0\nstack_poll_total{direction=\"outbound\",protocol=\"http\",name=\"balance.endpoint\",ready=\"true\"} 0\nstack_poll_total{direction=\"outbound\",protocol=\"tcp\",name=\"balancer\",ready=\"false\"} 0\nstack_poll_total{direction=\"outbound\",protocol=\"http\",name=\"logical\",ready=\"false\"} 0\nstack_poll_total{direction=\"outbound\",protocol=\"tcp\",name=\"logical\",ready=\"false\"} 0\nstack_poll_total{direction=\"inbound\",protocol=\"tcp\",name=\"gateway\",ready=\"false\"} 0\nstack_poll_total{direction=\"outbound\",protocol=\"tcp\",name=\"server\",ready=\"false\"} 1\nstack_poll_total{direction=\"outbound\",protocol=\"http\",name=\"balancer\",ready=\"false\"} 0\nstack_poll_total{direction=\"inbound\",protocol=\"http\",name=\"gateway\",ready=\"false\"} 0\nstack_poll_total{direction=\"inbound\",protocol=\"http\",name=\"logical\",ready=\"false\"} 0\nstack_poll_total{direction=\"outbound\",protocol=\"http\",name=\"balance.endpoint\",ready=\"false\"} 0\nstack_poll_total{direction=\"outbound\",protocol=\"tcp\",name=\"balancer\",ready=\"error\"} 0\nstack_poll_total{direction=\"outbound\",protocol=\"http\",name=\"logical\",ready=\"error\"} 0\nstack_poll_total{direction=\"outbound\",protocol=\"tcp\",name=\"logical\",ready=\"error\"} 0\nstack_poll_total{direction=\"inbound\",protocol=\"tcp\",name=\"gateway\",ready=\"error\"} 0\nstack_poll_total{direction=\"outbound\",protocol=\"tcp\",name=\"server\",ready=\"error\"} 0\nstack_poll_total{direction=\"outbound\",protocol=\"http\",name=\"balancer\",ready=\"error\"} 0\nstack_poll_total{direction=\"inbound\",protocol=\"http\",name=\"gateway\",ready=\"error\"} 0\nstack_poll_total{direction=\"inbound\",protocol=\"http\",name=\"logical\",ready=\"error\"} 0\nstack_poll_total{direction=\"outbound\",protocol=\"http\",name=\"balance.endpoint\",ready=\"error\"} 0\n# HELP stack_poll_total_ms Total number of milliseconds this service has spent awaiting readiness\n# TYPE stack_poll_total_ms counter\nstack_poll_total_ms{direction=\"outbound\",protocol=\"tcp\",name=\"balancer\"} 0\nstack_poll_total_ms{direction=\"outbound\",protocol=\"http\",name=\"logical\"} 0\nstack_poll_total_ms{direction=\"outbound\",protocol=\"tcp\",name=\"logical\"} 0\nstack_poll_total_ms{direction=\"inbound\",protocol=\"tcp\",name=\"gateway\"} 0\nstack_poll_total_ms{direction=\"outbound\",protocol=\"tcp\",name=\"server\"} 4\nstack_poll_total_ms{direction=\"outbound\",protocol=\"http\",name=\"balancer\"} 0\nstack_poll_total_ms{direction=\"inbound\",protocol=\"http\",name=\"gateway\"} 0\nstack_poll_total_ms{direction=\"inbound\",protocol=\"http\",name=\"logical\"} 0\nstack_poll_total_ms{direction=\"outbound\",protocol=\"http\",name=\"balance.endpoint\"} 0\n# HELP process_start_time_seconds Time that the process started (in seconds since the UNIX epoch)\n# TYPE process_start_time_seconds gauge\nprocess_start_time_seconds 1650967123\n# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.\n# TYPE process_cpu_seconds_total counter\nprocess_cpu_seconds_total 0.07\n# HELP process_virtual_memory_bytes Virtual memory size in bytes.\n# TYPE process_virtual_memory_bytes gauge\nprocess_virtual_memory_bytes 107995136\n# HELP process_resident_memory_bytes Resident memory size in bytes.\n# TYPE process_resident_memory_bytes gauge\nprocess_resident_memory_bytes 17805312\n# HELP process_open_fds Number of open file descriptors.\n# TYPE process_open_fds gauge\nprocess_open_fds 22\n# HELP process_max_fds Maximum number of open file descriptors.\n# TYPE process_max_fds gauge\nprocess_max_fds 1048576\n# HELP proxy_build_info Proxy build info\n# TYPE proxy_build_info gauge\nproxy_build_info{git_branch=\"HEAD\",git_sha=\"279b301\",git_version=\"279b301\",profile=\"release\",rust_version=\"rustc 1.59.0 (9d1b2106e 2022-02-23)\"} 1\n"
  },
  {
    "path": "testutil/stream.go",
    "content": "package testutil\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Stream provides the ability of read the output of an executing process while\n// it is still running\ntype Stream struct {\n\tcmd *exec.Cmd\n\tout io.ReadCloser\n}\n\n// Stop closes the stream and kills the process\nfunc (s *Stream) Stop() {\n\ts.out.Close()\n\ts.cmd.Process.Kill()\n}\n\n// ReadUntil reads from the process output until specified number of lines has\n// been reached, or until a timeout\nfunc (s *Stream) ReadUntil(lineCount int, timeout time.Duration) ([]string, error) {\n\toutput := make([]string, 0)\n\tlines := make(chan string)\n\ttimeoutAfter := time.NewTimer(timeout)\n\tdefer timeoutAfter.Stop()\n\tscanner := bufio.NewScanner(s.out)\n\tstopSignal := false\n\n\tgo func() {\n\t\tfor scanner.Scan() {\n\t\t\tlines <- scanner.Text()\n\n\t\t\tif stopSignal {\n\t\t\t\tclose(lines)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase <-timeoutAfter.C:\n\t\t\tstopSignal = true\n\t\t\treturn output, fmt.Errorf(\"cmd [%s] Timed out trying to read %d lines\", strings.Join(s.cmd.Args, \" \"), lineCount)\n\t\tcase line := <-lines:\n\t\t\toutput = append(output, line)\n\t\t\tif len(output) >= lineCount {\n\t\t\t\tstopSignal = true\n\t\t\t\treturn output, nil\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "testutil/tap.go",
    "content": "package testutil\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n)\n\n// TapEvent represents a tap event\ntype TapEvent struct {\n\tMethod     string\n\tAuthority  string\n\tPath       string\n\tHTTPStatus string\n\tGrpcStatus string\n\tTLS        string\n\tLineCount  int\n}\n\n// Tap executes a tap command and converts the command's streaming output into tap\n// events using each line's \"id\" field\nfunc Tap(target string, h *TestHelper, arg ...string) ([]*TapEvent, error) {\n\tcmd := append([]string{\"viz\", \"tap\", target}, arg...)\n\toutputStream, err := h.LinkerdRunStream(cmd...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer outputStream.Stop()\n\n\toutputLines, err := outputStream.ReadUntil(10, 1*time.Minute)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttapEventByID := make(map[string]*TapEvent)\n\tfor _, line := range outputLines {\n\t\tfields := toFieldMap(line)\n\t\tobj, ok := tapEventByID[fields[\"id\"]]\n\t\tif !ok {\n\t\t\tobj = &TapEvent{}\n\t\t\ttapEventByID[fields[\"id\"]] = obj\n\t\t}\n\t\tobj.LineCount++\n\t\tobj.TLS = fields[\"tls\"]\n\n\t\tswitch fields[\"type\"] {\n\t\tcase \"req\":\n\t\t\tobj.Method = fields[\":method\"]\n\t\t\tobj.Authority = fields[\":authority\"]\n\t\t\tobj.Path = fields[\":path\"]\n\t\tcase \"rsp\":\n\t\t\tobj.HTTPStatus = fields[\":status\"]\n\t\tcase \"end\":\n\t\t\tobj.GrpcStatus = fields[\"grpc-status\"]\n\t\t}\n\t}\n\n\toutput := make([]*TapEvent, 0)\n\tfor _, obj := range tapEventByID {\n\t\tif obj.LineCount == 3 { // filter out incomplete events\n\t\t\toutput = append(output, obj)\n\t\t}\n\t}\n\n\treturn output, nil\n}\n\nfunc toFieldMap(line string) map[string]string {\n\tfields := strings.Fields(line)\n\tfieldMap := map[string]string{\"type\": fields[0]}\n\tfor _, field := range fields[1:] {\n\t\tparts := strings.SplitN(field, \"=\", 2)\n\t\tfieldMap[parts[0]] = parts[1]\n\t}\n\treturn fieldMap\n}\n\n// ValidateExpected compares the received tap event with the expected tap event\nfunc ValidateExpected(events []*TapEvent, expectedEvent TapEvent) error {\n\tif len(events) == 0 {\n\t\treturn fmt.Errorf(\"Expected tap events, got nothing\")\n\t}\n\tfor _, event := range events {\n\t\tif *event != expectedEvent {\n\t\t\treturn fmt.Errorf(\"Unexpected tap event [%+v]; expected=[%+v]\", *event, expectedEvent)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "testutil/test_data_diff.go",
    "content": "package testutil\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"text/template\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/sergi/go-diff/diffmatchpatch\"\n\t\"gopkg.in/yaml.v2\"\n)\n\n// TestDataDiffer holds configuration for generating test diff\ntype TestDataDiffer struct {\n\tPrettyDiff     bool\n\tUpdateFixtures bool\n\tRejectPath     string\n}\n\n// DiffTestYAML compares a YAML structure to a fixture on the filestystem.\nfunc (td *TestDataDiffer) DiffTestYAML(path string, actualYAML string) error {\n\texpectedYAML := ReadTestdata(path)\n\treturn td.diffTestYAML(path, actualYAML, expectedYAML)\n}\n\n// DiffTestYAMLTemplate compares a YAML structure to a parameterized fixture on the filestystem.\nfunc (td *TestDataDiffer) DiffTestYAMLTemplate(path string, actualYAML string, params any) error {\n\tfile := filepath.Join(\"testdata\", path)\n\tt, err := template.New(path).ParseFiles(file)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to read YAML template from %s: %w\", path, err)\n\t}\n\tvar buf bytes.Buffer\n\terr = t.Execute(&buf, params)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to build YAML from template %s: %w\", path, err)\n\t}\n\treturn td.diffTestYAML(path, actualYAML, buf.String())\n}\n\nfunc (td *TestDataDiffer) diffTestYAML(path, actualYAML, expectedYAML string) error {\n\tactual, err := unmarshalYAML([]byte(actualYAML))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to unmarshal generated YAML: %w\", err)\n\t}\n\texpected, err := unmarshalYAML([]byte(expectedYAML))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to unmarshal expected YAML: %w\", err)\n\t}\n\tdiff := deep.Equal(expected, actual)\n\tif diff == nil {\n\t\treturn nil\n\t}\n\ttd.storeActual(path, []byte(actualYAML))\n\te := fmt.Sprintf(\"YAML mismatches %s:\", path)\n\tfor _, d := range diff {\n\t\te += fmt.Sprintf(\"\\n\t%s\", d)\n\t}\n\treturn errors.New(e)\n}\n\n// DiffTestdata generates the diff for actual w.r.the file in path\nfunc (td *TestDataDiffer) DiffTestdata(t *testing.T, path, actual string) {\n\tt.Helper()\n\texpected := ReadTestdata(path)\n\tif actual == expected {\n\t\treturn\n\t}\n\tdmp := diffmatchpatch.New()\n\tdiffs := dmp.DiffMain(expected, actual, true)\n\tdiffs = dmp.DiffCleanupSemantic(diffs)\n\tvar diff string\n\tif td.PrettyDiff {\n\t\tdiff = dmp.DiffPrettyText(diffs)\n\t} else {\n\t\tdiff = dmp.PatchToText(dmp.PatchMake(diffs))\n\t}\n\tt.Errorf(\"mismatch: %s\\n%s\", path, diff)\n\n\ttd.storeActual(path, []byte(actual))\n}\n\nfunc (td *TestDataDiffer) storeActual(path string, actual []byte) {\n\tif td.UpdateFixtures {\n\t\twriteTestdata(path, actual)\n\t}\n\n\tif td.RejectPath != \"\" {\n\t\twriteRejects(path, actual, td.RejectPath)\n\t}\n}\n\n// ReadTestdata reads a file and returns the contents of that file as a string.\nfunc ReadTestdata(fileName string) string {\n\tfile, err := os.Open(filepath.Join(\"testdata\", fileName))\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"Failed to open expected output file: %v\", err))\n\t}\n\n\tfixture, err := io.ReadAll(file)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"Failed to read expected output file: %v\", err))\n\t}\n\n\treturn string(fixture)\n}\n\nfunc unmarshalYAML(data []byte) ([]interface{}, error) {\n\tobjs := make([]interface{}, 0)\n\trd := bytes.NewReader(data)\n\tdecoder := yaml.NewDecoder(rd)\n\tfor {\n\t\tvar obj interface{}\n\t\tif err := decoder.Decode(&obj); err != nil {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\treturn objs, nil\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\n\t\tobjs = append(objs, obj)\n\t}\n}\n\nfunc writeTestdata(fileName string, data []byte) {\n\tp := filepath.Join(\"testdata\", fileName)\n\tif err := os.WriteFile(p, data, 0600); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc writeRejects(origFileName string, data []byte, rejectPath string) {\n\tp := filepath.Join(rejectPath, origFileName+\".rej\")\n\tif err := os.WriteFile(p, data, 0600); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "testutil/test_helper.go",
    "content": "package testutil\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tlog \"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\nconst GATEWAY_API_VERSION = \"v1.2.0\"\n\n// TestHelper provides helpers for running the linkerd integration tests.\ntype TestHelper struct {\n\tlinkerd                       string\n\tversion                       string\n\tnamespace                     string\n\tvizNamespace                  string\n\tupgradeFromVersion            string\n\tclusterDomain                 string\n\texternalIssuer                bool\n\texternalPrometheus            bool\n\tmulticluster                  bool\n\tmulticlusterSrcCtx            string\n\tmulticlusterTgtCtx            string\n\tmulticlusterManageControllers bool\n\tuninstall                     bool\n\tcni                           bool\n\tcalico                        bool\n\tdualStack                     bool\n\tnativeSidecar                 bool\n\tdefaultInboundPolicy          string\n\thttpClient                    http.Client\n\tKubernetesHelper\n\thelm\n\tinstalledExtensions []string\n}\n\ntype helm struct {\n\tpath                    string\n\tcharts                  string\n\tmulticlusterChart       string\n\tvizChart                string\n\tvizStableChart          string\n\treleaseName             string\n\tmulticlusterReleaseName string\n\tupgradeFromVersion      string\n}\n\n// DeploySpec is used to hold information about what deploys we should verify during testing\ntype DeploySpec struct {\n\tNamespace string\n\tReplicas  int\n}\n\n// Service is used to hold information about a Service we should verify during testing\ntype Service struct {\n\tNamespace string\n\tName      string\n}\n\n// LinkerdDeployReplicasEdge is a map containing the number of replicas for each Deployment and the main\n// container name in the current core installation\nvar LinkerdDeployReplicasEdge = map[string]DeploySpec{\n\t\"linkerd-destination\":    {\"linkerd\", 1},\n\t\"linkerd-identity\":       {\"linkerd\", 1},\n\t\"linkerd-proxy-injector\": {\"linkerd\", 1},\n}\n\n// LinkerdDeployReplicasStable is a map containing the number of replicas for\n// each Deployment and the main container name. Override whenever edge deviates\n// from stable.\nvar LinkerdDeployReplicasStable = LinkerdDeployReplicasEdge\n\n// LinkerdVizDeployReplicas is a map containing the number of replicas for\n// each Deployment and the main container name in the current linkerd-viz\n// installation\nvar LinkerdVizDeployReplicas = map[string]DeploySpec{\n\t\"prometheus\":   {\"linkerd-viz\", 1},\n\t\"metrics-api\":  {\"linkerd-viz\", 1},\n\t\"tap\":          {\"linkerd-viz\", 1},\n\t\"tap-injector\": {\"linkerd-viz\", 1},\n\t\"web\":          {\"linkerd-viz\", 1},\n}\n\n// MulticlusterDeployReplicas is a map containing the number of replicas for\n// each Deployment and the main container name for multicluster components\nvar MulticlusterDeployReplicas = map[string]DeploySpec{\n\t\"linkerd-gateway\": {\"linkerd-multicluster\", 1},\n}\n\n// ExternalVizDeployReplicas has an external prometheus instance that's in a\n// separate namespace\nvar ExternalVizDeployReplicas = map[string]DeploySpec{\n\t\"prometheus\":   {\"external-prometheus\", 1},\n\t\"metrics-api\":  {\"linkerd-viz\", 1},\n\t\"tap\":          {\"linkerd-viz\", 1},\n\t\"tap-injector\": {\"linkerd-viz\", 1},\n\t\"web\":          {\"linkerd-viz\", 1},\n}\n\n// SourceContextKey represents the key used to get the name of the Kubernetes\n// context corresponding to a source cluster in multicluster tests\nvar SourceContextKey = \"source\"\n\n// TargetContextKey represents the key used to get the name of the Kubernetes\n// context corresponding to a source cluster in multicluster tests\nvar TargetContextKey = \"target\"\n\n// NewGenericTestHelper returns a new *TestHelper from the options provided as function parameters.\n// This helper was created to be able to reuse this package without hard restrictions\n// as seen in `NewTestHelper()` which is primarily used with integration tests\n// See - https://github.com/linkerd/linkerd2/issues/4530\nfunc NewGenericTestHelper(\n\tlinkerd,\n\tversion,\n\tnamespace,\n\tvizNamespace,\n\tupgradeFromVersion,\n\tclusterDomain,\n\thelmPath,\n\thelmCharts,\n\thelmReleaseName,\n\thelmMulticlusterReleaseName,\n\thelmMulticlusterChart string,\n\texternalIssuer,\n\texternalPrometheus,\n\tmulticluster,\n\tcni,\n\tcalico,\n\tuninstall bool,\n\thttpClient http.Client,\n\tkubernetesHelper KubernetesHelper,\n) *TestHelper {\n\treturn &TestHelper{\n\t\tlinkerd:            linkerd,\n\t\tversion:            version,\n\t\tnamespace:          namespace,\n\t\tvizNamespace:       vizNamespace,\n\t\tupgradeFromVersion: upgradeFromVersion,\n\t\thelm: helm{\n\t\t\tpath:                    helmPath,\n\t\t\tcharts:                  helmCharts,\n\t\t\tmulticlusterChart:       helmMulticlusterChart,\n\t\t\tmulticlusterReleaseName: helmMulticlusterReleaseName,\n\t\t\treleaseName:             helmReleaseName,\n\t\t\tupgradeFromVersion:      upgradeFromVersion,\n\t\t},\n\t\tclusterDomain:      clusterDomain,\n\t\texternalIssuer:     externalIssuer,\n\t\texternalPrometheus: externalPrometheus,\n\t\tuninstall:          uninstall,\n\t\tcni:                cni,\n\t\tcalico:             calico,\n\t\thttpClient:         httpClient,\n\t\tmulticluster:       multicluster,\n\t\tKubernetesHelper:   kubernetesHelper,\n\t}\n}\n\n// NewTestHelper creates a new instance of TestHelper for the current test run.\n// The new TestHelper can be configured via command line flags.\nfunc NewTestHelper() *TestHelper {\n\texit := func(code int, msg string) {\n\t\tfmt.Fprintln(os.Stderr, msg)\n\t\tos.Exit(code)\n\t}\n\n\t// TODO (matei): clean-up flags\n\tk8sContext := flag.String(\"k8s-context\", \"\", \"kubernetes context associated with the test cluster\")\n\tlinkerdExec := flag.String(\"linkerd\", \"\", \"path to the linkerd binary to test\")\n\tnamespace := flag.String(\"linkerd-namespace\", \"linkerd\", \"the namespace where linkerd is installed\")\n\tvizNamespace := flag.String(\"viz-namespace\", \"linkerd-viz\", \"the namespace where linkerd viz extension is installed\")\n\tmulticluster := flag.Bool(\"multicluster\", false, \"when specified the multicluster install functionality is tested\")\n\tmulticlusterSourceCtx := flag.String(\"multicluster-source-context\", \"k3d-source\", \"the context belonging to source cluster in multicluster test\")\n\tmulticlusterTargetCtx := flag.String(\"multicluster-target-context\", \"k3d-target\", \"the context belonging to target cluster in multicluster test\")\n\tmulticlusterManageControllers := flag.Bool(\"multicluster-manage-controllers\", false, \"when specified the multicluster install will also install the service mirror controllers referred in the 'controllers` config\")\n\thelmPath := flag.String(\"helm-path\", \"target/helm\", \"path of the Helm binary\")\n\thelmCharts := flag.String(\"helm-charts\", \"charts/linkerd2\", \"path to linkerd2's Helm charts\")\n\tmulticlusterHelmChart := flag.String(\"multicluster-helm-chart\", \"charts/linkerd-multicluster\", \"path to linkerd2's multicluster Helm chart\")\n\tvizHelmChart := flag.String(\"viz-helm-chart\", \"charts/linkerd-viz\", \"path to linkerd2's viz extension Helm chart\")\n\tvizHelmStableChart := flag.String(\"viz-helm-stable-chart\", \"charts/linkerd-viz\", \"path to linkerd2's viz extension stable Helm chart\")\n\thelmReleaseName := flag.String(\"helm-release\", \"\", \"install linkerd via Helm using this release name\")\n\tmulticlusterHelmReleaseName := flag.String(\"multicluster-helm-release\", \"\", \"install linkerd multicluster via Helm using this release name\")\n\tupgradeFromVersion := flag.String(\"upgrade-from-version\", \"\", \"when specified, the upgrade test uses it as the base version of the upgrade\")\n\tclusterDomain := flag.String(\"cluster-domain\", \"cluster.local\", \"when specified, the install test uses a custom cluster domain\")\n\texternalIssuer := flag.Bool(\"external-issuer\", false, \"when specified, the install test uses it to install linkerd with --identity-external-issuer=true\")\n\texternalPrometheus := flag.Bool(\"external-prometheus\", false, \"when specified, the install test uses an external prometheus\")\n\trunTests := flag.Bool(\"integration-tests\", false, \"must be provided to run the integration tests\")\n\tverbose := flag.Bool(\"verbose\", false, \"turn on debug logging\")\n\tupgradeHelmFromVersion := flag.String(\"upgrade-helm-from-version\", \"\", \"Indicate a version of the Linkerd helm chart from which the helm installation is being upgraded\")\n\tuninstall := flag.Bool(\"uninstall\", false, \"whether to run the 'linkerd uninstall' integration test\")\n\tcni := flag.Bool(\"cni\", false, \"whether to install linkerd with CNI enabled\")\n\tcalico := flag.Bool(\"calico\", false, \"whether to install calico CNI plugin\")\n\tdualStack := flag.Bool(\"dual-stack\", false, \"whether to run the dual-stack tests\")\n\tnativeSidecar := flag.Bool(\"native-sidecar\", false, \"whether to install using native sidecar injection\")\n\tdefaultInboundPolicy := flag.String(\"default-inbound-policy\", \"\", \"if non-empty, passed to --set proxy.defaultInboundPolicy at linkerd's install time\")\n\tflag.Parse()\n\n\tif !*runTests {\n\t\texit(0, \"integration tests not enabled: enable with -integration-tests\")\n\t}\n\n\tif *linkerdExec == \"\" {\n\t\texit(1, \"-linkerd flag is required\")\n\t}\n\n\tlinkerd, err := filepath.Abs(*linkerdExec)\n\tif err != nil {\n\t\texit(1, fmt.Sprintf(\"abs: %s\", err))\n\t}\n\n\tif *verbose {\n\t\tlog.SetLevel(log.DebugLevel)\n\t} else {\n\t\tlog.SetLevel(log.PanicLevel)\n\t}\n\n\ttestHelper := &TestHelper{\n\t\tlinkerd:                       linkerd,\n\t\tnamespace:                     *namespace,\n\t\tvizNamespace:                  *vizNamespace,\n\t\tupgradeFromVersion:            *upgradeFromVersion,\n\t\tmulticluster:                  *multicluster,\n\t\tmulticlusterSrcCtx:            *multiclusterSourceCtx,\n\t\tmulticlusterTgtCtx:            *multiclusterTargetCtx,\n\t\tmulticlusterManageControllers: *multiclusterManageControllers,\n\t\thelm: helm{\n\t\t\tpath:                    *helmPath,\n\t\t\tcharts:                  *helmCharts,\n\t\t\tmulticlusterChart:       *multiclusterHelmChart,\n\t\t\tvizChart:                *vizHelmChart,\n\t\t\tvizStableChart:          *vizHelmStableChart,\n\t\t\treleaseName:             *helmReleaseName,\n\t\t\tmulticlusterReleaseName: *multiclusterHelmReleaseName,\n\t\t\tupgradeFromVersion:      *upgradeHelmFromVersion,\n\t\t},\n\t\tclusterDomain:        *clusterDomain,\n\t\texternalIssuer:       *externalIssuer,\n\t\texternalPrometheus:   *externalPrometheus,\n\t\tcni:                  *cni,\n\t\tcalico:               *calico,\n\t\tdualStack:            *dualStack,\n\t\tnativeSidecar:        *nativeSidecar,\n\t\tuninstall:            *uninstall,\n\t\tdefaultInboundPolicy: *defaultInboundPolicy,\n\t}\n\n\tversion, err := testHelper.LinkerdRun(\"version\", \"--client\", \"--short\")\n\tif err != nil {\n\t\texit(1, fmt.Sprintf(\"error getting linkerd version: %s\", err.Error()))\n\t}\n\ttestHelper.version = strings.TrimSpace(version)\n\n\tkubernetesHelper, err := NewKubernetesHelper(*k8sContext, RetryFor)\n\tif err != nil {\n\t\texit(1, fmt.Sprintf(\"error creating kubernetes helper: %s\", err.Error()))\n\t}\n\ttestHelper.KubernetesHelper = *kubernetesHelper\n\n\ttestHelper.httpClient = http.Client{\n\t\tTimeout: 10 * time.Second,\n\t}\n\n\treturn testHelper\n}\n\n// GetVersion returns the version of linkerd to test. This version corresponds\n// to the client version of the linkerd binary provided via the -linkerd command\n// line flag.\nfunc (h *TestHelper) GetVersion() string {\n\treturn h.version\n}\n\n// GetLinkerdNamespace returns the namespace where linkerd is installed. Set the\n// namespace using the -linkerd-namespace command line flag.\nfunc (h *TestHelper) GetLinkerdNamespace() string {\n\treturn h.namespace\n}\n\n// GetVizNamespace returns the namespace where linkerd Viz Extension is installed. Set the\n// namespace using the -linkerd-namespace command line flag.\nfunc (h *TestHelper) GetVizNamespace() string {\n\treturn h.vizNamespace\n}\n\n// GetMulticlusterNamespace returns the namespace where multicluster\n// components are installed.\nfunc (h *TestHelper) GetMulticlusterNamespace() string {\n\treturn fmt.Sprintf(\"%s-multicluster\", h.GetLinkerdNamespace())\n}\n\n// GetMulticlusterContexts returns a map with the context names for the clusters\n// used in the test\nfunc (h *TestHelper) GetMulticlusterContexts() map[string]string {\n\treturn map[string]string{\n\t\t\"source\": h.multiclusterSrcCtx,\n\t\t\"target\": h.multiclusterTgtCtx,\n\t}\n}\n\n// GetTestNamespace returns the namespace for the given test. The test namespace\n// is prefixed with the linkerd namespace.\nfunc (h *TestHelper) GetTestNamespace(testName string) string {\n\treturn h.namespace + \"-\" + testName\n}\n\n// GetHelmReleaseName returns the name of the Linkerd installation Helm release\nfunc (h *TestHelper) GetHelmReleaseName() string {\n\treturn h.helm.releaseName\n}\n\n// GetMulticlusterHelmReleaseName returns the name of the Linkerd multicluster installation Helm release\nfunc (h *TestHelper) GetMulticlusterHelmReleaseName() string {\n\treturn h.helm.multiclusterReleaseName\n}\n\n// GetHelmCharts returns the path to the Linkerd Helm chart\nfunc (h *TestHelper) GetHelmCharts() string {\n\treturn h.helm.charts\n}\n\n// GetMulticlusterHelmChart returns the path to the Linkerd multicluster Helm chart\nfunc (h *TestHelper) GetMulticlusterHelmChart() string {\n\treturn h.helm.multiclusterChart\n}\n\nfunc (h *TestHelper) GetMulticlusterManageControllers() bool {\n\treturn h.multiclusterManageControllers\n}\n\n// GetLinkerdVizHelmChart returns the path to the Linkerd viz Helm chart\nfunc (h *TestHelper) GetLinkerdVizHelmChart() string {\n\treturn h.helm.vizChart\n}\n\n// GetLinkerdVizHelmStableChart returns the path to the Linkerd viz Helm\n// stable chart\nfunc (h *TestHelper) GetLinkerdVizHelmStableChart() string {\n\treturn h.helm.vizStableChart\n}\n\n// UpgradeHelmFromVersion returns the version from which Linkerd should be upgraded with Helm\nfunc (h *TestHelper) UpgradeHelmFromVersion() string {\n\treturn h.helm.upgradeFromVersion\n}\n\n// ExternalIssuer determines whether linkerd should be installed with --identity-external-issuer\nfunc (h *TestHelper) ExternalIssuer() bool {\n\treturn h.externalIssuer\n}\n\n// ExternalPrometheus determines whether linkerd should be installed with --set prometheusUrl\nfunc (h *TestHelper) ExternalPrometheus() bool {\n\treturn h.externalPrometheus\n}\n\n// Multicluster determines whether multicluster components should be installed\nfunc (h *TestHelper) Multicluster() bool {\n\treturn h.multicluster\n}\n\n// Uninstall determines whether the \"linkerd uninstall\" integration test should be run\nfunc (h *TestHelper) Uninstall() bool {\n\treturn h.uninstall\n}\n\n// DefaultInboundPolicy returns the override value for proxy.defaultInboundPolicy\nfunc (h *TestHelper) DefaultInboundPolicy() string {\n\treturn h.defaultInboundPolicy\n}\n\n// UpgradeFromVersion returns the base version of the upgrade test.\nfunc (h *TestHelper) UpgradeFromVersion() string {\n\treturn h.upgradeFromVersion\n}\n\n// GetClusterDomain returns the custom cluster domain that needs to be used during linkerd installation\nfunc (h *TestHelper) GetClusterDomain() string {\n\treturn h.clusterDomain\n}\n\n// CNI determines whether CNI should be enabled\nfunc (h *TestHelper) CNI() bool {\n\treturn h.cni\n}\n\n// Calico determines whether Calico CNI plug-in is enabled\nfunc (h *TestHelper) Calico() bool {\n\treturn h.calico\n}\n\n// DualStack determines whether the DualStack tests are run\nfunc (h *TestHelper) DualStack() bool {\n\treturn h.dualStack\n}\n\n// NativeSidecar determines whether native sidecar injection is enabled\nfunc (h *TestHelper) NativeSidecar() bool {\n\treturn h.nativeSidecar\n}\n\n// AddInstalledExtension adds an extension name to installedExtensions to\n// track the currently installed linkerd extensions.\nfunc (h *TestHelper) AddInstalledExtension(extensionName string) {\n\th.installedExtensions = append(h.installedExtensions, extensionName)\n}\n\n// GetInstalledExtensions gets a list currently installed extensions\n// in a test run.\nfunc (h *TestHelper) GetInstalledExtensions() []string {\n\treturn h.installedExtensions\n}\n\n// CreateTLSSecret creates a TLS Kubernetes secret\nfunc (h *TestHelper) CreateTLSSecret(name, root, cert, key string) error {\n\tsecret := fmt.Sprintf(`\napiVersion: v1\ndata:\n    ca.crt: %s\n    tls.crt: %s\n    tls.key: %s\nkind: Secret\nmetadata:\n    name: %s\ntype: kubernetes.io/tls`, base64.StdEncoding.EncodeToString([]byte(root)), base64.StdEncoding.EncodeToString([]byte(cert)), base64.StdEncoding.EncodeToString([]byte(key)), name)\n\n\t_, err := h.KubectlApply(secret, h.GetLinkerdNamespace())\n\treturn err\n}\n\n// CmdRun executes an arbitrary command by calling the binary and return its\n// output\nfunc (h *TestHelper) CmdRun(cmd string, arg ...string) (string, error) {\n\tout, stderr, err := combinedOutput(\"\", cmd, arg...)\n\tif err != nil {\n\t\treturn out, fmt.Errorf(\"command failed: '%s %s'\\n%w\\n%s\", cmd, strings.Join(arg, \" \"), err, stderr)\n\t}\n\treturn out, nil\n}\n\n// LinkerdRun executes a linkerd command returning its stdout.\nfunc (h *TestHelper) LinkerdRun(arg ...string) (string, error) {\n\tout, stderr, err := h.PipeToLinkerdRun(\"\", arg...)\n\tif err != nil {\n\t\treturn out, fmt.Errorf(\"command failed: linkerd %s\\n%w\\n%s\", strings.Join(arg, \" \"), err, stderr)\n\t}\n\treturn out, nil\n}\n\n// PipeToLinkerdRun executes a linkerd command appended with the\n// --linkerd-namespace flag, and provides a string at Stdin.\nfunc (h *TestHelper) PipeToLinkerdRun(stdin string, arg ...string) (string, string, error) {\n\twithParams := append([]string{\"--linkerd-namespace\", h.namespace, \"--context=\" + h.k8sContext}, arg...)\n\treturn combinedOutput(stdin, h.linkerd, withParams...)\n}\n\n// HelmRun executes a helm command appended with the --context\nfunc (h *TestHelper) HelmRun(arg ...string) (string, string, error) {\n\treturn h.PipeToHelmRun(\"\", arg...)\n}\n\n// PipeToHelmRun executes a Helm command appended with the\n// --context flag, and provides a string at Stdin.\nfunc (h *TestHelper) PipeToHelmRun(stdin string, arg ...string) (string, string, error) {\n\twithParams := append([]string{\"--kube-context=\" + h.k8sContext}, arg...)\n\treturn combinedOutput(stdin, h.helm.path, withParams...)\n}\n\n// LinkerdRunStream initiates a linkerd command appended with the\n// --linkerd-namespace flag, and returns a Stream that can be used to read the\n// command's output while it is still executing.\nfunc (h *TestHelper) LinkerdRunStream(arg ...string) (*Stream, error) {\n\twithParams := append([]string{\"--linkerd-namespace\", h.namespace, \"--context=\" + h.k8sContext}, arg...)\n\tcmd := exec.Command(h.linkerd, withParams...)\n\n\tcmdReader, err := cmd.StdoutPipe()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = cmd.Start()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttime.Sleep(500 * time.Millisecond)\n\n\tif cmd.ProcessState != nil && cmd.ProcessState.Exited() {\n\t\treturn nil, fmt.Errorf(\"Process exited: %s\", cmd.ProcessState)\n\t}\n\n\treturn &Stream{cmd: cmd, out: cmdReader}, nil\n}\n\n// KubectlStream initiates a kubectl command appended with the\n// --namespace flag, and returns a Stream that can be used to read the\n// command's output while it is still executing.\nfunc (h *TestHelper) KubectlStream(arg ...string) (*Stream, error) {\n\n\twithContext := append([]string{\"--namespace\", h.namespace, \"--context=\" + h.k8sContext}, arg...)\n\tcmd := exec.Command(\"kubectl\", withContext...)\n\n\tcmdReader, err := cmd.StdoutPipe()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = cmd.Start()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttime.Sleep(500 * time.Millisecond)\n\n\tif cmd.ProcessState != nil && cmd.ProcessState.Exited() {\n\t\treturn nil, fmt.Errorf(\"Process exited: %s\", cmd.ProcessState)\n\t}\n\n\treturn &Stream{cmd: cmd, out: cmdReader}, nil\n}\n\n// HelmUpgrade runs the helm upgrade subcommand, with the provided arguments\nfunc (h *TestHelper) HelmUpgrade(chart, releaseName string, arg ...string) (string, string, error) {\n\twithParams := append([]string{\n\t\t\"upgrade\",\n\t\treleaseName,\n\t\t\"--kube-context\", h.k8sContext,\n\t\t\"--namespace\", h.namespace,\n\t\t\"--timeout\", \"5m\",\n\t\t\"--wait\",\n\t\tchart,\n\t}, arg...)\n\treturn combinedOutput(\"\", h.helm.path, withParams...)\n}\n\n// HelmInstall runs the helm install subcommand, with the provided arguments\nfunc (h *TestHelper) HelmInstall(chart, releaseName string, arg ...string) (string, string, error) {\n\twithParams := append([]string{\n\t\t\"install\",\n\t\treleaseName,\n\t\tchart,\n\t\t\"--kube-context\", h.k8sContext,\n\t\t\"--namespace\", h.namespace,\n\t\t\"--create-namespace\",\n\t\t\"--timeout\", \"5m\",\n\t\t\"--wait\",\n\t}, arg...)\n\treturn combinedOutput(\"\", h.helm.path, withParams...)\n}\n\n// HelmCmdPlain runs a helm subcommand, with the provided arguments and no defaults\nfunc (h *TestHelper) HelmCmdPlain(cmd, chart, releaseName string, arg ...string) (string, string, error) {\n\twithParams := append([]string{\n\t\tcmd,\n\t\treleaseName,\n\t\tchart,\n\t\t\"--kube-context\", h.k8sContext,\n\t}, arg...)\n\n\treturn combinedOutput(\"\", h.helm.path, withParams...)\n}\n\n// HelmInstallMulticluster runs the helm install subcommand for multicluster, with the provided arguments\nfunc (h *TestHelper) HelmInstallMulticluster(chart string, arg ...string) (string, string, error) {\n\twithParams := append([]string{\n\t\t\"install\",\n\t\th.helm.multiclusterReleaseName,\n\t\tchart,\n\t\t\"--kube-context\", h.k8sContext,\n\t\t\"--namespace\", h.GetMulticlusterNamespace(),\n\t\t\"--create-namespace\",\n\t\t\"--set\", \"linkerdNamespace=\" + h.GetLinkerdNamespace(),\n\t}, arg...)\n\treturn combinedOutput(\"\", h.helm.path, withParams...)\n}\n\n// HelmUninstallMulticluster runs the helm delete subcommand for multicluster\nfunc (h *TestHelper) HelmUninstallMulticluster(chart string) (string, string, error) {\n\twithParams := []string{\n\t\t\"delete\",\n\t\th.helm.multiclusterReleaseName,\n\t\t\"--kube-context\", h.k8sContext,\n\t}\n\treturn combinedOutput(\"\", h.helm.path, withParams...)\n}\n\n// ValidateOutput validates a string against the contents of a file in the\n// test's testdata directory.\nfunc (h *TestHelper) ValidateOutput(out, fixtureFile string) error {\n\texpected, err := ReadFile(\"testdata/\" + fixtureFile)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif out != expected {\n\t\treturn fmt.Errorf(\n\t\t\t\"Expected:\\n%s\\nActual:\\n%s\", expected, out)\n\t}\n\n\treturn nil\n}\n\n// CheckVersion validates the output of the \"linkerd version\" command.\nfunc (h *TestHelper) CheckVersion(serverVersion string) error {\n\tout, err := h.LinkerdRun(\"version\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !strings.Contains(out, fmt.Sprintf(\"Client version: %s\", h.version)) {\n\t\treturn fmt.Errorf(\"Expected client version [%s], got:\\n%s\", h.version, out)\n\t}\n\tif !strings.Contains(out, fmt.Sprintf(\"Server version: %s\", serverVersion)) {\n\t\treturn fmt.Errorf(\"Expected server version [%s], got:\\n%s\", serverVersion, out)\n\t}\n\treturn nil\n}\n\n// RetryFor retries a given function every second until the function returns\n// without an error, or a timeout is reached. If the timeout is reached, it\n// returns the last error received from the function.\nfunc RetryFor(timeout time.Duration, fn func() error) error {\n\terr := fn()\n\tif err == nil {\n\t\treturn nil\n\t}\n\n\ttimeoutAfter := time.NewTimer(timeout)\n\tdefer timeoutAfter.Stop()\n\tretryAfter := time.NewTicker(time.Second)\n\tdefer retryAfter.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-timeoutAfter.C:\n\t\t\treturn err\n\t\tcase <-retryAfter.C:\n\t\t\terr = fn()\n\t\t\tif err == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n}\n\n// HTTPGetURL sends a GET request to the given URL. It returns the response body\n// in the event of a successful 200 response. In the event of a non-200\n// response, it returns an error. It retries requests for up to 30 seconds,\n// giving pods time to start.\nfunc (h *TestHelper) HTTPGetURL(url string) (string, error) {\n\tvar body string\n\terr := RetryFor(time.Minute, func() error {\n\t\tresp, err := h.httpClient.Get(url)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tdefer resp.Body.Close()\n\t\tbytes, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Error reading response body: %w\", err)\n\t\t}\n\t\tbody = string(bytes)\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn fmt.Errorf(\"GET request to %s returned status code %d with body %q\", url, resp.StatusCode, body)\n\t\t}\n\n\t\treturn nil\n\t})\n\n\treturn body, err\n}\n\n// WithDataPlaneNamespace is used to create a test namespace that is deleted before the function returns\nfunc (h *TestHelper) WithDataPlaneNamespace(ctx context.Context, testName string, annotations map[string]string, t *testing.T, test func(t *testing.T, ns string)) {\n\tprefixedNs := h.GetTestNamespace(testName)\n\tif err := h.CreateDataPlaneNamespaceIfNotExists(ctx, prefixedNs, annotations); err != nil {\n\t\tAnnotatedFatalf(t, fmt.Sprintf(\"failed to create %s namespace\", prefixedNs),\n\t\t\t\"failed to create %s namespace: %s\", prefixedNs, err)\n\t}\n\ttest(t, prefixedNs)\n\tif err := h.DeleteNamespaceIfExists(ctx, prefixedNs); err != nil {\n\t\tAnnotatedFatalf(t, fmt.Sprintf(\"failed to delete %s namespace\", prefixedNs),\n\t\t\t\"failed to delete %s namespace: %s\", prefixedNs, err)\n\t}\n}\n\n// GetReleaseChannelVersions is used to fetch the latest versions for Linkerd's\n// release channels: edge and stable\nfunc (h *TestHelper) GetReleaseChannelVersions() (map[string]string, error) {\n\turl := \"https://versioncheck.linkerd.io/version.json\"\n\tresp, err := h.httpClient.Get(url)\n\tif err != nil {\n\t\treturn map[string]string{}, err\n\t}\n\tdefer resp.Body.Close()\n\n\tvar versions map[string]string\n\tif err := json.NewDecoder(resp.Body).Decode(&versions); err != nil {\n\t\treturn map[string]string{}, err\n\t}\n\n\treturn versions, nil\n}\n\n// DownloadCLIBinary is used to download the Linkerd CLI from GitHub Releases\n// page. The method takes the version to download and a filepath where to save\n// the binary.\nfunc (h *TestHelper) DownloadCLIBinary(filepath, version string) error {\n\turl := fmt.Sprintf(\"https://github.com/linkerd/linkerd2/releases/download/%[1]s/linkerd2-cli-%[1]s-%s-%s\", version, runtime.GOOS, runtime.GOARCH)\n\tresp, err := h.httpClient.Get(url)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\n\t// Create if it doesn't already exist\n\t// The CLI binary needs to be executable so we ignore lint errors about\n\t// file permissions.\n\t//nolint:gosec\n\tout, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE, 0500)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer out.Close()\n\n\t_, err = io.Copy(out, resp.Body)\n\treturn err\n}\n\nfunc (h *TestHelper) InstallGatewayAPI() error {\n\turl := fmt.Sprintf(\"https://github.com/kubernetes-sigs/gateway-api/releases/download/%s/experimental-install.yaml\", GATEWAY_API_VERSION)\n\t_, err := h.Kubectl(\"\", \"apply\", \"-f\", url)\n\treturn err\n}\n\n// ReadFile reads a file from disk and returns the contents as a string.\nfunc ReadFile(file string) (string, error) {\n\tb, err := os.ReadFile(file)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(b), nil\n}\n\n// combinedOutput executes a shell command and returns the output.\nfunc combinedOutput(stdin string, name string, arg ...string) (string, string, error) {\n\tcommand := exec.Command(name, arg...)\n\tcommand.Stdin = strings.NewReader(stdin)\n\tvar stderr bytes.Buffer\n\tcommand.Stderr = &stderr\n\n\tstdout, err := command.Output()\n\treturn string(stdout), stderr.String(), err\n}\n\n// RowStat is used to store the contents for a single row from the stat command\ntype RowStat struct {\n\tName               string\n\tStatus             string\n\tMeshed             string\n\tSuccess            string\n\tRps                string\n\tP50Latency         string\n\tP95Latency         string\n\tP99Latency         string\n\tTCPOpenConnections string\n\tUnauthorizedRPS    string\n}\n\n// CheckRowCount checks that expectedRowCount rows have been returned\nfunc CheckRowCount(out string, expectedRowCount int) ([]string, error) {\n\trows := strings.Split(strings.TrimSuffix(out, \"\\n\"), \"\\n\")\n\tif len(rows) < 2 {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"Expected at least 2 lines in %q\",\n\t\t\tout,\n\t\t)\n\t}\n\trows = rows[1:] // strip header\n\tif len(rows) != expectedRowCount {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"Expected %d rows in stat output but got %d in %q\",\n\t\t\texpectedRowCount, len(rows), out)\n\t}\n\n\treturn rows, nil\n}\n\n// ParseRows parses the output of linkerd stat into a map of resource names to RowStat objects\nfunc ParseRows(out string, expectedRowCount, expectedColumnCount int) (map[string]*RowStat, error) {\n\trows, err := CheckRowCount(out, expectedRowCount)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trowStats := make(map[string]*RowStat)\n\tfor _, row := range rows {\n\t\tfields := strings.Fields(row)\n\n\t\tif expectedColumnCount == 0 {\n\t\t\texpectedColumnCount = 8\n\t\t}\n\t\tif len(fields) != expectedColumnCount {\n\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\"Expected %d columns in stat output but got %d in %q\",\n\t\t\t\texpectedColumnCount, len(fields), row)\n\t\t}\n\n\t\trowStats[fields[0]] = &RowStat{\n\t\t\tName: fields[0],\n\t\t}\n\n\t\ti := 0\n\t\tif expectedColumnCount == 9 {\n\t\t\trowStats[fields[0]].Status = fields[1]\n\t\t\ti = 1\n\t\t}\n\t\trowStats[fields[0]].Meshed = fields[1+i]\n\t\trowStats[fields[0]].Success = fields[2+i]\n\t\trowStats[fields[0]].Rps = fields[3+i]\n\t\trowStats[fields[0]].P50Latency = fields[4+i]\n\t\trowStats[fields[0]].P95Latency = fields[5+i]\n\t\trowStats[fields[0]].P99Latency = fields[6+i]\n\n\t\tif 7+i < len(fields) {\n\t\t\trowStats[fields[0]].TCPOpenConnections = fields[7+i]\n\t\t}\n\t}\n\n\treturn rowStats, nil\n}\n\n// ParseEvents parses the output of kubectl events\nfunc ParseEvents(out string) ([]*corev1.Event, error) {\n\tvar list corev1.List\n\tif err := json.Unmarshal([]byte(out), &list); err != nil {\n\t\treturn nil, fmt.Errorf(\"error unmarshaling list from `kubectl get events`: %w\", err)\n\t}\n\n\tif len(list.Items) == 0 {\n\t\treturn nil, errors.New(\"no events found\")\n\t}\n\n\tvar events []*corev1.Event\n\tfor _, i := range list.Items {\n\t\tvar e corev1.Event\n\t\tif err := json.Unmarshal(i.Raw, &e); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error unmarshaling list event from `kubectl get events`: %w\", err)\n\t\t}\n\t\tevents = append(events, &e)\n\t}\n\n\treturn events, nil\n}\n"
  },
  {
    "path": "testutil/test_helper_check.go",
    "content": "package testutil\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\tvizHealthcheck \"github.com/linkerd/linkerd2/viz/pkg/healthcheck\"\n)\n\nvar preCategories = []healthcheck.CategoryID{\n\thealthcheck.KubernetesAPIChecks,\n\thealthcheck.KubernetesVersionChecks,\n\thealthcheck.LinkerdPreInstallChecks,\n\thealthcheck.LinkerdVersionChecks,\n}\n\nvar coreCategories = []healthcheck.CategoryID{\n\thealthcheck.KubernetesAPIChecks,\n\thealthcheck.KubernetesVersionChecks,\n\thealthcheck.LinkerdControlPlaneExistenceChecks,\n\thealthcheck.LinkerdConfigChecks,\n\thealthcheck.LinkerdIdentity,\n\thealthcheck.LinkerdWebhooksAndAPISvcTLS,\n\thealthcheck.LinkerdVersionChecks,\n\thealthcheck.LinkerdControlPlaneProxyChecks,\n}\n\nvar dataPlaneCategories = []healthcheck.CategoryID{\n\thealthcheck.LinkerdIdentityDataPlane,\n\thealthcheck.LinkerdControlPlaneProxyChecks,\n\thealthcheck.LinkerdDataPlaneChecks,\n}\n\n// TestCheckPre runs validates the output of `linkerd check --pre`\nfunc (h *TestHelper) TestCheckPre() error {\n\tcmd := []string{\"check\", \"--pre\", \"--output\", \"json\", \"--wait\", \"5m\"}\n\treturn h.testCheck(cmd, preCategories)\n}\n\n// TestCheck runs validates the output of `linkerd check`\nfunc (h *TestHelper) TestCheck(extraArgs ...string) error {\n\treturn h.TestCheckWith([]healthcheck.CategoryID{healthcheck.LinkerdControlPlaneVersionChecks, vizHealthcheck.LinkerdVizExtensionCheck}, extraArgs...)\n}\n\n// TestCheckWith validates the output of `linkerd check`. It will validate the\n// core categories and any additional categories that the caller provides.\nfunc (h *TestHelper) TestCheckWith(additional []healthcheck.CategoryID, extraArgs ...string) error {\n\tcmd := []string{\"check\", \"--output\", \"json\", \"--wait\", \"5m\"}\n\tcmd = append(cmd, extraArgs...)\n\tcategories := append(coreCategories, additional...)\n\treturn h.testCheck(cmd, categories)\n}\n\n// TestCheckProxy runs validates the output of `linkerd check --proxy`\nfunc (h *TestHelper) TestCheckProxy(expectedVersion, namespace string) error {\n\tcmd := []string{\"check\", \"--proxy\", \"--expected-version\", expectedVersion,\n\t\t\"--namespace\", namespace, \"--output\", \"json\", \"--wait\", \"5m\"}\n\tcategories := append(coreCategories, vizHealthcheck.LinkerdVizExtensionCheck,\n\t\tvizHealthcheck.LinkerdVizExtensionDataPlaneCheck)\n\tcategories = append(categories, dataPlaneCategories...)\n\treturn h.testCheck(cmd, categories)\n}\n\nfunc (h *TestHelper) testCheck(cmd []string, categories []healthcheck.CategoryID) error {\n\ttimeout := time.Minute * 10\n\treturn RetryFor(timeout, func() error {\n\t\tres, err := h.LinkerdRun(cmd...)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"'linkerd check' command failed\\n%w\\n%s\", err, res)\n\t\t}\n\n\t\treturnedCats := map[healthcheck.CategoryID]struct{}{}\n\n\t\t// We can't just use json.Unmarshal() because the check output is formatted as NDJSON\n\t\td := json.NewDecoder(strings.NewReader(res))\n\t\tfor {\n\t\t\tvar out healthcheck.CheckOutput\n\t\t\terr := d.Decode(&out)\n\t\t\tif err != nil {\n\t\t\t\t// io.EOF is expected at end of stream.\n\t\t\t\tif !errors.Is(err, io.EOF) {\n\t\t\t\t\treturn fmt.Errorf(\"error processing 'linkerd check' output: %w\", err)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\terrs := []string{}\n\t\t\tfor _, cat := range out.Categories {\n\t\t\t\tfor _, check := range cat.Checks {\n\t\t\t\t\treturnedCats[cat.Name] = struct{}{}\n\t\t\t\t\tif check.Result == healthcheck.CheckErr {\n\t\t\t\t\t\terrs = append(errs, fmt.Sprintf(\"%s: %s\", cat.Name, check.Error))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif len(errs) > 0 {\n\t\t\t\treturn errors.New(strings.Join(errs, \"\\n\"))\n\t\t\t}\n\t\t}\n\n\t\terrs := []string{}\n\t\tfor _, cat := range categories {\n\t\t\tif _, ok := returnedCats[cat]; !ok {\n\t\t\t\terrs = append(errs, fmt.Sprintf(\"missing category '%s'\", cat))\n\t\t\t}\n\t\t}\n\t\tif len(errs) > 0 {\n\t\t\treturn errors.New(strings.Join(errs, \"\\n\"))\n\t\t}\n\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "tools.go",
    "content": "//go:build tools\n// +build tools\n\npackage tools\n\nimport (\n\t_ \"github.com/shurcooL/vfsgen\"\n\t_ \"golang.org/x/tools/cmd/goimports\"\n\t_ \"google.golang.org/grpc/cmd/protoc-gen-go-grpc\"\n\t_ \"k8s.io/code-generator\"\n)\n"
  },
  {
    "path": "viz/charts/linkerd-viz/.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\nOWNERS\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*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n"
  },
  {
    "path": "viz/charts/linkerd-viz/Chart.yaml",
    "content": "apiVersion: \"v1\"\n# this version will be updated by the CI before publishing the Helm tarball\nappVersion: edge-XX.X.X\ndescription: |\n  The Linkerd-Viz extension contains observability and visualization\n  components for Linkerd.\nhome: https://linkerd.io\nkeywords:\n  - service-mesh\nkubeVersion: \">=1.23.0-0\"\nname: \"linkerd-viz\"\nsources:\n  - https://github.com/linkerd/linkerd2/\n# this version will be updated by the CI before publishing the Helm tarball\nversion: 0.0.0-undefined\nicon: https://linkerd.io/images/logo-only-200h.png\nmaintainers:\n  - name: Linkerd authors\n    email: cncf-linkerd-dev@lists.cncf.io\n    url: https://linkerd.io/\n"
  },
  {
    "path": "viz/charts/linkerd-viz/README.md.gotmpl",
    "content": "{{ template \"chart.header\" . }}\n{{ template \"chart.description\" . }}\n\n{{ template \"chart.versionBadge\" . }}\n{{ template \"chart.typeBadge\" . }}\n{{ template \"chart.appVersionBadge\" . }}\n\n{{ template \"chart.homepageLine\" . }}\n\n## Quickstart and documentation\n\nYou can run Linkerd on any Kubernetes cluster in a matter of seconds. See the\n[Linkerd Getting Started Guide][getting-started] for how.\n\nFor more comprehensive documentation, start with the [Linkerd\ndocs][linkerd-docs].\n\n## Prerequisite: Linkerd Core Control-Plane\n\nBefore installing the Linkerd Viz extension, The core control-plane has to\nbe installed first by following the [Linkerd Install\nGuide](https://linkerd.io/2/tasks/install/).\n\n## Adding Linkerd's Helm repository\n\n```bash\n# To add the repo for Linkerd edge releases:\nhelm repo add linkerd https://helm.linkerd.io/edge\n```\n\n## Installing the Viz Extension Chart\n\n```bash\nhelm install linkerd-viz -n linkerd-viz --create-namespace linkerd/linkerd-viz\n```\n\n## Get involved\n\n* Check out Linkerd's source code at [GitHub][linkerd2].\n* Join Linkerd's [user mailing list][linkerd-users], [developer mailing\n  list][linkerd-dev], and [announcements mailing list][linkerd-announce].\n* Follow [@linkerd][twitter] on Twitter.\n* Join the [Linkerd Slack][slack].\n\n[getting-started]: https://linkerd.io/2/getting-started/\n[linkerd2]: https://github.com/linkerd/linkerd2\n[linkerd-announce]: https://lists.cncf.io/g/cncf-linkerd-announce\n[linkerd-dev]: https://lists.cncf.io/g/cncf-linkerd-dev\n[linkerd-docs]: https://linkerd.io/2/overview/\n[linkerd-users]: https://lists.cncf.io/g/cncf-linkerd-users\n[slack]: http://slack.linkerd.io\n[twitter]: https://twitter.com/linkerd\n\n{{ template \"chart.requirementsSection\" . }}\n\n{{ template \"chart.valuesSection\" . }}\n\n{{ template \"helm-docs.versionFooter\" . }}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/requirements.yaml",
    "content": "dependencies:\n- name: partials\n  version: 0.1.0\n  repository:  file://../../../charts/partials\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/NOTES.txt",
    "content": "The Linkerd Viz extension was successfully installed 🎉\n\nTo make sure everything works as expected, run the following:\n\n  linkerd viz check\n\nTo view the linkerd dashboard, run the following:\n\n  linkerd viz dashboard\n\nLooking for more? Visit https://linkerd.io/2/getting-started/\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/admin-policy.yaml",
    "content": "---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: kubelet\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  # Ideally, this should be restricted to the actual set of IPs kubelet uses in\n  # a cluster. This can't easily be discovered.\n  networks:\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/metrics-api-policy.yaml",
    "content": "---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: metrics-api\n  port: http\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: metrics-api\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: MeshTLSAuthentication\n    name: metrics-api-web\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: MeshTLSAuthentication\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: metrics-api-web\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  identityRefs:\n  - kind: ServiceAccount\n    name: web\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/metrics-api-rbac.yaml",
    "content": "---\n###\n### Metrics API RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"daemonsets\", \"deployments\", \"replicasets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\" , \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"replicationcontrollers\", \"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"policy.linkerd.io\"]\n  resources: [\"servers\", \"serverauthorizations\", \"authorizationpolicies\", \"httproutes\"]\n  verbs: [\"list\", \"get\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-{{.Release.Namespace}}-metrics-api\nsubjects:\n- kind: ServiceAccount\n  name: metrics-api\n  namespace: {{.Release.Namespace}}\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: metrics-api\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n{{- include \"partials.image-pull-secrets\" .Values.imagePullSecrets }}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/metrics-api.yaml",
    "content": "---\n###\n### Metrics API\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: metrics-api\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    {{- with .Values.metricsAPI.service.annotations }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: metrics-api\n  ports:\n  - name: http\n    port: 8085\n    targetPort: 8085\n---\n{{- $tree := deepCopy . }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: metrics-api\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: {{.Values.linkerdVersion}}\n    component: metrics-api\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  name: metrics-api\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: {{.Values.metricsAPI.replicas}}\n  revisionHistoryLimit: {{.Values.revisionHistoryLimit}}\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: metrics-api\n  template:\n    metadata:\n      annotations:\n        {{- if empty .Values.cliVersion }}\n        checksum/config: {{ include (print $.Template.BasePath \"/metrics-api-rbac.yaml\") . | sha256sum }}\n        {{- end }}\n        {{ include \"partials.annotations.created-by\" . }}\n        {{- with .Values.metricsAPI.proxy }}\n        {{- include \"partials.proxy.config.annotations\" .resources | nindent 8 }}\n        {{- end }}\n        {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: metrics-api\n        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n    spec:\n      {{- if .Values.metricsAPI.tolerations -}}\n      {{- include \"linkerd.tolerations\" (dict \"Values\" .Values.metricsAPI) | nindent 6 }}\n      {{- end -}}\n      {{- include \"linkerd.node-selector\" (dict \"Values\" .Values.metricsAPI) | nindent 6 }}\n      {{- $_ := set $tree \"component\" \"metrics-api\" -}}\n      {{- $_ := set $tree \"label\" \"component\" -}}\n      {{- with include \"linkerd.affinity\" $tree }}\n      {{- . | nindent 6 }}\n      {{- end }}\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - -controller-namespace={{.Values.linkerdNamespace}}\n        - -log-level={{.Values.metricsAPI.logLevel | default .Values.defaultLogLevel}}\n        - -log-format={{.Values.metricsAPI.logFormat | default .Values.defaultLogFormat}}\n        - -cluster-domain={{.Values.clusterDomain}}\n        {{- if .Values.prometheusUrl }}\n        - -prometheus-url={{.Values.prometheusUrl}}\n        {{- else if .Values.prometheus.enabled }}\n        - -prometheus-url=http://prometheus.{{.Release.Namespace}}.svc.{{.Values.clusterDomain}}:9090\n        {{- else }}\n        {{ fail \"Please enable `linkerd-prometheus` or provide `prometheusUrl` for the viz extension to function properly\"}}\n        {{- end }}\n        {{- if .Values.prometheusCredsSecret }}\n        - -prometheus-user-file=/var/prometheus/user\n        - -prometheus-password-file=/var/prometheus/password\n        {{- end}}\n        - -enable-pprof={{.Values.enablePprof | default false}}\n        image: {{.Values.metricsAPI.image.registry | default .Values.defaultRegistry}}/{{.Values.metricsAPI.image.name}}:{{.Values.metricsAPI.image.tag | default .Values.linkerdVersion}}\n        imagePullPolicy: {{.Values.metricsAPI.image.pullPolicy | default .Values.defaultImagePullPolicy}}\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: metrics-api\n        ports:\n        - containerPort: 8085\n          name: http\n        - containerPort: 9995\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        {{- if .Values.metricsAPI.resources -}}\n        {{- include \"partials.resources\" .Values.metricsAPI.resources | nindent 8 }}\n        {{- end }}\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          runAsNonRoot: true\n          readOnlyRootFilesystem: true\n          runAsUser: {{.Values.metricsAPI.UID | default .Values.defaultUID}}\n          runAsGroup: {{.Values.metricsAPI.GID | default .Values.defaultGID}}\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n        {{- if .Values.prometheusCredsSecret }}\n        - mountPath: /var/prometheus\n          name: prom-creds\n          readOnly: true\n        {{- end}}\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: metrics-api\n      volumes:\n      - {{- include \"partials.volumes.manual-mount-service-account-token\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n      {{- with .Values.prometheusCredsSecret }}\n      - name: prom-creds\n        secret:\n          secretName: {{ . }}\n      {{- end }}\n{{- if and .Values.enablePodDisruptionBudget (gt (int .Values.metricsAPI.replicas) 1) }}\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: metrics-api\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: metrics-api\n{{- end }}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/namespace-metadata-rbac.yaml",
    "content": "{{- if .Values.createNamespaceMetadataJob}}\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    \"helm.sh/hook\": post-install\n    \"helm.sh/hook-weight\": \"0\"\n    \"helm.sh/hook-delete-policy\": before-hook-creation,hook-succeeded\n  name: namespace-metadata\n  namespace: {{.Release.Namespace}}\n{{- include \"partials.image-pull-secrets\" .Values.imagePullSecrets }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    \"helm.sh/hook\": post-install\n    \"helm.sh/hook-weight\": \"0\"\n    \"helm.sh/hook-delete-policy\": before-hook-creation,hook-succeeded\n  name: namespace-metadata\n  namespace: {{.Release.Namespace}}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"get\", \"patch\"]\n  resourceNames: [\"{{.Release.Namespace}}\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    \"helm.sh/hook\": post-install\n    \"helm.sh/hook-weight\": \"0\"\n    \"helm.sh/hook-delete-policy\": before-hook-creation,hook-succeeded\n  name: namespace-metadata\n  namespace: {{.Release.Namespace}}\nroleRef:\n  kind: Role\n  name: namespace-metadata\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: namespace-metadata\n  namespace: {{.Release.Namespace}}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  namespace: {{ .Values.linkerdNamespace }}\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    \"helm.sh/hook\": post-install\n    \"helm.sh/hook-weight\": \"0\"\n    \"helm.sh/hook-delete-policy\": before-hook-creation,hook-succeeded\n  name: viz-namespace-metadata-linkerd-config\nroleRef:\n  kind: Role\n  name: ext-namespace-metadata-linkerd-config\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: namespace-metadata\n  namespace: {{.Release.Namespace}}\n{{- end }}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/namespace-metadata.yaml",
    "content": "{{- if .Values.createNamespaceMetadataJob}}\napiVersion: batch/v1\nkind: Job\nmetadata:\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    \"helm.sh/hook\": post-install\n    \"helm.sh/hook-weight\": \"1\"\n    \"helm.sh/hook-delete-policy\": before-hook-creation,hook-succeeded\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: namespace-metadata\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: {{.Values.linkerdVersion}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  name: namespace-metadata\n  namespace: {{.Release.Namespace}}\nspec:\n  template:\n    metadata:\n      annotations:\n        {{ include \"partials.annotations.created-by\" . }}\n        linkerd.io/inject: disabled\n      labels:\n        linkerd.io/extension: viz\n        app.kubernetes.io/name: namespace-metadata\n        app.kubernetes.io/part-of: Linkerd\n        app.kubernetes.io/version: {{.Values.linkerdVersion}}\n        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n    spec:\n      {{- if .Values.namespaceMetadata.tolerations -}}\n      {{- include \"linkerd.tolerations\" (dict \"Values\" .Values.namespaceMetadata) | nindent 6 }}\n      {{- end -}}\n      {{- include \"linkerd.node-selector\" (dict \"Values\" .Values.namespaceMetadata) | nindent 6 }}\n      restartPolicy: Never\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: namespace-metadata\n      automountServiceAccountToken: false\n      containers:\n      - name: namespace-metadata\n        image: {{.Values.namespaceMetadata.image.registry | default .Values.defaultRegistry}}/{{.Values.namespaceMetadata.image.name}}:{{.Values.namespaceMetadata.image.tag}}\n        imagePullPolicy: {{.Values.namespaceMetadata.image.pullPolicy | default .Values.defaultImagePullPolicy}}\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          runAsNonRoot: true\n          readOnlyRootFilesystem: true\n          runAsUser: {{.Values.defaultUID}}\n          runAsGroup: {{.Values.defaultGID}}\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n        args:\n        - --log-format\n        - {{.Values.defaultLogFormat}}\n        - --log-level\n        - {{.Values.defaultLogLevel}}\n        - --extension\n        - viz\n        - --namespace\n        - {{.Release.Namespace}}\n        - --linkerd-namespace\n        - {{.Values.linkerdNamespace}}\n        {{- with .Values.prometheusUrl }}\n        - --prometheus-url\n        - {{.}}\n        {{- end }}\n      volumes:\n      - {{- include \"partials.volumes.manual-mount-service-account-token\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n{{- end }}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/namespace.yaml",
    "content": "{{- if eq .Release.Service \"CLI\" -}}\n---\n###\n### Linkerd Viz Extension Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: {{.Release.Namespace}}\n  labels:\n    linkerd.io/extension: viz\n    {{- /* linkerd-init requires extended capabilities and so requires priviledged mode */}}\n    pod-security.kubernetes.io/enforce: {{ if .Values.cniEnabled }}restricted{{ else }}privileged{{ end }}\n  annotations:\n    {{- if .Values.prometheusUrl }}\n    viz.linkerd.io/external-prometheus: {{.Values.prometheusUrl}}\n    {{- end }}\n{{ end -}}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/prometheus-policy.yaml",
    "content": "---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: prometheus-admin\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: prometheus\n      namespace: {{.Release.Namespace}}\n  port: admin\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: prometheus-admin\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: prometheus-admin\n  requiredAuthenticationRefs:\n    - kind: ServiceAccount\n      name: metrics-api\n      namespace: {{.Release.Namespace}}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/prometheus-rbac.yaml",
    "content": "{{ if .Values.prometheus.enabled -}}\n---\n###\n### Prometheus RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-prometheus\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"nodes\", \"nodes/proxy\", \"pods\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-prometheus\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-{{.Release.Namespace}}-prometheus\nsubjects:\n- kind: ServiceAccount\n  name: prometheus\n  namespace: {{.Release.Namespace}}\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: prometheus\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n{{- include \"partials.image-pull-secrets\" .Values.imagePullSecrets }}\n{{ end -}}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/prometheus.yaml",
    "content": "{{ if .Values.prometheus.enabled -}}\n---\n###\n### Prometheus\n###\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: prometheus-config\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\ndata:\n  prometheus.yml: |-\n    global:\n      {{- if .Values.prometheus.globalConfig -}}\n      {{- toYaml .Values.prometheus.globalConfig | trim | nindent 6 }}\n      {{- end}}\n\n    rule_files:\n    - /etc/prometheus/*_rules.yml\n    - /etc/prometheus/*_rules.yaml\n\n    scrape_configs:\n    - job_name: 'prometheus'\n      static_configs:\n      - targets: ['localhost:9090']\n\n    #  Required for: https://grafana.com/grafana/dashboards/315\n    - job_name: 'kubernetes-nodes-cadvisor'\n      scheme: https\n      tls_config:\n        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n        insecure_skip_verify: true\n      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token\n      kubernetes_sd_configs:\n      - role: node\n      relabel_configs:\n      - action: labelmap\n        regex: __meta_kubernetes_node_label_(.+)\n      - target_label: __address__\n        replacement: kubernetes.default.svc:443\n      - source_labels: [__meta_kubernetes_node_name]\n        regex: (.+)\n        target_label: __metrics_path__\n        replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor\n      metric_relabel_configs:\n      - source_labels: [__name__]\n        regex: '(container|machine)_(cpu|memory|network|fs)_(.+)'\n        action: keep\n      - source_labels: [__name__]\n        regex: 'container_memory_failures_total' # unneeded large metric\n        action: drop\n\n    - job_name: 'linkerd-controller'\n      kubernetes_sd_configs:\n      - role: pod\n        namespaces:\n          names:\n          - '{{.Values.linkerdNamespace}}'\n          - '{{.Release.Namespace}}'\n      relabel_configs:\n      - source_labels:\n        - __meta_kubernetes_pod_container_port_name\n        action: keep\n        regex: admin\n      - source_labels: [__meta_kubernetes_pod_container_name]\n        action: replace\n        target_label: component\n\n    - job_name: 'linkerd-multicluster-controller'\n      kubernetes_sd_configs:\n      - role: pod\n      relabel_configs:\n      - source_labels:\n        - __meta_kubernetes_pod_label_component\n        - __meta_kubernetes_pod_container_port_name\n        action: keep\n        regex: (linkerd-service-mirror|controller);admin$\n      - source_labels: [__meta_kubernetes_pod_container_name]\n        action: replace\n        target_label: component\n\n    - job_name: 'linkerd-proxy'\n      kubernetes_sd_configs:\n      - role: pod\n      relabel_configs:\n      - source_labels: [__meta_kubernetes_pod_phase]\n        regex: (Pending|Running)\n        action: keep\n      - source_labels:\n        - __meta_kubernetes_pod_container_name\n        - __meta_kubernetes_pod_container_port_name\n        - __meta_kubernetes_pod_label_linkerd_io_control_plane_ns\n        action: keep\n        regex: ^{{default .Values.proxyContainerName \"linkerd-proxy\" .Values.proxyContainerName}};linkerd-admin;{{.Values.linkerdNamespace}}$\n      - source_labels: [__meta_kubernetes_namespace]\n        action: replace\n        target_label: namespace\n      - source_labels: [__meta_kubernetes_pod_name]\n        action: replace\n        target_label: pod\n      # special case k8s' \"job\" label, to not interfere with prometheus' \"job\"\n      # label\n      # __meta_kubernetes_pod_label_linkerd_io_proxy_job=foo =>\n      # k8s_job=foo\n      - source_labels: [__meta_kubernetes_pod_label_linkerd_io_proxy_job]\n        action: replace\n        target_label: k8s_job\n      # drop __meta_kubernetes_pod_label_linkerd_io_proxy_job\n      - action: labeldrop\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_job\n      # __meta_kubernetes_pod_label_linkerd_io_proxy_deployment=foo =>\n      # deployment=foo\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)\n      # drop all labels that we just made copies of in the previous labelmap\n      - action: labeldrop\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)\n      # __meta_kubernetes_pod_label_linkerd_io_foo=bar =>\n      # foo=bar\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_linkerd_io_(.+)\n      # Copy all pod labels to tmp labels\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_(.+)\n        replacement: __tmp_pod_label_$1\n      # Take `linkerd_io_` prefixed labels and copy them without the prefix\n      - action: labelmap\n        regex: __tmp_pod_label_linkerd_io_(.+)\n        replacement:  __tmp_pod_label_$1\n      # Drop the `linkerd_io_` originals\n      - action: labeldrop\n        regex: __tmp_pod_label_linkerd_io_(.+)\n      # Copy tmp labels into real labels\n      - action: labelmap\n        regex: __tmp_pod_label_(.+)\n      {{- if .Values.prometheus.metricRelabelConfigs }}\n      metric_relabel_configs:\n      {{- toYaml .Values.prometheus.metricRelabelConfigs | trim | nindent 6 }}\n      {{- end}}\n\n    {{- if .Values.prometheus.scrapeConfigs }}\n    {{- toYaml .Values.prometheus.scrapeConfigs | trim | nindent 4 }}\n    {{- end }}\n\n    {{-  if (or .Values.prometheus.alertmanagers .Values.prometheus.alertRelabelConfigs) }}\n    alerting:\n      alert_relabel_configs:\n        {{- if .Values.prometheus.alertRelabelConfigs }}\n        {{- toYaml .Values.prometheus.alertRelabelConfigs | trim | nindent 6 }}\n        {{- end }}\n      alertmanagers:\n        {{- if .Values.prometheus.alertmanagers }}\n        {{- toYaml .Values.prometheus.alertmanagers | trim | nindent 6 }}\n        {{- end }}\n    {{- end }}\n\n    {{- if .Values.prometheus.remoteWrite }}\n    remote_write:\n      {{- toYaml .Values.prometheus.remoteWrite | trim | nindent 6 }}\n    {{- end }}\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: prometheus\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: prometheus\n  ports:\n  - name: admin\n    port: 9090\n    targetPort: 9090\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: prometheus\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: {{.Values.linkerdVersion}}\n    component: prometheus\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  name: prometheus\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: 1\n  revisionHistoryLimit: {{.Values.revisionHistoryLimit}}\n  {{- if .Values.prometheus.persistence }}\n  strategy:\n    type: Recreate\n  {{- end }}\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: prometheus\n      namespace: {{.Release.Namespace}}\n  template:\n    metadata:\n      annotations:\n        {{ include \"partials.annotations.created-by\" . }}\n        {{- with .Values.prometheus.proxy }}\n        {{- include \"partials.proxy.config.annotations\" .resources | nindent 8 }}\n        {{- end }}\n        {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n        {{- with .Values.prometheus.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n      labels:\n        linkerd.io/extension: viz\n        component: prometheus\n        namespace: {{.Release.Namespace}}\n        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n    spec:\n      {{- if .Values.prometheus.tolerations -}}\n      {{- include \"linkerd.tolerations\" (dict \"Values\" .Values.prometheus) | nindent 6 }}\n      {{- end -}}\n      {{- include \"linkerd.node-selector\" (dict \"Values\" .Values.prometheus) | nindent 6 }}\n      automountServiceAccountToken: false\n      containers:\n      {{- if .Values.prometheus.sidecarContainers -}}\n      {{- toYaml .Values.prometheus.sidecarContainers | trim | nindent 6 }}\n      {{- end}}\n      - args:\n        {{- if not (hasKey .Values.prometheus.args \"log.level\") }}\n        - --log.level={{.Values.prometheus.logLevel | default .Values.defaultLogLevel}}\n        {{- end }}\n        {{- if not (hasKey .Values.prometheus.args \"log.format\") }}\n        - --log.format={{.Values.prometheus.logFormat | default .Values.defaultLogFormat | replace \"plain\" \"logfmt\" }}\n        {{- end }}\n        {{- range $key, $value := .Values.prometheus.args}}\n        - --{{ $key }}{{ if $value }}={{ $value }}{{ end }}\n        {{- end }}\n        image: {{.Values.prometheus.image.registry}}/{{.Values.prometheus.image.name}}:{{.Values.prometheus.image.tag}}\n        imagePullPolicy: {{.Values.prometheus.image.pullPolicy | default .Values.defaultImagePullPolicy}}\n        livenessProbe:\n          httpGet:\n            path: /-/healthy\n            port: 9090\n          initialDelaySeconds: 30\n          timeoutSeconds: 30\n        name: prometheus\n        ports:\n        - containerPort: 9090\n          name: admin\n        readinessProbe:\n          httpGet:\n            path: /-/ready\n            port: 9090\n          initialDelaySeconds: 30\n          timeoutSeconds: 30\n        {{- if .Values.prometheus.resources -}}\n        {{- include \"partials.resources\" .Values.prometheus.resources | nindent 8 }}\n        {{- end }}\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n      {{- range .Values.prometheus.ruleConfigMapMounts }}\n        - name: {{ .name }}\n          mountPath: /etc/prometheus/{{ .subPath }}\n          subPath: {{ .subPath }}\n          readOnly: true\n      {{- end }}\n        - mountPath: /data\n          name: data\n        - mountPath: /etc/prometheus/prometheus.yml\n          name: prometheus-config\n          subPath: prometheus.yml\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        fsGroup: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: prometheus\n      volumes:\n    {{- range .Values.prometheus.ruleConfigMapMounts }}\n      - name: {{ .name }}\n        configMap:\n          name: {{ .configMap }}\n    {{- end }}\n      - name: data\n    {{- if .Values.prometheus.persistence }}\n        persistentVolumeClaim:\n          claimName: prometheus\n    {{- else }}\n        emptyDir: {}\n    {{- end }}\n      - configMap:\n          name: prometheus-config\n        name: prometheus-config\n      - {{- include \"partials.volumes.manual-mount-service-account-token\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n{{- if .Values.prometheus.persistence }}\n---\nkind: PersistentVolumeClaim\napiVersion: v1\nmetadata:\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: prometheus\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: {{.Values.linkerdVersion}}\n    component: prometheus\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  name: prometheus\n  namespace: {{ .Release.Namespace }}\nspec:\n  accessModes:\n    - {{ .Values.prometheus.persistence.accessMode | quote }}\n  resources:\n    requests:\n      storage: {{ .Values.prometheus.persistence.size | quote }}\n{{- if .Values.prometheus.persistence.storageClass }}\n  storageClassName: \"{{ .Values.prometheus.persistence.storageClass }}\"\n{{- end }}\n{{- end }}\n{{ end -}}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/psp.yaml",
    "content": "{{ if .Values.enablePSP -}}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: psp\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: ['policy', 'extensions']\n  resources: ['podsecuritypolicies']\n  verbs: ['use']\n  resourceNames:\n  - linkerd-{{.Values.linkerdNamespace}}-control-plane\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: viz-psp\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  kind: Role\n  name: psp\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: {{.Release.Namespace}}\n- kind: ServiceAccount\n  name: web\n  namespace: {{.Release.Namespace}}\n{{ if .Values.prometheus.enabled -}}\n- kind: ServiceAccount\n  name: prometheus\n  namespace: {{.Release.Namespace}}\n{{ end -}}\n- kind: ServiceAccount\n  name: metrics-api\n  namespace: {{.Release.Namespace}}\n- kind: ServiceAccount\n  name: tap-injector\n  namespace: {{.Release.Namespace}}\n- kind: ServiceAccount\n  name: namespace-metadata\n  namespace: {{.Release.Namespace}}\n{{ end -}}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/service-profiles.yaml",
    "content": "---\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: metrics-api.{{.Release.Namespace}}.svc.{{.Values.clusterDomain}}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nspec:\n  routes:\n  - name: POST /api/v1/StatSummary\n    condition:\n      method: POST\n      pathRegex: /api/v1/StatSummary\n  - name: POST /api/v1/TopRoutes\n    condition:\n      method: POST\n      pathRegex: /api/v1/TopRoutes\n  - name: POST /api/v1/ListPods\n    condition:\n      method: POST\n      pathRegex: /api/v1/ListPods\n  - name: POST /api/v1/ListServices\n    condition:\n      method: POST\n      pathRegex: /api/v1/ListServices\n  - name: POST /api/v1/SelfCheck\n    condition:\n      method: POST\n      pathRegex: /api/v1/SelfCheck\n  - name: POST /api/v1/Gateways\n    condition:\n      method: POST\n      pathRegex: /api/v1/Gateways\n  - name: POST /api/v1/Edges\n    condition:\n      method: POST\n      pathRegex: /api/v1/Edges\n{{ if .Values.prometheus.enabled -}}\n---\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: prometheus.{{.Release.Namespace}}.svc.{{.Values.clusterDomain}}\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nspec:\n  routes:\n  - name: POST /api/v1/query\n    condition:\n      method: POST\n      pathRegex: /api/v1/query\n  - name: GET /api/v1/query_range\n    condition:\n      method: GET\n      pathRegex: /api/v1/query_range\n  - name: GET /api/v1/series\n    condition:\n      method: GET\n      pathRegex: /api/v1/series\n{{ end -}}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/tap-injector-policy.yaml",
    "content": "---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: tap-injector-webhook\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap-injector\n  port: tap-injector\n  proxyProtocol: TLS\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: tap-injector\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: tap-injector-webhook\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: NetworkAuthentication\n    name: kube-api-server\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: kube-api-server\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  # Ideally, this should be restricted to the actual set of IPs the kubelet API\n  # server uses for webhooks in a cluster. This can't easily be discovered.\n  networks:\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/tap-injector-rbac.yaml",
    "content": "---\n###\n### Tap Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-tap-injector\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-tap-injector\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nsubjects:\n- kind: ServiceAccount\n  name: tap-injector\n  namespace: {{.Release.Namespace}}\nroleRef:\n  kind: ClusterRole\n  name: linkerd-tap-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: tap-injector\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n{{- include \"partials.image-pull-secrets\" .Values.imagePullSecrets }}\n---\n{{- $host := printf \"tap-injector.%s.svc\" .Release.Namespace }}\n{{- $ca := genSelfSignedCert $host (list) (list $host) 365 }}\n{{- if (not .Values.tapInjector.externalSecret) }}\nkind: Secret\napiVersion: v1\nmetadata:\n  name: tap-injector-k8s-tls\n  namespace: {{ .Release.Namespace }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\ntype: kubernetes.io/tls\ndata:\n  tls.crt: {{ ternary (b64enc (trim $ca.Cert)) (b64enc (trim .Values.tapInjector.crtPEM)) (empty .Values.tapInjector.crtPEM) }}\n  tls.key: {{ ternary (b64enc (trim $ca.Key)) (b64enc (trim .Values.tapInjector.keyPEM)) (empty .Values.tapInjector.keyPEM) }}\n---\n{{- end }}\n{{- include \"linkerd.webhook.validation\" .Values.tapInjector }}\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-tap-injector-webhook-config\n  {{- if or (.Values.tapInjector.injectCaFrom) (.Values.tapInjector.injectCaFromSecret) }}\n  annotations:\n  {{- if .Values.tapInjector.injectCaFrom }}\n    cert-manager.io/inject-ca-from: {{ .Values.tapInjector.injectCaFrom }}\n  {{- end }}\n  {{- if .Values.tapInjector.injectCaFromSecret }}\n    cert-manager.io/inject-ca-from-secret: {{ .Values.tapInjector.injectCaFromSecret }}\n  {{- end }}\n  {{- end }}\n  labels:\n    linkerd.io/extension: viz\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nwebhooks:\n- name: tap-injector.linkerd.io\n  {{- if .Values.tapInjector.namespaceSelector }}\n  namespaceSelector:\n{{ toYaml .Values.tapInjector.namespaceSelector | trim | indent 4 -}}\n  {{- end }}\n  {{- if .Values.tapInjector.objectSelector }}\n  objectSelector:\n{{ toYaml .Values.tapInjector.objectSelector | trim | indent 4 -}}\n  {{- end }}\n  clientConfig:\n    service:\n      name: tap-injector\n      namespace: {{ .Release.Namespace }}\n      path: \"/\"\n    {{- if and (empty .Values.tapInjector.injectCaFrom) (empty .Values.tapInjector.injectCaFromSecret) }}\n    caBundle: {{ ternary (b64enc (trim $ca.Cert)) (b64enc (trim .Values.tapInjector.caBundle)) (empty .Values.tapInjector.caBundle) }}\n    {{- end }}\n  failurePolicy: {{.Values.tapInjector.failurePolicy}}\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  reinvocationPolicy: IfNeeded\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/tap-injector.yaml",
    "content": "---\n###\n### Tap Injector\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: tap-injector\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    {{- with .Values.tapInjector.service.annotations }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: tap-injector\n  ports:\n  - name: tap-injector\n    port: 443\n    targetPort: tap-injector\n---\n{{- $tree := deepCopy . }}\nkind: Deployment\napiVersion: apps/v1\nmetadata:\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: tap-injector\n    app.kubernetes.io/part-of: Linkerd\n    component: tap-injector\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  name: tap-injector\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: {{.Values.tapInjector.replicas}}\n  revisionHistoryLimit: {{.Values.revisionHistoryLimit}}\n  selector:\n    matchLabels:\n      component: tap-injector\n  {{- if .Values.enablePodAntiAffinity }}\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 1\n  {{- end }}\n  template:\n    metadata:\n      annotations:\n        {{- if empty .Values.cliVersion }}\n        checksum/config: {{ include (print $.Template.BasePath \"/tap-injector-rbac.yaml\") . | sha256sum }}\n        {{- end }}\n        {{ include \"partials.annotations.created-by\" . }}\n        {{- with .Values.tapInjector.proxy }}\n        {{- include \"partials.proxy.config.annotations\" .resources | nindent 8 }}\n        {{- end }}\n        {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: tap-injector\n        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n    spec:\n      {{- if .Values.tolerations -}}\n      {{- include \"linkerd.tolerations\" . | nindent 6 }}\n      {{- end -}}\n      {{- include \"linkerd.node-selector\" . | nindent 6 }}\n      {{- $_ := set $tree \"component\" \"tap-injector\" -}}\n      {{- $_ := set $tree \"label\" \"component\" -}}\n      {{- with include \"linkerd.affinity\" $tree }}\n      {{- . | nindent 6 }}\n      {{- end }}\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - injector\n        - -tap-service-name=tap.{{.Release.Namespace}}.serviceaccount.identity.{{.Values.linkerdNamespace}}.{{.Values.identityTrustDomain | default .Values.clusterDomain}}\n        - -log-level={{.Values.tapInjector.logLevel | default .Values.defaultLogLevel}}\n        - -log-format={{.Values.tapInjector.logFormat | default .Values.defaultLogFormat}}\n        - -enable-pprof={{.Values.enablePprof | default false}}\n        image: {{.Values.tapInjector.image.registry | default .Values.defaultRegistry}}/{{.Values.tapInjector.image.name}}:{{.Values.tapInjector.image.tag | default .Values.linkerdVersion}}\n        imagePullPolicy: {{.Values.tapInjector.image.pullPolicy | default .Values.defaultImagePullPolicy}}\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: tap-injector\n        ports:\n        - containerPort: 8443\n          name: tap-injector\n        - containerPort: 9995\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        {{- if .Values.tapInjector.resources -}}\n        {{- include \"partials.resources\" .Values.tapInjector.resources | nindent 8 }}\n        {{- end }}\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: {{.Values.tapInjector.UID | default .Values.defaultUID}}\n          runAsGroup: {{.Values.tapInjector.GID | default .Values.defaultGID}}\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: tap-injector\n      volumes:\n      - name: tls\n        secret:\n          secretName: tap-injector-k8s-tls\n      - {{- include \"partials.volumes.manual-mount-service-account-token\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n{{- if and .Values.enablePodDisruptionBudget (gt (int .Values.tapInjector.replicas) 1) }}\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: tap-injector\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap-injector\n{{- end }}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/tap-policy.yaml",
    "content": "---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: tap-api\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap\n  port: apiserver\n  proxyProtocol: TLS\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: {{ .Release.Namespace }}\n  name: tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: tap-api\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: NetworkAuthentication\n    name: kube-api-server\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/tap-rbac.yaml",
    "content": "---\n###\n### Tap RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"services\", \"replicationcontrollers\", \"namespaces\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"daemonsets\", \"deployments\", \"replicasets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\" , \"get\", \"watch\"]\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-tap-admin\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"tap.linkerd.io\"]\n  resources: [\"*\"]\n  verbs: [\"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-{{.Release.Namespace}}-tap\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: {{.Release.Namespace}}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-tap-auth-delegator\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: system:auth-delegator\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: {{.Release.Namespace}}\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: tap\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n{{- include \"partials.image-pull-secrets\" .Values.imagePullSecrets }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-tap-auth-reader\n  namespace: kube-system\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: extension-apiserver-authentication-reader\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: {{.Release.Namespace}}\n---\n{{- $host := printf \"tap.%s.svc\" .Release.Namespace }}\n{{- $ca := genSelfSignedCert $host (list) (list $host) 365 }}\n{{- if (not .Values.tap.externalSecret) }}\nkind: Secret\napiVersion: v1\nmetadata:\n  name: tap-k8s-tls\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\ntype: kubernetes.io/tls\ndata:\n  tls.crt: {{ ternary (b64enc (trim $ca.Cert)) (b64enc (trim .Values.tap.crtPEM)) (empty .Values.tap.crtPEM) }}\n  tls.key: {{ ternary (b64enc (trim $ca.Key)) (b64enc (trim .Values.tap.keyPEM)) (empty .Values.tap.keyPEM) }}\n---\n{{- end }}\n{{- include \"linkerd.webhook.validation\" .Values.tap }}\napiVersion: apiregistration.k8s.io/v1\nkind: APIService\nmetadata:\n  name: v1alpha1.tap.linkerd.io\n  {{- if or (.Values.tap.injectCaFrom) (.Values.tap.injectCaFromSecret) }}\n  annotations:\n  {{- if .Values.tap.injectCaFrom }}\n    cert-manager.io/inject-ca-from: {{ .Values.tap.injectCaFrom }}\n  {{- end }}\n  {{- if .Values.tap.injectCaFromSecret }}\n    cert-manager.io/inject-ca-from-secret: {{ .Values.tap.injectCaFromSecret }}\n  {{- end }}\n  {{- end }}\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nspec:\n  group: tap.linkerd.io\n  version: v1alpha1\n  groupPriorityMinimum: 1000\n  versionPriority: 100\n  service:\n    name: tap\n    namespace: {{.Release.Namespace}}\n  {{- if and (empty .Values.tap.injectCaFrom) (empty .Values.tap.injectCaFromSecret) }}\n  caBundle: {{ ternary (b64enc (trim $ca.Cert)) (b64enc (trim .Values.tap.caBundle)) (empty .Values.tap.caBundle) }}\n  {{- end }}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/tap.yaml",
    "content": "---\n###\n### Tap\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: tap\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    {{- with .Values.tap.service.annotations }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: tap\n  ports:\n  - name: grpc\n    port: 8088\n    targetPort: 8088\n  - name: apiserver\n    port: 443\n    targetPort: apiserver\n---\n{{- $tree := deepCopy . }}\nkind: Deployment\napiVersion: apps/v1\nmetadata:\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: tap\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: {{.Values.linkerdVersion}}\n    component: tap\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  name: tap\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: {{.Values.tap.replicas}}\n  revisionHistoryLimit: {{.Values.revisionHistoryLimit}}\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap\n      namespace: {{.Release.Namespace}}\n  {{- if .Values.enablePodAntiAffinity }}\n  strategy:\n    rollingUpdate:\n      maxUnavailable: 1\n  {{- end }}\n  template:\n    metadata:\n      annotations:\n        {{- if empty .Values.cliVersion }}\n        checksum/config: {{ include (print $.Template.BasePath \"/tap-rbac.yaml\") . | sha256sum }}\n        {{- end }}\n        {{ include \"partials.annotations.created-by\" . }}\n        {{- with .Values.tap.proxy }}\n        {{- include \"partials.proxy.config.annotations\" .resources | nindent 8 }}\n        {{- end }}\n        {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: tap\n        namespace: {{.Release.Namespace}}\n        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n    spec:\n      {{- if .Values.tolerations -}}\n      {{- include \"linkerd.tolerations\" . | nindent 6 }}\n      {{- end -}}\n      {{- include \"linkerd.node-selector\" . | nindent 6 }}\n      {{- $_ := set $tree \"component\" \"tap\" -}}\n      {{- $_ := set $tree \"label\" \"component\" -}}\n      {{- with include \"linkerd.affinity\" $tree }}\n      {{- . | nindent 6 }}\n      {{- end }}\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - api\n        - -api-namespace={{.Values.linkerdNamespace}}\n        - -log-level={{.Values.tap.logLevel | default .Values.defaultLogLevel}}\n        - -log-format={{.Values.tap.logFormat | default .Values.defaultLogFormat}}\n        - -identity-trust-domain={{.Values.identityTrustDomain | default .Values.clusterDomain}}\n        - -enable-pprof={{.Values.enablePprof | default false}}\n        {{- if .Values.tap.ignoreHeaders }}\n        - -ignore-headers={{ .Values.tap.ignoreHeaders | join \",\" }}\n        {{- end }}\n        image: {{.Values.tap.image.registry | default .Values.defaultRegistry}}/{{.Values.tap.image.name}}:{{.Values.tap.image.tag | default .Values.linkerdVersion}}\n        imagePullPolicy: {{.Values.tap.image.pullPolicy | default .Values.defaultImagePullPolicy}}\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9998\n          initialDelaySeconds: 10\n        name: tap\n        ports:\n        - containerPort: 8088\n          name: grpc\n        - containerPort: 8089\n          name: apiserver\n        - containerPort: 9998\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9998\n        {{- if .Values.tap.resources -}}\n        {{- include \"partials.resources\" .Values.tap.resources | nindent 8 }}\n        {{- end }}\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: {{.Values.tap.UID | default .Values.defaultUID}}\n          runAsGroup: {{.Values.tap.GID | default .Values.defaultGID}}\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: tap\n      volumes:\n      - name: tls\n        secret:\n          secretName: tap-k8s-tls\n      - {{- include \"partials.volumes.manual-mount-service-account-token\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n{{- if and .Values.enablePodDisruptionBudget (gt (int .Values.tap.replicas) 1) }}\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: tap\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap\n{{- end }}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/web-rbac.yaml",
    "content": "---\n###\n### Web RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: web\n  namespace: {{.Values.linkerdNamespace}}\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: {{.Values.linkerdNamespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n  {{- if not .Values.dashboard.restrictPrivileges }}\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"configmaps\"]\n  verbs: [\"get\"]\n- apiGroups: [\"\"]\n  resources: [\"serviceaccounts\", \"pods\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\"]\n  {{- end }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: web\n  namespace: {{.Values.linkerdNamespace}}\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: {{.Values.linkerdNamespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  kind: Role\n  name: web\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: {{.Release.Namespace}}\n---\n{{- if not .Values.dashboard.restrictPrivileges }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-web-check\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"rbac.authorization.k8s.io\"]\n  resources: [\"clusterroles\", \"clusterrolebindings\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apiextensions.k8s.io\"]\n  resources: [\"customresourcedefinitions\"]\n  verbs: [\"list\"]\n- apiGroups: [\"admissionregistration.k8s.io\"]\n  resources: [\"mutatingwebhookconfigurations\", \"validatingwebhookconfigurations\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n- apiGroups: [\"\"]\n  resources: [\"nodes\", \"pods\", \"services\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apiregistration.k8s.io\"]\n  resources: [\"apiservices\"]\n  verbs: [\"get\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-web-check\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  kind: ClusterRole\n  name: linkerd-{{.Release.Namespace}}-web-check\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: {{.Release.Namespace}}\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-web-admin\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-{{.Release.Namespace}}-tap-admin\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: {{.Release.Namespace}}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-web-api\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-{{.Release.Namespace}}-web-api\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\nroleRef:\n  kind: ClusterRole\n  name: linkerd-{{.Release.Namespace}}-web-api\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: {{.Release.Namespace}}\n---\n{{- end}}\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: web\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n{{- include \"partials.image-pull-secrets\" .Values.imagePullSecrets }}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/templates/web.yaml",
    "content": "---\n###\n### Web\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: web\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n    {{- with .Values.dashboard.service.labels }}{{ toYaml . | trim | nindent 4 }}{{ end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    {{ with .Values.dashboard.service.annotations }}{{ toYaml . | trim | nindent 4 }}{{ end }}\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: web\n  ports:\n  - name: http\n    port: 8084\n    targetPort: 8084\n  - name: admin\n    port: 9994\n    targetPort: 9994\n---\n{{- $tree := deepCopy . }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: web\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: {{.Values.linkerdVersion}}\n    component: web\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  name: web\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: {{.Values.dashboard.replicas}}\n  revisionHistoryLimit: {{.Values.revisionHistoryLimit}}\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: web\n      namespace: {{.Release.Namespace}}\n  template:\n    metadata:\n      annotations:\n        {{ include \"partials.annotations.created-by\" . }}\n        {{- with .Values.dashboard.proxy }}\n        {{- include \"partials.proxy.config.annotations\" .resources | nindent 8 }}\n        {{- end }}\n        {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: web\n        namespace: {{.Release.Namespace}}\n        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}\n    spec:\n      {{- if .Values.tolerations -}}\n      {{- include \"linkerd.tolerations\" . | nindent 6 }}\n      {{- end -}}\n      {{- include \"linkerd.node-selector\" . | nindent 6 }}\n      {{- $_ := set $tree \"component\" \"web\" -}}\n      {{- $_ := set $tree \"label\" \"component\" -}}\n      {{- with include \"linkerd.affinity\" $tree }}\n      {{- . | nindent 6 }}\n      {{- end }}\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - -linkerd-metrics-api-addr=metrics-api.{{.Release.Namespace}}.svc.{{.Values.clusterDomain}}:8085\n        - -cluster-domain={{.Values.clusterDomain}}\n        {{- if and .Values.grafana.url .Values.grafana.externalUrl }}\n        {{ fail \"Cannot set both grafana.url (on-cluster Grafana) and grafana.externalUrl (off-cluster Grafana)\"}}\n        {{- end}}\n        {{- if .Values.grafana.url }}\n        - -grafana-addr={{.Values.grafana.url}}\n        {{- end}}\n        {{- if .Values.grafana.externalUrl }}\n        - -grafana-external-addr={{.Values.grafana.externalUrl}}\n        {{- end}}\n        {{- if .Values.grafana.uidPrefix }}\n        - -grafana-prefix={{.Values.grafana.uidPrefix}}\n        {{- end}}\n        {{- if .Values.jaegerUrl }}\n        - -jaeger-addr={{.Values.jaegerUrl}}\n        {{- end}}\n        - -controller-namespace={{.Values.linkerdNamespace}}\n        - -log-level={{.Values.dashboard.logLevel | default .Values.defaultLogLevel}}\n        - -log-format={{.Values.dashboard.logFormat | default .Values.defaultLogFormat}}\n        {{- if .Values.dashboard.enforcedHostRegexp }}\n        - -enforced-host={{.Values.dashboard.enforcedHostRegexp}}\n        {{- else -}}\n        {{- $hostFull := replace \".\" \"\\\\.\" (printf \"web.%s.svc.%s\" .Release.Namespace .Values.clusterDomain) }}\n        {{- $hostAbbrev := replace \".\" \"\\\\.\" (printf \"web.%s.svc\" .Release.Namespace) }}\n        - -enforced-host=^(localhost|127\\.0\\.0\\.1|{{ $hostFull }}|{{ $hostAbbrev }}|\\[::1\\])(:\\d+)?$\n        {{- end}}\n        - -enable-pprof={{.Values.enablePprof | default false}}\n        image: {{.Values.dashboard.image.registry | default .Values.defaultRegistry}}/{{.Values.dashboard.image.name}}:{{.Values.dashboard.image.tag | default .Values.linkerdVersion}}\n        imagePullPolicy: {{.Values.dashboard.image.pullPolicy | default .Values.defaultImagePullPolicy}}\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9994\n          initialDelaySeconds: 10\n        name: web\n        ports:\n        - containerPort: 8084\n          name: http\n        - containerPort: 9994\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9994\n        {{- if .Values.dashboard.resources -}}\n        {{- include \"partials.resources\" .Values.dashboard.resources | nindent 8 }}\n        {{- end }}\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: {{.Values.dashboard.UID | default .Values.defaultUID}}\n          runAsGroup: {{.Values.dashboard.GID | default .Values.defaultGID}}\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: web\n      volumes:\n      - {{- include \"partials.volumes.manual-mount-service-account-token\" . | indent 8 | trimPrefix (repeat 7 \" \") }}\n{{- if and .Values.enablePodDisruptionBudget (gt (int .Values.dashboard.replicas) 1) }}\n---\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: web\n  namespace: {{ .Release.Namespace }}\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: {{.Release.Namespace}}\n    {{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}\n  annotations:\n    {{ include \"partials.annotations.created-by\" . }}\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: web\n{{- end }}\n"
  },
  {
    "path": "viz/charts/linkerd-viz/values-ha.yaml",
    "content": "# This values.yaml file contains the values needed to enable HA mode.\n# Usage:\n#   helm install -f values.yaml -f values-ha.yaml\n\nenablePodAntiAffinity: true\nenablePodDisruptionBudget: true\n\n# nodeAffinity:\n\nresources: &ha_resources\n  cpu: &ha_resources_cpu\n    limit: \"\"\n    request: 100m\n  memory:\n    limit: 250Mi\n    request: 50Mi\n\n\n# tap configuration\ntap:\n  replicas: 3\n  resources: *ha_resources\n\n# web configuration\ndashboard:\n  resources: *ha_resources\n\n# prometheus configuration\nprometheus:\n  resources:\n    cpu:\n      limit: \"\"\n      request: 300m\n    memory:\n      limit: 8192Mi\n      request: 300Mi\n"
  },
  {
    "path": "viz/charts/linkerd-viz/values.yaml",
    "content": "# Default values for linkerd.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\n\n# Fields that should be common with the core control plane\n\n# -- control plane version. See Proxy section for proxy version\nlinkerdVersion: linkerdVersionValue\n# -- Kubernetes DNS Domain name to use\nclusterDomain: cluster.local\n# -- Additional labels to add to all pods\npodLabels: {}\n# -- Labels to apply to all resources\ncommonLabels: {}\n# -- Trust domain used for identity\n# @default -- clusterDomain\nidentityTrustDomain: \"\"\n# -- Specifies the number of old ReplicaSets to retain to allow rollback.\nrevisionHistoryLimit: 10\n\n# -- Docker registry for all viz components\ndefaultRegistry: cr.l5d.io/linkerd\n# -- Docker imagePullPolicy for all viz components\ndefaultImagePullPolicy: IfNotPresent\n# -- Log level for all the viz components\ndefaultLogLevel: info\n# -- Log format (`plain` or `json`) for all the viz components.\ndefaultLogFormat: plain\n# -- UID for all the viz components\ndefaultUID: 2103\n# -- GID for all the viz components\ndefaultGID: 2103\n\n# -- Namespace of the Linkerd core control-plane install\nlinkerdNamespace: linkerd\n\n# -- Default nodeSelector section, See the\n# [K8S documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) for more information\nnodeSelector: &default_node_selector\n  kubernetes.io/os: linux\n\n# -- For Private docker registries, authentication is needed.\n#  Registry secrets are applied to the respective service accounts\nimagePullSecrets: []\n# - name: my-private-docker-registry-login-secret\n\n# -- Default tolerations section, See the\n# [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/)\n# for more information\ntolerations: &default_tolerations\n\n# -- Enables Pod Anti Affinity logic to balance the placement of replicas\n# across hosts and zones for High Availability.\n# Enable this only when you have multiple replicas of components.\nenablePodAntiAffinity: false\n\n# -- enables the creation of pod disruption budgets for tap, tap-injector, web and metrics-api components\nenablePodDisruptionBudget: false\n\n# -- NodeAffinity section, See the\n# [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity)\n# for more information\n# nodeAffinity:\n\n# -- Creates a Job that adds necessary metadata to the extension's namespace\n# during install; disable if lack of privileges require doing this manually\ncreateNamespaceMetadataJob: true\n\n# -- Create Roles and RoleBindings to associate this extension's\n# ServiceAccounts to the control plane PSP resource. This requires that\n# `enabledPSP` is set to true on the control plane install. Note PSP has been\n# deprecated since k8s v1.21\nenablePSP: false\n\n# -- url of external prometheus instance\nprometheusUrl: \"\"\n\n# -- Name of the prometheus credentials secret. If this is set, the metrics-api\n# will use basic auth to connect to prometheus and load the user and password\n# from the \"user\" and \"password\" keys respectively in the given secret. The\n# secret must be in the same namespace and must exist before the metrics-api is\n# deployed.\nprometheusCredsSecret: \"\"\n\n# -- url of external jaeger instance\n# Set this to the name of your jaeger instance, e.g. `jaeger.<namespace>.svc.<clusterDomain>:16686`.\njaegerUrl: \"\"\n\n# metrics API configuration\nmetricsAPI:\n  # -- Number of replicas of the metrics-api component\n  replicas: 1\n  # -- log level of the metrics-api component\n  # @default -- defaultLogLevel\n  logLevel: \"\"\n  # -- log format of the metrics-api component\n  # @default -- defaultLogFormat\n  logFormat: \"\"\n  image:\n    # -- Docker registry for the metrics-api component\n    # @default -- defaultRegistry\n    registry: \"\"\n    # -- Docker image name for the metrics-api component\n    name: metrics-api\n    # -- Docker image tag for the metrics-api component\n    # @default -- linkerdVersion\n    tag: \"\"\n    # -- Pull policy for the metrics-api component\n    # @default -- defaultImagePullPolicy\n    pullPolicy: \"\"\n\n  resources:\n    cpu:\n      # -- Maximum amount of CPU units that the metrics-api container can use\n      limit:\n      # -- Amount of CPU units that the metrics-api container requests\n      request:\n    memory:\n      # -- Maximum amount of memory that metrics-api container can use\n      limit:\n      # -- Amount of memory that the metrics-api container requests\n      request:\n    ephemeral-storage:\n      # -- Maximum amount of ephemeral storage that the metrics-api container can use\n      limit: \"\"\n      # -- Amount of ephemeral storage that the metrics-api container requests\n      request: \"\"\n\n  proxy:\n    # -- If set, overrides default proxy resources for the proxy injected\n    # into the metrics-api component\n    # resources:\n\n  # -- UID for the metrics-api resource\n  UID:\n\n  # -- GID for the metrics-api resource\n  GID:\n\n  # -- NodeSelector section, See the\n  # [K8S documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) for more information\n  nodeSelector: *default_node_selector\n  # -- Tolerations section, See the\n  # [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/)\n  # for more information\n  tolerations: *default_tolerations\n\n  # -- metrics-api service configuration\n  service:\n    # -- Additional annotations to add to metrics-api service\n    annotations: {}\n\n# tap configuration\ntap:\n  # -- Number of tap component replicas\n  replicas: 1\n  # -- log level of the tap component\n  # @default -- defaultLogLevel\n  logLevel: \"\"\n  # -- log format of the tap component\n  # @default -- defaultLogFormat\n  logFormat: \"\"\n  image:\n    # -- Docker registry for the tap instance\n    # @default -- defaultRegistry\n    registry: \"\"\n    # -- Docker image name for the tap instance\n    name: tap\n    # -- Docker image tag for the tap instance\n    # @default -- linkerdVersion\n    tag: \"\"\n    # -- Pull policy for the tap component\n    # @default -- defaultImagePullPolicy\n    pullPolicy: \"\"\n\n  # -- Do not create a secret resource for the Tap component.\n  # If this is set to `true`, the value `tap.caBundle` must be set\n  # or the ca bundle must injected with cert-manager ca injector using\n  # `tap.injectCaFrom` or `tap.injectCaFromSecret` (see below).\n  externalSecret: false\n\n  # -- Certificate for the Tap component. If not provided and not using an external secret\n  # then Helm will generate one.\n  crtPEM: |\n\n  # -- Certificate key for Tap component. If not provided and not using an external secret\n  # then Helm will generate one.\n  keyPEM: |\n\n  # -- Bundle of CA certificates for tap.\n  # If not provided nor injected with cert-manager,\n  # then Helm will use the certificate generated for `tap.crtPEM`.\n  # If `tap.externalSecret` is set to true, this value, injectCaFrom, or\n  # injectCaFromSecret must be set, as no certificate will be generated.\n  # See the cert-manager [CA Injector Docs](https://cert-manager.io/docs/concepts/ca-injector) for more information.\n  caBundle: |\n\n  # -- Inject the CA bundle from a cert-manager Certificate.\n  # See the cert-manager [CA Injector Docs](https://cert-manager.io/docs/concepts/ca-injector/#injecting-ca-data-from-a-certificate-resource)\n  # for more information.\n  injectCaFrom: \"\"\n\n  # -- Inject the CA bundle from a Secret.\n  # If set, the `cert-manager.io/inject-ca-from-secret` annotation will be added to the webhook.\n  # The Secret must have the CA Bundle stored in the `ca.crt` key and have\n  # the `cert-manager.io/allow-direct-injection` annotation set to `true`.\n  # See the cert-manager [CA Injector Docs](https://cert-manager.io/docs/concepts/ca-injector/#injecting-ca-data-from-a-secret-resource)\n  # for more information.\n  injectCaFromSecret: \"\"\n\n  resources:\n    cpu:\n      # -- Maximum amount of CPU units that the tap container can use\n      limit:\n      # -- Amount of CPU units that the tap container requests\n      request:\n    memory:\n      # -- Maximum amount of memory that tap container can use\n      limit:\n      # -- Amount of memory that the tap container requests\n      request:\n    ephemeral-storage:\n      # -- Maximum amount of ephemeral storage that the tap container can use\n      limit: \"\"\n      # -- Amount of ephemeral storage that the tap container requests\n      request: \"\"\n\n  # -- List of headers that will be ignored for Linkerd Tap\n  ignoreHeaders: []\n\n  proxy:\n    # -- If set, overrides default proxy resources for the proxy injected\n    # into the tap component\n    # resources:\n\n  # -- UID for the tap component\n  UID:\n\n  # -- GID for the tap component\n  GID:\n\n  # -- tap service configuration\n  service:\n    # -- Additional annotations to add to tap service\n    annotations: {}\n\n# tapInjector configuration\ntapInjector:\n  # -- Number of replicas of tapInjector\n  replicas: 1\n  # -- log level of the tapInjector\n  # @default -- defaultLogLevel\n  logLevel: \"\"\n  # -- log format of the tapInjector component\n  # @default -- defaultLogFormat\n  logFormat: \"\"\n  image:\n    # -- Docker registry for the tapInjector instance\n    # @default -- defaultRegistry\n    registry: \"\"\n    # -- Docker image name for the tapInjector instance\n    name: tap\n    # -- Docker image tag for the tapInjector instance\n    # @default -- linkerdVersion\n    tag: \"\"\n    # -- Pull policy for the tapInjector component\n    # @default -- defaultImagePullPolicy\n    pullPolicy: \"\"\n\n  # -- Namespace selector used by admission webhook.\n  namespaceSelector:\n    matchExpressions:\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n  objectSelector:\n    # matchLabels:\n    #   foo: bar\n  # -- UID for the tapInjector resource\n  UID:\n  # -- GID for the tapInjector resource\n  GID:\n  failurePolicy: Ignore\n  resources:\n    cpu:\n      # -- Maximum amount of CPU units that the tapInjector container can use\n      limit:\n      # -- Amount of CPU units that the tapInjector container requests\n      request:\n    memory:\n      # -- Maximum amount of memory that tapInjector container can use\n      limit:\n      # -- Amount of memory that the tapInjector container requests\n      request:\n    ephemeral-storage:\n      # -- Maximum amount of ephemeral storage that the tapInjector container can use\n      limit: \"\"\n      # -- Amount of ephemeral storage that the tapInjector container requests\n      request: \"\"\n  proxy:\n    # -- If set, overrides default proxy resources for the proxy injected\n    # into the tapInjector component\n    # resources:\n\n  # -- Do not create a secret resource for the tapInjector webhook.\n  # If this is set to `true`, the value `tapInjector.caBundle` must be set\n  # or the ca bundle must injected with cert-manager ca injector using\n  # `tapInjector.injectCaFrom` or `tapInjector.injectCaFromSecret` (see below).\n  externalSecret: false\n\n  # -- Certificate for the tapInjector. If not provided and not using an external secret\n  # then Helm will generate one.\n  crtPEM: |\n\n  # -- Certificate key for the tapInjector. If not provided and not using an external secret\n  # then Helm will generate one.\n  keyPEM: |\n\n  # -- Bundle of CA certificates for the tapInjector.\n  # If not provided nor injected with cert-manager,\n  # then Helm will use the certificate generated for `tapInjector.crtPEM`.\n  # If `tapInjector.externalSecret` is set to true, this value, injectCaFrom, or\n  # injectCaFromSecret must be set, as no certificate will be generated.\n  # See the cert-manager [CA Injector Docs](https://cert-manager.io/docs/concepts/ca-injector) for more information.\n  caBundle: |\n\n  # -- Inject the CA bundle from a cert-manager Certificate.\n  # See the cert-manager [CA Injector Docs](https://cert-manager.io/docs/concepts/ca-injector/#injecting-ca-data-from-a-certificate-resource)\n  # for more information.\n  injectCaFrom: \"\"\n\n  # -- Inject the CA bundle from a Secret.\n  # If set, the `cert-manager.io/inject-ca-from-secret` annotation will be added to the webhook.\n  # The Secret must have the CA Bundle stored in the `ca.crt` key and have\n  # the `cert-manager.io/allow-direct-injection` annotation set to `true`.\n  # See the cert-manager [CA Injector Docs](https://cert-manager.io/docs/concepts/ca-injector/#injecting-ca-data-from-a-secret-resource)\n  # for more information.\n  injectCaFromSecret: \"\"\n\n  # -- tap service configuration\n  service:\n    # -- Additional annotations to add to tapInjector service\n    annotations: {}\n\n# web dashboard configuration\ndashboard:\n  # -- Number of replicas of dashboard\n  replicas: 1\n  # -- log level of the dashboard component\n  # @default -- defaultLogLevel\n  logLevel: \"\"\n  # -- log format of the dashboard component\n  # @default -- defaultLogFormat\n  logFormat: \"\"\n  image:\n    # -- Docker registry for the web instance\n    # @default -- defaultRegistry\n    registry: \"\"\n    # -- Docker image name for the web instance\n    name: web\n    # -- Docker image tag for the web instance\n    # @default -- linkerdVersion\n    tag: \"\"\n    # -- Pull policy for the  web component\n    # @default -- defaultImagePullPolicy\n    pullPolicy: \"\"\n\n  # -- UID for the dashboard resource\n  UID:\n\n  # -- GID for the dashboard resource\n  GID:\n\n  # -- Restrict the Linkerd Dashboard's default privileges to disallow Tap and Check\n  restrictPrivileges: false\n\n  # -- Host header validation regex for the dashboard. See the [Linkerd\n  # documentation](https://linkerd.io/2/tasks/exposing-dashboard) for more\n  # information\n  enforcedHostRegexp: \"\"\n  resources:\n    cpu:\n      # -- Maximum amount of CPU units that the web container can use\n      limit:\n      # -- Amount of CPU units that the web container requests\n      request:\n    memory:\n      # -- Maximum amount of memory that web container can use\n      limit:\n      # -- Amount of memory that the web container requests\n      request:\n    ephemeral-storage:\n      # -- Maximum amount of ephemeral storage that the web container can use\n      limit: \"\"\n      # -- Amount of ephemeral storage that the web container requests\n      request: \"\"\n\n  proxy:\n    # -- If set, overrides default proxy resources for the proxy injected\n    # into the dashboard component\n    # resources:\n\n  # -- dashboard service configuration\n  service:\n    # -- Additional annotations to add to dashboard service\n    annotations: {}\n    # -- Additional labels to add to dashboard service\n    labels: {}\n\nnamespaceMetadata:\n  image:\n    # -- Docker registry for the namespace-metadata instance\n    # @default -- defaultRegistry\n    registry: \"\"\n    # -- Docker image name for the namespace-metadata instance\n    name: extension-init\n    # -- Docker image tag for the namespace-metadata instance\n    tag: v0.1.9\n    # -- Pull policy for the namespace-metadata instance\n    # @default -- defaultImagePullPolicy\n    pullPolicy: \"\"\n\n  # -- NodeSelector section, See the\n  # [K8S documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) for more information\n  nodeSelector: *default_node_selector\n  # -- Tolerations section, See the\n  # [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/)\n  # for more information\n  tolerations: *default_tolerations\n\ngrafana:\n  # -- url of an in-cluster Grafana instance with reverse proxy configured, used by the\n  # Linkerd viz web dashboard to provide direct links to specific Grafana\n  # dashboards. Cannot be set if grafana.externalUrl is set. See the [Linkerd\n  # documentation](https://linkerd.io/2/tasks/grafana) for more information\n  url:\n  # -- url of a Grafana instance hosted off-cluster. Cannot be set if\n  # grafana.url is set. The reverse proxy will not be used for this URL.\n  externalUrl:\n  # -- prefix for Grafana dashboard UID's, used when grafana.externalUrl is\n  # set.\n  uidPrefix:\n\nprometheus:\n  # -- toggle field to enable or disable prometheus\n  enabled: true\n  image:\n    # -- Docker registry for the prometheus instance\n    registry: prom\n    # -- Docker image name for the prometheus instance\n    name: prometheus\n    # -- Docker image tag for the prometheus instance\n    tag: v2.55.1\n    # -- Pull policy for the prometheus instance\n    # @default -- defaultImagePullPolicy\n    pullPolicy: \"\"\n\n  # -- log level of the prometheus instance\n  # @default -- defaultLogLevel\n  logLevel: \"\"\n  # -- log format (plain, json) of the prometheus instance\n  # @default -- defaultLogLevel\n  logFormat: \"\"\n  # -- Command line options for Prometheus binary\n  args:\n    storage.tsdb.path: /data\n    storage.tsdb.retention.time: 6h\n    config.file: /etc/prometheus/prometheus.yml\n  # -- The global configuration specifies parameters that are valid in all other\n  # configuration contexts.\n  globalConfig:\n    scrape_interval: 10s\n    scrape_timeout: 10s\n    evaluation_interval: 10s\n\n  # -- annotations for the prometheus pod\n  podAnnotations: {}\n\n  # -- Alert relabeling is applied to alerts before they are sent to the\n  # Alertmanager.\n  alertRelabelConfigs:\n  # Ex:\n  # - action: labeldrop\n  #   regex: prometheus_replica\n\n  # -- Alertmanager instances the Prometheus server sends alerts to configured via\n  # the static_configs parameter.\n  alertmanagers:\n  # Ex:\n  # - scheme: http\n  #   static_configs:\n  #   - targets:\n  #     - \"alertmanager.linkerd.svc:9093\"\n\n  # -- Allows transparently sending samples to an endpoint. Mostly used for long\n  # term storage.\n  remoteWrite:\n\n  # -- Alerting/recording rule ConfigMap mounts (sub-path names must end in\n  # ´_rules.yml´ or ´_rules.yaml´)\n  ruleConfigMapMounts:\n  # Ex:\n  # - name: alerting-rules\n  #   subPath: alerting_rules.yml\n  #   configMap: linkerd-prometheus-rules\n  # - name: recording-rules\n  #   subPath: recording_rules.yml\n  #   configMap: linkerd-prometheus-rules\n\n  # -- A scrapeConfigs section specifies a set of targets and parameters\n  # describing how to scrape them.\n  scrapeConfigs:\n  # Ex:\n  # - job_name: 'kubernetes-nodes'\n  #   scheme: https\n  #   tls_config:\n  #     ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n  #   bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token\n  #   kubernetes_sd_configs:\n  #   - role: node\n  #   relabel_configs:\n  #   - action: labelmap\n  #     regex: __meta_kubernetes_node_label_(.+)\n\n  # -- A metricRelabelConfigs section allows to drop high cardinality metrics.\n  # *NOTE:* Please use with caution. Some metrics are needed for linkerd-viz to\n  # function properly.\n  metricRelabelConfigs:\n  # This allows us to fine tune prometheus cardinality by dropping certain\n  # metrics as suggested here: https://itnext.io/optimizing-linkerd-metrics-in-prometheus-de607ec10f6b\n  #\n  # Ex:\n  # - action: keep\n  #   source_labels: [le]\n  #   regex: \"(?i)(|10|50|100|500|1000|10000|30000|\\\\+Inf)\"\n\n  # -- A sidecarContainers section specifies a list of secondary containers to run\n  # in the prometheus pod e.g. to export data to non-prometheus systems\n  sidecarContainers:\n  # Ex:\n  # - name: sidecar\n  #   image: gcr.io/myproject/stackdriver-prometheus-sidecar\n  #   imagePullPolicy: Always\n  #   command:\n  #   - /bin/sh\n  #   - -c\n  #   - |\n  #     exec /bin/stackdriver-prometheus-sidecar \\\n  #       --stackdriver.project-id=myproject \\\n  #       --stackdriver.kubernetes.location=us-central1 \\\n  #       --stackdriver.kubernetes.cluster-name=mycluster \\\n  #       --prometheus.wal-directory=/data/wal \\\n  #       --log.level=info\n  #   volumeMounts:\n  #   - mountPath: /data\n  #     name: data\n  #   ports:\n  #   - name: foo\n  #     containerPort: 9091\n  #     protocol: TCP\n  ### WARNING: persistence is experimental and has not been tested/vetted by the Linkerd team.\n  ### As such, please refer to https://linkerd.io/2/tasks/exporting-metrics/ for the recommended approach to metrics data retention.\n  # if enabled, creates a persistent volume claim for prometheus data\n  # https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims\n  #persistence:\n  # -- Storage class used to create prometheus data PV.\n  #  storageClass:\n  # -- PVC access mode.\n  #  accessMode:\n  # -- Prometheus data volume size.\n  #  size:\n\n  resources:\n    cpu:\n      # -- Maximum amount of CPU units that the prometheus container can use\n      limit:\n      # -- Amount of CPU units that the prometheus container requests\n      request:\n    memory:\n      # -- Maximum amount of memory that prometheus container can use\n      limit:\n      # -- Amount of memory that the prometheus container requests\n      request:\n    ephemeral-storage:\n      # -- Maximum amount of ephemeral storage that the prometheus container can use\n      limit: \"\"\n      # -- Amount of ephemeral storage that the prometheus container requests\n      request: \"\"\n\n  proxy:\n    # -- If set, overrides default proxy resources for the proxy injected\n    # into the prometheus component\n    # resources:\n\n  # -- NodeSelector section, See the\n  # [K8S documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) for more information\n  nodeSelector: *default_node_selector\n  # -- Tolerations section, See the\n  # [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/)\n  # for more information\n  tolerations: *default_tolerations\n"
  },
  {
    "path": "viz/charts/templates.go",
    "content": "package charts\n\nimport (\n\t\"embed\"\n)\n\n//go:embed linkerd-viz\nvar Templates embed.FS\n"
  },
  {
    "path": "viz/cmd/allow-scrapes.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"text/template\"\n\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\t\"github.com/spf13/cobra\"\n)\n\nconst (\n\tallowScrapePolicy = `---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  name: proxy-admin\n  namespace: {{ .TargetNs }}\n  annotations:\n    linkerd-io/created-by: {{ .ChartName }} {{ .Version }}\n  labels:\n    linkerd.io/extension: {{ .ExtensionName }}\nspec:\n  podSelector:\n    matchExpressions:\n    - key: linkerd.io/control-plane-ns\n      operator: Exists\n  port: linkerd-admin\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: HTTPRoute\nmetadata:\n  name: proxy-metrics\n  namespace: {{ .TargetNs }}\n  annotations:\n    linkerd-io/created-by: {{ .ChartName }} {{ .Version }}\n  labels:\n    linkerd.io/extension: {{ .ExtensionName }}\nspec:\n  parentRefs:\n    - name: proxy-admin\n      kind: Server\n      group: policy.linkerd.io\n  rules:\n    - matches:\n      - path:\n          value: \"/metrics\"\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: HTTPRoute\nmetadata:\n  name: proxy-probes\n  namespace: {{ .TargetNs }}\n  annotations:\n    linkerd-io/created-by: {{ .ChartName }} {{ .Version }}\n  labels:\n    linkerd.io/extension: {{ .ExtensionName }}\nspec:\n  parentRefs:\n    - name: proxy-admin\n      kind: Server\n      group: policy.linkerd.io\n  rules:\n    - matches:\n      - path:\n          value: \"/live\"\n      - path:\n          value: \"/ready\"\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  name: prometheus-scrape\n  namespace: {{ .TargetNs }}\n  annotations:\n    linkerd-io/created-by: {{ .ChartName }} {{ .Version }}\n  labels:\n    linkerd.io/extension: {{ .ExtensionName }}\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: HTTPRoute\n    name: proxy-metrics\n  requiredAuthenticationRefs:\n    - kind: ServiceAccount\n      name: prometheus\n      namespace: {{ .VizNs }}\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  name: proxy-probes\n  namespace: {{ .TargetNs }}\n  annotations:\n    linkerd-io/created-by: {{ .ChartName }} {{ .Version }}\n  labels:\n    linkerd.io/extension: {{ .ExtensionName }}\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: HTTPRoute\n    name: proxy-probes\n  requiredAuthenticationRefs:\n    - kind: NetworkAuthentication\n      group: policy.linkerd.io\n      name: kubelet\n      namespace: {{ .VizNs }}`\n)\n\ntype templateOptions struct {\n\tChartName     string\n\tVersion       string\n\tExtensionName string\n\tVizNs         string\n\tTargetNs      string\n}\n\n// newCmdAllowScrapes creates a new cobra command `allow-scrapes`\nfunc newCmdAllowScrapes() *cobra.Command {\n\toutput := \"yaml\"\n\toptions := templateOptions{\n\t\tExtensionName: ExtensionName,\n\t\tChartName:     VizChartName,\n\t\tVersion:       version.Version,\n\t\tVizNs:         defaultNamespace,\n\t}\n\tcmd := &cobra.Command{\n\t\tUse:   \"allow-scrapes {-n | --namespace } namespace\",\n\t\tShort: \"Output Kubernetes resources to authorize Prometheus scrapes\",\n\t\tLong:  `Output Kubernetes resources to authorize Prometheus scrapes in a namespace or cluster with config.linkerd.io/default-inbound-policy: deny.`,\n\t\tExample: `# Allow scrapes in the 'emojivoto' namespace\nlinkerd viz allow-scrapes --namespace emojivoto | kubectl apply -f -`,\n\t\tArgs: cobra.NoArgs,\n\t\tPreRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn cmd.MarkFlagRequired(\"namespace\")\n\t\t},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tt := template.Must(template.New(\"allow-scrapes\").Parse(allowScrapePolicy))\n\t\t\tvar buf bytes.Buffer\n\t\t\terr := t.Execute(&buf, options)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\treturn pkgcmd.RenderYAMLAs(&buf, stdout, output)\n\t\t},\n\t}\n\tcmd.Flags().StringVarP(&options.TargetNs, \"namespace\", \"n\", options.TargetNs, \"The namespace in which to authorize Prometheus scrapes.\")\n\tcmd.Flags().StringVarP(&output, \"output\", \"o\", output, \"Output format. One of: json|yaml\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tcmd, []string{\"n\", \"namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\treturn cmd\n}\n"
  },
  {
    "path": "viz/cmd/authz.go",
    "content": "package cmd\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/cli/table\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/metrics-api/util\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/api\"\n\thc \"github.com/linkerd/linkerd2/viz/pkg/healthcheck\"\n\tpkgUtil \"github.com/linkerd/linkerd2/viz/pkg/util\"\n\t\"github.com/spf13/cobra\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// NewCmdAuthz creates a new cobra command `authz`\nfunc NewCmdAuthz() *cobra.Command {\n\toptions := newStatOptions()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"authz [flags] resource\",\n\t\tShort: \"Display stats for authorizations for a resource\",\n\t\tLong:  \"Display stats for authorizations for a resource.\",\n\t\tArgs:  cobra.MinimumNArgs(1),\n\t\tValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\tcc := k8s.NewCommandCompletion(k8sAPI, options.namespace)\n\n\t\t\tresults, err := cc.Complete(args, toComplete)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\treturn results, cobra.ShellCompDirectiveDefault\n\t\t},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\t// The gRPC client is concurrency-safe, so we can reuse it in all the following goroutines\n\t\t\t// https://github.com/grpc/grpc-go/issues/682\n\t\t\tclient := api.CheckClientOrExit(hc.VizOptions{\n\t\t\t\tOptions: &healthcheck.Options{\n\t\t\t\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\t\t\t\tKubeConfig:            kubeconfigPath,\n\t\t\t\t\tImpersonate:           impersonate,\n\t\t\t\t\tImpersonateGroup:      impersonateGroup,\n\t\t\t\t\tKubeContext:           kubeContext,\n\t\t\t\t\tAPIAddr:               apiAddr,\n\t\t\t\t},\n\t\t\t\tVizNamespaceOverride: vizNamespace,\n\t\t\t})\n\n\t\t\tvar resource string\n\t\t\tif len(args) == 1 {\n\t\t\t\tresource = args[0]\n\t\t\t} else if len(args) == 2 {\n\t\t\t\tresource = args[0] + \"/\" + args[1]\n\t\t\t}\n\n\t\t\tcols := []table.Column{\n\t\t\t\ttable.NewColumn(\"ROUTE\").WithLeftAlign(),\n\t\t\t\ttable.NewColumn(\"SERVER\").WithLeftAlign(),\n\t\t\t\ttable.NewColumn(\"AUTHORIZATION\").WithLeftAlign(),\n\t\t\t\ttable.NewColumn(\"UNAUTHORIZED\"),\n\t\t\t\ttable.NewColumn(\"SUCCESS\"),\n\t\t\t\ttable.NewColumn(\"RPS\"),\n\t\t\t\ttable.NewColumn(\"LATENCY_P50\"),\n\t\t\t\ttable.NewColumn(\"LATENCY_P95\"),\n\t\t\t\ttable.NewColumn(\"LATENCY_P99\"),\n\t\t\t}\n\t\t\trows := []table.Row{}\n\n\t\t\treq := pb.AuthzRequest{}\n\t\t\twindow, err := util.ValidateTimeWindow(options.timeWindow)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treq.TimeWindow = window\n\n\t\t\ttarget, err := pkgUtil.BuildResource(options.namespace, resource)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif options.allNamespaces && target.Name != \"\" {\n\t\t\t\treturn errors.New(\"stats for a resource cannot be retrieved by name across all namespaces\")\n\t\t\t}\n\n\t\t\tif options.allNamespaces {\n\t\t\t\ttarget.Namespace = \"\"\n\t\t\t} else if target.Namespace == \"\" {\n\t\t\t\ttarget.Namespace = corev1.NamespaceDefault\n\t\t\t}\n\n\t\t\treq.Resource = target\n\n\t\t\tresp, err := client.Authz(cmd.Context(), &req)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"Authz API error: %s\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tif e := resp.GetError(); e != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"Authz API error: %s\", e.Error)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\tfor _, row := range resp.GetOk().GetStatTable().GetPodGroup().GetRows() {\n\t\t\t\tserver := row.GetSrvStats().GetSrv().GetName()\n\t\t\t\tif row.GetSrvStats().GetSrv().GetType() == \"default\" {\n\t\t\t\t\tserver = fmt.Sprintf(\"%s:%s\", row.GetSrvStats().GetSrv().GetType(), row.GetSrvStats().GetSrv().GetName())\n\t\t\t\t}\n\t\t\t\tauthz := fmt.Sprintf(\"%s/%s\", row.GetSrvStats().GetAuthz().GetType(), row.GetSrvStats().GetAuthz().GetName())\n\t\t\t\tif row.GetSrvStats().GetAuthz().GetType() == \"\" {\n\t\t\t\t\tauthz = \"\"\n\t\t\t\t}\n\t\t\t\tif row.GetStats().GetSuccessCount()+row.GetStats().GetFailureCount()+row.GetSrvStats().GetDeniedCount() > 0 {\n\t\t\t\t\trows = append(rows, table.Row{\n\t\t\t\t\t\trow.GetSrvStats().GetRoute().GetName(),\n\t\t\t\t\t\tserver,\n\t\t\t\t\t\tauthz,\n\t\t\t\t\t\tfmt.Sprintf(\"%.1frps\", getRequestRate(row.GetSrvStats().GetDeniedCount(), 0, window)),\n\t\t\t\t\t\tfmt.Sprintf(\"%.2f%%\", getSuccessRate(row.Stats.GetSuccessCount(), row.Stats.GetFailureCount())*100),\n\t\t\t\t\t\tfmt.Sprintf(\"%.1frps\", getRequestRate(row.Stats.GetSuccessCount(), row.Stats.GetFailureCount(), window)),\n\t\t\t\t\t\tfmt.Sprintf(\"%dms\", row.Stats.LatencyMsP50),\n\t\t\t\t\t\tfmt.Sprintf(\"%dms\", row.Stats.LatencyMsP95),\n\t\t\t\t\t\tfmt.Sprintf(\"%dms\", row.Stats.LatencyMsP99),\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\tif row.GetSrvStats().GetAuthz().GetType() == \"\" || row.GetSrvStats().GetAuthz().GetType() == \"default\" {\n\t\t\t\t\t\t// Skip showing the default or unauthorized entries if there are no requests for them.\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\trows = append(rows, table.Row{\n\t\t\t\t\t\trow.GetSrvStats().GetRoute().GetName(),\n\t\t\t\t\t\tserver,\n\t\t\t\t\t\tauthz,\n\t\t\t\t\t\t\"-\",\n\t\t\t\t\t\t\"-\",\n\t\t\t\t\t\t\"-\",\n\t\t\t\t\t\t\"-\",\n\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\n\t\t\tdata := table.NewTable(cols, rows)\n\t\t\tdata.Sort = []int{1, 0, 2} // Sort by Server, then Route, then Authorization\n\t\t\tif options.outputFormat == \"json\" {\n\t\t\t\terr = renderJSON(data, os.Stdout)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Fprint(os.Stderr, err.Error())\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdata.Render(os.Stdout)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace, \"Namespace of the specified resource\")\n\tcmd.PersistentFlags().StringVarP(&options.timeWindow, \"time-window\", \"t\", options.timeWindow, \"Stat window (for example: \\\"15s\\\", \\\"1m\\\", \\\"10m\\\", \\\"1h\\\"). Needs to be at least 15s.\")\n\tcmd.PersistentFlags().StringVarP(&options.outputFormat, \"output\", \"o\", options.outputFormat, \"Output format; one of: \\\"table\\\" or \\\"json\\\" or \\\"wide\\\"\")\n\tcmd.PersistentFlags().StringVarP(&options.labelSelector, \"selector\", \"l\", options.labelSelector, \"Selector (label query) to filter on, supports '=', '==', and '!='\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tcmd, []string{\"namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\treturn cmd\n}\n\nfunc renderJSON(t table.Table, w io.Writer) error {\n\trows := make([]map[string]interface{}, len(t.Data))\n\tfor i, data := range t.Data {\n\t\trows[i] = make(map[string]interface{})\n\t\tfor j, col := range t.Columns {\n\t\t\tif data[j] == \"-\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfield := strings.ToLower(col.Header)\n\t\t\tvar percentile string\n\n\t\t\tif n, _ := fmt.Sscanf(field, \"latency_%s\", &percentile); n == 1 {\n\t\t\t\tvar latency int\n\t\t\t\tn, _ := fmt.Sscanf(data[j], \"%dms\", &latency)\n\t\t\t\tif n == 1 {\n\t\t\t\t\trows[i][\"latency_ms_\"+percentile] = latency\n\t\t\t\t} else {\n\t\t\t\t\trows[i][\"latency_ms_\"+percentile] = data[j]\n\t\t\t\t}\n\t\t\t} else if field == \"rps\" || field == \"unauthorized\" {\n\t\t\t\tvar rps float32\n\t\t\t\tif n, _ := fmt.Sscanf(data[j], \"%frps\", &rps); n == 1 {\n\t\t\t\t\trows[i][field] = rps\n\t\t\t\t} else {\n\t\t\t\t\trows[i][field] = data[j]\n\t\t\t\t}\n\t\t\t} else if field == \"success\" {\n\t\t\t\tvar success float32\n\t\t\t\tif n, _ := fmt.Sscanf(data[j], \"%f%%\", &success); n == 1 {\n\t\t\t\t\trows[i][field] = success / 100.0\n\t\t\t\t} else {\n\t\t\t\t\trows[i][field] = data[j]\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\trows[i][field] = data[j]\n\t\t\t}\n\t\t}\n\t}\n\tout, err := json.MarshalIndent(rows, \"\", \"  \")\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = w.Write(out)\n\treturn err\n}\n"
  },
  {
    "path": "viz/cmd/check.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"time\"\n\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\tvizHealthCheck \"github.com/linkerd/linkerd2/viz/pkg/healthcheck\"\n\t\"github.com/spf13/cobra\"\n)\n\ntype checkOptions struct {\n\tproxy     bool\n\twait      time.Duration\n\tnamespace string\n\toutput    string\n}\n\nfunc newCheckOptions() *checkOptions {\n\treturn &checkOptions{\n\t\twait:   300 * time.Second,\n\t\toutput: healthcheck.TableOutput,\n\t}\n}\n\nfunc (options *checkOptions) validate() error {\n\tif options.output != healthcheck.TableOutput && options.output != healthcheck.JSONOutput && options.output != healthcheck.ShortOutput {\n\t\treturn fmt.Errorf(\"Invalid output type '%s'. Supported output types are: %s, %s, %s\", options.output, healthcheck.JSONOutput, healthcheck.TableOutput, healthcheck.ShortOutput)\n\t}\n\treturn nil\n}\n\n// NewCmdCheck generates a new cobra command for the viz extension.\nfunc NewCmdCheck() *cobra.Command {\n\toptions := newCheckOptions()\n\tcmd := &cobra.Command{\n\t\tUse:   \"check [flags]\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Check the Linkerd Viz extension for potential problems\",\n\t\tLong: `Check the Linkerd Viz extension for potential problems.\n\nThe check command will perform a series of checks to validate that the Linkerd Viz\nextension is configured correctly. If the command encounters a failure it will\nprint additional information about the failure and exit with a non-zero exit\ncode.`,\n\t\tExample: `  # Check that the viz extension is up and running\n  linkerd viz check`,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\n\t\t\treturn configureAndRunChecks(stdout, stderr, options)\n\t\t},\n\t}\n\n\tcmd.Flags().StringVarP(&options.output, \"output\", \"o\", options.output, \"Output format. One of: table, json, short\")\n\tcmd.Flags().BoolVar(&options.proxy, \"proxy\", options.proxy, \"Also run data-plane checks, to determine if the data plane is healthy\")\n\tcmd.Flags().DurationVar(&options.wait, \"wait\", options.wait, \"Maximum allowed time for all tests to pass\")\n\tcmd.Flags().StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace, \"Namespace to use for --proxy checks (default: all namespaces)\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tcmd, []string{\"namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\treturn cmd\n}\n\nfunc configureAndRunChecks(wout io.Writer, werr io.Writer, options *checkOptions) error {\n\terr := options.validate()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"validation error when executing check command: %w\", err)\n\t}\n\n\thc := vizHealthCheck.NewHealthChecker([]healthcheck.CategoryID{}, &vizHealthCheck.VizOptions{\n\t\tOptions: &healthcheck.Options{\n\t\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\t\tKubeConfig:            kubeconfigPath,\n\t\t\tKubeContext:           kubeContext,\n\t\t\tImpersonate:           impersonate,\n\t\t\tImpersonateGroup:      impersonateGroup,\n\t\t\tAPIAddr:               apiAddr,\n\t\t\tRetryDeadline:         time.Now().Add(options.wait),\n\t\t\tDataPlaneNamespace:    options.namespace,\n\t\t},\n\t\tVizNamespaceOverride: vizNamespace,\n\t})\n\terr = hc.InitializeKubeAPIClient()\n\tif err != nil {\n\t\tfmt.Fprintf(werr, \"Error initializing k8s API client: %s\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\thc.AppendCategories(hc.VizCategory(true))\n\tif options.proxy {\n\t\thc.AppendCategories(hc.VizDataPlaneCategory())\n\t}\n\tsuccess, warning := healthcheck.RunChecks(wout, werr, hc, options.output)\n\thealthcheck.PrintChecksResult(wout, options.output, success, warning)\n\n\tif !success {\n\t\tos.Exit(1)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "viz/cmd/dashboard.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/api\"\n\thc \"github.com/linkerd/linkerd2/viz/pkg/healthcheck\"\n\t\"github.com/pkg/browser\"\n\t\"github.com/spf13/cobra\"\n)\n\n// These constants are used by the `show` flag.\nconst (\n\t// showLinkerd opens the Linkerd dashboard in a web browser (default).\n\tshowLinkerd = \"linkerd\"\n\n\t// showGrafana opens the Grafana dashboard in a web browser.\n\tshowGrafana = \"grafana\"\n\n\t// showURL displays dashboard URLs without opening a browser.\n\tshowURL = \"url\"\n\n\t// webDeployment is the name of the web deployment in cli/install/template.go\n\twebDeployment = \"web\"\n\n\t// webPort is the http port from the web pod spec in cli/install/template.go\n\twebPort = 8084\n\n\t// defaultHost is the default host used for port-forwarding via `linkerd dashboard`\n\tdefaultHost = \"localhost\"\n\n\t// defaultPort is for port-forwarding via `linkerd dashboard`\n\tdefaultPort = 50750\n)\n\n// dashboardOptions holds values for command line flags that apply to the dashboard\n// command.\ntype dashboardOptions struct {\n\thost string\n\tport int\n\tshow string\n\twait time.Duration\n}\n\n// newDashboardOptions initializes dashboard options with default\n// values for host, port, and which dashboard to show. Also, set\n// max wait time duration for 300 seconds for the dashboard to\n// become available\n//\n// These options may be overridden on the CLI at run-time\nfunc newDashboardOptions() *dashboardOptions {\n\treturn &dashboardOptions{\n\t\thost: defaultHost,\n\t\tport: defaultPort,\n\t\tshow: showLinkerd,\n\t\twait: 300 * time.Second,\n\t}\n}\n\n// NewCmdDashboard creates a new cobra command `dashboard` which contains commands for visualizing linkerd's dashboards.\n// After validating flag values, it will use the Kubernetes API to portforward requests to the Grafana and Web Deployments\n// until the process gets killed/canceled\nfunc NewCmdDashboard() *cobra.Command {\n\toptions := newDashboardOptions()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"dashboard [flags]\",\n\t\tShort: \"Open the Linkerd dashboard in a web browser\",\n\t\tArgs:  cobra.NoArgs,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif options.port < 0 {\n\t\t\t\treturn fmt.Errorf(\"port must be greater than or equal to zero, was %d\", options.port)\n\t\t\t}\n\n\t\t\tif options.show != showLinkerd && options.show != showGrafana && options.show != showURL {\n\t\t\t\treturn fmt.Errorf(\"unknown value for 'show' param, was: %s, must be one of: %s, %s, %s\",\n\t\t\t\t\toptions.show, showLinkerd, showGrafana, showURL)\n\t\t\t}\n\n\t\t\t// ensure we can connect to the viz API before starting the proxy\n\t\t\tapi.CheckClientOrRetryOrExit(hc.VizOptions{\n\t\t\t\tOptions: &healthcheck.Options{\n\t\t\t\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\t\t\t\tKubeConfig:            kubeconfigPath,\n\t\t\t\t\tImpersonate:           impersonate,\n\t\t\t\t\tImpersonateGroup:      impersonateGroup,\n\t\t\t\t\tKubeContext:           kubeContext,\n\t\t\t\t\tAPIAddr:               apiAddr,\n\t\t\t\t\tRetryDeadline:         time.Now().Add(options.wait),\n\t\t\t\t},\n\t\t\t\tVizNamespaceOverride: vizNamespace,\n\t\t\t}, true)\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tvizNs, err := k8sAPI.GetNamespaceWithExtensionLabel(context.Background(), ExtensionName)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tsignals := make(chan os.Signal, 1)\n\t\t\tsignal.Notify(signals, os.Interrupt)\n\t\t\tdefer signal.Stop(signals)\n\n\t\t\tportforward, err := k8s.NewPortForward(\n\t\t\t\tcmd.Context(),\n\t\t\t\tk8sAPI,\n\t\t\t\tvizNs.Name,\n\t\t\t\twebDeployment,\n\t\t\t\toptions.host,\n\t\t\t\toptions.port,\n\t\t\t\twebPort,\n\t\t\t\tverbose,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"Failed to initialize port-forward: %s\\n\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\tif err = portforward.Init(); err != nil {\n\t\t\t\t// TODO: consider falling back to an ephemeral port if defaultPort is taken\n\t\t\t\tfmt.Fprintf(os.Stderr, \"Error running port-forward: %s\\nCheck for `linkerd dashboard` running in other terminal sessions, or use the `--port` flag.\\n\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\tgo func() {\n\t\t\t\t<-signals\n\t\t\t\tportforward.Stop()\n\t\t\t}()\n\n\t\t\twebURL := portforward.URLFor(\"\")\n\t\t\tgrafanaURL := portforward.URLFor(\"/grafana\")\n\n\t\t\tfmt.Printf(\"Linkerd dashboard available at:\\n%s\\n\", webURL)\n\t\t\tfmt.Printf(\"Grafana dashboard available at:\\n%s\\n\", grafanaURL)\n\n\t\t\tswitch options.show {\n\t\t\tcase showLinkerd:\n\t\t\t\tfmt.Println(\"Opening Linkerd dashboard in the default browser\")\n\n\t\t\t\terr = browser.OpenURL(webURL)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Fprintln(os.Stderr, \"Failed to open Linkerd dashboard automatically\")\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"Visit %s in your browser to view the dashboard\\n\", webURL)\n\t\t\t\t}\n\t\t\tcase showGrafana:\n\t\t\t\tfmt.Println(\"Opening Grafana dashboard in the default browser\")\n\n\t\t\t\terr = browser.OpenURL(grafanaURL)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Fprintln(os.Stderr, \"Failed to open Grafana dashboard automatically\")\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"Visit %s in your browser to view the dashboard\\n\", grafanaURL)\n\t\t\t\t}\n\t\t\tcase showURL:\n\t\t\t\t// no-op, we already printed the URLs\n\t\t\t}\n\n\t\t\t<-portforward.GetStop()\n\t\t\treturn nil\n\t\t},\n\t}\n\n\t// This is identical to what `kubectl proxy --help` reports, `--port 0` indicates a random port.\n\tcmd.PersistentFlags().StringVar(&options.host, \"address\", options.host, \"The address at which to serve requests\")\n\tcmd.PersistentFlags().IntVarP(&options.port, \"port\", \"p\", options.port, \"The local port on which to serve requests (when set to 0, a random port will be used)\")\n\tcmd.PersistentFlags().StringVar(&options.show, \"show\", options.show, \"Open a dashboard in a browser or show URLs in the CLI (one of: linkerd, grafana, url)\")\n\tcmd.PersistentFlags().DurationVar(&options.wait, \"wait\", options.wait, \"Wait for dashboard to become available if it's not available when the command is run\")\n\n\treturn cmd\n}\n"
  },
  {
    "path": "viz/cmd/edges.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\n\t\"github.com/fatih/color\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/metrics-api/util\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/api\"\n\thc \"github.com/linkerd/linkerd2/viz/pkg/healthcheck\"\n\tpkgUtil \"github.com/linkerd/linkerd2/viz/pkg/util\"\n\t\"github.com/spf13/cobra\"\n\tv1 \"k8s.io/api/core/v1\"\n)\n\nvar (\n\tokStatus = color.New(color.FgGreen, color.Bold).SprintFunc()(\"\\u221A\") // √\n\n)\n\ntype edgesOptions struct {\n\tnamespace     string\n\toutputFormat  string\n\tallNamespaces bool\n}\n\nfunc newEdgesOptions() *edgesOptions {\n\treturn &edgesOptions{\n\t\toutputFormat:  tableOutput,\n\t\tallNamespaces: false,\n\t}\n}\n\ntype indexedEdgeResults struct {\n\tix   int\n\trows []*pb.Edge\n\terr  error\n}\n\n// NewCmdEdges creates a new cobra command `edges` for edges functionality\nfunc NewCmdEdges() *cobra.Command {\n\toptions := newEdgesOptions()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"edges [flags] (RESOURCETYPE)\",\n\t\tShort: \"Display connections between resources, and Linkerd proxy identities\",\n\t\tLong: `Display connections between resources, and Linkerd proxy identities.\n\n  The RESOURCETYPE argument specifies the type of resource to display edges within.\n\n  Examples:\n  * cronjob\n  * deploy\n  * ds\n  * job\n  * po\n  * rc\n  * rs\n  * sts\n\n  Valid resource types include:\n  * cronjobs\n  * daemonsets\n  * deployments\n  * jobs\n  * pods\n  * replicasets\n  * replicationcontrollers\n  * statefulsets`,\n\t\tExample: `  # Get all edges between pods that either originate from or terminate in the test namespace.\n  linkerd viz edges po -n test\n\n  # Get all edges between pods that either originate from or terminate in the default namespace.\n  linkerd viz edges po\n\n  # Get all edges between pods in all namespaces.\n  linkerd viz edges po --all-namespaces`,\n\t\tArgs: cobra.ExactArgs(1),\n\t\tValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\t\t// This command requires only one argument. If we already have\n\t\t\t// one after requesting autocompletion i.e. [tab][tab]\n\t\t\t// skip running validArgsFunction\n\t\t\tif len(args) > 0 {\n\t\t\t\treturn []string{}, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\tif options.allNamespaces {\n\t\t\t\toptions.namespace = v1.NamespaceAll\n\t\t\t}\n\n\t\t\tcc := k8s.NewCommandCompletion(k8sAPI, options.namespace)\n\n\t\t\tresults, err := cc.Complete(args, toComplete)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\treturn results, cobra.ShellCompDirectiveDefault\n\t\t},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\treqs, err := buildEdgesRequests(args, options)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"Error creating edges request: %w\", err)\n\t\t\t}\n\n\t\t\t// The gRPC client is concurrency-safe, so we can reuse it in all the following goroutines\n\t\t\t// https://github.com/grpc/grpc-go/issues/682\n\t\t\tclient := api.CheckClientOrExit(hc.VizOptions{\n\t\t\t\tOptions: &healthcheck.Options{\n\t\t\t\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\t\t\t\tKubeConfig:            kubeconfigPath,\n\t\t\t\t\tImpersonate:           impersonate,\n\t\t\t\t\tImpersonateGroup:      impersonateGroup,\n\t\t\t\t\tKubeContext:           kubeContext,\n\t\t\t\t\tAPIAddr:               apiAddr,\n\t\t\t\t},\n\t\t\t\tVizNamespaceOverride: vizNamespace,\n\t\t\t})\n\n\t\t\tc := make(chan indexedEdgeResults, len(reqs))\n\t\t\tfor num, req := range reqs {\n\t\t\t\tgo func(num int, req *pb.EdgesRequest) {\n\t\t\t\t\tresp, err := requestEdgesFromAPI(client, req)\n\t\t\t\t\trows := edgesRespToRows(resp)\n\t\t\t\t\tc <- indexedEdgeResults{num, rows, err}\n\t\t\t\t}(num, req)\n\t\t\t}\n\n\t\t\ttotalRows := make([]*pb.Edge, 0)\n\t\t\ti := 0\n\t\t\tfor res := range c {\n\t\t\t\tif res.err != nil {\n\t\t\t\t\tfmt.Fprint(os.Stderr, res.err.Error())\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t\ttotalRows = append(totalRows, res.rows...)\n\t\t\t\tif i++; i == len(reqs) {\n\t\t\t\t\tclose(c)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\toutput := renderEdgeStats(totalRows, options)\n\t\t\t_, err = fmt.Print(output)\n\n\t\t\treturn err\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace, \"Namespace of the specified resource\")\n\tcmd.PersistentFlags().StringVarP(&options.outputFormat, \"output\", \"o\", options.outputFormat, \"Output format; one of: \\\"table\\\" or \\\"json\\\" or \\\"wide\\\"\")\n\tcmd.PersistentFlags().BoolVarP(&options.allNamespaces, \"all-namespaces\", \"A\", options.allNamespaces, \"If present, returns edges across all namespaces, ignoring the \\\"--namespace\\\" flag\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tcmd, []string{\"namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\treturn cmd\n}\n\n// validateEdgesRequestInputs ensures that the resource type and output format are both supported\n// by the edges command, since the edges command does not support all k8s resource types.\nfunc validateEdgesRequestInputs(targets []*pb.Resource, options *edgesOptions) error {\n\tfor _, target := range targets {\n\t\tif target.Name != \"\" {\n\t\t\treturn fmt.Errorf(\"Edges cannot be returned for a specific resource name; remove %s from query\", target.Name)\n\t\t}\n\t\tswitch target.Type {\n\t\tcase \"authority\", \"service\", \"all\":\n\t\t\treturn fmt.Errorf(\"Resource type is not supported: %s\", target.Type)\n\t\t}\n\t}\n\n\tswitch options.outputFormat {\n\tcase tableOutput, jsonOutput, wideOutput:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"--output supports %s, %s and %s\", tableOutput, jsonOutput, wideOutput)\n\t}\n}\n\nfunc buildEdgesRequests(resources []string, options *edgesOptions) ([]*pb.EdgesRequest, error) {\n\ttargets, err := pkgUtil.BuildResources(options.namespace, resources)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = validateEdgesRequestInputs(targets, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trequests := make([]*pb.EdgesRequest, 0)\n\tfor _, target := range targets {\n\t\trequestParams := util.EdgesRequestParams{\n\t\t\tResourceType:  target.Type,\n\t\t\tNamespace:     options.namespace,\n\t\t\tAllNamespaces: options.allNamespaces,\n\t\t}\n\n\t\treq, err := util.BuildEdgesRequest(requestParams)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trequests = append(requests, req)\n\t}\n\treturn requests, nil\n}\n\nfunc edgesRespToRows(resp *pb.EdgesResponse) []*pb.Edge {\n\trows := make([]*pb.Edge, 0)\n\tif resp != nil {\n\t\trows = append(rows, resp.GetOk().Edges...)\n\t}\n\treturn rows\n}\n\nfunc requestEdgesFromAPI(client pb.ApiClient, req *pb.EdgesRequest) (*pb.EdgesResponse, error) {\n\tresp, err := client.Edges(context.Background(), req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Edges API error: %w\", err)\n\t}\n\tif e := resp.GetError(); e != nil {\n\t\treturn nil, fmt.Errorf(\"Edges API response error: %s\", e.Error)\n\t}\n\treturn resp, nil\n}\n\nfunc renderEdgeStats(rows []*pb.Edge, options *edgesOptions) string {\n\tvar buffer bytes.Buffer\n\tw := tabwriter.NewWriter(&buffer, 0, 0, padding, ' ', tabwriter.AlignRight)\n\twriteEdgesToBuffer(rows, w, options)\n\tw.Flush()\n\n\treturn renderEdges(buffer, options)\n}\n\ntype edgeRow struct {\n\tsrc          string\n\tsrcNamespace string\n\tdst          string\n\tdstNamespace string\n\tclient       string\n\tserver       string\n\tmsg          string\n}\n\nconst (\n\tsrcHeader          = \"SRC\"\n\tdstHeader          = \"DST\"\n\tsrcNamespaceHeader = \"SRC_NS\"\n\tdstNamespaceHeader = \"DST_NS\"\n\tclientHeader       = \"CLIENT_ID\"\n\tserverHeader       = \"SERVER_ID\"\n\tmsgHeader          = \"SECURED\"\n)\n\nfunc writeEdgesToBuffer(rows []*pb.Edge, w *tabwriter.Writer, options *edgesOptions) {\n\tmaxSrcLength := len(srcHeader)\n\tmaxDstLength := len(dstHeader)\n\tmaxSrcNamespaceLength := len(srcNamespaceHeader)\n\tmaxDstNamespaceLength := len(dstNamespaceHeader)\n\tmaxClientLength := len(clientHeader)\n\tmaxServerLength := len(serverHeader)\n\tmaxMsgLength := len(msgHeader)\n\n\tedgeRows := []edgeRow{}\n\tfor _, r := range rows {\n\t\tclientID := r.ClientId\n\t\tserverID := r.ServerId\n\t\tmsg := r.NoIdentityMsg\n\t\tif msg == \"\" && options.outputFormat != jsonOutput {\n\t\t\tmsg = okStatus\n\t\t}\n\t\tif len(clientID) > 0 {\n\t\t\tparts := strings.Split(clientID, \".\")\n\t\t\tclientID = parts[0] + \".\" + parts[1]\n\t\t}\n\t\tif len(serverID) > 0 {\n\t\t\tparts := strings.Split(serverID, \".\")\n\t\t\tserverID = parts[0] + \".\" + parts[1]\n\t\t}\n\n\t\trow := edgeRow{\n\t\t\tclient:       clientID,\n\t\t\tserver:       serverID,\n\t\t\tmsg:          msg,\n\t\t\tsrc:          r.Src.Name,\n\t\t\tsrcNamespace: r.Src.Namespace,\n\t\t\tdst:          r.Dst.Name,\n\t\t\tdstNamespace: r.Dst.Namespace,\n\t\t}\n\n\t\tedgeRows = append(edgeRows, row)\n\n\t\tif len(r.Src.Name) > maxSrcLength {\n\t\t\tmaxSrcLength = len(r.Src.Name)\n\t\t}\n\t\tif len(r.Src.Namespace) > maxSrcNamespaceLength {\n\t\t\tmaxSrcNamespaceLength = len(r.Src.Namespace)\n\t\t}\n\t\tif len(r.Dst.Name) > maxDstLength {\n\t\t\tmaxDstLength = len(r.Dst.Name)\n\t\t}\n\t\tif len(r.Dst.Namespace) > maxDstNamespaceLength {\n\t\t\tmaxDstNamespaceLength = len(r.Dst.Namespace)\n\t\t}\n\t\tif len(clientID) > maxClientLength {\n\t\t\tmaxClientLength = len(clientID)\n\t\t}\n\t\tif len(serverID) > maxServerLength {\n\t\t\tmaxServerLength = len(serverID)\n\t\t}\n\t\tif len(msg) > maxMsgLength {\n\t\t\tmaxMsgLength = len(msg)\n\t\t}\n\t}\n\n\t// ordering the rows first by SRC/DST namespace, then by SRC/DST resource\n\tsort.Slice(edgeRows, func(i, j int) bool {\n\t\tkeyI := edgeRows[i].srcNamespace + edgeRows[i].dstNamespace + edgeRows[i].src + edgeRows[i].dst\n\t\tkeyJ := edgeRows[j].srcNamespace + edgeRows[j].dstNamespace + edgeRows[j].src + edgeRows[j].dst\n\t\treturn keyI < keyJ\n\t})\n\n\tswitch options.outputFormat {\n\tcase tableOutput, wideOutput:\n\t\tif len(edgeRows) == 0 {\n\t\t\tfmt.Fprintln(os.Stderr, \"No edges found.\")\n\t\t\tos.Exit(0)\n\t\t}\n\t\tprintEdgeTable(edgeRows, w, maxSrcLength, maxSrcNamespaceLength, maxDstLength, maxDstNamespaceLength, maxClientLength, maxServerLength, maxMsgLength, options.outputFormat)\n\tcase jsonOutput:\n\t\tprintEdgesJSON(edgeRows, w)\n\t}\n}\n\nfunc printEdgeTable(edgeRows []edgeRow, w *tabwriter.Writer, maxSrcLength, maxSrcNamespaceLength, maxDstLength, maxDstNamespaceLength, maxClientLength, maxServerLength, maxMsgLength int, outputFormat string) {\n\tsrcTemplate := fmt.Sprintf(\"%%-%ds\", maxSrcLength)\n\tdstTemplate := fmt.Sprintf(\"%%-%ds\", maxDstLength)\n\tsrcNamespaceTemplate := fmt.Sprintf(\"%%-%ds\", maxSrcNamespaceLength)\n\tdstNamespaceTemplate := fmt.Sprintf(\"%%-%ds\", maxDstNamespaceLength)\n\tmsgTemplate := fmt.Sprintf(\"%%-%ds\", maxMsgLength)\n\tclientTemplate := fmt.Sprintf(\"%%-%ds\", maxClientLength)\n\tserverTemplate := fmt.Sprintf(\"%%-%ds\", maxServerLength)\n\n\theaders := []string{\n\t\tfmt.Sprintf(srcTemplate, srcHeader),\n\t\tfmt.Sprintf(dstTemplate, dstHeader),\n\t\tfmt.Sprintf(srcNamespaceTemplate, srcNamespaceHeader),\n\t\tfmt.Sprintf(dstNamespaceTemplate, dstNamespaceHeader),\n\t}\n\n\tif outputFormat == wideOutput {\n\t\theaders = append(headers, fmt.Sprintf(clientTemplate, clientHeader), fmt.Sprintf(serverTemplate, serverHeader))\n\t}\n\n\theaders = append(headers, fmt.Sprintf(msgTemplate, msgHeader)+\"\\t\")\n\n\tfmt.Fprintln(w, strings.Join(headers, \"\\t\"))\n\n\tfor _, row := range edgeRows {\n\t\tvalues := []interface{}{\n\t\t\trow.src,\n\t\t\trow.dst,\n\t\t\trow.srcNamespace,\n\t\t\trow.dstNamespace,\n\t\t}\n\t\ttemplateString := fmt.Sprintf(\"%s\\t%s\\t%s\\t%s\\t\", srcTemplate, dstTemplate, srcNamespaceTemplate, dstNamespaceTemplate)\n\n\t\tif outputFormat == wideOutput {\n\t\t\ttemplateString += fmt.Sprintf(\"%s\\t%s\\t\", clientTemplate, serverTemplate)\n\t\t\tvalues = append(values, row.client, row.server)\n\t\t}\n\n\t\ttemplateString += fmt.Sprintf(\"%s\\t\\n\", msgTemplate)\n\t\tvalues = append(values, row.msg)\n\n\t\tfmt.Fprintf(w, templateString, values...)\n\t}\n}\n\nfunc renderEdges(buffer bytes.Buffer, options *edgesOptions) string {\n\tvar out string\n\tswitch options.outputFormat {\n\tcase jsonOutput:\n\t\tout = buffer.String()\n\tdefault:\n\t\t// strip left padding on the first column\n\t\tout = string(buffer.Bytes()[padding:])\n\t\tout = strings.ReplaceAll(out, \"\\n\"+strings.Repeat(\" \", padding), \"\\n\")\n\t}\n\n\treturn out\n}\n\ntype edgesJSONStats struct {\n\tSrc          string `json:\"src\"`\n\tSrcNamespace string `json:\"src_namespace\"`\n\tDst          string `json:\"dst\"`\n\tDstNamespace string `json:\"dst_namespace\"`\n\tClient       string `json:\"client_id\"`\n\tServer       string `json:\"server_id\"`\n\tMsg          string `json:\"no_tls_reason\"`\n}\n\nfunc printEdgesJSON(edgeRows []edgeRow, w *tabwriter.Writer) {\n\t// avoid nil initialization so that if there are not stats it gets marshalled as an empty array vs null\n\tentries := []*edgesJSONStats{}\n\n\tfor _, row := range edgeRows {\n\t\tentry := &edgesJSONStats{\n\t\t\tSrc:          row.src,\n\t\t\tSrcNamespace: row.srcNamespace,\n\t\t\tDst:          row.dst,\n\t\t\tDstNamespace: row.dstNamespace,\n\t\t\tClient:       row.client,\n\t\t\tServer:       row.server,\n\t\t\tMsg:          row.msg}\n\t\tentries = append(entries, entry)\n\t}\n\n\tb, err := json.MarshalIndent(entries, \"\", \"  \")\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Error marshalling JSON: %s\\n\", err)\n\t\treturn\n\t}\n\tfmt.Fprintf(w, \"%s\\n\", b)\n}\n"
  },
  {
    "path": "viz/cmd/edges_test.go",
    "content": "package cmd\n\nimport (\n\t\"testing\"\n\n\tapi \"github.com/linkerd/linkerd2/viz/metrics-api\"\n)\n\ntype edgesParamsExp struct {\n\toptions      *edgesOptions\n\tresourceType string\n\tfile         string\n}\n\nfunc TestEdges(t *testing.T) {\n\toptions := newEdgesOptions()\n\toptions.outputFormat = tableOutput\n\toptions.allNamespaces = true\n\tt.Run(\"Returns edges\", func(t *testing.T) {\n\t\ttestEdgesCall(edgesParamsExp{\n\t\t\toptions:      options,\n\t\t\tresourceType: \"deployment\",\n\t\t\tfile:         \"edges_one_output.golden\",\n\t\t}, t)\n\t})\n\n\toptions.outputFormat = jsonOutput\n\tt.Run(\"Returns edges (json)\", func(t *testing.T) {\n\t\ttestEdgesCall(edgesParamsExp{\n\t\t\toptions:      options,\n\t\t\tresourceType: \"deployment\",\n\t\t\tfile:         \"edges_one_output_json.golden\",\n\t\t}, t)\n\t})\n\n\tt.Run(\"Returns edges (wide)\", func(t *testing.T) {\n\t\toptions.outputFormat = wideOutput\n\t\ttestEdgesCall(edgesParamsExp{\n\t\t\toptions:      options,\n\t\t\tresourceType: \"deployment\",\n\t\t\tfile:         \"edges_wide_output.golden\",\n\t\t}, t)\n\t})\n\n\tt.Run(\"Returns an error if outputFormat specified is not wide, table or json\", func(t *testing.T) {\n\t\toptions.outputFormat = \"test\"\n\t\targs := []string{\"deployment\"}\n\t\texpectedError := \"--output supports table, json and wide\"\n\n\t\t_, err := buildEdgesRequests(args, options)\n\t\tif err == nil || err.Error() != expectedError {\n\t\t\tt.Fatalf(\"Expected error [%s] instead got [%s]\", expectedError, err)\n\t\t}\n\t})\n\n\tt.Run(\"Returns an error if request includes the resource name\", func(t *testing.T) {\n\t\toptions.outputFormat = tableOutput\n\t\targs := []string{\"pod/pod-name\"}\n\t\texpectedError := \"Edges cannot be returned for a specific resource name; remove pod-name from query\"\n\n\t\t_, err := buildEdgesRequests(args, options)\n\t\tif err == nil || err.Error() != expectedError {\n\t\t\tt.Fatalf(\"Expected error [%s] instead got [%s]\", expectedError, err)\n\t\t}\n\t})\n\n\tt.Run(\"Returns an error if request is for authority\", func(t *testing.T) {\n\t\toptions.outputFormat = tableOutput\n\t\targs := []string{\"authority\"}\n\t\texpectedError := \"cannot find Kubernetes canonical name from friendly name [authority]\"\n\n\t\t_, err := buildEdgesRequests(args, options)\n\t\tif err == nil || err.Error() != expectedError {\n\t\t\tt.Fatalf(\"Expected error [%s] instead got [%s]\", expectedError, err)\n\t\t}\n\t})\n\n\tt.Run(\"Returns an error if request is for service\", func(t *testing.T) {\n\t\toptions.outputFormat = tableOutput\n\t\targs := []string{\"service\"}\n\t\texpectedError := \"Resource type is not supported: service\"\n\n\t\t_, err := buildEdgesRequests(args, options)\n\t\tif err == nil || err.Error() != expectedError {\n\t\t\tt.Fatalf(\"Expected error [%s] instead got [%s]\", expectedError, err)\n\t\t}\n\t})\n\n\tt.Run(\"Returns an error if request is for all resource types\", func(t *testing.T) {\n\t\toptions.outputFormat = tableOutput\n\t\targs := []string{\"all\"}\n\t\texpectedError := \"Resource type is not supported: all\"\n\n\t\t_, err := buildEdgesRequests(args, options)\n\t\tif err == nil || err.Error() != expectedError {\n\t\t\tt.Fatalf(\"Expected error [%s] instead got [%s]\", expectedError, err)\n\t\t}\n\t})\n}\n\nfunc testEdgesCall(exp edgesParamsExp, t *testing.T) {\n\tmockClient := &api.MockAPIClient{}\n\tresponse := api.GenEdgesResponse(exp.resourceType, \"all\")\n\n\tmockClient.EdgesResponseToReturn = response\n\n\targs := []string{\"deployment\"}\n\treqs, err := buildEdgesRequests(args, exp.options)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tresp, err := requestEdgesFromAPI(mockClient, reqs[0])\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\trows := edgesRespToRows(resp)\n\toutput := renderEdgeStats(rows, exp.options)\n\n\ttestDataDiffer.DiffTestdata(t, exp.file, output)\n}\n"
  },
  {
    "path": "viz/cmd/install.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"path\"\n\t\"time\"\n\n\tchartspkg \"github.com/linkerd/linkerd2/pkg/charts\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/viz/charts\"\n\t\"github.com/spf13/cobra\"\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/values\"\n\t\"helm.sh/helm/v3/pkg/engine\"\n)\n\nvar (\n\t// this doesn't include the namespace-metadata.* templates, which are Helm-only\n\ttemplatesViz = []string{\n\t\t\"templates/namespace.yaml\",\n\t\t\"templates/metrics-api-rbac.yaml\",\n\t\t\"templates/prometheus-rbac.yaml\",\n\t\t\"templates/tap-rbac.yaml\",\n\t\t\"templates/web-rbac.yaml\",\n\t\t\"templates/psp.yaml\",\n\t\t\"templates/metrics-api.yaml\",\n\t\t\"templates/metrics-api-policy.yaml\",\n\t\t\"templates/admin-policy.yaml\",\n\t\t\"templates/prometheus.yaml\",\n\t\t\"templates/prometheus-policy.yaml\",\n\t\t\"templates/tap.yaml\",\n\t\t\"templates/tap-policy.yaml\",\n\t\t\"templates/tap-injector-rbac.yaml\",\n\t\t\"templates/tap-injector.yaml\",\n\t\t\"templates/tap-injector-policy.yaml\",\n\t\t\"templates/web.yaml\",\n\t\t\"templates/service-profiles.yaml\",\n\t}\n)\n\nfunc newCmdInstall() *cobra.Command {\n\tvar skipChecks bool\n\tvar ignoreCluster bool\n\tvar ha bool\n\tvar cniEnabled bool\n\tvar wait time.Duration\n\tvar options values.Options\n\tvar output string\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"install [flags]\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Output Kubernetes resources to install linkerd-viz extension\",\n\t\tLong:  `Output Kubernetes resources to install linkerd-viz extension.`,\n\t\tExample: `  # Default install.\n  linkerd viz install | kubectl apply -f -\n\nThe installation can be configured by using the --set, --values, --set-string and --set-file flags.\nA full list of configurable values can be found at https://www.github.com/linkerd/linkerd2/tree/main/viz/charts/linkerd-viz/README.md\n  `,\n\t\tRunE: func(_ *cobra.Command, _ []string) error {\n\t\t\tif !skipChecks && !ignoreCluster {\n\t\t\t\t// Wait for the core control-plane to be up and running\n\t\t\t\thc := healthcheck.NewWithCoreChecks(&healthcheck.Options{\n\t\t\t\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\t\t\t\tKubeConfig:            kubeconfigPath,\n\t\t\t\t\tKubeContext:           kubeContext,\n\t\t\t\t\tImpersonate:           impersonate,\n\t\t\t\t\tImpersonateGroup:      impersonateGroup,\n\t\t\t\t\tAPIAddr:               apiAddr,\n\t\t\t\t\tRetryDeadline:         time.Now().Add(wait),\n\t\t\t\t})\n\t\t\t\thc.RunWithExitOnError()\n\t\t\t\tcniEnabled = hc.CNIEnabled\n\t\t\t}\n\t\t\treturn install(os.Stdout, options, ha, cniEnabled, output)\n\t\t},\n\t}\n\n\tcmd.Flags().BoolVar(&skipChecks, \"skip-checks\", false, `Skip checks for linkerd core control-plane existence`)\n\tcmd.Flags().BoolVar(&ignoreCluster, \"ignore-cluster\", false,\n\t\t\"Ignore the current Kubernetes cluster when checking for existing cluster configuration (default false)\")\n\tcmd.Flags().BoolVar(&ha, \"ha\", false, `Install Viz Extension in High Availability mode.`)\n\tcmd.Flags().DurationVar(&wait, \"wait\", 300*time.Second, \"Wait for core control-plane components to be available\")\n\tcmd.PersistentFlags().StringVarP(&output, \"output\", \"o\", \"yaml\", \"Output format. One of: json|yaml\")\n\n\tflags.AddValueOptionsFlags(cmd.Flags(), &options)\n\n\treturn cmd\n}\n\nfunc install(w io.Writer, options values.Options, ha, cniEnabled bool, format string) error {\n\n\t// Create values override\n\tvaluesOverrides, err := options.MergeValues(nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// sync values overrides with Helm values\n\tif controlPlaneNamespace != defaultLinkerdNamespace {\n\t\tvaluesOverrides[\"linkerdNamespace\"] = controlPlaneNamespace\n\t}\n\tif reg := os.Getenv(flags.EnvOverrideDockerRegistry); reg != \"\" {\n\t\tvaluesOverrides[\"defaultRegistry\"] = reg\n\t}\n\n\tif ha {\n\t\tvaluesOverrides, err = chartspkg.OverrideFromFile(valuesOverrides, charts.Templates, VizChartName, \"values-ha.yaml\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif cniEnabled {\n\t\tvaluesOverrides[\"cniEnabled\"] = true\n\t}\n\n\t// TODO: Add any validation logic here\n\n\treturn render(w, valuesOverrides, format)\n}\n\nfunc render(w io.Writer, valuesOverrides map[string]interface{}, format string) error {\n\n\tfiles := []*loader.BufferedFile{\n\t\t{Name: chartutil.ChartfileName},\n\t\t{Name: chartutil.ValuesfileName},\n\t}\n\n\tfor _, template := range templatesViz {\n\t\tfiles = append(files,\n\t\t\t&loader.BufferedFile{Name: template},\n\t\t)\n\t}\n\n\t// Load all Viz chart files into buffer\n\tif err := chartspkg.FilesReader(charts.Templates, VizChartName+\"/\", files); err != nil {\n\t\treturn err\n\t}\n\n\tpartialFiles, err := chartspkg.LoadPartials()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Create a Chart obj from the files\n\tchart, err := loader.LoadFiles(append(files, partialFiles...))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvaluesOverrides[\"cliVersion\"] = k8s.CreatedByAnnotationValue()\n\n\tvals, err := chartutil.CoalesceValues(chart, valuesOverrides)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvals, err = chartspkg.InsertVersionValues(vals)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfullValues := map[string]interface{}{\n\t\t\"Values\": vals,\n\t\t\"Release\": map[string]interface{}{\n\t\t\t\"Namespace\": defaultNamespace,\n\t\t\t\"Service\":   \"CLI\",\n\t\t},\n\t}\n\n\t// Attach the final values into the `Values` field for rendering to work\n\trenderedTemplates, err := engine.Render(chart, fullValues)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Merge templates and inject\n\tvar buf bytes.Buffer\n\tfor _, tmpl := range chart.Templates {\n\t\tt := path.Join(chart.Metadata.Name, tmpl.Name)\n\t\tif _, err := buf.WriteString(renderedTemplates[t]); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn pkgcmd.RenderYAMLAs(&buf, w, format)\n}\n"
  },
  {
    "path": "viz/cmd/install_test.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"testing\"\n\n\tcharts \"github.com/linkerd/linkerd2/pkg/charts\"\n)\n\nfunc TestRender(t *testing.T) {\n\n\t// pin values that are changed by render functions on each test run\n\tdefaultValues := map[string]interface{}{\n\t\t\"tap\": map[string]interface{}{\n\t\t\t\"externalSecret\": true,\n\t\t\t\"caBundle\":       \"test-tap-ca-bundle\",\n\t\t},\n\t\t\"tapInjector\": map[string]interface{}{\n\t\t\t\"externalSecret\": true,\n\t\t\t\"caBundle\":       \"test-tap-ca-bundle\",\n\t\t},\n\t}\n\n\tproxyResources := map[string]interface{}{\n\t\t\"proxy\": map[string]interface{}{\n\t\t\t\"resources\": map[string]interface{}{\n\t\t\t\t\"cpu\": map[string]interface{}{\n\t\t\t\t\t\"request\": \"500m\",\n\t\t\t\t\t\"limit\":   \"100m\",\n\t\t\t\t},\n\t\t\t\t\"memory\": map[string]interface{}{\n\t\t\t\t\t\"request\": \"20Mi\",\n\t\t\t\t\t\"limit\":   \"250Mi\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestCases := []struct {\n\t\tvalues         map[string]interface{}\n\t\tgoldenFileName string\n\t}{\n\t\t{\n\t\t\tnil,\n\t\t\t\"install_default.golden\",\n\t\t},\n\t\t{\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"prometheus\": map[string]interface{}{\n\t\t\t\t\t\"args\": map[string]interface{}{\n\t\t\t\t\t\t\"log.level\": \"debug\",\n\t\t\t\t\t}},\n\t\t\t},\n\t\t\t\"install_prometheus_loglevel_from_args.golden\",\n\t\t},\n\t\t{\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"prometheus\":    map[string]interface{}{\"enabled\": false},\n\t\t\t\t\"prometheusUrl\": \"external-prom.com\",\n\t\t\t},\n\t\t\t\"install_prometheus_disabled.golden\",\n\t\t},\n\t\t{\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"prometheus\": proxyResources,\n\t\t\t\t\"tap\":        proxyResources,\n\t\t\t\t\"dashboard\":  proxyResources,\n\t\t\t},\n\t\t\t\"install_proxy_resources.golden\",\n\t\t},\n\t\t{\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"defaultLogLevel\": \"debug\",\n\t\t\t\t\"defaultUID\":      1234,\n\t\t\t\t\"defaultGID\":      1234,\n\t\t\t\t\"defaultRegistry\": \"gcr.io/linkerd\",\n\t\t\t\t\"tap\": map[string]interface{}{\n\t\t\t\t\t\"logLevel\": \"info\",\n\t\t\t\t\t\"UID\":      5678,\n\t\t\t\t\t\"GID\":      5678,\n\t\t\t\t\t\"image\": map[string]interface{}{\n\t\t\t\t\t\t\"registry\": \"cr.l5d.io/linkerd\",\n\t\t\t\t\t\t\"tag\":      \"stable-9.2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"grafana\": map[string]interface{}{\"url\": \"grafana.grafana:3000\"},\n\t\t\t},\n\t\t\t\"install_default_overrides.golden\",\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(fmt.Sprintf(\"%d: %s\", i, tc.goldenFileName), func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\t// Merge overrides with default\n\t\t\tif err := render(&buf, charts.MergeMaps(defaultValues, tc.values), \"yaml\"); err != nil {\n\t\t\t\tt.Fatalf(\"Failed to render templates: %v\", err)\n\t\t\t}\n\t\t\tif err := testDataDiffer.DiffTestYAML(tc.goldenFileName, buf.String()); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "viz/cmd/list.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\tpkgK8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\tvizLabels \"github.com/linkerd/linkerd2/viz/pkg/labels\"\n\t\"github.com/spf13/cobra\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\ntype listOptions struct {\n\tnamespace     string\n\tallNamespaces bool\n}\n\nfunc newCmdList() *cobra.Command {\n\tvar options listOptions\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"list [flags]\",\n\t\tShort: \"Lists which pods can be tapped\",\n\t\tArgs:  cobra.NoArgs,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tk8sAPI, err := pkgK8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\t\t\tif options.allNamespaces {\n\t\t\t\toptions.namespace = v1.NamespaceAll\n\t\t\t}\n\n\t\t\tpods, err := k8sAPI.CoreV1().Pods(options.namespace).List(cmd.Context(), metav1.ListOptions{})\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintln(os.Stderr, err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\tvar tapEnabled, tapDisabled, tapNotEnabled []v1.Pod\n\n\t\t\tfor _, pod := range pods.Items {\n\t\t\t\tpod := pod\n\t\t\t\tif pkgK8s.IsMeshed(&pod, controlPlaneNamespace) {\n\t\t\t\t\tif vizLabels.IsTapDisabled(pod.GetObjectMeta()) {\n\t\t\t\t\t\ttapDisabled = append(tapDisabled, pod)\n\t\t\t\t\t} else if !vizLabels.IsTapEnabled(&pod) {\n\t\t\t\t\t\ttapNotEnabled = append(tapNotEnabled, pod)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttapEnabled = append(tapEnabled, pod)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(tapEnabled) > 0 {\n\t\t\t\tfmt.Println(\"Pods with tap enabled:\")\n\t\t\t\tfor _, pod := range tapEnabled {\n\t\t\t\t\tfmt.Printf(\"\\t* %s/%s\\n\", pod.Namespace, pod.Name)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(tapDisabled) > 0 {\n\t\t\t\tfmt.Println(\"Pods with tap disabled:\")\n\t\t\t\tfor _, pod := range tapDisabled {\n\t\t\t\t\tfmt.Printf(\"\\t* %s/%s\\n\", pod.Namespace, pod.Name)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(tapNotEnabled) > 0 {\n\t\t\t\tfmt.Println(\"Pods missing tap configuration (restart these pods to enable tap):\")\n\t\t\t\tfor _, pod := range tapNotEnabled {\n\t\t\t\t\tfmt.Printf(\"\\t* %s/%s\\n\", pod.Namespace, pod.Name)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(tapEnabled)+len(tapDisabled)+len(tapNotEnabled) == 0 {\n\t\t\t\tfmt.Println(\"No meshed pods found\")\n\t\t\t}\n\n\t\t\treturn nil\n\t\t},\n\t}\n\tcmd.Flags().StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace, \"The namespace to list pods in\")\n\tcmd.Flags().BoolVarP(&options.allNamespaces, \"all-namespaces\", \"A\", options.allNamespaces, \"If present, list pods across all namespaces\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tcmd, []string{\"namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\treturn cmd\n}\n"
  },
  {
    "path": "viz/cmd/main_test.go",
    "content": "package cmd\n\nimport (\n\t\"flag\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/testutil\"\n)\n\nvar (\n\ttestDataDiffer testutil.TestDataDiffer\n)\n\n// TestMain parses flags before running tests\nfunc TestMain(m *testing.M) {\n\tflag.BoolVar(&testDataDiffer.UpdateFixtures, \"update\", false, \"update text fixtures in place\")\n\tprettyDiff := os.Getenv(\"LINKERD_TEST_PRETTY_DIFF\") != \"\"\n\tflag.BoolVar(&testDataDiffer.PrettyDiff, \"pretty-diff\", prettyDiff, \"display the full text when diffing\")\n\tflag.StringVar(&testDataDiffer.RejectPath, \"reject-path\", \"\", \"write results for failed tests to this path (path is relative to the test location)\")\n\tflag.Parse()\n\tos.Exit(m.Run())\n}\n"
  },
  {
    "path": "viz/cmd/profile.go",
    "content": "package cmd\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/profiles\"\n\t\"github.com/linkerd/linkerd2/pkg/protohttp\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/api\"\n\thc \"github.com/linkerd/linkerd2/viz/pkg/healthcheck\"\n\tvizutil \"github.com/linkerd/linkerd2/viz/pkg/util\"\n\tpb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n\t\"github.com/linkerd/linkerd2/viz/tap/pkg\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n\t\"sigs.k8s.io/yaml\"\n)\n\ntype profileOptions struct {\n\tname          string\n\tnamespace     string\n\ttap           string\n\ttapDuration   time.Duration\n\ttapRouteLimit uint\n\toutput        string\n}\n\nfunc newProfileOptions() *profileOptions {\n\treturn &profileOptions{\n\t\ttapDuration:   5 * time.Second,\n\t\ttapRouteLimit: 20,\n\t\toutput:        \"yaml\",\n\t}\n}\n\nfunc (options *profileOptions) validate() error {\n\tif options.tap == \"\" {\n\t\treturn errors.New(\"The --tap flag must be specified\")\n\t}\n\t// a DNS-1035 label must consist of lower case alphanumeric characters or '-',\n\t// start with an alphabetic character, and end with an alphanumeric character\n\tif errs := validation.IsDNS1035Label(options.name); len(errs) != 0 {\n\t\treturn fmt.Errorf(\"invalid service %q: %v\", options.name, errs)\n\t}\n\t// a DNS-1123 label must consist of lower case alphanumeric characters or '-',\n\t// and must start and end with an alphanumeric character\n\tif errs := validation.IsDNS1123Label(options.namespace); len(errs) != 0 {\n\t\treturn fmt.Errorf(\"invalid namespace %q: %v\", options.namespace, errs)\n\t}\n\treturn nil\n}\n\n// newCmdProfile creates a new cobra command for the Profile subcommand which\n// generates Linkerd service profile based off tap data.\nfunc newCmdProfile() *cobra.Command {\n\toptions := newProfileOptions()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"profile [flags] --tap resource (SERVICE)\",\n\t\tShort: \"Output service profile config for Kubernetes based off tap data\",\n\t\tLong:  \"Output service profile config for Kubernetes based off tap data.\",\n\t\tExample: `  # Generate a profile by watching live traffic.\n  linkerd viz profile -n emojivoto web-svc --tap deploy/web --tap-duration 10s --tap-route-limit 5\n`,\n\t\tArgs: cobra.ExactArgs(1),\n\t\tValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\t\t// Skip providing suggestions if one or more arguments are provided\n\t\t\t// We either have a suggestion selected or more multiple args are provided\n\t\t\t// which is not allowed for this command.\n\t\t\tif len(args) > 0 {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\tcc := k8s.NewCommandCompletion(k8sAPI, options.namespace)\n\n\t\t\t// Profiles require completion for only services so prepend the service resource\n\t\t\t// type to the list of args\n\t\t\tresults, err := cc.Complete([]string{k8s.Service}, toComplete)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\treturn results, cobra.ShellCompDirectiveDefault\n\t\t},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tapi.CheckClientOrExit(hc.VizOptions{\n\t\t\t\tOptions: &healthcheck.Options{\n\t\t\t\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\t\t\t\tKubeConfig:            kubeconfigPath,\n\t\t\t\t\tImpersonate:           impersonate,\n\t\t\t\t\tImpersonateGroup:      impersonateGroup,\n\t\t\t\t\tKubeContext:           kubeContext,\n\t\t\t\t\tAPIAddr:               apiAddr,\n\t\t\t\t},\n\t\t\t\tVizNamespaceOverride: vizNamespace,\n\t\t\t})\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\t\t\toptions.name = args[0]\n\t\t\tclusterDomain := \"cluster.local\"\n\t\t\tvar k8sAPI *k8s.KubernetesAPI\n\t\t\terr := options.validate()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tk8sAPI, err = k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, values, err := healthcheck.FetchCurrentConfiguration(cmd.Context(), k8sAPI, controlPlaneNamespace)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif cd := values.ClusterDomain; cd != \"\" {\n\t\t\t\tclusterDomain = cd\n\t\t\t}\n\t\t\treturn renderTapOutputProfile(cmd.Context(), k8sAPI, options.tap, options.namespace, options.name, clusterDomain, options.tapDuration, int(options.tapRouteLimit), options.output, os.Stdout)\n\t\t},\n\t}\n\tcmd.PersistentFlags().StringVar(&options.tap, \"tap\", options.tap, \"Output a service profile based on tap data for the given target resource\")\n\tcmd.PersistentFlags().DurationVar(&options.tapDuration, \"tap-duration\", options.tapDuration, \"Duration over which tap data is collected (for example: \\\"10s\\\", \\\"1m\\\", \\\"10m\\\")\")\n\tcmd.PersistentFlags().UintVar(&options.tapRouteLimit, \"tap-route-limit\", options.tapRouteLimit, \"Max number of routes to add to the profile\")\n\tcmd.PersistentFlags().StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace, \"Namespace of the service\")\n\tcmd.PersistentFlags().StringVarP(&options.output, \"output\", \"o\", options.output, \"Output format. One of: yaml, json\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tcmd, []string{\"namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\treturn cmd\n}\n\n// renderTapOutputProfile performs a tap on the desired resource and generates\n// a service profile with routes pre-populated from the tap data\n// Only inbound tap traffic is considered.\nfunc renderTapOutputProfile(ctx context.Context, k8sAPI *k8s.KubernetesAPI, tapResource, namespace, name, clusterDomain string, tapDuration time.Duration, routeLimit int, format string, w io.Writer) error {\n\trequestParams := pkg.TapRequestParams{\n\t\tResource:  tapResource,\n\t\tNamespace: namespace,\n\t}\n\tlog.Debugf(\"Running `linkerd tap %s --namespace %s`\", tapResource, namespace)\n\treq, err := pkg.BuildTapByResourceRequest(requestParams)\n\tif err != nil {\n\t\treturn err\n\t}\n\tprofile, err := tapToServiceProfile(ctx, k8sAPI, req, namespace, name, clusterDomain, tapDuration, routeLimit)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar output []byte\n\tif format == pkgcmd.YamlOutput {\n\t\toutput, err = yaml.Marshal(profile)\n\t} else if format == pkgcmd.JsonOutput {\n\t\toutput, err = json.Marshal(profile)\n\t} else {\n\t\treturn errors.New(\"output format must be one of yaml or json\")\n\t}\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error writing Service Profile: %w\", err)\n\t}\n\tw.Write(output)\n\treturn nil\n}\n\nfunc tapToServiceProfile(ctx context.Context, k8sAPI *k8s.KubernetesAPI, tapReq *pb.TapByResourceRequest, namespace, name, clusterDomain string, tapDuration time.Duration, routeLimit int) (sp.ServiceProfile, error) {\n\tprofile := sp.ServiceProfile{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      fmt.Sprintf(\"%s.%s.svc.%s\", name, namespace, clusterDomain),\n\t\t\tNamespace: namespace,\n\t\t},\n\t\tTypeMeta: profiles.ServiceProfileMeta,\n\t}\n\tctxWithTime, cancel := context.WithTimeout(ctx, tapDuration)\n\tdefer cancel()\n\treader, body, err := pkg.Reader(ctxWithTime, k8sAPI, tapReq)\n\tif err != nil {\n\t\treturn profile, err\n\t}\n\tdefer body.Close()\n\troutes := routeSpecFromTap(reader, routeLimit)\n\tprofile.Spec.Routes = routes\n\treturn profile, nil\n}\n\nfunc routeSpecFromTap(tapByteStream *bufio.Reader, routeLimit int) []*sp.RouteSpec {\n\troutes := make([]*sp.RouteSpec, 0)\n\troutesMap := make(map[string]*sp.RouteSpec)\n\tfor {\n\t\tlog.Debug(\"Waiting for data...\")\n\t\tevent := pb.TapEvent{}\n\t\terr := protohttp.FromByteStreamToProtocolBuffers(tapByteStream, &event)\n\t\tif err != nil {\n\t\t\t// expected errors when hitting the tapDuration deadline\n\t\t\tvar e net.Error\n\t\t\tif !errors.Is(err, io.EOF) &&\n\t\t\t\t!(errors.As(err, &e) && e.Timeout()) &&\n\t\t\t\t!errors.Is(err, context.DeadlineExceeded) &&\n\t\t\t\t!strings.HasSuffix(err.Error(), pkg.ErrClosedResponseBody) {\n\t\t\t\tfmt.Fprintln(os.Stderr, err)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\trouteSpec := getPathDataFromTap(&event)\n\t\tlog.Debugf(\"Created route spec: %v\", routeSpec)\n\t\tif routeSpec != nil {\n\t\t\troutesMap[routeSpec.Name] = routeSpec\n\t\t\tif len(routesMap) >= routeLimit {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tfor _, path := range sortMapKeys(routesMap) {\n\t\troutes = append(routes, routesMap[path])\n\t}\n\treturn routes\n}\n\nfunc sortMapKeys(m map[string]*sp.RouteSpec) (keys []string) {\n\tfor key := range m {\n\t\tkeys = append(keys, key)\n\t}\n\tsort.Strings(keys)\n\treturn\n}\n\nfunc getPathDataFromTap(event *pb.TapEvent) *sp.RouteSpec {\n\tif event.GetProxyDirection() != pb.TapEvent_INBOUND {\n\t\treturn nil\n\t}\n\tswitch ev := event.GetHttp().GetEvent().(type) {\n\tcase *pb.TapEvent_Http_RequestInit_:\n\t\tpath := ev.RequestInit.GetPath()\n\t\tif path == \"/\" {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn profiles.MkRouteSpec(\n\t\t\tpath,\n\t\t\tprofiles.PathToRegex(path), // for now, no path consolidation\n\t\t\tvizutil.HTTPMethodToString(ev.RequestInit.GetMethod()),\n\t\t\tnil)\n\tdefault:\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "viz/cmd/profile_test.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/profiles\"\n\t\"github.com/linkerd/linkerd2/pkg/protohttp\"\n\tmetricsPb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\ttapPb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n\t\"github.com/linkerd/linkerd2/viz/tap/pkg\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestTapToServiceProfile(t *testing.T) {\n\tname := \"service-name\"\n\tnamespace := \"service-namespace\"\n\tclusterDomain := \"service-cluster.local\"\n\ttapDuration := 5 * time.Second\n\trouteLimit := 20\n\n\tparams := pkg.TapRequestParams{\n\t\tResource:  \"deploy/\" + name,\n\t\tNamespace: namespace,\n\t}\n\n\ttapReq, err := pkg.BuildTapByResourceRequest(params)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tevent1 := pkg.CreateTapEvent(\n\t\t&tapPb.TapEvent_Http{\n\t\t\tEvent: &tapPb.TapEvent_Http_RequestInit_{\n\n\t\t\t\tRequestInit: &tapPb.TapEvent_Http_RequestInit{\n\t\t\t\t\tId: &tapPb.TapEvent_Http_StreamId{\n\t\t\t\t\t\tBase: 1,\n\t\t\t\t\t},\n\t\t\t\t\tAuthority: \"\",\n\t\t\t\t\tPath:      \"/emojivoto.v1.VotingService/VoteFire\",\n\t\t\t\t\tMethod: &metricsPb.HttpMethod{\n\t\t\t\t\t\tType: &metricsPb.HttpMethod_Registered_{\n\t\t\t\t\t\t\tRegistered: metricsPb.HttpMethod_POST,\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\tmap[string]string{},\n\t\ttapPb.TapEvent_INBOUND,\n\t)\n\n\tevent2 := pkg.CreateTapEvent(\n\t\t&tapPb.TapEvent_Http{\n\t\t\tEvent: &tapPb.TapEvent_Http_RequestInit_{\n\n\t\t\t\tRequestInit: &tapPb.TapEvent_Http_RequestInit{\n\t\t\t\t\tId: &tapPb.TapEvent_Http_StreamId{\n\t\t\t\t\t\tBase: 2,\n\t\t\t\t\t},\n\t\t\t\t\tAuthority: \"\",\n\t\t\t\t\tPath:      \"/my/path/hi\",\n\t\t\t\t\tMethod: &metricsPb.HttpMethod{\n\t\t\t\t\t\tType: &metricsPb.HttpMethod_Registered_{\n\t\t\t\t\t\t\tRegistered: metricsPb.HttpMethod_GET,\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\tmap[string]string{},\n\t\ttapPb.TapEvent_INBOUND,\n\t)\n\n\tkubeAPI, err := k8s.NewFakeAPI()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tts := httptest.NewServer(http.HandlerFunc(\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tfor _, event := range []*tapPb.TapEvent{event1, event2} {\n\t\t\t\tevent := event // pin\n\t\t\t\terr = protohttp.WriteProtoToHTTPResponse(w, event)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t)\n\tdefer ts.Close()\n\tkubeAPI.Config.Host = ts.URL\n\n\texpectedServiceProfile := sp.ServiceProfile{\n\t\tTypeMeta: profiles.ServiceProfileMeta,\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name + \".\" + namespace + \".svc.\" + clusterDomain,\n\t\t\tNamespace: namespace,\n\t\t},\n\t\tSpec: sp.ServiceProfileSpec{\n\t\t\tRoutes: []*sp.RouteSpec{\n\t\t\t\t{\n\t\t\t\t\tName: \"GET /my/path/hi\",\n\t\t\t\t\tCondition: &sp.RequestMatch{\n\t\t\t\t\t\tPathRegex: `/my/path/hi`,\n\t\t\t\t\t\tMethod:    \"GET\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"POST /emojivoto.v1.VotingService/VoteFire\",\n\t\t\t\t\tCondition: &sp.RequestMatch{\n\t\t\t\t\t\tPathRegex: `/emojivoto\\.v1\\.VotingService/VoteFire`,\n\t\t\t\t\t\tMethod:    \"POST\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tactualServiceProfile, err := tapToServiceProfile(context.Background(), kubeAPI, tapReq, namespace, name, clusterDomain, tapDuration, routeLimit)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create ServiceProfile: %v\", err)\n\t}\n\n\terr = profiles.ServiceProfileYamlEquals(actualServiceProfile, expectedServiceProfile)\n\tif err != nil {\n\t\tt.Fatalf(\"ServiceProfiles are not equal: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "viz/cmd/prune.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\tpkgCmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tvizHealthcheck \"github.com/linkerd/linkerd2/viz/pkg/healthcheck\"\n\t\"github.com/spf13/cobra\"\n\t\"helm.sh/helm/v3/pkg/cli/values\"\n)\n\nfunc newCmdPrune() *cobra.Command {\n\tvar ha bool\n\tvar cniEnabled bool\n\tvar wait time.Duration\n\tvar options values.Options\n\tvar output string\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"prune [flags]\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Output extraneous Kubernetes resources in the linkerd-viz extension\",\n\t\tLong:  `Output extraneous Kubernetes resources in the linkerd-viz extension.`,\n\t\tExample: `  # Prune extraneous resources.\n  linkerd viz prune | kubectl delete -f -\n  `,\n\t\tRunE: func(cmd *cobra.Command, _ []string) error {\n\t\t\thc := healthcheck.NewWithCoreChecks(&healthcheck.Options{\n\t\t\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\t\t\tKubeConfig:            kubeconfigPath,\n\t\t\t\tKubeContext:           kubeContext,\n\t\t\t\tImpersonate:           impersonate,\n\t\t\t\tImpersonateGroup:      impersonateGroup,\n\t\t\t\tAPIAddr:               apiAddr,\n\t\t\t\tRetryDeadline:         time.Now().Add(wait),\n\t\t\t})\n\t\t\thc.RunWithExitOnError()\n\t\t\tcniEnabled = hc.CNIEnabled\n\n\t\t\tmanifests := strings.Builder{}\n\n\t\t\terr := install(&manifests, options, ha, cniEnabled, \"yaml\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tlabel := fmt.Sprintf(\"%s=%s\", k8s.LinkerdExtensionLabel, vizHealthcheck.VizExtensionName)\n\t\t\treturn pkgCmd.Prune(cmd.Context(), hc.KubeAPIClient(), manifests.String(), label, output)\n\t\t},\n\t}\n\n\tcmd.Flags().BoolVar(&ha, \"ha\", false, `Set if Linkerd Viz Extension is installed in High Availability mode.`)\n\tcmd.Flags().DurationVar(&wait, \"wait\", 300*time.Second, \"Wait for extension components to be available\")\n\tcmd.PersistentFlags().StringVarP(&output, \"output\", \"o\", \"yaml\", \"Output format. One of: json|yaml\")\n\n\tflags.AddValueOptionsFlags(cmd.Flags(), &options)\n\n\treturn cmd\n}\n"
  },
  {
    "path": "viz/cmd/root.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/fatih/color\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n)\n\nconst (\n\t// ExtensionName is the value that the viz extension resources should be labeled with\n\tExtensionName = \"viz\"\n\n\t// LegacyExtensionName is the value that the viz extension resources were labeled with\n\t// until 15d1809bd043192bb21cacbc96112cce35bf384f\n\tLegacyExtensionName = \"linkerd-viz\"\n\n\tdefaultNamespace        = \"linkerd-viz\"\n\tdefaultLinkerdNamespace = \"linkerd\"\n\tmaxRps                  = 100.0\n\n\tjsonOutput     = healthcheck.JSONOutput\n\ttableOutput    = healthcheck.TableOutput\n\twideOutput     = healthcheck.WideOutput\n\tjsonPathOutput = \"jsonpath\"\n)\n\nvar (\n\tVizChartName = \"linkerd-viz\"\n\n\t// special handling for Windows, on all other platforms these resolve to\n\t// os.Stdout and os.Stderr, thanks to https://github.com/mattn/go-colorable\n\tstdout = color.Output\n\tstderr = color.Error\n\n\tapiAddr               string // An empty value means \"use the Kubernetes configuration\"\n\tcontrolPlaneNamespace string\n\tkubeconfigPath        string\n\tkubeContext           string\n\tvizNamespace          string\n\timpersonate           string\n\timpersonateGroup      []string\n\tverbose               bool\n\n\t// These regexs are not as strict as they could be, but are a quick and dirty\n\t// sanity check against illegal characters.\n\talphaNumDash = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)\n)\n\n// NewCmdViz returns a new viz command\nfunc NewCmdViz() *cobra.Command {\n\tvizCmd := &cobra.Command{\n\t\tUse:   \"viz\",\n\t\tShort: \"viz manages the linkerd-viz extension of Linkerd service mesh\",\n\t\tLong:  `viz manages the linkerd-viz extension of Linkerd service mesh.`,\n\t\tPersistentPreRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\t// enable / disable logging\n\t\t\tif verbose {\n\t\t\t\tlog.SetLevel(log.DebugLevel)\n\t\t\t} else {\n\t\t\t\tlog.SetLevel(log.PanicLevel)\n\t\t\t}\n\n\t\t\tif !alphaNumDash.MatchString(controlPlaneNamespace) {\n\t\t\t\treturn fmt.Errorf(\"%s is not a valid namespace\", controlPlaneNamespace)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tvizCmd.PersistentFlags().StringVarP(&controlPlaneNamespace, \"linkerd-namespace\", \"L\", defaultLinkerdNamespace, \"Namespace in which Linkerd is installed\")\n\tvizCmd.PersistentFlags().StringVar(&kubeconfigPath, \"kubeconfig\", \"\", \"Path to the kubeconfig file to use for CLI requests\")\n\tvizCmd.PersistentFlags().StringVar(&kubeContext, \"context\", \"\", \"Name of the kubeconfig context to use\")\n\tvizCmd.PersistentFlags().StringVar(&impersonate, \"as\", \"\", \"Username to impersonate for Kubernetes operations\")\n\tvizCmd.PersistentFlags().StringArrayVar(&impersonateGroup, \"as-group\", []string{}, \"Group to impersonate for Kubernetes operations\")\n\tvizCmd.PersistentFlags().StringVar(&apiAddr, \"api-addr\", \"\", \"Override kubeconfig and communicate directly with the control plane at host:port (mostly for testing)\")\n\tvizCmd.PersistentFlags().StringVar(&vizNamespace, \"viz-namespace\", \"\", \"Name of the linkerd-viz namespace. If not set, it's automatically detected\")\n\tvizCmd.PersistentFlags().BoolVar(&verbose, \"verbose\", false, \"Turn on debug logging\")\n\tvizCmd.AddCommand(NewCmdAuthz())\n\tvizCmd.AddCommand(NewCmdCheck())\n\tvizCmd.AddCommand(NewCmdDashboard())\n\tvizCmd.AddCommand(NewCmdEdges())\n\tvizCmd.AddCommand(newCmdInstall())\n\tvizCmd.AddCommand(newCmdList())\n\tvizCmd.AddCommand(newCmdProfile())\n\tvizCmd.AddCommand(NewCmdRoutes())\n\tvizCmd.AddCommand(NewCmdStat())\n\tvizCmd.AddCommand(NewCmdStatInbound())\n\tvizCmd.AddCommand(NewCmdStatOutbound())\n\tvizCmd.AddCommand(NewCmdTap())\n\tvizCmd.AddCommand(NewCmdTop())\n\tvizCmd.AddCommand(newCmdUninstall())\n\tvizCmd.AddCommand(newCmdAllowScrapes())\n\tvizCmd.AddCommand(newCmdPrune())\n\n\t// resource-aware completion flag configurations\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tvizCmd, []string{\"linkerd-namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\n\tpkgcmd.ConfigureKubeContextFlagCompletion(vizCmd, kubeconfigPath)\n\treturn vizCmd\n}\n"
  },
  {
    "path": "viz/cmd/routes.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\t\"time\"\n\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/metrics-api/util\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/api\"\n\thc \"github.com/linkerd/linkerd2/viz/pkg/healthcheck\"\n\n\tpkgUtil \"github.com/linkerd/linkerd2/viz/pkg/util\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n)\n\ntype routesOptions struct {\n\tnamespace string\n\tstatOptionsBase\n\ttoResource    string\n\ttoNamespace   string\n\tdstIsService  bool\n\tlabelSelector string\n}\n\ntype routeRowStats struct {\n\trowStats\n\tactualRequestRate float64\n\tactualSuccessRate float64\n\thasRequestData    bool\n}\n\nfunc newRoutesOptions() *routesOptions {\n\treturn &routesOptions{\n\t\tstatOptionsBase: *newStatOptionsBase(),\n\t\ttoResource:      \"\",\n\t\ttoNamespace:     \"\",\n\t\tlabelSelector:   \"\",\n\t}\n}\n\n// NewCmdRoutes creates a new cobra command `routes` for routes functionality\nfunc NewCmdRoutes() *cobra.Command {\n\toptions := newRoutesOptions()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"routes [flags] (RESOURCES)\",\n\t\tShort: \"Display route stats\",\n\t\tLong: `Display route stats.\n\nThis command will only display traffic which is sent to a service that has a Service Profile defined.`,\n\t\tExample: `  # Routes for the webapp service in the test namespace.\n  linkerd viz routes service/webapp -n test\n\n  # Routes for calls from the traffic deployment to the webapp service in the test namespace.\n  linkerd viz routes deploy/traffic -n test --to svc/webapp`,\n\t\tArgs:      cobra.ExactArgs(1),\n\t\tValidArgs: pkgUtil.ValidTargets,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\t\t\treq, err := buildTopRoutesRequest(args[0], options)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error creating metrics request while making routes request: %w\", err)\n\t\t\t}\n\n\t\t\toutput, err := requestRouteStatsFromAPI(\n\t\t\t\tapi.CheckClientOrExit(hc.VizOptions{\n\t\t\t\t\tOptions: &healthcheck.Options{\n\t\t\t\t\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\t\t\t\t\tKubeConfig:            kubeconfigPath,\n\t\t\t\t\t\tImpersonate:           impersonate,\n\t\t\t\t\t\tImpersonateGroup:      impersonateGroup,\n\t\t\t\t\t\tKubeContext:           kubeContext,\n\t\t\t\t\t\tAPIAddr:               apiAddr,\n\t\t\t\t\t},\n\t\t\t\t\tVizNamespaceOverride: vizNamespace,\n\t\t\t\t}),\n\t\t\t\treq,\n\t\t\t\toptions,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprint(os.Stderr, err.Error())\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\t_, err = fmt.Print(output)\n\n\t\t\treturn err\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace, \"Namespace of the specified resource\")\n\tcmd.PersistentFlags().StringVarP(&options.timeWindow, \"time-window\", \"t\", options.timeWindow, \"Stat window (for example: \\\"10s\\\", \\\"1m\\\", \\\"10m\\\", \\\"1h\\\")\")\n\tcmd.PersistentFlags().StringVar(&options.toResource, \"to\", options.toResource, \"If present, shows outbound stats to the specified resource\")\n\tcmd.PersistentFlags().StringVar(&options.toNamespace, \"to-namespace\", options.toNamespace, \"Sets the namespace used to lookup the \\\"--to\\\" resource; by default the current \\\"--namespace\\\" is used\")\n\tcmd.PersistentFlags().StringVarP(&options.outputFormat, \"output\", \"o\", options.outputFormat, fmt.Sprintf(\"Output format; one of: \\\"%s\\\", \\\"%s\\\", or \\\"%s\\\"\", tableOutput, wideOutput, jsonOutput))\n\tcmd.PersistentFlags().StringVarP(&options.labelSelector, \"selector\", \"l\", options.labelSelector, \"Selector (label query) to filter on, supports '=', '==', and '!='\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tcmd, []string{\"namespace\", \"to-namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\treturn cmd\n}\n\nfunc requestRouteStatsFromAPI(client pb.ApiClient, req *pb.TopRoutesRequest, options *routesOptions) (string, error) {\n\tresp, err := client.TopRoutes(context.Background(), req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"TopRoutes API error: %w\", err)\n\t}\n\tif e := resp.GetError(); e != nil {\n\t\treturn \"\", errors.New(e.Error)\n\t}\n\n\treturn renderRouteStats(resp, options), nil\n}\n\nfunc renderRouteStats(resp *pb.TopRoutesResponse, options *routesOptions) string {\n\tvar buffer bytes.Buffer\n\tw := tabwriter.NewWriter(&buffer, 0, 0, padding, ' ', tabwriter.AlignRight)\n\twriteRouteStatsToBuffer(resp, w, options)\n\tw.Flush()\n\n\treturn renderStats(buffer, &options.statOptionsBase)\n}\n\nfunc writeRouteStatsToBuffer(resp *pb.TopRoutesResponse, w *tabwriter.Writer, options *routesOptions) {\n\n\ttables := make(map[string][]*routeRowStats)\n\n\tfor _, resourceTable := range resp.GetOk().GetRoutes() {\n\n\t\ttable := make([]*routeRowStats, 0)\n\n\t\tfor _, r := range resourceTable.GetRows() {\n\t\t\tif r.Stats != nil {\n\t\t\t\troute := r.GetRoute()\n\t\t\t\ttable = append(table, &routeRowStats{\n\t\t\t\t\trowStats: rowStats{\n\t\t\t\t\t\troute:       route,\n\t\t\t\t\t\tdst:         r.GetAuthority(),\n\t\t\t\t\t\trequestRate: getRequestRate(r.Stats.GetSuccessCount(), r.Stats.GetFailureCount(), r.TimeWindow),\n\t\t\t\t\t\tsuccessRate: getSuccessRate(r.Stats.GetSuccessCount(), r.Stats.GetFailureCount()),\n\t\t\t\t\t\tlatencyP50:  r.Stats.LatencyMsP50,\n\t\t\t\t\t\tlatencyP95:  r.Stats.LatencyMsP95,\n\t\t\t\t\t\tlatencyP99:  r.Stats.LatencyMsP99,\n\t\t\t\t\t},\n\t\t\t\t\tactualRequestRate: getRequestRate(r.Stats.GetActualSuccessCount(), r.Stats.GetActualFailureCount(), r.TimeWindow),\n\t\t\t\t\tactualSuccessRate: getSuccessRate(r.Stats.GetActualSuccessCount(), r.Stats.GetActualFailureCount()),\n\t\t\t\t\thasRequestData:    statHasRequestData(r.Stats),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tsort.Slice(table, func(i, j int) bool {\n\t\t\treturn table[i].dst+table[i].route < table[j].dst+table[j].route\n\t\t})\n\n\t\ttables[resourceTable.GetResource()] = table\n\t}\n\n\tresources := make([]string, 0)\n\tfor resource := range tables {\n\t\tresources = append(resources, resource)\n\t}\n\tsort.Strings(resources)\n\n\tswitch options.outputFormat {\n\tcase tableOutput, wideOutput:\n\t\tfor _, resource := range resources {\n\t\t\tif len(tables) > 1 {\n\t\t\t\tfmt.Fprintf(w, \"==> %s <==\\t\\f\", resource)\n\t\t\t}\n\t\t\tprintRouteTable(tables[resource], w, options)\n\t\t\tfmt.Fprintln(w)\n\t\t}\n\tcase jsonOutput:\n\t\tprintRouteJSON(tables, w, options)\n\t}\n}\n\nfunc printRouteTable(stats []*routeRowStats, w *tabwriter.Writer, options *routesOptions) {\n\t// template for left-aligning the route column\n\trouteTemplate := fmt.Sprintf(\"%%-%ds\", routeWidth(stats))\n\n\tauthorityColumn := \"AUTHORITY\"\n\tif options.dstIsService {\n\t\tauthorityColumn = \"SERVICE\"\n\t}\n\n\theaders := []string{\n\t\tfmt.Sprintf(routeTemplate, \"ROUTE\"),\n\t\tauthorityColumn,\n\t}\n\toutputActual := options.toResource != \"\" && options.outputFormat == wideOutput\n\tif outputActual {\n\t\theaders = append(headers, []string{\n\t\t\t\"EFFECTIVE_SUCCESS\",\n\t\t\t\"EFFECTIVE_RPS\",\n\t\t\t\"ACTUAL_SUCCESS\",\n\t\t\t\"ACTUAL_RPS\",\n\t\t}...)\n\t} else {\n\t\theaders = append(headers, []string{\n\t\t\t\"SUCCESS\",\n\t\t\t\"RPS\",\n\t\t}...)\n\t}\n\n\theaders = append(headers, []string{\n\t\t\"LATENCY_P50\",\n\t\t\"LATENCY_P95\",\n\t\t\"LATENCY_P99\\t\", // trailing \\t is required to format last column\n\t}...)\n\n\tfmt.Fprintln(w, strings.Join(headers, \"\\t\"))\n\n\t// route, success rate, rps\n\ttemplateString := routeTemplate + \"\\t%s\\t%.2f%%\\t%.1frps\\t\"\n\tif outputActual {\n\t\t// actual success rate, actual rps\n\t\ttemplateString += \"%.2f%%\\t%.1frps\\t\"\n\t}\n\t// p50, p95, p99\n\ttemplateString += \"%dms\\t%dms\\t%dms\\t\\n\"\n\n\tvar emptyTemplateString string\n\tif outputActual {\n\t\temptyTemplateString = routeTemplate + \"\\t%s\\t-\\t-\\t-\\t-\\t-\\t-\\t-\\t\\n\"\n\t} else {\n\t\temptyTemplateString = routeTemplate + \"\\t%s\\t-\\t-\\t-\\t-\\t-\\t\\n\"\n\t}\n\n\tfor _, row := range stats {\n\n\t\tvalues := []interface{}{\n\t\t\trow.route,\n\t\t\trow.dst,\n\t\t}\n\n\t\tif row.hasRequestData {\n\t\t\tvalues = append(values, []interface{}{\n\t\t\t\trow.successRate * 100,\n\t\t\t\trow.requestRate,\n\t\t\t}...)\n\n\t\t\tif outputActual {\n\t\t\t\tvalues = append(values, []interface{}{\n\t\t\t\t\trow.actualSuccessRate * 100,\n\t\t\t\t\trow.actualRequestRate,\n\t\t\t\t}...)\n\t\t\t}\n\t\t\tvalues = append(values, []interface{}{\n\t\t\t\trow.latencyP50,\n\t\t\t\trow.latencyP95,\n\t\t\t\trow.latencyP99,\n\t\t\t}...)\n\n\t\t\tfmt.Fprintf(w, templateString, values...)\n\t\t} else {\n\t\t\tfmt.Fprintf(w, emptyTemplateString, values...)\n\t\t}\n\t}\n}\n\n// getRequestRate calculates request rate from Public API BasicStats.\nfunc getRequestRate(success, failure uint64, timeWindow string) float64 {\n\twindowLength, err := time.ParseDuration(timeWindow)\n\tif err != nil {\n\t\tlog.Error(err.Error())\n\t\treturn 0.0\n\t}\n\treturn float64(success+failure) / windowLength.Seconds()\n}\n\n// getSuccessRate calculates success rate from Public API BasicStats.\nfunc getSuccessRate(success, failure uint64) float64 {\n\tif success+failure == 0 {\n\t\treturn 0.0\n\t}\n\treturn float64(success) / float64(success+failure)\n}\n\n// JSONRouteStats represents the JSON output of the routes command\n// Using pointers there where the value is NA and the corresponding json is null\ntype JSONRouteStats struct {\n\tRoute            string   `json:\"route\"`\n\tAuthority        string   `json:\"authority\"`\n\tSuccess          *float64 `json:\"success,omitempty\"`\n\tRps              *float64 `json:\"rps,omitempty\"`\n\tEffectiveSuccess *float64 `json:\"effective_success,omitempty\"`\n\tEffectiveRps     *float64 `json:\"effective_rps,omitempty\"`\n\tActualSuccess    *float64 `json:\"actual_success,omitempty\"`\n\tActualRps        *float64 `json:\"actual_rps,omitempty\"`\n\tLatencyMSp50     *uint64  `json:\"latency_ms_p50\"`\n\tLatencyMSp95     *uint64  `json:\"latency_ms_p95\"`\n\tLatencyMSp99     *uint64  `json:\"latency_ms_p99\"`\n}\n\nfunc printRouteJSON(tables map[string][]*routeRowStats, w *tabwriter.Writer, options *routesOptions) {\n\t// avoid nil initialization so that if there are not stats it gets marshalled as an empty array vs null\n\tentries := map[string][]*JSONRouteStats{}\n\tfor resource, table := range tables {\n\t\tfor _, row := range table {\n\t\t\troute := row.route\n\t\t\tentry := &JSONRouteStats{\n\t\t\t\tRoute: route,\n\t\t\t}\n\n\t\t\tentry.Authority = row.dst\n\t\t\tif options.toResource != \"\" {\n\t\t\t\tentry.EffectiveSuccess = &row.successRate\n\t\t\t\tentry.EffectiveRps = &row.requestRate\n\t\t\t\tentry.ActualSuccess = &row.actualSuccessRate\n\t\t\t\tentry.ActualRps = &row.actualRequestRate\n\t\t\t} else {\n\t\t\t\tentry.Success = &row.successRate\n\t\t\t\tentry.Rps = &row.requestRate\n\t\t\t}\n\t\t\tentry.LatencyMSp50 = &row.latencyP50\n\t\t\tentry.LatencyMSp95 = &row.latencyP95\n\t\t\tentry.LatencyMSp99 = &row.latencyP99\n\n\t\t\tentries[resource] = append(entries[resource], entry)\n\t\t}\n\t}\n\tb, err := json.MarshalIndent(entries, \"\", \"  \")\n\tif err != nil {\n\t\tlog.Error(err.Error())\n\t\treturn\n\t}\n\tfmt.Fprintf(w, \"%s\\n\", b)\n}\n\nfunc (o *routesOptions) validateOutputFormat() error {\n\tswitch o.outputFormat {\n\tcase tableOutput, jsonOutput:\n\t\treturn nil\n\tcase wideOutput:\n\t\tif o.toResource == \"\" {\n\t\t\treturn fmt.Errorf(\"%s output is only available when --to is specified\", wideOutput)\n\t\t}\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"--output currently only supports %s, %s, and %s\", tableOutput, wideOutput, jsonOutput)\n\t}\n}\n\nfunc buildTopRoutesRequest(resource string, options *routesOptions) (*pb.TopRoutesRequest, error) {\n\terr := options.validateOutputFormat()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttarget, err := pkgUtil.BuildResource(options.namespace, resource)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trequestParams := util.TopRoutesRequestParams{\n\t\tStatsBaseRequestParams: util.StatsBaseRequestParams{\n\t\t\tTimeWindow:   options.timeWindow,\n\t\t\tResourceName: target.Name,\n\t\t\tResourceType: target.Type,\n\t\t\tNamespace:    options.namespace,\n\t\t},\n\t\tLabelSelector: options.labelSelector,\n\t}\n\n\toptions.dstIsService = true\n\n\tif options.toResource != \"\" {\n\t\tif options.toNamespace == \"\" {\n\t\t\toptions.toNamespace = options.namespace\n\t\t}\n\t\ttoRes, err := pkgUtil.BuildResource(options.toNamespace, options.toResource)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\toptions.dstIsService = true\n\n\t\trequestParams.ToName = toRes.Name\n\t\trequestParams.ToNamespace = toRes.Namespace\n\t\trequestParams.ToType = toRes.Type\n\t}\n\n\treturn util.BuildTopRoutesRequest(requestParams)\n}\n\n// returns the length of the longest route name\nfunc routeWidth(stats []*routeRowStats) int {\n\tmaxLength := 0\n\tfor _, row := range stats {\n\t\tif len(row.route) > maxLength {\n\t\t\tmaxLength = len(row.route)\n\t\t}\n\t}\n\treturn maxLength\n}\n"
  },
  {
    "path": "viz/cmd/routes_test.go",
    "content": "package cmd\n\nimport (\n\t\"testing\"\n\n\tapi \"github.com/linkerd/linkerd2/viz/metrics-api\"\n)\n\ntype routesParamsExp struct {\n\toptions *routesOptions\n\troutes  []string\n\tcounts  []uint64\n\tfile    string\n}\n\nfunc TestRoutes(t *testing.T) {\n\toptions := newRoutesOptions()\n\tt.Run(\"Returns route stats\", func(t *testing.T) {\n\t\ttestRoutesCall(routesParamsExp{\n\t\t\troutes:  []string{\"/a\", \"/b\", \"/c\"},\n\t\t\tcounts:  []uint64{90, 60, 0, 30},\n\t\t\toptions: options,\n\t\t\tfile:    \"routes_one_output.golden\",\n\t\t}, t)\n\t})\n\n\toptions.outputFormat = jsonOutput\n\tt.Run(\"Returns route stats (json)\", func(t *testing.T) {\n\t\ttestRoutesCall(routesParamsExp{\n\t\t\troutes:  []string{\"/a\", \"/b\", \"/c\"},\n\t\t\tcounts:  []uint64{90, 60, 0, 30},\n\t\t\toptions: options,\n\t\t\tfile:    \"routes_one_output_json.golden\",\n\t\t}, t)\n\t})\n\n\twideOptions := newRoutesOptions()\n\twideOptions.toResource = \"deploy/bar\"\n\twideOptions.outputFormat = wideOutput\n\tt.Run(\"Returns wider route stats\", func(t *testing.T) {\n\t\ttestRoutesCall(routesParamsExp{\n\t\t\troutes:  []string{\"/a\", \"/b\", \"/c\"},\n\t\t\tcounts:  []uint64{90, 60, 0, 30},\n\t\t\toptions: wideOptions,\n\t\t\tfile:    \"routes_one_output_wide.golden\",\n\t\t}, t)\n\t})\n}\n\nfunc testRoutesCall(exp routesParamsExp, t *testing.T) {\n\tmockClient := &api.MockAPIClient{}\n\n\tresponse := api.GenTopRoutesResponse(exp.routes, exp.counts, exp.options.toResource != \"\", \"foobar\")\n\n\tmockClient.TopRoutesResponseToReturn = response\n\n\treq, err := buildTopRoutesRequest(\"deploy/foobar\", exp.options)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\toutput, err := requestRouteStatsFromAPI(mockClient, req, exp.options)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\ttestDataDiffer.DiffTestdata(t, exp.file, output)\n}\n"
  },
  {
    "path": "viz/cmd/stat-inbound.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/cli/table\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/api\"\n\thc \"github.com/linkerd/linkerd2/viz/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/prometheus\"\n\tpkgUtil \"github.com/linkerd/linkerd2/viz/pkg/util\"\n\tpromv1 \"github.com/prometheus/client_golang/api/prometheus/v1\"\n\t\"github.com/prometheus/common/model\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n)\n\ntype statInboundOptions struct {\n\tstatOptionsBase\n\tprometheusURL string\n}\n\ntype inboundRowKey struct {\n\tname      string\n\tserver    string\n\tport      string\n\troute     string\n\trouteType string\n}\n\ntype inboundRow struct {\n\tsuccesses  uint64\n\tfailures   uint64\n\tlatencyP50 float64\n\tlatencyP95 float64\n\tlatencyP99 float64\n}\n\ntype inboundJsonRow struct {\n\tName        string   `json:\"name\"`\n\tServer      string   `json:\"server\"`\n\tPort        string   `json:\"port\"`\n\tRoute       string   `json:\"route\"`\n\tRouteType   string   `json:\"routeType\"`\n\tSuccessRate float64  `json:\"successesRate\"`\n\tRPS         float64  `json:\"rps\"`\n\tLatencyP50  *float64 `json:\"latencyMsP50\"`\n\tLatencyP95  *float64 `json:\"latencyMsP95\"`\n\tLatencyP99  *float64 `json:\"latencyMsP99\"`\n}\n\nfunc newStatInboundOptions() *statInboundOptions {\n\treturn &statInboundOptions{\n\t\tstatOptionsBase: *newStatOptionsBase(),\n\t}\n}\n\n// NewCmdStatInbound creates a new cobra command `stat-inbound` for inbound stats functionality\nfunc NewCmdStatInbound() *cobra.Command {\n\toptions := newStatInboundOptions()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"stat-inbound [flags] (RESOURCE)\",\n\t\tShort: \"Display inbound traffic stats about a resource\",\n\t\tLong: `Display inbound traffic stats about a resource.\n\n  The RESOURCE argument specifies the target resource to display stats from:\n  TYPE/NAME\n\n  Examples:\n  * cronjob/my-cronjob\n  * deploy/my-deploy\n  * ds/my-daemonset\n  * job/my-job\n  * ns/my-ns\n  * rc/my-replication-controller\n  * rs/my-replicaset\n  * sts/my-statefulset\n\n  Valid resource types include:\n  * cronjobs\n  * daemonsets\n  * deployments\n  * namespaces\n  * jobs\n  * pods\n  * replicasets\n  * replicationcontrollers\n  * statefulsets`,\n\t\tArgs: cobra.MinimumNArgs(1),\n\t\tValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\tcc := k8s.NewCommandCompletion(k8sAPI, options.namespace)\n\n\t\t\tresults, err := cc.Complete(args, toComplete)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\treturn results, cobra.ShellCompDirectiveDefault\n\t\t},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\tpromApi, err := api.NewPrometheusClient(cmd.Context(), hc.VizOptions{\n\t\t\t\tOptions: &healthcheck.Options{\n\t\t\t\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\t\t\t\tKubeConfig:            kubeconfigPath,\n\t\t\t\t\tImpersonate:           impersonate,\n\t\t\t\t\tImpersonateGroup:      impersonateGroup,\n\t\t\t\t\tKubeContext:           kubeContext,\n\t\t\t\t\tAPIAddr:               apiAddr,\n\t\t\t\t},\n\t\t\t\tVizNamespaceOverride: vizNamespace,\n\t\t\t}, options.prometheusURL)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tresource, err := pkgUtil.BuildResource(options.namespace, args[0])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// Issue Prometheus queries.\n\n\t\t\tresponseChan := queryRate(\n\t\t\t\tcmd.Context(),\n\t\t\t\tpromApi,\n\t\t\t\t\"response_total\",\n\t\t\t\toptions.timeWindow,\n\t\t\t\tmodel.LabelSet{\"direction\": \"inbound\"},\n\t\t\t\tmodel.LabelNames{\"srv_name\", \"srv_kind\", \"route_name\", \"route_kind\", \"classification\", \"target_port\"},\n\t\t\t\tresource,\n\t\t\t)\n\n\t\t\tquantiles := queryQuantiles(\n\t\t\t\tcmd.Context(),\n\t\t\t\tpromApi,\n\t\t\t\t\"response_latency_ms_bucket\",\n\t\t\t\toptions.timeWindow,\n\t\t\t\tmodel.LabelSet{\"direction\": \"inbound\"},\n\t\t\t\tmodel.LabelNames{\"srv_name\", \"srv_kind\", \"route_name\", \"route_kind\", \"target_port\"},\n\t\t\t\tresource,\n\t\t\t)\n\n\t\t\t// Collect Prometheus results.\n\n\t\t\tresults := make(map[inboundRowKey]inboundRow)\n\n\t\t\tfor sample := range responseChan {\n\t\t\t\tlabels := sample.Metric\n\t\t\t\tkey := inboundKeyForSample(sample, resource)\n\t\t\t\trow := results[key]\n\t\t\t\tif labels[\"classification\"] == \"success\" {\n\t\t\t\t\trow.successes += uint64(sample.Value)\n\t\t\t\t} else {\n\t\t\t\t\trow.failures += uint64(sample.Value)\n\t\t\t\t}\n\t\t\t\tresults[key] = row\n\t\t\t}\n\n\t\t\tfor quantile, resultsChan := range quantiles {\n\t\t\t\tfor sample := range resultsChan {\n\t\t\t\t\tkey := inboundKeyForSample(sample, resource)\n\t\t\t\t\trow := results[key]\n\t\t\t\t\trow.populateLatency(quantile, sample)\n\t\t\t\t\tresults[key] = row\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Render output.\n\t\t\ttimeWindow, err := time.ParseDuration(options.timeWindow)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif options.outputFormat == \"json\" {\n\t\t\t\treturn renderStatInboundJson(results, timeWindow)\n\t\t\t} else if options.outputFormat == \"table\" || options.outputFormat == \"\" {\n\t\t\t\treturn renderStatInboundTable(results, timeWindow)\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"Invalid output format: %s\", options.outputFormat)\n\t\t\t}\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVarP(&options.outputFormat, \"output\", \"o\", options.outputFormat, \"Output format; one of: \\\"table\\\" or \\\"json\\\"\")\n\tcmd.PersistentFlags().StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace, \"Namespace\")\n\tcmd.PersistentFlags().StringVarP(&options.timeWindow, \"time-window\", \"t\", options.timeWindow, \"Stat window (for example: \\\"10s\\\", \\\"1m\\\", \\\"10m\\\", \\\"1h\\\")\")\n\tcmd.PersistentFlags().StringVar(&options.prometheusURL, \"prometheusURL\", options.prometheusURL, \"Address of Prometheus instance to query\")\n\n\treturn cmd\n}\n\nfunc queryRate(\n\tctx context.Context,\n\tpromAPI promv1.API,\n\tmetric string,\n\ttimeWindow string,\n\tlabels model.LabelSet,\n\tgroupBy model.LabelNames,\n\tresource *pb.Resource,\n) <-chan *model.Sample {\n\tresults := make(chan *model.Sample)\n\tgo func() {\n\t\tdefer close(results)\n\t\tquery := fmt.Sprintf(\"sum(increase(%s%s[%s])) by (%s)\",\n\t\t\tmetric,\n\t\t\tlabels.Merge(prometheus.QueryLabels(resource)),\n\t\t\ttimeWindow,\n\t\t\tappend(groupBy, prometheus.GroupByLabelNames(resource)...),\n\t\t)\n\t\tlog.Debug(query)\n\t\tval, warn, err := promAPI.Query(ctx, query, time.Time{})\n\t\tif warn != nil {\n\t\t\tlog.Warnf(\"%v\", warn)\n\t\t}\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(os.Stderr, \"failed to query Prometheus: \", err.Error())\n\t\t\tos.Exit(1)\n\t\t}\n\t\tfor _, sample := range val.(model.Vector) {\n\t\t\tresults <- sample\n\t\t}\n\t}()\n\treturn results\n}\n\nfunc queryQuantiles(\n\tctx context.Context,\n\tpromAPI promv1.API,\n\tmetric string,\n\ttimeWindow string,\n\tlabels model.LabelSet,\n\tgroupBy model.LabelNames,\n\tresource *pb.Resource,\n) map[string]chan *model.Sample {\n\tresults := map[string]chan *model.Sample{\n\t\t\"0.5\":  make(chan *model.Sample),\n\t\t\"0.95\": make(chan *model.Sample),\n\t\t\"0.99\": make(chan *model.Sample),\n\t}\n\tfor quantile, resultsChan := range results {\n\t\tgo func(quantile string) {\n\t\t\tdefer close(resultsChan)\n\t\t\tquery := fmt.Sprintf(\"histogram_quantile(%s, sum(irate(%s%s[%s])) by (le, %s))\",\n\t\t\t\tquantile,\n\t\t\t\tmetric,\n\t\t\t\tlabels.Merge(prometheus.QueryLabels(resource)),\n\t\t\t\ttimeWindow,\n\t\t\t\tappend(groupBy, prometheus.GroupByLabelNames(resource)...),\n\t\t\t)\n\t\t\tlog.Debug(query)\n\t\t\tval, warn, err := promAPI.Query(ctx, query, time.Time{})\n\t\t\tif warn != nil {\n\t\t\t\tlog.Warnf(\"%v\", warn)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintln(os.Stderr, \"failed to query Prometheus: \", err.Error())\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tfor _, sample := range val.(model.Vector) {\n\t\t\t\tresultsChan <- sample\n\t\t\t}\n\t\t}(quantile)\n\t}\n\treturn results\n}\n\nfunc inboundKeyForSample(sample *model.Sample, resource *pb.Resource) inboundRowKey {\n\tlabels := sample.Metric\n\tserver := (string)(labels[\"srv_name\"])\n\troute := (string)(labels[\"route_name\"])\n\trouteType := (string)(labels[\"route_kind\"])\n\tif labels[\"srv_kind\"] == \"default\" {\n\t\tserver = \"[default]\"\n\t}\n\tif labels[\"route_name\"] == \"default\" {\n\t\troute = \"[default]\"\n\t}\n\tif labels[\"route_kind\"] == \"default\" {\n\t\trouteType = \"\"\n\t}\n\n\treturn inboundRowKey{\n\t\tname:      (string)(labels[prometheus.ResourceType(resource)]),\n\t\tserver:    server,\n\t\troute:     route,\n\t\tport:      (string)(labels[\"target_port\"]),\n\t\trouteType: routeType,\n\t}\n}\n\nfunc formatLatencyMs(value float64) string {\n\tlatency := \"-\"\n\tif !math.IsNaN(value) {\n\t\tlatency = fmt.Sprintf(\"%.0fms\", value)\n\t}\n\treturn latency\n}\n\nfunc (r *inboundRow) populateLatency(quantile string, sample *model.Sample) {\n\tswitch quantile {\n\tcase \"0.5\":\n\t\tr.latencyP50 = float64(sample.Value)\n\tcase \"0.95\":\n\t\tr.latencyP95 = float64(sample.Value)\n\tcase \"0.99\":\n\t\tr.latencyP99 = float64(sample.Value)\n\t}\n}\n\nfunc renderStatInboundTable(results map[inboundRowKey]inboundRow, windowLength time.Duration) error {\n\trows := make([][]string, 0)\n\n\tfor key, row := range results {\n\t\tif row.failures+row.successes == 0 {\n\t\t\tcontinue\n\t\t}\n\t\trows = append(rows, []string{\n\t\t\tkey.name,\n\t\t\tfmt.Sprintf(\"%s:%s\", key.server, key.port),\n\t\t\tkey.route,\n\t\t\tkey.routeType,\n\t\t\tfmt.Sprintf(\"%.2f%%\", (float32)(row.successes)/(float32)(row.successes+row.failures)*100.0),\n\t\t\tfmt.Sprintf(\"%.2f\", (float32)(row.successes+row.failures)/float32(windowLength.Seconds())),\n\t\t\tformatLatencyMs(row.latencyP50),\n\t\t\tformatLatencyMs(row.latencyP95),\n\t\t\tformatLatencyMs(row.latencyP99),\n\t\t})\n\t}\n\n\tcolumns := []table.Column{\n\t\ttable.NewColumn(\"NAME\").WithLeftAlign(),\n\t\ttable.NewColumn(\"SERVER\").WithLeftAlign(),\n\t\ttable.NewColumn(\"ROUTE\").WithLeftAlign(),\n\t\ttable.NewColumn(\"TYPE\").WithLeftAlign(),\n\t\ttable.NewColumn(\"SUCCESS\"),\n\t\ttable.NewColumn(\"RPS\"),\n\t\ttable.NewColumn(\"LATENCY_P50\"),\n\t\ttable.NewColumn(\"LATENCY_P95\"),\n\t\ttable.NewColumn(\"LATENCY_P99\"),\n\t}\n\n\ttable := table.NewTable(columns, rows)\n\ttable.Sort = []int{0, 1, 3} // Name, Server, Route\n\ttable.Render(os.Stdout)\n\n\treturn nil\n}\n\nfunc renderStatInboundJson(results map[inboundRowKey]inboundRow, windowLength time.Duration) error {\n\trows := make([]inboundJsonRow, 0)\n\tfor key, result := range results {\n\t\tresult := result // To avoid golangci-lint complaining about memory aliasing.\n\t\tif result.failures+result.successes == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\trow := inboundJsonRow{\n\t\t\tName:        key.name,\n\t\t\tServer:      key.server,\n\t\t\tPort:        key.port,\n\t\t\tRoute:       key.route,\n\t\t\tRouteType:   key.routeType,\n\t\t\tSuccessRate: float64(result.successes) / float64(result.successes+result.failures),\n\t\t\tRPS:         float64(result.successes+result.failures) / windowLength.Seconds(),\n\t\t\tLatencyP50:  &result.latencyP50,\n\t\t\tLatencyP95:  &result.latencyP95,\n\t\t\tLatencyP99:  &result.latencyP99,\n\t\t}\n\n\t\tif math.IsNaN(result.latencyP50) {\n\t\t\trow.LatencyP50 = nil\n\t\t}\n\t\tif math.IsNaN(result.latencyP95) {\n\t\t\trow.LatencyP95 = nil\n\t\t}\n\t\tif math.IsNaN(result.latencyP99) {\n\t\t\trow.LatencyP99 = nil\n\t\t}\n\n\t\trows = append(rows, row)\n\t}\n\tout, err := json.Marshal(rows)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Println(string(out))\n\treturn nil\n}\n"
  },
  {
    "path": "viz/cmd/stat-outbound.go",
    "content": "package cmd\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/cli/table\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/api\"\n\thc \"github.com/linkerd/linkerd2/viz/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/prometheus\"\n\tpkgUtil \"github.com/linkerd/linkerd2/viz/pkg/util\"\n\t\"github.com/prometheus/common/model\"\n\t\"github.com/spf13/cobra\"\n)\n\ntype outboundRowKey struct {\n\tname      string\n\tservice   string\n\tport      string\n\troute     string\n\trouteType string\n}\n\ntype outboundBackendRow struct {\n\tsuccesses  uint64\n\tfailures   uint64\n\tlatencyP50 float64\n\tlatencyP95 float64\n\tlatencyP99 float64\n\ttimeouts   uint64\n}\n\ntype backendKey struct {\n\tname string\n\tport string\n}\n\ntype outboundRouteRow struct {\n\toutboundBackendRow\n\tretries  uint64\n\tbackends map[backendKey]outboundBackendRow\n}\n\ntype outboundJsonRow struct {\n\tName        string   `json:\"name\"`\n\tService     string   `json:\"service\"`\n\tPort        string   `json:\"port\"`\n\tRoute       string   `json:\"route\"`\n\tRouteType   string   `json:\"routeType\"`\n\tBackend     string   `json:\"backend,omitempty\"`\n\tBackendPort string   `json:\"backendPort,omitempty\"`\n\tSuccessRate float64  `json:\"successRate\"`\n\tRPS         float64  `json:\"rps\"`\n\tLatencyP50  *float64 `json:\"latencyMsP50\"`\n\tLatencyP95  *float64 `json:\"latencyMsP95\"`\n\tLatencyP99  *float64 `json:\"latencyMsP99\"`\n\tTimeoutRate float64  `json:\"timeoutRate\"`\n\tRetryRate   *float64 `json:\"retryRate,omitempty\"`\n}\n\n// NewCmdStatOutbound creates a new cobra command `stat-outbound` for outbound stat functionality\nfunc NewCmdStatOutbound() *cobra.Command {\n\toptions := newStatInboundOptions()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"stat-outbound [flags] (RESOURCE)\",\n\t\tShort: \"Display outbound traffic stats about a resource\",\n\t\tLong: `Display outbound traffic stats about a resource.\n\n  The RESOURCE argument specifies the target resource to aggregate stats over:\n  TYPE/NAME\n\n  Examples:\n  * cronjob/my-cronjob\n  * deploy/my-deploy\n  * ds/my-daemonset\n  * job/my-job\n  * ns/my-ns\n  * rc/my-replication-controller\n  * rs/my-replicaset\n  * sts/my-statefulset\n\n  Valid resource types include:\n  * cronjobs\n  * daemonsets\n  * deployments\n  * namespaces\n  * jobs\n  * pods\n  * replicasets\n  * replicationcontrollers\n  * statefulsets`,\n\t\tArgs: cobra.MinimumNArgs(1),\n\t\tValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\tcc := k8s.NewCommandCompletion(k8sAPI, options.namespace)\n\n\t\t\tresults, err := cc.Complete(args, toComplete)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\treturn results, cobra.ShellCompDirectiveDefault\n\t\t},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\tpromApi, err := api.NewPrometheusClient(cmd.Context(), hc.VizOptions{\n\t\t\t\tOptions: &healthcheck.Options{\n\t\t\t\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\t\t\t\tKubeConfig:            kubeconfigPath,\n\t\t\t\t\tImpersonate:           impersonate,\n\t\t\t\t\tImpersonateGroup:      impersonateGroup,\n\t\t\t\t\tKubeContext:           kubeContext,\n\t\t\t\t\tAPIAddr:               apiAddr,\n\t\t\t\t},\n\t\t\t\tVizNamespaceOverride: vizNamespace,\n\t\t\t}, options.prometheusURL)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tresource, err := pkgUtil.BuildResource(options.namespace, args[0])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// Issue Prometheus queries for HTTP.\n\n\t\t\thttpBackendChan := queryRate(\n\t\t\t\tcmd.Context(),\n\t\t\t\tpromApi,\n\t\t\t\t\"outbound_http_route_backend_response_statuses_total\",\n\t\t\t\toptions.timeWindow,\n\t\t\t\tmodel.LabelSet{},\n\t\t\t\tmodel.LabelNames{\"parent_name\", \"parent_port\", \"backend_name\", \"backend_port\", \"route_name\", \"route_kind\", \"http_status\", \"error\"},\n\t\t\t\tresource,\n\t\t\t)\n\n\t\t\thttpBackendQuantiles := queryQuantiles(\n\t\t\t\tcmd.Context(),\n\t\t\t\tpromApi,\n\t\t\t\t\"outbound_http_route_backend_response_duration_seconds_bucket\",\n\t\t\t\toptions.timeWindow,\n\t\t\t\tmodel.LabelSet{},\n\t\t\t\tmodel.LabelNames{\"parent_name\", \"parent_port\", \"backend_name\", \"backend_port\", \"route_name\", \"route_kind\"},\n\t\t\t\tresource,\n\t\t\t)\n\n\t\t\thttpRouteChan := queryRate(\n\t\t\t\tcmd.Context(),\n\t\t\t\tpromApi,\n\t\t\t\t\"outbound_http_route_request_statuses_total\",\n\t\t\t\toptions.timeWindow,\n\t\t\t\tmodel.LabelSet{},\n\t\t\t\tmodel.LabelNames{\"parent_name\", \"parent_port\", \"route_name\", \"route_kind\", \"http_status\", \"error\"},\n\t\t\t\tresource,\n\t\t\t)\n\n\t\t\thttpRetiesChan := queryRate(\n\t\t\t\tcmd.Context(),\n\t\t\t\tpromApi,\n\t\t\t\t\"outbound_http_route_retry_requests_total\",\n\t\t\t\toptions.timeWindow,\n\t\t\t\tmodel.LabelSet{},\n\t\t\t\tmodel.LabelNames{\"parent_name\", \"parent_port\", \"route_name\", \"route_kind\"},\n\t\t\t\tresource,\n\t\t\t)\n\n\t\t\thttpRouteQuantiles := queryQuantiles(\n\t\t\t\tcmd.Context(),\n\t\t\t\tpromApi,\n\t\t\t\t\"outbound_http_route_request_duration_seconds_bucket\",\n\t\t\t\toptions.timeWindow,\n\t\t\t\tmodel.LabelSet{},\n\t\t\t\tmodel.LabelNames{\"parent_name\", \"parent_port\", \"route_name\", \"route_kind\"},\n\t\t\t\tresource,\n\t\t\t)\n\n\t\t\t// Issue Prometheus queries for gRPC.\n\n\t\t\tgrpcBackendChan := queryRate(\n\t\t\t\tcmd.Context(),\n\t\t\t\tpromApi,\n\t\t\t\t\"outbound_grpc_route_backend_response_statuses_total\",\n\t\t\t\toptions.timeWindow,\n\t\t\t\tmodel.LabelSet{},\n\t\t\t\tmodel.LabelNames{\"parent_name\", \"parent_port\", \"backend_name\", \"backend_port\", \"route_name\", \"route_kind\", \"grpc_status\", \"error\"},\n\t\t\t\tresource,\n\t\t\t)\n\n\t\t\tgrpcBackendQuantiles := queryQuantiles(\n\t\t\t\tcmd.Context(),\n\t\t\t\tpromApi,\n\t\t\t\t\"outbound_grpc_route_backend_response_duration_seconds_bucket\",\n\t\t\t\toptions.timeWindow,\n\t\t\t\tmodel.LabelSet{},\n\t\t\t\tmodel.LabelNames{\"parent_name\", \"parent_port\", \"backend_name\", \"backend_port\", \"route_name\", \"route_kind\"},\n\t\t\t\tresource,\n\t\t\t)\n\n\t\t\tgrpcRouteChan := queryRate(\n\t\t\t\tcmd.Context(),\n\t\t\t\tpromApi,\n\t\t\t\t\"outbound_grpc_route_request_statuses_total\",\n\t\t\t\toptions.timeWindow,\n\t\t\t\tmodel.LabelSet{},\n\t\t\t\tmodel.LabelNames{\"parent_name\", \"parent_port\", \"route_name\", \"route_kind\", \"grpc_status\", \"error\"},\n\t\t\t\tresource,\n\t\t\t)\n\n\t\t\tgrpcRetiesChan := queryRate(\n\t\t\t\tcmd.Context(),\n\t\t\t\tpromApi,\n\t\t\t\t\"outbound_grpc_route_retry_requests_total\",\n\t\t\t\toptions.timeWindow,\n\t\t\t\tmodel.LabelSet{},\n\t\t\t\tmodel.LabelNames{\"parent_name\", \"parent_port\", \"route_name\", \"route_kind\"},\n\t\t\t\tresource,\n\t\t\t)\n\n\t\t\tgrpcRouteQuantiles := queryQuantiles(\n\t\t\t\tcmd.Context(),\n\t\t\t\tpromApi,\n\t\t\t\t\"outbound_grpc_route_request_duration_seconds_bucket\",\n\t\t\t\toptions.timeWindow,\n\t\t\t\tmodel.LabelSet{},\n\t\t\t\tmodel.LabelNames{\"parent_name\", \"parent_port\", \"route_name\", \"route_kind\"},\n\t\t\t\tresource,\n\t\t\t)\n\n\t\t\t// Collect Prometheus results for HTTP.\n\n\t\t\tresults := make(map[outboundRowKey]outboundRouteRow)\n\n\t\t\tfor sample := range httpBackendChan {\n\t\t\t\tkey := outboundKeyForSample(sample, resource)\n\t\t\t\trow := results[key]\n\t\t\t\trow.populateBackendHTTPCounts(sample)\n\t\t\t\tresults[key] = row\n\t\t\t}\n\n\t\t\tfor quantile, resultsChan := range httpBackendQuantiles {\n\t\t\t\tfor sample := range resultsChan {\n\t\t\t\t\tkey := outboundKeyForSample(sample, resource)\n\t\t\t\t\trow := results[key]\n\t\t\t\t\trow.populateBackendLatency(quantile, sample)\n\t\t\t\t\tresults[key] = row\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor sample := range httpRouteChan {\n\t\t\t\tkey := outboundKeyForSample(sample, resource)\n\t\t\t\trow := results[key]\n\t\t\t\trow.populateHTTPCounts(sample)\n\t\t\t\tresults[key] = row\n\t\t\t}\n\t\t\tfor sample := range httpRetiesChan {\n\t\t\t\tkey := outboundKeyForSample(sample, resource)\n\t\t\t\trow := results[key]\n\t\t\t\trow.retries += uint64(sample.Value)\n\t\t\t\tresults[key] = row\n\t\t\t}\n\t\t\tfor quantile, resultsChan := range httpRouteQuantiles {\n\t\t\t\tfor sample := range resultsChan {\n\t\t\t\t\tkey := outboundKeyForSample(sample, resource)\n\t\t\t\t\trow := results[key]\n\t\t\t\t\trow.populateLatency(quantile, sample)\n\t\t\t\t\tresults[key] = row\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Collect Prometheus results for gRPC.\n\n\t\t\tfor sample := range grpcBackendChan {\n\t\t\t\tkey := outboundKeyForSample(sample, resource)\n\t\t\t\trow := results[key]\n\t\t\t\trow.populateBackendGRPCCounts(sample)\n\t\t\t\tresults[key] = row\n\t\t\t}\n\n\t\t\tfor quantile, resultsChan := range grpcBackendQuantiles {\n\t\t\t\tfor sample := range resultsChan {\n\t\t\t\t\tkey := outboundKeyForSample(sample, resource)\n\t\t\t\t\trow := results[key]\n\t\t\t\t\trow.populateBackendLatency(quantile, sample)\n\t\t\t\t\tresults[key] = row\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor sample := range grpcRouteChan {\n\t\t\t\tkey := outboundKeyForSample(sample, resource)\n\t\t\t\trow := results[key]\n\t\t\t\trow.populateGRPCCounts(sample)\n\t\t\t\tresults[key] = row\n\t\t\t}\n\t\t\tfor sample := range grpcRetiesChan {\n\t\t\t\tkey := outboundKeyForSample(sample, resource)\n\t\t\t\trow := results[key]\n\t\t\t\trow.retries += uint64(sample.Value)\n\t\t\t\tresults[key] = row\n\t\t\t}\n\t\t\tfor quantile, resultsChan := range grpcRouteQuantiles {\n\t\t\t\tfor sample := range resultsChan {\n\t\t\t\t\tkey := outboundKeyForSample(sample, resource)\n\t\t\t\t\trow := results[key]\n\t\t\t\t\trow.populateLatency(quantile, sample)\n\t\t\t\t\tresults[key] = row\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Render output.\n\n\t\t\twindowLength, err := time.ParseDuration(options.timeWindow)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif options.outputFormat == \"json\" {\n\t\t\t\treturn renderStatOutboundJSON(results, windowLength)\n\t\t\t} else if options.outputFormat == \"table\" || options.outputFormat == \"\" {\n\t\t\t\trenderStatOutboundTable(results, windowLength)\n\t\t\t\treturn nil\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"Invalid output format: %s\", options.outputFormat)\n\t\t\t}\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVarP(&options.outputFormat, \"output\", \"o\", options.outputFormat, \"Output format; one of: \\\"table\\\" or \\\"json\\\"\")\n\tcmd.PersistentFlags().StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace, \"Namespace\")\n\tcmd.PersistentFlags().StringVarP(&options.timeWindow, \"time-window\", \"t\", options.timeWindow, \"Stat window (for example: \\\"10s\\\", \\\"1m\\\", \\\"10m\\\", \\\"1h\\\")\")\n\tcmd.PersistentFlags().StringVar(&options.prometheusURL, \"prometheusURL\", options.prometheusURL, \"Address of Prometheus instance to query\")\n\n\treturn cmd\n}\n\nfunc outboundKeyForSample(sample *model.Sample, resource *pb.Resource) outboundRowKey {\n\tlabels := sample.Metric\n\troute := (string)(labels[\"route_name\"])\n\trouteType := (string)(labels[\"route_kind\"])\n\tif labels[\"route_name\"] == \"default\" {\n\t\troute = \"[default]\"\n\t}\n\tif labels[\"route_kind\"] == \"default\" {\n\t\trouteType = \"\"\n\t\troute = \"[default]\"\n\t}\n\n\treturn outboundRowKey{\n\t\tname:      (string)(labels[prometheus.ResourceType(resource)]),\n\t\tservice:   (string)(labels[\"parent_name\"]),\n\t\tport:      (string)(labels[\"parent_port\"]),\n\t\troute:     route,\n\t\trouteType: routeType,\n\t}\n}\n\nfunc (r *outboundBackendRow) populateLatency(quantile string, sample *model.Sample) {\n\tswitch quantile {\n\tcase \"0.5\":\n\t\tr.latencyP50 = float64(sample.Value * 1000)\n\tcase \"0.95\":\n\t\tr.latencyP95 = float64(sample.Value * 1000)\n\tcase \"0.99\":\n\t\tr.latencyP99 = float64(sample.Value * 1000)\n\t}\n}\n\nfunc (r *outboundRouteRow) populateBackendLatency(quantile string, sample *model.Sample) {\n\tkey := backendKey{\n\t\tname: (string)(sample.Metric[\"backend_name\"]),\n\t\tport: (string)(sample.Metric[\"backend_port\"]),\n\t}\n\tbackend := r.backends[key]\n\tbackend.populateLatency(quantile, sample)\n\tif r.backends == nil {\n\t\tr.backends = make(map[backendKey]outboundBackendRow)\n\t}\n\tr.backends[key] = backend\n}\n\nfunc (r *outboundBackendRow) populateHTTPCounts(sample *model.Sample) {\n\tlabels := sample.Metric\n\tif strings.HasPrefix((string)(labels[\"http_status\"]), \"5\") || labels[\"error\"] != \"\" {\n\t\tr.failures += uint64(sample.Value)\n\t} else {\n\t\tr.successes += uint64(sample.Value)\n\t}\n\tif labels[\"error\"] == \"RESPONSE_HEADERS_TIMEOUT\" || labels[\"error\"] == \"REQUEST_TIMEOUT\" {\n\t\tr.timeouts += uint64(sample.Value)\n\t}\n}\n\nfunc (r *outboundRouteRow) populateBackendHTTPCounts(sample *model.Sample) {\n\tkey := backendKey{\n\t\tname: (string)(sample.Metric[\"backend_name\"]),\n\t\tport: (string)(sample.Metric[\"backend_port\"]),\n\t}\n\tbackend := r.backends[key]\n\tbackend.populateHTTPCounts(sample)\n\tif r.backends == nil {\n\t\tr.backends = make(map[backendKey]outboundBackendRow)\n\t}\n\tr.backends[key] = backend\n}\n\nfunc (r *outboundBackendRow) populateGRPCCounts(sample *model.Sample) {\n\tlabels := sample.Metric\n\tif labels[\"grpc_status\"] != \"OK\" || labels[\"error\"] != \"\" {\n\t\tr.failures += uint64(sample.Value)\n\t} else {\n\t\tr.successes += uint64(sample.Value)\n\t}\n\tif labels[\"error\"] == \"RESPONSE_HEADERS_TIMEOUT\" || labels[\"error\"] == \"REQUEST_TIMEOUT\" {\n\t\tr.timeouts += uint64(sample.Value)\n\t}\n}\n\nfunc (r *outboundRouteRow) populateBackendGRPCCounts(sample *model.Sample) {\n\tkey := backendKey{\n\t\tname: (string)(sample.Metric[\"backend_name\"]),\n\t\tport: (string)(sample.Metric[\"backend_port\"]),\n\t}\n\tbackend := r.backends[key]\n\tbackend.populateGRPCCounts(sample)\n\tif r.backends == nil {\n\t\tr.backends = make(map[backendKey]outboundBackendRow)\n\t}\n\tr.backends[key] = backend\n}\n\nfunc renderStatOutboundTable(results map[outboundRowKey]outboundRouteRow, windowLength time.Duration) {\n\trows := make([][]string, 0)\n\n\tfor key, row := range results {\n\t\tif row.failures+row.successes == 0 {\n\t\t\tcontinue\n\t\t}\n\t\trows = append(rows, []string{\n\t\t\tkey.name,\n\t\t\tfmt.Sprintf(\"%s:%s\", key.service, key.port),\n\t\t\tkey.route,\n\t\t\tkey.routeType,\n\t\t\t\"\", // backend\n\t\t\tfmt.Sprintf(\"%.2f%%\", (float32)(row.successes)/(float32)(row.successes+row.failures)*100.0),\n\t\t\tfmt.Sprintf(\"%.2f\", (float32)(row.successes+row.failures)/float32(windowLength.Seconds())),\n\t\t\tformatLatencyMs(row.latencyP50),\n\t\t\tformatLatencyMs(row.latencyP95),\n\t\t\tformatLatencyMs(row.latencyP99),\n\t\t\tfmt.Sprintf(\"%.2f%%\", (float32)(row.timeouts)/(float32)(row.successes+row.failures)*100.0),\n\t\t\tfmt.Sprintf(\"%.2f%%\", (float32)(row.retries)/(float32)(row.successes+row.failures+row.retries)*100.0),\n\t\t})\n\t\tfor backend, backendRow := range row.backends {\n\t\t\trows = append(rows, []string{\n\t\t\t\t\"\",\n\t\t\t\t\"\",\n\t\t\t\t\"├─\",\n\t\t\t\t\"─►\",\n\t\t\t\tfmt.Sprintf(\"%s:%s\", backend.name, backend.port),\n\t\t\t\tfmt.Sprintf(\"%.2f%%\", (float32)(backendRow.successes)/(float32)(backendRow.successes+backendRow.failures)*100.0),\n\t\t\t\tfmt.Sprintf(\"%.2f\", (float32)(backendRow.successes+backendRow.failures)/float32(windowLength.Seconds())),\n\t\t\t\tformatLatencyMs(backendRow.latencyP50),\n\t\t\t\tformatLatencyMs(backendRow.latencyP95),\n\t\t\t\tformatLatencyMs(backendRow.latencyP99),\n\t\t\t\tfmt.Sprintf(\"%.2f%%\", (float32)(backendRow.timeouts)/(float32)(backendRow.successes+backendRow.failures)*100.0),\n\t\t\t\t\"\", // retries\n\t\t\t})\n\t\t}\n\t\tlastBackendLine := rows[len(rows)-1][2]\n\t\trows[len(rows)-1][2] = strings.Replace(lastBackendLine, \"├\", \"└\", 1)\n\t}\n\n\tcolumns := []table.Column{\n\t\ttable.NewColumn(\"NAME\").WithLeftAlign(),\n\t\ttable.NewColumn(\"SERVICE\").WithLeftAlign(),\n\t\ttable.NewColumn(\"ROUTE\").WithLeftAlign(),\n\t\ttable.NewColumn(\"TYPE\").WithLeftAlign(),\n\t\ttable.NewColumn(\"BACKEND\").WithLeftAlign(),\n\t\ttable.NewColumn(\"SUCCESS\"),\n\t\ttable.NewColumn(\"RPS\"),\n\t\ttable.NewColumn(\"LATENCY_P50\"),\n\t\ttable.NewColumn(\"LATENCY_P95\"),\n\t\ttable.NewColumn(\"LATENCY_P99\"),\n\t\ttable.NewColumn(\"TIMEOUTS\"),\n\t\ttable.NewColumn(\"RETRIES\"),\n\t}\n\n\ttable := table.NewTable(columns, rows)\n\ttable.Render(os.Stdout)\n}\n\nfunc renderStatOutboundJSON(results map[outboundRowKey]outboundRouteRow, windowLength time.Duration) error {\n\trows := make([]outboundJsonRow, 0)\n\n\tfor key, result := range results {\n\t\tresult := result // To avoid golangci-lint complaining about memory aliasing.\n\t\tif result.failures+result.successes == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tretryRate := (float64)(result.retries) / (float64)(result.successes+result.failures+result.retries)\n\t\trow := outboundJsonRow{\n\t\t\tName:        key.name,\n\t\t\tService:     key.service,\n\t\t\tPort:        key.port,\n\t\t\tRoute:       key.route,\n\t\t\tRouteType:   key.routeType,\n\t\t\tSuccessRate: (float64)(result.successes) / (float64)(result.successes+result.failures),\n\t\t\tRPS:         (float64)(result.successes+result.failures) / windowLength.Seconds(),\n\t\t\tLatencyP50:  &result.latencyP50,\n\t\t\tLatencyP95:  &result.latencyP95,\n\t\t\tLatencyP99:  &result.latencyP99,\n\t\t\tTimeoutRate: (float64)(result.timeouts) / (float64)(result.successes+result.failures),\n\t\t\tRetryRate:   &retryRate,\n\t\t}\n\t\tif math.IsNaN(result.latencyP50) {\n\t\t\trow.LatencyP50 = nil\n\t\t}\n\t\tif math.IsNaN(result.latencyP95) {\n\t\t\trow.LatencyP95 = nil\n\t\t}\n\t\tif math.IsNaN(result.latencyP99) {\n\t\t\trow.LatencyP99 = nil\n\t\t}\n\n\t\trows = append(rows, row)\n\n\t\tfor backend, result := range result.backends {\n\t\t\tresult := result // To avoid golangci-lint complaining about memory aliasing.\n\t\t\tif result.failures+result.successes == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trow := outboundJsonRow{\n\t\t\t\tName:        key.name,\n\t\t\t\tService:     key.service,\n\t\t\t\tPort:        key.port,\n\t\t\t\tRoute:       key.route,\n\t\t\t\tRouteType:   key.routeType,\n\t\t\t\tBackend:     backend.name,\n\t\t\t\tBackendPort: backend.port,\n\t\t\t\tSuccessRate: (float64)(result.successes) / (float64)(result.successes+result.failures),\n\t\t\t\tRPS:         (float64)(result.successes+result.failures) / windowLength.Seconds(),\n\t\t\t\tLatencyP50:  &result.latencyP50,\n\t\t\t\tLatencyP95:  &result.latencyP95,\n\t\t\t\tLatencyP99:  &result.latencyP99,\n\t\t\t\tTimeoutRate: (float64)(result.timeouts) / (float64)(result.successes+result.failures),\n\t\t\t}\n\t\t\tif math.IsNaN(result.latencyP50) {\n\t\t\t\trow.LatencyP50 = nil\n\t\t\t}\n\t\t\tif math.IsNaN(result.latencyP95) {\n\t\t\t\trow.LatencyP95 = nil\n\t\t\t}\n\t\t\tif math.IsNaN(result.latencyP99) {\n\t\t\t\trow.LatencyP99 = nil\n\t\t\t}\n\t\t\trows = append(rows, row)\n\t\t}\n\t}\n\n\tout, err := json.Marshal(rows)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Println(string(out))\n\treturn nil\n}\n"
  },
  {
    "path": "viz/cmd/stat.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\t\"time\"\n\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/metrics-api/util\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/api\"\n\thc \"github.com/linkerd/linkerd2/viz/pkg/healthcheck\"\n\tpkgUtil \"github.com/linkerd/linkerd2/viz/pkg/util\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\tv1 \"k8s.io/api/core/v1\"\n)\n\ntype statOptions struct {\n\tstatOptionsBase\n\ttoNamespace   string\n\ttoResource    string\n\tfromNamespace string\n\tfromResource  string\n\tallNamespaces bool\n\tlabelSelector string\n\tunmeshed      bool\n}\n\ntype statOptionsBase struct {\n\t// namespace is only referenced from the outer struct statOptions (e.g.\n\t// options.namespace where options's type is _not_ this struct).\n\t// structcheck issues a false positive for this field as it does not think\n\t// it's used.\n\t//nolint:structcheck\n\tnamespace    string\n\ttimeWindow   string\n\toutputFormat string\n}\n\nfunc newStatOptionsBase() *statOptionsBase {\n\treturn &statOptionsBase{\n\t\ttimeWindow:   \"1m\",\n\t\toutputFormat: tableOutput,\n\t}\n}\n\nfunc (o *statOptionsBase) validateOutputFormat() error {\n\tswitch o.outputFormat {\n\tcase tableOutput, jsonOutput, wideOutput:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"--output currently only supports %s, %s and %s\", tableOutput, jsonOutput, wideOutput)\n\t}\n}\n\ntype indexedResults struct {\n\tix   int\n\trows []*pb.StatTable_PodGroup_Row\n\terr  error\n}\n\nfunc newStatOptions() *statOptions {\n\treturn &statOptions{\n\t\tstatOptionsBase: *newStatOptionsBase(),\n\t\ttoNamespace:     \"\",\n\t\ttoResource:      \"\",\n\t\tfromNamespace:   \"\",\n\t\tfromResource:    \"\",\n\t\tallNamespaces:   false,\n\t\tlabelSelector:   \"\",\n\t\tunmeshed:        false,\n\t}\n}\n\n// NewCmdStat creates a new cobra command `stat` for stat functionality\nfunc NewCmdStat() *cobra.Command {\n\toptions := newStatOptions()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"stat [flags] (RESOURCES)\",\n\t\tShort: \"Display traffic stats about one or many resources\",\n\t\tLong: `Display traffic stats about one or many resources.\n\n  The RESOURCES argument specifies the target resource(s) to aggregate stats over:\n  (TYPE [NAME] | TYPE/NAME)\n  or (TYPE [NAME1] [NAME2]...)\n  or (TYPE1/NAME1 TYPE2/NAME2...)\n\n  Examples:\n  * cronjob/my-cronjob\n  * deploy\n  * deploy/my-deploy\n  * deploy/ po/\n  * ds/my-daemonset\n  * job/my-job\n  * ns/my-ns\n  * po/mypod1 rc/my-replication-controller\n  * po mypod1 mypod2\n  * rc/my-replication-controller\n  * rs\n  * rs/my-replicaset\n  * sts/my-statefulset\n  * ts/my-split\n  * authority\n  * au/my-authority\n  * httproute/my-route\n  * route/my-route\n  * all\n\n  Valid resource types include:\n  * cronjobs\n  * daemonsets\n  * deployments\n  * namespaces\n  * jobs\n  * pods\n  * replicasets\n  * replicationcontrollers\n  * statefulsets\n  * authorities (not supported in --from)\n  * authorizationpolicies (not supported in --from)\n  * httproutes (not supported in --from)\n  * services (not supported in --from)\n  * servers (not supported in --from)\n  * serverauthorizations (not supported in --from)\n  * all (all resource types, not supported in --from or --to)\n\nThis command will hide resources that have completed, such as pods that are in the Succeeded or Failed phases.\nIf no resource name is specified, displays stats about all resources of the specified RESOURCETYPE`,\n\t\tExample: `  # Get all deployments in the test namespace.\n  linkerd viz stat deployments -n test\n\n  # Get the hello1 replication controller in the test namespace.\n  linkerd viz stat replicationcontrollers hello1 -n test\n\n  # Get all namespaces.\n  linkerd viz stat namespaces\n\n  # Get all inbound stats to the web deployment.\n  linkerd viz stat deploy/web\n\n  # Get all inbound stats to the pod1 and pod2 pods\n  linkerd viz stat po pod1 pod2\n\n  # Get all inbound stats to the pod1 pod and the web deployment\n  linkerd viz stat po/pod1 deploy/web\n\n  # Get all pods in all namespaces that call the hello1 deployment in the test namespace.\n  linkerd viz stat pods --to deploy/hello1 --to-namespace test --all-namespaces\n\n  # Get all pods in all namespaces that call the hello1 service in the test namespace.\n  linkerd viz stat pods --to svc/hello1 --to-namespace test --all-namespaces\n\n  # Get the web service. With Services, metrics are generated from the outbound metrics\n  # of clients, and thus will not include unmeshed client request metrics.\n  linkerd viz stat svc/web\n\n  # Get the web services and metrics for any traffic coming to the service from the hello1 deployment\n  # in the test namespace.\n  linkerd viz stat svc/web --from deploy/hello1 --from-namespace test\n\n  # Get the web services and metrics for all the traffic that reaches the web-pod1 pod\n  # in the test namespace exclusively.\n  linkerd viz stat svc/web --to pod/web-pod1 --to-namespace test\n\n  # Get all services in all namespaces that receive calls from hello1 deployment in the test namespace.\n  linkerd viz stat services --from deploy/hello1 --from-namespace test --all-namespaces\n\n  # Get all namespaces that receive traffic from the default namespace.\n  linkerd viz stat namespaces --from ns/default\n\n  # Get all inbound stats to the test namespace.\n  linkerd viz stat ns/test\n\n  # Get all inbound stats to the emoji-grpc server\n  linkerd viz stat server/emoji-grpc\n\n  # Get all inbound stats to the web-public server authorization resource\n  linkerd viz stat serverauthorization/web-public\n\n  # Get all inbound stats to the web-get and web-delete HTTP route resources\n  linkerd viz stat route/web-get route/web-delete\n\n  # Get all inbound stats to the web-authz authorization policy resource\n  linkerd viz stat authorizationpolicy/web-authz\n  `,\n\t\tArgs: cobra.MinimumNArgs(1),\n\t\tValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\tif options.allNamespaces {\n\t\t\t\toptions.namespace = v1.NamespaceAll\n\t\t\t}\n\n\t\t\tcc := k8s.NewCommandCompletion(k8sAPI, options.namespace)\n\n\t\t\tresults, err := cc.Complete(args, toComplete)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\treturn results, cobra.ShellCompDirectiveDefault\n\t\t},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\treqs, err := buildStatSummaryRequests(args, options)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error creating metrics request while making stats request: %w\", err)\n\t\t\t}\n\n\t\t\t// The gRPC client is concurrency-safe, so we can reuse it in all the following goroutines\n\t\t\t// https://github.com/grpc/grpc-go/issues/682\n\t\t\tclient := api.CheckClientOrExit(hc.VizOptions{\n\t\t\t\tOptions: &healthcheck.Options{\n\t\t\t\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\t\t\t\tKubeConfig:            kubeconfigPath,\n\t\t\t\t\tImpersonate:           impersonate,\n\t\t\t\t\tImpersonateGroup:      impersonateGroup,\n\t\t\t\t\tKubeContext:           kubeContext,\n\t\t\t\t\tAPIAddr:               apiAddr,\n\t\t\t\t},\n\t\t\t\tVizNamespaceOverride: vizNamespace,\n\t\t\t})\n\n\t\t\tc := make(chan indexedResults, len(reqs))\n\t\t\tfor num, req := range reqs {\n\t\t\t\tgo func(num int, req *pb.StatSummaryRequest) {\n\t\t\t\t\tresp, err := requestStatsFromAPI(client, req)\n\t\t\t\t\trows := respToRows(resp)\n\t\t\t\t\tc <- indexedResults{num, rows, err}\n\t\t\t\t}(num, req)\n\t\t\t}\n\n\t\t\ttotalRows := make([]*pb.StatTable_PodGroup_Row, 0)\n\t\t\ti := 0\n\t\t\tfor res := range c {\n\t\t\t\tif res.err != nil {\n\t\t\t\t\tfmt.Fprint(os.Stderr, res.err.Error())\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t\ttotalRows = append(totalRows, res.rows...)\n\t\t\t\tif i++; i == len(reqs) {\n\t\t\t\t\tclose(c)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\toutput := renderStatStats(totalRows, options)\n\t\t\t_, err = fmt.Print(output)\n\n\t\t\treturn err\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace, \"Namespace of the specified resource\")\n\tcmd.PersistentFlags().StringVarP(&options.timeWindow, \"time-window\", \"t\", options.timeWindow, \"Stat window (for example: \\\"15s\\\", \\\"1m\\\", \\\"10m\\\", \\\"1h\\\"). Needs to be at least 15s.\")\n\tcmd.PersistentFlags().StringVar(&options.toResource, \"to\", options.toResource, \"If present, restricts outbound stats to the specified resource name\")\n\tcmd.PersistentFlags().StringVar(&options.toNamespace, \"to-namespace\", options.toNamespace, \"Sets the namespace used to lookup the \\\"--to\\\" resource; by default the current \\\"--namespace\\\" is used\")\n\tcmd.PersistentFlags().StringVar(&options.fromResource, \"from\", options.fromResource, \"If present, restricts outbound stats from the specified resource name\")\n\tcmd.PersistentFlags().StringVar(&options.fromNamespace, \"from-namespace\", options.fromNamespace, \"Sets the namespace used from lookup the \\\"--from\\\" resource; by default the current \\\"--namespace\\\" is used\")\n\tcmd.PersistentFlags().BoolVarP(&options.allNamespaces, \"all-namespaces\", \"A\", options.allNamespaces, \"If present, returns stats across all namespaces, ignoring the \\\"--namespace\\\" flag\")\n\tcmd.PersistentFlags().StringVarP(&options.outputFormat, \"output\", \"o\", options.outputFormat, \"Output format; one of: \\\"table\\\" or \\\"json\\\" or \\\"wide\\\"\")\n\tcmd.PersistentFlags().StringVarP(&options.labelSelector, \"selector\", \"l\", options.labelSelector, \"Selector (label query) to filter on, supports '=', '==', and '!='\")\n\tcmd.PersistentFlags().BoolVar(&options.unmeshed, \"unmeshed\", options.unmeshed, \"If present, include unmeshed resources in the output\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tcmd, []string{\"namespace\", \"to-namespace\", \"from-namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\treturn cmd\n}\n\nfunc respToRows(resp *pb.StatSummaryResponse) []*pb.StatTable_PodGroup_Row {\n\trows := make([]*pb.StatTable_PodGroup_Row, 0)\n\tif resp != nil {\n\t\tfor _, statTable := range resp.GetOk().StatTables {\n\t\t\trows = append(rows, statTable.GetPodGroup().Rows...)\n\t\t}\n\t}\n\treturn rows\n}\n\nfunc requestStatsFromAPI(client pb.ApiClient, req *pb.StatSummaryRequest) (*pb.StatSummaryResponse, error) {\n\tresp, err := client.StatSummary(context.Background(), req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"StatSummary API error: %w\", err)\n\t}\n\tif e := resp.GetError(); e != nil {\n\t\treturn nil, fmt.Errorf(\"StatSummary API response error: %v\", e.Error)\n\t}\n\n\treturn resp, nil\n}\n\nfunc renderStatStats(rows []*pb.StatTable_PodGroup_Row, options *statOptions) string {\n\tvar buffer bytes.Buffer\n\tw := tabwriter.NewWriter(&buffer, 0, 0, padding, ' ', tabwriter.AlignRight)\n\twriteStatsToBuffer(rows, w, options)\n\tw.Flush()\n\n\treturn renderStats(buffer, &options.statOptionsBase)\n}\n\nconst padding = 3\n\ntype rowStats struct {\n\troute              string\n\tdst                string\n\trequestRate        float64\n\tsuccessRate        float64\n\tlatencyP50         uint64\n\tlatencyP95         uint64\n\tlatencyP99         uint64\n\ttcpOpenConnections uint64\n\ttcpReadBytes       float64\n\ttcpWriteBytes      float64\n}\n\ntype srvStats struct {\n\tunauthorizedRate float64\n\tserver           string\n}\n\ntype row struct {\n\tmeshed string\n\tstatus string\n\t*rowStats\n\t*tsStats\n\t*dstStats\n\t*srvStats\n}\n\ntype tsStats struct {\n\tapex   string\n\tleaf   string\n\tweight string\n}\n\ntype dstStats struct {\n\tdst    string\n\tweight string\n}\n\nvar (\n\tnameHeader      = \"NAME\"\n\tnamespaceHeader = \"NAMESPACE\"\n\tapexHeader      = \"APEX\"\n\tleafHeader      = \"LEAF\"\n\tweightHeader    = \"WEIGHT\"\n)\n\nfunc statHasRequestData(stat *pb.BasicStats) bool {\n\treturn stat.GetSuccessCount() != 0 || stat.GetFailureCount() != 0 || stat.GetActualSuccessCount() != 0 || stat.GetActualFailureCount() != 0\n}\n\nfunc isPodOwnerResource(typ string) bool {\n\treturn typ != k8s.Service && typ != k8s.Server && typ != k8s.ServerAuthorization && typ != k8s.AuthorizationPolicy && typ != k8s.HTTPRoute\n}\n\nfunc writeStatsToBuffer(rows []*pb.StatTable_PodGroup_Row, w *tabwriter.Writer, options *statOptions) {\n\tmaxNameLength := len(nameHeader)\n\tmaxNamespaceLength := len(namespaceHeader)\n\tmaxApexLength := len(apexHeader)\n\tmaxLeafLength := len(leafHeader)\n\tmaxDstLength := len(dstHeader)\n\tmaxWeightLength := len(weightHeader)\n\n\tstatTables := make(map[string]map[string]*row)\n\n\tprefixTypes := make(map[string]bool)\n\tfor _, r := range rows {\n\t\tprefixTypes[r.Resource.Type] = true\n\t}\n\tusePrefix := false\n\tif len(prefixTypes) > 1 {\n\t\tusePrefix = true\n\t}\n\n\tfor _, r := range rows {\n\n\t\t// Skip unmeshed pods if the unmeshed option isn't enabled.\n\t\tif !options.unmeshed && r.GetMeshedPodCount() == 0 &&\n\t\t\t// Skip only if the resource can own pods\n\t\t\tisPodOwnerResource(r.Resource.Type) &&\n\t\t\t// Skip only if --from isn't specified (unmeshed resources can show\n\t\t\t// stats in --from mode because metrics are collected on the client\n\t\t\t// side).\n\t\t\toptions.fromResource == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tname := r.Resource.Name\n\t\tnameWithPrefix := name\n\t\tif usePrefix {\n\t\t\tnameWithPrefix = getNamePrefix(r.Resource.Type) + nameWithPrefix\n\t\t}\n\n\t\tnamespace := r.Resource.Namespace\n\t\tkey := fmt.Sprintf(\"%s/%s\", namespace, name)\n\n\t\tresourceKey := r.Resource.Type\n\n\t\tif _, ok := statTables[resourceKey]; !ok {\n\t\t\tstatTables[resourceKey] = make(map[string]*row)\n\t\t}\n\n\t\tif len(nameWithPrefix) > maxNameLength {\n\t\t\tmaxNameLength = len(nameWithPrefix)\n\t\t}\n\n\t\tif len(namespace) > maxNamespaceLength {\n\t\t\tmaxNamespaceLength = len(namespace)\n\t\t}\n\n\t\tstatTables[resourceKey][key] = &row{}\n\t\tif resourceKey != k8s.Server && resourceKey != k8s.ServerAuthorization {\n\t\t\tmeshedCount := fmt.Sprintf(\"%d/%d\", r.MeshedPodCount, r.RunningPodCount)\n\t\t\tif resourceKey == k8s.Service {\n\t\t\t\tmeshedCount = \"-\"\n\t\t\t}\n\t\t\tstatTables[resourceKey][key] = &row{\n\t\t\t\tmeshed: meshedCount,\n\t\t\t\tstatus: r.Status,\n\t\t\t}\n\t\t}\n\n\t\tif r.Stats != nil && statHasRequestData(r.Stats) {\n\t\t\tstatTables[resourceKey][key].rowStats = &rowStats{\n\t\t\t\trequestRate:        getRequestRate(r.Stats.GetSuccessCount(), r.Stats.GetFailureCount(), r.TimeWindow),\n\t\t\t\tsuccessRate:        getSuccessRate(r.Stats.GetSuccessCount(), r.Stats.GetFailureCount()),\n\t\t\t\tlatencyP50:         r.Stats.LatencyMsP50,\n\t\t\t\tlatencyP95:         r.Stats.LatencyMsP95,\n\t\t\t\tlatencyP99:         r.Stats.LatencyMsP99,\n\t\t\t\ttcpOpenConnections: r.GetTcpStats().GetOpenConnections(),\n\t\t\t\ttcpReadBytes:       getByteRate(r.GetTcpStats().GetReadBytesTotal(), r.TimeWindow),\n\t\t\t\ttcpWriteBytes:      getByteRate(r.GetTcpStats().GetWriteBytesTotal(), r.TimeWindow),\n\t\t\t}\n\t\t}\n\n\t\tif r.SrvStats != nil {\n\t\t\tstatTables[resourceKey][key].srvStats = &srvStats{\n\t\t\t\tunauthorizedRate: getSuccessRate(r.SrvStats.GetDeniedCount(), r.SrvStats.GetAllowedCount()),\n\t\t\t\tserver:           r.GetSrvStats().GetSrv().GetName(),\n\t\t\t}\n\t\t}\n\t}\n\n\tswitch options.outputFormat {\n\tcase tableOutput, wideOutput:\n\t\tif len(statTables) == 0 {\n\t\t\tfmt.Fprintln(os.Stderr, \"No traffic found.\")\n\t\t\treturn\n\t\t}\n\t\tprintStatTables(statTables, w, maxNameLength, maxNamespaceLength, maxLeafLength, maxApexLength, maxDstLength, maxWeightLength, options)\n\tcase jsonOutput:\n\t\tprintStatJSON(statTables, w)\n\t}\n}\n\nfunc printStatTables(statTables map[string]map[string]*row, w *tabwriter.Writer, maxNameLength, maxNamespaceLength, maxLeafLength, maxApexLength, maxDstLength, maxWeightLength int, options *statOptions) {\n\tusePrefix := false\n\tif len(statTables) > 1 {\n\t\tusePrefix = true\n\t}\n\n\tfirstDisplayedStat := true // don't print a newline before the first stat\n\tfor _, resourceType := range k8s.AllResources {\n\t\tif stats, ok := statTables[resourceType]; ok {\n\t\t\tif !firstDisplayedStat {\n\t\t\t\tfmt.Fprint(w, \"\\n\")\n\t\t\t}\n\t\t\tfirstDisplayedStat = false\n\t\t\tresourceTypeLabel := resourceType\n\t\t\tif !usePrefix {\n\t\t\t\tresourceTypeLabel = \"\"\n\t\t\t}\n\t\t\tprintSingleStatTable(stats, resourceTypeLabel, resourceType, w, maxNameLength, maxNamespaceLength, maxLeafLength, maxApexLength, maxDstLength, maxWeightLength, options)\n\t\t}\n\t}\n}\n\nfunc showTCPBytes(options *statOptions, resourceType string) bool {\n\treturn (options.outputFormat == wideOutput || options.outputFormat == jsonOutput) &&\n\t\tshowTCPConns(resourceType)\n}\n\nfunc showTCPConns(resourceType string) bool {\n\treturn resourceType != k8s.ServerAuthorization && resourceType != k8s.AuthorizationPolicy && resourceType != k8s.HTTPRoute\n}\n\nfunc printSingleStatTable(stats map[string]*row, resourceTypeLabel, resourceType string, w *tabwriter.Writer, maxNameLength, maxNamespaceLength, maxLeafLength, maxApexLength, maxDstLength, maxWeightLength int, options *statOptions) {\n\theaders := make([]string, 0)\n\tnameTemplate := fmt.Sprintf(\"%%-%ds\", maxNameLength)\n\tnamespaceTemplate := fmt.Sprintf(\"%%-%ds\", maxNamespaceLength)\n\tapexTemplate := fmt.Sprintf(\"%%-%ds\", maxApexLength)\n\tleafTemplate := fmt.Sprintf(\"%%-%ds\", maxLeafLength)\n\tdstTemplate := fmt.Sprintf(\"%%-%ds\", maxDstLength)\n\tweightTemplate := fmt.Sprintf(\"%%-%ds\", maxWeightLength)\n\n\thasDstStats := false\n\tfor _, r := range stats {\n\t\tif r.dstStats != nil {\n\t\t\thasDstStats = true\n\t\t}\n\t}\n\n\thasTsStats := false\n\tfor _, r := range stats {\n\t\tif r.tsStats != nil {\n\t\t\thasTsStats = true\n\t\t}\n\t}\n\n\tif options.allNamespaces {\n\t\theaders = append(headers,\n\t\t\tfmt.Sprintf(namespaceTemplate, namespaceHeader))\n\t}\n\n\theaders = append(headers,\n\t\tfmt.Sprintf(nameTemplate, nameHeader))\n\n\tif resourceType == k8s.Pod {\n\t\theaders = append(headers, \"STATUS\")\n\t}\n\n\tif resourceType == k8s.HTTPRoute {\n\t\theaders = append(headers, \"SERVER\")\n\t}\n\n\tif hasDstStats {\n\t\theaders = append(headers,\n\t\t\tfmt.Sprintf(dstTemplate, dstHeader),\n\t\t\tfmt.Sprintf(weightTemplate, weightHeader))\n\t} else if hasTsStats {\n\t\theaders = append(headers,\n\t\t\tfmt.Sprintf(apexTemplate, apexHeader),\n\t\t\tfmt.Sprintf(leafTemplate, leafHeader),\n\t\t\tfmt.Sprintf(weightTemplate, weightHeader))\n\t} else if resourceType != k8s.Server && resourceType != k8s.ServerAuthorization && resourceType != k8s.AuthorizationPolicy && resourceType != k8s.HTTPRoute {\n\t\theaders = append(headers, \"MESHED\")\n\t}\n\n\tif resourceType == k8s.Server || resourceType == k8s.HTTPRoute {\n\t\theaders = append(headers, \"UNAUTHORIZED\")\n\t}\n\n\theaders = append(headers, []string{\n\t\t\"SUCCESS\",\n\t\t\"RPS\",\n\t\t\"LATENCY_P50\",\n\t\t\"LATENCY_P95\",\n\t\t\"LATENCY_P99\",\n\t}...)\n\n\tif showTCPConns(resourceType) {\n\t\theaders = append(headers, \"TCP_CONN\")\n\t}\n\n\tif showTCPBytes(options, resourceType) {\n\t\theaders = append(headers, []string{\n\t\t\t\"READ_BYTES/SEC\",\n\t\t\t\"WRITE_BYTES/SEC\",\n\t\t}...)\n\t}\n\n\theaders[len(headers)-1] = headers[len(headers)-1] + \"\\t\" // trailing \\t is required to format last column\n\n\tfmt.Fprintln(w, strings.Join(headers, \"\\t\"))\n\n\tsortedKeys := sortStatsKeys(stats)\n\tfor _, key := range sortedKeys {\n\t\tnamespace, name := namespaceName(resourceTypeLabel, key)\n\t\tvalues := make([]interface{}, 0)\n\t\ttemplateString := \"%s\\t%s\\t%.2f%%\\t%.1frps\\t%dms\\t%dms\\t%dms\\t\"\n\t\ttemplateStringEmpty := \"%s\\t%s\\t-\\t-\\t-\\t-\\t-\\t\"\n\t\tif resourceType == k8s.Pod {\n\t\t\ttemplateString = \"%s\\t\" + templateString\n\t\t\ttemplateStringEmpty = \"%s\\t\" + templateStringEmpty\n\t\t}\n\n\t\tif hasTsStats {\n\t\t\ttemplateString = \"%s\\t%s\\t%s\\t%s\\t%.2f%%\\t%.1frps\\t%dms\\t%dms\\t%dms\\t\"\n\t\t\ttemplateStringEmpty = \"%s\\t%s\\t%s\\t%s\\t-\\t-\\t-\\t-\\t-\\t\"\n\t\t} else if hasDstStats {\n\t\t\ttemplateString = \"%s\\t%s\\t%s\\t%.2f%%\\t%.1frps\\t%dms\\t%dms\\t%dms\\t\"\n\t\t\ttemplateStringEmpty = \"%s\\t%s\\t%s\\t-\\t-\\t-\\t-\\t-\\t\"\n\t\t} else if resourceType == k8s.ServerAuthorization || resourceType == k8s.AuthorizationPolicy {\n\t\t\ttemplateString = \"%s\\t%.2f%%\\t%.1frps\\t%dms\\t%dms\\t%dms\\t\"\n\t\t\ttemplateStringEmpty = \"%s\\t-\\t-\\t-\\t-\\t-\\t\"\n\t\t} else if resourceType == k8s.Server {\n\t\t\ttemplateString = \"%s\\t%.1frps\\t%.2f%%\\t%.1frps\\t%dms\\t%dms\\t%dms\\t\"\n\t\t\ttemplateStringEmpty = \"%s\\t%.1frps\\t-\\t-\\t-\\t-\\t-\\t\"\n\t\t} else if resourceType == k8s.HTTPRoute {\n\t\t\ttemplateString = \"%s\\t%s\\t%.1frps\\t%.2f%%\\t%.1frps\\t%dms\\t%dms\\t%dms\\t\"\n\t\t\ttemplateStringEmpty = \"%s\\t%s\\t%.1frps\\t-\\t-\\t-\\t-\\t-\\t\"\n\t\t}\n\n\t\tif showTCPConns(resourceType) {\n\t\t\ttemplateString += \"%d\\t\"\n\t\t\ttemplateStringEmpty += \"-\\t\"\n\t\t}\n\n\t\tif showTCPBytes(options, resourceType) {\n\t\t\ttemplateString += \"%.1fB/s\\t%.1fB/s\\t\"\n\t\t\ttemplateStringEmpty += \"-\\t-\\t\"\n\t\t}\n\n\t\tif options.allNamespaces {\n\t\t\tvalues = append(values,\n\t\t\t\tnamespace+strings.Repeat(\" \", maxNamespaceLength-len(namespace)))\n\t\t\ttemplateString = \"%s\\t\" + templateString\n\t\t\ttemplateStringEmpty = \"%s\\t\" + templateStringEmpty\n\t\t}\n\n\t\ttemplateString += \"\\n\"\n\t\ttemplateStringEmpty += \"\\n\"\n\n\t\tpadding := 0\n\t\tif maxNameLength > len(name) {\n\t\t\tpadding = maxNameLength - len(name)\n\t\t}\n\n\t\tapexPadding := 0\n\t\tleafPadding := 0\n\t\tdstPadding := 0\n\n\t\tif stats[key].tsStats != nil {\n\t\t\tif maxApexLength > len(stats[key].tsStats.apex) {\n\t\t\t\tapexPadding = maxApexLength - len(stats[key].tsStats.apex)\n\t\t\t}\n\t\t\tif maxLeafLength > len(stats[key].tsStats.leaf) {\n\t\t\t\tleafPadding = maxLeafLength - len(stats[key].tsStats.leaf)\n\t\t\t}\n\t\t} else if stats[key].dstStats != nil {\n\t\t\tif maxDstLength > len(stats[key].dstStats.dst) {\n\t\t\t\tdstPadding = maxDstLength - len(stats[key].dstStats.dst)\n\t\t\t}\n\t\t}\n\n\t\tvalues = append(values, name+strings.Repeat(\" \", padding))\n\t\tif resourceType == k8s.Pod {\n\t\t\tvalues = append(values, stats[key].status)\n\t\t}\n\n\t\tif hasTsStats {\n\t\t\tvalues = append(values,\n\t\t\t\tstats[key].tsStats.apex+strings.Repeat(\" \", apexPadding),\n\t\t\t\tstats[key].tsStats.leaf+strings.Repeat(\" \", leafPadding),\n\t\t\t\tstats[key].tsStats.weight,\n\t\t\t)\n\t\t} else if hasDstStats {\n\t\t\tvalues = append(values,\n\t\t\t\tstats[key].dstStats.dst+strings.Repeat(\" \", dstPadding),\n\t\t\t\tstats[key].dstStats.weight,\n\t\t\t)\n\t\t} else if resourceType != k8s.ServerAuthorization && resourceType != k8s.Server && resourceType != k8s.AuthorizationPolicy && resourceType != k8s.HTTPRoute {\n\t\t\tvalues = append(values, []interface{}{\n\t\t\t\tstats[key].meshed,\n\t\t\t}...)\n\t\t}\n\n\t\tif resourceType == k8s.HTTPRoute {\n\t\t\tvalues = append(values, stats[key].srvStats.server)\n\t\t}\n\n\t\tif resourceType == k8s.Server || resourceType == k8s.HTTPRoute {\n\t\t\tvar unauthorizedRate float64\n\t\t\tif stats[key].srvStats != nil {\n\t\t\t\tunauthorizedRate = stats[key].srvStats.unauthorizedRate\n\t\t\t}\n\t\t\tvalues = append(values, []interface{}{\n\t\t\t\tunauthorizedRate,\n\t\t\t}...)\n\t\t}\n\n\t\tif stats[key].rowStats != nil {\n\t\t\tvalues = append(values, []interface{}{\n\t\t\t\tstats[key].successRate * 100,\n\t\t\t\tstats[key].requestRate,\n\t\t\t\tstats[key].latencyP50,\n\t\t\t\tstats[key].latencyP95,\n\t\t\t\tstats[key].latencyP99,\n\t\t\t}...)\n\n\t\t\tif showTCPConns(resourceType) {\n\t\t\t\tvalues = append(values, stats[key].tcpOpenConnections)\n\t\t\t}\n\n\t\t\tif showTCPBytes(options, resourceType) {\n\t\t\t\tvalues = append(values, []interface{}{\n\t\t\t\t\tstats[key].tcpReadBytes,\n\t\t\t\t\tstats[key].tcpWriteBytes,\n\t\t\t\t}...)\n\t\t\t}\n\n\t\t\tfmt.Fprintf(w, templateString, values...)\n\t\t} else {\n\t\t\tfmt.Fprintf(w, templateStringEmpty, values...)\n\t\t}\n\t}\n}\n\nfunc namespaceName(resourceType string, key string) (string, string) {\n\tparts := strings.Split(key, \"/\")\n\tnamespace := parts[0]\n\tnamePrefix := getNamePrefix(resourceType)\n\tname := namePrefix + parts[1]\n\treturn namespace, name\n}\n\n// Using pointers where the value is NA and the corresponding json is null\ntype jsonStats struct {\n\tNamespace      string   `json:\"namespace\"`\n\tKind           string   `json:\"kind\"`\n\tName           string   `json:\"name\"`\n\tMeshed         string   `json:\"meshed,omitempty\"`\n\tSuccess        *float64 `json:\"success\"`\n\tRps            *float64 `json:\"rps\"`\n\tLatencyMSp50   *uint64  `json:\"latency_ms_p50\"`\n\tLatencyMSp95   *uint64  `json:\"latency_ms_p95\"`\n\tLatencyMSp99   *uint64  `json:\"latency_ms_p99\"`\n\tTCPConnections *uint64  `json:\"tcp_open_connections,omitempty\"`\n\tTCPReadBytes   *float64 `json:\"tcp_read_bytes_rate,omitempty\"`\n\tTCPWriteBytes  *float64 `json:\"tcp_write_bytes_rate,omitempty\"`\n\tApex           string   `json:\"apex,omitempty\"`\n\tLeaf           string   `json:\"leaf,omitempty\"`\n\tDst            string   `json:\"dst,omitempty\"`\n\tWeight         string   `json:\"weight,omitempty\"`\n\tUnauthorized   *float64 `json:\"unauthorized,omitempty\"`\n}\n\nfunc printStatJSON(statTables map[string]map[string]*row, w *tabwriter.Writer) {\n\t// avoid nil initialization so that if there are not stats it gets marshalled as an empty array vs null\n\tentries := []*jsonStats{}\n\tfor _, resourceType := range k8s.AllResources {\n\t\tif stats, ok := statTables[resourceType]; ok {\n\t\t\tsortedKeys := sortStatsKeys(stats)\n\t\t\tfor _, key := range sortedKeys {\n\t\t\t\tnamespace, name := namespaceName(\"\", key)\n\t\t\t\tentry := &jsonStats{\n\t\t\t\t\tNamespace: namespace,\n\t\t\t\t\tKind:      resourceType,\n\t\t\t\t\tName:      name,\n\t\t\t\t}\n\n\t\t\t\tif stats[key].rowStats != nil {\n\t\t\t\t\tentry.Success = &stats[key].successRate\n\t\t\t\t\tentry.Rps = &stats[key].requestRate\n\t\t\t\t\tentry.LatencyMSp50 = &stats[key].latencyP50\n\t\t\t\t\tentry.LatencyMSp95 = &stats[key].latencyP95\n\t\t\t\t\tentry.LatencyMSp99 = &stats[key].latencyP99\n\n\t\t\t\t\tif showTCPConns(resourceType) {\n\t\t\t\t\t\tentry.TCPConnections = &stats[key].tcpOpenConnections\n\t\t\t\t\t\tentry.TCPReadBytes = &stats[key].tcpReadBytes\n\t\t\t\t\t\tentry.TCPWriteBytes = &stats[key].tcpWriteBytes\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif stats[key].tsStats != nil {\n\t\t\t\t\tentry.Apex = stats[key].apex\n\t\t\t\t\tentry.Leaf = stats[key].leaf\n\t\t\t\t\tentry.Weight = stats[key].tsStats.weight\n\t\t\t\t} else if stats[key].dstStats != nil {\n\t\t\t\t\tentry.Dst = stats[key].dstStats.dst\n\t\t\t\t\tentry.Weight = stats[key].dstStats.weight\n\t\t\t\t}\n\n\t\t\t\tif resourceType == k8s.Server {\n\t\t\t\t\tif stats[key].srvStats != nil {\n\t\t\t\t\t\tentry.Unauthorized = &stats[key].srvStats.unauthorizedRate\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tentries = append(entries, entry)\n\t\t\t}\n\t\t}\n\t}\n\tb, err := json.MarshalIndent(entries, \"\", \"  \")\n\tif err != nil {\n\t\tlog.Error(err.Error())\n\t\treturn\n\t}\n\tfmt.Fprintf(w, \"%s\\n\", b)\n}\n\nfunc getNamePrefix(resourceType string) string {\n\tif resourceType == \"\" {\n\t\treturn \"\"\n\t}\n\n\tcanonicalType := k8s.ShortNameFromCanonicalResourceName(resourceType)\n\treturn canonicalType + \"/\"\n}\n\nfunc buildStatSummaryRequests(resources []string, options *statOptions) ([]*pb.StatSummaryRequest, error) {\n\ttargets, err := pkgUtil.BuildResources(options.namespace, resources)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar toRes, fromRes *pb.Resource\n\tif options.toResource != \"\" {\n\t\ttoRes, err = pkgUtil.BuildResource(options.toNamespace, options.toResource)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif options.fromResource != \"\" {\n\t\tfromRes, err = pkgUtil.BuildResource(options.fromNamespace, options.fromResource)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\trequests := make([]*pb.StatSummaryRequest, 0)\n\tfor _, target := range targets {\n\t\terr = options.validate(target.Type)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\trequestParams := util.StatsSummaryRequestParams{\n\t\t\tStatsBaseRequestParams: util.StatsBaseRequestParams{\n\t\t\t\tTimeWindow:    options.timeWindow,\n\t\t\t\tResourceName:  target.Name,\n\t\t\t\tResourceType:  target.Type,\n\t\t\t\tNamespace:     options.namespace,\n\t\t\t\tAllNamespaces: options.allNamespaces,\n\t\t\t},\n\t\t\tToNamespace:   options.toNamespace,\n\t\t\tFromNamespace: options.fromNamespace,\n\t\t\tTCPStats:      true,\n\t\t\tLabelSelector: options.labelSelector,\n\t\t}\n\t\tif fromRes != nil {\n\t\t\trequestParams.FromName = fromRes.Name\n\t\t\trequestParams.FromType = fromRes.Type\n\t\t}\n\t\tif toRes != nil {\n\t\t\trequestParams.ToName = toRes.Name\n\t\t\trequestParams.ToType = toRes.Type\n\t\t}\n\n\t\treq, err := util.BuildStatSummaryRequest(requestParams)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trequests = append(requests, req)\n\t}\n\n\treturn requests, nil\n}\n\nfunc sortStatsKeys(stats map[string]*row) []string {\n\tvar sortedKeys []string\n\tfor key := range stats {\n\t\tsortedKeys = append(sortedKeys, key)\n\t}\n\tsort.Strings(sortedKeys)\n\treturn sortedKeys\n}\n\n// validate performs all validation on the command-line options.\n// It returns the first error encountered, or `nil` if the options are valid.\nfunc (o *statOptions) validate(resourceType string) error {\n\terr := o.validateConflictingFlags()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif resourceType == k8s.Namespace {\n\t\terr := o.validateNamespaceFlags()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn o.validateOutputFormat()\n}\n\n// validateConflictingFlags validates that the options do not contain mutually\n// exclusive flags.\nfunc (o *statOptions) validateConflictingFlags() error {\n\tif o.toResource != \"\" && o.fromResource != \"\" {\n\t\treturn fmt.Errorf(\"--to and --from flags are mutually exclusive\")\n\t}\n\n\tif o.toNamespace != \"\" && o.fromNamespace != \"\" {\n\t\treturn fmt.Errorf(\"--to-namespace and --from-namespace flags are mutually exclusive\")\n\t}\n\n\tif o.allNamespaces && o.namespace != pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext) {\n\t\treturn fmt.Errorf(\"--all-namespaces and --namespace flags are mutually exclusive\")\n\t}\n\n\treturn nil\n}\n\n// validateNamespaceFlags performs additional validation for options when the target\n// resource type is a namespace.\nfunc (o *statOptions) validateNamespaceFlags() error {\n\tif o.toNamespace != \"\" {\n\t\treturn fmt.Errorf(\"--to-namespace flag is incompatible with namespace resource type\")\n\t}\n\n\tif o.fromNamespace != \"\" {\n\t\treturn fmt.Errorf(\"--from-namespace flag is incompatible with namespace resource type\")\n\t}\n\n\t// Note: technically, this allows you to say `stat ns --namespace <default-namespace-from-kubectl-context>`, but that\n\t// seems like an edge case.\n\tif o.namespace != pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext) {\n\t\treturn fmt.Errorf(\"--namespace flag is incompatible with namespace resource type\")\n\t}\n\n\treturn nil\n}\n\n// get byte rate calculates the read/write byte rate\nfunc getByteRate(bytes uint64, timeWindow string) float64 {\n\twindowLength, err := time.ParseDuration(timeWindow)\n\tif err != nil {\n\t\tlog.Error(err.Error())\n\t\treturn 0.0\n\t}\n\treturn float64(bytes) / windowLength.Seconds()\n}\n\nfunc renderStats(buffer bytes.Buffer, options *statOptionsBase) string {\n\tvar out string\n\tswitch options.outputFormat {\n\tcase jsonOutput:\n\t\tout = buffer.String()\n\tdefault:\n\t\t// strip left padding on the first column\n\t\tb := buffer.Bytes()\n\t\tif len(b) > padding {\n\t\t\tout = string(b[padding:])\n\t\t}\n\t\tout = strings.ReplaceAll(out, \"\\n\"+strings.Repeat(\" \", padding), \"\\n\")\n\t}\n\n\treturn out\n}\n"
  },
  {
    "path": "viz/cmd/stat_test.go",
    "content": "package cmd\n\nimport (\n\t\"testing\"\n\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tapi \"github.com/linkerd/linkerd2/viz/metrics-api\"\n)\n\ntype paramsExp struct {\n\tcounts  *api.PodCounts\n\toptions *statOptions\n\tresNs   []string\n\tfile    string\n}\n\nfunc TestStat(t *testing.T) {\n\toptions := newStatOptions()\n\tt.Run(\"Returns namespace stats\", func(t *testing.T) {\n\t\ttestStatCall(paramsExp{\n\t\t\tcounts: &api.PodCounts{\n\t\t\t\tMeshedPods:  1,\n\t\t\t\tRunningPods: 2,\n\t\t\t\tFailedPods:  0,\n\t\t\t},\n\t\t\toptions: options,\n\t\t\tresNs:   []string{\"emojivoto1\"},\n\t\t\tfile:    \"stat_one_output.golden\",\n\t\t}, k8s.Namespace, t)\n\t})\n\n\tt.Run(\"Returns pod stats\", func(t *testing.T) {\n\t\ttestStatCall(paramsExp{\n\t\t\tcounts: &api.PodCounts{\n\t\t\t\tStatus:      \"Running\",\n\t\t\t\tMeshedPods:  1,\n\t\t\t\tRunningPods: 1,\n\t\t\t\tFailedPods:  0,\n\t\t\t},\n\t\t\toptions: options,\n\t\t\tresNs:   []string{\"emojivoto1\"},\n\t\t\tfile:    \"stat_one_pod_output.golden\",\n\t\t}, k8s.Pod, t)\n\t})\n\n\toptions.outputFormat = jsonOutput\n\tt.Run(\"Returns namespace stats (json)\", func(t *testing.T) {\n\t\ttestStatCall(paramsExp{\n\t\t\tcounts: &api.PodCounts{\n\t\t\t\tMeshedPods:  1,\n\t\t\t\tRunningPods: 2,\n\t\t\t\tFailedPods:  0,\n\t\t\t},\n\t\t\toptions: options,\n\t\t\tresNs:   []string{\"emojivoto1\"},\n\t\t\tfile:    \"stat_one_output_json.golden\",\n\t\t}, k8s.Namespace, t)\n\t})\n\n\toptions = newStatOptions()\n\toptions.allNamespaces = true\n\tt.Run(\"Returns all namespace stats\", func(t *testing.T) {\n\t\ttestStatCall(paramsExp{\n\t\t\tcounts: &api.PodCounts{\n\t\t\t\tMeshedPods:  1,\n\t\t\t\tRunningPods: 2,\n\t\t\t\tFailedPods:  0,\n\t\t\t},\n\t\t\toptions: options,\n\t\t\tresNs:   []string{\"emojivoto1\", \"emojivoto2\"},\n\t\t\tfile:    \"stat_all_output.golden\",\n\t\t}, k8s.Namespace, t)\n\t})\n\n\toptions.outputFormat = jsonOutput\n\tt.Run(\"Returns all namespace stats (json)\", func(t *testing.T) {\n\t\ttestStatCall(paramsExp{\n\t\t\tcounts: &api.PodCounts{\n\t\t\t\tMeshedPods:  1,\n\t\t\t\tRunningPods: 2,\n\t\t\t\tFailedPods:  0,\n\t\t\t},\n\t\t\toptions: options,\n\t\t\tresNs:   []string{\"emojivoto1\", \"emojivoto2\"},\n\t\t\tfile:    \"stat_all_output_json.golden\",\n\t\t}, k8s.Namespace, t)\n\t})\n\n\toptions = newStatOptions()\n\toptions.outputFormat = \"wide\"\n\tt.Run(\"Returns TCP stats\", func(t *testing.T) {\n\t\ttestStatCall(paramsExp{\n\t\t\tcounts: &api.PodCounts{\n\t\t\t\tMeshedPods:  1,\n\t\t\t\tRunningPods: 2,\n\t\t\t\tFailedPods:  0,\n\t\t\t},\n\t\t\toptions: options,\n\t\t\tresNs:   []string{\"emojivoto1\"},\n\t\t\tfile:    \"stat_one_tcp_output.golden\",\n\t\t}, k8s.Namespace, t)\n\t})\n\n\tt.Run(\"Returns an error for named resource queries with the --all-namespaces flag\", func(t *testing.T) {\n\t\toptions := newStatOptions()\n\t\toptions.allNamespaces = true\n\t\tif options.namespace == \"\" {\n\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t}\n\t\targs := []string{\"po\", \"web\"}\n\t\texpectedError := \"stats for a resource cannot be retrieved by name across all namespaces\"\n\n\t\t_, err := buildStatSummaryRequests(args, options)\n\t\tif err == nil || err.Error() != expectedError {\n\t\t\tt.Fatalf(\"Expected error [%s] instead got [%s]\", expectedError, err)\n\t\t}\n\t})\n\n\tt.Run(\"Rejects commands with both --to and --from flags\", func(t *testing.T) {\n\t\toptions := newStatOptions()\n\t\tif options.namespace == \"\" {\n\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t}\n\t\toptions.toResource = \"deploy/foo\"\n\t\toptions.fromResource = \"deploy/bar\"\n\t\targs := []string{\"ns\", \"test\"}\n\t\texpectedError := \"--to and --from flags are mutually exclusive\"\n\n\t\t_, err := buildStatSummaryRequests(args, options)\n\t\tif err == nil || err.Error() != expectedError {\n\t\t\tt.Fatalf(\"Expected error [%s] instead got [%s]\", expectedError, err)\n\t\t}\n\t})\n\n\tt.Run(\"Rejects commands with both --to-namespace and --from-namespace flags\", func(t *testing.T) {\n\t\toptions := newStatOptions()\n\t\tif options.namespace == \"\" {\n\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t}\n\t\toptions.toNamespace = \"foo\"\n\t\toptions.fromNamespace = \"bar\"\n\t\targs := []string{\"po\"}\n\t\texpectedError := \"--to-namespace and --from-namespace flags are mutually exclusive\"\n\n\t\t_, err := buildStatSummaryRequests(args, options)\n\t\tif err == nil || err.Error() != expectedError {\n\t\t\tt.Fatalf(\"Expected error [%s] instead got [%s]\", expectedError, err)\n\t\t}\n\t})\n\n\tt.Run(\"Rejects commands with both --all-namespaces and --namespace flags\", func(t *testing.T) {\n\t\toptions := newStatOptions()\n\t\tif options.namespace == \"\" {\n\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t}\n\t\toptions.allNamespaces = true\n\t\toptions.namespace = \"ns\"\n\t\targs := []string{\"po\"}\n\t\texpectedError := \"--all-namespaces and --namespace flags are mutually exclusive\"\n\n\t\t_, err := buildStatSummaryRequests(args, options)\n\t\tif err == nil || err.Error() != expectedError {\n\t\t\tt.Fatalf(\"Expected error [%s] instead got [%s]\", expectedError, err)\n\t\t}\n\t})\n\n\tt.Run(\"Rejects --to-namespace flag when the target is a namespace\", func(t *testing.T) {\n\t\toptions := newStatOptions()\n\t\tif options.namespace == \"\" {\n\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t}\n\t\toptions.toNamespace = \"bar\"\n\t\targs := []string{\"ns\", \"foo\"}\n\t\texpectedError := \"--to-namespace flag is incompatible with namespace resource type\"\n\n\t\t_, err := buildStatSummaryRequests(args, options)\n\t\tif err == nil || err.Error() != expectedError {\n\t\t\tt.Fatalf(\"Expected error [%s] instead got [%s]\", expectedError, err)\n\t\t}\n\t})\n\n\tt.Run(\"Rejects --from-namespace flag when the target is a namespace\", func(t *testing.T) {\n\t\toptions := newStatOptions()\n\t\tif options.namespace == \"\" {\n\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t}\n\t\toptions.fromNamespace = \"foo\"\n\t\targs := []string{\"ns/bar\"}\n\t\texpectedError := \"--from-namespace flag is incompatible with namespace resource type\"\n\n\t\t_, err := buildStatSummaryRequests(args, options)\n\t\tif err == nil || err.Error() != expectedError {\n\t\t\tt.Fatalf(\"Expected error [%s] instead got [%s]\", expectedError, err)\n\t\t}\n\t})\n\n\tt.Run(\"Returns an error if --time-window is not more than 15s\", func(t *testing.T) {\n\t\toptions := newStatOptions()\n\t\tif options.namespace == \"\" {\n\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t}\n\t\toptions.timeWindow = \"10s\"\n\t\targs := []string{\"ns/bar\"}\n\t\texpectedError := \"metrics time window needs to be at least 15s\"\n\n\t\t_, err := buildStatSummaryRequests(args, options)\n\t\tif err == nil || err.Error() != expectedError {\n\t\t\tt.Fatalf(\"Expected error [%s] instead got [%s]\", expectedError, err)\n\t\t}\n\t})\n}\n\nfunc testStatCall(exp paramsExp, resourceType string, t *testing.T) {\n\tmockClient := &api.MockAPIClient{}\n\tresponse := api.GenStatSummaryResponse(\"emoji\", resourceType, exp.resNs, exp.counts, true, true)\n\n\tmockClient.StatSummaryResponseToReturn = response\n\n\tif exp.options.namespace == \"\" {\n\t\texp.options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t}\n\treqs, err := buildStatSummaryRequests([]string{\"ns\"}, exp.options)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tresp, err := requestStatsFromAPI(mockClient, reqs[0])\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\trows := respToRows(resp)\n\toutput := renderStatStats(rows, exp.options)\n\n\ttestDataDiffer.DiffTestdata(t, exp.file, output)\n}\n"
  },
  {
    "path": "viz/cmd/tap.go",
    "content": "package cmd\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/golang/protobuf/ptypes/duration\"\n\tnetPb \"github.com/linkerd/linkerd2/controller/gen/common/net\"\n\t\"github.com/linkerd/linkerd2/pkg/addr\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/protohttp\"\n\tmetricsPb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/api\"\n\thc \"github.com/linkerd/linkerd2/viz/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/jsonpath\"\n\tvizutil \"github.com/linkerd/linkerd2/viz/pkg/util\"\n\ttapPb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n\t\"github.com/linkerd/linkerd2/viz/tap/pkg\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\t\"google.golang.org/grpc/codes\"\n)\n\ntype renderTapEventFunc func(*tapPb.TapEvent, ...renderOptions) string\n\ntype tapOptions struct {\n\tnamespace     string\n\ttoResource    string\n\ttoNamespace   string\n\tmaxRps        float32\n\tscheme        string\n\tmethod        string\n\tauthority     string\n\tpath          string\n\toutput        string\n\tlabelSelector string\n}\n\ntype endpoint struct {\n\tIP       string            `json:\"ip\"`\n\tPort     uint32            `json:\"port\"`\n\tMetadata map[string]string `json:\"metadata\"`\n}\n\ntype streamID struct {\n\tBase   uint32 `json:\"base\"`\n\tStream uint64 `json:\"stream\"`\n}\n\ntype metadata interface {\n\tisMetadata()\n}\n\ntype metadataStr struct {\n\tName     string `json:\"name\"`\n\tValueStr string `json:\"valueStr\"`\n}\n\nfunc (*metadataStr) isMetadata() {}\n\ntype metadataBin struct {\n\tName     string `json:\"name\"`\n\tValueBin []byte `json:\"valueBin\"`\n}\n\nfunc (*metadataBin) isMetadata() {}\n\ntype requestInitEvent struct {\n\tID        *streamID  `json:\"id\"`\n\tMethod    string     `json:\"method\"`\n\tScheme    string     `json:\"scheme\"`\n\tAuthority string     `json:\"authority\"`\n\tPath      string     `json:\"path\"`\n\tHeaders   []metadata `json:\"headers\"`\n}\n\ntype responseInitEvent struct {\n\tID               *streamID          `json:\"id\"`\n\tSinceRequestInit *duration.Duration `json:\"sinceRequestInit\"`\n\tHTTPStatus       uint32             `json:\"httpStatus\"`\n\tHeaders          []metadata         `json:\"headers\"`\n}\n\ntype responseEndEvent struct {\n\tID                *streamID          `json:\"id\"`\n\tSinceRequestInit  *duration.Duration `json:\"sinceRequestInit\"`\n\tSinceResponseInit *duration.Duration `json:\"sinceResponseInit\"`\n\tResponseBytes     uint64             `json:\"responseBytes\"`\n\tTrailers          []metadata         `json:\"trailers\"`\n\tGrpcStatusCode    uint32             `json:\"grpcStatusCode\"`\n\tResetErrorCode    uint32             `json:\"resetErrorCode,omitempty\"`\n}\n\n// Private type used for displaying JSON encoded tap events\ntype tapEvent struct {\n\tSource            *endpoint          `json:\"source\"`\n\tDestination       *endpoint          `json:\"destination\"`\n\tRouteMeta         map[string]string  `json:\"routeMeta\"`\n\tProxyDirection    string             `json:\"proxyDirection\"`\n\tRequestInitEvent  *requestInitEvent  `json:\"requestInitEvent,omitempty\"`\n\tResponseInitEvent *responseInitEvent `json:\"responseInitEvent,omitempty\"`\n\tResponseEndEvent  *responseEndEvent  `json:\"responseEndEvent,omitempty\"`\n}\n\nfunc newTapOptions() *tapOptions {\n\treturn &tapOptions{\n\t\ttoResource:    \"\",\n\t\ttoNamespace:   \"\",\n\t\tmaxRps:        maxRps,\n\t\tscheme:        \"\",\n\t\tmethod:        \"\",\n\t\tauthority:     \"\",\n\t\tpath:          \"\",\n\t\toutput:        \"\",\n\t\tlabelSelector: \"\",\n\t}\n}\n\ntype renderFilter struct {\n\tJsonPath string\n}\n\ntype renderOptions func(f *renderFilter)\n\nfunc WithJsonPath(jsonPath string) renderOptions {\n\treturn func(r *renderFilter) {\n\t\tr.JsonPath = jsonPath\n\t}\n}\n\nfunc (o *tapOptions) validate() error {\n\tif o.output == \"\" || o.output == wideOutput || o.output == jsonOutput || strings.HasPrefix(o.output, jsonPathOutput) {\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"output format \\\"%s\\\" not recognized\", o.output)\n}\n\n// NewCmdTap creates a new cobra command `tap` for tap functionality\nfunc NewCmdTap() *cobra.Command {\n\toptions := newTapOptions()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"tap [flags] (RESOURCE)\",\n\t\tShort: \"Listen to a traffic stream\",\n\t\tLong: `Listen to a traffic stream.\n\n  The RESOURCE argument specifies the target resource(s) to tap:\n  (TYPE [NAME] | TYPE/NAME)\n\n  Examples:\n  * cronjob/my-cronjob\n  * deploy\n  * deploy/my-deploy\n  * deploy my-deploy\n  * ds/my-daemonset\n  * job/my-job\n  * ns/my-ns\n  * rs\n  * rs/my-replicaset\n  * sts\n  * sts/my-statefulset\n\n  Valid resource types include:\n  * cronjobs\n  * daemonsets\n  * deployments\n  * jobs\n  * namespaces\n  * pods\n  * replicasets\n  * replicationcontrollers\n  * statefulsets\n  * services (only supported as a --to resource)`,\n\t\tExample: `  # tap the web deployment in the default namespace\n  linkerd viz tap deploy/web\n\n  # tap the web-dlbvj pod in the default namespace\n  linkerd viz tap pod/web-dlbvj\n\n  # tap the test namespace, filter by request to prod namespace\n  linkerd viz tap ns/test --to ns/prod`,\n\t\tArgs: cobra.RangeArgs(1, 2),\n\t\tValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\t\t// This command requires at most two arguments if we already have\n\t\t\t// two after requesting autocompletion i.e. [tab][tab]\n\t\t\t// skip running validArgsFunction\n\t\t\tif len(args) > 1 {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\tcc := k8s.NewCommandCompletion(k8sAPI, options.namespace)\n\n\t\t\tresults, err := cc.Complete(args, toComplete)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\treturn results, cobra.ShellCompDirectiveDefault\n\t\t},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\tapi.CheckClientOrExit(hc.VizOptions{\n\t\t\t\tOptions: &healthcheck.Options{\n\t\t\t\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\t\t\t\tKubeConfig:            kubeconfigPath,\n\t\t\t\t\tImpersonate:           impersonate,\n\t\t\t\t\tImpersonateGroup:      impersonateGroup,\n\t\t\t\t\tKubeContext:           kubeContext,\n\t\t\t\t\tAPIAddr:               apiAddr,\n\t\t\t\t},\n\t\t\t\tVizNamespaceOverride: vizNamespace,\n\t\t\t})\n\n\t\t\trequestParams := pkg.TapRequestParams{\n\t\t\t\tResource:      strings.Join(args, \"/\"),\n\t\t\t\tNamespace:     options.namespace,\n\t\t\t\tToResource:    options.toResource,\n\t\t\t\tToNamespace:   options.toNamespace,\n\t\t\t\tMaxRps:        options.maxRps,\n\t\t\t\tScheme:        options.scheme,\n\t\t\t\tMethod:        options.method,\n\t\t\t\tAuthority:     options.authority,\n\t\t\t\tPath:          options.path,\n\t\t\t\tExtract:       options.output == jsonOutput,\n\t\t\t\tLabelSelector: options.labelSelector,\n\t\t\t}\n\n\t\t\terr := options.validate()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"validation error when executing tap command: %w\", err)\n\t\t\t}\n\n\t\t\treq, err := pkg.BuildTapByResourceRequest(requestParams)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\terr = requestTapByResourceFromAPI(cmd.Context(), os.Stdout, k8sAPI, req, options)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace,\n\t\t\"Namespace of the specified resource\")\n\tcmd.PersistentFlags().StringVar(&options.toResource, \"to\", options.toResource,\n\t\t\"Display requests to this resource\")\n\tcmd.PersistentFlags().StringVar(&options.toNamespace, \"to-namespace\", options.toNamespace,\n\t\t\"Sets the namespace used to lookup the \\\"--to\\\" resource; by default the current \\\"--namespace\\\" is used\")\n\tcmd.PersistentFlags().Float32Var(&options.maxRps, \"max-rps\", options.maxRps,\n\t\t\"Maximum requests per second to tap.\")\n\tcmd.PersistentFlags().StringVar(&options.scheme, \"scheme\", options.scheme,\n\t\t\"Display requests with this scheme\")\n\tcmd.PersistentFlags().StringVar(&options.method, \"method\", options.method,\n\t\t\"Display requests with this HTTP method\")\n\tcmd.PersistentFlags().StringVar(&options.authority, \"authority\", options.authority,\n\t\t\"Display requests with this :authority\")\n\tcmd.PersistentFlags().StringVar(&options.path, \"path\", options.path,\n\t\t\"Display requests with paths that start with this prefix\")\n\tcmd.PersistentFlags().StringVarP(&options.output, \"output\", \"o\", options.output,\n\t\tfmt.Sprintf(\"Output format. One of: \\\"%s\\\", \\\"%s\\\", \\\"%s\\\"\", wideOutput, jsonOutput, jsonPathOutput))\n\tcmd.PersistentFlags().StringVarP(&options.labelSelector, \"selector\", \"l\", options.labelSelector,\n\t\t\"Selector (label query) to filter on, supports '=', '==', and '!='\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tcmd, []string{\"namespace\", \"to-namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\treturn cmd\n}\n\nfunc requestTapByResourceFromAPI(ctx context.Context, w io.Writer, k8sAPI *k8s.KubernetesAPI, req *tapPb.TapByResourceRequest, options *tapOptions) error {\n\treader, body, err := pkg.Reader(ctx, k8sAPI, req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer body.Close()\n\n\treturn writeTapEventsToBuffer(w, reader, options)\n}\n\nfunc writeTapEventsToBuffer(w io.Writer, tapByteStream *bufio.Reader, options *tapOptions) error {\n\toutput := options.output\n\n\tswitch {\n\tcase output == \"\":\n\t\treturn renderTapEvents(tapByteStream, w, renderTapEvent)\n\tcase output == wideOutput:\n\t\treturn renderTapEvents(tapByteStream, w, renderTapEventWide)\n\tcase output == jsonOutput:\n\t\treturn renderTapEvents(tapByteStream, w, renderTapEventJSON)\n\tcase strings.HasPrefix(output, jsonPathOutput):\n\t\tjPathFilter, err := jsonpath.GetJsonPathFlagVal(output)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn renderTapEvents(tapByteStream, w, renderTapEventJSON, WithJsonPath(jPathFilter))\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown output format: %q\", options.output)\n\t}\n}\n\nfunc renderTapEvents(tapByteStream *bufio.Reader, w io.Writer, render renderTapEventFunc, opts ...renderOptions) error {\n\tfor {\n\t\tlog.Debug(\"Waiting for data...\")\n\t\tevent := tapPb.TapEvent{}\n\t\terr := protohttp.FromByteStreamToProtocolBuffers(tapByteStream, &event)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tfmt.Fprintln(os.Stderr, err)\n\t\t\tbreak\n\t\t}\n\t\t_, err = fmt.Fprintln(w, render(&event, opts...))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc renderTapEventWide(event *tapPb.TapEvent, _ ...renderOptions) string {\n\tdst := dst(event)\n\tsrc := src(event)\n\n\tout := []string{renderTapEvent(event)}\n\tout = append(out, src.formatResource()...)\n\tout = append(out, dst.formatResource()...)\n\tout = append(out, routeLabels(event)...)\n\treturn strings.Join(out, \" \")\n}\n\n// renderTapEvent renders a Public API TapEvent to a string.\nfunc renderTapEvent(event *tapPb.TapEvent, _ ...renderOptions) string {\n\tdst := dst(event)\n\tsrc := src(event)\n\n\tproxy := \"???\"\n\ttls := \"\"\n\tswitch event.GetProxyDirection() {\n\tcase tapPb.TapEvent_INBOUND:\n\t\tproxy = \"in \" // A space is added so it aligns with `out`.\n\t\ttls = src.tlsStatus()\n\tcase tapPb.TapEvent_OUTBOUND:\n\t\tproxy = \"out\"\n\t\ttls = dst.tlsStatus()\n\tdefault:\n\t\t// Too old for TLS.\n\t}\n\n\tflow := fmt.Sprintf(\"proxy=%s %s %s tls=%s\",\n\t\tproxy,\n\t\tsrc.formatAddr(),\n\t\tdst.formatAddr(),\n\t\ttls,\n\t)\n\n\tswitch ev := event.GetHttp().GetEvent().(type) {\n\tcase *tapPb.TapEvent_Http_RequestInit_:\n\t\treturn fmt.Sprintf(\"req id=%d:%d %s :method=%s :authority=%s :path=%s\",\n\t\t\tev.RequestInit.GetId().GetBase(),\n\t\t\tev.RequestInit.GetId().GetStream(),\n\t\t\tflow,\n\t\t\tvizutil.HTTPMethodToString(ev.RequestInit.GetMethod()),\n\t\t\tev.RequestInit.GetAuthority(),\n\t\t\tev.RequestInit.GetPath(),\n\t\t)\n\n\tcase *tapPb.TapEvent_Http_ResponseInit_:\n\t\treturn fmt.Sprintf(\"rsp id=%d:%d %s :status=%d latency=%dµs\",\n\t\t\tev.ResponseInit.GetId().GetBase(),\n\t\t\tev.ResponseInit.GetId().GetStream(),\n\t\t\tflow,\n\t\t\tev.ResponseInit.GetHttpStatus(),\n\t\t\tev.ResponseInit.GetSinceRequestInit().AsDuration().Microseconds(),\n\t\t)\n\n\tcase *tapPb.TapEvent_Http_ResponseEnd_:\n\t\tswitch eos := ev.ResponseEnd.GetEos().GetEnd().(type) {\n\t\tcase *metricsPb.Eos_GrpcStatusCode:\n\t\t\treturn fmt.Sprintf(\n\t\t\t\t\"end id=%d:%d %s grpc-status=%s duration=%dµs response-length=%dB\",\n\t\t\t\tev.ResponseEnd.GetId().GetBase(),\n\t\t\t\tev.ResponseEnd.GetId().GetStream(),\n\t\t\t\tflow,\n\t\t\t\tcodes.Code(eos.GrpcStatusCode),\n\t\t\t\tev.ResponseEnd.GetSinceResponseInit().AsDuration().Microseconds(),\n\t\t\t\tev.ResponseEnd.GetResponseBytes(),\n\t\t\t)\n\n\t\tcase *metricsPb.Eos_ResetErrorCode:\n\t\t\treturn fmt.Sprintf(\n\t\t\t\t\"end id=%d:%d %s reset-error=%+v duration=%dµs response-length=%dB\",\n\t\t\t\tev.ResponseEnd.GetId().GetBase(),\n\t\t\t\tev.ResponseEnd.GetId().GetStream(),\n\t\t\t\tflow,\n\t\t\t\teos.ResetErrorCode,\n\t\t\t\tev.ResponseEnd.GetSinceResponseInit().AsDuration().Microseconds(),\n\t\t\t\tev.ResponseEnd.GetResponseBytes(),\n\t\t\t)\n\n\t\tdefault:\n\t\t\treturn fmt.Sprintf(\"end id=%d:%d %s duration=%dµs response-length=%dB\",\n\t\t\t\tev.ResponseEnd.GetId().GetBase(),\n\t\t\t\tev.ResponseEnd.GetId().GetStream(),\n\t\t\t\tflow,\n\t\t\t\tev.ResponseEnd.GetSinceResponseInit().AsDuration().Microseconds(),\n\t\t\t\tev.ResponseEnd.GetResponseBytes(),\n\t\t\t)\n\t\t}\n\n\tdefault:\n\t\treturn fmt.Sprintf(\"unknown %s\", flow)\n\t}\n}\n\n// renderTapEventJSON renders a Public API TapEvent to a string in JSON format.\nfunc renderTapEventJSON(event *tapPb.TapEvent, opts ...renderOptions) string {\n\tfilter := &renderFilter{}\n\tfor _, opt := range opts {\n\t\topt(filter)\n\t}\n\tm := mapPublicToDisplayTapEvent(event)\n\tif filter.JsonPath != \"\" {\n\t\tfilteredJson, err := jsonpath.GetJsonFilteredByJPath(m, filter.JsonPath)\n\t\tif err != nil {\n\t\t\treturn err.Error()\n\t\t}\n\t\treturn filteredJson[0]\n\t}\n\te, err := json.MarshalIndent(m, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Sprintf(\"{\\\"error marshalling JSON\\\": \\\"%s\\\"}\", err)\n\t}\n\treturn string(e)\n}\n\n// Map public API `TapEvent`s to `displayTapEvent`s\nfunc mapPublicToDisplayTapEvent(event *tapPb.TapEvent) *tapEvent {\n\t// Map source endpoint\n\tsip := addr.PublicIPToString(event.GetSource().GetIp())\n\tsrc := &endpoint{\n\t\tIP:       sip,\n\t\tPort:     event.GetSource().GetPort(),\n\t\tMetadata: event.GetSourceMeta().GetLabels(),\n\t}\n\n\t// Map destination endpoint\n\tdip := addr.PublicIPToString(event.GetDestination().GetIp())\n\tdst := &endpoint{\n\t\tIP:       dip,\n\t\tPort:     event.GetDestination().GetPort(),\n\t\tMetadata: event.GetDestinationMeta().GetLabels(),\n\t}\n\n\treturn &tapEvent{\n\t\tSource:            src,\n\t\tDestination:       dst,\n\t\tRouteMeta:         event.GetRouteMeta().GetLabels(),\n\t\tProxyDirection:    event.GetProxyDirection().String(),\n\t\tRequestInitEvent:  getRequestInitEvent(event.GetHttp()),\n\t\tResponseInitEvent: getResponseInitEvent(event.GetHttp()),\n\t\tResponseEndEvent:  getResponseEndEvent(event.GetHttp()),\n\t}\n}\n\n// Attempt to map a `TapEvent_Http_RequestInit event to a `requestInitEvent`\nfunc getRequestInitEvent(pubEv *tapPb.TapEvent_Http) *requestInitEvent {\n\treqI := pubEv.GetRequestInit()\n\tif reqI == nil {\n\t\treturn nil\n\t}\n\tsid := &streamID{\n\t\tBase:   reqI.GetId().GetBase(),\n\t\tStream: reqI.GetId().GetStream(),\n\t}\n\treturn &requestInitEvent{\n\t\tID:        sid,\n\t\tMethod:    formatMethod(reqI.GetMethod()),\n\t\tScheme:    formatScheme(reqI.GetScheme()),\n\t\tAuthority: reqI.GetAuthority(),\n\t\tPath:      reqI.GetPath(),\n\t\tHeaders:   formatHeadersTrailers(reqI.GetHeaders()),\n\t}\n}\n\nfunc formatMethod(m *metricsPb.HttpMethod) string {\n\tif x, ok := m.GetType().(*metricsPb.HttpMethod_Registered_); ok {\n\t\treturn x.Registered.String()\n\t}\n\tif s, ok := m.GetType().(*metricsPb.HttpMethod_Unregistered); ok {\n\t\treturn s.Unregistered\n\t}\n\treturn \"\"\n}\n\nfunc formatScheme(s *metricsPb.Scheme) string {\n\tif x, ok := s.GetType().(*metricsPb.Scheme_Registered_); ok {\n\t\treturn x.Registered.String()\n\t}\n\tif str, ok := s.GetType().(*metricsPb.Scheme_Unregistered); ok {\n\t\treturn str.Unregistered\n\t}\n\treturn \"\"\n}\n\n// Attempt to map a `TapEvent_Http_ResponseInit` event to a `responseInitEvent`\nfunc getResponseInitEvent(pubEv *tapPb.TapEvent_Http) *responseInitEvent {\n\tresI := pubEv.GetResponseInit()\n\tif resI == nil {\n\t\treturn nil\n\t}\n\tsid := &streamID{\n\t\tBase:   resI.GetId().GetBase(),\n\t\tStream: resI.GetId().GetStream(),\n\t}\n\treturn &responseInitEvent{\n\t\tID:               sid,\n\t\tSinceRequestInit: resI.GetSinceRequestInit(),\n\t\tHTTPStatus:       resI.GetHttpStatus(),\n\t\tHeaders:          formatHeadersTrailers(resI.GetHeaders()),\n\t}\n}\n\n// Attempt to map a `TapEvent_Http_ResponseEnd` event to a `responseEndEvent`\nfunc getResponseEndEvent(pubEv *tapPb.TapEvent_Http) *responseEndEvent {\n\tresE := pubEv.GetResponseEnd()\n\tif resE == nil {\n\t\treturn nil\n\t}\n\tsid := &streamID{\n\t\tBase:   resE.GetId().GetBase(),\n\t\tStream: resE.GetId().GetStream(),\n\t}\n\treturn &responseEndEvent{\n\t\tID:                sid,\n\t\tSinceRequestInit:  resE.GetSinceRequestInit(),\n\t\tSinceResponseInit: resE.GetSinceResponseInit(),\n\t\tResponseBytes:     resE.GetResponseBytes(),\n\t\tTrailers:          formatHeadersTrailers(resE.GetTrailers()),\n\t\tGrpcStatusCode:    resE.GetEos().GetGrpcStatusCode(),\n\t\tResetErrorCode:    resE.GetEos().GetResetErrorCode(),\n\t}\n}\n\nfunc formatHeadersTrailers(hs *metricsPb.Headers) []metadata {\n\tvar fm []metadata\n\tfor _, h := range hs.GetHeaders() {\n\t\tswitch h.GetValue().(type) {\n\t\tcase *metricsPb.Headers_Header_ValueStr:\n\t\t\tfht := &metadataStr{Name: h.GetName(), ValueStr: h.GetValueStr()}\n\t\t\tfm = append(fm, fht)\n\t\t\tcontinue\n\t\tcase *metricsPb.Headers_Header_ValueBin:\n\t\t\tfht := &metadataBin{Name: h.GetName(), ValueBin: h.GetValueBin()}\n\t\t\tfm = append(fm, fht)\n\t\t\tcontinue\n\t\t}\n\t}\n\treturn fm\n}\n\n// src returns the source peer of a `TapEvent`.\nfunc src(event *tapPb.TapEvent) peer {\n\treturn peer{\n\t\taddress:   event.GetSource(),\n\t\tlabels:    event.GetSourceMeta().GetLabels(),\n\t\tdirection: \"src\",\n\t}\n}\n\n// dst returns the destination peer of a `TapEvent`.\nfunc dst(event *tapPb.TapEvent) peer {\n\treturn peer{\n\t\taddress:   event.GetDestination(),\n\t\tlabels:    event.GetDestinationMeta().GetLabels(),\n\t\tdirection: \"dst\",\n\t}\n}\n\ntype peer struct {\n\taddress   *netPb.TcpAddress\n\tlabels    map[string]string\n\tdirection string\n}\n\n// formatAddr formats the peer's TCP address for the `src` or `dst` element in\n// the tap output corresponding to this peer.\nfunc (p *peer) formatAddr() string {\n\treturn fmt.Sprintf(\n\t\t\"%s=%s\",\n\t\tp.direction,\n\t\taddr.PublicAddressToString(p.address),\n\t)\n}\n\n// formatResource returns the peer's labels formatted and sorted.\nfunc (p *peer) formatResource() []string {\n\tlabels := []string{}\n\tfor k, v := range p.labels {\n\t\tlabels = append(labels, fmt.Sprintf(\"%s_%s=%s\", p.direction, k, v))\n\t}\n\tsort.Strings(labels)\n\treturn labels\n}\n\nfunc (p *peer) tlsStatus() string {\n\treturn p.labels[\"tls\"]\n}\n\nfunc routeLabels(event *tapPb.TapEvent) []string {\n\tout := []string{}\n\tfor key, val := range event.GetRouteMeta().GetLabels() {\n\t\tout = append(out, fmt.Sprintf(\"rt_%s=%s\", key, val))\n\t}\n\treturn out\n}\n"
  },
  {
    "path": "viz/cmd/tap_test.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/golang/protobuf/ptypes/duration\"\n\tnetPb \"github.com/linkerd/linkerd2/controller/gen/common/net\"\n\t\"github.com/linkerd/linkerd2/pkg/addr\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/protohttp\"\n\tmetricsPb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\ttapPb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n\t\"github.com/linkerd/linkerd2/viz/tap/pkg\"\n\t\"google.golang.org/grpc/codes\"\n)\n\nconst targetName = \"pod-666\"\n\nfunc busyTest(t *testing.T, output string) {\n\tresourceType := k8s.Pod\n\tparams := pkg.TapRequestParams{\n\t\tResource:  resourceType + \"/\" + targetName,\n\t\tScheme:    \"https\",\n\t\tMethod:    \"GET\",\n\t\tAuthority: \"localhost\",\n\t\tPath:      \"/some/path\",\n\t}\n\n\treq, err := pkg.BuildTapByResourceRequest(params)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tevent1 := pkg.CreateTapEvent(\n\t\t&tapPb.TapEvent_Http{\n\t\t\tEvent: &tapPb.TapEvent_Http_RequestInit_{\n\t\t\t\tRequestInit: &tapPb.TapEvent_Http_RequestInit{\n\t\t\t\t\tId: &tapPb.TapEvent_Http_StreamId{\n\t\t\t\t\t\tBase: 1,\n\t\t\t\t\t},\n\t\t\t\t\tMethod: &metricsPb.HttpMethod{\n\t\t\t\t\t\tType: &metricsPb.HttpMethod_Registered_{\n\t\t\t\t\t\t\tRegistered: metricsPb.HttpMethod_GET,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tScheme: &metricsPb.Scheme{\n\t\t\t\t\t\tType: &metricsPb.Scheme_Registered_{\n\t\t\t\t\t\t\tRegistered: metricsPb.Scheme_HTTPS,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tAuthority: params.Authority,\n\t\t\t\t\tPath:      params.Path,\n\t\t\t\t\tHeaders: &metricsPb.Headers{\n\t\t\t\t\t\tHeaders: []*metricsPb.Headers_Header{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"header-name-1\",\n\t\t\t\t\t\t\t\tValue: &metricsPb.Headers_Header_ValueStr{\n\t\t\t\t\t\t\t\t\tValueStr: \"header-value-str-1\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"header-name-2\",\n\t\t\t\t\t\t\t\tValue: &metricsPb.Headers_Header_ValueBin{\n\t\t\t\t\t\t\t\t\tValueBin: []byte(\"header-value-bin-2\"),\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\tmap[string]string{\n\t\t\t\"pod\": \"my-pod\",\n\t\t\t\"tls\": \"true\",\n\t\t},\n\t\ttapPb.TapEvent_OUTBOUND,\n\t)\n\tevent2 := pkg.CreateTapEvent(\n\t\t&tapPb.TapEvent_Http{\n\t\t\tEvent: &tapPb.TapEvent_Http_ResponseEnd_{\n\t\t\t\tResponseEnd: &tapPb.TapEvent_Http_ResponseEnd{\n\t\t\t\t\tId: &tapPb.TapEvent_Http_StreamId{\n\t\t\t\t\t\tBase: 1,\n\t\t\t\t\t},\n\t\t\t\t\tEos: &metricsPb.Eos{\n\t\t\t\t\t\tEnd: &metricsPb.Eos_GrpcStatusCode{GrpcStatusCode: 666},\n\t\t\t\t\t},\n\t\t\t\t\tSinceRequestInit: &duration.Duration{\n\t\t\t\t\t\tSeconds: 10,\n\t\t\t\t\t},\n\t\t\t\t\tSinceResponseInit: &duration.Duration{\n\t\t\t\t\t\tSeconds: 100,\n\t\t\t\t\t},\n\t\t\t\t\tResponseBytes: 1337,\n\t\t\t\t\tTrailers: &metricsPb.Headers{\n\t\t\t\t\t\tHeaders: []*metricsPb.Headers_Header{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"trailer-name\",\n\t\t\t\t\t\t\t\tValue: &metricsPb.Headers_Header_ValueBin{\n\t\t\t\t\t\t\t\t\tValueBin: []byte(\"header-value-bin\"),\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\tmap[string]string{},\n\t\ttapPb.TapEvent_OUTBOUND,\n\t)\n\tkubeAPI, err := k8s.NewFakeAPI()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tts := httptest.NewServer(http.HandlerFunc(\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tfor _, event := range []*tapPb.TapEvent{event1, event2} {\n\t\t\t\terr = protohttp.WriteProtoToHTTPResponse(w, event)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t)\n\tdefer ts.Close()\n\tkubeAPI.Config.Host = ts.URL\n\n\toptions := newTapOptions()\n\toptions.output = output\n\n\twriter := bytes.NewBufferString(\"\")\n\terr = requestTapByResourceFromAPI(context.Background(), writer, kubeAPI, req, options)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tvar goldenFilePath string\n\tswitch {\n\tcase options.output == wideOutput:\n\t\tgoldenFilePath = \"testdata/tap_busy_output_wide.golden\"\n\tcase options.output == jsonOutput:\n\t\tgoldenFilePath = \"testdata/tap_busy_output_json.golden\"\n\tcase strings.HasPrefix(options.output, jsonPathOutput):\n\t\tgoldenFilePath = \"testdata/tap_busy_output_jsonpath.golden\"\n\tdefault:\n\t\tgoldenFilePath = \"testdata/tap_busy_output.golden\"\n\t}\n\n\tgoldenFileBytes, err := os.ReadFile(goldenFilePath)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\texpectedContent := string(goldenFileBytes)\n\tactual := writer.String()\n\tif expectedContent != actual {\n\t\tt.Fatalf(\"Expected function to render:\\n%s\\bbut got:\\n%s\", expectedContent, actual)\n\t}\n}\n\nfunc TestRequestTapByResourceFromAPI(t *testing.T) {\n\n\tctx := context.Background()\n\tt.Run(\"Should render busy response if everything went well\", func(t *testing.T) {\n\t\tbusyTest(t, \"\")\n\t})\n\n\tt.Run(\"Should render wide busy response if everything went well\", func(t *testing.T) {\n\t\tbusyTest(t, \"wide\")\n\t})\n\n\tt.Run(\"Should render JSON busy response if everything went well\", func(t *testing.T) {\n\t\tbusyTest(t, \"json\")\n\t})\n\n\tt.Run(\"Should render jsonpath busy response if everything went well\", func(t *testing.T) {\n\t\tbusyTest(t, \"jsonpath={.source}\")\n\t})\n\n\tt.Run(\"Should render empty response if no events returned\", func(t *testing.T) {\n\t\tresourceType := k8s.Pod\n\t\tparams := pkg.TapRequestParams{\n\t\t\tResource:  resourceType + \"/\" + targetName,\n\t\t\tScheme:    \"https\",\n\t\t\tMethod:    \"GET\",\n\t\t\tAuthority: \"localhost\",\n\t\t\tPath:      \"/some/path\",\n\t\t}\n\n\t\treq, err := pkg.BuildTapByResourceRequest(params)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\tkubeAPI, err := k8s.NewFakeAPI()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\t\tts := httptest.NewServer(http.HandlerFunc(\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {}),\n\t\t)\n\t\tdefer ts.Close()\n\t\tkubeAPI.Config.Host = ts.URL\n\n\t\toptions := newTapOptions()\n\t\twriter := bytes.NewBufferString(\"\")\n\t\terr = requestTapByResourceFromAPI(ctx, writer, kubeAPI, req, options)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\tgoldenFileBytes, err := os.ReadFile(\"testdata/tap_empty_output.golden\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\t\texpectedContent := string(goldenFileBytes)\n\t\toutput := writer.String()\n\t\tif expectedContent != output {\n\t\t\tt.Fatalf(\"Expected function to render:\\n%s\\bbut got:\\n%s\", expectedContent, output)\n\t\t}\n\t})\n\n\tt.Run(\"Should return error if stream returned error\", func(t *testing.T) {\n\t\tt.SkipNow()\n\t\tresourceType := k8s.Pod\n\t\tparams := pkg.TapRequestParams{\n\t\t\tResource:  resourceType + \"/\" + targetName,\n\t\t\tScheme:    \"https\",\n\t\t\tMethod:    \"GET\",\n\t\t\tAuthority: \"localhost\",\n\t\t\tPath:      \"/some/path\",\n\t\t}\n\n\t\treq, err := pkg.BuildTapByResourceRequest(params)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\tkubeAPI, err := k8s.NewFakeAPI()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t}\n\n\t\toptions := newTapOptions()\n\t\twriter := bytes.NewBufferString(\"\")\n\t\terr = requestTapByResourceFromAPI(ctx, writer, kubeAPI, req, options)\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"Expecting error, got nothing but output [%s]\", writer.String())\n\t\t}\n\t})\n}\n\nfunc TestEventToString(t *testing.T) {\n\ttoTapEvent := func(httpEvent *tapPb.TapEvent_Http) *tapPb.TapEvent {\n\t\tstreamID := &tapPb.TapEvent_Http_StreamId{\n\t\t\tBase:   7,\n\t\t\tStream: 8,\n\t\t}\n\n\t\tswitch httpEvent.Event.(type) {\n\t\tcase *tapPb.TapEvent_Http_RequestInit_:\n\t\t\thttpEvent.GetRequestInit().Id = streamID\n\t\tcase *tapPb.TapEvent_Http_ResponseInit_:\n\t\t\thttpEvent.GetResponseInit().Id = streamID\n\t\tcase *tapPb.TapEvent_Http_ResponseEnd_:\n\t\t\thttpEvent.GetResponseEnd().Id = streamID\n\t\t}\n\n\t\tsrcIP, _ := addr.ParsePublicIP(\"1.2.3.4\")\n\t\tdestIP, _ := addr.ParsePublicIP(\"2.3.4.5\")\n\t\treturn &tapPb.TapEvent{\n\t\t\tProxyDirection: tapPb.TapEvent_OUTBOUND,\n\t\t\tSource: &netPb.TcpAddress{\n\t\t\t\tIp:   srcIP,\n\t\t\t\tPort: 5555,\n\t\t\t},\n\t\t\tDestination: &netPb.TcpAddress{\n\t\t\t\tIp:   destIP,\n\t\t\t\tPort: 6666,\n\t\t\t},\n\t\t\tEvent: &tapPb.TapEvent_Http_{Http: httpEvent},\n\t\t}\n\t}\n\n\tt.Run(\"Converts HTTP request init event to string\", func(t *testing.T) {\n\t\tevent := toTapEvent(&tapPb.TapEvent_Http{\n\t\t\tEvent: &tapPb.TapEvent_Http_RequestInit_{\n\t\t\t\tRequestInit: &tapPb.TapEvent_Http_RequestInit{\n\t\t\t\t\tMethod: &metricsPb.HttpMethod{\n\t\t\t\t\t\tType: &metricsPb.HttpMethod_Registered_{\n\t\t\t\t\t\t\tRegistered: metricsPb.HttpMethod_POST,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tScheme: &metricsPb.Scheme{\n\t\t\t\t\t\tType: &metricsPb.Scheme_Registered_{\n\t\t\t\t\t\t\tRegistered: metricsPb.Scheme_HTTPS,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tAuthority: \"hello.default:7777\",\n\t\t\t\t\tPath:      \"/hello.v1.HelloService/Hello\",\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\n\t\texpectedOutput := \"req id=7:8 proxy=out src=1.2.3.4:5555 dst=2.3.4.5:6666 tls= :method=POST :authority=hello.default:7777 :path=/hello.v1.HelloService/Hello\"\n\t\toutput := renderTapEvent(event)\n\t\tif output != expectedOutput {\n\t\t\tt.Fatalf(\"Expecting command output to be [%s], got [%s]\", expectedOutput, output)\n\t\t}\n\t})\n\n\tt.Run(\"Converts HTTP response init event to string\", func(t *testing.T) {\n\t\tevent := toTapEvent(&tapPb.TapEvent_Http{\n\t\t\tEvent: &tapPb.TapEvent_Http_ResponseInit_{\n\t\t\t\tResponseInit: &tapPb.TapEvent_Http_ResponseInit{\n\t\t\t\t\tSinceRequestInit: &duration.Duration{Seconds: 9, Nanos: 999000},\n\t\t\t\t\tHttpStatus:       http.StatusOK,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\n\t\texpectedOutput := \"rsp id=7:8 proxy=out src=1.2.3.4:5555 dst=2.3.4.5:6666 tls= :status=200 latency=9000999µs\"\n\t\toutput := renderTapEvent(event)\n\t\tif output != expectedOutput {\n\t\t\tt.Fatalf(\"Expecting command output to be [%s], got [%s]\", expectedOutput, output)\n\t\t}\n\t})\n\n\tt.Run(\"Converts gRPC response end event to string\", func(t *testing.T) {\n\t\tevent := toTapEvent(&tapPb.TapEvent_Http{\n\t\t\tEvent: &tapPb.TapEvent_Http_ResponseEnd_{\n\t\t\t\tResponseEnd: &tapPb.TapEvent_Http_ResponseEnd{\n\t\t\t\t\tSinceRequestInit:  &duration.Duration{Nanos: 999000},\n\t\t\t\t\tSinceResponseInit: &duration.Duration{Nanos: 888000},\n\t\t\t\t\tResponseBytes:     111,\n\t\t\t\t\tEos: &metricsPb.Eos{\n\t\t\t\t\t\tEnd: &metricsPb.Eos_GrpcStatusCode{GrpcStatusCode: uint32(codes.OK)},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\n\t\texpectedOutput := \"end id=7:8 proxy=out src=1.2.3.4:5555 dst=2.3.4.5:6666 tls= grpc-status=OK duration=888µs response-length=111B\"\n\t\toutput := renderTapEvent(event)\n\t\tif output != expectedOutput {\n\t\t\tt.Fatalf(\"Expecting command output to be [%s], got [%s]\", expectedOutput, output)\n\t\t}\n\t})\n\n\tt.Run(\"Converts HTTP response end event with reset error code to string\", func(t *testing.T) {\n\t\tevent := toTapEvent(&tapPb.TapEvent_Http{\n\t\t\tEvent: &tapPb.TapEvent_Http_ResponseEnd_{\n\t\t\t\tResponseEnd: &tapPb.TapEvent_Http_ResponseEnd{\n\t\t\t\t\tSinceRequestInit:  &duration.Duration{Nanos: 999000},\n\t\t\t\t\tSinceResponseInit: &duration.Duration{Nanos: 888000},\n\t\t\t\t\tResponseBytes:     111,\n\t\t\t\t\tEos: &metricsPb.Eos{\n\t\t\t\t\t\tEnd: &metricsPb.Eos_ResetErrorCode{ResetErrorCode: 123},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\n\t\texpectedOutput := \"end id=7:8 proxy=out src=1.2.3.4:5555 dst=2.3.4.5:6666 tls= reset-error=123 duration=888µs response-length=111B\"\n\t\toutput := renderTapEvent(event)\n\t\tif output != expectedOutput {\n\t\t\tt.Fatalf(\"Expecting command output to be [%s], got [%s]\", expectedOutput, output)\n\t\t}\n\t})\n\n\tt.Run(\"Converts HTTP response end event with empty EOS context string\", func(t *testing.T) {\n\t\tevent := toTapEvent(&tapPb.TapEvent_Http{\n\t\t\tEvent: &tapPb.TapEvent_Http_ResponseEnd_{\n\t\t\t\tResponseEnd: &tapPb.TapEvent_Http_ResponseEnd{\n\t\t\t\t\tSinceRequestInit:  &duration.Duration{Nanos: 999000},\n\t\t\t\t\tSinceResponseInit: &duration.Duration{Nanos: 888000},\n\t\t\t\t\tResponseBytes:     111,\n\t\t\t\t\tEos:               &metricsPb.Eos{},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\n\t\texpectedOutput := \"end id=7:8 proxy=out src=1.2.3.4:5555 dst=2.3.4.5:6666 tls= duration=888µs response-length=111B\"\n\t\toutput := renderTapEvent(event)\n\t\tif output != expectedOutput {\n\t\t\tt.Fatalf(\"Expecting command output to be [%s], got [%s]\", expectedOutput, output)\n\t\t}\n\t})\n\n\tt.Run(\"Converts HTTP response end event without EOS context string\", func(t *testing.T) {\n\t\tevent := toTapEvent(&tapPb.TapEvent_Http{\n\t\t\tEvent: &tapPb.TapEvent_Http_ResponseEnd_{\n\t\t\t\tResponseEnd: &tapPb.TapEvent_Http_ResponseEnd{\n\t\t\t\t\tSinceRequestInit:  &duration.Duration{Nanos: 999000},\n\t\t\t\t\tSinceResponseInit: &duration.Duration{Nanos: 888000},\n\t\t\t\t\tResponseBytes:     111,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\n\t\texpectedOutput := \"end id=7:8 proxy=out src=1.2.3.4:5555 dst=2.3.4.5:6666 tls= duration=888µs response-length=111B\"\n\t\toutput := renderTapEvent(event)\n\t\tif output != expectedOutput {\n\t\t\tt.Fatalf(\"Expecting command output to be [%s], got [%s]\", expectedOutput, output)\n\t\t}\n\t})\n\n\tt.Run(\"Handles unknown event types\", func(t *testing.T) {\n\t\tevent := toTapEvent(&tapPb.TapEvent_Http{})\n\n\t\texpectedOutput := \"unknown proxy=out src=1.2.3.4:5555 dst=2.3.4.5:6666 tls=\"\n\t\toutput := renderTapEvent(event)\n\t\tif output != expectedOutput {\n\t\t\tt.Fatalf(\"Expecting command output to be [%s], got [%s]\", expectedOutput, output)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "viz/cmd/testdata/edges_one_output.golden",
    "content": "SRC                DST                  SRC_NS      DST_NS      SECURED\nvote-bot           web                  emojivoto   emojivoto   √      \nweb                emoji                emojivoto   emojivoto   √      \nweb                voting               emojivoto   emojivoto   √      \nlinkerd-identity   linkerd-prometheus   linkerd     linkerd     √      \n"
  },
  {
    "path": "viz/cmd/testdata/edges_one_output_json.golden",
    "content": "[\n  {\n    \"src\": \"vote-bot\",\n    \"src_namespace\": \"emojivoto\",\n    \"dst\": \"web\",\n    \"dst_namespace\": \"emojivoto\",\n    \"client_id\": \"default.emojivoto\",\n    \"server_id\": \"web.emojivoto\",\n    \"no_tls_reason\": \"\"\n  },\n  {\n    \"src\": \"web\",\n    \"src_namespace\": \"emojivoto\",\n    \"dst\": \"emoji\",\n    \"dst_namespace\": \"emojivoto\",\n    \"client_id\": \"web.emojivoto\",\n    \"server_id\": \"emoji.emojivoto\",\n    \"no_tls_reason\": \"\"\n  },\n  {\n    \"src\": \"web\",\n    \"src_namespace\": \"emojivoto\",\n    \"dst\": \"voting\",\n    \"dst_namespace\": \"emojivoto\",\n    \"client_id\": \"web.emojivoto\",\n    \"server_id\": \"voting.emojivoto\",\n    \"no_tls_reason\": \"\"\n  },\n  {\n    \"src\": \"linkerd-identity\",\n    \"src_namespace\": \"linkerd\",\n    \"dst\": \"linkerd-prometheus\",\n    \"dst_namespace\": \"linkerd\",\n    \"client_id\": \"linkerd-identity.linkerd\",\n    \"server_id\": \"linkerd-prometheus.linkerd\",\n    \"no_tls_reason\": \"\"\n  }\n]\n"
  },
  {
    "path": "viz/cmd/testdata/edges_wide_output.golden",
    "content": "SRC                DST                  SRC_NS      DST_NS      CLIENT_ID                  SERVER_ID                    SECURED\nvote-bot           web                  emojivoto   emojivoto   default.emojivoto          web.emojivoto                √      \nweb                emoji                emojivoto   emojivoto   web.emojivoto              emoji.emojivoto              √      \nweb                voting               emojivoto   emojivoto   web.emojivoto              voting.emojivoto             √      \nlinkerd-identity   linkerd-prometheus   linkerd     linkerd     linkerd-identity.linkerd   linkerd-prometheus.linkerd   √      \n"
  },
  {
    "path": "viz/cmd/testdata/install_default.golden",
    "content": "---\n###\n### Linkerd Viz Extension Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    pod-security.kubernetes.io/enforce: privileged\n  annotations:\n---\n###\n### Metrics API RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\nrules:\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"daemonsets\", \"deployments\", \"replicasets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\" , \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"replicationcontrollers\", \"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"policy.linkerd.io\"]\n  resources: [\"servers\", \"serverauthorizations\", \"authorizationpolicies\", \"httproutes\"]\n  verbs: [\"list\", \"get\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-metrics-api\nsubjects:\n- kind: ServiceAccount\n  name: metrics-api\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: metrics-api\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n---\n###\n### Prometheus RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-prometheus\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\nrules:\n- apiGroups: [\"\"]\n  resources: [\"nodes\", \"nodes/proxy\", \"pods\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-prometheus\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-prometheus\nsubjects:\n- kind: ServiceAccount\n  name: prometheus\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: prometheus\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    namespace: linkerd-viz\n---\n###\n### Tap RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"services\", \"replicationcontrollers\", \"namespaces\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"daemonsets\", \"deployments\", \"replicasets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\" , \"get\", \"watch\"]\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-tap-admin\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"tap.linkerd.io\"]\n  resources: [\"*\"]\n  verbs: [\"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-tap\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-tap-auth-delegator\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: system:auth-delegator\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: tap\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-tap-auth-reader\n  namespace: kube-system\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: linkerd-viz\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: extension-apiserver-authentication-reader\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: linkerd-viz\n---\napiVersion: apiregistration.k8s.io/v1\nkind: APIService\nmetadata:\n  name: v1alpha1.tap.linkerd.io\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nspec:\n  group: tap.linkerd.io\n  version: v1alpha1\n  groupPriorityMinimum: 1000\n  versionPriority: 100\n  service:\n    name: tap\n    namespace: linkerd-viz\n  caBundle: dGVzdC10YXAtY2EtYnVuZGxl\n---\n###\n### Web RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: web\n  namespace: linkerd\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"configmaps\"]\n  verbs: [\"get\"]\n- apiGroups: [\"\"]\n  resources: [\"serviceaccounts\", \"pods\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: web\n  namespace: linkerd\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd\nroleRef:\n  kind: Role\n  name: web\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-linkerd-viz-web-check\n  labels:\n    linkerd.io/extension: viz\n    component: web\nrules:\n- apiGroups: [\"rbac.authorization.k8s.io\"]\n  resources: [\"clusterroles\", \"clusterrolebindings\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apiextensions.k8s.io\"]\n  resources: [\"customresourcedefinitions\"]\n  verbs: [\"list\"]\n- apiGroups: [\"admissionregistration.k8s.io\"]\n  resources: [\"mutatingwebhookconfigurations\", \"validatingwebhookconfigurations\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n- apiGroups: [\"\"]\n  resources: [\"nodes\", \"pods\", \"services\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apiregistration.k8s.io\"]\n  resources: [\"apiservices\"]\n  verbs: [\"get\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-web-check\n  labels:\n    linkerd.io/extension: viz\n    component: web\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-web-check\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-web-admin\n  labels:\n    linkerd.io/extension: viz\n    component: web\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-tap-admin\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-linkerd-viz-web-api\n  labels:\n    linkerd.io/extension: viz\n    component: web\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-web-api\n  labels:\n    linkerd.io/extension: viz\n    component: web\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-web-api\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: web\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd-viz\n---\n###\n### Metrics API\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: metrics-api\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: metrics-api\n  ports:\n  - name: http\n    port: 8085\n    targetPort: 8085\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: metrics-api\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: metrics-api\n  name: metrics-api\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: metrics-api\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: metrics-api\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - -controller-namespace=linkerd\n        - -log-level=info\n        - -log-format=plain\n        - -cluster-domain=cluster.local\n        - -prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/metrics-api:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: metrics-api\n        ports:\n        - containerPort: 8085\n          name: http\n        - containerPort: 9995\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          runAsNonRoot: true\n          readOnlyRootFilesystem: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: metrics-api\n      volumes:\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: metrics-api\n  port: http\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: metrics-api\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: MeshTLSAuthentication\n    name: metrics-api-web\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: MeshTLSAuthentication\nmetadata:\n  namespace: linkerd-viz\n  name: metrics-api-web\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  identityRefs:\n  - kind: ServiceAccount\n    name: web\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: linkerd-viz\n  name: kubelet\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  # Ideally, this should be restricted to the actual set of IPs kubelet uses in\n  # a cluster. This can't easily be discovered.\n  networks:\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n---\n###\n### Prometheus\n###\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: prometheus-config\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  prometheus.yml: |-\n    global:\n      evaluation_interval: 10s\n      scrape_interval: 10s\n      scrape_timeout: 10s\n\n    rule_files:\n    - /etc/prometheus/*_rules.yml\n    - /etc/prometheus/*_rules.yaml\n\n    scrape_configs:\n    - job_name: 'prometheus'\n      static_configs:\n      - targets: ['localhost:9090']\n\n    #  Required for: https://grafana.com/grafana/dashboards/315\n    - job_name: 'kubernetes-nodes-cadvisor'\n      scheme: https\n      tls_config:\n        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n        insecure_skip_verify: true\n      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token\n      kubernetes_sd_configs:\n      - role: node\n      relabel_configs:\n      - action: labelmap\n        regex: __meta_kubernetes_node_label_(.+)\n      - target_label: __address__\n        replacement: kubernetes.default.svc:443\n      - source_labels: [__meta_kubernetes_node_name]\n        regex: (.+)\n        target_label: __metrics_path__\n        replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor\n      metric_relabel_configs:\n      - source_labels: [__name__]\n        regex: '(container|machine)_(cpu|memory|network|fs)_(.+)'\n        action: keep\n      - source_labels: [__name__]\n        regex: 'container_memory_failures_total' # unneeded large metric\n        action: drop\n\n    - job_name: 'linkerd-controller'\n      kubernetes_sd_configs:\n      - role: pod\n        namespaces:\n          names:\n          - 'linkerd'\n          - 'linkerd-viz'\n      relabel_configs:\n      - source_labels:\n        - __meta_kubernetes_pod_container_port_name\n        action: keep\n        regex: admin\n      - source_labels: [__meta_kubernetes_pod_container_name]\n        action: replace\n        target_label: component\n\n    - job_name: 'linkerd-multicluster-controller'\n      kubernetes_sd_configs:\n      - role: pod\n      relabel_configs:\n      - source_labels:\n        - __meta_kubernetes_pod_label_component\n        - __meta_kubernetes_pod_container_port_name\n        action: keep\n        regex: (linkerd-service-mirror|controller);admin$\n      - source_labels: [__meta_kubernetes_pod_container_name]\n        action: replace\n        target_label: component\n\n    - job_name: 'linkerd-proxy'\n      kubernetes_sd_configs:\n      - role: pod\n      relabel_configs:\n      - source_labels: [__meta_kubernetes_pod_phase]\n        regex: (Pending|Running)\n        action: keep\n      - source_labels:\n        - __meta_kubernetes_pod_container_name\n        - __meta_kubernetes_pod_container_port_name\n        - __meta_kubernetes_pod_label_linkerd_io_control_plane_ns\n        action: keep\n        regex: ^linkerd-proxy;linkerd-admin;linkerd$\n      - source_labels: [__meta_kubernetes_namespace]\n        action: replace\n        target_label: namespace\n      - source_labels: [__meta_kubernetes_pod_name]\n        action: replace\n        target_label: pod\n      # special case k8s' \"job\" label, to not interfere with prometheus' \"job\"\n      # label\n      # __meta_kubernetes_pod_label_linkerd_io_proxy_job=foo =>\n      # k8s_job=foo\n      - source_labels: [__meta_kubernetes_pod_label_linkerd_io_proxy_job]\n        action: replace\n        target_label: k8s_job\n      # drop __meta_kubernetes_pod_label_linkerd_io_proxy_job\n      - action: labeldrop\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_job\n      # __meta_kubernetes_pod_label_linkerd_io_proxy_deployment=foo =>\n      # deployment=foo\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)\n      # drop all labels that we just made copies of in the previous labelmap\n      - action: labeldrop\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)\n      # __meta_kubernetes_pod_label_linkerd_io_foo=bar =>\n      # foo=bar\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_linkerd_io_(.+)\n      # Copy all pod labels to tmp labels\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_(.+)\n        replacement: __tmp_pod_label_$1\n      # Take `linkerd_io_` prefixed labels and copy them without the prefix\n      - action: labelmap\n        regex: __tmp_pod_label_linkerd_io_(.+)\n        replacement:  __tmp_pod_label_$1\n      # Drop the `linkerd_io_` originals\n      - action: labeldrop\n        regex: __tmp_pod_label_linkerd_io_(.+)\n      # Copy tmp labels into real labels\n      - action: labelmap\n        regex: __tmp_pod_label_(.+)\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: prometheus\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: prometheus\n  ports:\n  - name: admin\n    port: 9090\n    targetPort: 9090\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: prometheus\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: prometheus\n    namespace: linkerd-viz\n  name: prometheus\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: prometheus\n      namespace: linkerd-viz\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n      labels:\n        linkerd.io/extension: viz\n        component: prometheus\n        namespace: linkerd-viz\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - --log.level=info\n        - --log.format=logfmt\n        - --config.file=/etc/prometheus/prometheus.yml\n        - --storage.tsdb.path=/data\n        - --storage.tsdb.retention.time=6h\n        image: prom/prometheus:v2.55.1\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /-/healthy\n            port: 9090\n          initialDelaySeconds: 30\n          timeoutSeconds: 30\n        name: prometheus\n        ports:\n        - containerPort: 9090\n          name: admin\n        readinessProbe:\n          httpGet:\n            path: /-/ready\n            port: 9090\n          initialDelaySeconds: 30\n          timeoutSeconds: 30\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /data\n          name: data\n        - mountPath: /etc/prometheus/prometheus.yml\n          name: prometheus-config\n          subPath: prometheus.yml\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        fsGroup: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: prometheus\n      volumes:\n      - name: data\n        emptyDir: {}\n      - configMap:\n          name: prometheus-config\n        name: prometheus-config\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: prometheus-admin\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: prometheus\n      namespace: linkerd-viz\n  port: admin\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: prometheus-admin\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: prometheus-admin\n  requiredAuthenticationRefs:\n    - kind: ServiceAccount\n      name: metrics-api\n      namespace: linkerd-viz\n---\n###\n### Tap\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: tap\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: tap\n  ports:\n  - name: grpc\n    port: 8088\n    targetPort: 8088\n  - name: apiserver\n    port: 443\n    targetPort: apiserver\n---\nkind: Deployment\napiVersion: apps/v1\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: tap\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: tap\n    namespace: linkerd-viz\n  name: tap\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap\n      namespace: linkerd-viz\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: tap\n        namespace: linkerd-viz\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - api\n        - -api-namespace=linkerd\n        - -log-level=info\n        - -log-format=plain\n        - -identity-trust-domain=cluster.local\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/tap:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9998\n          initialDelaySeconds: 10\n        name: tap\n        ports:\n        - containerPort: 8088\n          name: grpc\n        - containerPort: 8089\n          name: apiserver\n        - containerPort: 9998\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9998\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: tap\n      volumes:\n      - name: tls\n        secret:\n          secretName: tap-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: tap-api\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap\n  port: apiserver\n  proxyProtocol: TLS\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: tap-api\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: NetworkAuthentication\n    name: kube-api-server\n---\n###\n### Tap Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-tap-injector\n  labels:\n    linkerd.io/extension: viz\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-tap-injector\n  labels:\n    linkerd.io/extension: viz\nsubjects:\n- kind: ServiceAccount\n  name: tap-injector\n  namespace: linkerd-viz\nroleRef:\n  kind: ClusterRole\n  name: linkerd-tap-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: tap-injector\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-tap-injector-webhook-config\n  labels:\n    linkerd.io/extension: viz\nwebhooks:\n- name: tap-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n  clientConfig:\n    service:\n      name: tap-injector\n      namespace: linkerd-viz\n      path: \"/\"\n    caBundle: dGVzdC10YXAtY2EtYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  reinvocationPolicy: IfNeeded\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n---\n###\n### Tap Injector\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: tap-injector\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: tap-injector\n  ports:\n  - name: tap-injector\n    port: 443\n    targetPort: tap-injector\n---\nkind: Deployment\napiVersion: apps/v1\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: tap-injector\n    app.kubernetes.io/part-of: Linkerd\n    component: tap-injector\n  name: tap-injector\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      component: tap-injector\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: tap-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - injector\n        - -tap-service-name=tap.linkerd-viz.serviceaccount.identity.linkerd.cluster.local\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/tap:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: tap-injector\n        ports:\n        - containerPort: 8443\n          name: tap-injector\n        - containerPort: 9995\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: tap-injector\n      volumes:\n      - name: tls\n        secret:\n          secretName: tap-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: tap-injector-webhook\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap-injector\n  port: tap-injector\n  proxyProtocol: TLS\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: tap-injector\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: tap-injector-webhook\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: NetworkAuthentication\n    name: kube-api-server\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: linkerd-viz\n  name: kube-api-server\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  # Ideally, this should be restricted to the actual set of IPs the kubelet API\n  # server uses for webhooks in a cluster. This can't easily be discovered.\n  networks:\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n---\n###\n### Web\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: web\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    \n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: web\n  ports:\n  - name: http\n    port: 8084\n    targetPort: 8084\n  - name: admin\n    port: 9994\n    targetPort: 9994\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: web\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: web\n    namespace: linkerd-viz\n  name: web\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: web\n      namespace: linkerd-viz\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: web\n        namespace: linkerd-viz\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - -linkerd-metrics-api-addr=metrics-api.linkerd-viz.svc.cluster.local:8085\n        - -cluster-domain=cluster.local\n        - -controller-namespace=linkerd\n        - -log-level=info\n        - -log-format=plain\n        - -enforced-host=^(localhost|127\\.0\\.0\\.1|web\\.linkerd-viz\\.svc\\.cluster\\.local|web\\.linkerd-viz\\.svc|\\[::1\\])(:\\d+)?$\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/web:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9994\n          initialDelaySeconds: 10\n        name: web\n        ports:\n        - containerPort: 8084\n          name: http\n        - containerPort: 9994\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9994\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: web\n      volumes:\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: metrics-api.linkerd-viz.svc.cluster.local\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\nspec:\n  routes:\n  - name: POST /api/v1/StatSummary\n    condition:\n      method: POST\n      pathRegex: /api/v1/StatSummary\n  - name: POST /api/v1/TopRoutes\n    condition:\n      method: POST\n      pathRegex: /api/v1/TopRoutes\n  - name: POST /api/v1/ListPods\n    condition:\n      method: POST\n      pathRegex: /api/v1/ListPods\n  - name: POST /api/v1/ListServices\n    condition:\n      method: POST\n      pathRegex: /api/v1/ListServices\n  - name: POST /api/v1/SelfCheck\n    condition:\n      method: POST\n      pathRegex: /api/v1/SelfCheck\n  - name: POST /api/v1/Gateways\n    condition:\n      method: POST\n      pathRegex: /api/v1/Gateways\n  - name: POST /api/v1/Edges\n    condition:\n      method: POST\n      pathRegex: /api/v1/Edges\n---\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: prometheus.linkerd-viz.svc.cluster.local\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\nspec:\n  routes:\n  - name: POST /api/v1/query\n    condition:\n      method: POST\n      pathRegex: /api/v1/query\n  - name: GET /api/v1/query_range\n    condition:\n      method: GET\n      pathRegex: /api/v1/query_range\n  - name: GET /api/v1/series\n    condition:\n      method: GET\n      pathRegex: /api/v1/series\n"
  },
  {
    "path": "viz/cmd/testdata/install_default_overrides.golden",
    "content": "---\n###\n### Linkerd Viz Extension Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    pod-security.kubernetes.io/enforce: privileged\n  annotations:\n---\n###\n### Metrics API RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\nrules:\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"daemonsets\", \"deployments\", \"replicasets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\" , \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"replicationcontrollers\", \"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"policy.linkerd.io\"]\n  resources: [\"servers\", \"serverauthorizations\", \"authorizationpolicies\", \"httproutes\"]\n  verbs: [\"list\", \"get\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-metrics-api\nsubjects:\n- kind: ServiceAccount\n  name: metrics-api\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: metrics-api\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n---\n###\n### Prometheus RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-prometheus\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\nrules:\n- apiGroups: [\"\"]\n  resources: [\"nodes\", \"nodes/proxy\", \"pods\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-prometheus\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-prometheus\nsubjects:\n- kind: ServiceAccount\n  name: prometheus\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: prometheus\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    namespace: linkerd-viz\n---\n###\n### Tap RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"services\", \"replicationcontrollers\", \"namespaces\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"daemonsets\", \"deployments\", \"replicasets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\" , \"get\", \"watch\"]\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-tap-admin\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"tap.linkerd.io\"]\n  resources: [\"*\"]\n  verbs: [\"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-tap\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-tap-auth-delegator\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: system:auth-delegator\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: tap\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-tap-auth-reader\n  namespace: kube-system\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: linkerd-viz\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: extension-apiserver-authentication-reader\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: linkerd-viz\n---\napiVersion: apiregistration.k8s.io/v1\nkind: APIService\nmetadata:\n  name: v1alpha1.tap.linkerd.io\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nspec:\n  group: tap.linkerd.io\n  version: v1alpha1\n  groupPriorityMinimum: 1000\n  versionPriority: 100\n  service:\n    name: tap\n    namespace: linkerd-viz\n  caBundle: dGVzdC10YXAtY2EtYnVuZGxl\n---\n###\n### Web RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: web\n  namespace: linkerd\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"configmaps\"]\n  verbs: [\"get\"]\n- apiGroups: [\"\"]\n  resources: [\"serviceaccounts\", \"pods\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: web\n  namespace: linkerd\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd\nroleRef:\n  kind: Role\n  name: web\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-linkerd-viz-web-check\n  labels:\n    linkerd.io/extension: viz\n    component: web\nrules:\n- apiGroups: [\"rbac.authorization.k8s.io\"]\n  resources: [\"clusterroles\", \"clusterrolebindings\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apiextensions.k8s.io\"]\n  resources: [\"customresourcedefinitions\"]\n  verbs: [\"list\"]\n- apiGroups: [\"admissionregistration.k8s.io\"]\n  resources: [\"mutatingwebhookconfigurations\", \"validatingwebhookconfigurations\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n- apiGroups: [\"\"]\n  resources: [\"nodes\", \"pods\", \"services\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apiregistration.k8s.io\"]\n  resources: [\"apiservices\"]\n  verbs: [\"get\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-web-check\n  labels:\n    linkerd.io/extension: viz\n    component: web\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-web-check\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-web-admin\n  labels:\n    linkerd.io/extension: viz\n    component: web\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-tap-admin\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-linkerd-viz-web-api\n  labels:\n    linkerd.io/extension: viz\n    component: web\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-web-api\n  labels:\n    linkerd.io/extension: viz\n    component: web\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-web-api\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: web\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd-viz\n---\n###\n### Metrics API\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: metrics-api\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: metrics-api\n  ports:\n  - name: http\n    port: 8085\n    targetPort: 8085\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: metrics-api\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: metrics-api\n  name: metrics-api\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: metrics-api\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: metrics-api\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - -controller-namespace=linkerd\n        - -log-level=debug\n        - -log-format=plain\n        - -cluster-domain=cluster.local\n        - -prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\n        - -enable-pprof=false\n        image: gcr.io/linkerd/metrics-api:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: metrics-api\n        ports:\n        - containerPort: 8085\n          name: http\n        - containerPort: 9995\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          runAsNonRoot: true\n          readOnlyRootFilesystem: true\n          runAsUser: 1234\n          runAsGroup: 1234\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: metrics-api\n      volumes:\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: metrics-api\n  port: http\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: metrics-api\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: MeshTLSAuthentication\n    name: metrics-api-web\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: MeshTLSAuthentication\nmetadata:\n  namespace: linkerd-viz\n  name: metrics-api-web\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  identityRefs:\n  - kind: ServiceAccount\n    name: web\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: linkerd-viz\n  name: kubelet\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  # Ideally, this should be restricted to the actual set of IPs kubelet uses in\n  # a cluster. This can't easily be discovered.\n  networks:\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n---\n###\n### Prometheus\n###\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: prometheus-config\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  prometheus.yml: |-\n    global:\n      evaluation_interval: 10s\n      scrape_interval: 10s\n      scrape_timeout: 10s\n\n    rule_files:\n    - /etc/prometheus/*_rules.yml\n    - /etc/prometheus/*_rules.yaml\n\n    scrape_configs:\n    - job_name: 'prometheus'\n      static_configs:\n      - targets: ['localhost:9090']\n\n    #  Required for: https://grafana.com/grafana/dashboards/315\n    - job_name: 'kubernetes-nodes-cadvisor'\n      scheme: https\n      tls_config:\n        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n        insecure_skip_verify: true\n      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token\n      kubernetes_sd_configs:\n      - role: node\n      relabel_configs:\n      - action: labelmap\n        regex: __meta_kubernetes_node_label_(.+)\n      - target_label: __address__\n        replacement: kubernetes.default.svc:443\n      - source_labels: [__meta_kubernetes_node_name]\n        regex: (.+)\n        target_label: __metrics_path__\n        replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor\n      metric_relabel_configs:\n      - source_labels: [__name__]\n        regex: '(container|machine)_(cpu|memory|network|fs)_(.+)'\n        action: keep\n      - source_labels: [__name__]\n        regex: 'container_memory_failures_total' # unneeded large metric\n        action: drop\n\n    - job_name: 'linkerd-controller'\n      kubernetes_sd_configs:\n      - role: pod\n        namespaces:\n          names:\n          - 'linkerd'\n          - 'linkerd-viz'\n      relabel_configs:\n      - source_labels:\n        - __meta_kubernetes_pod_container_port_name\n        action: keep\n        regex: admin\n      - source_labels: [__meta_kubernetes_pod_container_name]\n        action: replace\n        target_label: component\n\n    - job_name: 'linkerd-multicluster-controller'\n      kubernetes_sd_configs:\n      - role: pod\n      relabel_configs:\n      - source_labels:\n        - __meta_kubernetes_pod_label_component\n        - __meta_kubernetes_pod_container_port_name\n        action: keep\n        regex: (linkerd-service-mirror|controller);admin$\n      - source_labels: [__meta_kubernetes_pod_container_name]\n        action: replace\n        target_label: component\n\n    - job_name: 'linkerd-proxy'\n      kubernetes_sd_configs:\n      - role: pod\n      relabel_configs:\n      - source_labels: [__meta_kubernetes_pod_phase]\n        regex: (Pending|Running)\n        action: keep\n      - source_labels:\n        - __meta_kubernetes_pod_container_name\n        - __meta_kubernetes_pod_container_port_name\n        - __meta_kubernetes_pod_label_linkerd_io_control_plane_ns\n        action: keep\n        regex: ^linkerd-proxy;linkerd-admin;linkerd$\n      - source_labels: [__meta_kubernetes_namespace]\n        action: replace\n        target_label: namespace\n      - source_labels: [__meta_kubernetes_pod_name]\n        action: replace\n        target_label: pod\n      # special case k8s' \"job\" label, to not interfere with prometheus' \"job\"\n      # label\n      # __meta_kubernetes_pod_label_linkerd_io_proxy_job=foo =>\n      # k8s_job=foo\n      - source_labels: [__meta_kubernetes_pod_label_linkerd_io_proxy_job]\n        action: replace\n        target_label: k8s_job\n      # drop __meta_kubernetes_pod_label_linkerd_io_proxy_job\n      - action: labeldrop\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_job\n      # __meta_kubernetes_pod_label_linkerd_io_proxy_deployment=foo =>\n      # deployment=foo\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)\n      # drop all labels that we just made copies of in the previous labelmap\n      - action: labeldrop\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)\n      # __meta_kubernetes_pod_label_linkerd_io_foo=bar =>\n      # foo=bar\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_linkerd_io_(.+)\n      # Copy all pod labels to tmp labels\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_(.+)\n        replacement: __tmp_pod_label_$1\n      # Take `linkerd_io_` prefixed labels and copy them without the prefix\n      - action: labelmap\n        regex: __tmp_pod_label_linkerd_io_(.+)\n        replacement:  __tmp_pod_label_$1\n      # Drop the `linkerd_io_` originals\n      - action: labeldrop\n        regex: __tmp_pod_label_linkerd_io_(.+)\n      # Copy tmp labels into real labels\n      - action: labelmap\n        regex: __tmp_pod_label_(.+)\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: prometheus\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: prometheus\n  ports:\n  - name: admin\n    port: 9090\n    targetPort: 9090\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: prometheus\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: prometheus\n    namespace: linkerd-viz\n  name: prometheus\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: prometheus\n      namespace: linkerd-viz\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n      labels:\n        linkerd.io/extension: viz\n        component: prometheus\n        namespace: linkerd-viz\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - --log.level=debug\n        - --log.format=logfmt\n        - --config.file=/etc/prometheus/prometheus.yml\n        - --storage.tsdb.path=/data\n        - --storage.tsdb.retention.time=6h\n        image: prom/prometheus:v2.55.1\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /-/healthy\n            port: 9090\n          initialDelaySeconds: 30\n          timeoutSeconds: 30\n        name: prometheus\n        ports:\n        - containerPort: 9090\n          name: admin\n        readinessProbe:\n          httpGet:\n            path: /-/ready\n            port: 9090\n          initialDelaySeconds: 30\n          timeoutSeconds: 30\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /data\n          name: data\n        - mountPath: /etc/prometheus/prometheus.yml\n          name: prometheus-config\n          subPath: prometheus.yml\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        fsGroup: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: prometheus\n      volumes:\n      - name: data\n        emptyDir: {}\n      - configMap:\n          name: prometheus-config\n        name: prometheus-config\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: prometheus-admin\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: prometheus\n      namespace: linkerd-viz\n  port: admin\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: prometheus-admin\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: prometheus-admin\n  requiredAuthenticationRefs:\n    - kind: ServiceAccount\n      name: metrics-api\n      namespace: linkerd-viz\n---\n###\n### Tap\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: tap\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: tap\n  ports:\n  - name: grpc\n    port: 8088\n    targetPort: 8088\n  - name: apiserver\n    port: 443\n    targetPort: apiserver\n---\nkind: Deployment\napiVersion: apps/v1\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: tap\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: tap\n    namespace: linkerd-viz\n  name: tap\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap\n      namespace: linkerd-viz\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: tap\n        namespace: linkerd-viz\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - api\n        - -api-namespace=linkerd\n        - -log-level=info\n        - -log-format=plain\n        - -identity-trust-domain=cluster.local\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/tap:stable-9.2\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9998\n          initialDelaySeconds: 10\n        name: tap\n        ports:\n        - containerPort: 8088\n          name: grpc\n        - containerPort: 8089\n          name: apiserver\n        - containerPort: 9998\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9998\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 5678\n          runAsGroup: 5678\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: tap\n      volumes:\n      - name: tls\n        secret:\n          secretName: tap-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: tap-api\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap\n  port: apiserver\n  proxyProtocol: TLS\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: tap-api\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: NetworkAuthentication\n    name: kube-api-server\n---\n###\n### Tap Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-tap-injector\n  labels:\n    linkerd.io/extension: viz\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-tap-injector\n  labels:\n    linkerd.io/extension: viz\nsubjects:\n- kind: ServiceAccount\n  name: tap-injector\n  namespace: linkerd-viz\nroleRef:\n  kind: ClusterRole\n  name: linkerd-tap-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: tap-injector\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-tap-injector-webhook-config\n  labels:\n    linkerd.io/extension: viz\nwebhooks:\n- name: tap-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n  clientConfig:\n    service:\n      name: tap-injector\n      namespace: linkerd-viz\n      path: \"/\"\n    caBundle: dGVzdC10YXAtY2EtYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  reinvocationPolicy: IfNeeded\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n---\n###\n### Tap Injector\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: tap-injector\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: tap-injector\n  ports:\n  - name: tap-injector\n    port: 443\n    targetPort: tap-injector\n---\nkind: Deployment\napiVersion: apps/v1\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: tap-injector\n    app.kubernetes.io/part-of: Linkerd\n    component: tap-injector\n  name: tap-injector\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      component: tap-injector\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: tap-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - injector\n        - -tap-service-name=tap.linkerd-viz.serviceaccount.identity.linkerd.cluster.local\n        - -log-level=debug\n        - -log-format=plain\n        - -enable-pprof=false\n        image: gcr.io/linkerd/tap:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: tap-injector\n        ports:\n        - containerPort: 8443\n          name: tap-injector\n        - containerPort: 9995\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 1234\n          runAsGroup: 1234\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: tap-injector\n      volumes:\n      - name: tls\n        secret:\n          secretName: tap-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: tap-injector-webhook\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap-injector\n  port: tap-injector\n  proxyProtocol: TLS\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: tap-injector\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: tap-injector-webhook\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: NetworkAuthentication\n    name: kube-api-server\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: linkerd-viz\n  name: kube-api-server\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  # Ideally, this should be restricted to the actual set of IPs the kubelet API\n  # server uses for webhooks in a cluster. This can't easily be discovered.\n  networks:\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n---\n###\n### Web\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: web\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    \n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: web\n  ports:\n  - name: http\n    port: 8084\n    targetPort: 8084\n  - name: admin\n    port: 9994\n    targetPort: 9994\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: web\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: web\n    namespace: linkerd-viz\n  name: web\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: web\n      namespace: linkerd-viz\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: web\n        namespace: linkerd-viz\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - -linkerd-metrics-api-addr=metrics-api.linkerd-viz.svc.cluster.local:8085\n        - -cluster-domain=cluster.local\n        - -grafana-addr=grafana.grafana:3000\n        - -controller-namespace=linkerd\n        - -log-level=debug\n        - -log-format=plain\n        - -enforced-host=^(localhost|127\\.0\\.0\\.1|web\\.linkerd-viz\\.svc\\.cluster\\.local|web\\.linkerd-viz\\.svc|\\[::1\\])(:\\d+)?$\n        - -enable-pprof=false\n        image: gcr.io/linkerd/web:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9994\n          initialDelaySeconds: 10\n        name: web\n        ports:\n        - containerPort: 8084\n          name: http\n        - containerPort: 9994\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9994\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 1234\n          runAsGroup: 1234\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: web\n      volumes:\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: metrics-api.linkerd-viz.svc.cluster.local\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\nspec:\n  routes:\n  - name: POST /api/v1/StatSummary\n    condition:\n      method: POST\n      pathRegex: /api/v1/StatSummary\n  - name: POST /api/v1/TopRoutes\n    condition:\n      method: POST\n      pathRegex: /api/v1/TopRoutes\n  - name: POST /api/v1/ListPods\n    condition:\n      method: POST\n      pathRegex: /api/v1/ListPods\n  - name: POST /api/v1/ListServices\n    condition:\n      method: POST\n      pathRegex: /api/v1/ListServices\n  - name: POST /api/v1/SelfCheck\n    condition:\n      method: POST\n      pathRegex: /api/v1/SelfCheck\n  - name: POST /api/v1/Gateways\n    condition:\n      method: POST\n      pathRegex: /api/v1/Gateways\n  - name: POST /api/v1/Edges\n    condition:\n      method: POST\n      pathRegex: /api/v1/Edges\n---\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: prometheus.linkerd-viz.svc.cluster.local\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\nspec:\n  routes:\n  - name: POST /api/v1/query\n    condition:\n      method: POST\n      pathRegex: /api/v1/query\n  - name: GET /api/v1/query_range\n    condition:\n      method: GET\n      pathRegex: /api/v1/query_range\n  - name: GET /api/v1/series\n    condition:\n      method: GET\n      pathRegex: /api/v1/series\n"
  },
  {
    "path": "viz/cmd/testdata/install_prometheus_disabled.golden",
    "content": "---\n###\n### Linkerd Viz Extension Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    pod-security.kubernetes.io/enforce: privileged\n  annotations:\n    viz.linkerd.io/external-prometheus: external-prom.com\n---\n###\n### Metrics API RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\nrules:\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"daemonsets\", \"deployments\", \"replicasets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\" , \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"replicationcontrollers\", \"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"policy.linkerd.io\"]\n  resources: [\"servers\", \"serverauthorizations\", \"authorizationpolicies\", \"httproutes\"]\n  verbs: [\"list\", \"get\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-metrics-api\nsubjects:\n- kind: ServiceAccount\n  name: metrics-api\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: metrics-api\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n---\n###\n### Tap RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"services\", \"replicationcontrollers\", \"namespaces\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"daemonsets\", \"deployments\", \"replicasets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\" , \"get\", \"watch\"]\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-tap-admin\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"tap.linkerd.io\"]\n  resources: [\"*\"]\n  verbs: [\"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-tap\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-tap-auth-delegator\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: system:auth-delegator\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: tap\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-tap-auth-reader\n  namespace: kube-system\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: linkerd-viz\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: extension-apiserver-authentication-reader\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: linkerd-viz\n---\napiVersion: apiregistration.k8s.io/v1\nkind: APIService\nmetadata:\n  name: v1alpha1.tap.linkerd.io\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nspec:\n  group: tap.linkerd.io\n  version: v1alpha1\n  groupPriorityMinimum: 1000\n  versionPriority: 100\n  service:\n    name: tap\n    namespace: linkerd-viz\n  caBundle: dGVzdC10YXAtY2EtYnVuZGxl\n---\n###\n### Web RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: web\n  namespace: linkerd\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"configmaps\"]\n  verbs: [\"get\"]\n- apiGroups: [\"\"]\n  resources: [\"serviceaccounts\", \"pods\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: web\n  namespace: linkerd\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd\nroleRef:\n  kind: Role\n  name: web\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-linkerd-viz-web-check\n  labels:\n    linkerd.io/extension: viz\n    component: web\nrules:\n- apiGroups: [\"rbac.authorization.k8s.io\"]\n  resources: [\"clusterroles\", \"clusterrolebindings\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apiextensions.k8s.io\"]\n  resources: [\"customresourcedefinitions\"]\n  verbs: [\"list\"]\n- apiGroups: [\"admissionregistration.k8s.io\"]\n  resources: [\"mutatingwebhookconfigurations\", \"validatingwebhookconfigurations\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n- apiGroups: [\"\"]\n  resources: [\"nodes\", \"pods\", \"services\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apiregistration.k8s.io\"]\n  resources: [\"apiservices\"]\n  verbs: [\"get\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-web-check\n  labels:\n    linkerd.io/extension: viz\n    component: web\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-web-check\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-web-admin\n  labels:\n    linkerd.io/extension: viz\n    component: web\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-tap-admin\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-linkerd-viz-web-api\n  labels:\n    linkerd.io/extension: viz\n    component: web\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-web-api\n  labels:\n    linkerd.io/extension: viz\n    component: web\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-web-api\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: web\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd-viz\n---\n###\n### Metrics API\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: metrics-api\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: metrics-api\n  ports:\n  - name: http\n    port: 8085\n    targetPort: 8085\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: metrics-api\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: metrics-api\n  name: metrics-api\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: metrics-api\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: metrics-api\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - -controller-namespace=linkerd\n        - -log-level=info\n        - -log-format=plain\n        - -cluster-domain=cluster.local\n        - -prometheus-url=external-prom.com\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/metrics-api:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: metrics-api\n        ports:\n        - containerPort: 8085\n          name: http\n        - containerPort: 9995\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          runAsNonRoot: true\n          readOnlyRootFilesystem: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: metrics-api\n      volumes:\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: metrics-api\n  port: http\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: metrics-api\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: MeshTLSAuthentication\n    name: metrics-api-web\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: MeshTLSAuthentication\nmetadata:\n  namespace: linkerd-viz\n  name: metrics-api-web\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  identityRefs:\n  - kind: ServiceAccount\n    name: web\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: linkerd-viz\n  name: kubelet\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  # Ideally, this should be restricted to the actual set of IPs kubelet uses in\n  # a cluster. This can't easily be discovered.\n  networks:\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: prometheus-admin\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: prometheus\n      namespace: linkerd-viz\n  port: admin\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: prometheus-admin\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: prometheus-admin\n  requiredAuthenticationRefs:\n    - kind: ServiceAccount\n      name: metrics-api\n      namespace: linkerd-viz\n---\n###\n### Tap\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: tap\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: tap\n  ports:\n  - name: grpc\n    port: 8088\n    targetPort: 8088\n  - name: apiserver\n    port: 443\n    targetPort: apiserver\n---\nkind: Deployment\napiVersion: apps/v1\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: tap\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: tap\n    namespace: linkerd-viz\n  name: tap\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap\n      namespace: linkerd-viz\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: tap\n        namespace: linkerd-viz\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - api\n        - -api-namespace=linkerd\n        - -log-level=info\n        - -log-format=plain\n        - -identity-trust-domain=cluster.local\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/tap:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9998\n          initialDelaySeconds: 10\n        name: tap\n        ports:\n        - containerPort: 8088\n          name: grpc\n        - containerPort: 8089\n          name: apiserver\n        - containerPort: 9998\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9998\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: tap\n      volumes:\n      - name: tls\n        secret:\n          secretName: tap-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: tap-api\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap\n  port: apiserver\n  proxyProtocol: TLS\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: tap-api\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: NetworkAuthentication\n    name: kube-api-server\n---\n###\n### Tap Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-tap-injector\n  labels:\n    linkerd.io/extension: viz\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-tap-injector\n  labels:\n    linkerd.io/extension: viz\nsubjects:\n- kind: ServiceAccount\n  name: tap-injector\n  namespace: linkerd-viz\nroleRef:\n  kind: ClusterRole\n  name: linkerd-tap-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: tap-injector\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-tap-injector-webhook-config\n  labels:\n    linkerd.io/extension: viz\nwebhooks:\n- name: tap-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n  clientConfig:\n    service:\n      name: tap-injector\n      namespace: linkerd-viz\n      path: \"/\"\n    caBundle: dGVzdC10YXAtY2EtYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  reinvocationPolicy: IfNeeded\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n---\n###\n### Tap Injector\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: tap-injector\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: tap-injector\n  ports:\n  - name: tap-injector\n    port: 443\n    targetPort: tap-injector\n---\nkind: Deployment\napiVersion: apps/v1\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: tap-injector\n    app.kubernetes.io/part-of: Linkerd\n    component: tap-injector\n  name: tap-injector\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      component: tap-injector\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: tap-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - injector\n        - -tap-service-name=tap.linkerd-viz.serviceaccount.identity.linkerd.cluster.local\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/tap:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: tap-injector\n        ports:\n        - containerPort: 8443\n          name: tap-injector\n        - containerPort: 9995\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: tap-injector\n      volumes:\n      - name: tls\n        secret:\n          secretName: tap-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: tap-injector-webhook\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap-injector\n  port: tap-injector\n  proxyProtocol: TLS\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: tap-injector\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: tap-injector-webhook\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: NetworkAuthentication\n    name: kube-api-server\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: linkerd-viz\n  name: kube-api-server\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  # Ideally, this should be restricted to the actual set of IPs the kubelet API\n  # server uses for webhooks in a cluster. This can't easily be discovered.\n  networks:\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n---\n###\n### Web\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: web\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    \n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: web\n  ports:\n  - name: http\n    port: 8084\n    targetPort: 8084\n  - name: admin\n    port: 9994\n    targetPort: 9994\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: web\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: web\n    namespace: linkerd-viz\n  name: web\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: web\n      namespace: linkerd-viz\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: web\n        namespace: linkerd-viz\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - -linkerd-metrics-api-addr=metrics-api.linkerd-viz.svc.cluster.local:8085\n        - -cluster-domain=cluster.local\n        - -controller-namespace=linkerd\n        - -log-level=info\n        - -log-format=plain\n        - -enforced-host=^(localhost|127\\.0\\.0\\.1|web\\.linkerd-viz\\.svc\\.cluster\\.local|web\\.linkerd-viz\\.svc|\\[::1\\])(:\\d+)?$\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/web:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9994\n          initialDelaySeconds: 10\n        name: web\n        ports:\n        - containerPort: 8084\n          name: http\n        - containerPort: 9994\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9994\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: web\n      volumes:\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: metrics-api.linkerd-viz.svc.cluster.local\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\nspec:\n  routes:\n  - name: POST /api/v1/StatSummary\n    condition:\n      method: POST\n      pathRegex: /api/v1/StatSummary\n  - name: POST /api/v1/TopRoutes\n    condition:\n      method: POST\n      pathRegex: /api/v1/TopRoutes\n  - name: POST /api/v1/ListPods\n    condition:\n      method: POST\n      pathRegex: /api/v1/ListPods\n  - name: POST /api/v1/ListServices\n    condition:\n      method: POST\n      pathRegex: /api/v1/ListServices\n  - name: POST /api/v1/SelfCheck\n    condition:\n      method: POST\n      pathRegex: /api/v1/SelfCheck\n  - name: POST /api/v1/Gateways\n    condition:\n      method: POST\n      pathRegex: /api/v1/Gateways\n  - name: POST /api/v1/Edges\n    condition:\n      method: POST\n      pathRegex: /api/v1/Edges\n"
  },
  {
    "path": "viz/cmd/testdata/install_prometheus_loglevel_from_args.golden",
    "content": "---\n###\n### Linkerd Viz Extension Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    pod-security.kubernetes.io/enforce: privileged\n  annotations:\n---\n###\n### Metrics API RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\nrules:\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"daemonsets\", \"deployments\", \"replicasets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\" , \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"replicationcontrollers\", \"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"policy.linkerd.io\"]\n  resources: [\"servers\", \"serverauthorizations\", \"authorizationpolicies\", \"httproutes\"]\n  verbs: [\"list\", \"get\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-metrics-api\nsubjects:\n- kind: ServiceAccount\n  name: metrics-api\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: metrics-api\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n---\n###\n### Prometheus RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-prometheus\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\nrules:\n- apiGroups: [\"\"]\n  resources: [\"nodes\", \"nodes/proxy\", \"pods\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-prometheus\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-prometheus\nsubjects:\n- kind: ServiceAccount\n  name: prometheus\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: prometheus\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    namespace: linkerd-viz\n---\n###\n### Tap RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"services\", \"replicationcontrollers\", \"namespaces\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"daemonsets\", \"deployments\", \"replicasets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\" , \"get\", \"watch\"]\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-tap-admin\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"tap.linkerd.io\"]\n  resources: [\"*\"]\n  verbs: [\"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-tap\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-tap-auth-delegator\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: system:auth-delegator\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: tap\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-tap-auth-reader\n  namespace: kube-system\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: linkerd-viz\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: extension-apiserver-authentication-reader\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: linkerd-viz\n---\napiVersion: apiregistration.k8s.io/v1\nkind: APIService\nmetadata:\n  name: v1alpha1.tap.linkerd.io\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nspec:\n  group: tap.linkerd.io\n  version: v1alpha1\n  groupPriorityMinimum: 1000\n  versionPriority: 100\n  service:\n    name: tap\n    namespace: linkerd-viz\n  caBundle: dGVzdC10YXAtY2EtYnVuZGxl\n---\n###\n### Web RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: web\n  namespace: linkerd\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"configmaps\"]\n  verbs: [\"get\"]\n- apiGroups: [\"\"]\n  resources: [\"serviceaccounts\", \"pods\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: web\n  namespace: linkerd\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd\nroleRef:\n  kind: Role\n  name: web\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-linkerd-viz-web-check\n  labels:\n    linkerd.io/extension: viz\n    component: web\nrules:\n- apiGroups: [\"rbac.authorization.k8s.io\"]\n  resources: [\"clusterroles\", \"clusterrolebindings\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apiextensions.k8s.io\"]\n  resources: [\"customresourcedefinitions\"]\n  verbs: [\"list\"]\n- apiGroups: [\"admissionregistration.k8s.io\"]\n  resources: [\"mutatingwebhookconfigurations\", \"validatingwebhookconfigurations\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n- apiGroups: [\"\"]\n  resources: [\"nodes\", \"pods\", \"services\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apiregistration.k8s.io\"]\n  resources: [\"apiservices\"]\n  verbs: [\"get\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-web-check\n  labels:\n    linkerd.io/extension: viz\n    component: web\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-web-check\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-web-admin\n  labels:\n    linkerd.io/extension: viz\n    component: web\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-tap-admin\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-linkerd-viz-web-api\n  labels:\n    linkerd.io/extension: viz\n    component: web\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-web-api\n  labels:\n    linkerd.io/extension: viz\n    component: web\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-web-api\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: web\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd-viz\n---\n###\n### Metrics API\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: metrics-api\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: metrics-api\n  ports:\n  - name: http\n    port: 8085\n    targetPort: 8085\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: metrics-api\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: metrics-api\n  name: metrics-api\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: metrics-api\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: metrics-api\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - -controller-namespace=linkerd\n        - -log-level=info\n        - -log-format=plain\n        - -cluster-domain=cluster.local\n        - -prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/metrics-api:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: metrics-api\n        ports:\n        - containerPort: 8085\n          name: http\n        - containerPort: 9995\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          runAsNonRoot: true\n          readOnlyRootFilesystem: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: metrics-api\n      volumes:\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: metrics-api\n  port: http\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: metrics-api\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: MeshTLSAuthentication\n    name: metrics-api-web\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: MeshTLSAuthentication\nmetadata:\n  namespace: linkerd-viz\n  name: metrics-api-web\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  identityRefs:\n  - kind: ServiceAccount\n    name: web\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: linkerd-viz\n  name: kubelet\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  # Ideally, this should be restricted to the actual set of IPs kubelet uses in\n  # a cluster. This can't easily be discovered.\n  networks:\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n---\n###\n### Prometheus\n###\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: prometheus-config\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  prometheus.yml: |-\n    global:\n      evaluation_interval: 10s\n      scrape_interval: 10s\n      scrape_timeout: 10s\n\n    rule_files:\n    - /etc/prometheus/*_rules.yml\n    - /etc/prometheus/*_rules.yaml\n\n    scrape_configs:\n    - job_name: 'prometheus'\n      static_configs:\n      - targets: ['localhost:9090']\n\n    #  Required for: https://grafana.com/grafana/dashboards/315\n    - job_name: 'kubernetes-nodes-cadvisor'\n      scheme: https\n      tls_config:\n        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n        insecure_skip_verify: true\n      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token\n      kubernetes_sd_configs:\n      - role: node\n      relabel_configs:\n      - action: labelmap\n        regex: __meta_kubernetes_node_label_(.+)\n      - target_label: __address__\n        replacement: kubernetes.default.svc:443\n      - source_labels: [__meta_kubernetes_node_name]\n        regex: (.+)\n        target_label: __metrics_path__\n        replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor\n      metric_relabel_configs:\n      - source_labels: [__name__]\n        regex: '(container|machine)_(cpu|memory|network|fs)_(.+)'\n        action: keep\n      - source_labels: [__name__]\n        regex: 'container_memory_failures_total' # unneeded large metric\n        action: drop\n\n    - job_name: 'linkerd-controller'\n      kubernetes_sd_configs:\n      - role: pod\n        namespaces:\n          names:\n          - 'linkerd'\n          - 'linkerd-viz'\n      relabel_configs:\n      - source_labels:\n        - __meta_kubernetes_pod_container_port_name\n        action: keep\n        regex: admin\n      - source_labels: [__meta_kubernetes_pod_container_name]\n        action: replace\n        target_label: component\n\n    - job_name: 'linkerd-multicluster-controller'\n      kubernetes_sd_configs:\n      - role: pod\n      relabel_configs:\n      - source_labels:\n        - __meta_kubernetes_pod_label_component\n        - __meta_kubernetes_pod_container_port_name\n        action: keep\n        regex: (linkerd-service-mirror|controller);admin$\n      - source_labels: [__meta_kubernetes_pod_container_name]\n        action: replace\n        target_label: component\n\n    - job_name: 'linkerd-proxy'\n      kubernetes_sd_configs:\n      - role: pod\n      relabel_configs:\n      - source_labels: [__meta_kubernetes_pod_phase]\n        regex: (Pending|Running)\n        action: keep\n      - source_labels:\n        - __meta_kubernetes_pod_container_name\n        - __meta_kubernetes_pod_container_port_name\n        - __meta_kubernetes_pod_label_linkerd_io_control_plane_ns\n        action: keep\n        regex: ^linkerd-proxy;linkerd-admin;linkerd$\n      - source_labels: [__meta_kubernetes_namespace]\n        action: replace\n        target_label: namespace\n      - source_labels: [__meta_kubernetes_pod_name]\n        action: replace\n        target_label: pod\n      # special case k8s' \"job\" label, to not interfere with prometheus' \"job\"\n      # label\n      # __meta_kubernetes_pod_label_linkerd_io_proxy_job=foo =>\n      # k8s_job=foo\n      - source_labels: [__meta_kubernetes_pod_label_linkerd_io_proxy_job]\n        action: replace\n        target_label: k8s_job\n      # drop __meta_kubernetes_pod_label_linkerd_io_proxy_job\n      - action: labeldrop\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_job\n      # __meta_kubernetes_pod_label_linkerd_io_proxy_deployment=foo =>\n      # deployment=foo\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)\n      # drop all labels that we just made copies of in the previous labelmap\n      - action: labeldrop\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)\n      # __meta_kubernetes_pod_label_linkerd_io_foo=bar =>\n      # foo=bar\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_linkerd_io_(.+)\n      # Copy all pod labels to tmp labels\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_(.+)\n        replacement: __tmp_pod_label_$1\n      # Take `linkerd_io_` prefixed labels and copy them without the prefix\n      - action: labelmap\n        regex: __tmp_pod_label_linkerd_io_(.+)\n        replacement:  __tmp_pod_label_$1\n      # Drop the `linkerd_io_` originals\n      - action: labeldrop\n        regex: __tmp_pod_label_linkerd_io_(.+)\n      # Copy tmp labels into real labels\n      - action: labelmap\n        regex: __tmp_pod_label_(.+)\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: prometheus\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: prometheus\n  ports:\n  - name: admin\n    port: 9090\n    targetPort: 9090\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: prometheus\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: prometheus\n    namespace: linkerd-viz\n  name: prometheus\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: prometheus\n      namespace: linkerd-viz\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n      labels:\n        linkerd.io/extension: viz\n        component: prometheus\n        namespace: linkerd-viz\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - --log.format=logfmt\n        - --config.file=/etc/prometheus/prometheus.yml\n        - --log.level=debug\n        - --storage.tsdb.path=/data\n        - --storage.tsdb.retention.time=6h\n        image: prom/prometheus:v2.55.1\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /-/healthy\n            port: 9090\n          initialDelaySeconds: 30\n          timeoutSeconds: 30\n        name: prometheus\n        ports:\n        - containerPort: 9090\n          name: admin\n        readinessProbe:\n          httpGet:\n            path: /-/ready\n            port: 9090\n          initialDelaySeconds: 30\n          timeoutSeconds: 30\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /data\n          name: data\n        - mountPath: /etc/prometheus/prometheus.yml\n          name: prometheus-config\n          subPath: prometheus.yml\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        fsGroup: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: prometheus\n      volumes:\n      - name: data\n        emptyDir: {}\n      - configMap:\n          name: prometheus-config\n        name: prometheus-config\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: prometheus-admin\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: prometheus\n      namespace: linkerd-viz\n  port: admin\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: prometheus-admin\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: prometheus-admin\n  requiredAuthenticationRefs:\n    - kind: ServiceAccount\n      name: metrics-api\n      namespace: linkerd-viz\n---\n###\n### Tap\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: tap\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: tap\n  ports:\n  - name: grpc\n    port: 8088\n    targetPort: 8088\n  - name: apiserver\n    port: 443\n    targetPort: apiserver\n---\nkind: Deployment\napiVersion: apps/v1\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: tap\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: tap\n    namespace: linkerd-viz\n  name: tap\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap\n      namespace: linkerd-viz\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: tap\n        namespace: linkerd-viz\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - api\n        - -api-namespace=linkerd\n        - -log-level=info\n        - -log-format=plain\n        - -identity-trust-domain=cluster.local\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/tap:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9998\n          initialDelaySeconds: 10\n        name: tap\n        ports:\n        - containerPort: 8088\n          name: grpc\n        - containerPort: 8089\n          name: apiserver\n        - containerPort: 9998\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9998\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: tap\n      volumes:\n      - name: tls\n        secret:\n          secretName: tap-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: tap-api\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap\n  port: apiserver\n  proxyProtocol: TLS\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: tap-api\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: NetworkAuthentication\n    name: kube-api-server\n---\n###\n### Tap Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-tap-injector\n  labels:\n    linkerd.io/extension: viz\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-tap-injector\n  labels:\n    linkerd.io/extension: viz\nsubjects:\n- kind: ServiceAccount\n  name: tap-injector\n  namespace: linkerd-viz\nroleRef:\n  kind: ClusterRole\n  name: linkerd-tap-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: tap-injector\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-tap-injector-webhook-config\n  labels:\n    linkerd.io/extension: viz\nwebhooks:\n- name: tap-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n  clientConfig:\n    service:\n      name: tap-injector\n      namespace: linkerd-viz\n      path: \"/\"\n    caBundle: dGVzdC10YXAtY2EtYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  reinvocationPolicy: IfNeeded\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n---\n###\n### Tap Injector\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: tap-injector\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: tap-injector\n  ports:\n  - name: tap-injector\n    port: 443\n    targetPort: tap-injector\n---\nkind: Deployment\napiVersion: apps/v1\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: tap-injector\n    app.kubernetes.io/part-of: Linkerd\n    component: tap-injector\n  name: tap-injector\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      component: tap-injector\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: tap-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - injector\n        - -tap-service-name=tap.linkerd-viz.serviceaccount.identity.linkerd.cluster.local\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/tap:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: tap-injector\n        ports:\n        - containerPort: 8443\n          name: tap-injector\n        - containerPort: 9995\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: tap-injector\n      volumes:\n      - name: tls\n        secret:\n          secretName: tap-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: tap-injector-webhook\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap-injector\n  port: tap-injector\n  proxyProtocol: TLS\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: tap-injector\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: tap-injector-webhook\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: NetworkAuthentication\n    name: kube-api-server\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: linkerd-viz\n  name: kube-api-server\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  # Ideally, this should be restricted to the actual set of IPs the kubelet API\n  # server uses for webhooks in a cluster. This can't easily be discovered.\n  networks:\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n---\n###\n### Web\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: web\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    \n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: web\n  ports:\n  - name: http\n    port: 8084\n    targetPort: 8084\n  - name: admin\n    port: 9994\n    targetPort: 9994\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: web\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: web\n    namespace: linkerd-viz\n  name: web\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: web\n      namespace: linkerd-viz\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: web\n        namespace: linkerd-viz\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - -linkerd-metrics-api-addr=metrics-api.linkerd-viz.svc.cluster.local:8085\n        - -cluster-domain=cluster.local\n        - -controller-namespace=linkerd\n        - -log-level=info\n        - -log-format=plain\n        - -enforced-host=^(localhost|127\\.0\\.0\\.1|web\\.linkerd-viz\\.svc\\.cluster\\.local|web\\.linkerd-viz\\.svc|\\[::1\\])(:\\d+)?$\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/web:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9994\n          initialDelaySeconds: 10\n        name: web\n        ports:\n        - containerPort: 8084\n          name: http\n        - containerPort: 9994\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9994\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: web\n      volumes:\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: metrics-api.linkerd-viz.svc.cluster.local\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\nspec:\n  routes:\n  - name: POST /api/v1/StatSummary\n    condition:\n      method: POST\n      pathRegex: /api/v1/StatSummary\n  - name: POST /api/v1/TopRoutes\n    condition:\n      method: POST\n      pathRegex: /api/v1/TopRoutes\n  - name: POST /api/v1/ListPods\n    condition:\n      method: POST\n      pathRegex: /api/v1/ListPods\n  - name: POST /api/v1/ListServices\n    condition:\n      method: POST\n      pathRegex: /api/v1/ListServices\n  - name: POST /api/v1/SelfCheck\n    condition:\n      method: POST\n      pathRegex: /api/v1/SelfCheck\n  - name: POST /api/v1/Gateways\n    condition:\n      method: POST\n      pathRegex: /api/v1/Gateways\n  - name: POST /api/v1/Edges\n    condition:\n      method: POST\n      pathRegex: /api/v1/Edges\n---\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: prometheus.linkerd-viz.svc.cluster.local\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\nspec:\n  routes:\n  - name: POST /api/v1/query\n    condition:\n      method: POST\n      pathRegex: /api/v1/query\n  - name: GET /api/v1/query_range\n    condition:\n      method: GET\n      pathRegex: /api/v1/query_range\n  - name: GET /api/v1/series\n    condition:\n      method: GET\n      pathRegex: /api/v1/series\n"
  },
  {
    "path": "viz/cmd/testdata/install_proxy_resources.golden",
    "content": "---\n###\n### Linkerd Viz Extension Namespace\n###\nkind: Namespace\napiVersion: v1\nmetadata:\n  name: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    pod-security.kubernetes.io/enforce: privileged\n  annotations:\n---\n###\n### Metrics API RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\nrules:\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"daemonsets\", \"deployments\", \"replicasets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\" , \"get\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"endpoints\", \"services\", \"replicationcontrollers\", \"namespaces\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"policy.linkerd.io\"]\n  resources: [\"servers\", \"serverauthorizations\", \"authorizationpolicies\", \"httproutes\"]\n  verbs: [\"list\", \"get\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-metrics-api\nsubjects:\n- kind: ServiceAccount\n  name: metrics-api\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: metrics-api\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n---\n###\n### Prometheus RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-prometheus\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\nrules:\n- apiGroups: [\"\"]\n  resources: [\"nodes\", \"nodes/proxy\", \"pods\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-prometheus\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-prometheus\nsubjects:\n- kind: ServiceAccount\n  name: prometheus\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: prometheus\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    namespace: linkerd-viz\n---\n###\n### Tap RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\", \"services\", \"replicationcontrollers\", \"namespaces\", \"nodes\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"apps\"]\n  resources: [\"daemonsets\", \"deployments\", \"replicasets\", \"statefulsets\"]\n  verbs: [\"list\", \"get\", \"watch\"]\n- apiGroups: [\"extensions\", \"batch\"]\n  resources: [\"cronjobs\", \"jobs\"]\n  verbs: [\"list\" , \"get\", \"watch\"]\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-tap-admin\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n- apiGroups: [\"tap.linkerd.io\"]\n  resources: [\"*\"]\n  verbs: [\"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-tap\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-tap-auth-delegator\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: system:auth-delegator\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: tap\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-tap-auth-reader\n  namespace: kube-system\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: linkerd-viz\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: extension-apiserver-authentication-reader\nsubjects:\n- kind: ServiceAccount\n  name: tap\n  namespace: linkerd-viz\n---\napiVersion: apiregistration.k8s.io/v1\nkind: APIService\nmetadata:\n  name: v1alpha1.tap.linkerd.io\n  labels:\n    linkerd.io/extension: viz\n    component: tap\nspec:\n  group: tap.linkerd.io\n  version: v1alpha1\n  groupPriorityMinimum: 1000\n  versionPriority: 100\n  service:\n    name: tap\n    namespace: linkerd-viz\n  caBundle: dGVzdC10YXAtY2EtYnVuZGxl\n---\n###\n### Web RBAC\n###\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: web\n  namespace: linkerd\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd\nrules:\n- apiGroups: [\"\"]\n  resources: [\"configmaps\"]\n  verbs: [\"get\"]\n  resourceNames: [\"linkerd-config\"]\n- apiGroups: [\"\"]\n  resources: [\"namespaces\", \"configmaps\"]\n  verbs: [\"get\"]\n- apiGroups: [\"\"]\n  resources: [\"serviceaccounts\", \"pods\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apps\"]\n  resources: [\"replicasets\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: web\n  namespace: linkerd\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd\nroleRef:\n  kind: Role\n  name: web\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-linkerd-viz-web-check\n  labels:\n    linkerd.io/extension: viz\n    component: web\nrules:\n- apiGroups: [\"rbac.authorization.k8s.io\"]\n  resources: [\"clusterroles\", \"clusterrolebindings\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apiextensions.k8s.io\"]\n  resources: [\"customresourcedefinitions\"]\n  verbs: [\"list\"]\n- apiGroups: [\"admissionregistration.k8s.io\"]\n  resources: [\"mutatingwebhookconfigurations\", \"validatingwebhookconfigurations\"]\n  verbs: [\"list\"]\n- apiGroups: [\"linkerd.io\"]\n  resources: [\"serviceprofiles\"]\n  verbs: [\"list\"]\n- apiGroups: [\"\"]\n  resources: [\"nodes\", \"pods\", \"services\"]\n  verbs: [\"list\"]\n- apiGroups: [\"apiregistration.k8s.io\"]\n  resources: [\"apiservices\"]\n  verbs: [\"get\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-web-check\n  labels:\n    linkerd.io/extension: viz\n    component: web\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-web-check\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-linkerd-viz-web-admin\n  labels:\n    linkerd.io/extension: viz\n    component: web\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-tap-admin\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: linkerd-linkerd-viz-web-api\n  labels:\n    linkerd.io/extension: viz\n    component: web\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: linkerd-linkerd-viz-web-api\n  labels:\n    linkerd.io/extension: viz\n    component: web\nroleRef:\n  kind: ClusterRole\n  name: linkerd-linkerd-viz-web-api\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: web\n  namespace: linkerd-viz\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: web\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd-viz\n---\n###\n### Metrics API\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: metrics-api\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: metrics-api\n  ports:\n  - name: http\n    port: 8085\n    targetPort: 8085\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: metrics-api\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: metrics-api\n  name: metrics-api\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: metrics-api\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: metrics-api\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - -controller-namespace=linkerd\n        - -log-level=info\n        - -log-format=plain\n        - -cluster-domain=cluster.local\n        - -prometheus-url=http://prometheus.linkerd-viz.svc.cluster.local:9090\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/metrics-api:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: metrics-api\n        ports:\n        - containerPort: 8085\n          name: http\n        - containerPort: 9995\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          runAsNonRoot: true\n          readOnlyRootFilesystem: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: metrics-api\n      volumes:\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: metrics-api\n  port: http\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: metrics-api\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: metrics-api\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: MeshTLSAuthentication\n    name: metrics-api-web\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: MeshTLSAuthentication\nmetadata:\n  namespace: linkerd-viz\n  name: metrics-api-web\n  labels:\n    linkerd.io/extension: viz\n    component: metrics-api\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  identityRefs:\n  - kind: ServiceAccount\n    name: web\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: linkerd-viz\n  name: kubelet\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  # Ideally, this should be restricted to the actual set of IPs kubelet uses in\n  # a cluster. This can't easily be discovered.\n  networks:\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n---\n###\n### Prometheus\n###\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: prometheus-config\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\ndata:\n  prometheus.yml: |-\n    global:\n      evaluation_interval: 10s\n      scrape_interval: 10s\n      scrape_timeout: 10s\n\n    rule_files:\n    - /etc/prometheus/*_rules.yml\n    - /etc/prometheus/*_rules.yaml\n\n    scrape_configs:\n    - job_name: 'prometheus'\n      static_configs:\n      - targets: ['localhost:9090']\n\n    #  Required for: https://grafana.com/grafana/dashboards/315\n    - job_name: 'kubernetes-nodes-cadvisor'\n      scheme: https\n      tls_config:\n        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n        insecure_skip_verify: true\n      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token\n      kubernetes_sd_configs:\n      - role: node\n      relabel_configs:\n      - action: labelmap\n        regex: __meta_kubernetes_node_label_(.+)\n      - target_label: __address__\n        replacement: kubernetes.default.svc:443\n      - source_labels: [__meta_kubernetes_node_name]\n        regex: (.+)\n        target_label: __metrics_path__\n        replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor\n      metric_relabel_configs:\n      - source_labels: [__name__]\n        regex: '(container|machine)_(cpu|memory|network|fs)_(.+)'\n        action: keep\n      - source_labels: [__name__]\n        regex: 'container_memory_failures_total' # unneeded large metric\n        action: drop\n\n    - job_name: 'linkerd-controller'\n      kubernetes_sd_configs:\n      - role: pod\n        namespaces:\n          names:\n          - 'linkerd'\n          - 'linkerd-viz'\n      relabel_configs:\n      - source_labels:\n        - __meta_kubernetes_pod_container_port_name\n        action: keep\n        regex: admin\n      - source_labels: [__meta_kubernetes_pod_container_name]\n        action: replace\n        target_label: component\n\n    - job_name: 'linkerd-multicluster-controller'\n      kubernetes_sd_configs:\n      - role: pod\n      relabel_configs:\n      - source_labels:\n        - __meta_kubernetes_pod_label_component\n        - __meta_kubernetes_pod_container_port_name\n        action: keep\n        regex: (linkerd-service-mirror|controller);admin$\n      - source_labels: [__meta_kubernetes_pod_container_name]\n        action: replace\n        target_label: component\n\n    - job_name: 'linkerd-proxy'\n      kubernetes_sd_configs:\n      - role: pod\n      relabel_configs:\n      - source_labels: [__meta_kubernetes_pod_phase]\n        regex: (Pending|Running)\n        action: keep\n      - source_labels:\n        - __meta_kubernetes_pod_container_name\n        - __meta_kubernetes_pod_container_port_name\n        - __meta_kubernetes_pod_label_linkerd_io_control_plane_ns\n        action: keep\n        regex: ^linkerd-proxy;linkerd-admin;linkerd$\n      - source_labels: [__meta_kubernetes_namespace]\n        action: replace\n        target_label: namespace\n      - source_labels: [__meta_kubernetes_pod_name]\n        action: replace\n        target_label: pod\n      # special case k8s' \"job\" label, to not interfere with prometheus' \"job\"\n      # label\n      # __meta_kubernetes_pod_label_linkerd_io_proxy_job=foo =>\n      # k8s_job=foo\n      - source_labels: [__meta_kubernetes_pod_label_linkerd_io_proxy_job]\n        action: replace\n        target_label: k8s_job\n      # drop __meta_kubernetes_pod_label_linkerd_io_proxy_job\n      - action: labeldrop\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_job\n      # __meta_kubernetes_pod_label_linkerd_io_proxy_deployment=foo =>\n      # deployment=foo\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)\n      # drop all labels that we just made copies of in the previous labelmap\n      - action: labeldrop\n        regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+)\n      # __meta_kubernetes_pod_label_linkerd_io_foo=bar =>\n      # foo=bar\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_linkerd_io_(.+)\n      # Copy all pod labels to tmp labels\n      - action: labelmap\n        regex: __meta_kubernetes_pod_label_(.+)\n        replacement: __tmp_pod_label_$1\n      # Take `linkerd_io_` prefixed labels and copy them without the prefix\n      - action: labelmap\n        regex: __tmp_pod_label_linkerd_io_(.+)\n        replacement:  __tmp_pod_label_$1\n      # Drop the `linkerd_io_` originals\n      - action: labeldrop\n        regex: __tmp_pod_label_linkerd_io_(.+)\n      # Copy tmp labels into real labels\n      - action: labelmap\n        regex: __tmp_pod_label_(.+)\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: prometheus\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: prometheus\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: prometheus\n  ports:\n  - name: admin\n    port: 9090\n    targetPort: 9090\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: prometheus\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: prometheus\n    namespace: linkerd-viz\n  name: prometheus\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: prometheus\n      namespace: linkerd-viz\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        config.linkerd.io/proxy-cpu-request: \"500m\"\n        config.linkerd.io/proxy-cpu-limit: \"100m\"\n        config.linkerd.io/proxy-memory-request: \"20Mi\"\n        config.linkerd.io/proxy-memory-limit: \"250Mi\"\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n      labels:\n        linkerd.io/extension: viz\n        component: prometheus\n        namespace: linkerd-viz\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - --log.level=info\n        - --log.format=logfmt\n        - --config.file=/etc/prometheus/prometheus.yml\n        - --storage.tsdb.path=/data\n        - --storage.tsdb.retention.time=6h\n        image: prom/prometheus:v2.55.1\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /-/healthy\n            port: 9090\n          initialDelaySeconds: 30\n          timeoutSeconds: 30\n        name: prometheus\n        ports:\n        - containerPort: 9090\n          name: admin\n        readinessProbe:\n          httpGet:\n            path: /-/ready\n            port: 9090\n          initialDelaySeconds: 30\n          timeoutSeconds: 30\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsGroup: 65534\n          runAsNonRoot: true\n          runAsUser: 65534\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /data\n          name: data\n        - mountPath: /etc/prometheus/prometheus.yml\n          name: prometheus-config\n          subPath: prometheus.yml\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        fsGroup: 65534\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: prometheus\n      volumes:\n      - name: data\n        emptyDir: {}\n      - configMap:\n          name: prometheus-config\n        name: prometheus-config\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: prometheus-admin\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: prometheus\n      namespace: linkerd-viz\n  port: admin\n  proxyProtocol: HTTP/1\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: prometheus-admin\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: prometheus-admin\n  requiredAuthenticationRefs:\n    - kind: ServiceAccount\n      name: metrics-api\n      namespace: linkerd-viz\n---\n###\n### Tap\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: tap\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: tap\n  ports:\n  - name: grpc\n    port: 8088\n    targetPort: 8088\n  - name: apiserver\n    port: 443\n    targetPort: apiserver\n---\nkind: Deployment\napiVersion: apps/v1\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: tap\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: tap\n    namespace: linkerd-viz\n  name: tap\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap\n      namespace: linkerd-viz\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        config.linkerd.io/proxy-cpu-request: \"500m\"\n        config.linkerd.io/proxy-cpu-limit: \"100m\"\n        config.linkerd.io/proxy-memory-request: \"20Mi\"\n        config.linkerd.io/proxy-memory-limit: \"250Mi\"\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: tap\n        namespace: linkerd-viz\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - api\n        - -api-namespace=linkerd\n        - -log-level=info\n        - -log-format=plain\n        - -identity-trust-domain=cluster.local\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/tap:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9998\n          initialDelaySeconds: 10\n        name: tap\n        ports:\n        - containerPort: 8088\n          name: grpc\n        - containerPort: 8089\n          name: apiserver\n        - containerPort: 9998\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9998\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: tap\n      volumes:\n      - name: tls\n        secret:\n          secretName: tap-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: tap-api\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap\n  port: apiserver\n  proxyProtocol: TLS\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: tap\n  labels:\n    linkerd.io/extension: viz\n    component: tap\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: tap-api\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: NetworkAuthentication\n    name: kube-api-server\n---\n###\n### Tap Injector RBAC\n###\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-tap-injector\n  labels:\n    linkerd.io/extension: viz\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: linkerd-tap-injector\n  labels:\n    linkerd.io/extension: viz\nsubjects:\n- kind: ServiceAccount\n  name: tap-injector\n  namespace: linkerd-viz\nroleRef:\n  kind: ClusterRole\n  name: linkerd-tap-injector\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: ServiceAccount\napiVersion: v1\nmetadata:\n  name: tap-injector\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: linkerd-tap-injector-webhook-config\n  labels:\n    linkerd.io/extension: viz\nwebhooks:\n- name: tap-injector.linkerd.io\n  namespaceSelector:\n    matchExpressions:\n    - key: kubernetes.io/metadata.name\n      operator: NotIn\n      values:\n      - kube-system\n  clientConfig:\n    service:\n      name: tap-injector\n      namespace: linkerd-viz\n      path: \"/\"\n    caBundle: dGVzdC10YXAtY2EtYnVuZGxl\n  failurePolicy: Ignore\n  admissionReviewVersions: [\"v1\", \"v1beta1\"]\n  reinvocationPolicy: IfNeeded\n  rules:\n  - operations: [ \"CREATE\" ]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\"]\n    scope: \"Namespaced\"\n  sideEffects: None\n---\n###\n### Tap Injector\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: tap-injector\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: tap-injector\n  ports:\n  - name: tap-injector\n    port: 443\n    targetPort: tap-injector\n---\nkind: Deployment\napiVersion: apps/v1\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: tap-injector\n    app.kubernetes.io/part-of: Linkerd\n    component: tap-injector\n  name: tap-injector\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      component: tap-injector\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: tap-injector\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - injector\n        - -tap-service-name=tap.linkerd-viz.serviceaccount.identity.linkerd.cluster.local\n        - -log-level=info\n        - -log-format=plain\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/tap:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9995\n          initialDelaySeconds: 10\n        name: tap-injector\n        ports:\n        - containerPort: 8443\n          name: tap-injector\n        - containerPort: 9995\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9995\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/linkerd/tls\n          name: tls\n          readOnly: true\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: tap-injector\n      volumes:\n      - name: tls\n        secret:\n          secretName: tap-injector-k8s-tls\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: policy.linkerd.io/v1beta3\nkind: Server\nmetadata:\n  namespace: linkerd-viz\n  name: tap-injector-webhook\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  podSelector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: tap-injector\n  port: tap-injector\n  proxyProtocol: TLS\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: AuthorizationPolicy\nmetadata:\n  namespace: linkerd-viz\n  name: tap-injector\n  labels:\n    linkerd.io/extension: viz\n    component: tap-injector\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  targetRef:\n    group: policy.linkerd.io\n    kind: Server\n    name: tap-injector-webhook\n  requiredAuthenticationRefs:\n  - group: policy.linkerd.io\n    kind: NetworkAuthentication\n    name: kube-api-server\n---\napiVersion: policy.linkerd.io/v1alpha1\nkind: NetworkAuthentication\nmetadata:\n  namespace: linkerd-viz\n  name: kube-api-server\n  labels:\n    linkerd.io/extension: viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\nspec:\n  # Ideally, this should be restricted to the actual set of IPs the kubelet API\n  # server uses for webhooks in a cluster. This can't easily be discovered.\n  networks:\n  - cidr: \"0.0.0.0/0\"\n  - cidr: \"::/0\"\n---\n###\n### Web\n###\nkind: Service\napiVersion: v1\nmetadata:\n  name: web\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\n    component: web\n    namespace: linkerd-viz\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    \n    linkerd.io/inject: enabled\nspec:\n  type: ClusterIP\n  selector:\n    linkerd.io/extension: viz\n    component: web\n  ports:\n  - name: http\n    port: 8084\n    targetPort: 8084\n  - name: admin\n    port: 9994\n    targetPort: 9994\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    linkerd.io/created-by: linkerd/cli dev-undefined\n    linkerd.io/inject: enabled\n    config.linkerd.io/proxy-await: \"enabled\"\n  labels:\n    linkerd.io/extension: viz\n    app.kubernetes.io/name: web\n    app.kubernetes.io/part-of: Linkerd\n    app.kubernetes.io/version: dev-undefined\n    component: web\n    namespace: linkerd-viz\n  name: web\n  namespace: linkerd-viz\nspec:\n  replicas: 1\n  revisionHistoryLimit: 10\n  selector:\n    matchLabels:\n      linkerd.io/extension: viz\n      component: web\n      namespace: linkerd-viz\n  template:\n    metadata:\n      annotations:\n        linkerd.io/created-by: linkerd/cli dev-undefined\n        config.linkerd.io/proxy-cpu-request: \"500m\"\n        config.linkerd.io/proxy-cpu-limit: \"100m\"\n        config.linkerd.io/proxy-memory-request: \"20Mi\"\n        config.linkerd.io/proxy-memory-limit: \"250Mi\"\n        linkerd.io/inject: enabled\n        config.alpha.linkerd.io/proxy-wait-before-exit-seconds: \"0\"\n        cluster-autoscaler.kubernetes.io/safe-to-evict: \"true\"\n      labels:\n        linkerd.io/extension: viz\n        component: web\n        namespace: linkerd-viz\n    spec:\n      nodeSelector:\n        kubernetes.io/os: linux\n      automountServiceAccountToken: false\n      containers:\n      - args:\n        - -linkerd-metrics-api-addr=metrics-api.linkerd-viz.svc.cluster.local:8085\n        - -cluster-domain=cluster.local\n        - -controller-namespace=linkerd\n        - -log-level=info\n        - -log-format=plain\n        - -enforced-host=^(localhost|127\\.0\\.0\\.1|web\\.linkerd-viz\\.svc\\.cluster\\.local|web\\.linkerd-viz\\.svc|\\[::1\\])(:\\d+)?$\n        - -enable-pprof=false\n        image: cr.l5d.io/linkerd/web:dev-undefined\n        imagePullPolicy: IfNotPresent\n        livenessProbe:\n          httpGet:\n            path: /ping\n            port: 9994\n          initialDelaySeconds: 10\n        name: web\n        ports:\n        - containerPort: 8084\n          name: http\n        - containerPort: 9994\n          name: admin\n        readinessProbe:\n          failureThreshold: 7\n          httpGet:\n            path: /ready\n            port: 9994\n        resources:\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n          runAsNonRoot: true\n          runAsUser: 2103\n          runAsGroup: 2103\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount\n          name: kube-api-access\n          readOnly: true\n      securityContext:\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: web\n      volumes:\n      - name: kube-api-access\n        projected:\n          defaultMode: 420\n          sources:\n          - serviceAccountToken:\n              expirationSeconds: 3607\n              path: token\n          - configMap:\n              items:\n              - key: ca.crt\n                path: ca.crt\n              name: kube-root-ca.crt\n          - downwardAPI:\n              items:\n              - fieldRef:\n                  apiVersion: v1\n                  fieldPath: metadata.namespace\n                path: namespace\n---\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: metrics-api.linkerd-viz.svc.cluster.local\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\nspec:\n  routes:\n  - name: POST /api/v1/StatSummary\n    condition:\n      method: POST\n      pathRegex: /api/v1/StatSummary\n  - name: POST /api/v1/TopRoutes\n    condition:\n      method: POST\n      pathRegex: /api/v1/TopRoutes\n  - name: POST /api/v1/ListPods\n    condition:\n      method: POST\n      pathRegex: /api/v1/ListPods\n  - name: POST /api/v1/ListServices\n    condition:\n      method: POST\n      pathRegex: /api/v1/ListServices\n  - name: POST /api/v1/SelfCheck\n    condition:\n      method: POST\n      pathRegex: /api/v1/SelfCheck\n  - name: POST /api/v1/Gateways\n    condition:\n      method: POST\n      pathRegex: /api/v1/Gateways\n  - name: POST /api/v1/Edges\n    condition:\n      method: POST\n      pathRegex: /api/v1/Edges\n---\napiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: prometheus.linkerd-viz.svc.cluster.local\n  namespace: linkerd-viz\n  labels:\n    linkerd.io/extension: viz\nspec:\n  routes:\n  - name: POST /api/v1/query\n    condition:\n      method: POST\n      pathRegex: /api/v1/query\n  - name: GET /api/v1/query_range\n    condition:\n      method: GET\n      pathRegex: /api/v1/query_range\n  - name: GET /api/v1/series\n    condition:\n      method: GET\n      pathRegex: /api/v1/series\n"
  },
  {
    "path": "viz/cmd/testdata/routes_one_output.golden",
    "content": "ROUTE       SERVICE   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99\n/a           foobar   100.00%   1.5rps         123ms         123ms         123ms\n/b           foobar   100.00%   1.0rps         123ms         123ms         123ms\n/c           foobar         -        -             -             -             -\n[DEFAULT]    foobar   100.00%   0.5rps         123ms         123ms         123ms\n\n"
  },
  {
    "path": "viz/cmd/testdata/routes_one_output_json.golden",
    "content": "{\n  \"deploy/foobar\": [\n    {\n      \"route\": \"/a\",\n      \"authority\": \"foobar\",\n      \"success\": 1,\n      \"rps\": 1.5,\n      \"latency_ms_p50\": 123,\n      \"latency_ms_p95\": 123,\n      \"latency_ms_p99\": 123\n    },\n    {\n      \"route\": \"/b\",\n      \"authority\": \"foobar\",\n      \"success\": 1,\n      \"rps\": 1,\n      \"latency_ms_p50\": 123,\n      \"latency_ms_p95\": 123,\n      \"latency_ms_p99\": 123\n    },\n    {\n      \"route\": \"/c\",\n      \"authority\": \"foobar\",\n      \"success\": 0,\n      \"rps\": 0,\n      \"latency_ms_p50\": 123,\n      \"latency_ms_p95\": 123,\n      \"latency_ms_p99\": 123\n    },\n    {\n      \"route\": \"[DEFAULT]\",\n      \"authority\": \"foobar\",\n      \"success\": 1,\n      \"rps\": 0.5,\n      \"latency_ms_p50\": 123,\n      \"latency_ms_p95\": 123,\n      \"latency_ms_p99\": 123\n    }\n  ]\n}\n"
  },
  {
    "path": "viz/cmd/testdata/routes_one_output_wide.golden",
    "content": "ROUTE       SERVICE   EFFECTIVE_SUCCESS   EFFECTIVE_RPS   ACTUAL_SUCCESS   ACTUAL_RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99\n/a           foobar             100.00%          1.5rps          100.00%       1.5rps         123ms         123ms         123ms\n/b           foobar             100.00%          1.0rps          100.00%       1.0rps         123ms         123ms         123ms\n/c           foobar                   -               -                -            -             -             -             -\n[DEFAULT]    foobar             100.00%          0.5rps          100.00%       0.5rps         123ms         123ms         123ms\n\n"
  },
  {
    "path": "viz/cmd/testdata/stat_all_output.golden",
    "content": "NAMESPACE    NAME    MESHED   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99   TCP_CONN\nemojivoto1   emoji      1/2   100.00%   2.0rps         123ms         123ms         123ms        123\nemojivoto2   emoji      1/2   100.00%   2.0rps         123ms         123ms         123ms        123\n"
  },
  {
    "path": "viz/cmd/testdata/stat_all_output_json.golden",
    "content": "[\n  {\n    \"namespace\": \"emojivoto1\",\n    \"kind\": \"namespace\",\n    \"name\": \"emoji\",\n    \"success\": 1,\n    \"rps\": 2.05,\n    \"latency_ms_p50\": 123,\n    \"latency_ms_p95\": 123,\n    \"latency_ms_p99\": 123,\n    \"tcp_open_connections\": 123,\n    \"tcp_read_bytes_rate\": 2.05,\n    \"tcp_write_bytes_rate\": 2.05\n  },\n  {\n    \"namespace\": \"emojivoto2\",\n    \"kind\": \"namespace\",\n    \"name\": \"emoji\",\n    \"success\": 1,\n    \"rps\": 2.05,\n    \"latency_ms_p50\": 123,\n    \"latency_ms_p95\": 123,\n    \"latency_ms_p99\": 123,\n    \"tcp_open_connections\": 123,\n    \"tcp_read_bytes_rate\": 2.05,\n    \"tcp_write_bytes_rate\": 2.05\n  }\n]\n"
  },
  {
    "path": "viz/cmd/testdata/stat_one_output.golden",
    "content": "NAME    MESHED   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99   TCP_CONN\nemoji      1/2   100.00%   2.0rps         123ms         123ms         123ms        123\n"
  },
  {
    "path": "viz/cmd/testdata/stat_one_output_json.golden",
    "content": "[\n  {\n    \"namespace\": \"emojivoto1\",\n    \"kind\": \"namespace\",\n    \"name\": \"emoji\",\n    \"success\": 1,\n    \"rps\": 2.05,\n    \"latency_ms_p50\": 123,\n    \"latency_ms_p95\": 123,\n    \"latency_ms_p99\": 123,\n    \"tcp_open_connections\": 123,\n    \"tcp_read_bytes_rate\": 2.05,\n    \"tcp_write_bytes_rate\": 2.05\n  }\n]\n"
  },
  {
    "path": "viz/cmd/testdata/stat_one_pod_output.golden",
    "content": "NAME     STATUS   MESHED   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99   TCP_CONN\nemoji   Running      1/1   100.00%   2.0rps         123ms         123ms         123ms        123\n"
  },
  {
    "path": "viz/cmd/testdata/stat_one_tcp_output.golden",
    "content": "NAME    MESHED   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99   TCP_CONN   READ_BYTES/SEC   WRITE_BYTES/SEC\nemoji      1/2   100.00%   2.0rps         123ms         123ms         123ms        123           2.0B/s            2.0B/s\n"
  },
  {
    "path": "viz/cmd/testdata/stat_one_ts_output.golden",
    "content": "NAME        APEX        LEAF        WEIGHT   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99\nfoo-split   apex_name   service-1     900m   100.00%   2.0rps         123ms         123ms         123ms\nfoo-split   apex_name   service-2     100m   100.00%   2.0rps         123ms         123ms         123ms\n"
  },
  {
    "path": "viz/cmd/testdata/stat_one_ts_output_json.golden",
    "content": "[\n  {\n    \"namespace\": \"default\",\n    \"kind\": \"trafficsplit\",\n    \"name\": \"foo-split\",\n    \"success\": 1,\n    \"rps\": 2.05,\n    \"latency_ms_p50\": 123,\n    \"latency_ms_p95\": 123,\n    \"latency_ms_p99\": 123,\n    \"apex\": \"apex_name\",\n    \"leaf\": \"service-1\",\n    \"weight\": \"900m\"\n  },\n  {\n    \"namespace\": \"default\",\n    \"kind\": \"trafficsplit\",\n    \"name\": \"foo-split\",\n    \"success\": 1,\n    \"rps\": 2.05,\n    \"latency_ms_p50\": 123,\n    \"latency_ms_p95\": 123,\n    \"latency_ms_p99\": 123,\n    \"apex\": \"apex_name\",\n    \"leaf\": \"service-2\",\n    \"weight\": \"100m\"\n  }\n]\n"
  },
  {
    "path": "viz/cmd/testdata/tap_busy_output.golden",
    "content": "req id=1:0 proxy=out src=0.0.0.1:0 dst=[ff01::1]:0 tls=true :method=GET :authority=localhost :path=/some/path\nend id=1:0 proxy=out src=0.0.0.1:0 dst=[ff01::1]:0 tls= grpc-status=Code(666) duration=100000000µs response-length=1337B\n"
  },
  {
    "path": "viz/cmd/testdata/tap_busy_output_json.golden",
    "content": "{\n  \"source\": {\n    \"ip\": \"0.0.0.1\",\n    \"port\": 0,\n    \"metadata\": null\n  },\n  \"destination\": {\n    \"ip\": \"ff01::1\",\n    \"port\": 0,\n    \"metadata\": {\n      \"pod\": \"my-pod\",\n      \"tls\": \"true\"\n    }\n  },\n  \"routeMeta\": null,\n  \"proxyDirection\": \"OUTBOUND\",\n  \"requestInitEvent\": {\n    \"id\": {\n      \"base\": 1,\n      \"stream\": 0\n    },\n    \"method\": \"GET\",\n    \"scheme\": \"HTTPS\",\n    \"authority\": \"localhost\",\n    \"path\": \"/some/path\",\n    \"headers\": [\n      {\n        \"name\": \"header-name-1\",\n        \"valueStr\": \"header-value-str-1\"\n      },\n      {\n        \"name\": \"header-name-2\",\n        \"valueBin\": \"aGVhZGVyLXZhbHVlLWJpbi0y\"\n      }\n    ]\n  }\n}\n{\n  \"source\": {\n    \"ip\": \"0.0.0.1\",\n    \"port\": 0,\n    \"metadata\": null\n  },\n  \"destination\": {\n    \"ip\": \"ff01::1\",\n    \"port\": 0,\n    \"metadata\": null\n  },\n  \"routeMeta\": null,\n  \"proxyDirection\": \"OUTBOUND\",\n  \"responseEndEvent\": {\n    \"id\": {\n      \"base\": 1,\n      \"stream\": 0\n    },\n    \"sinceRequestInit\": {\n      \"seconds\": 10\n    },\n    \"sinceResponseInit\": {\n      \"seconds\": 100\n    },\n    \"responseBytes\": 1337,\n    \"trailers\": [\n      {\n        \"name\": \"trailer-name\",\n        \"valueBin\": \"aGVhZGVyLXZhbHVlLWJpbg==\"\n      }\n    ],\n    \"grpcStatusCode\": 666\n  }\n}\n"
  },
  {
    "path": "viz/cmd/testdata/tap_busy_output_jsonpath.golden",
    "content": "{\n  \"ip\": \"0.0.0.1\",\n  \"port\": 0,\n  \"metadata\": null\n}\n{\n  \"ip\": \"0.0.0.1\",\n  \"port\": 0,\n  \"metadata\": null\n}\n"
  },
  {
    "path": "viz/cmd/testdata/tap_busy_output_wide.golden",
    "content": "req id=1:0 proxy=out src=0.0.0.1:0 dst=[ff01::1]:0 tls=true :method=GET :authority=localhost :path=/some/path dst_pod=my-pod dst_tls=true\nend id=1:0 proxy=out src=0.0.0.1:0 dst=[ff01::1]:0 tls= grpc-status=Code(666) duration=100000000µs response-length=1337B\n"
  },
  {
    "path": "viz/cmd/testdata/tap_empty_output.golden",
    "content": ""
  },
  {
    "path": "viz/cmd/top.go",
    "content": "package cmd\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/pkg/addr\"\n\tpkgcmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/protohttp\"\n\tmetricsAPI \"github.com/linkerd/linkerd2/viz/metrics-api\"\n\tmetricsPb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/api\"\n\thc \"github.com/linkerd/linkerd2/viz/pkg/healthcheck\"\n\tvizutil \"github.com/linkerd/linkerd2/viz/pkg/util\"\n\ttapPb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n\t\"github.com/linkerd/linkerd2/viz/tap/pkg\"\n\trunewidth \"github.com/mattn/go-runewidth\"\n\ttermbox \"github.com/nsf/termbox-go\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\t\"google.golang.org/grpc/codes\"\n)\n\ntype topOptions struct {\n\tnamespace     string\n\ttoResource    string\n\ttoNamespace   string\n\tmaxRps        float32\n\tscheme        string\n\tmethod        string\n\tauthority     string\n\tpath          string\n\thideSources   bool\n\troutes        bool\n\tlabelSelector string\n}\n\ntype topRequest struct {\n\tevent   *tapPb.TapEvent\n\treqInit *tapPb.TapEvent_Http_RequestInit\n\trspInit *tapPb.TapEvent_Http_ResponseInit\n\trspEnd  *tapPb.TapEvent_Http_ResponseEnd\n}\n\ntype topRequestID struct {\n\tsrc    string\n\tdst    string\n\tstream uint64\n}\n\nfunc (id topRequestID) String() string {\n\treturn fmt.Sprintf(\"%s->%s(%d)\", id.src, id.dst, id.stream)\n}\n\ntype tableColumn struct {\n\theader string\n\twidth  int\n\t// Columns with key=true will be treated as the primary key for the table.\n\t// In other words, if two rows have equal values for the key=true columns\n\t// then those rows will be merged.\n\tkey bool\n\t// If true, render this column.\n\tdisplay bool\n\t// If true, set the width to the widest value in this column.\n\tflexible   bool\n\trightAlign bool\n\tvalue      func(tableRow) string\n}\n\ntype tableRow struct {\n\tpath        string\n\tmethod      string\n\troute       string\n\tsource      string\n\tdestination string\n\tcount       int\n\tbest        time.Duration\n\tworst       time.Duration\n\tlast        time.Duration\n\tsuccesses   int\n\tfailures    int\n}\n\nfunc (r tableRow) merge(other tableRow) tableRow {\n\tr.count += other.count\n\tif other.best.Nanoseconds() < r.best.Nanoseconds() {\n\t\tr.best = other.best\n\t}\n\tif other.worst.Nanoseconds() > r.worst.Nanoseconds() {\n\t\tr.worst = other.worst\n\t}\n\tr.last = other.last\n\tr.successes += other.successes\n\tr.failures += other.failures\n\treturn r\n}\n\ntype column int\n\nconst (\n\tsourceColumn column = iota\n\tdestinationColumn\n\tmethodColumn\n\tpathColumn\n\trouteColumn\n\tcountColumn\n\tbestColumn\n\tworstColumn\n\tlastColumn\n\tsuccessRateColumn\n\n\tcolumnCount\n)\n\ntype topTable struct {\n\tcolumns [columnCount]tableColumn\n\trows    []tableRow\n}\n\nfunc newTopTable() *topTable {\n\ttable := topTable{}\n\n\ttable.columns[sourceColumn] =\n\t\ttableColumn{\n\t\t\theader:   \"Source\",\n\t\t\twidth:    23,\n\t\t\tkey:      true,\n\t\t\tdisplay:  true,\n\t\t\tflexible: true,\n\t\t\tvalue: func(r tableRow) string {\n\t\t\t\treturn r.source\n\t\t\t},\n\t\t}\n\n\ttable.columns[destinationColumn] =\n\t\ttableColumn{\n\t\t\theader:   \"Destination\",\n\t\t\twidth:    23,\n\t\t\tkey:      true,\n\t\t\tdisplay:  true,\n\t\t\tflexible: true,\n\t\t\tvalue: func(r tableRow) string {\n\t\t\t\treturn r.destination\n\t\t\t},\n\t\t}\n\n\ttable.columns[methodColumn] =\n\t\ttableColumn{\n\t\t\theader:   \"Method\",\n\t\t\twidth:    10,\n\t\t\tkey:      true,\n\t\t\tdisplay:  true,\n\t\t\tflexible: false,\n\t\t\tvalue: func(r tableRow) string {\n\t\t\t\treturn r.method\n\t\t\t},\n\t\t}\n\n\ttable.columns[pathColumn] =\n\t\ttableColumn{\n\t\t\theader:   \"Path\",\n\t\t\twidth:    37,\n\t\t\tkey:      true,\n\t\t\tdisplay:  true,\n\t\t\tflexible: true,\n\t\t\tvalue: func(r tableRow) string {\n\t\t\t\treturn r.path\n\t\t\t},\n\t\t}\n\n\ttable.columns[routeColumn] =\n\t\ttableColumn{\n\t\t\theader:   \"Route\",\n\t\t\twidth:    47,\n\t\t\tkey:      false,\n\t\t\tdisplay:  false,\n\t\t\tflexible: true,\n\t\t\tvalue: func(r tableRow) string {\n\t\t\t\treturn r.route\n\t\t\t},\n\t\t}\n\n\ttable.columns[countColumn] =\n\t\ttableColumn{\n\t\t\theader:     \"Count\",\n\t\t\twidth:      6,\n\t\t\tkey:        false,\n\t\t\tdisplay:    true,\n\t\t\tflexible:   false,\n\t\t\trightAlign: true,\n\t\t\tvalue: func(r tableRow) string {\n\t\t\t\treturn strconv.Itoa(r.count)\n\t\t\t},\n\t\t}\n\n\ttable.columns[bestColumn] =\n\t\ttableColumn{\n\t\t\theader:     \"Best\",\n\t\t\twidth:      6,\n\t\t\tkey:        false,\n\t\t\tdisplay:    true,\n\t\t\tflexible:   false,\n\t\t\trightAlign: true,\n\t\t\tvalue: func(r tableRow) string {\n\t\t\t\treturn formatDuration(r.best)\n\t\t\t},\n\t\t}\n\n\ttable.columns[worstColumn] =\n\t\ttableColumn{\n\t\t\theader:     \"Worst\",\n\t\t\twidth:      6,\n\t\t\tkey:        false,\n\t\t\tdisplay:    true,\n\t\t\tflexible:   false,\n\t\t\trightAlign: true,\n\t\t\tvalue: func(r tableRow) string {\n\t\t\t\treturn formatDuration(r.worst)\n\t\t\t},\n\t\t}\n\n\ttable.columns[lastColumn] =\n\t\ttableColumn{\n\t\t\theader:     \"Last\",\n\t\t\twidth:      6,\n\t\t\tkey:        false,\n\t\t\tdisplay:    true,\n\t\t\tflexible:   false,\n\t\t\trightAlign: true,\n\t\t\tvalue: func(r tableRow) string {\n\t\t\t\treturn formatDuration(r.last)\n\t\t\t},\n\t\t}\n\n\ttable.columns[successRateColumn] =\n\t\ttableColumn{\n\t\t\theader:     \"Success Rate\",\n\t\t\twidth:      12,\n\t\t\tkey:        false,\n\t\t\tdisplay:    true,\n\t\t\tflexible:   false,\n\t\t\trightAlign: true,\n\t\t\tvalue: func(r tableRow) string {\n\t\t\t\treturn fmt.Sprintf(\"%.2f%%\", 100.0*float32(r.successes)/float32(r.successes+r.failures))\n\t\t\t},\n\t\t}\n\n\treturn &table\n}\n\nconst (\n\theaderHeight  = 4\n\tcolumnSpacing = 2\n\txOffset       = 5\n)\n\nfunc newTopOptions() *topOptions {\n\treturn &topOptions{\n\t\ttoResource:    \"\",\n\t\ttoNamespace:   \"\",\n\t\tmaxRps:        maxRps,\n\t\tscheme:        \"\",\n\t\tmethod:        \"\",\n\t\tauthority:     \"\",\n\t\tpath:          \"\",\n\t\thideSources:   false,\n\t\troutes:        false,\n\t\tlabelSelector: \"\",\n\t}\n}\n\n// NewCmdTop creates a new cobra command `top` for top functionality\nfunc NewCmdTop() *cobra.Command {\n\toptions := newTopOptions()\n\n\ttable := newTopTable()\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"top [flags] (RESOURCE)\",\n\t\tShort: \"Display sorted information about live traffic\",\n\t\tLong: `Display sorted information about live traffic.\n\n  The RESOURCE argument specifies the target resource(s) to view traffic for:\n  (TYPE [NAME] | TYPE/NAME)\n\n  Examples:\n  * cronjob/my-cronjob\n  * deploy\n  * deploy/my-deploy\n  * deploy my-deploy\n  * ds/my-daemonset\n  * job/my-job\n  * ns/my-ns\n  * rs\n  * rs/my-replicaset\n  * sts\n  * sts/my-statefulset\n\n  Valid resource types include:\n  * cronjobs\n  * daemonsets\n  * deployments\n  * jobs\n  * namespaces\n  * pods\n  * replicasets\n  * replicationcontrollers\n  * statefulsets\n  * services (only supported as a --to resource)`,\n\t\tExample: `  # display traffic for the web deployment in the default namespace\n  linkerd viz top deploy/web\n\n  # display traffic for the web-dlbvj pod in the default namespace\n  linkerd viz top pod/web-dlbvj`,\n\t\tArgs: cobra.RangeArgs(1, 2),\n\t\tValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\t\t// This command requires at most two arguments if we already have\n\t\t\t// two after requesting autocompletion i.e. [tab][tab]\n\t\t\t// skip running validArgsFunction\n\t\t\tif len(args) > 1 {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\tcc := k8s.NewCommandCompletion(k8sAPI, options.namespace)\n\n\t\t\tresults, err := cc.Complete(args, toComplete)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t\t}\n\n\t\t\treturn results, cobra.ShellCompDirectiveDefault\n\t\t},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif options.namespace == \"\" {\n\t\t\t\toptions.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)\n\t\t\t}\n\n\t\t\tapi.CheckClientOrExit(hc.VizOptions{\n\t\t\t\tOptions: &healthcheck.Options{\n\t\t\t\t\tControlPlaneNamespace: controlPlaneNamespace,\n\t\t\t\t\tKubeConfig:            kubeconfigPath,\n\t\t\t\t\tImpersonate:           impersonate,\n\t\t\t\t\tImpersonateGroup:      impersonateGroup,\n\t\t\t\t\tKubeContext:           kubeContext,\n\t\t\t\t\tAPIAddr:               apiAddr,\n\t\t\t\t},\n\t\t\t\tVizNamespaceOverride: vizNamespace,\n\t\t\t})\n\n\t\t\trequestParams := pkg.TapRequestParams{\n\t\t\t\tResource:      strings.Join(args, \"/\"),\n\t\t\t\tNamespace:     options.namespace,\n\t\t\t\tToResource:    options.toResource,\n\t\t\t\tToNamespace:   options.toNamespace,\n\t\t\t\tMaxRps:        options.maxRps,\n\t\t\t\tScheme:        options.scheme,\n\t\t\t\tMethod:        options.method,\n\t\t\t\tAuthority:     options.authority,\n\t\t\t\tPath:          options.path,\n\t\t\t\tLabelSelector: options.labelSelector,\n\t\t\t}\n\n\t\t\tif options.hideSources {\n\t\t\t\ttable.columns[sourceColumn].key = false\n\t\t\t\ttable.columns[sourceColumn].display = false\n\t\t\t}\n\n\t\t\tif options.routes {\n\t\t\t\ttable.columns[methodColumn].key = false\n\t\t\t\ttable.columns[methodColumn].display = false\n\t\t\t\ttable.columns[pathColumn].key = false\n\t\t\t\ttable.columns[pathColumn].display = false\n\t\t\t\ttable.columns[routeColumn].key = true\n\t\t\t\ttable.columns[routeColumn].display = true\n\t\t\t}\n\n\t\t\treq, err := pkg.BuildTapByResourceRequest(requestParams)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\treturn getTrafficByResourceFromAPI(cmd.Context(), k8sAPI, req, table)\n\t\t},\n\t}\n\n\tcmd.PersistentFlags().StringVarP(&options.namespace, \"namespace\", \"n\", options.namespace,\n\t\t\"Namespace of the specified resource\")\n\tcmd.PersistentFlags().StringVar(&options.toResource, \"to\", options.toResource,\n\t\t\"Display requests to this resource\")\n\tcmd.PersistentFlags().StringVar(&options.toNamespace, \"to-namespace\", options.toNamespace,\n\t\t\"Sets the namespace used to lookup the \\\"--to\\\" resource; by default the current \\\"--namespace\\\" is used\")\n\tcmd.PersistentFlags().Float32Var(&options.maxRps, \"max-rps\", options.maxRps,\n\t\t\"Maximum requests per second to tap.\")\n\tcmd.PersistentFlags().StringVar(&options.scheme, \"scheme\", options.scheme,\n\t\t\"Display requests with this scheme\")\n\tcmd.PersistentFlags().StringVar(&options.method, \"method\", options.method,\n\t\t\"Display requests with this HTTP method\")\n\tcmd.PersistentFlags().StringVar(&options.authority, \"authority\", options.authority,\n\t\t\"Display requests with this :authority\")\n\tcmd.PersistentFlags().StringVar(&options.path, \"path\", options.path,\n\t\t\"Display requests with paths that start with this prefix\")\n\tcmd.PersistentFlags().BoolVar(&options.hideSources, \"hide-sources\", options.hideSources, \"Hide the source column\")\n\tcmd.PersistentFlags().BoolVar(&options.routes, \"routes\", options.routes, \"Display data per route instead of per path\")\n\tcmd.PersistentFlags().StringVarP(&options.labelSelector, \"selector\", \"l\", options.labelSelector, \"Selector (label query) to filter on, supports '=', '==', and '!='\")\n\n\tpkgcmd.ConfigureNamespaceFlagCompletion(\n\t\tcmd, []string{\"namespace\", \"to-namespace\"},\n\t\tkubeconfigPath, impersonate, impersonateGroup, kubeContext)\n\treturn cmd\n}\n\nfunc getTrafficByResourceFromAPI(ctx context.Context, k8sAPI *k8s.KubernetesAPI, req *tapPb.TapByResourceRequest, table *topTable) error {\n\treader, body, err := pkg.Reader(ctx, k8sAPI, req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer body.Close()\n\n\terr = termbox.Init()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer termbox.Close()\n\n\t// for event processing:\n\t// reader ->\n\t//   recvEvents() ->\n\t//     eventCh ->\n\t//       processEvents() ->\n\t//         requestCh ->\n\t//           renderTable()\n\teventCh := make(chan *tapPb.TapEvent)\n\trequestCh := make(chan topRequest, 100)\n\n\t// for closing:\n\t// recvEvents() || pollInput() ->\n\t//   closing ->\n\t//     done ->\n\t//       processEvents() && renderTable()\n\tclosing := make(chan struct{}, 1)\n\tdone := make(chan struct{})\n\thorizontalScroll := make(chan int)\n\n\tgo pollInput(done, horizontalScroll)\n\tgo recvEvents(reader, eventCh, closing)\n\tgo processEvents(eventCh, requestCh, done)\n\n\tgo func() {\n\t\t<-closing\n\t}()\n\n\trenderTable(table, requestCh, done, horizontalScroll)\n\n\treturn nil\n}\n\nfunc recvEvents(tapByteStream *bufio.Reader, eventCh chan<- *tapPb.TapEvent, closing chan<- struct{}) {\n\tfor {\n\t\tevent := &tapPb.TapEvent{}\n\t\terr := protohttp.FromByteStreamToProtocolBuffers(tapByteStream, event)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\tfmt.Println(\"Tap stream terminated\")\n\t\t\t} else if !strings.HasSuffix(err.Error(), pkg.ErrClosedResponseBody) {\n\t\t\t\tfmt.Println(err.Error())\n\t\t\t}\n\n\t\t\tclosing <- struct{}{}\n\t\t\treturn\n\t\t}\n\n\t\teventCh <- event\n\t}\n}\n\nfunc processEvents(eventCh <-chan *tapPb.TapEvent, requestCh chan<- topRequest, done <-chan struct{}) {\n\toutstandingRequests := make(map[topRequestID]topRequest)\n\n\tfor {\n\t\tselect {\n\t\tcase <-done:\n\t\t\treturn\n\t\tcase event := <-eventCh:\n\t\t\tid := topRequestID{\n\t\t\t\tsrc: addr.PublicAddressToString(event.GetSource()),\n\t\t\t\tdst: addr.PublicAddressToString(event.GetDestination()),\n\t\t\t}\n\t\t\tswitch ev := event.GetHttp().GetEvent().(type) {\n\t\t\tcase *tapPb.TapEvent_Http_RequestInit_:\n\t\t\t\tid.stream = ev.RequestInit.GetId().Stream\n\t\t\t\toutstandingRequests[id] = topRequest{\n\t\t\t\t\tevent:   event,\n\t\t\t\t\treqInit: ev.RequestInit,\n\t\t\t\t}\n\n\t\t\tcase *tapPb.TapEvent_Http_ResponseInit_:\n\t\t\t\tid.stream = ev.ResponseInit.GetId().Stream\n\t\t\t\tif req, ok := outstandingRequests[id]; ok {\n\t\t\t\t\treq.rspInit = ev.ResponseInit\n\t\t\t\t\toutstandingRequests[id] = req\n\t\t\t\t} else {\n\t\t\t\t\tlog.Warnf(\"Got ResponseInit for unknown stream: %s\", id)\n\t\t\t\t}\n\n\t\t\tcase *tapPb.TapEvent_Http_ResponseEnd_:\n\t\t\t\tid.stream = ev.ResponseEnd.GetId().Stream\n\t\t\t\tif req, ok := outstandingRequests[id]; ok {\n\t\t\t\t\treq.rspEnd = ev.ResponseEnd\n\t\t\t\t\trequestCh <- req\n\t\t\t\t} else {\n\t\t\t\t\tlog.Warnf(\"Got ResponseEnd for unknown stream: %s\", id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc pollInput(done chan<- struct{}, horizontalScroll chan int) {\n\tfor {\n\t\tswitch ev := termbox.PollEvent(); ev.Type {\n\t\tcase termbox.EventKey:\n\t\t\tif ev.Ch == 'q' || ev.Key == termbox.KeyCtrlC {\n\t\t\t\tclose(done)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif ev.Ch == 'a' || ev.Key == termbox.KeyArrowLeft {\n\t\t\t\thorizontalScroll <- xOffset\n\t\t\t}\n\t\t\tif ev.Ch == 'd' || ev.Key == termbox.KeyArrowRight {\n\t\t\t\thorizontalScroll <- -xOffset\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc renderTable(table *topTable, requestCh <-chan topRequest, done <-chan struct{}, horizontalScroll chan int) {\n\tscrollpos := 0\n\tticker := time.NewTicker(100 * time.Millisecond)\n\twidth, _ := termbox.Size()\n\ttablewidth := table.tableWidthCalc()\n\n\tfor {\n\t\tselect {\n\t\tcase <-done:\n\t\t\treturn\n\t\tcase req := <-requestCh:\n\t\t\ttable.insert(req)\n\t\tcase <-ticker.C:\n\t\t\ttermbox.Clear(termbox.ColorDefault, termbox.ColorDefault)\n\t\t\twidth, _ = termbox.Size()\n\t\t\ttable.adjustColumnWidths()\n\t\t\ttablewidth = table.tableWidthCalc()\n\t\t\ttable.renderHeaders(scrollpos)\n\t\t\ttable.renderBody(scrollpos)\n\t\t\ttermbox.Flush()\n\t\tcase offset := <-horizontalScroll:\n\t\t\tif (offset > 0 && scrollpos < 0) || (offset < 0 && scrollpos > (width-tablewidth)) {\n\t\t\t\tscrollpos += offset\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc newRow(req topRequest) (tableRow, error) {\n\tpath := req.reqInit.GetPath()\n\troute := req.event.GetRouteMeta().GetLabels()[\"route\"]\n\tif route == \"\" {\n\t\troute = metricsAPI.DefaultRouteName\n\t}\n\n\tsource := stripPort(addr.PublicAddressToString(req.event.GetSource()))\n\tif pod := req.event.SourceMeta.Labels[\"pod\"]; pod != \"\" {\n\t\tsource = pod\n\t}\n\tdestination := stripPort(addr.PublicAddressToString(req.event.GetDestination()))\n\tif pod := req.event.DestinationMeta.Labels[\"pod\"]; pod != \"\" {\n\t\tdestination = pod\n\t}\n\n\terr := req.rspEnd.GetSinceRequestInit().CheckValid()\n\tif err != nil {\n\t\treturn tableRow{}, fmt.Errorf(\"error parsing duration %v: %w\", req.rspEnd.GetSinceRequestInit(), err)\n\t}\n\tlatency := req.rspEnd.GetSinceRequestInit().AsDuration()\n\n\t// TODO: Once tap events have a classification field, we should use that field\n\t// instead of determining success here.\n\tsuccess := req.rspInit.GetHttpStatus() < 500\n\tif success {\n\t\tswitch eos := req.rspEnd.GetEos().GetEnd().(type) {\n\t\tcase *metricsPb.Eos_GrpcStatusCode:\n\t\t\tswitch codes.Code(eos.GrpcStatusCode) {\n\t\t\tcase codes.Unknown,\n\t\t\t\tcodes.DeadlineExceeded,\n\t\t\t\tcodes.Internal,\n\t\t\t\tcodes.Unavailable,\n\t\t\t\tcodes.DataLoss:\n\t\t\t\tsuccess = false\n\t\t\tdefault:\n\t\t\t\tsuccess = true\n\t\t\t}\n\n\t\tcase *metricsPb.Eos_ResetErrorCode:\n\t\t\tsuccess = false\n\t\t}\n\t}\n\n\tsuccesses := 0\n\tfailures := 0\n\tif success {\n\t\tsuccesses = 1\n\t} else {\n\t\tfailures = 1\n\t}\n\n\treturn tableRow{\n\t\tpath:        path,\n\t\tmethod:      vizutil.HTTPMethodToString(req.reqInit.GetMethod()),\n\t\troute:       route,\n\t\tsource:      source,\n\t\tdestination: destination,\n\t\tbest:        latency,\n\t\tworst:       latency,\n\t\tlast:        latency,\n\t\tcount:       1,\n\t\tsuccesses:   successes,\n\t\tfailures:    failures,\n\t}, nil\n}\n\nfunc (t *topTable) insert(req topRequest) {\n\tinsert, err := newRow(req)\n\tif err != nil {\n\t\tlog.Error(err.Error())\n\t\treturn\n\t}\n\n\tfound := false\n\t// Search for a matching row\n\tfor i, row := range t.rows {\n\t\tmatch := true\n\t\t// If the rows have equal values in all of the key columns, merge them.\n\t\tfor _, col := range t.columns {\n\t\t\tif col.key {\n\t\t\t\tif col.value(row) != col.value(insert) {\n\t\t\t\t\tmatch = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif match {\n\t\t\tfound = true\n\t\t\tt.rows[i] = t.rows[i].merge(insert)\n\t\t\tbreak\n\t\t}\n\t}\n\tif !found {\n\t\tt.rows = append(t.rows, insert)\n\t}\n}\n\nfunc stripPort(address string) string {\n\treturn strings.Split(address, \":\")[0]\n}\n\nfunc (t *topTable) renderHeaders(scrollpos int) {\n\ttbprint(0, 0, \"(press q to quit)\")\n\ttbprint(0, 1, \"(press a/LeftArrowKey to scroll left, d/RightArrowKey to scroll right)\")\n\tx := scrollpos\n\tfor _, col := range t.columns {\n\t\tif !col.display {\n\t\t\tcontinue\n\t\t}\n\t\tpadding := 0\n\t\tif col.rightAlign {\n\t\t\tpadding = col.width - runewidth.StringWidth(col.header)\n\t\t}\n\t\ttbprintBold(x+padding, headerHeight-1, col.header)\n\t\tx += col.width + columnSpacing\n\t}\n}\n\nfunc (t *topTable) tableWidthCalc() int {\n\ttablewidth := 0\n\tfor i := range t.columns {\n\t\ttablewidth = tablewidth + t.columns[i].width + columnSpacing\n\t}\n\treturn tablewidth - columnSpacing\n}\n\nfunc (t *topTable) adjustColumnWidths() {\n\tfor i, col := range t.columns {\n\t\tif !col.flexible {\n\t\t\tcontinue\n\t\t}\n\t\tt.columns[i].width = runewidth.StringWidth(col.header)\n\t\tfor _, row := range t.rows {\n\t\t\tcellWidth := runewidth.StringWidth(col.value(row))\n\t\t\tif cellWidth > t.columns[i].width {\n\t\t\t\tt.columns[i].width = cellWidth\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (t *topTable) renderBody(scrollpos int) {\n\tsort.SliceStable(t.rows, func(i, j int) bool {\n\t\treturn t.rows[i].count > t.rows[j].count\n\t})\n\n\tfor i, row := range t.rows {\n\t\tx := scrollpos\n\n\t\tfor _, col := range t.columns {\n\t\t\tif !col.display {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvalue := col.value(row)\n\t\t\tpadding := 0\n\t\t\tif col.rightAlign {\n\t\t\t\tpadding = col.width - runewidth.StringWidth(value)\n\t\t\t}\n\t\t\ttbprint(x+padding, i+headerHeight, value)\n\t\t\tx += col.width + columnSpacing\n\t\t}\n\t}\n}\n\nfunc tbprint(x, y int, msg string) {\n\tfor _, c := range msg {\n\t\ttermbox.SetCell(x, y, c, termbox.ColorDefault, termbox.ColorDefault)\n\t\tx += runewidth.RuneWidth(c)\n\t}\n}\n\nfunc tbprintBold(x, y int, msg string) {\n\tfor _, c := range msg {\n\t\ttermbox.SetCell(x, y, c, termbox.AttrBold, termbox.ColorDefault)\n\t\tx += runewidth.RuneWidth(c)\n\t}\n}\n\nfunc formatDuration(d time.Duration) string {\n\tif d < time.Millisecond {\n\t\treturn d.Round(time.Microsecond).String()\n\t}\n\tif d < time.Second {\n\t\treturn d.Round(time.Millisecond).String()\n\t}\n\treturn d.Round(time.Second).String()\n}\n"
  },
  {
    "path": "viz/cmd/uninstall.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tpkgCmd \"github.com/linkerd/linkerd2/pkg/cmd\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s/resource\"\n\t\"github.com/spf13/cobra\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc newCmdUninstall() *cobra.Command {\n\tvar output string\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"uninstall\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Output Kubernetes resources to uninstall the linkerd-viz extension\",\n\t\tLong: `Output Kubernetes resources to uninstall the linkerd-viz extension.\n\nThis command provides all Kubernetes namespace-scoped and cluster-scoped resources (e.g services, deployments, RBACs, etc.) necessary to uninstall the Linkerd-viz extension.`,\n\t\tExample: `linkerd viz uninstall | kubectl delete -f -`,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\terr := uninstallRunE(cmd.Context(), output)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintln(os.Stderr, err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tcmd.PersistentFlags().StringVarP(&output, \"output\", \"o\", \"yaml\", \"Output format. One of: json|yaml\")\n\n\treturn cmd\n}\n\nfunc uninstallRunE(ctx context.Context, format string) error {\n\tk8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tselector, err := pkgCmd.GetLabelSelector(k8s.LinkerdExtensionLabel, ExtensionName, LegacyExtensionName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// / `Uninstall` deletes cluster-scoped resources created by the extension\n\t// (including the extension's namespace).\n\tif err := pkgCmd.Uninstall(ctx, k8sAPI, selector, format); err != nil {\n\t\treturn err\n\t}\n\n\t// delete any HTTPRoute, AuthorizationPolicy, and Server resources created\n\t// by the viz extension in any namespace.\n\t//\n\t// note that these are not deleted by the `Uninstall` call above, because\n\t// they are namespaced resources.\n\tpolicy := k8sAPI.L5dCrdClient.PolicyV1alpha1()\n\tauthzs, err := policy.AuthorizationPolicies(v1.NamespaceAll).List(ctx, metav1.ListOptions{LabelSelector: selector})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, authz := range authzs.Items {\n\t\tif err := deleteResource(authz.TypeMeta, authz.ObjectMeta, format); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\trts, err := policy.HTTPRoutes(v1.NamespaceAll).List(ctx, metav1.ListOptions{LabelSelector: selector})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, rt := range rts.Items {\n\t\tif err := deleteResource(rt.TypeMeta, rt.ObjectMeta, format); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tsrvs, err := k8sAPI.L5dCrdClient.ServerV1beta3().Servers(v1.NamespaceAll).List(ctx, metav1.ListOptions{LabelSelector: selector})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, srv := range srvs.Items {\n\t\tif err := deleteResource(srv.TypeMeta, srv.ObjectMeta, format); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc deleteResource(ty metav1.TypeMeta, meta metav1.ObjectMeta, format string) error {\n\tr := resource.NewNamespaced(ty.APIVersion, ty.Kind, meta.Name, meta.Namespace)\n\tif format == pkgCmd.JsonOutput {\n\t\tif err := r.RenderResourceJSON(os.Stdout); err != nil {\n\t\t\treturn fmt.Errorf(\"error rendering Kubernetes resource: %w\", err)\n\t\t}\n\t\treturn nil\n\t} else if format == pkgCmd.YamlOutput {\n\t\tif err := r.RenderResource(os.Stdout); err != nil {\n\t\t\treturn fmt.Errorf(\"error rendering Kubernetes resource: %w\", err)\n\t\t}\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unsupported output format: %s\", format)\n}\n"
  },
  {
    "path": "viz/main.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\n\t\"github.com/linkerd/linkerd2/viz/cmd\"\n)\n\nfunc main() {\n\tif err := cmd.NewCmdViz().Execute(); err != nil {\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "viz/metrics-api/Dockerfile",
    "content": "ARG BUILDPLATFORM=linux/amd64\n\n# Precompile key slow-to-build dependencies\nFROM --platform=$BUILDPLATFORM golang:1.25-alpine AS go-deps\nWORKDIR /linkerd-build\nCOPY go.mod go.sum ./\nCOPY bin/install-deps bin/\nRUN go mod download\nARG TARGETARCH\nRUN ./bin/install-deps $TARGETARCH\n\n## compile metrics-apiservice\nFROM go-deps AS golang\nWORKDIR /linkerd-build\nCOPY pkg pkg\nCOPY controller controller\nCOPY viz/metrics-api viz/metrics-api\nCOPY viz/pkg viz/pkg\n\nARG TARGETARCH\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -o /out/metrics-api -tags prod -mod=readonly -ldflags \"-s -w\" ./viz/metrics-api/cmd\n\n## package runtime\nFROM scratch\nLABEL org.opencontainers.image.source=https://github.com/linkerd/linkerd2\nCOPY LICENSE /linkerd/LICENSE\nCOPY --from=golang /out/metrics-api /metrics-api\nCOPY --from=golang /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/\n\nENTRYPOINT [\"/metrics-api\"]\n"
  },
  {
    "path": "viz/metrics-api/authz.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/prometheus\"\n\t\"github.com/prometheus/common/model\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nfunc (s *grpcServer) Authz(ctx context.Context, req *pb.AuthzRequest) (*pb.AuthzResponse, error) {\n\n\t// check for well-formed request\n\tif req.GetResource() == nil {\n\t\treturn &pb.AuthzResponse{\n\t\t\tResponse: &pb.AuthzResponse_Error{\n\t\t\t\tError: &pb.ResourceError{\n\t\t\t\t\tError: \"AuthzRequest missing resource\",\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\n\tlabels := prometheus.QueryLabels(req.GetResource())\n\treqLabels := labels.Merge(model.LabelSet{\n\t\t\"direction\": model.LabelValue(\"inbound\"),\n\t})\n\n\tgroupBy := model.LabelNames{\n\t\tprometheus.RouteKindLabel, prometheus.RouteNameLabel, prometheus.AuthorizationKindLabel, prometheus.AuthorizationNameLabel, prometheus.ServerKindLabel, prometheus.ServerNameLabel,\n\t}\n\tpromQueries := make(map[promType]string)\n\tpromQueries[promRequests] = fmt.Sprintf(reqQuery, reqLabels, req.TimeWindow, groupBy.String())\n\t// Use `labels` as direction isn't present with authorization metrics\n\tpromQueries[promAllowedRequests] = fmt.Sprintf(httpAuthzAllowQuery, labels, req.TimeWindow, groupBy.String())\n\tpromQueries[promDeniedRequests] = fmt.Sprintf(httpAuthzDenyQuery, labels, req.TimeWindow, groupBy.String())\n\tquantileQueries := generateQuantileQueries(latencyQuantileQuery, reqLabels.String(), req.TimeWindow, groupBy.String())\n\tresults, err := s.getPrometheusMetrics(ctx, promQueries, quantileQueries)\n\tif err != nil {\n\t\treturn &pb.AuthzResponse{\n\t\t\tResponse: &pb.AuthzResponse_Error{\n\t\t\t\tError: &pb.ResourceError{\n\t\t\t\t\tError: err.Error(),\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\n\ttype rowKey struct {\n\t\trouteName  string\n\t\trouteKind  string\n\t\tserverName string\n\t\tserverKind string\n\t\tauthzName  string\n\t\tauthzKind  string\n\t}\n\trows := map[rowKey]*pb.StatTable_PodGroup_Row{}\n\n\tfor _, result := range results {\n\t\tfor _, sample := range result.vec {\n\t\t\tkey := rowKey{\n\t\t\t\trouteName:  string(sample.Metric[prometheus.RouteNameLabel]),\n\t\t\t\trouteKind:  string(sample.Metric[prometheus.RouteKindLabel]),\n\t\t\t\tserverName: string(sample.Metric[prometheus.ServerNameLabel]),\n\t\t\t\tserverKind: string(sample.Metric[prometheus.ServerKindLabel]),\n\t\t\t\tauthzName:  string(sample.Metric[prometheus.AuthorizationNameLabel]),\n\t\t\t\tauthzKind:  string(sample.Metric[prometheus.AuthorizationKindLabel]),\n\t\t\t}\n\t\t\t// Get the row if it exists or initialize an empty one\n\t\t\trow := rows[key]\n\t\t\tif row == nil {\n\t\t\t\trow = &pb.StatTable_PodGroup_Row{\n\t\t\t\t\tResource: req.Resource,\n\t\t\t\t\tStats:    &pb.BasicStats{},\n\t\t\t\t\tSrvStats: &pb.ServerStats{\n\t\t\t\t\t\tSrv: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: string(sample.Metric[prometheus.NamespaceLabel]),\n\t\t\t\t\t\t\tType:      key.serverKind,\n\t\t\t\t\t\t\tName:      key.serverName,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tRoute: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: string(sample.Metric[prometheus.NamespaceLabel]),\n\t\t\t\t\t\t\tType:      key.routeKind,\n\t\t\t\t\t\t\tName:      key.routeName,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tAuthz: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: string(sample.Metric[prometheus.NamespaceLabel]),\n\t\t\t\t\t\t\tType:      key.authzKind,\n\t\t\t\t\t\t\tName:      key.authzName,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\trows[key] = row\n\t\t\t}\n\n\t\t\tvalue := extractSampleValue(sample)\n\t\t\tswitch result.prom {\n\t\t\tcase promRequests:\n\t\t\t\tswitch string(sample.Metric[model.LabelName(\"classification\")]) {\n\t\t\t\tcase success:\n\t\t\t\t\trow.Stats.SuccessCount += value\n\t\t\t\tcase failure:\n\t\t\t\t\trow.Stats.FailureCount += value\n\t\t\t\t}\n\t\t\tcase promLatencyP50:\n\t\t\t\trow.Stats.LatencyMsP50 = value\n\t\t\tcase promLatencyP95:\n\t\t\t\trow.Stats.LatencyMsP95 = value\n\t\t\tcase promLatencyP99:\n\t\t\t\trow.Stats.LatencyMsP99 = value\n\t\t\tcase promAllowedRequests:\n\t\t\t\trow.SrvStats.AllowedCount = value\n\t\t\tcase promDeniedRequests:\n\t\t\t\trow.SrvStats.DeniedCount = value\n\t\t\t}\n\t\t}\n\t}\n\n\ttable := []*pb.StatTable_PodGroup_Row{}\n\tfor _, row := range rows {\n\t\ttable = append(table, row)\n\t}\n\n\trsp := pb.AuthzResponse{\n\t\tResponse: &pb.AuthzResponse_Ok_{ // https://github.com/golang/protobuf/issues/205\n\t\t\tOk: &pb.AuthzResponse_Ok{\n\t\t\t\tStatTable: &pb.StatTable{\n\t\t\t\t\tTable: &pb.StatTable_PodGroup_{\n\t\t\t\t\t\tPodGroup: &pb.StatTable_PodGroup{\n\t\t\t\t\t\t\tRows: table,\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\tlog.Debugf(\"Sent response as %+v\\n\", table)\n\treturn &rsp, nil\n}\n"
  },
  {
    "path": "viz/metrics-api/client/client.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"go.opencensus.io/plugin/ocgrpc\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nconst (\n\tapiPort       = 8085\n\tapiDeployment = \"metrics-api\"\n)\n\n// NewInternalClient creates a new Viz API client intended to run inside a\n// Kubernetes cluster.\nfunc NewInternalClient(addr string) (pb.ApiClient, error) {\n\tconn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStatsHandler(&ocgrpc.ClientHandler{}))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pb.NewApiClient(conn), nil\n}\n\n// NewExternalClient creates a new Viz API client intended to run from\n// outside a Kubernetes cluster.\nfunc NewExternalClient(ctx context.Context, namespace string, kubeAPI *k8s.KubernetesAPI) (pb.ApiClient, error) {\n\tportforward, err := k8s.NewPortForward(\n\t\tctx,\n\t\tkubeAPI,\n\t\tnamespace,\n\t\tapiDeployment,\n\t\t\"localhost\",\n\t\t0,\n\t\tapiPort,\n\t\tfalse,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\taddr := portforward.AddressAndPort()\n\tif err = portforward.Init(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tconn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStatsHandler(&ocgrpc.ClientHandler{}))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pb.NewApiClient(conn), nil\n}\n"
  },
  {
    "path": "viz/metrics-api/cmd/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/admin\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/trace\"\n\tapi \"github.com/linkerd/linkerd2/viz/metrics-api\"\n\tpromApi \"github.com/prometheus/client_golang/api\"\n\tpromv1 \"github.com/prometheus/client_golang/api/prometheus/v1\"\n\t\"github.com/prometheus/common/config\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nfunc main() {\n\tcmd := flag.NewFlagSet(\"metrics-api\", flag.ExitOnError)\n\n\taddr := cmd.String(\"addr\", \":8085\", \"address to serve on\")\n\tkubeConfigPath := cmd.String(\"kubeconfig\", \"\", \"path to kube config\")\n\tprometheusURL := cmd.String(\"prometheus-url\", \"\", \"prometheus url\")\n\tprometheusUser := cmd.String(\"prometheus-user-file\", \"\", \"file containing username for prometheus basic auth\")\n\tprometheusPassword := cmd.String(\"prometheus-password-file\", \"\", \"file containing password for prometheus basic auth\")\n\tmetricsAddr := cmd.String(\"metrics-addr\", \":9995\", \"address to serve scrapable metrics on\")\n\tcontrollerNamespace := cmd.String(\"controller-namespace\", \"linkerd\", \"namespace in which Linkerd is installed\")\n\tignoredNamespaces := cmd.String(\"ignore-namespaces\", \"kube-system\", \"comma separated list of namespaces to not list pods from\")\n\tclusterDomain := cmd.String(\"cluster-domain\", \"cluster.local\", \"kubernetes cluster domain\")\n\tenablePprof := cmd.Bool(\"enable-pprof\", false, \"Enable pprof endpoints on the admin server\")\n\n\ttraceCollector := flags.AddTraceFlags(cmd)\n\n\tflags.ConfigureAndParse(cmd, os.Args[1:])\n\n\tready := false\n\tadminServer := admin.NewServer(*metricsAddr, *enablePprof, &ready)\n\n\tgo func() {\n\t\tlog.Infof(\"starting admin server on %s\", *metricsAddr)\n\t\tif err := adminServer.ListenAndServe(); err != nil {\n\t\t\tlog.Errorf(\"failed to start metrics API admin server: %s\", err)\n\t\t}\n\t}()\n\n\tctx := context.Background()\n\n\tstop := make(chan os.Signal, 1)\n\tsignal.Notify(stop, os.Interrupt, syscall.SIGTERM)\n\n\tk8sAPI, err := k8s.InitializeAPI(\n\t\tctx,\n\t\t*kubeConfigPath,\n\t\ttrue,\n\t\t\"local\",\n\t\tk8s.CJ, k8s.DS, k8s.Deploy, k8s.Job, k8s.NS, k8s.Pod, k8s.RC, k8s.RS, k8s.Svc, k8s.SS, k8s.SP,\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to initialize K8s API: %s\", err)\n\t}\n\n\tvar prometheusClient promApi.Client\n\tif *prometheusURL != \"\" {\n\t\tpromConfig := promApi.Config{Address: *prometheusURL}\n\t\tif *prometheusUser != \"\" && *prometheusPassword != \"\" {\n\t\t\tuser, err := os.ReadFile(*prometheusUser)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalf(\"failed to read file containing username for prometheus basic auth: %s\", err)\n\t\t\t}\n\t\t\tpassword, err := os.ReadFile(*prometheusPassword)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalf(\"failed to read file containing password for prometheus basic auth: %s\", err)\n\t\t\t}\n\t\t\tpromConfig.RoundTripper = config.NewBasicAuthRoundTripper(\n\t\t\t\tconfig.NewInlineSecret(string(user)),\n\t\t\t\tconfig.NewInlineSecret(string(password)),\n\t\t\t\tpromApi.DefaultRoundTripper,\n\t\t\t)\n\t\t} else if *prometheusUser != \"\" || *prometheusPassword != \"\" {\n\t\t\tlog.Fatal(\"both prometheus-user-file and prometheus-password-file must be set\")\n\t\t}\n\t\tprometheusClient, err = promApi.NewClient(promConfig)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err.Error())\n\t\t}\n\t}\n\n\tlog.Infof(\"prometheusClient: %#v\", prometheusClient)\n\tlog.Info(\"Using cluster domain: \", *clusterDomain)\n\n\tif *traceCollector != \"\" {\n\t\tif err := trace.InitializeTracing(\"linkerd-public-api\", *traceCollector); err != nil {\n\t\t\tlog.Warnf(\"failed to initialize tracing: %s\", err)\n\t\t}\n\t}\n\n\tvar promAPI promv1.API\n\tif prometheusClient != nil {\n\t\tpromAPI = promv1.NewAPI(prometheusClient)\n\t}\n\n\tserver := api.NewGrpcServer(\n\t\tpromAPI,\n\t\tk8sAPI,\n\t\t*controllerNamespace,\n\t\t*clusterDomain,\n\t\tstrings.Split(*ignoredNamespaces, \",\"),\n\t)\n\n\tk8sAPI.Sync(nil) // blocks until caches are synced\n\n\tlis, err := net.Listen(\"tcp\", *addr)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to listen on %s: %s\", *addr, err)\n\t}\n\tgo func() {\n\t\tlog.Infof(\"starting HTTP server on %+v\", *addr)\n\n\t\tif err := server.Serve(lis); err != nil {\n\t\t\tlog.Errorf(\"failed to start metrics API HTTP server: %s\", err)\n\t\t}\n\t}()\n\n\tready = true\n\n\t<-stop\n\n\tlog.Infof(\"shutting down HTTP server on %+v\", *addr)\n\tserver.GracefulStop()\n\tadminServer.Shutdown(ctx)\n}\n"
  },
  {
    "path": "viz/metrics-api/edges.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sort\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/prometheus\"\n\t\"github.com/prometheus/common/model\"\n\tlog \"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n)\n\nconst (\n\tedgesQuery = \"sum(%s%s) by (%s, dst_%s, pod, server_id, namespace, dst_namespace, no_tls_reason)\"\n)\n\nvar formatMsg = map[string]string{\n\t\"disabled\":                          \"Disabled\",\n\t\"loopback\":                          \"Loopback\",\n\t\"no_authority_in_http_request\":      \"No Authority In HTTP Request\",\n\t\"not_http\":                          \"Not HTTP\",\n\t\"not_provided_by_remote\":            \"Not Provided By Remote\",\n\t\"not_provided_by_service_discovery\": \"Not Provided By Service Discovery\",\n}\n\ntype edgeKey struct {\n\tsrc   string\n\tsrcNs string\n\tdst   string\n\tdstNs string\n}\n\nfunc (s *grpcServer) Edges(ctx context.Context, req *pb.EdgesRequest) (*pb.EdgesResponse, error) {\n\tlog.Debugf(\"Edges request: %+v\", req)\n\tif req.GetSelector().GetResource() == nil {\n\t\treturn edgesError(req, \"Edges request missing Selector Resource\"), nil\n\t}\n\n\tresourceType := prometheus.ResourceType(req.GetSelector().GetResource())\n\tdstResourceType := \"dst_\" + resourceType\n\tlabelsOutbound := promDirectionLabels(\"outbound\")\n\tlabelsOutboundStr := generateLabelStringWithExclusion(labelsOutbound, string(resourceType), string(dstResourceType))\n\tquery := fmt.Sprintf(edgesQuery, \"tcp_open_connections\", labelsOutboundStr, resourceType, resourceType)\n\n\tpromResult, err := s.queryProm(ctx, query)\n\tif err != nil {\n\t\treturn edgesError(req, err.Error()), nil\n\t}\n\n\tedgeMap := make(map[edgeKey]*pb.Edge)\n\n\tfor _, sample := range promResult {\n\t\tif sample.Value == 0.0 {\n\t\t\tcontinue\n\t\t}\n\t\tkey := edgeKey{\n\t\t\tsrc:   string(sample.Metric[resourceType]),\n\t\t\tsrcNs: string(sample.Metric[model.LabelName(\"namespace\")]),\n\t\t\tdst:   string(sample.Metric[dstResourceType]),\n\t\t\tdstNs: string(sample.Metric[model.LabelName(\"dst_namespace\")]),\n\t\t}\n\t\trequestedNs := req.GetSelector().GetResource().GetNamespace()\n\t\tif requestedNs != v1.NamespaceAll {\n\t\t\tif requestedNs != key.srcNs && requestedNs != key.dstNs {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif _, ok := edgeMap[key]; !ok {\n\n\t\t\tclientID, err := s.getPodIdentity(string(sample.Metric[model.LabelName(\"pod\")]), key.srcNs)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warnf(\"failed to get pod identity for %s: %v\", sample.Metric[model.LabelName(\"pod\")], err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tedgeMap[key] = &pb.Edge{\n\t\t\t\tSrc: &pb.Resource{\n\t\t\t\t\tNamespace: key.srcNs,\n\t\t\t\t\tName:      key.src,\n\t\t\t\t\tType:      string(resourceType),\n\t\t\t\t},\n\t\t\t\tDst: &pb.Resource{\n\t\t\t\t\tNamespace: key.dstNs,\n\t\t\t\t\tName:      key.dst,\n\t\t\t\t\tType:      string(resourceType),\n\t\t\t\t},\n\t\t\t\tServerId:      string(sample.Metric[model.LabelName(\"server_id\")]),\n\t\t\t\tClientId:      clientID,\n\t\t\t\tNoIdentityMsg: formatMsg[string(sample.Metric[model.LabelName(\"no_tls_reason\")])],\n\t\t\t}\n\t\t}\n\t}\n\n\tedges := []*pb.Edge{}\n\tfor _, edge := range edgeMap {\n\t\tedges = append(edges, edge)\n\t}\n\tedges = sortEdgeRows(edges)\n\n\treturn &pb.EdgesResponse{\n\t\tResponse: &pb.EdgesResponse_Ok_{\n\t\t\tOk: &pb.EdgesResponse_Ok{\n\t\t\t\tEdges: edges,\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nfunc edgesError(req *pb.EdgesRequest, message string) *pb.EdgesResponse {\n\treturn &pb.EdgesResponse{\n\t\tResponse: &pb.EdgesResponse_Error{\n\t\t\tError: &pb.ResourceError{\n\t\t\t\tResource: req.GetSelector().GetResource(),\n\t\t\t\tError:    message,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc (s *grpcServer) getPodIdentity(pod string, namespace string) (string, error) {\n\tpo, err := s.k8sAPI.Pod().Lister().Pods(namespace).Get(pod)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn k8s.PodIdentity(po)\n}\n\nfunc sortEdgeRows(rows []*pb.Edge) []*pb.Edge {\n\tsort.Slice(rows, func(i, j int) bool {\n\t\tkeyI := rows[i].GetSrc().GetNamespace() + rows[i].GetDst().GetNamespace() + rows[i].GetSrc().GetName() + rows[i].GetDst().GetName()\n\t\tkeyJ := rows[j].GetSrc().GetNamespace() + rows[j].GetDst().GetNamespace() + rows[j].GetSrc().GetName() + rows[j].GetDst().GetName()\n\t\treturn keyI < keyJ\n\t})\n\treturn rows\n}\n"
  },
  {
    "path": "viz/metrics-api/edges_test.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\tpkgK8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/prometheus\"\n\t\"github.com/prometheus/common/model\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nconst (\n\tserverIDLabel = model.LabelName(\"server_id\")\n\tresourceLabel = model.LabelName(\"deployment\")\n\tpodLabel      = model.LabelName(\"pod\")\n)\n\ntype edgesExpected struct {\n\texpectedStatRPC\n\treq              *pb.EdgesRequest  // the request we would like to test\n\texpectedResponse *pb.EdgesResponse // the edges response we expect\n}\n\nfunc genOutboundPromSample(resourceNamespace, resourceName, resourceNameDst, resourceNamespaceDst, serverID string) *model.Sample {\n\tdstResourceLabel := \"dst_\" + resourceLabel\n\n\treturn &model.Sample{\n\t\tMetric: model.Metric{\n\t\t\tresourceLabel:                model.LabelValue(resourceName),\n\t\t\tprometheus.NamespaceLabel:    model.LabelValue(resourceNamespace),\n\t\t\tprometheus.DstNamespaceLabel: model.LabelValue(resourceNamespaceDst),\n\t\t\tdstResourceLabel:             model.LabelValue(resourceNameDst),\n\t\t\tserverIDLabel:                model.LabelValue(serverID),\n\t\t\tpodLabel:                     model.LabelValue(resourceName + \"-0\"),\n\t\t},\n\t\tValue:     123,\n\t\tTimestamp: 456,\n\t}\n}\n\nfunc genPod(name, namespace, sa string) string {\n\treturn fmt.Sprintf(`apiVersion: v1\nkind: Pod\nmetadata:\n  name: %s\n  namespace: %s\nspec:\n  containers:\n  - name: linkerd-proxy\n    env:\n    - name: LINKERD2_PROXY_IDENTITY_LOCAL_NAME\n      value: $(_pod_sa).$(_pod_ns).serviceaccount.identity.linkerd.cluster.local\n  serviceAccountName: %s\nstatus:\n  phase: Running\n`, name, namespace, sa)\n}\n\nfunc testEdges(t *testing.T, expectations []edgesExpected) {\n\tfor _, exp := range expectations {\n\t\tmockProm, fakeGrpcServer, err := newMockGrpcServer(exp.expectedStatRPC)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error creating mock grpc server: %s\", err)\n\t\t}\n\n\t\trsp, err := fakeGrpcServer.Edges(context.TODO(), exp.req)\n\t\tif !errors.Is(err, exp.err) {\n\t\t\tt.Fatalf(\"Expected error: %s, Got: %s\", exp.err, err)\n\t\t}\n\n\t\terr = exp.verifyPromQueries(mockProm)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trspEdgeRows := rsp.GetOk().Edges\n\n\t\tif len(rspEdgeRows) != len(exp.expectedResponse.GetOk().Edges) {\n\t\t\tt.Fatalf(\n\t\t\t\t\"Expected [%d] edge rows, got [%d].\\nExpected:\\n%s\\nGot:\\n%s\",\n\t\t\t\tlen(exp.expectedResponse.GetOk().Edges),\n\t\t\t\tlen(rspEdgeRows),\n\t\t\t\texp.expectedResponse.GetOk().Edges,\n\t\t\t\trspEdgeRows,\n\t\t\t)\n\t\t}\n\n\t\tfor i, st := range rspEdgeRows {\n\t\t\texpected := exp.expectedResponse.GetOk().Edges[i]\n\t\t\tif !proto.Equal(st, expected) {\n\t\t\t\tt.Fatalf(\"Expected: %+v\\n Got: %+v\\n\", expected, st)\n\t\t\t}\n\t\t}\n\n\t\tif !proto.Equal(exp.expectedResponse.GetOk(), rsp.GetOk()) {\n\t\t\tt.Fatalf(\"Expected edgesOkResp: %+v\\n Got: %+v\", &exp.expectedResponse, rsp)\n\t\t}\n\t}\n}\n\nfunc TestEdges(t *testing.T) {\n\tmockPromResponse := model.Vector{\n\t\tgenOutboundPromSample(\"emojivoto\", \"web\", \"emoji\", \"emojivoto\", \"emoji.emojivoto.serviceaccount.identity.linkerd.cluster.local\"),\n\t\tgenOutboundPromSample(\"emojivoto\", \"web\", \"voting\", \"emojivoto\", \"voting.emojivoto.serviceaccount.identity.linkerd.cluster.local\"),\n\t\tgenOutboundPromSample(\"emojivoto\", \"vote-bot\", \"web\", \"emojivoto\", \"web.emojivoto.serviceaccount.identity.linkerd.cluster.local\"),\n\t\tgenOutboundPromSample(\"linkerd\", \"linkerd-identity\", \"linkerd-prometheus\", \"linkerd\", \"linkerd-prometheus.linkerd.serviceaccount.identity.linkerd.cluster.local\"),\n\t}\n\tpods := []string{\n\t\tgenPod(\"web-0\", \"emojivoto\", \"web\"),\n\t\tgenPod(\"vote-bot-0\", \"emojivoto\", \"default\"),\n\t\tgenPod(\"linkerd-identity-0\", \"linkerd\", \"linkerd-identity\"),\n\t}\n\n\tt.Run(\"Successfully returns edges for resource type Deployment and namespace emojivoto\", func(t *testing.T) {\n\t\texpectations := []edgesExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr:              nil,\n\t\t\t\t\tmockPromResponse: mockPromResponse,\n\t\t\t\t\tk8sConfigs:       pods,\n\t\t\t\t},\n\t\t\t\treq: &pb.EdgesRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Deployment,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenEdgesResponse(\"deployment\", \"emojivoto\"),\n\t\t\t}}\n\n\t\ttestEdges(t, expectations)\n\t})\n\n\tt.Run(\"Successfully returns edges for resource type Deployment and namespace linkerd\", func(t *testing.T) {\n\t\texpectations := []edgesExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr:              nil,\n\t\t\t\t\tmockPromResponse: mockPromResponse,\n\t\t\t\t\tk8sConfigs:       pods,\n\t\t\t\t},\n\t\t\t\treq: &pb.EdgesRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"linkerd\",\n\t\t\t\t\t\t\tType:      pkgK8s.Deployment,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenEdgesResponse(\"deployment\", \"linkerd\"),\n\t\t\t}}\n\n\t\ttestEdges(t, expectations)\n\t})\n\n\tt.Run(\"Successfully returns edges for resource type Deployment and all namespaces\", func(t *testing.T) {\n\t\texpectations := []edgesExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr:              nil,\n\t\t\t\t\tmockPromResponse: mockPromResponse,\n\t\t\t\t\tk8sConfigs:       pods,\n\t\t\t\t},\n\t\t\t\treq: &pb.EdgesRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tType: pkgK8s.Deployment,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenEdgesResponse(\"deployment\", \"all\"),\n\t\t\t}}\n\n\t\ttestEdges(t, expectations)\n\t})\n}\n"
  },
  {
    "path": "viz/metrics-api/gateways.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/prometheus\"\n\t\"github.com/prometheus/common/model\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nconst (\n\tgatewayAliveQuery           = \"sum(gateway_alive%s) by (%s)\"\n\tgatewayLatencyQuantileQuery = \"histogram_quantile(%s, sum(irate(gateway_probe_latency_ms_bucket%s[%s])) by (le, %s))\"\n)\n\nfunc (s *grpcServer) Gateways(ctx context.Context, req *pb.GatewaysRequest) (*pb.GatewaysResponse, error) {\n\tarray := []*pb.GatewaysTable_Row{}\n\tmetrics, err := s.getGatewaysMetrics(ctx, req, req.TimeWindow)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, v := range metrics {\n\t\tarray = append(array, v)\n\t}\n\treturn &pb.GatewaysResponse{\n\t\tResponse: &pb.GatewaysResponse_Ok_{\n\t\t\tOk: &pb.GatewaysResponse_Ok{\n\t\t\t\tGatewaysTable: &pb.GatewaysTable{\n\t\t\t\t\tRows: array,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nfunc buildGatewaysRequestLabels(req *pb.GatewaysRequest) (labels model.LabelSet, labelNames model.LabelNames) {\n\tlabels = model.LabelSet{}\n\n\tif req.GatewayNamespace != \"\" {\n\t\tlabels[prometheus.GatewayNamespaceLabel] = model.LabelValue(req.GatewayNamespace)\n\t}\n\n\tif req.RemoteClusterName != \"\" {\n\t\tlabels[prometheus.RemoteClusterNameLabel] = model.LabelValue(req.RemoteClusterName)\n\t}\n\n\tgroupBy := model.LabelNames{prometheus.GatewayNamespaceLabel, prometheus.RemoteClusterNameLabel, prometheus.GatewayNameLabel}\n\n\treturn labels, groupBy\n}\n\n// this function returns a map of target cluster to the number of services mirrored\n// from it\nfunc (s *grpcServer) getNumServicesMap(ctx context.Context) (map[string]uint64, error) {\n\n\tresults := make(map[string]uint64)\n\tselector := fmt.Sprintf(\"%s,!%s\", k8s.MirroredResourceLabel, k8s.MirroredGatewayLabel)\n\tservices, err := s.k8sAPI.Client.CoreV1().Services(corev1.NamespaceAll).List(ctx, metav1.ListOptions{LabelSelector: selector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, svc := range services.Items {\n\t\tclusterName := svc.Labels[k8s.RemoteClusterNameLabel]\n\t\tresults[clusterName]++\n\t}\n\n\treturn results, nil\n}\n\nfunc processPrometheusResult(results []promResult, numSvcMap map[string]uint64) map[string]*pb.GatewaysTable_Row {\n\n\trows := make(map[string]*pb.GatewaysTable_Row)\n\n\tfor _, result := range results {\n\t\tfor _, sample := range result.vec {\n\n\t\t\tclusterName := string(sample.Metric[prometheus.RemoteClusterNameLabel])\n\t\t\tnumPairedSvc := numSvcMap[clusterName]\n\n\t\t\taddRow := func() {\n\t\t\t\tif rows[clusterName] == nil {\n\t\t\t\t\trows[clusterName] = &pb.GatewaysTable_Row{}\n\t\t\t\t\trows[clusterName].ClusterName = clusterName\n\t\t\t\t\trows[clusterName].PairedServices = numPairedSvc\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvalue := extractSampleValue(sample)\n\n\t\t\tswitch result.prom {\n\t\t\tcase promGatewayAlive:\n\t\t\t\taddRow()\n\t\t\t\trows[clusterName].Alive = value > 0\n\t\t\tcase promLatencyP50:\n\t\t\t\taddRow()\n\t\t\t\trows[clusterName].LatencyMsP50 = value\n\t\t\tcase promLatencyP95:\n\t\t\t\taddRow()\n\t\t\t\trows[clusterName].LatencyMsP95 = value\n\t\t\tcase promLatencyP99:\n\t\t\t\taddRow()\n\t\t\t\trows[clusterName].LatencyMsP99 = value\n\t\t\t}\n\t\t}\n\t}\n\n\treturn rows\n}\n\nfunc (s *grpcServer) getGatewaysMetrics(ctx context.Context, req *pb.GatewaysRequest, timeWindow string) (map[string]*pb.GatewaysTable_Row, error) {\n\tlabels, groupBy := buildGatewaysRequestLabels(req)\n\n\tpromQueries := map[promType]string{\n\t\tpromGatewayAlive: fmt.Sprintf(gatewayAliveQuery, labels.String(), groupBy.String()),\n\t}\n\n\tquantileQueries := generateQuantileQueries(gatewayLatencyQuantileQuery, labels.String(), timeWindow, groupBy.String())\n\tmetricsResp, err := s.getPrometheusMetrics(ctx, promQueries, quantileQueries)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnumSvcMap, err := s.getNumServicesMap(ctx)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trowsMap := processPrometheusResult(metricsResp, numSvcMap)\n\n\treturn rowsMap, nil\n}\n"
  },
  {
    "path": "viz/metrics-api/gen/viz/viz.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.35.2\n// \tprotoc        v6.32.1\n// source: viz.proto\n\npackage viz\n\nimport (\n\tduration \"github.com/golang/protobuf/ptypes/duration\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\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 CheckStatus int32\n\nconst (\n\tCheckStatus_OK    CheckStatus = 0\n\tCheckStatus_FAIL  CheckStatus = 1\n\tCheckStatus_ERROR CheckStatus = 2\n)\n\n// Enum value maps for CheckStatus.\nvar (\n\tCheckStatus_name = map[int32]string{\n\t\t0: \"OK\",\n\t\t1: \"FAIL\",\n\t\t2: \"ERROR\",\n\t}\n\tCheckStatus_value = map[string]int32{\n\t\t\"OK\":    0,\n\t\t\"FAIL\":  1,\n\t\t\"ERROR\": 2,\n\t}\n)\n\nfunc (x CheckStatus) Enum() *CheckStatus {\n\tp := new(CheckStatus)\n\t*p = x\n\treturn p\n}\n\nfunc (x CheckStatus) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (CheckStatus) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_viz_proto_enumTypes[0].Descriptor()\n}\n\nfunc (CheckStatus) Type() protoreflect.EnumType {\n\treturn &file_viz_proto_enumTypes[0]\n}\n\nfunc (x CheckStatus) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use CheckStatus.Descriptor instead.\nfunc (CheckStatus) EnumDescriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{0}\n}\n\ntype HttpMethod_Registered int32\n\nconst (\n\tHttpMethod_GET     HttpMethod_Registered = 0\n\tHttpMethod_POST    HttpMethod_Registered = 1\n\tHttpMethod_PUT     HttpMethod_Registered = 2\n\tHttpMethod_DELETE  HttpMethod_Registered = 3\n\tHttpMethod_PATCH   HttpMethod_Registered = 4\n\tHttpMethod_OPTIONS HttpMethod_Registered = 5\n\tHttpMethod_CONNECT HttpMethod_Registered = 6\n\tHttpMethod_HEAD    HttpMethod_Registered = 7\n\tHttpMethod_TRACE   HttpMethod_Registered = 8\n)\n\n// Enum value maps for HttpMethod_Registered.\nvar (\n\tHttpMethod_Registered_name = map[int32]string{\n\t\t0: \"GET\",\n\t\t1: \"POST\",\n\t\t2: \"PUT\",\n\t\t3: \"DELETE\",\n\t\t4: \"PATCH\",\n\t\t5: \"OPTIONS\",\n\t\t6: \"CONNECT\",\n\t\t7: \"HEAD\",\n\t\t8: \"TRACE\",\n\t}\n\tHttpMethod_Registered_value = map[string]int32{\n\t\t\"GET\":     0,\n\t\t\"POST\":    1,\n\t\t\"PUT\":     2,\n\t\t\"DELETE\":  3,\n\t\t\"PATCH\":   4,\n\t\t\"OPTIONS\": 5,\n\t\t\"CONNECT\": 6,\n\t\t\"HEAD\":    7,\n\t\t\"TRACE\":   8,\n\t}\n)\n\nfunc (x HttpMethod_Registered) Enum() *HttpMethod_Registered {\n\tp := new(HttpMethod_Registered)\n\t*p = x\n\treturn p\n}\n\nfunc (x HttpMethod_Registered) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (HttpMethod_Registered) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_viz_proto_enumTypes[1].Descriptor()\n}\n\nfunc (HttpMethod_Registered) Type() protoreflect.EnumType {\n\treturn &file_viz_proto_enumTypes[1]\n}\n\nfunc (x HttpMethod_Registered) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use HttpMethod_Registered.Descriptor instead.\nfunc (HttpMethod_Registered) EnumDescriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{10, 0}\n}\n\ntype Scheme_Registered int32\n\nconst (\n\tScheme_HTTP  Scheme_Registered = 0\n\tScheme_HTTPS Scheme_Registered = 1\n)\n\n// Enum value maps for Scheme_Registered.\nvar (\n\tScheme_Registered_name = map[int32]string{\n\t\t0: \"HTTP\",\n\t\t1: \"HTTPS\",\n\t}\n\tScheme_Registered_value = map[string]int32{\n\t\t\"HTTP\":  0,\n\t\t\"HTTPS\": 1,\n\t}\n)\n\nfunc (x Scheme_Registered) Enum() *Scheme_Registered {\n\tp := new(Scheme_Registered)\n\t*p = x\n\treturn p\n}\n\nfunc (x Scheme_Registered) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Scheme_Registered) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_viz_proto_enumTypes[2].Descriptor()\n}\n\nfunc (Scheme_Registered) Type() protoreflect.EnumType {\n\treturn &file_viz_proto_enumTypes[2]\n}\n\nfunc (x Scheme_Registered) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Scheme_Registered.Descriptor instead.\nfunc (Scheme_Registered) EnumDescriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{11, 0}\n}\n\ntype Empty struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *Empty) Reset() {\n\t*x = Empty{}\n\tmi := &file_viz_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Empty) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Empty) ProtoMessage() {}\n\nfunc (x *Empty) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 Empty.ProtoReflect.Descriptor instead.\nfunc (*Empty) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{0}\n}\n\ntype CheckResult struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSubsystemName         string      `protobuf:\"bytes,1,opt,name=SubsystemName,proto3\" json:\"SubsystemName,omitempty\"`\n\tCheckDescription      string      `protobuf:\"bytes,2,opt,name=CheckDescription,proto3\" json:\"CheckDescription,omitempty\"`\n\tStatus                CheckStatus `protobuf:\"varint,3,opt,name=Status,proto3,enum=linkerd2.viz.CheckStatus\" json:\"Status,omitempty\"`\n\tFriendlyMessageToUser string      `protobuf:\"bytes,4,opt,name=FriendlyMessageToUser,proto3\" json:\"FriendlyMessageToUser,omitempty\"`\n}\n\nfunc (x *CheckResult) Reset() {\n\t*x = CheckResult{}\n\tmi := &file_viz_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CheckResult) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CheckResult) ProtoMessage() {}\n\nfunc (x *CheckResult) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 CheckResult.ProtoReflect.Descriptor instead.\nfunc (*CheckResult) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *CheckResult) GetSubsystemName() string {\n\tif x != nil {\n\t\treturn x.SubsystemName\n\t}\n\treturn \"\"\n}\n\nfunc (x *CheckResult) GetCheckDescription() string {\n\tif x != nil {\n\t\treturn x.CheckDescription\n\t}\n\treturn \"\"\n}\n\nfunc (x *CheckResult) GetStatus() CheckStatus {\n\tif x != nil {\n\t\treturn x.Status\n\t}\n\treturn CheckStatus_OK\n}\n\nfunc (x *CheckResult) GetFriendlyMessageToUser() string {\n\tif x != nil {\n\t\treturn x.FriendlyMessageToUser\n\t}\n\treturn \"\"\n}\n\ntype SelfCheckRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *SelfCheckRequest) Reset() {\n\t*x = SelfCheckRequest{}\n\tmi := &file_viz_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SelfCheckRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SelfCheckRequest) ProtoMessage() {}\n\nfunc (x *SelfCheckRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 SelfCheckRequest.ProtoReflect.Descriptor instead.\nfunc (*SelfCheckRequest) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{2}\n}\n\ntype SelfCheckResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tResults []*CheckResult `protobuf:\"bytes,1,rep,name=results,proto3\" json:\"results,omitempty\"`\n}\n\nfunc (x *SelfCheckResponse) Reset() {\n\t*x = SelfCheckResponse{}\n\tmi := &file_viz_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SelfCheckResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SelfCheckResponse) ProtoMessage() {}\n\nfunc (x *SelfCheckResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 SelfCheckResponse.ProtoReflect.Descriptor instead.\nfunc (*SelfCheckResponse) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *SelfCheckResponse) GetResults() []*CheckResult {\n\tif x != nil {\n\t\treturn x.Results\n\t}\n\treturn nil\n}\n\ntype ListServicesRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tNamespace string `protobuf:\"bytes,1,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n}\n\nfunc (x *ListServicesRequest) Reset() {\n\t*x = ListServicesRequest{}\n\tmi := &file_viz_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListServicesRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListServicesRequest) ProtoMessage() {}\n\nfunc (x *ListServicesRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 ListServicesRequest.ProtoReflect.Descriptor instead.\nfunc (*ListServicesRequest) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *ListServicesRequest) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\ntype ListServicesResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tServices []*Service `protobuf:\"bytes,1,rep,name=services,proto3\" json:\"services,omitempty\"`\n}\n\nfunc (x *ListServicesResponse) Reset() {\n\t*x = ListServicesResponse{}\n\tmi := &file_viz_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListServicesResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListServicesResponse) ProtoMessage() {}\n\nfunc (x *ListServicesResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 ListServicesResponse.ProtoReflect.Descriptor instead.\nfunc (*ListServicesResponse) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *ListServicesResponse) GetServices() []*Service {\n\tif x != nil {\n\t\treturn x.Services\n\t}\n\treturn nil\n}\n\ntype Service struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tName      string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tNamespace string `protobuf:\"bytes,2,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n}\n\nfunc (x *Service) Reset() {\n\t*x = Service{}\n\tmi := &file_viz_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Service) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Service) ProtoMessage() {}\n\nfunc (x *Service) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 Service.ProtoReflect.Descriptor instead.\nfunc (*Service) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *Service) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Service) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\ntype ListPodsRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSelector *ResourceSelection `protobuf:\"bytes,2,opt,name=selector,proto3\" json:\"selector,omitempty\"`\n}\n\nfunc (x *ListPodsRequest) Reset() {\n\t*x = ListPodsRequest{}\n\tmi := &file_viz_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListPodsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListPodsRequest) ProtoMessage() {}\n\nfunc (x *ListPodsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 ListPodsRequest.ProtoReflect.Descriptor instead.\nfunc (*ListPodsRequest) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *ListPodsRequest) GetSelector() *ResourceSelection {\n\tif x != nil {\n\t\treturn x.Selector\n\t}\n\treturn nil\n}\n\ntype ListPodsResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tPods []*Pod `protobuf:\"bytes,1,rep,name=pods,proto3\" json:\"pods,omitempty\"`\n}\n\nfunc (x *ListPodsResponse) Reset() {\n\t*x = ListPodsResponse{}\n\tmi := &file_viz_proto_msgTypes[8]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListPodsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListPodsResponse) ProtoMessage() {}\n\nfunc (x *ListPodsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 ListPodsResponse.ProtoReflect.Descriptor instead.\nfunc (*ListPodsResponse) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *ListPodsResponse) GetPods() []*Pod {\n\tif x != nil {\n\t\treturn x.Pods\n\t}\n\treturn nil\n}\n\ntype Pod struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tName  string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tPodIP string `protobuf:\"bytes,2,opt,name=podIP,proto3\" json:\"podIP,omitempty\"`\n\t// Types that are assignable to Owner:\n\t//\n\t//\t*Pod_Deployment\n\t//\t*Pod_ReplicaSet\n\t//\t*Pod_ReplicationController\n\t//\t*Pod_StatefulSet\n\t//\t*Pod_DaemonSet\n\t//\t*Pod_Job\n\tOwner               isPod_Owner        `protobuf_oneof:\"owner\"`\n\tStatus              string             `protobuf:\"bytes,4,opt,name=status,proto3\" json:\"status,omitempty\"`\n\tAdded               bool               `protobuf:\"varint,5,opt,name=added,proto3\" json:\"added,omitempty\"` // true if this pod has a proxy sidecar (data plane)\n\tSinceLastReport     *duration.Duration `protobuf:\"bytes,6,opt,name=sinceLastReport,proto3\" json:\"sinceLastReport,omitempty\"`\n\tControllerNamespace string             `protobuf:\"bytes,7,opt,name=controllerNamespace,proto3\" json:\"controllerNamespace,omitempty\"` // namespace of controller this pod reports to\n\tControlPlane        bool               `protobuf:\"varint,8,opt,name=controlPlane,proto3\" json:\"controlPlane,omitempty\"`              // true if this pod is part of the control plane\n\tUptime              *duration.Duration `protobuf:\"bytes,9,opt,name=uptime,proto3\" json:\"uptime,omitempty\"`                           // uptime of this pod\n\tProxyReady          bool               `protobuf:\"varint,15,opt,name=proxyReady,proto3\" json:\"proxyReady,omitempty\"`                 // true if this pod has proxy container and that one is in ready state\n\tProxyVersion        string             `protobuf:\"bytes,16,opt,name=proxyVersion,proto3\" json:\"proxyVersion,omitempty\"`              // version of the proxy if present\n\tResourceVersion     string             `protobuf:\"bytes,17,opt,name=resourceVersion,proto3\" json:\"resourceVersion,omitempty\"`        // resource version in the Kubernetes API\n}\n\nfunc (x *Pod) Reset() {\n\t*x = Pod{}\n\tmi := &file_viz_proto_msgTypes[9]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Pod) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Pod) ProtoMessage() {}\n\nfunc (x *Pod) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 Pod.ProtoReflect.Descriptor instead.\nfunc (*Pod) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *Pod) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Pod) GetPodIP() string {\n\tif x != nil {\n\t\treturn x.PodIP\n\t}\n\treturn \"\"\n}\n\nfunc (m *Pod) GetOwner() isPod_Owner {\n\tif m != nil {\n\t\treturn m.Owner\n\t}\n\treturn nil\n}\n\nfunc (x *Pod) GetDeployment() string {\n\tif x, ok := x.GetOwner().(*Pod_Deployment); ok {\n\t\treturn x.Deployment\n\t}\n\treturn \"\"\n}\n\nfunc (x *Pod) GetReplicaSet() string {\n\tif x, ok := x.GetOwner().(*Pod_ReplicaSet); ok {\n\t\treturn x.ReplicaSet\n\t}\n\treturn \"\"\n}\n\nfunc (x *Pod) GetReplicationController() string {\n\tif x, ok := x.GetOwner().(*Pod_ReplicationController); ok {\n\t\treturn x.ReplicationController\n\t}\n\treturn \"\"\n}\n\nfunc (x *Pod) GetStatefulSet() string {\n\tif x, ok := x.GetOwner().(*Pod_StatefulSet); ok {\n\t\treturn x.StatefulSet\n\t}\n\treturn \"\"\n}\n\nfunc (x *Pod) GetDaemonSet() string {\n\tif x, ok := x.GetOwner().(*Pod_DaemonSet); ok {\n\t\treturn x.DaemonSet\n\t}\n\treturn \"\"\n}\n\nfunc (x *Pod) GetJob() string {\n\tif x, ok := x.GetOwner().(*Pod_Job); ok {\n\t\treturn x.Job\n\t}\n\treturn \"\"\n}\n\nfunc (x *Pod) GetStatus() string {\n\tif x != nil {\n\t\treturn x.Status\n\t}\n\treturn \"\"\n}\n\nfunc (x *Pod) GetAdded() bool {\n\tif x != nil {\n\t\treturn x.Added\n\t}\n\treturn false\n}\n\nfunc (x *Pod) GetSinceLastReport() *duration.Duration {\n\tif x != nil {\n\t\treturn x.SinceLastReport\n\t}\n\treturn nil\n}\n\nfunc (x *Pod) GetControllerNamespace() string {\n\tif x != nil {\n\t\treturn x.ControllerNamespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *Pod) GetControlPlane() bool {\n\tif x != nil {\n\t\treturn x.ControlPlane\n\t}\n\treturn false\n}\n\nfunc (x *Pod) GetUptime() *duration.Duration {\n\tif x != nil {\n\t\treturn x.Uptime\n\t}\n\treturn nil\n}\n\nfunc (x *Pod) GetProxyReady() bool {\n\tif x != nil {\n\t\treturn x.ProxyReady\n\t}\n\treturn false\n}\n\nfunc (x *Pod) GetProxyVersion() string {\n\tif x != nil {\n\t\treturn x.ProxyVersion\n\t}\n\treturn \"\"\n}\n\nfunc (x *Pod) GetResourceVersion() string {\n\tif x != nil {\n\t\treturn x.ResourceVersion\n\t}\n\treturn \"\"\n}\n\ntype isPod_Owner interface {\n\tisPod_Owner()\n}\n\ntype Pod_Deployment struct {\n\tDeployment string `protobuf:\"bytes,3,opt,name=deployment,proto3,oneof\"`\n}\n\ntype Pod_ReplicaSet struct {\n\tReplicaSet string `protobuf:\"bytes,10,opt,name=replica_set,json=replicaSet,proto3,oneof\"`\n}\n\ntype Pod_ReplicationController struct {\n\tReplicationController string `protobuf:\"bytes,11,opt,name=replication_controller,json=replicationController,proto3,oneof\"`\n}\n\ntype Pod_StatefulSet struct {\n\tStatefulSet string `protobuf:\"bytes,12,opt,name=stateful_set,json=statefulSet,proto3,oneof\"`\n}\n\ntype Pod_DaemonSet struct {\n\tDaemonSet string `protobuf:\"bytes,13,opt,name=daemon_set,json=daemonSet,proto3,oneof\"`\n}\n\ntype Pod_Job struct {\n\tJob string `protobuf:\"bytes,14,opt,name=job,proto3,oneof\"`\n}\n\nfunc (*Pod_Deployment) isPod_Owner() {}\n\nfunc (*Pod_ReplicaSet) isPod_Owner() {}\n\nfunc (*Pod_ReplicationController) isPod_Owner() {}\n\nfunc (*Pod_StatefulSet) isPod_Owner() {}\n\nfunc (*Pod_DaemonSet) isPod_Owner() {}\n\nfunc (*Pod_Job) isPod_Owner() {}\n\ntype HttpMethod struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Type:\n\t//\n\t//\t*HttpMethod_Registered_\n\t//\t*HttpMethod_Unregistered\n\tType isHttpMethod_Type `protobuf_oneof:\"type\"`\n}\n\nfunc (x *HttpMethod) Reset() {\n\t*x = HttpMethod{}\n\tmi := &file_viz_proto_msgTypes[10]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *HttpMethod) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*HttpMethod) ProtoMessage() {}\n\nfunc (x *HttpMethod) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 HttpMethod.ProtoReflect.Descriptor instead.\nfunc (*HttpMethod) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (m *HttpMethod) GetType() isHttpMethod_Type {\n\tif m != nil {\n\t\treturn m.Type\n\t}\n\treturn nil\n}\n\nfunc (x *HttpMethod) GetRegistered() HttpMethod_Registered {\n\tif x, ok := x.GetType().(*HttpMethod_Registered_); ok {\n\t\treturn x.Registered\n\t}\n\treturn HttpMethod_GET\n}\n\nfunc (x *HttpMethod) GetUnregistered() string {\n\tif x, ok := x.GetType().(*HttpMethod_Unregistered); ok {\n\t\treturn x.Unregistered\n\t}\n\treturn \"\"\n}\n\ntype isHttpMethod_Type interface {\n\tisHttpMethod_Type()\n}\n\ntype HttpMethod_Registered_ struct {\n\tRegistered HttpMethod_Registered `protobuf:\"varint,1,opt,name=registered,proto3,enum=linkerd2.viz.HttpMethod_Registered,oneof\"`\n}\n\ntype HttpMethod_Unregistered struct {\n\tUnregistered string `protobuf:\"bytes,2,opt,name=unregistered,proto3,oneof\"`\n}\n\nfunc (*HttpMethod_Registered_) isHttpMethod_Type() {}\n\nfunc (*HttpMethod_Unregistered) isHttpMethod_Type() {}\n\ntype Scheme struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Type:\n\t//\n\t//\t*Scheme_Registered_\n\t//\t*Scheme_Unregistered\n\tType isScheme_Type `protobuf_oneof:\"type\"`\n}\n\nfunc (x *Scheme) Reset() {\n\t*x = Scheme{}\n\tmi := &file_viz_proto_msgTypes[11]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Scheme) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Scheme) ProtoMessage() {}\n\nfunc (x *Scheme) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 Scheme.ProtoReflect.Descriptor instead.\nfunc (*Scheme) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (m *Scheme) GetType() isScheme_Type {\n\tif m != nil {\n\t\treturn m.Type\n\t}\n\treturn nil\n}\n\nfunc (x *Scheme) GetRegistered() Scheme_Registered {\n\tif x, ok := x.GetType().(*Scheme_Registered_); ok {\n\t\treturn x.Registered\n\t}\n\treturn Scheme_HTTP\n}\n\nfunc (x *Scheme) GetUnregistered() string {\n\tif x, ok := x.GetType().(*Scheme_Unregistered); ok {\n\t\treturn x.Unregistered\n\t}\n\treturn \"\"\n}\n\ntype isScheme_Type interface {\n\tisScheme_Type()\n}\n\ntype Scheme_Registered_ struct {\n\tRegistered Scheme_Registered `protobuf:\"varint,1,opt,name=registered,proto3,enum=linkerd2.viz.Scheme_Registered,oneof\"`\n}\n\ntype Scheme_Unregistered struct {\n\tUnregistered string `protobuf:\"bytes,2,opt,name=unregistered,proto3,oneof\"`\n}\n\nfunc (*Scheme_Registered_) isScheme_Type() {}\n\nfunc (*Scheme_Unregistered) isScheme_Type() {}\n\ntype Headers struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tHeaders []*Headers_Header `protobuf:\"bytes,1,rep,name=headers,proto3\" json:\"headers,omitempty\"`\n}\n\nfunc (x *Headers) Reset() {\n\t*x = Headers{}\n\tmi := &file_viz_proto_msgTypes[12]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Headers) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Headers) ProtoMessage() {}\n\nfunc (x *Headers) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 Headers.ProtoReflect.Descriptor instead.\nfunc (*Headers) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *Headers) GetHeaders() []*Headers_Header {\n\tif x != nil {\n\t\treturn x.Headers\n\t}\n\treturn nil\n}\n\ntype Eos struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to End:\n\t//\n\t//\t*Eos_GrpcStatusCode\n\t//\t*Eos_ResetErrorCode\n\tEnd isEos_End `protobuf_oneof:\"end\"`\n}\n\nfunc (x *Eos) Reset() {\n\t*x = Eos{}\n\tmi := &file_viz_proto_msgTypes[13]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Eos) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Eos) ProtoMessage() {}\n\nfunc (x *Eos) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 Eos.ProtoReflect.Descriptor instead.\nfunc (*Eos) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (m *Eos) GetEnd() isEos_End {\n\tif m != nil {\n\t\treturn m.End\n\t}\n\treturn nil\n}\n\nfunc (x *Eos) GetGrpcStatusCode() uint32 {\n\tif x, ok := x.GetEnd().(*Eos_GrpcStatusCode); ok {\n\t\treturn x.GrpcStatusCode\n\t}\n\treturn 0\n}\n\nfunc (x *Eos) GetResetErrorCode() uint32 {\n\tif x, ok := x.GetEnd().(*Eos_ResetErrorCode); ok {\n\t\treturn x.ResetErrorCode\n\t}\n\treturn 0\n}\n\ntype isEos_End interface {\n\tisEos_End()\n}\n\ntype Eos_GrpcStatusCode struct {\n\tGrpcStatusCode uint32 `protobuf:\"varint,1,opt,name=grpc_status_code,json=grpcStatusCode,proto3,oneof\"`\n}\n\ntype Eos_ResetErrorCode struct {\n\tResetErrorCode uint32 `protobuf:\"varint,2,opt,name=reset_error_code,json=resetErrorCode,proto3,oneof\"`\n}\n\nfunc (*Eos_GrpcStatusCode) isEos_End() {}\n\nfunc (*Eos_ResetErrorCode) isEos_End() {}\n\ntype ApiError struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tError string `protobuf:\"bytes,1,opt,name=error,proto3\" json:\"error,omitempty\"`\n}\n\nfunc (x *ApiError) Reset() {\n\t*x = ApiError{}\n\tmi := &file_viz_proto_msgTypes[14]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ApiError) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ApiError) ProtoMessage() {}\n\nfunc (x *ApiError) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 ApiError.ProtoReflect.Descriptor instead.\nfunc (*ApiError) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{14}\n}\n\nfunc (x *ApiError) GetError() string {\n\tif x != nil {\n\t\treturn x.Error\n\t}\n\treturn \"\"\n}\n\ntype PodErrors struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tErrors []*PodErrors_PodError `protobuf:\"bytes,1,rep,name=errors,proto3\" json:\"errors,omitempty\"`\n}\n\nfunc (x *PodErrors) Reset() {\n\t*x = PodErrors{}\n\tmi := &file_viz_proto_msgTypes[15]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PodErrors) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PodErrors) ProtoMessage() {}\n\nfunc (x *PodErrors) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 PodErrors.ProtoReflect.Descriptor instead.\nfunc (*PodErrors) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *PodErrors) GetErrors() []*PodErrors_PodError {\n\tif x != nil {\n\t\treturn x.Errors\n\t}\n\treturn nil\n}\n\ntype Resource struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// The namespace the resource is in.\n\t//\n\t// If empty, indicates all namespaces should be considered.\n\tNamespace string `protobuf:\"bytes,1,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n\t// The type of resource.\n\t//\n\t// This can be:\n\t// - \"all\" -- includes all Kubernetes resource types only\n\t// - \"authority\" -- a special resource type derived from request `:authority` values\n\t// - Otherwise, the resource type may be any Kubernetes resource (e.g. \"namespace\", \"deployment\").\n\tType string `protobuf:\"bytes,2,opt,name=type,proto3\" json:\"type,omitempty\"`\n\t// An optional resource name.\n\tName string `protobuf:\"bytes,3,opt,name=name,proto3\" json:\"name,omitempty\"`\n}\n\nfunc (x *Resource) Reset() {\n\t*x = Resource{}\n\tmi := &file_viz_proto_msgTypes[16]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Resource) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Resource) ProtoMessage() {}\n\nfunc (x *Resource) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 Resource.ProtoReflect.Descriptor instead.\nfunc (*Resource) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{16}\n}\n\nfunc (x *Resource) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *Resource) GetType() string {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn \"\"\n}\n\nfunc (x *Resource) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\ntype ResourceSelection struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Identifies a Kubernetes resource.\n\tResource *Resource `protobuf:\"bytes,1,opt,name=resource,proto3\" json:\"resource,omitempty\"`\n\t// A string-formatted Kubernetes label selector as passed to `kubectl get\n\t// --selector`.\n\t//\n\t// XXX in the future this may be superseded by a data structure that more\n\t// richly describes a parsed label selector.\n\tLabelSelector string `protobuf:\"bytes,2,opt,name=label_selector,json=labelSelector,proto3\" json:\"label_selector,omitempty\"`\n}\n\nfunc (x *ResourceSelection) Reset() {\n\t*x = ResourceSelection{}\n\tmi := &file_viz_proto_msgTypes[17]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ResourceSelection) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ResourceSelection) ProtoMessage() {}\n\nfunc (x *ResourceSelection) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 ResourceSelection.ProtoReflect.Descriptor instead.\nfunc (*ResourceSelection) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *ResourceSelection) GetResource() *Resource {\n\tif x != nil {\n\t\treturn x.Resource\n\t}\n\treturn nil\n}\n\nfunc (x *ResourceSelection) GetLabelSelector() string {\n\tif x != nil {\n\t\treturn x.LabelSelector\n\t}\n\treturn \"\"\n}\n\ntype ResourceError struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tResource *Resource `protobuf:\"bytes,1,opt,name=resource,proto3\" json:\"resource,omitempty\"`\n\tError    string    `protobuf:\"bytes,2,opt,name=error,proto3\" json:\"error,omitempty\"`\n}\n\nfunc (x *ResourceError) Reset() {\n\t*x = ResourceError{}\n\tmi := &file_viz_proto_msgTypes[18]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ResourceError) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ResourceError) ProtoMessage() {}\n\nfunc (x *ResourceError) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 ResourceError.ProtoReflect.Descriptor instead.\nfunc (*ResourceError) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{18}\n}\n\nfunc (x *ResourceError) GetResource() *Resource {\n\tif x != nil {\n\t\treturn x.Resource\n\t}\n\treturn nil\n}\n\nfunc (x *ResourceError) GetError() string {\n\tif x != nil {\n\t\treturn x.Error\n\t}\n\treturn \"\"\n}\n\ntype StatSummaryRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSelector   *ResourceSelection `protobuf:\"bytes,1,opt,name=selector,proto3\" json:\"selector,omitempty\"`\n\tTimeWindow string             `protobuf:\"bytes,2,opt,name=time_window,json=timeWindow,proto3\" json:\"time_window,omitempty\"`\n\t// Types that are assignable to Outbound:\n\t//\n\t//\t*StatSummaryRequest_None\n\t//\t*StatSummaryRequest_ToResource\n\t//\t*StatSummaryRequest_FromResource\n\tOutbound  isStatSummaryRequest_Outbound `protobuf_oneof:\"outbound\"`\n\tSkipStats bool                          `protobuf:\"varint,6,opt,name=skip_stats,json=skipStats,proto3\" json:\"skip_stats,omitempty\"` // true if we want to skip stats from Prometheus\n\tTcpStats  bool                          `protobuf:\"varint,7,opt,name=tcp_stats,json=tcpStats,proto3\" json:\"tcp_stats,omitempty\"`\n}\n\nfunc (x *StatSummaryRequest) Reset() {\n\t*x = StatSummaryRequest{}\n\tmi := &file_viz_proto_msgTypes[19]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *StatSummaryRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*StatSummaryRequest) ProtoMessage() {}\n\nfunc (x *StatSummaryRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 StatSummaryRequest.ProtoReflect.Descriptor instead.\nfunc (*StatSummaryRequest) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{19}\n}\n\nfunc (x *StatSummaryRequest) GetSelector() *ResourceSelection {\n\tif x != nil {\n\t\treturn x.Selector\n\t}\n\treturn nil\n}\n\nfunc (x *StatSummaryRequest) GetTimeWindow() string {\n\tif x != nil {\n\t\treturn x.TimeWindow\n\t}\n\treturn \"\"\n}\n\nfunc (m *StatSummaryRequest) GetOutbound() isStatSummaryRequest_Outbound {\n\tif m != nil {\n\t\treturn m.Outbound\n\t}\n\treturn nil\n}\n\nfunc (x *StatSummaryRequest) GetNone() *Empty {\n\tif x, ok := x.GetOutbound().(*StatSummaryRequest_None); ok {\n\t\treturn x.None\n\t}\n\treturn nil\n}\n\nfunc (x *StatSummaryRequest) GetToResource() *Resource {\n\tif x, ok := x.GetOutbound().(*StatSummaryRequest_ToResource); ok {\n\t\treturn x.ToResource\n\t}\n\treturn nil\n}\n\nfunc (x *StatSummaryRequest) GetFromResource() *Resource {\n\tif x, ok := x.GetOutbound().(*StatSummaryRequest_FromResource); ok {\n\t\treturn x.FromResource\n\t}\n\treturn nil\n}\n\nfunc (x *StatSummaryRequest) GetSkipStats() bool {\n\tif x != nil {\n\t\treturn x.SkipStats\n\t}\n\treturn false\n}\n\nfunc (x *StatSummaryRequest) GetTcpStats() bool {\n\tif x != nil {\n\t\treturn x.TcpStats\n\t}\n\treturn false\n}\n\ntype isStatSummaryRequest_Outbound interface {\n\tisStatSummaryRequest_Outbound()\n}\n\ntype StatSummaryRequest_None struct {\n\tNone *Empty `protobuf:\"bytes,3,opt,name=none,proto3,oneof\"`\n}\n\ntype StatSummaryRequest_ToResource struct {\n\tToResource *Resource `protobuf:\"bytes,4,opt,name=to_resource,json=toResource,proto3,oneof\"`\n}\n\ntype StatSummaryRequest_FromResource struct {\n\tFromResource *Resource `protobuf:\"bytes,5,opt,name=from_resource,json=fromResource,proto3,oneof\"`\n}\n\nfunc (*StatSummaryRequest_None) isStatSummaryRequest_Outbound() {}\n\nfunc (*StatSummaryRequest_ToResource) isStatSummaryRequest_Outbound() {}\n\nfunc (*StatSummaryRequest_FromResource) isStatSummaryRequest_Outbound() {}\n\ntype StatSummaryResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Response:\n\t//\n\t//\t*StatSummaryResponse_Ok_\n\t//\t*StatSummaryResponse_Error\n\tResponse isStatSummaryResponse_Response `protobuf_oneof:\"response\"`\n}\n\nfunc (x *StatSummaryResponse) Reset() {\n\t*x = StatSummaryResponse{}\n\tmi := &file_viz_proto_msgTypes[20]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *StatSummaryResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*StatSummaryResponse) ProtoMessage() {}\n\nfunc (x *StatSummaryResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 StatSummaryResponse.ProtoReflect.Descriptor instead.\nfunc (*StatSummaryResponse) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{20}\n}\n\nfunc (m *StatSummaryResponse) GetResponse() isStatSummaryResponse_Response {\n\tif m != nil {\n\t\treturn m.Response\n\t}\n\treturn nil\n}\n\nfunc (x *StatSummaryResponse) GetOk() *StatSummaryResponse_Ok {\n\tif x, ok := x.GetResponse().(*StatSummaryResponse_Ok_); ok {\n\t\treturn x.Ok\n\t}\n\treturn nil\n}\n\nfunc (x *StatSummaryResponse) GetError() *ResourceError {\n\tif x, ok := x.GetResponse().(*StatSummaryResponse_Error); ok {\n\t\treturn x.Error\n\t}\n\treturn nil\n}\n\ntype isStatSummaryResponse_Response interface {\n\tisStatSummaryResponse_Response()\n}\n\ntype StatSummaryResponse_Ok_ struct {\n\tOk *StatSummaryResponse_Ok `protobuf:\"bytes,1,opt,name=ok,proto3,oneof\"`\n}\n\ntype StatSummaryResponse_Error struct {\n\tError *ResourceError `protobuf:\"bytes,2,opt,name=error,proto3,oneof\"`\n}\n\nfunc (*StatSummaryResponse_Ok_) isStatSummaryResponse_Response() {}\n\nfunc (*StatSummaryResponse_Error) isStatSummaryResponse_Response() {}\n\ntype AuthzRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tResource   *Resource `protobuf:\"bytes,1,opt,name=resource,proto3\" json:\"resource,omitempty\"`\n\tTimeWindow string    `protobuf:\"bytes,2,opt,name=time_window,json=timeWindow,proto3\" json:\"time_window,omitempty\"`\n}\n\nfunc (x *AuthzRequest) Reset() {\n\t*x = AuthzRequest{}\n\tmi := &file_viz_proto_msgTypes[21]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AuthzRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AuthzRequest) ProtoMessage() {}\n\nfunc (x *AuthzRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 AuthzRequest.ProtoReflect.Descriptor instead.\nfunc (*AuthzRequest) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{21}\n}\n\nfunc (x *AuthzRequest) GetResource() *Resource {\n\tif x != nil {\n\t\treturn x.Resource\n\t}\n\treturn nil\n}\n\nfunc (x *AuthzRequest) GetTimeWindow() string {\n\tif x != nil {\n\t\treturn x.TimeWindow\n\t}\n\treturn \"\"\n}\n\ntype AuthzResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Response:\n\t//\n\t//\t*AuthzResponse_Ok_\n\t//\t*AuthzResponse_Error\n\tResponse isAuthzResponse_Response `protobuf_oneof:\"response\"`\n}\n\nfunc (x *AuthzResponse) Reset() {\n\t*x = AuthzResponse{}\n\tmi := &file_viz_proto_msgTypes[22]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AuthzResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AuthzResponse) ProtoMessage() {}\n\nfunc (x *AuthzResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 AuthzResponse.ProtoReflect.Descriptor instead.\nfunc (*AuthzResponse) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{22}\n}\n\nfunc (m *AuthzResponse) GetResponse() isAuthzResponse_Response {\n\tif m != nil {\n\t\treturn m.Response\n\t}\n\treturn nil\n}\n\nfunc (x *AuthzResponse) GetOk() *AuthzResponse_Ok {\n\tif x, ok := x.GetResponse().(*AuthzResponse_Ok_); ok {\n\t\treturn x.Ok\n\t}\n\treturn nil\n}\n\nfunc (x *AuthzResponse) GetError() *ResourceError {\n\tif x, ok := x.GetResponse().(*AuthzResponse_Error); ok {\n\t\treturn x.Error\n\t}\n\treturn nil\n}\n\ntype isAuthzResponse_Response interface {\n\tisAuthzResponse_Response()\n}\n\ntype AuthzResponse_Ok_ struct {\n\tOk *AuthzResponse_Ok `protobuf:\"bytes,1,opt,name=ok,proto3,oneof\"`\n}\n\ntype AuthzResponse_Error struct {\n\tError *ResourceError `protobuf:\"bytes,2,opt,name=error,proto3,oneof\"`\n}\n\nfunc (*AuthzResponse_Ok_) isAuthzResponse_Response() {}\n\nfunc (*AuthzResponse_Error) isAuthzResponse_Response() {}\n\ntype BasicStats struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSuccessCount       uint64 `protobuf:\"varint,1,opt,name=success_count,json=successCount,proto3\" json:\"success_count,omitempty\"`\n\tFailureCount       uint64 `protobuf:\"varint,2,opt,name=failure_count,json=failureCount,proto3\" json:\"failure_count,omitempty\"`\n\tLatencyMsP50       uint64 `protobuf:\"varint,3,opt,name=latency_ms_p50,json=latencyMsP50,proto3\" json:\"latency_ms_p50,omitempty\"`\n\tLatencyMsP95       uint64 `protobuf:\"varint,4,opt,name=latency_ms_p95,json=latencyMsP95,proto3\" json:\"latency_ms_p95,omitempty\"`\n\tLatencyMsP99       uint64 `protobuf:\"varint,5,opt,name=latency_ms_p99,json=latencyMsP99,proto3\" json:\"latency_ms_p99,omitempty\"`\n\tActualSuccessCount uint64 `protobuf:\"varint,6,opt,name=actual_success_count,json=actualSuccessCount,proto3\" json:\"actual_success_count,omitempty\"`\n\tActualFailureCount uint64 `protobuf:\"varint,7,opt,name=actual_failure_count,json=actualFailureCount,proto3\" json:\"actual_failure_count,omitempty\"`\n}\n\nfunc (x *BasicStats) Reset() {\n\t*x = BasicStats{}\n\tmi := &file_viz_proto_msgTypes[23]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *BasicStats) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*BasicStats) ProtoMessage() {}\n\nfunc (x *BasicStats) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 BasicStats.ProtoReflect.Descriptor instead.\nfunc (*BasicStats) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{23}\n}\n\nfunc (x *BasicStats) GetSuccessCount() uint64 {\n\tif x != nil {\n\t\treturn x.SuccessCount\n\t}\n\treturn 0\n}\n\nfunc (x *BasicStats) GetFailureCount() uint64 {\n\tif x != nil {\n\t\treturn x.FailureCount\n\t}\n\treturn 0\n}\n\nfunc (x *BasicStats) GetLatencyMsP50() uint64 {\n\tif x != nil {\n\t\treturn x.LatencyMsP50\n\t}\n\treturn 0\n}\n\nfunc (x *BasicStats) GetLatencyMsP95() uint64 {\n\tif x != nil {\n\t\treturn x.LatencyMsP95\n\t}\n\treturn 0\n}\n\nfunc (x *BasicStats) GetLatencyMsP99() uint64 {\n\tif x != nil {\n\t\treturn x.LatencyMsP99\n\t}\n\treturn 0\n}\n\nfunc (x *BasicStats) GetActualSuccessCount() uint64 {\n\tif x != nil {\n\t\treturn x.ActualSuccessCount\n\t}\n\treturn 0\n}\n\nfunc (x *BasicStats) GetActualFailureCount() uint64 {\n\tif x != nil {\n\t\treturn x.ActualFailureCount\n\t}\n\treturn 0\n}\n\ntype TcpStats struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// number of currently open connections\n\tOpenConnections uint64 `protobuf:\"varint,1,opt,name=open_connections,json=openConnections,proto3\" json:\"open_connections,omitempty\"`\n\t// total count of bytes read from peers\n\tReadBytesTotal uint64 `protobuf:\"varint,2,opt,name=read_bytes_total,json=readBytesTotal,proto3\" json:\"read_bytes_total,omitempty\"`\n\t// total count of bytes written to peers\n\tWriteBytesTotal uint64 `protobuf:\"varint,3,opt,name=write_bytes_total,json=writeBytesTotal,proto3\" json:\"write_bytes_total,omitempty\"`\n}\n\nfunc (x *TcpStats) Reset() {\n\t*x = TcpStats{}\n\tmi := &file_viz_proto_msgTypes[24]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TcpStats) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TcpStats) ProtoMessage() {}\n\nfunc (x *TcpStats) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 TcpStats.ProtoReflect.Descriptor instead.\nfunc (*TcpStats) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{24}\n}\n\nfunc (x *TcpStats) GetOpenConnections() uint64 {\n\tif x != nil {\n\t\treturn x.OpenConnections\n\t}\n\treturn 0\n}\n\nfunc (x *TcpStats) GetReadBytesTotal() uint64 {\n\tif x != nil {\n\t\treturn x.ReadBytesTotal\n\t}\n\treturn 0\n}\n\nfunc (x *TcpStats) GetWriteBytesTotal() uint64 {\n\tif x != nil {\n\t\treturn x.WriteBytesTotal\n\t}\n\treturn 0\n}\n\ntype TrafficSplitStats struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tApex   string `protobuf:\"bytes,2,opt,name=apex,proto3\" json:\"apex,omitempty\"`\n\tLeaf   string `protobuf:\"bytes,3,opt,name=leaf,proto3\" json:\"leaf,omitempty\"`\n\tWeight string `protobuf:\"bytes,4,opt,name=weight,proto3\" json:\"weight,omitempty\"`\n}\n\nfunc (x *TrafficSplitStats) Reset() {\n\t*x = TrafficSplitStats{}\n\tmi := &file_viz_proto_msgTypes[25]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TrafficSplitStats) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TrafficSplitStats) ProtoMessage() {}\n\nfunc (x *TrafficSplitStats) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 TrafficSplitStats.ProtoReflect.Descriptor instead.\nfunc (*TrafficSplitStats) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{25}\n}\n\nfunc (x *TrafficSplitStats) GetApex() string {\n\tif x != nil {\n\t\treturn x.Apex\n\t}\n\treturn \"\"\n}\n\nfunc (x *TrafficSplitStats) GetLeaf() string {\n\tif x != nil {\n\t\treturn x.Leaf\n\t}\n\treturn \"\"\n}\n\nfunc (x *TrafficSplitStats) GetWeight() string {\n\tif x != nil {\n\t\treturn x.Weight\n\t}\n\treturn \"\"\n}\n\ntype ServerStats struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAllowedCount uint64    `protobuf:\"varint,1,opt,name=allowed_count,json=allowedCount,proto3\" json:\"allowed_count,omitempty\"`\n\tDeniedCount  uint64    `protobuf:\"varint,2,opt,name=denied_count,json=deniedCount,proto3\" json:\"denied_count,omitempty\"`\n\tSrv          *Resource `protobuf:\"bytes,3,opt,name=srv,proto3\" json:\"srv,omitempty\"`\n\tRoute        *Resource `protobuf:\"bytes,4,opt,name=route,proto3\" json:\"route,omitempty\"`\n\tAuthz        *Resource `protobuf:\"bytes,5,opt,name=authz,proto3\" json:\"authz,omitempty\"`\n}\n\nfunc (x *ServerStats) Reset() {\n\t*x = ServerStats{}\n\tmi := &file_viz_proto_msgTypes[26]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ServerStats) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ServerStats) ProtoMessage() {}\n\nfunc (x *ServerStats) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 ServerStats.ProtoReflect.Descriptor instead.\nfunc (*ServerStats) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{26}\n}\n\nfunc (x *ServerStats) GetAllowedCount() uint64 {\n\tif x != nil {\n\t\treturn x.AllowedCount\n\t}\n\treturn 0\n}\n\nfunc (x *ServerStats) GetDeniedCount() uint64 {\n\tif x != nil {\n\t\treturn x.DeniedCount\n\t}\n\treturn 0\n}\n\nfunc (x *ServerStats) GetSrv() *Resource {\n\tif x != nil {\n\t\treturn x.Srv\n\t}\n\treturn nil\n}\n\nfunc (x *ServerStats) GetRoute() *Resource {\n\tif x != nil {\n\t\treturn x.Route\n\t}\n\treturn nil\n}\n\nfunc (x *ServerStats) GetAuthz() *Resource {\n\tif x != nil {\n\t\treturn x.Authz\n\t}\n\treturn nil\n}\n\ntype StatTable struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Table:\n\t//\n\t//\t*StatTable_PodGroup_\n\tTable isStatTable_Table `protobuf_oneof:\"table\"`\n}\n\nfunc (x *StatTable) Reset() {\n\t*x = StatTable{}\n\tmi := &file_viz_proto_msgTypes[27]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *StatTable) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*StatTable) ProtoMessage() {}\n\nfunc (x *StatTable) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 StatTable.ProtoReflect.Descriptor instead.\nfunc (*StatTable) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{27}\n}\n\nfunc (m *StatTable) GetTable() isStatTable_Table {\n\tif m != nil {\n\t\treturn m.Table\n\t}\n\treturn nil\n}\n\nfunc (x *StatTable) GetPodGroup() *StatTable_PodGroup {\n\tif x, ok := x.GetTable().(*StatTable_PodGroup_); ok {\n\t\treturn x.PodGroup\n\t}\n\treturn nil\n}\n\ntype isStatTable_Table interface {\n\tisStatTable_Table()\n}\n\ntype StatTable_PodGroup_ struct {\n\tPodGroup *StatTable_PodGroup `protobuf:\"bytes,1,opt,name=pod_group,json=podGroup,proto3,oneof\"`\n}\n\nfunc (*StatTable_PodGroup_) isStatTable_Table() {}\n\ntype EdgesRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSelector *ResourceSelection `protobuf:\"bytes,1,opt,name=selector,proto3\" json:\"selector,omitempty\"`\n}\n\nfunc (x *EdgesRequest) Reset() {\n\t*x = EdgesRequest{}\n\tmi := &file_viz_proto_msgTypes[28]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *EdgesRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*EdgesRequest) ProtoMessage() {}\n\nfunc (x *EdgesRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 EdgesRequest.ProtoReflect.Descriptor instead.\nfunc (*EdgesRequest) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{28}\n}\n\nfunc (x *EdgesRequest) GetSelector() *ResourceSelection {\n\tif x != nil {\n\t\treturn x.Selector\n\t}\n\treturn nil\n}\n\ntype EdgesResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Response:\n\t//\n\t//\t*EdgesResponse_Ok_\n\t//\t*EdgesResponse_Error\n\tResponse isEdgesResponse_Response `protobuf_oneof:\"response\"`\n}\n\nfunc (x *EdgesResponse) Reset() {\n\t*x = EdgesResponse{}\n\tmi := &file_viz_proto_msgTypes[29]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *EdgesResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*EdgesResponse) ProtoMessage() {}\n\nfunc (x *EdgesResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 EdgesResponse.ProtoReflect.Descriptor instead.\nfunc (*EdgesResponse) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{29}\n}\n\nfunc (m *EdgesResponse) GetResponse() isEdgesResponse_Response {\n\tif m != nil {\n\t\treturn m.Response\n\t}\n\treturn nil\n}\n\nfunc (x *EdgesResponse) GetOk() *EdgesResponse_Ok {\n\tif x, ok := x.GetResponse().(*EdgesResponse_Ok_); ok {\n\t\treturn x.Ok\n\t}\n\treturn nil\n}\n\nfunc (x *EdgesResponse) GetError() *ResourceError {\n\tif x, ok := x.GetResponse().(*EdgesResponse_Error); ok {\n\t\treturn x.Error\n\t}\n\treturn nil\n}\n\ntype isEdgesResponse_Response interface {\n\tisEdgesResponse_Response()\n}\n\ntype EdgesResponse_Ok_ struct {\n\tOk *EdgesResponse_Ok `protobuf:\"bytes,1,opt,name=ok,proto3,oneof\"`\n}\n\ntype EdgesResponse_Error struct {\n\tError *ResourceError `protobuf:\"bytes,2,opt,name=error,proto3,oneof\"`\n}\n\nfunc (*EdgesResponse_Ok_) isEdgesResponse_Response() {}\n\nfunc (*EdgesResponse_Error) isEdgesResponse_Response() {}\n\ntype Edge struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSrc           *Resource `protobuf:\"bytes,1,opt,name=src,proto3\" json:\"src,omitempty\"`\n\tDst           *Resource `protobuf:\"bytes,2,opt,name=dst,proto3\" json:\"dst,omitempty\"`\n\tClientId      string    `protobuf:\"bytes,3,opt,name=client_id,json=clientId,proto3\" json:\"client_id,omitempty\"`\n\tServerId      string    `protobuf:\"bytes,4,opt,name=server_id,json=serverId,proto3\" json:\"server_id,omitempty\"`\n\tNoIdentityMsg string    `protobuf:\"bytes,5,opt,name=no_identity_msg,json=noIdentityMsg,proto3\" json:\"no_identity_msg,omitempty\"`\n}\n\nfunc (x *Edge) Reset() {\n\t*x = Edge{}\n\tmi := &file_viz_proto_msgTypes[30]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Edge) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Edge) ProtoMessage() {}\n\nfunc (x *Edge) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 Edge.ProtoReflect.Descriptor instead.\nfunc (*Edge) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{30}\n}\n\nfunc (x *Edge) GetSrc() *Resource {\n\tif x != nil {\n\t\treturn x.Src\n\t}\n\treturn nil\n}\n\nfunc (x *Edge) GetDst() *Resource {\n\tif x != nil {\n\t\treturn x.Dst\n\t}\n\treturn nil\n}\n\nfunc (x *Edge) GetClientId() string {\n\tif x != nil {\n\t\treturn x.ClientId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Edge) GetServerId() string {\n\tif x != nil {\n\t\treturn x.ServerId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Edge) GetNoIdentityMsg() string {\n\tif x != nil {\n\t\treturn x.NoIdentityMsg\n\t}\n\treturn \"\"\n}\n\ntype TopRoutesRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSelector   *ResourceSelection `protobuf:\"bytes,1,opt,name=selector,proto3\" json:\"selector,omitempty\"`\n\tTimeWindow string             `protobuf:\"bytes,2,opt,name=time_window,json=timeWindow,proto3\" json:\"time_window,omitempty\"`\n\t// Types that are assignable to Outbound:\n\t//\n\t//\t*TopRoutesRequest_None\n\t//\t*TopRoutesRequest_ToResource\n\tOutbound isTopRoutesRequest_Outbound `protobuf_oneof:\"outbound\"`\n}\n\nfunc (x *TopRoutesRequest) Reset() {\n\t*x = TopRoutesRequest{}\n\tmi := &file_viz_proto_msgTypes[31]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TopRoutesRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TopRoutesRequest) ProtoMessage() {}\n\nfunc (x *TopRoutesRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 TopRoutesRequest.ProtoReflect.Descriptor instead.\nfunc (*TopRoutesRequest) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{31}\n}\n\nfunc (x *TopRoutesRequest) GetSelector() *ResourceSelection {\n\tif x != nil {\n\t\treturn x.Selector\n\t}\n\treturn nil\n}\n\nfunc (x *TopRoutesRequest) GetTimeWindow() string {\n\tif x != nil {\n\t\treturn x.TimeWindow\n\t}\n\treturn \"\"\n}\n\nfunc (m *TopRoutesRequest) GetOutbound() isTopRoutesRequest_Outbound {\n\tif m != nil {\n\t\treturn m.Outbound\n\t}\n\treturn nil\n}\n\nfunc (x *TopRoutesRequest) GetNone() *Empty {\n\tif x, ok := x.GetOutbound().(*TopRoutesRequest_None); ok {\n\t\treturn x.None\n\t}\n\treturn nil\n}\n\nfunc (x *TopRoutesRequest) GetToResource() *Resource {\n\tif x, ok := x.GetOutbound().(*TopRoutesRequest_ToResource); ok {\n\t\treturn x.ToResource\n\t}\n\treturn nil\n}\n\ntype isTopRoutesRequest_Outbound interface {\n\tisTopRoutesRequest_Outbound()\n}\n\ntype TopRoutesRequest_None struct {\n\tNone *Empty `protobuf:\"bytes,3,opt,name=none,proto3,oneof\"`\n}\n\ntype TopRoutesRequest_ToResource struct {\n\tToResource *Resource `protobuf:\"bytes,7,opt,name=to_resource,json=toResource,proto3,oneof\"`\n}\n\nfunc (*TopRoutesRequest_None) isTopRoutesRequest_Outbound() {}\n\nfunc (*TopRoutesRequest_ToResource) isTopRoutesRequest_Outbound() {}\n\ntype TopRoutesResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Response:\n\t//\n\t//\t*TopRoutesResponse_Error\n\t//\t*TopRoutesResponse_Ok_\n\tResponse isTopRoutesResponse_Response `protobuf_oneof:\"response\"`\n}\n\nfunc (x *TopRoutesResponse) Reset() {\n\t*x = TopRoutesResponse{}\n\tmi := &file_viz_proto_msgTypes[32]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TopRoutesResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TopRoutesResponse) ProtoMessage() {}\n\nfunc (x *TopRoutesResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 TopRoutesResponse.ProtoReflect.Descriptor instead.\nfunc (*TopRoutesResponse) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{32}\n}\n\nfunc (m *TopRoutesResponse) GetResponse() isTopRoutesResponse_Response {\n\tif m != nil {\n\t\treturn m.Response\n\t}\n\treturn nil\n}\n\nfunc (x *TopRoutesResponse) GetError() *ResourceError {\n\tif x, ok := x.GetResponse().(*TopRoutesResponse_Error); ok {\n\t\treturn x.Error\n\t}\n\treturn nil\n}\n\nfunc (x *TopRoutesResponse) GetOk() *TopRoutesResponse_Ok {\n\tif x, ok := x.GetResponse().(*TopRoutesResponse_Ok_); ok {\n\t\treturn x.Ok\n\t}\n\treturn nil\n}\n\ntype isTopRoutesResponse_Response interface {\n\tisTopRoutesResponse_Response()\n}\n\ntype TopRoutesResponse_Error struct {\n\tError *ResourceError `protobuf:\"bytes,2,opt,name=error,proto3,oneof\"`\n}\n\ntype TopRoutesResponse_Ok_ struct {\n\tOk *TopRoutesResponse_Ok `protobuf:\"bytes,3,opt,name=ok,proto3,oneof\"`\n}\n\nfunc (*TopRoutesResponse_Error) isTopRoutesResponse_Response() {}\n\nfunc (*TopRoutesResponse_Ok_) isTopRoutesResponse_Response() {}\n\ntype RouteTable struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tRows     []*RouteTable_Row `protobuf:\"bytes,1,rep,name=rows,proto3\" json:\"rows,omitempty\"`\n\tResource string            `protobuf:\"bytes,2,opt,name=resource,proto3\" json:\"resource,omitempty\"`\n}\n\nfunc (x *RouteTable) Reset() {\n\t*x = RouteTable{}\n\tmi := &file_viz_proto_msgTypes[33]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RouteTable) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RouteTable) ProtoMessage() {}\n\nfunc (x *RouteTable) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 RouteTable.ProtoReflect.Descriptor instead.\nfunc (*RouteTable) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{33}\n}\n\nfunc (x *RouteTable) GetRows() []*RouteTable_Row {\n\tif x != nil {\n\t\treturn x.Rows\n\t}\n\treturn nil\n}\n\nfunc (x *RouteTable) GetResource() string {\n\tif x != nil {\n\t\treturn x.Resource\n\t}\n\treturn \"\"\n}\n\ntype GatewaysTable struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tRows []*GatewaysTable_Row `protobuf:\"bytes,1,rep,name=rows,proto3\" json:\"rows,omitempty\"`\n}\n\nfunc (x *GatewaysTable) Reset() {\n\t*x = GatewaysTable{}\n\tmi := &file_viz_proto_msgTypes[34]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GatewaysTable) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GatewaysTable) ProtoMessage() {}\n\nfunc (x *GatewaysTable) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 GatewaysTable.ProtoReflect.Descriptor instead.\nfunc (*GatewaysTable) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{34}\n}\n\nfunc (x *GatewaysTable) GetRows() []*GatewaysTable_Row {\n\tif x != nil {\n\t\treturn x.Rows\n\t}\n\treturn nil\n}\n\ntype GatewaysRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tRemoteClusterName string `protobuf:\"bytes,1,opt,name=remote_cluster_name,json=remoteClusterName,proto3\" json:\"remote_cluster_name,omitempty\"`\n\tGatewayNamespace  string `protobuf:\"bytes,2,opt,name=gateway_namespace,json=gatewayNamespace,proto3\" json:\"gateway_namespace,omitempty\"`\n\tTimeWindow        string `protobuf:\"bytes,3,opt,name=time_window,json=timeWindow,proto3\" json:\"time_window,omitempty\"`\n}\n\nfunc (x *GatewaysRequest) Reset() {\n\t*x = GatewaysRequest{}\n\tmi := &file_viz_proto_msgTypes[35]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GatewaysRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GatewaysRequest) ProtoMessage() {}\n\nfunc (x *GatewaysRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 GatewaysRequest.ProtoReflect.Descriptor instead.\nfunc (*GatewaysRequest) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{35}\n}\n\nfunc (x *GatewaysRequest) GetRemoteClusterName() string {\n\tif x != nil {\n\t\treturn x.RemoteClusterName\n\t}\n\treturn \"\"\n}\n\nfunc (x *GatewaysRequest) GetGatewayNamespace() string {\n\tif x != nil {\n\t\treturn x.GatewayNamespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *GatewaysRequest) GetTimeWindow() string {\n\tif x != nil {\n\t\treturn x.TimeWindow\n\t}\n\treturn \"\"\n}\n\ntype GatewaysResponse struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Response:\n\t//\n\t//\t*GatewaysResponse_Ok_\n\t//\t*GatewaysResponse_Error\n\tResponse isGatewaysResponse_Response `protobuf_oneof:\"response\"`\n}\n\nfunc (x *GatewaysResponse) Reset() {\n\t*x = GatewaysResponse{}\n\tmi := &file_viz_proto_msgTypes[36]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GatewaysResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GatewaysResponse) ProtoMessage() {}\n\nfunc (x *GatewaysResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 GatewaysResponse.ProtoReflect.Descriptor instead.\nfunc (*GatewaysResponse) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{36}\n}\n\nfunc (m *GatewaysResponse) GetResponse() isGatewaysResponse_Response {\n\tif m != nil {\n\t\treturn m.Response\n\t}\n\treturn nil\n}\n\nfunc (x *GatewaysResponse) GetOk() *GatewaysResponse_Ok {\n\tif x, ok := x.GetResponse().(*GatewaysResponse_Ok_); ok {\n\t\treturn x.Ok\n\t}\n\treturn nil\n}\n\nfunc (x *GatewaysResponse) GetError() *ResourceError {\n\tif x, ok := x.GetResponse().(*GatewaysResponse_Error); ok {\n\t\treturn x.Error\n\t}\n\treturn nil\n}\n\ntype isGatewaysResponse_Response interface {\n\tisGatewaysResponse_Response()\n}\n\ntype GatewaysResponse_Ok_ struct {\n\tOk *GatewaysResponse_Ok `protobuf:\"bytes,1,opt,name=ok,proto3,oneof\"`\n}\n\ntype GatewaysResponse_Error struct {\n\tError *ResourceError `protobuf:\"bytes,2,opt,name=error,proto3,oneof\"`\n}\n\nfunc (*GatewaysResponse_Ok_) isGatewaysResponse_Response() {}\n\nfunc (*GatewaysResponse_Error) isGatewaysResponse_Response() {}\n\ntype Headers_Header struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// The name of a header in a request.\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// The value of a header in a request. If the value consists entirely of\n\t// UTF-8 encodings, `value` will be set; otherwise a binary value is\n\t// assumed and `value_bin` will be set.\n\t//\n\t// Types that are assignable to Value:\n\t//\n\t//\t*Headers_Header_ValueStr\n\t//\t*Headers_Header_ValueBin\n\tValue isHeaders_Header_Value `protobuf_oneof:\"value\"`\n}\n\nfunc (x *Headers_Header) Reset() {\n\t*x = Headers_Header{}\n\tmi := &file_viz_proto_msgTypes[37]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Headers_Header) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Headers_Header) ProtoMessage() {}\n\nfunc (x *Headers_Header) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 Headers_Header.ProtoReflect.Descriptor instead.\nfunc (*Headers_Header) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{12, 0}\n}\n\nfunc (x *Headers_Header) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (m *Headers_Header) GetValue() isHeaders_Header_Value {\n\tif m != nil {\n\t\treturn m.Value\n\t}\n\treturn nil\n}\n\nfunc (x *Headers_Header) GetValueStr() string {\n\tif x, ok := x.GetValue().(*Headers_Header_ValueStr); ok {\n\t\treturn x.ValueStr\n\t}\n\treturn \"\"\n}\n\nfunc (x *Headers_Header) GetValueBin() []byte {\n\tif x, ok := x.GetValue().(*Headers_Header_ValueBin); ok {\n\t\treturn x.ValueBin\n\t}\n\treturn nil\n}\n\ntype isHeaders_Header_Value interface {\n\tisHeaders_Header_Value()\n}\n\ntype Headers_Header_ValueStr struct {\n\tValueStr string `protobuf:\"bytes,2,opt,name=value_str,json=valueStr,proto3,oneof\"`\n}\n\ntype Headers_Header_ValueBin struct {\n\tValueBin []byte `protobuf:\"bytes,3,opt,name=value_bin,json=valueBin,proto3,oneof\"`\n}\n\nfunc (*Headers_Header_ValueStr) isHeaders_Header_Value() {}\n\nfunc (*Headers_Header_ValueBin) isHeaders_Header_Value() {}\n\ntype PodErrors_PodError struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Error:\n\t//\n\t//\t*PodErrors_PodError_Container\n\tError isPodErrors_PodError_Error `protobuf_oneof:\"error\"`\n}\n\nfunc (x *PodErrors_PodError) Reset() {\n\t*x = PodErrors_PodError{}\n\tmi := &file_viz_proto_msgTypes[38]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PodErrors_PodError) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PodErrors_PodError) ProtoMessage() {}\n\nfunc (x *PodErrors_PodError) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 PodErrors_PodError.ProtoReflect.Descriptor instead.\nfunc (*PodErrors_PodError) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{15, 0}\n}\n\nfunc (m *PodErrors_PodError) GetError() isPodErrors_PodError_Error {\n\tif m != nil {\n\t\treturn m.Error\n\t}\n\treturn nil\n}\n\nfunc (x *PodErrors_PodError) GetContainer() *PodErrors_PodError_ContainerError {\n\tif x, ok := x.GetError().(*PodErrors_PodError_Container); ok {\n\t\treturn x.Container\n\t}\n\treturn nil\n}\n\ntype isPodErrors_PodError_Error interface {\n\tisPodErrors_PodError_Error()\n}\n\ntype PodErrors_PodError_Container struct {\n\tContainer *PodErrors_PodError_ContainerError `protobuf:\"bytes,1,opt,name=container,proto3,oneof\"`\n}\n\nfunc (*PodErrors_PodError_Container) isPodErrors_PodError_Error() {}\n\n// To report init-container and container failures\ntype PodErrors_PodError_ContainerError struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tMessage   string `protobuf:\"bytes,1,opt,name=message,proto3\" json:\"message,omitempty\"`\n\tContainer string `protobuf:\"bytes,2,opt,name=container,proto3\" json:\"container,omitempty\"`\n\tImage     string `protobuf:\"bytes,3,opt,name=image,proto3\" json:\"image,omitempty\"`\n\tReason    string `protobuf:\"bytes,4,opt,name=reason,proto3\" json:\"reason,omitempty\"`\n}\n\nfunc (x *PodErrors_PodError_ContainerError) Reset() {\n\t*x = PodErrors_PodError_ContainerError{}\n\tmi := &file_viz_proto_msgTypes[39]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PodErrors_PodError_ContainerError) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PodErrors_PodError_ContainerError) ProtoMessage() {}\n\nfunc (x *PodErrors_PodError_ContainerError) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 PodErrors_PodError_ContainerError.ProtoReflect.Descriptor instead.\nfunc (*PodErrors_PodError_ContainerError) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{15, 0, 0}\n}\n\nfunc (x *PodErrors_PodError_ContainerError) GetMessage() string {\n\tif x != nil {\n\t\treturn x.Message\n\t}\n\treturn \"\"\n}\n\nfunc (x *PodErrors_PodError_ContainerError) GetContainer() string {\n\tif x != nil {\n\t\treturn x.Container\n\t}\n\treturn \"\"\n}\n\nfunc (x *PodErrors_PodError_ContainerError) GetImage() string {\n\tif x != nil {\n\t\treturn x.Image\n\t}\n\treturn \"\"\n}\n\nfunc (x *PodErrors_PodError_ContainerError) GetReason() string {\n\tif x != nil {\n\t\treturn x.Reason\n\t}\n\treturn \"\"\n}\n\ntype StatSummaryResponse_Ok struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tStatTables []*StatTable `protobuf:\"bytes,1,rep,name=stat_tables,json=statTables,proto3\" json:\"stat_tables,omitempty\"`\n}\n\nfunc (x *StatSummaryResponse_Ok) Reset() {\n\t*x = StatSummaryResponse_Ok{}\n\tmi := &file_viz_proto_msgTypes[40]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *StatSummaryResponse_Ok) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*StatSummaryResponse_Ok) ProtoMessage() {}\n\nfunc (x *StatSummaryResponse_Ok) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 StatSummaryResponse_Ok.ProtoReflect.Descriptor instead.\nfunc (*StatSummaryResponse_Ok) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{20, 0}\n}\n\nfunc (x *StatSummaryResponse_Ok) GetStatTables() []*StatTable {\n\tif x != nil {\n\t\treturn x.StatTables\n\t}\n\treturn nil\n}\n\ntype AuthzResponse_Ok struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tStatTable *StatTable `protobuf:\"bytes,1,opt,name=stat_table,json=statTable,proto3\" json:\"stat_table,omitempty\"`\n}\n\nfunc (x *AuthzResponse_Ok) Reset() {\n\t*x = AuthzResponse_Ok{}\n\tmi := &file_viz_proto_msgTypes[41]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AuthzResponse_Ok) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AuthzResponse_Ok) ProtoMessage() {}\n\nfunc (x *AuthzResponse_Ok) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 AuthzResponse_Ok.ProtoReflect.Descriptor instead.\nfunc (*AuthzResponse_Ok) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{22, 0}\n}\n\nfunc (x *AuthzResponse_Ok) GetStatTable() *StatTable {\n\tif x != nil {\n\t\treturn x.StatTable\n\t}\n\treturn nil\n}\n\ntype StatTable_PodGroup struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tRows []*StatTable_PodGroup_Row `protobuf:\"bytes,1,rep,name=rows,proto3\" json:\"rows,omitempty\"`\n}\n\nfunc (x *StatTable_PodGroup) Reset() {\n\t*x = StatTable_PodGroup{}\n\tmi := &file_viz_proto_msgTypes[42]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *StatTable_PodGroup) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*StatTable_PodGroup) ProtoMessage() {}\n\nfunc (x *StatTable_PodGroup) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 StatTable_PodGroup.ProtoReflect.Descriptor instead.\nfunc (*StatTable_PodGroup) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{27, 0}\n}\n\nfunc (x *StatTable_PodGroup) GetRows() []*StatTable_PodGroup_Row {\n\tif x != nil {\n\t\treturn x.Rows\n\t}\n\treturn nil\n}\n\ntype StatTable_PodGroup_Row struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tResource   *Resource `protobuf:\"bytes,1,opt,name=resource,proto3\" json:\"resource,omitempty\"`\n\tTimeWindow string    `protobuf:\"bytes,2,opt,name=time_window,json=timeWindow,proto3\" json:\"time_window,omitempty\"`\n\t// pod status on Kubernetes\n\tStatus string `protobuf:\"bytes,9,opt,name=status,proto3\" json:\"status,omitempty\"`\n\t// number of pending or running pods in this resource that have linkerd injected\n\tMeshedPodCount uint64 `protobuf:\"varint,3,opt,name=meshed_pod_count,json=meshedPodCount,proto3\" json:\"meshed_pod_count,omitempty\"`\n\t// number of pending or running pods in this resource\n\tRunningPodCount uint64 `protobuf:\"varint,4,opt,name=running_pod_count,json=runningPodCount,proto3\" json:\"running_pod_count,omitempty\"`\n\t// number of pods in this resource that have Phase PodFailed\n\tFailedPodCount uint64             `protobuf:\"varint,6,opt,name=failed_pod_count,json=failedPodCount,proto3\" json:\"failed_pod_count,omitempty\"`\n\tStats          *BasicStats        `protobuf:\"bytes,5,opt,name=stats,proto3\" json:\"stats,omitempty\"`\n\tTcpStats       *TcpStats          `protobuf:\"bytes,8,opt,name=tcp_stats,json=tcpStats,proto3\" json:\"tcp_stats,omitempty\"`\n\tTsStats        *TrafficSplitStats `protobuf:\"bytes,10,opt,name=ts_stats,json=tsStats,proto3\" json:\"ts_stats,omitempty\"`\n\tSrvStats       *ServerStats       `protobuf:\"bytes,11,opt,name=srv_stats,json=srvStats,proto3\" json:\"srv_stats,omitempty\"`\n\t// Stores a set of errors for each pod name. If a pod has no errors, it may be omitted.\n\tErrorsByPod map[string]*PodErrors `protobuf:\"bytes,7,rep,name=errors_by_pod,json=errorsByPod,proto3\" json:\"errors_by_pod,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"bytes,2,opt,name=value,proto3\"`\n}\n\nfunc (x *StatTable_PodGroup_Row) Reset() {\n\t*x = StatTable_PodGroup_Row{}\n\tmi := &file_viz_proto_msgTypes[43]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *StatTable_PodGroup_Row) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*StatTable_PodGroup_Row) ProtoMessage() {}\n\nfunc (x *StatTable_PodGroup_Row) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 StatTable_PodGroup_Row.ProtoReflect.Descriptor instead.\nfunc (*StatTable_PodGroup_Row) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{27, 0, 0}\n}\n\nfunc (x *StatTable_PodGroup_Row) GetResource() *Resource {\n\tif x != nil {\n\t\treturn x.Resource\n\t}\n\treturn nil\n}\n\nfunc (x *StatTable_PodGroup_Row) GetTimeWindow() string {\n\tif x != nil {\n\t\treturn x.TimeWindow\n\t}\n\treturn \"\"\n}\n\nfunc (x *StatTable_PodGroup_Row) GetStatus() string {\n\tif x != nil {\n\t\treturn x.Status\n\t}\n\treturn \"\"\n}\n\nfunc (x *StatTable_PodGroup_Row) GetMeshedPodCount() uint64 {\n\tif x != nil {\n\t\treturn x.MeshedPodCount\n\t}\n\treturn 0\n}\n\nfunc (x *StatTable_PodGroup_Row) GetRunningPodCount() uint64 {\n\tif x != nil {\n\t\treturn x.RunningPodCount\n\t}\n\treturn 0\n}\n\nfunc (x *StatTable_PodGroup_Row) GetFailedPodCount() uint64 {\n\tif x != nil {\n\t\treturn x.FailedPodCount\n\t}\n\treturn 0\n}\n\nfunc (x *StatTable_PodGroup_Row) GetStats() *BasicStats {\n\tif x != nil {\n\t\treturn x.Stats\n\t}\n\treturn nil\n}\n\nfunc (x *StatTable_PodGroup_Row) GetTcpStats() *TcpStats {\n\tif x != nil {\n\t\treturn x.TcpStats\n\t}\n\treturn nil\n}\n\nfunc (x *StatTable_PodGroup_Row) GetTsStats() *TrafficSplitStats {\n\tif x != nil {\n\t\treturn x.TsStats\n\t}\n\treturn nil\n}\n\nfunc (x *StatTable_PodGroup_Row) GetSrvStats() *ServerStats {\n\tif x != nil {\n\t\treturn x.SrvStats\n\t}\n\treturn nil\n}\n\nfunc (x *StatTable_PodGroup_Row) GetErrorsByPod() map[string]*PodErrors {\n\tif x != nil {\n\t\treturn x.ErrorsByPod\n\t}\n\treturn nil\n}\n\ntype EdgesResponse_Ok struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEdges []*Edge `protobuf:\"bytes,1,rep,name=edges,proto3\" json:\"edges,omitempty\"`\n}\n\nfunc (x *EdgesResponse_Ok) Reset() {\n\t*x = EdgesResponse_Ok{}\n\tmi := &file_viz_proto_msgTypes[45]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *EdgesResponse_Ok) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*EdgesResponse_Ok) ProtoMessage() {}\n\nfunc (x *EdgesResponse_Ok) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 EdgesResponse_Ok.ProtoReflect.Descriptor instead.\nfunc (*EdgesResponse_Ok) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{29, 0}\n}\n\nfunc (x *EdgesResponse_Ok) GetEdges() []*Edge {\n\tif x != nil {\n\t\treturn x.Edges\n\t}\n\treturn nil\n}\n\ntype TopRoutesResponse_Ok struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tRoutes []*RouteTable `protobuf:\"bytes,1,rep,name=routes,proto3\" json:\"routes,omitempty\"`\n}\n\nfunc (x *TopRoutesResponse_Ok) Reset() {\n\t*x = TopRoutesResponse_Ok{}\n\tmi := &file_viz_proto_msgTypes[46]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TopRoutesResponse_Ok) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TopRoutesResponse_Ok) ProtoMessage() {}\n\nfunc (x *TopRoutesResponse_Ok) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 TopRoutesResponse_Ok.ProtoReflect.Descriptor instead.\nfunc (*TopRoutesResponse_Ok) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{32, 0}\n}\n\nfunc (x *TopRoutesResponse_Ok) GetRoutes() []*RouteTable {\n\tif x != nil {\n\t\treturn x.Routes\n\t}\n\treturn nil\n}\n\ntype RouteTable_Row struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tRoute      string      `protobuf:\"bytes,1,opt,name=route,proto3\" json:\"route,omitempty\"`\n\tTimeWindow string      `protobuf:\"bytes,2,opt,name=time_window,json=timeWindow,proto3\" json:\"time_window,omitempty\"`\n\tAuthority  string      `protobuf:\"bytes,6,opt,name=authority,proto3\" json:\"authority,omitempty\"`\n\tStats      *BasicStats `protobuf:\"bytes,5,opt,name=stats,proto3\" json:\"stats,omitempty\"`\n}\n\nfunc (x *RouteTable_Row) Reset() {\n\t*x = RouteTable_Row{}\n\tmi := &file_viz_proto_msgTypes[47]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RouteTable_Row) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RouteTable_Row) ProtoMessage() {}\n\nfunc (x *RouteTable_Row) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 RouteTable_Row.ProtoReflect.Descriptor instead.\nfunc (*RouteTable_Row) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{33, 0}\n}\n\nfunc (x *RouteTable_Row) GetRoute() string {\n\tif x != nil {\n\t\treturn x.Route\n\t}\n\treturn \"\"\n}\n\nfunc (x *RouteTable_Row) GetTimeWindow() string {\n\tif x != nil {\n\t\treturn x.TimeWindow\n\t}\n\treturn \"\"\n}\n\nfunc (x *RouteTable_Row) GetAuthority() string {\n\tif x != nil {\n\t\treturn x.Authority\n\t}\n\treturn \"\"\n}\n\nfunc (x *RouteTable_Row) GetStats() *BasicStats {\n\tif x != nil {\n\t\treturn x.Stats\n\t}\n\treturn nil\n}\n\ntype GatewaysTable_Row struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tNamespace      string `protobuf:\"bytes,1,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n\tName           string `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tClusterName    string `protobuf:\"bytes,3,opt,name=cluster_name,json=clusterName,proto3\" json:\"cluster_name,omitempty\"`\n\tPairedServices uint64 `protobuf:\"varint,4,opt,name=paired_services,json=pairedServices,proto3\" json:\"paired_services,omitempty\"`\n\tAlive          bool   `protobuf:\"varint,5,opt,name=alive,proto3\" json:\"alive,omitempty\"`\n\tLatencyMsP50   uint64 `protobuf:\"varint,6,opt,name=latency_ms_p50,json=latencyMsP50,proto3\" json:\"latency_ms_p50,omitempty\"`\n\tLatencyMsP95   uint64 `protobuf:\"varint,7,opt,name=latency_ms_p95,json=latencyMsP95,proto3\" json:\"latency_ms_p95,omitempty\"`\n\tLatencyMsP99   uint64 `protobuf:\"varint,8,opt,name=latency_ms_p99,json=latencyMsP99,proto3\" json:\"latency_ms_p99,omitempty\"`\n}\n\nfunc (x *GatewaysTable_Row) Reset() {\n\t*x = GatewaysTable_Row{}\n\tmi := &file_viz_proto_msgTypes[48]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GatewaysTable_Row) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GatewaysTable_Row) ProtoMessage() {}\n\nfunc (x *GatewaysTable_Row) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 GatewaysTable_Row.ProtoReflect.Descriptor instead.\nfunc (*GatewaysTable_Row) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{34, 0}\n}\n\nfunc (x *GatewaysTable_Row) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *GatewaysTable_Row) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *GatewaysTable_Row) GetClusterName() string {\n\tif x != nil {\n\t\treturn x.ClusterName\n\t}\n\treturn \"\"\n}\n\nfunc (x *GatewaysTable_Row) GetPairedServices() uint64 {\n\tif x != nil {\n\t\treturn x.PairedServices\n\t}\n\treturn 0\n}\n\nfunc (x *GatewaysTable_Row) GetAlive() bool {\n\tif x != nil {\n\t\treturn x.Alive\n\t}\n\treturn false\n}\n\nfunc (x *GatewaysTable_Row) GetLatencyMsP50() uint64 {\n\tif x != nil {\n\t\treturn x.LatencyMsP50\n\t}\n\treturn 0\n}\n\nfunc (x *GatewaysTable_Row) GetLatencyMsP95() uint64 {\n\tif x != nil {\n\t\treturn x.LatencyMsP95\n\t}\n\treturn 0\n}\n\nfunc (x *GatewaysTable_Row) GetLatencyMsP99() uint64 {\n\tif x != nil {\n\t\treturn x.LatencyMsP99\n\t}\n\treturn 0\n}\n\ntype GatewaysResponse_Ok struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tGatewaysTable *GatewaysTable `protobuf:\"bytes,1,opt,name=gateways_table,json=gatewaysTable,proto3\" json:\"gateways_table,omitempty\"`\n}\n\nfunc (x *GatewaysResponse_Ok) Reset() {\n\t*x = GatewaysResponse_Ok{}\n\tmi := &file_viz_proto_msgTypes[49]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GatewaysResponse_Ok) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GatewaysResponse_Ok) ProtoMessage() {}\n\nfunc (x *GatewaysResponse_Ok) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_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 GatewaysResponse_Ok.ProtoReflect.Descriptor instead.\nfunc (*GatewaysResponse_Ok) Descriptor() ([]byte, []int) {\n\treturn file_viz_proto_rawDescGZIP(), []int{36, 0}\n}\n\nfunc (x *GatewaysResponse_Ok) GetGatewaysTable() *GatewaysTable {\n\tif x != nil {\n\t\treturn x.GatewaysTable\n\t}\n\treturn nil\n}\n\nvar File_viz_proto protoreflect.FileDescriptor\n\nvar file_viz_proto_rawDesc = []byte{\n\t0x0a, 0x09, 0x76, 0x69, 0x7a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x6c, 0x69, 0x6e,\n\t0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,\n\t0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70,\n\t0x74, 0x79, 0x22, 0xc8, 0x01, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75,\n\t0x6c, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4e,\n\t0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x53, 0x75, 0x62, 0x73, 0x79,\n\t0x73, 0x74, 0x65, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x10, 0x43, 0x68, 0x65, 0x63,\n\t0x6b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x10, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,\n\t0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,\n\t0x76, 0x69, 0x7a, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,\n\t0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x34, 0x0a, 0x15, 0x46, 0x72, 0x69, 0x65, 0x6e,\n\t0x64, 0x6c, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x55, 0x73, 0x65, 0x72,\n\t0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79,\n\t0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x55, 0x73, 0x65, 0x72, 0x22, 0x12, 0x0a,\n\t0x10, 0x53, 0x65, 0x6c, 0x66, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x22, 0x48, 0x0a, 0x11, 0x53, 0x65, 0x6c, 0x66, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65,\n\t0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74,\n\t0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72,\n\t0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75,\n\t0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x33, 0x0a, 0x13, 0x4c,\n\t0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,\n\t0x22, 0x49, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,\n\t0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76,\n\t0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x6e,\n\t0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,\n\t0x65, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x3b, 0x0a, 0x07, 0x53,\n\t0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61,\n\t0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e,\n\t0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x4e, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74,\n\t0x50, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x08, 0x73,\n\t0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73,\n\t0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08,\n\t0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x39, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74,\n\t0x50, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x04,\n\t0x70, 0x6f, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x6e,\n\t0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x50, 0x6f, 0x64, 0x52, 0x04, 0x70,\n\t0x6f, 0x64, 0x73, 0x22, 0xfa, 0x04, 0x0a, 0x03, 0x50, 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e,\n\t0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,\n\t0x14, 0x0a, 0x05, 0x70, 0x6f, 0x64, 0x49, 0x50, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,\n\t0x70, 0x6f, 0x64, 0x49, 0x50, 0x12, 0x20, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d,\n\t0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x65, 0x70,\n\t0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0b, 0x72, 0x65, 0x70, 0x6c, 0x69,\n\t0x63, 0x61, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a,\n\t0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x53, 0x65, 0x74, 0x12, 0x37, 0x0a, 0x16, 0x72, 0x65,\n\t0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,\n\t0x6c, 0x6c, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x15, 0x72, 0x65,\n\t0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,\n\t0x6c, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x65, 0x66, 0x75, 0x6c, 0x5f,\n\t0x73, 0x65, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x61,\n\t0x74, 0x65, 0x66, 0x75, 0x6c, 0x53, 0x65, 0x74, 0x12, 0x1f, 0x0a, 0x0a, 0x64, 0x61, 0x65, 0x6d,\n\t0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09,\n\t0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x03, 0x6a, 0x6f, 0x62,\n\t0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x6a, 0x6f, 0x62, 0x12, 0x16, 0x0a,\n\t0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73,\n\t0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x05,\n\t0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x0f, 0x73,\n\t0x69, 0x6e, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,\n\t0x0f, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74,\n\t0x12, 0x30, 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x4e, 0x61,\n\t0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x63,\n\t0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,\n\t0x63, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61,\n\t0x6e, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,\n\t0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65,\n\t0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f,\n\t0x6e, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x72, 0x6f,\n\t0x78, 0x79, 0x52, 0x65, 0x61, 0x64, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x70,\n\t0x72, 0x6f, 0x78, 0x79, 0x52, 0x65, 0x61, 0x64, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x6f,\n\t0x78, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x0c, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a,\n\t0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,\n\t0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,\n\t0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72,\n\t0x22, 0xf1, 0x01, 0x0a, 0x0a, 0x48, 0x74, 0x74, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12,\n\t0x45, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76,\n\t0x69, 0x7a, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x2e, 0x52, 0x65,\n\t0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69,\n\t0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0c, 0x75, 0x6e, 0x72, 0x65, 0x67, 0x69,\n\t0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c,\n\t0x75, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x22, 0x6e, 0x0a, 0x0a,\n\t0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x07, 0x0a, 0x03, 0x47, 0x45,\n\t0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f, 0x53, 0x54, 0x10, 0x01, 0x12, 0x07, 0x0a,\n\t0x03, 0x50, 0x55, 0x54, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45,\n\t0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x41, 0x54, 0x43, 0x48, 0x10, 0x04, 0x12, 0x0b, 0x0a,\n\t0x07, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f,\n\t0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x06, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x45, 0x41, 0x44, 0x10,\n\t0x07, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x08, 0x42, 0x06, 0x0a, 0x04,\n\t0x74, 0x79, 0x70, 0x65, 0x22, 0x9c, 0x01, 0x0a, 0x06, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x12,\n\t0x41, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76,\n\t0x69, 0x7a, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,\n\t0x65, 0x72, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,\n\t0x65, 0x64, 0x12, 0x24, 0x0a, 0x0c, 0x75, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,\n\t0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x75, 0x6e, 0x72, 0x65,\n\t0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x22, 0x21, 0x0a, 0x0a, 0x52, 0x65, 0x67, 0x69,\n\t0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00,\n\t0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x74,\n\t0x79, 0x70, 0x65, 0x22, 0xa6, 0x01, 0x0a, 0x07, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12,\n\t0x36, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,\n\t0x32, 0x1c, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e,\n\t0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x07,\n\t0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x63, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65,\n\t0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x73,\n\t0x74, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x76, 0x61, 0x6c, 0x75,\n\t0x65, 0x53, 0x74, 0x72, 0x12, 0x1d, 0x0a, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x62, 0x69,\n\t0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x08, 0x76, 0x61, 0x6c, 0x75, 0x65,\n\t0x42, 0x69, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x64, 0x0a, 0x03,\n\t0x45, 0x6f, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x74, 0x61, 0x74,\n\t0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52,\n\t0x0e, 0x67, 0x72, 0x70, 0x63, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12,\n\t0x2a, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63,\n\t0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x0e, 0x72, 0x65, 0x73,\n\t0x65, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x42, 0x05, 0x0a, 0x03, 0x65,\n\t0x6e, 0x64, 0x22, 0x20, 0x0a, 0x08, 0x41, 0x70, 0x69, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x14,\n\t0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65,\n\t0x72, 0x72, 0x6f, 0x72, 0x22, 0xa4, 0x02, 0x0a, 0x09, 0x50, 0x6f, 0x64, 0x45, 0x72, 0x72, 0x6f,\n\t0x72, 0x73, 0x12, 0x38, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69,\n\t0x7a, 0x2e, 0x50, 0x6f, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x50, 0x6f, 0x64, 0x45,\n\t0x72, 0x72, 0x6f, 0x72, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0xdc, 0x01, 0x0a,\n\t0x08, 0x50, 0x6f, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4f, 0x0a, 0x09, 0x63, 0x6f, 0x6e,\n\t0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6c,\n\t0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x50, 0x6f, 0x64, 0x45,\n\t0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x50, 0x6f, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x43,\n\t0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52,\n\t0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x1a, 0x76, 0x0a, 0x0e, 0x43, 0x6f,\n\t0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07,\n\t0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d,\n\t0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69,\n\t0x6e, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61,\n\t0x69, 0x6e, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65,\n\t0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73,\n\t0x6f, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x50, 0x0a, 0x08, 0x52,\n\t0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73,\n\t0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65,\n\t0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,\n\t0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x6e, 0x0a,\n\t0x11, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,\n\t0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65,\n\t0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x5f,\n\t0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,\n\t0x6c, 0x61, 0x62, 0x65, 0x6c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x59, 0x0a,\n\t0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x32,\n\t0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e,\n\t0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,\n\t0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xdf, 0x02, 0x0a, 0x12, 0x53, 0x74, 0x61,\n\t0x74, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,\n\t0x3b, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a,\n\t0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b,\n\t0x74, 0x69, 0x6d, 0x65, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x29, 0x0a,\n\t0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x69,\n\t0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,\n\t0x48, 0x00, 0x52, 0x04, 0x6e, 0x6f, 0x6e, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x74, 0x6f, 0x5f, 0x72,\n\t0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73,\n\t0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x74, 0x6f, 0x52, 0x65, 0x73, 0x6f, 0x75,\n\t0x72, 0x63, 0x65, 0x12, 0x3d, 0x0a, 0x0d, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x6f,\n\t0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e,\n\t0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72,\n\t0x63, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x66, 0x72, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72,\n\t0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73,\n\t0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x6b, 0x69, 0x70, 0x53, 0x74, 0x61, 0x74,\n\t0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x63, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x07,\n\t0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x63, 0x70, 0x53, 0x74, 0x61, 0x74, 0x73, 0x42, 0x0a,\n\t0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0xce, 0x01, 0x0a, 0x13, 0x53,\n\t0x74, 0x61, 0x74, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x12, 0x36, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24,\n\t0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x53, 0x74,\n\t0x61, 0x74, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,\n\t0x65, 0x2e, 0x4f, 0x6b, 0x48, 0x00, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x33, 0x0a, 0x05, 0x65, 0x72,\n\t0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b,\n\t0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,\n\t0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a,\n\t0x3e, 0x0a, 0x02, 0x4f, 0x6b, 0x12, 0x38, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x74, 0x61,\n\t0x62, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x69, 0x6e,\n\t0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61,\n\t0x62, 0x6c, 0x65, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x42,\n\t0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x63, 0x0a, 0x0c, 0x41,\n\t0x75, 0x74, 0x68, 0x7a, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x08, 0x72,\n\t0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73,\n\t0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12,\n\t0x1f, 0x0a, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77,\n\t0x22, 0xc0, 0x01, 0x0a, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x12, 0x30, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e,\n\t0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x41, 0x75,\n\t0x74, 0x68, 0x7a, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x6b, 0x48, 0x00,\n\t0x52, 0x02, 0x6f, 0x6b, 0x12, 0x33, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76,\n\t0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72,\n\t0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0x3c, 0x0a, 0x02, 0x4f, 0x6b, 0x12,\n\t0x36, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76,\n\t0x69, 0x7a, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x09, 0x73, 0x74,\n\t0x61, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f,\n\t0x6e, 0x73, 0x65, 0x22, 0xac, 0x02, 0x0a, 0x0a, 0x42, 0x61, 0x73, 0x69, 0x63, 0x53, 0x74, 0x61,\n\t0x74, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6f,\n\t0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x73, 0x75, 0x63, 0x63, 0x65,\n\t0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75,\n\t0x72, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c,\n\t0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e,\n\t0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73, 0x5f, 0x70, 0x35, 0x30, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x73, 0x50,\n\t0x35, 0x30, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73,\n\t0x5f, 0x70, 0x39, 0x35, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65,\n\t0x6e, 0x63, 0x79, 0x4d, 0x73, 0x50, 0x39, 0x35, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x74, 0x65,\n\t0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73, 0x5f, 0x70, 0x39, 0x39, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04,\n\t0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x73, 0x50, 0x39, 0x39, 0x12, 0x30,\n\t0x0a, 0x14, 0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73,\n\t0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x61, 0x63,\n\t0x74, 0x75, 0x61, 0x6c, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74,\n\t0x12, 0x30, 0x0a, 0x14, 0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x75,\n\t0x72, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12,\n\t0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x75,\n\t0x6e, 0x74, 0x22, 0x8b, 0x01, 0x0a, 0x08, 0x54, 0x63, 0x70, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12,\n\t0x29, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x6e, 0x43,\n\t0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65,\n\t0x61, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x72, 0x65, 0x61, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x54,\n\t0x6f, 0x74, 0x61, 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x62, 0x79,\n\t0x74, 0x65, 0x73, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52,\n\t0x0f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x54, 0x6f, 0x74, 0x61, 0x6c,\n\t0x22, 0x53, 0x0a, 0x11, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x53, 0x70, 0x6c, 0x69, 0x74,\n\t0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x70, 0x65, 0x78, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x70, 0x65, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x65, 0x61,\n\t0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x65, 0x61, 0x66, 0x12, 0x16, 0x0a,\n\t0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x77,\n\t0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0xdb, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,\n\t0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64,\n\t0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x61, 0x6c,\n\t0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65,\n\t0x6e, 0x69, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04,\n\t0x52, 0x0b, 0x64, 0x65, 0x6e, 0x69, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a,\n\t0x03, 0x73, 0x72, 0x76, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e,\n\t0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72,\n\t0x63, 0x65, 0x52, 0x03, 0x73, 0x72, 0x76, 0x12, 0x2c, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65,\n\t0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64,\n\t0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x05,\n\t0x72, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x2c, 0x0a, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x18, 0x05,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,\n\t0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x05, 0x61, 0x75,\n\t0x74, 0x68, 0x7a, 0x22, 0x9e, 0x06, 0x0a, 0x09, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x62, 0x6c,\n\t0x65, 0x12, 0x3f, 0x0a, 0x09, 0x70, 0x6f, 0x64, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,\n\t0x76, 0x69, 0x7a, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x50, 0x6f,\n\t0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x48, 0x00, 0x52, 0x08, 0x70, 0x6f, 0x64, 0x47, 0x72, 0x6f,\n\t0x75, 0x70, 0x1a, 0xc6, 0x05, 0x0a, 0x08, 0x50, 0x6f, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12,\n\t0x38, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x53, 0x74, 0x61,\n\t0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x50, 0x6f, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e,\n\t0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x1a, 0xff, 0x04, 0x0a, 0x03, 0x52, 0x6f,\n\t0x77, 0x12, 0x32, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76,\n\t0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73,\n\t0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x77, 0x69,\n\t0x6e, 0x64, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65,\n\t0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,\n\t0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x28,\n\t0x0a, 0x10, 0x6d, 0x65, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x64, 0x5f, 0x63, 0x6f, 0x75,\n\t0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x6d, 0x65, 0x73, 0x68, 0x65, 0x64,\n\t0x50, 0x6f, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x75, 0x6e, 0x6e,\n\t0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20,\n\t0x01, 0x28, 0x04, 0x52, 0x0f, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x64, 0x43,\n\t0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x70,\n\t0x6f, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e,\n\t0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x50, 0x6f, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e,\n\t0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x42, 0x61, 0x73,\n\t0x69, 0x63, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x12, 0x33,\n\t0x0a, 0x09, 0x74, 0x63, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a,\n\t0x2e, 0x54, 0x63, 0x70, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x08, 0x74, 0x63, 0x70, 0x53, 0x74,\n\t0x61, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x74, 0x73, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18,\n\t0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32,\n\t0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x53, 0x70, 0x6c, 0x69,\n\t0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x07, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12,\n\t0x36, 0x0a, 0x09, 0x73, 0x72, 0x76, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69,\n\t0x7a, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x08, 0x73,\n\t0x72, 0x76, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x59, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72,\n\t0x73, 0x5f, 0x62, 0x79, 0x5f, 0x70, 0x6f, 0x64, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35,\n\t0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x53, 0x74,\n\t0x61, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x50, 0x6f, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70,\n\t0x2e, 0x52, 0x6f, 0x77, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x42, 0x79, 0x50, 0x6f, 0x64,\n\t0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x42, 0x79, 0x50,\n\t0x6f, 0x64, 0x1a, 0x57, 0x0a, 0x10, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x42, 0x79, 0x50, 0x6f,\n\t0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72,\n\t0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x50, 0x6f, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73,\n\t0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x74,\n\t0x61, 0x62, 0x6c, 0x65, 0x22, 0x4b, 0x0a, 0x0c, 0x45, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71,\n\t0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64,\n\t0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65,\n\t0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f,\n\t0x72, 0x22, 0xb2, 0x01, 0x0a, 0x0d, 0x45, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,\n\t0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x1e, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x45,\n\t0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x6b, 0x48,\n\t0x00, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x33, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,\n\t0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x72, 0x72, 0x6f,\n\t0x72, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0x2e, 0x0a, 0x02, 0x4f, 0x6b,\n\t0x12, 0x28, 0x0a, 0x05, 0x65, 0x64, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,\n\t0x12, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x45,\n\t0x64, 0x67, 0x65, 0x52, 0x05, 0x65, 0x64, 0x67, 0x65, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65,\n\t0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xbc, 0x01, 0x0a, 0x04, 0x45, 0x64, 0x67, 0x65, 0x12,\n\t0x28, 0x0a, 0x03, 0x73, 0x72, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c,\n\t0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f,\n\t0x75, 0x72, 0x63, 0x65, 0x52, 0x03, 0x73, 0x72, 0x63, 0x12, 0x28, 0x0a, 0x03, 0x64, 0x73, 0x74,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64,\n\t0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x03,\n\t0x64, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64,\n\t0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64,\n\t0x12, 0x1b, 0x0a, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a,\n\t0x0f, 0x6e, 0x6f, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x6d, 0x73, 0x67,\n\t0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x6f, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69,\n\t0x74, 0x79, 0x4d, 0x73, 0x67, 0x22, 0xe2, 0x01, 0x0a, 0x10, 0x54, 0x6f, 0x70, 0x52, 0x6f, 0x75,\n\t0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x08, 0x73, 0x65,\n\t0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c,\n\t0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f,\n\t0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x73,\n\t0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x5f,\n\t0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x69,\n\t0x6d, 0x65, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x29, 0x0a, 0x04, 0x6e, 0x6f, 0x6e, 0x65,\n\t0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64,\n\t0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x48, 0x00, 0x52, 0x04, 0x6e,\n\t0x6f, 0x6e, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x74, 0x6f, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,\n\t0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,\n\t0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,\n\t0x48, 0x00, 0x52, 0x0a, 0x74, 0x6f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x42, 0x0a,\n\t0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0xc2, 0x01, 0x0a, 0x11, 0x54,\n\t0x6f, 0x70, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,\n\t0x12, 0x33, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52,\n\t0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x05,\n\t0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x34, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x22, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a,\n\t0x2e, 0x54, 0x6f, 0x70, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x2e, 0x4f, 0x6b, 0x48, 0x00, 0x52, 0x02, 0x6f, 0x6b, 0x1a, 0x36, 0x0a, 0x02, 0x4f,\n\t0x6b, 0x12, 0x30, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,\n\t0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a,\n\t0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x06, 0x72, 0x6f, 0x75,\n\t0x74, 0x65, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,\n\t0xe7, 0x01, 0x0a, 0x0a, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x30,\n\t0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6c,\n\t0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x6f, 0x75, 0x74,\n\t0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73,\n\t0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x1a, 0x8a, 0x01, 0x0a,\n\t0x03, 0x52, 0x6f, 0x77, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x69,\n\t0x6d, 0x65, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x0a, 0x74, 0x69, 0x6d, 0x65, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x1c, 0x0a, 0x09, 0x61,\n\t0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,\n\t0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2e, 0x0a, 0x05, 0x73, 0x74, 0x61,\n\t0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,\n\t0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x42, 0x61, 0x73, 0x69, 0x63, 0x53, 0x74, 0x61,\n\t0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0xd2, 0x02, 0x0a, 0x0d, 0x47, 0x61,\n\t0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x72,\n\t0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b,\n\t0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79,\n\t0x73, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73,\n\t0x1a, 0x8b, 0x02, 0x0a, 0x03, 0x52, 0x6f, 0x77, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65,\n\t0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d,\n\t0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c,\n\t0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a,\n\t0x0f, 0x70, 0x61, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,\n\t0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x70, 0x61, 0x69, 0x72, 0x65, 0x64, 0x53, 0x65,\n\t0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x18,\n\t0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x24, 0x0a, 0x0e,\n\t0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73, 0x5f, 0x70, 0x35, 0x30, 0x18, 0x06,\n\t0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x73, 0x50,\n\t0x35, 0x30, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73,\n\t0x5f, 0x70, 0x39, 0x35, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65,\n\t0x6e, 0x63, 0x79, 0x4d, 0x73, 0x50, 0x39, 0x35, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x74, 0x65,\n\t0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73, 0x5f, 0x70, 0x39, 0x39, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04,\n\t0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x73, 0x50, 0x39, 0x39, 0x22, 0x8f,\n\t0x01, 0x0a, 0x0f, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x6c, 0x75,\n\t0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x11, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61,\n\t0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x6e, 0x61,\n\t0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x67,\n\t0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12,\n\t0x1f, 0x0a, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77,\n\t0x22, 0xd2, 0x01, 0x0a, 0x10, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x21, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a,\n\t0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,\n\t0x65, 0x2e, 0x4f, 0x6b, 0x48, 0x00, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x33, 0x0a, 0x05, 0x65, 0x72,\n\t0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b,\n\t0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,\n\t0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a,\n\t0x48, 0x0a, 0x02, 0x4f, 0x6b, 0x12, 0x42, 0x0a, 0x0e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79,\n\t0x73, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x47, 0x61, 0x74,\n\t0x65, 0x77, 0x61, 0x79, 0x73, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x0d, 0x67, 0x61, 0x74, 0x65,\n\t0x77, 0x61, 0x79, 0x73, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x2a, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x74,\n\t0x61, 0x74, 0x75, 0x73, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04,\n\t0x46, 0x41, 0x49, 0x4c, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10,\n\t0x02, 0x32, 0xf6, 0x04, 0x0a, 0x03, 0x41, 0x70, 0x69, 0x12, 0x54, 0x0a, 0x0b, 0x53, 0x74, 0x61,\n\t0x74, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x20, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,\n\t0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x53, 0x75, 0x6d, 0x6d,\n\t0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6c, 0x69, 0x6e,\n\t0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x53, 0x75,\n\t0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,\n\t0x42, 0x0a, 0x05, 0x45, 0x64, 0x67, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,\n\t0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x45, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71,\n\t0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,\n\t0x76, 0x69, 0x7a, 0x2e, 0x45, 0x64, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,\n\t0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x08, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x12,\n\t0x1d, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x47,\n\t0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e,\n\t0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x47, 0x61,\n\t0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,\n\t0x12, 0x4e, 0x0a, 0x09, 0x54, 0x6f, 0x70, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1e, 0x2e,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x54, 0x6f, 0x70,\n\t0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x54, 0x6f, 0x70,\n\t0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,\n\t0x12, 0x4b, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x64, 0x73, 0x12, 0x1d, 0x2e, 0x6c,\n\t0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x4c, 0x69, 0x73, 0x74,\n\t0x50, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x69,\n\t0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50,\n\t0x6f, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a,\n\t0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x21, 0x2e,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x4c, 0x69, 0x73,\n\t0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x1a, 0x22, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e,\n\t0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70,\n\t0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x09, 0x53, 0x65, 0x6c, 0x66, 0x43, 0x68,\n\t0x65, 0x63, 0x6b, 0x12, 0x1e, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76,\n\t0x69, 0x7a, 0x2e, 0x53, 0x65, 0x6c, 0x66, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75,\n\t0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76,\n\t0x69, 0x7a, 0x2e, 0x53, 0x65, 0x6c, 0x66, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70,\n\t0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x05, 0x41, 0x75, 0x74, 0x68, 0x7a, 0x12,\n\t0x1a, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x41,\n\t0x75, 0x74, 0x68, 0x7a, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x69,\n\t0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x7a,\n\t0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69,\n\t0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64,\n\t0x2f, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2f, 0x76, 0x69, 0x7a, 0x2f, 0x6d, 0x65,\n\t0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x76, 0x69,\n\t0x7a, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_viz_proto_rawDescOnce sync.Once\n\tfile_viz_proto_rawDescData = file_viz_proto_rawDesc\n)\n\nfunc file_viz_proto_rawDescGZIP() []byte {\n\tfile_viz_proto_rawDescOnce.Do(func() {\n\t\tfile_viz_proto_rawDescData = protoimpl.X.CompressGZIP(file_viz_proto_rawDescData)\n\t})\n\treturn file_viz_proto_rawDescData\n}\n\nvar file_viz_proto_enumTypes = make([]protoimpl.EnumInfo, 3)\nvar file_viz_proto_msgTypes = make([]protoimpl.MessageInfo, 50)\nvar file_viz_proto_goTypes = []any{\n\t(CheckStatus)(0),                          // 0: linkerd2.viz.CheckStatus\n\t(HttpMethod_Registered)(0),                // 1: linkerd2.viz.HttpMethod.Registered\n\t(Scheme_Registered)(0),                    // 2: linkerd2.viz.Scheme.Registered\n\t(*Empty)(nil),                             // 3: linkerd2.viz.Empty\n\t(*CheckResult)(nil),                       // 4: linkerd2.viz.CheckResult\n\t(*SelfCheckRequest)(nil),                  // 5: linkerd2.viz.SelfCheckRequest\n\t(*SelfCheckResponse)(nil),                 // 6: linkerd2.viz.SelfCheckResponse\n\t(*ListServicesRequest)(nil),               // 7: linkerd2.viz.ListServicesRequest\n\t(*ListServicesResponse)(nil),              // 8: linkerd2.viz.ListServicesResponse\n\t(*Service)(nil),                           // 9: linkerd2.viz.Service\n\t(*ListPodsRequest)(nil),                   // 10: linkerd2.viz.ListPodsRequest\n\t(*ListPodsResponse)(nil),                  // 11: linkerd2.viz.ListPodsResponse\n\t(*Pod)(nil),                               // 12: linkerd2.viz.Pod\n\t(*HttpMethod)(nil),                        // 13: linkerd2.viz.HttpMethod\n\t(*Scheme)(nil),                            // 14: linkerd2.viz.Scheme\n\t(*Headers)(nil),                           // 15: linkerd2.viz.Headers\n\t(*Eos)(nil),                               // 16: linkerd2.viz.Eos\n\t(*ApiError)(nil),                          // 17: linkerd2.viz.ApiError\n\t(*PodErrors)(nil),                         // 18: linkerd2.viz.PodErrors\n\t(*Resource)(nil),                          // 19: linkerd2.viz.Resource\n\t(*ResourceSelection)(nil),                 // 20: linkerd2.viz.ResourceSelection\n\t(*ResourceError)(nil),                     // 21: linkerd2.viz.ResourceError\n\t(*StatSummaryRequest)(nil),                // 22: linkerd2.viz.StatSummaryRequest\n\t(*StatSummaryResponse)(nil),               // 23: linkerd2.viz.StatSummaryResponse\n\t(*AuthzRequest)(nil),                      // 24: linkerd2.viz.AuthzRequest\n\t(*AuthzResponse)(nil),                     // 25: linkerd2.viz.AuthzResponse\n\t(*BasicStats)(nil),                        // 26: linkerd2.viz.BasicStats\n\t(*TcpStats)(nil),                          // 27: linkerd2.viz.TcpStats\n\t(*TrafficSplitStats)(nil),                 // 28: linkerd2.viz.TrafficSplitStats\n\t(*ServerStats)(nil),                       // 29: linkerd2.viz.ServerStats\n\t(*StatTable)(nil),                         // 30: linkerd2.viz.StatTable\n\t(*EdgesRequest)(nil),                      // 31: linkerd2.viz.EdgesRequest\n\t(*EdgesResponse)(nil),                     // 32: linkerd2.viz.EdgesResponse\n\t(*Edge)(nil),                              // 33: linkerd2.viz.Edge\n\t(*TopRoutesRequest)(nil),                  // 34: linkerd2.viz.TopRoutesRequest\n\t(*TopRoutesResponse)(nil),                 // 35: linkerd2.viz.TopRoutesResponse\n\t(*RouteTable)(nil),                        // 36: linkerd2.viz.RouteTable\n\t(*GatewaysTable)(nil),                     // 37: linkerd2.viz.GatewaysTable\n\t(*GatewaysRequest)(nil),                   // 38: linkerd2.viz.GatewaysRequest\n\t(*GatewaysResponse)(nil),                  // 39: linkerd2.viz.GatewaysResponse\n\t(*Headers_Header)(nil),                    // 40: linkerd2.viz.Headers.Header\n\t(*PodErrors_PodError)(nil),                // 41: linkerd2.viz.PodErrors.PodError\n\t(*PodErrors_PodError_ContainerError)(nil), // 42: linkerd2.viz.PodErrors.PodError.ContainerError\n\t(*StatSummaryResponse_Ok)(nil),            // 43: linkerd2.viz.StatSummaryResponse.Ok\n\t(*AuthzResponse_Ok)(nil),                  // 44: linkerd2.viz.AuthzResponse.Ok\n\t(*StatTable_PodGroup)(nil),                // 45: linkerd2.viz.StatTable.PodGroup\n\t(*StatTable_PodGroup_Row)(nil),            // 46: linkerd2.viz.StatTable.PodGroup.Row\n\tnil,                                       // 47: linkerd2.viz.StatTable.PodGroup.Row.ErrorsByPodEntry\n\t(*EdgesResponse_Ok)(nil),                  // 48: linkerd2.viz.EdgesResponse.Ok\n\t(*TopRoutesResponse_Ok)(nil),              // 49: linkerd2.viz.TopRoutesResponse.Ok\n\t(*RouteTable_Row)(nil),                    // 50: linkerd2.viz.RouteTable.Row\n\t(*GatewaysTable_Row)(nil),                 // 51: linkerd2.viz.GatewaysTable.Row\n\t(*GatewaysResponse_Ok)(nil),               // 52: linkerd2.viz.GatewaysResponse.Ok\n\t(*duration.Duration)(nil),                 // 53: google.protobuf.Duration\n}\nvar file_viz_proto_depIdxs = []int32{\n\t0,  // 0: linkerd2.viz.CheckResult.Status:type_name -> linkerd2.viz.CheckStatus\n\t4,  // 1: linkerd2.viz.SelfCheckResponse.results:type_name -> linkerd2.viz.CheckResult\n\t9,  // 2: linkerd2.viz.ListServicesResponse.services:type_name -> linkerd2.viz.Service\n\t20, // 3: linkerd2.viz.ListPodsRequest.selector:type_name -> linkerd2.viz.ResourceSelection\n\t12, // 4: linkerd2.viz.ListPodsResponse.pods:type_name -> linkerd2.viz.Pod\n\t53, // 5: linkerd2.viz.Pod.sinceLastReport:type_name -> google.protobuf.Duration\n\t53, // 6: linkerd2.viz.Pod.uptime:type_name -> google.protobuf.Duration\n\t1,  // 7: linkerd2.viz.HttpMethod.registered:type_name -> linkerd2.viz.HttpMethod.Registered\n\t2,  // 8: linkerd2.viz.Scheme.registered:type_name -> linkerd2.viz.Scheme.Registered\n\t40, // 9: linkerd2.viz.Headers.headers:type_name -> linkerd2.viz.Headers.Header\n\t41, // 10: linkerd2.viz.PodErrors.errors:type_name -> linkerd2.viz.PodErrors.PodError\n\t19, // 11: linkerd2.viz.ResourceSelection.resource:type_name -> linkerd2.viz.Resource\n\t19, // 12: linkerd2.viz.ResourceError.resource:type_name -> linkerd2.viz.Resource\n\t20, // 13: linkerd2.viz.StatSummaryRequest.selector:type_name -> linkerd2.viz.ResourceSelection\n\t3,  // 14: linkerd2.viz.StatSummaryRequest.none:type_name -> linkerd2.viz.Empty\n\t19, // 15: linkerd2.viz.StatSummaryRequest.to_resource:type_name -> linkerd2.viz.Resource\n\t19, // 16: linkerd2.viz.StatSummaryRequest.from_resource:type_name -> linkerd2.viz.Resource\n\t43, // 17: linkerd2.viz.StatSummaryResponse.ok:type_name -> linkerd2.viz.StatSummaryResponse.Ok\n\t21, // 18: linkerd2.viz.StatSummaryResponse.error:type_name -> linkerd2.viz.ResourceError\n\t19, // 19: linkerd2.viz.AuthzRequest.resource:type_name -> linkerd2.viz.Resource\n\t44, // 20: linkerd2.viz.AuthzResponse.ok:type_name -> linkerd2.viz.AuthzResponse.Ok\n\t21, // 21: linkerd2.viz.AuthzResponse.error:type_name -> linkerd2.viz.ResourceError\n\t19, // 22: linkerd2.viz.ServerStats.srv:type_name -> linkerd2.viz.Resource\n\t19, // 23: linkerd2.viz.ServerStats.route:type_name -> linkerd2.viz.Resource\n\t19, // 24: linkerd2.viz.ServerStats.authz:type_name -> linkerd2.viz.Resource\n\t45, // 25: linkerd2.viz.StatTable.pod_group:type_name -> linkerd2.viz.StatTable.PodGroup\n\t20, // 26: linkerd2.viz.EdgesRequest.selector:type_name -> linkerd2.viz.ResourceSelection\n\t48, // 27: linkerd2.viz.EdgesResponse.ok:type_name -> linkerd2.viz.EdgesResponse.Ok\n\t21, // 28: linkerd2.viz.EdgesResponse.error:type_name -> linkerd2.viz.ResourceError\n\t19, // 29: linkerd2.viz.Edge.src:type_name -> linkerd2.viz.Resource\n\t19, // 30: linkerd2.viz.Edge.dst:type_name -> linkerd2.viz.Resource\n\t20, // 31: linkerd2.viz.TopRoutesRequest.selector:type_name -> linkerd2.viz.ResourceSelection\n\t3,  // 32: linkerd2.viz.TopRoutesRequest.none:type_name -> linkerd2.viz.Empty\n\t19, // 33: linkerd2.viz.TopRoutesRequest.to_resource:type_name -> linkerd2.viz.Resource\n\t21, // 34: linkerd2.viz.TopRoutesResponse.error:type_name -> linkerd2.viz.ResourceError\n\t49, // 35: linkerd2.viz.TopRoutesResponse.ok:type_name -> linkerd2.viz.TopRoutesResponse.Ok\n\t50, // 36: linkerd2.viz.RouteTable.rows:type_name -> linkerd2.viz.RouteTable.Row\n\t51, // 37: linkerd2.viz.GatewaysTable.rows:type_name -> linkerd2.viz.GatewaysTable.Row\n\t52, // 38: linkerd2.viz.GatewaysResponse.ok:type_name -> linkerd2.viz.GatewaysResponse.Ok\n\t21, // 39: linkerd2.viz.GatewaysResponse.error:type_name -> linkerd2.viz.ResourceError\n\t42, // 40: linkerd2.viz.PodErrors.PodError.container:type_name -> linkerd2.viz.PodErrors.PodError.ContainerError\n\t30, // 41: linkerd2.viz.StatSummaryResponse.Ok.stat_tables:type_name -> linkerd2.viz.StatTable\n\t30, // 42: linkerd2.viz.AuthzResponse.Ok.stat_table:type_name -> linkerd2.viz.StatTable\n\t46, // 43: linkerd2.viz.StatTable.PodGroup.rows:type_name -> linkerd2.viz.StatTable.PodGroup.Row\n\t19, // 44: linkerd2.viz.StatTable.PodGroup.Row.resource:type_name -> linkerd2.viz.Resource\n\t26, // 45: linkerd2.viz.StatTable.PodGroup.Row.stats:type_name -> linkerd2.viz.BasicStats\n\t27, // 46: linkerd2.viz.StatTable.PodGroup.Row.tcp_stats:type_name -> linkerd2.viz.TcpStats\n\t28, // 47: linkerd2.viz.StatTable.PodGroup.Row.ts_stats:type_name -> linkerd2.viz.TrafficSplitStats\n\t29, // 48: linkerd2.viz.StatTable.PodGroup.Row.srv_stats:type_name -> linkerd2.viz.ServerStats\n\t47, // 49: linkerd2.viz.StatTable.PodGroup.Row.errors_by_pod:type_name -> linkerd2.viz.StatTable.PodGroup.Row.ErrorsByPodEntry\n\t18, // 50: linkerd2.viz.StatTable.PodGroup.Row.ErrorsByPodEntry.value:type_name -> linkerd2.viz.PodErrors\n\t33, // 51: linkerd2.viz.EdgesResponse.Ok.edges:type_name -> linkerd2.viz.Edge\n\t36, // 52: linkerd2.viz.TopRoutesResponse.Ok.routes:type_name -> linkerd2.viz.RouteTable\n\t26, // 53: linkerd2.viz.RouteTable.Row.stats:type_name -> linkerd2.viz.BasicStats\n\t37, // 54: linkerd2.viz.GatewaysResponse.Ok.gateways_table:type_name -> linkerd2.viz.GatewaysTable\n\t22, // 55: linkerd2.viz.Api.StatSummary:input_type -> linkerd2.viz.StatSummaryRequest\n\t31, // 56: linkerd2.viz.Api.Edges:input_type -> linkerd2.viz.EdgesRequest\n\t38, // 57: linkerd2.viz.Api.Gateways:input_type -> linkerd2.viz.GatewaysRequest\n\t34, // 58: linkerd2.viz.Api.TopRoutes:input_type -> linkerd2.viz.TopRoutesRequest\n\t10, // 59: linkerd2.viz.Api.ListPods:input_type -> linkerd2.viz.ListPodsRequest\n\t7,  // 60: linkerd2.viz.Api.ListServices:input_type -> linkerd2.viz.ListServicesRequest\n\t5,  // 61: linkerd2.viz.Api.SelfCheck:input_type -> linkerd2.viz.SelfCheckRequest\n\t24, // 62: linkerd2.viz.Api.Authz:input_type -> linkerd2.viz.AuthzRequest\n\t23, // 63: linkerd2.viz.Api.StatSummary:output_type -> linkerd2.viz.StatSummaryResponse\n\t32, // 64: linkerd2.viz.Api.Edges:output_type -> linkerd2.viz.EdgesResponse\n\t39, // 65: linkerd2.viz.Api.Gateways:output_type -> linkerd2.viz.GatewaysResponse\n\t35, // 66: linkerd2.viz.Api.TopRoutes:output_type -> linkerd2.viz.TopRoutesResponse\n\t11, // 67: linkerd2.viz.Api.ListPods:output_type -> linkerd2.viz.ListPodsResponse\n\t8,  // 68: linkerd2.viz.Api.ListServices:output_type -> linkerd2.viz.ListServicesResponse\n\t6,  // 69: linkerd2.viz.Api.SelfCheck:output_type -> linkerd2.viz.SelfCheckResponse\n\t25, // 70: linkerd2.viz.Api.Authz:output_type -> linkerd2.viz.AuthzResponse\n\t63, // [63:71] is the sub-list for method output_type\n\t55, // [55:63] is the sub-list for method input_type\n\t55, // [55:55] is the sub-list for extension type_name\n\t55, // [55:55] is the sub-list for extension extendee\n\t0,  // [0:55] is the sub-list for field type_name\n}\n\nfunc init() { file_viz_proto_init() }\nfunc file_viz_proto_init() {\n\tif File_viz_proto != nil {\n\t\treturn\n\t}\n\tfile_viz_proto_msgTypes[9].OneofWrappers = []any{\n\t\t(*Pod_Deployment)(nil),\n\t\t(*Pod_ReplicaSet)(nil),\n\t\t(*Pod_ReplicationController)(nil),\n\t\t(*Pod_StatefulSet)(nil),\n\t\t(*Pod_DaemonSet)(nil),\n\t\t(*Pod_Job)(nil),\n\t}\n\tfile_viz_proto_msgTypes[10].OneofWrappers = []any{\n\t\t(*HttpMethod_Registered_)(nil),\n\t\t(*HttpMethod_Unregistered)(nil),\n\t}\n\tfile_viz_proto_msgTypes[11].OneofWrappers = []any{\n\t\t(*Scheme_Registered_)(nil),\n\t\t(*Scheme_Unregistered)(nil),\n\t}\n\tfile_viz_proto_msgTypes[13].OneofWrappers = []any{\n\t\t(*Eos_GrpcStatusCode)(nil),\n\t\t(*Eos_ResetErrorCode)(nil),\n\t}\n\tfile_viz_proto_msgTypes[19].OneofWrappers = []any{\n\t\t(*StatSummaryRequest_None)(nil),\n\t\t(*StatSummaryRequest_ToResource)(nil),\n\t\t(*StatSummaryRequest_FromResource)(nil),\n\t}\n\tfile_viz_proto_msgTypes[20].OneofWrappers = []any{\n\t\t(*StatSummaryResponse_Ok_)(nil),\n\t\t(*StatSummaryResponse_Error)(nil),\n\t}\n\tfile_viz_proto_msgTypes[22].OneofWrappers = []any{\n\t\t(*AuthzResponse_Ok_)(nil),\n\t\t(*AuthzResponse_Error)(nil),\n\t}\n\tfile_viz_proto_msgTypes[27].OneofWrappers = []any{\n\t\t(*StatTable_PodGroup_)(nil),\n\t}\n\tfile_viz_proto_msgTypes[29].OneofWrappers = []any{\n\t\t(*EdgesResponse_Ok_)(nil),\n\t\t(*EdgesResponse_Error)(nil),\n\t}\n\tfile_viz_proto_msgTypes[31].OneofWrappers = []any{\n\t\t(*TopRoutesRequest_None)(nil),\n\t\t(*TopRoutesRequest_ToResource)(nil),\n\t}\n\tfile_viz_proto_msgTypes[32].OneofWrappers = []any{\n\t\t(*TopRoutesResponse_Error)(nil),\n\t\t(*TopRoutesResponse_Ok_)(nil),\n\t}\n\tfile_viz_proto_msgTypes[36].OneofWrappers = []any{\n\t\t(*GatewaysResponse_Ok_)(nil),\n\t\t(*GatewaysResponse_Error)(nil),\n\t}\n\tfile_viz_proto_msgTypes[37].OneofWrappers = []any{\n\t\t(*Headers_Header_ValueStr)(nil),\n\t\t(*Headers_Header_ValueBin)(nil),\n\t}\n\tfile_viz_proto_msgTypes[38].OneofWrappers = []any{\n\t\t(*PodErrors_PodError_Container)(nil),\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: file_viz_proto_rawDesc,\n\t\t\tNumEnums:      3,\n\t\t\tNumMessages:   50,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_viz_proto_goTypes,\n\t\tDependencyIndexes: file_viz_proto_depIdxs,\n\t\tEnumInfos:         file_viz_proto_enumTypes,\n\t\tMessageInfos:      file_viz_proto_msgTypes,\n\t}.Build()\n\tFile_viz_proto = out.File\n\tfile_viz_proto_rawDesc = nil\n\tfile_viz_proto_goTypes = nil\n\tfile_viz_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "viz/metrics-api/gen/viz/viz_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.5.1\n// - protoc             v6.32.1\n// source: viz.proto\n\npackage viz\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\tApi_StatSummary_FullMethodName  = \"/linkerd2.viz.Api/StatSummary\"\n\tApi_Edges_FullMethodName        = \"/linkerd2.viz.Api/Edges\"\n\tApi_Gateways_FullMethodName     = \"/linkerd2.viz.Api/Gateways\"\n\tApi_TopRoutes_FullMethodName    = \"/linkerd2.viz.Api/TopRoutes\"\n\tApi_ListPods_FullMethodName     = \"/linkerd2.viz.Api/ListPods\"\n\tApi_ListServices_FullMethodName = \"/linkerd2.viz.Api/ListServices\"\n\tApi_SelfCheck_FullMethodName    = \"/linkerd2.viz.Api/SelfCheck\"\n\tApi_Authz_FullMethodName        = \"/linkerd2.viz.Api/Authz\"\n)\n\n// ApiClient is the client API for Api 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 ApiClient interface {\n\tStatSummary(ctx context.Context, in *StatSummaryRequest, opts ...grpc.CallOption) (*StatSummaryResponse, error)\n\tEdges(ctx context.Context, in *EdgesRequest, opts ...grpc.CallOption) (*EdgesResponse, error)\n\tGateways(ctx context.Context, in *GatewaysRequest, opts ...grpc.CallOption) (*GatewaysResponse, error)\n\tTopRoutes(ctx context.Context, in *TopRoutesRequest, opts ...grpc.CallOption) (*TopRoutesResponse, error)\n\tListPods(ctx context.Context, in *ListPodsRequest, opts ...grpc.CallOption) (*ListPodsResponse, error)\n\tListServices(ctx context.Context, in *ListServicesRequest, opts ...grpc.CallOption) (*ListServicesResponse, error)\n\tSelfCheck(ctx context.Context, in *SelfCheckRequest, opts ...grpc.CallOption) (*SelfCheckResponse, error)\n\tAuthz(ctx context.Context, in *AuthzRequest, opts ...grpc.CallOption) (*AuthzResponse, error)\n}\n\ntype apiClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewApiClient(cc grpc.ClientConnInterface) ApiClient {\n\treturn &apiClient{cc}\n}\n\nfunc (c *apiClient) StatSummary(ctx context.Context, in *StatSummaryRequest, opts ...grpc.CallOption) (*StatSummaryResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(StatSummaryResponse)\n\terr := c.cc.Invoke(ctx, Api_StatSummary_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *apiClient) Edges(ctx context.Context, in *EdgesRequest, opts ...grpc.CallOption) (*EdgesResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(EdgesResponse)\n\terr := c.cc.Invoke(ctx, Api_Edges_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *apiClient) Gateways(ctx context.Context, in *GatewaysRequest, opts ...grpc.CallOption) (*GatewaysResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GatewaysResponse)\n\terr := c.cc.Invoke(ctx, Api_Gateways_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *apiClient) TopRoutes(ctx context.Context, in *TopRoutesRequest, opts ...grpc.CallOption) (*TopRoutesResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(TopRoutesResponse)\n\terr := c.cc.Invoke(ctx, Api_TopRoutes_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *apiClient) ListPods(ctx context.Context, in *ListPodsRequest, opts ...grpc.CallOption) (*ListPodsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListPodsResponse)\n\terr := c.cc.Invoke(ctx, Api_ListPods_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *apiClient) ListServices(ctx context.Context, in *ListServicesRequest, opts ...grpc.CallOption) (*ListServicesResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListServicesResponse)\n\terr := c.cc.Invoke(ctx, Api_ListServices_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *apiClient) SelfCheck(ctx context.Context, in *SelfCheckRequest, opts ...grpc.CallOption) (*SelfCheckResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(SelfCheckResponse)\n\terr := c.cc.Invoke(ctx, Api_SelfCheck_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *apiClient) Authz(ctx context.Context, in *AuthzRequest, opts ...grpc.CallOption) (*AuthzResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(AuthzResponse)\n\terr := c.cc.Invoke(ctx, Api_Authz_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// ApiServer is the server API for Api service.\n// All implementations must embed UnimplementedApiServer\n// for forward compatibility.\ntype ApiServer interface {\n\tStatSummary(context.Context, *StatSummaryRequest) (*StatSummaryResponse, error)\n\tEdges(context.Context, *EdgesRequest) (*EdgesResponse, error)\n\tGateways(context.Context, *GatewaysRequest) (*GatewaysResponse, error)\n\tTopRoutes(context.Context, *TopRoutesRequest) (*TopRoutesResponse, error)\n\tListPods(context.Context, *ListPodsRequest) (*ListPodsResponse, error)\n\tListServices(context.Context, *ListServicesRequest) (*ListServicesResponse, error)\n\tSelfCheck(context.Context, *SelfCheckRequest) (*SelfCheckResponse, error)\n\tAuthz(context.Context, *AuthzRequest) (*AuthzResponse, error)\n\tmustEmbedUnimplementedApiServer()\n}\n\n// UnimplementedApiServer 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 UnimplementedApiServer struct{}\n\nfunc (UnimplementedApiServer) StatSummary(context.Context, *StatSummaryRequest) (*StatSummaryResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method StatSummary not implemented\")\n}\nfunc (UnimplementedApiServer) Edges(context.Context, *EdgesRequest) (*EdgesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Edges not implemented\")\n}\nfunc (UnimplementedApiServer) Gateways(context.Context, *GatewaysRequest) (*GatewaysResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Gateways not implemented\")\n}\nfunc (UnimplementedApiServer) TopRoutes(context.Context, *TopRoutesRequest) (*TopRoutesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method TopRoutes not implemented\")\n}\nfunc (UnimplementedApiServer) ListPods(context.Context, *ListPodsRequest) (*ListPodsResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListPods not implemented\")\n}\nfunc (UnimplementedApiServer) ListServices(context.Context, *ListServicesRequest) (*ListServicesResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListServices not implemented\")\n}\nfunc (UnimplementedApiServer) SelfCheck(context.Context, *SelfCheckRequest) (*SelfCheckResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method SelfCheck not implemented\")\n}\nfunc (UnimplementedApiServer) Authz(context.Context, *AuthzRequest) (*AuthzResponse, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Authz not implemented\")\n}\nfunc (UnimplementedApiServer) mustEmbedUnimplementedApiServer() {}\nfunc (UnimplementedApiServer) testEmbeddedByValue()             {}\n\n// UnsafeApiServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to ApiServer will\n// result in compilation errors.\ntype UnsafeApiServer interface {\n\tmustEmbedUnimplementedApiServer()\n}\n\nfunc RegisterApiServer(s grpc.ServiceRegistrar, srv ApiServer) {\n\t// If the following call pancis, it indicates UnimplementedApiServer 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(&Api_ServiceDesc, srv)\n}\n\nfunc _Api_StatSummary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(StatSummaryRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ApiServer).StatSummary(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Api_StatSummary_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ApiServer).StatSummary(ctx, req.(*StatSummaryRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Api_Edges_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(EdgesRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ApiServer).Edges(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Api_Edges_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ApiServer).Edges(ctx, req.(*EdgesRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Api_Gateways_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GatewaysRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ApiServer).Gateways(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Api_Gateways_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ApiServer).Gateways(ctx, req.(*GatewaysRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Api_TopRoutes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(TopRoutesRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ApiServer).TopRoutes(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Api_TopRoutes_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ApiServer).TopRoutes(ctx, req.(*TopRoutesRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Api_ListPods_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListPodsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ApiServer).ListPods(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Api_ListPods_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ApiServer).ListPods(ctx, req.(*ListPodsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Api_ListServices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListServicesRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ApiServer).ListServices(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Api_ListServices_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ApiServer).ListServices(ctx, req.(*ListServicesRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Api_SelfCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SelfCheckRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ApiServer).SelfCheck(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Api_SelfCheck_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ApiServer).SelfCheck(ctx, req.(*SelfCheckRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Api_Authz_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AuthzRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ApiServer).Authz(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Api_Authz_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ApiServer).Authz(ctx, req.(*AuthzRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// Api_ServiceDesc is the grpc.ServiceDesc for Api 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 Api_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"linkerd2.viz.Api\",\n\tHandlerType: (*ApiServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"StatSummary\",\n\t\t\tHandler:    _Api_StatSummary_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Edges\",\n\t\t\tHandler:    _Api_Edges_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Gateways\",\n\t\t\tHandler:    _Api_Gateways_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"TopRoutes\",\n\t\t\tHandler:    _Api_TopRoutes_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ListPods\",\n\t\t\tHandler:    _Api_ListPods_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ListServices\",\n\t\t\tHandler:    _Api_ListServices_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SelfCheck\",\n\t\t\tHandler:    _Api_SelfCheck_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Authz\",\n\t\t\tHandler:    _Api_Authz_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"viz.proto\",\n}\n"
  },
  {
    "path": "viz/metrics-api/grpc_server.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/golang/protobuf/ptypes/duration\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tpkgK8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/prometheus\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/metrics-api/util\"\n\tpromv1 \"github.com/prometheus/client_golang/api/prometheus/v1\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"google.golang.org/grpc\"\n\t\"gopkg.in/yaml.v2\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n)\n\n// Server specifies the interface the Viz metric API server should implement\ntype Server interface {\n\tpb.ApiServer\n}\n\ntype grpcServer struct {\n\tpb.UnimplementedApiServer\n\tprometheusAPI       promv1.API\n\tk8sAPI              *k8s.API\n\tcontrollerNamespace string\n\tclusterDomain       string\n\tignoredNamespaces   []string\n}\n\ntype podReport struct {\n\tlastReport              time.Time\n\tprocessStartTimeSeconds time.Time\n}\n\nconst (\n\tpodQuery                   = \"max(process_start_time_seconds{%s}) by (pod, namespace)\"\n\tk8sClientSubsystemName     = \"kubernetes\"\n\tk8sClientCheckDescription  = \"linkerd viz can talk to Kubernetes\"\n\tpromClientSubsystemName    = \"prometheus\"\n\tpromClientCheckDescription = \"linkerd viz can talk to Prometheus\"\n)\n\n// NewGrpcServer creates a new instance of the Api server and registers it\n// with Prometheus.\nfunc NewGrpcServer(\n\tpromAPI promv1.API,\n\tk8sAPI *k8s.API,\n\tcontrollerNamespace string,\n\tclusterDomain string,\n\tignoredNamespaces []string,\n) *grpc.Server {\n\n\tserver := &grpcServer{\n\t\tprometheusAPI:       promAPI,\n\t\tk8sAPI:              k8sAPI,\n\t\tcontrollerNamespace: controllerNamespace,\n\t\tclusterDomain:       clusterDomain,\n\t\tignoredNamespaces:   ignoredNamespaces,\n\t}\n\n\ts := prometheus.NewGrpcServer(grpc.MaxConcurrentStreams(0))\n\tpb.RegisterApiServer(s, server)\n\n\treturn s\n}\n\nfunc (s *grpcServer) ListPods(ctx context.Context, req *pb.ListPodsRequest) (*pb.ListPodsResponse, error) {\n\tlog.Debugf(\"ListPods request: %+v\", req)\n\n\ttargetOwner := req.GetSelector().GetResource()\n\n\t// Reports is a map from instance name to the absolute time of the most recent\n\t// report from that instance and its process start time\n\treports := make(map[string]podReport)\n\n\tlabelSelector := labels.Everything()\n\tif s := req.GetSelector().GetLabelSelector(); s != \"\" {\n\t\tvar err error\n\t\tlabelSelector, err = labels.Parse(s)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid label selector %q: %w\", s, err)\n\t\t}\n\t}\n\n\tnsQuery := \"\"\n\tnamespace := \"\"\n\tif targetOwner.GetNamespace() != \"\" {\n\t\tnamespace = targetOwner.GetNamespace()\n\t} else if targetOwner.GetType() == pkgK8s.Namespace {\n\t\tnamespace = targetOwner.GetName()\n\t}\n\tif namespace != \"\" {\n\t\tnsQuery = fmt.Sprintf(\"namespace=\\\"%s\\\"\", namespace)\n\t}\n\tprocessStartTimeQuery := fmt.Sprintf(podQuery, nsQuery)\n\n\t// Query Prometheus for all pods present\n\tvec, err := s.queryProm(ctx, processStartTimeQuery)\n\tif err != nil && !errors.Is(err, ErrNoPrometheusInstance) {\n\t\treturn nil, err\n\t}\n\n\tfor _, sample := range vec {\n\t\tpod := string(sample.Metric[\"pod\"])\n\t\ttimestamp := sample.Timestamp\n\n\t\treports[pod] = podReport{\n\t\t\tlastReport:              time.Unix(0, int64(timestamp)*int64(time.Millisecond)),\n\t\t\tprocessStartTimeSeconds: time.Unix(0, int64(sample.Value)*int64(time.Second)),\n\t\t}\n\t}\n\n\tvar pods []*corev1.Pod\n\tif namespace != \"\" {\n\t\tpods, err = s.k8sAPI.Pod().Lister().Pods(namespace).List(labelSelector)\n\t} else {\n\t\tpods, err = s.k8sAPI.Pod().Lister().List(labelSelector)\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpodList := make([]*pb.Pod, 0)\n\n\tfor _, pod := range pods {\n\t\tif s.shouldIgnore(pod) {\n\t\t\tcontinue\n\t\t}\n\n\t\townerKind, ownerName := s.k8sAPI.GetOwnerKindAndName(ctx, pod, false)\n\t\t// filter out pods without matching owner\n\t\tif targetOwner.GetNamespace() != \"\" && targetOwner.GetNamespace() != pod.GetNamespace() {\n\t\t\tcontinue\n\t\t}\n\t\tif targetOwner.GetType() != \"\" && targetOwner.GetType() != ownerKind {\n\t\t\tcontinue\n\t\t}\n\t\tif targetOwner.GetName() != \"\" && targetOwner.GetName() != ownerName {\n\t\t\tcontinue\n\t\t}\n\n\t\tupdated, added := reports[pod.Name]\n\n\t\titem := util.K8sPodToPublicPod(*pod, ownerKind, ownerName)\n\t\titem.Added = added\n\n\t\tif added {\n\t\t\tsince := time.Since(updated.lastReport)\n\t\t\titem.SinceLastReport = &duration.Duration{\n\t\t\t\tSeconds: int64(since / time.Second),\n\t\t\t\tNanos:   int32(since % time.Second),\n\t\t\t}\n\t\t\tsinceStarting := time.Since(updated.processStartTimeSeconds)\n\t\t\titem.Uptime = &duration.Duration{\n\t\t\t\tSeconds: int64(sinceStarting / time.Second),\n\t\t\t\tNanos:   int32(sinceStarting % time.Second),\n\t\t\t}\n\t\t}\n\n\t\tpodList = append(podList, item)\n\t}\n\n\trsp := pb.ListPodsResponse{Pods: podList}\n\n\tlog.Debugf(\"ListPods response: %s\", rsp.String())\n\n\treturn &rsp, nil\n}\n\nfunc (s *grpcServer) SelfCheck(ctx context.Context, in *pb.SelfCheckRequest) (*pb.SelfCheckResponse, error) {\n\tk8sClientCheck := &pb.CheckResult{\n\t\tSubsystemName:    k8sClientSubsystemName,\n\t\tCheckDescription: k8sClientCheckDescription,\n\t\tStatus:           pb.CheckStatus_OK,\n\t}\n\t_, err := s.k8sAPI.Pod().Lister().List(labels.Everything())\n\tif err != nil {\n\t\tk8sClientCheck.Status = pb.CheckStatus_ERROR\n\t\tk8sClientCheck.FriendlyMessageToUser = fmt.Sprintf(\"Error calling the Kubernetes API: %s\", err)\n\t}\n\n\tresponse := &pb.SelfCheckResponse{\n\t\tResults: []*pb.CheckResult{\n\t\t\tk8sClientCheck,\n\t\t},\n\t}\n\n\tif s.prometheusAPI != nil {\n\t\tpromClientCheck := &pb.CheckResult{\n\t\t\tSubsystemName:    promClientSubsystemName,\n\t\t\tCheckDescription: promClientCheckDescription,\n\t\t\tStatus:           pb.CheckStatus_OK,\n\t\t}\n\t\t_, err = s.queryProm(ctx, fmt.Sprintf(podQuery, \"\"))\n\t\tif err != nil {\n\t\t\tpromClientCheck.Status = pb.CheckStatus_ERROR\n\t\t\tpromClientCheck.FriendlyMessageToUser = fmt.Sprintf(\"Error calling Prometheus from the control plane: %s\", err)\n\t\t}\n\n\t\tresponse.Results = append(response.Results, promClientCheck)\n\t}\n\n\treturn response, nil\n}\n\nfunc (s *grpcServer) shouldIgnore(pod *corev1.Pod) bool {\n\tfor _, namespace := range s.ignoredNamespaces {\n\t\tif pod.Namespace == namespace {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (s *grpcServer) ListServices(ctx context.Context, req *pb.ListServicesRequest) (*pb.ListServicesResponse, error) {\n\tlog.Debugf(\"ListServices request: %+v\", req)\n\n\tservices, err := s.k8sAPI.GetServices(req.Namespace, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsvcs := make([]*pb.Service, 0)\n\tfor _, svc := range services {\n\t\tsvcs = append(svcs, &pb.Service{\n\t\t\tName:      svc.GetName(),\n\t\t\tNamespace: svc.GetNamespace(),\n\t\t})\n\t}\n\n\treturn &pb.ListServicesResponse{Services: svcs}, nil\n}\n\n// validateTimeWindow returns an error if the Prometheus scrape interval\n// is longer than the query time window. This is an opportunistic, best-effort\n// validation: if we cannot determine the Prometheus scrape interval for any\n// reason, we do not return an error.\nfunc (s *grpcServer) validateTimeWindow(ctx context.Context, window string) error {\n\tconfig, err := s.prometheusAPI.Config(ctx)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\ttype PrometheusConfig struct {\n\t\tGlobal map[string]string\n\t}\n\n\tvar prom PrometheusConfig\n\terr = yaml.Unmarshal([]byte(config.YAML), &prom)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tscrape_interval_str, found := prom.Global[\"scrape_interval\"]\n\tif !found {\n\t\treturn nil\n\t}\n\n\tscrape_interval, err := time.ParseDuration(scrape_interval_str)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tt, err := time.ParseDuration(window)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif t < scrape_interval {\n\t\treturn fmt.Errorf(\"time window (%s) must be at least as long as the Prometheus scrape interval (%s)\", window, scrape_interval)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "viz/metrics-api/grpc_server_test.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/golang/protobuf/ptypes/duration\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tpkgK8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/prometheus\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/prometheus/common/model\"\n)\n\ntype listPodsExpected struct {\n\terr              error\n\tk8sRes           []string\n\tpromRes          model.Value\n\treq              *pb.ListPodsRequest\n\tres              *pb.ListPodsResponse\n\tpromReqNamespace string\n}\n\ntype listServicesExpected struct {\n\terr    error\n\tk8sRes []string\n\tres    *pb.ListServicesResponse\n}\n\n// sort Pods in ListPodResponses for easier comparison\ntype ByPod []*pb.Pod\n\nfunc (bp ByPod) Len() int           { return len(bp) }\nfunc (bp ByPod) Swap(i, j int)      { bp[i], bp[j] = bp[j], bp[i] }\nfunc (bp ByPod) Less(i, j int) bool { return bp[i].Name <= bp[j].Name }\n\n// sort Services in ListServiceResponses for easier comparison\ntype ByService []*pb.Service\n\nfunc (bs ByService) Len() int           { return len(bs) }\nfunc (bs ByService) Swap(i, j int)      { bs[i], bs[j] = bs[j], bs[i] }\nfunc (bs ByService) Less(i, j int) bool { return bs[i].Name <= bs[j].Name }\n\nfunc listPodResponsesEqual(a *pb.ListPodsResponse, b *pb.ListPodsResponse) bool {\n\tif a == nil || b == nil {\n\t\treturn a == b\n\t}\n\n\tif len(a.Pods) != len(b.Pods) {\n\t\treturn false\n\t}\n\n\tsort.Sort(ByPod(a.Pods))\n\tsort.Sort(ByPod(b.Pods))\n\n\tfor i := 0; i < len(a.Pods); i++ {\n\t\taPod := a.Pods[i]\n\t\tbPod := b.Pods[i]\n\n\t\tif (aPod.Name != bPod.Name) ||\n\t\t\t(aPod.Added != bPod.Added) ||\n\t\t\t(aPod.Status != bPod.Status) ||\n\t\t\t(aPod.PodIP != bPod.PodIP) ||\n\t\t\t(aPod.GetDeployment() != bPod.GetDeployment()) {\n\t\t\treturn false\n\t\t}\n\n\t\tif (aPod.SinceLastReport == nil && bPod.SinceLastReport != nil) ||\n\t\t\t(aPod.SinceLastReport != nil && bPod.SinceLastReport == nil) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc TestListPods(t *testing.T) {\n\tt.Run(\"Queries to the ListPods endpoint\", func(t *testing.T) {\n\t\texpectations := []listPodsExpected{\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tpromRes: model.Vector{\n\t\t\t\t\t&model.Sample{\n\t\t\t\t\t\tMetric:    model.Metric{\"pod\": \"emojivoto-meshed\"},\n\t\t\t\t\t\tTimestamp: 456,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    pod-template-hash: hash-meshed\n  ownerReferences:\n  - apiVersion: apps/v1\n    kind: ReplicaSet\n    name: rs-emojivoto-meshed\nstatus:\n  phase: Running\n  podIP: 1.2.3.4\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-not-meshed\n  namespace: emojivoto\n  labels:\n    pod-template-hash: hash-not-meshed\n  ownerReferences:\n  - apiVersion: apps/v1\n    kind: ReplicaSet\n    name: rs-emojivoto-not-meshed\nstatus:\n  phase: Pending\n  podIP: 4.3.2.1\n`, `\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  name: rs-emojivoto-meshed\n  namespace: emojivoto\n  ownerReferences:\n  - apiVersion: apps/v1\n    kind: Deployment\n    name: meshed-deployment\nspec:\n  selector:\n    matchLabels:\n      pod-template-hash: hash-meshed\n`, `\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  name: rs-emojivoto-not-meshed\n  namespace: emojivoto\n  ownerReferences:\n  - apiVersion: apps/v1\n    kind: Deployment\n    name: not-meshed-deployment\nspec:\n  selector:\n    matchLabels:\n      pod-template-hash: hash-not-meshed\n`,\n\t\t\t\t},\n\t\t\t\treq: &pb.ListPodsRequest{},\n\t\t\t\tres: &pb.ListPodsResponse{\n\t\t\t\t\tPods: []*pb.Pod{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:            \"emojivoto/emojivoto-meshed\",\n\t\t\t\t\t\t\tAdded:           true,\n\t\t\t\t\t\t\tSinceLastReport: &duration.Duration{},\n\t\t\t\t\t\t\tStatus:          \"Running\",\n\t\t\t\t\t\t\tPodIP:           \"1.2.3.4\",\n\t\t\t\t\t\t\tOwner:           &pb.Pod_Deployment{Deployment: \"emojivoto/meshed-deployment\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:   \"emojivoto/emojivoto-not-meshed\",\n\t\t\t\t\t\t\tStatus: \"Pending\",\n\t\t\t\t\t\t\tPodIP:  \"4.3.2.1\",\n\t\t\t\t\t\t\tOwner:  &pb.Pod_Deployment{Deployment: \"emojivoto/not-meshed-deployment\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tpromRes: model.Vector{\n\t\t\t\t\t&model.Sample{\n\t\t\t\t\t\tMetric:    model.Metric{\"pod\": \"emojivoto-meshed\"},\n\t\t\t\t\t\tTimestamp: 456,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tk8sRes: []string{},\n\t\t\t\treq: &pb.ListPodsRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"testnamespace\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tres:              &pb.ListPodsResponse{},\n\t\t\t\tpromReqNamespace: \"testnamespace\",\n\t\t\t},\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tpromRes: model.Vector{\n\t\t\t\t\t&model.Sample{\n\t\t\t\t\t\tMetric:    model.Metric{\"pod\": \"emojivoto-meshed\"},\n\t\t\t\t\t\tTimestamp: 456,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tk8sRes: []string{},\n\t\t\t\treq: &pb.ListPodsRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tType: pkgK8s.Namespace,\n\t\t\t\t\t\t\tName: \"testnamespace\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tres:              &pb.ListPodsResponse{},\n\t\t\t\tpromReqNamespace: \"testnamespace\",\n\t\t\t},\n\t\t\t// non-matching owner type -> no pod in the result\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tpromRes: model.Vector{\n\t\t\t\t\t&model.Sample{\n\t\t\t\t\t\tMetric:    model.Metric{\"pod\": \"emojivoto-meshed\"},\n\t\t\t\t\t\tTimestamp: 456,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    pod-template-hash: hash-meshed\n  ownerReferences:\n  - apiVersion: apps/v1\n    kind: Deployment\n    name: meshed-deployment\nstatus:\n  phase: Running\n  podIP: 1.2.3.4\n`,\n\t\t\t\t},\n\t\t\t\treq: &pb.ListPodsRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tType: pkgK8s.Pod,\n\t\t\t\t\t\t\tName: \"non-existing-pod\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tres: &pb.ListPodsResponse{},\n\t\t\t},\n\t\t\t// matching owner type -> pod is part of the result\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tpromRes: model.Vector{\n\t\t\t\t\t&model.Sample{\n\t\t\t\t\t\tMetric:    model.Metric{\"pod\": \"emojivoto-meshed\"},\n\t\t\t\t\t\tTimestamp: 456,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    pod-template-hash: hash-meshed\n  ownerReferences:\n  - apiVersion: apps/v1\n    kind: Deployment\n    name: meshed-deployment\nstatus:\n  phase: Running\n  podIP: 1.2.3.4\n`,\n\t\t\t\t},\n\t\t\t\treq: &pb.ListPodsRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tType: pkgK8s.Deployment,\n\t\t\t\t\t\t\tName: \"meshed-deployment\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tres: &pb.ListPodsResponse{\n\t\t\t\t\tPods: []*pb.Pod{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:            \"emojivoto/emojivoto-meshed\",\n\t\t\t\t\t\t\tAdded:           true,\n\t\t\t\t\t\t\tSinceLastReport: &duration.Duration{},\n\t\t\t\t\t\t\tStatus:          \"Running\",\n\t\t\t\t\t\t\tPodIP:           \"1.2.3.4\",\n\t\t\t\t\t\t\tOwner:           &pb.Pod_Deployment{Deployment: \"emojivoto/meshed-deployment\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// matching label in request -> pod is in the response\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tpromRes: model.Vector{\n\t\t\t\t\t&model.Sample{\n\t\t\t\t\t\tMetric:    model.Metric{\"pod\": \"emojivoto-meshed\"},\n\t\t\t\t\t\tTimestamp: 456,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    pod-template-hash: hash-meshed\n  ownerReferences:\n  - apiVersion: apps/v1\n    kind: Deployment\n    name: meshed-deployment\nstatus:\n  phase: Running\n  podIP: 1.2.3.4\n`,\n\t\t\t\t},\n\t\t\t\treq: &pb.ListPodsRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tLabelSelector: \"pod-template-hash=hash-meshed\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tres: &pb.ListPodsResponse{\n\t\t\t\t\tPods: []*pb.Pod{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:            \"emojivoto/emojivoto-meshed\",\n\t\t\t\t\t\t\tAdded:           true,\n\t\t\t\t\t\t\tSinceLastReport: &duration.Duration{},\n\t\t\t\t\t\t\tStatus:          \"Running\",\n\t\t\t\t\t\t\tPodIP:           \"1.2.3.4\",\n\t\t\t\t\t\t\tOwner:           &pb.Pod_Deployment{Deployment: \"emojivoto/meshed-deployment\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// NOT matching label in request -> pod is NOT in the response\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tpromRes: model.Vector{\n\t\t\t\t\t&model.Sample{\n\t\t\t\t\t\tMetric:    model.Metric{\"pod\": \"emojivoto-meshed\"},\n\t\t\t\t\t\tTimestamp: 456,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    pod-template-hash: hash-meshed\n  ownerReferences:\n  - apiVersion: apps/v1\n    kind: Deployment\n    name: meshed-deployment\nstatus:\n  phase: Running\n  podIP: 1.2.3.4\n`,\n\t\t\t\t},\n\t\t\t\treq: &pb.ListPodsRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tLabelSelector: \"non-existent-label=value\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tres: &pb.ListPodsResponse{},\n\t\t\t},\n\t\t}\n\n\t\tfor _, exp := range expectations {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(exp.k8sRes...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tmProm := prometheus.MockProm{Res: exp.promRes}\n\n\t\t\tfakeGrpcServer := grpcServer{\n\t\t\t\tprometheusAPI:       &mProm,\n\t\t\t\tk8sAPI:              k8sAPI,\n\t\t\t\tcontrollerNamespace: \"linkerd\",\n\t\t\t\tclusterDomain:       \"mycluster.local\",\n\t\t\t\tignoredNamespaces:   []string{},\n\t\t\t}\n\n\t\t\tk8sAPI.Sync(nil)\n\n\t\t\trsp, err := fakeGrpcServer.ListPods(context.TODO(), exp.req)\n\t\t\tif diff := deep.Equal(err, exp.err); diff != nil {\n\t\t\t\tt.Fatalf(\"%+v\", diff)\n\t\t\t}\n\n\t\t\tif !listPodResponsesEqual(exp.res, rsp) {\n\t\t\t\tt.Fatalf(\"Expected: %+v, Got: %+v\", exp.res, rsp)\n\t\t\t}\n\n\t\t\tif exp.promReqNamespace != \"\" {\n\t\t\t\terr := verifyPromQueries(&mProm, exp.promReqNamespace)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Expected prometheus query with namespace: %s, Got error: %s\", exp.promReqNamespace, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n\n// TODO: consider refactoring with expectedStatRPC.verifyPromQueries\nfunc verifyPromQueries(mProm *prometheus.MockProm, namespace string) error {\n\tnamespaceSelector := fmt.Sprintf(\"namespace=\\\"%s\\\"\", namespace)\n\tfor _, element := range mProm.QueriesExecuted {\n\t\tif strings.Contains(element, namespaceSelector) {\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn fmt.Errorf(\"Prometheus queries incorrect. \\nExpected query containing:\\n%s \\nGot:\\n%+v\",\n\t\tnamespaceSelector, mProm.QueriesExecuted)\n}\n\nfunc listServiceResponsesEqual(a *pb.ListServicesResponse, b *pb.ListServicesResponse) bool {\n\tif len(a.Services) != len(b.Services) {\n\t\treturn false\n\t}\n\n\tsort.Sort(ByService(a.Services))\n\tsort.Sort(ByService(b.Services))\n\n\tfor i := 0; i < len(a.Services); i++ {\n\t\taSvc := a.Services[i]\n\t\tbSvc := b.Services[i]\n\n\t\tif aSvc.Name != bSvc.Name || aSvc.Namespace != bSvc.Namespace {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc TestListServices(t *testing.T) {\n\tt.Run(\"Successfully queries for services\", func(t *testing.T) {\n\t\texpectations := []listServicesExpected{\n\t\t\t{\n\t\t\t\terr: nil,\n\t\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Service\nmetadata:\n  name: service-foo\n  namespace: emojivoto\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: service-bar\n  namespace: default\n`,\n\t\t\t\t},\n\t\t\t\tres: &pb.ListServicesResponse{\n\t\t\t\t\tServices: []*pb.Service{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:      \"service-foo\",\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:      \"service-bar\",\n\t\t\t\t\t\t\tNamespace: \"default\",\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\n\t\tfor _, exp := range expectations {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(exp.k8sRes...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tfakeGrpcServer := grpcServer{\n\t\t\t\tprometheusAPI:       &prometheus.MockProm{},\n\t\t\t\tk8sAPI:              k8sAPI,\n\t\t\t\tcontrollerNamespace: \"linkerd\",\n\t\t\t\tclusterDomain:       \"mycluster.local\",\n\t\t\t\tignoredNamespaces:   []string{},\n\t\t\t}\n\n\t\t\tk8sAPI.Sync(nil)\n\n\t\t\trsp, err := fakeGrpcServer.ListServices(context.TODO(), &pb.ListServicesRequest{})\n\t\t\tif !errors.Is(err, exp.err) {\n\t\t\t\tt.Fatalf(\"Expected error: %s, Got: %s\", exp.err, err)\n\t\t\t}\n\n\t\t\tif !listServiceResponsesEqual(exp.res, rsp) {\n\t\t\t\tt.Fatalf(\"Expected: %+v, Got: %+v\", &exp.res, rsp)\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "viz/metrics-api/policy.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/prometheus\"\n\t\"github.com/prometheus/common/model\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\nconst (\n\thttpAuthzDenyQuery  = \"sum(increase(inbound_http_authz_deny_total%s[%s])) by (%s)\"\n\thttpAuthzAllowQuery = \"sum(increase(inbound_http_authz_allow_total%s[%s])) by (%s)\"\n)\n\nfunc isPolicyResource(resource *pb.Resource) bool {\n\tif resource != nil {\n\t\tif resource.GetType() == k8s.Server ||\n\t\t\tresource.GetType() == k8s.ServerAuthorization ||\n\t\t\tresource.GetType() == k8s.AuthorizationPolicy ||\n\t\t\tresource.GetType() == k8s.HTTPRoute {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (s *grpcServer) policyResourceQuery(ctx context.Context, req *pb.StatSummaryRequest) resourceResult {\n\n\tpolicyResources, err := s.getPolicyResourceKeys(req)\n\tif err != nil {\n\t\treturn resourceResult{res: nil, err: err}\n\t}\n\n\tvar requestMetrics map[rKey]*pb.BasicStats\n\tvar tcpMetrics map[rKey]*pb.TcpStats\n\tvar authzMetrics map[rKey]*pb.ServerStats\n\tif !req.SkipStats {\n\t\trequestMetrics, tcpMetrics, authzMetrics, err = s.getPolicyMetrics(ctx, req, req.TimeWindow)\n\t\tif err != nil {\n\t\t\treturn resourceResult{res: nil, err: err}\n\t\t}\n\t}\n\n\trows := make([]*pb.StatTable_PodGroup_Row, 0)\n\tfor _, key := range policyResources {\n\t\trow := pb.StatTable_PodGroup_Row{\n\t\t\tResource: &pb.Resource{\n\t\t\t\tName:      key.Name,\n\t\t\t\tNamespace: key.Namespace,\n\t\t\t\tType:      req.GetSelector().GetResource().GetType(),\n\t\t\t},\n\t\t\tTimeWindow: req.TimeWindow,\n\t\t\tStats:      requestMetrics[key],\n\t\t\tTcpStats:   tcpMetrics[key],\n\t\t\tSrvStats:   authzMetrics[key],\n\t\t}\n\n\t\trows = append(rows, &row)\n\t}\n\n\trsp := pb.StatTable{\n\t\tTable: &pb.StatTable_PodGroup_{\n\t\t\tPodGroup: &pb.StatTable_PodGroup{\n\t\t\t\tRows: rows,\n\t\t\t},\n\t\t},\n\t}\n\treturn resourceResult{res: &rsp, err: nil}\n}\n\nfunc (s *grpcServer) getPolicyResourceKeys(req *pb.StatSummaryRequest) ([]rKey, error) {\n\tvar err error\n\tvar unstructuredResources *unstructured.UnstructuredList\n\n\t// TODO(ver): We should use a typed client\n\tvar gvr schema.GroupVersionResource\n\tif req.GetSelector().Resource.GetType() == k8s.Server {\n\t\tgvr = k8s.ServerGVR\n\t} else if req.GetSelector().Resource.GetType() == k8s.ServerAuthorization {\n\t\tgvr = k8s.SazGVR\n\t} else if req.GetSelector().Resource.GetType() == k8s.AuthorizationPolicy {\n\t\tgvr = k8s.AuthorizationPolicyGVR\n\t} else if req.GetSelector().Resource.GetType() == k8s.HTTPRoute {\n\t\tgvr = k8s.HTTPRouteGVR\n\t}\n\n\tres := req.GetSelector().GetResource()\n\tlabelSelector, err := getLabelSelector(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif res.GetNamespace() == \"\" {\n\t\tunstructuredResources, err = s.k8sAPI.DynamicClient.Resource(gvr).Namespace(\"\").\n\t\t\tList(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector.String()})\n\t} else if res.GetName() == \"\" {\n\t\tunstructuredResources, err = s.k8sAPI.DynamicClient.Resource(gvr).Namespace(res.GetNamespace()).\n\t\t\tList(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector.String()})\n\t} else {\n\t\tvar ts *unstructured.Unstructured\n\t\tts, err = s.k8sAPI.DynamicClient.Resource(gvr).Namespace(res.GetNamespace()).\n\t\t\tGet(context.TODO(), res.GetName(), metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tunstructuredResources = &unstructured.UnstructuredList{Items: []unstructured.Unstructured{*ts}}\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar resourceKeys []rKey\n\tfor _, resource := range unstructuredResources.Items {\n\t\t// Resource Key's type should be singular and lowercased while the kind isn't\n\t\tresourceKeys = append(resourceKeys, rKey{\n\t\t\tNamespace: resource.GetNamespace(),\n\t\t\t// TODO(ver) This isn't a reliable way to make a plural name singular.\n\t\t\tType: strings.ToLower(resource.GetKind()[0:len(resource.GetKind())]),\n\t\t\tName: resource.GetName(),\n\t\t})\n\t}\n\treturn resourceKeys, nil\n}\n\nfunc (s *grpcServer) getPolicyMetrics(\n\tctx context.Context,\n\treq *pb.StatSummaryRequest,\n\ttimeWindow string,\n) (map[rKey]*pb.BasicStats, map[rKey]*pb.TcpStats, map[rKey]*pb.ServerStats, error) {\n\tlabels, groupBy := buildServerRequestLabels(req)\n\t// These metrics are always inbound.\n\treqLabels := labels.Merge(model.LabelSet{\n\t\t\"direction\": model.LabelValue(\"inbound\"),\n\t})\n\n\tpromQueries := make(map[promType]string)\n\tif req.GetSelector().GetResource().GetType() == k8s.Server {\n\t\t// TCP metrics are only supported with servers\n\t\tif req.TcpStats {\n\t\t\t// peer is always `src` as these are inbound metrics\n\t\t\ttcpLabels := reqLabels.Merge(promPeerLabel(\"src\"))\n\t\t\tpromQueries[promTCPConnections] = fmt.Sprintf(tcpConnectionsQuery, tcpLabels.String(), groupBy.String())\n\t\t\tpromQueries[promTCPReadBytes] = fmt.Sprintf(tcpReadBytesQuery, tcpLabels.String(), timeWindow, groupBy.String())\n\t\t\tpromQueries[promTCPWriteBytes] = fmt.Sprintf(tcpWriteBytesQuery, tcpLabels.String(), timeWindow, groupBy.String())\n\t\t}\n\t}\n\n\tpromQueries[promRequests] = fmt.Sprintf(reqQuery, reqLabels, timeWindow, groupBy.String())\n\t// Use `labels` as direction isn't present with authorization metrics\n\tpromQueries[promAllowedRequests] = fmt.Sprintf(httpAuthzAllowQuery, labels, timeWindow, groupBy.String())\n\tpromQueries[promDeniedRequests] = fmt.Sprintf(httpAuthzDenyQuery, labels, timeWindow, groupBy.String())\n\tquantileQueries := generateQuantileQueries(latencyQuantileQuery, reqLabels.String(), timeWindow, groupBy.String())\n\tresults, err := s.getPrometheusMetrics(ctx, promQueries, quantileQueries)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\n\tbasicStats, tcpStats, authzStats := processPrometheusMetrics(req, results, groupBy)\n\treturn basicStats, tcpStats, authzStats, nil\n}\n\nfunc buildServerRequestLabels(req *pb.StatSummaryRequest) (labels model.LabelSet, labelNames model.LabelNames) {\n\tif req.GetSelector().GetResource().GetNamespace() != \"\" {\n\t\tlabels = labels.Merge(model.LabelSet{\n\t\t\tprometheus.NamespaceLabel: model.LabelValue(req.GetSelector().GetResource().GetNamespace()),\n\t\t})\n\t}\n\tvar groupBy model.LabelNames\n\tif req.GetSelector().GetResource().GetType() == k8s.Server {\n\t\t// note that metricToKey assumes the label ordering (..., namespace, name)\n\t\tgroupBy = model.LabelNames{prometheus.ServerKindLabel, prometheus.NamespaceLabel, prometheus.ServerNameLabel}\n\t\tlabels = labels.Merge(model.LabelSet{\n\t\t\tprometheus.ServerKindLabel: model.LabelValue(\"server\"),\n\t\t})\n\t\tif req.GetSelector().GetResource().GetName() != \"\" {\n\t\t\tlabels = labels.Merge(model.LabelSet{\n\t\t\t\tprometheus.ServerNameLabel: model.LabelValue(req.GetSelector().GetResource().GetName()),\n\t\t\t})\n\t\t}\n\t} else if req.GetSelector().GetResource().GetType() == k8s.ServerAuthorization {\n\t\t// note that metricToKey assumes the label ordering (..., namespace, name)\n\t\tgroupBy = model.LabelNames{prometheus.NamespaceLabel, prometheus.AuthorizationNameLabel}\n\t\tlabels = labels.Merge(model.LabelSet{\n\t\t\tprometheus.AuthorizationKindLabel: model.LabelValue(\"serverauthorization\"),\n\t\t})\n\t\tif req.GetSelector().GetResource().GetName() != \"\" {\n\t\t\tlabels = labels.Merge(model.LabelSet{\n\t\t\t\tprometheus.AuthorizationNameLabel: model.LabelValue(req.GetSelector().GetResource().GetName()),\n\t\t\t})\n\t\t}\n\t} else if req.GetSelector().GetResource().GetType() == k8s.AuthorizationPolicy {\n\t\t// note that metricToKey assumes the label ordering (..., namespace, name)\n\t\tgroupBy = model.LabelNames{prometheus.NamespaceLabel, prometheus.AuthorizationNameLabel}\n\t\tlabels = labels.Merge(model.LabelSet{\n\t\t\tprometheus.AuthorizationKindLabel: model.LabelValue(\"authorizationpolicy\"),\n\t\t})\n\t\tif req.GetSelector().GetResource().GetName() != \"\" {\n\t\t\tlabels = labels.Merge(model.LabelSet{\n\t\t\t\tprometheus.AuthorizationNameLabel: model.LabelValue(req.GetSelector().GetResource().GetName()),\n\t\t\t})\n\t\t}\n\t} else if req.GetSelector().GetResource().GetType() == k8s.HTTPRoute {\n\t\t// note that metricToKey assumes the label ordering (..., namespace, name)\n\t\tgroupBy = model.LabelNames{prometheus.ServerNameLabel, prometheus.ServerKindLabel, prometheus.RouteNameLabel, prometheus.RouteKindLabel, prometheus.NamespaceLabel, prometheus.RouteNameLabel}\n\t\tif req.GetSelector().GetResource().GetName() != \"\" {\n\t\t\tlabels = labels.Merge(model.LabelSet{\n\t\t\t\tprometheus.RouteNameLabel: model.LabelValue(req.GetSelector().GetResource().GetName()),\n\t\t\t})\n\t\t}\n\t}\n\n\tswitch out := req.Outbound.(type) {\n\tcase *pb.StatSummaryRequest_ToResource:\n\t\t// if --to flag is passed, Calculate traffic sent to the policy resource\n\t\t// with additional filtering narrowing down to the workload\n\t\t// it is sent to.\n\t\tlabels = labels.Merge(prometheus.QueryLabels(out.ToResource))\n\n\t// No FromResource case as policy metrics are all inbound\n\tdefault:\n\t\t// no extra labels needed\n\t}\n\n\treturn labels, groupBy\n}\n"
  },
  {
    "path": "viz/metrics-api/prometheus.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/prometheus/common/model\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"go.opencensus.io/trace\"\n)\n\ntype promType string\ntype promResult struct {\n\tprom promType\n\tvec  model.Vector\n\terr  error\n}\n\nconst (\n\tpromGatewayAlive    = promType(\"QUERY_GATEWAY_ALIVE\")\n\tpromRequests        = promType(\"QUERY_REQUESTS\")\n\tpromAllowedRequests = promType(\"QUERY_ALLOWED_REQUESTS\")\n\tpromDeniedRequests  = promType(\"QUERY_DENIED_REQUESTS\")\n\tpromActualRequests  = promType(\"QUERY_ACTUAL_REQUESTS\")\n\tpromTCPConnections  = promType(\"QUERY_TCP_CONNECTIONS\")\n\tpromTCPReadBytes    = promType(\"QUERY_TCP_READ_BYTES\")\n\tpromTCPWriteBytes   = promType(\"QUERY_TCP_WRITE_BYTES\")\n\tpromLatencyP50      = promType(\"0.5\")\n\tpromLatencyP95      = promType(\"0.95\")\n\tpromLatencyP99      = promType(\"0.99\")\n)\n\nvar (\n\t// ErrNoPrometheusInstance is returned when there is no prometheus instance configured\n\tErrNoPrometheusInstance = errors.New(\"No prometheus instance to connect\")\n)\n\nfunc extractSampleValue(sample *model.Sample) uint64 {\n\tvalue := uint64(0)\n\tif !math.IsNaN(float64(sample.Value)) {\n\t\tvalue = uint64(math.Round(float64(sample.Value)))\n\t}\n\treturn value\n}\n\nfunc (s *grpcServer) queryProm(ctx context.Context, query string) (model.Vector, error) {\n\tlog.Debugf(\"Query request: %q\", query)\n\n\t_, span := trace.StartSpan(ctx, \"query.prometheus\")\n\tdefer span.End()\n\tspan.AddAttributes(trace.StringAttribute(\"queryString\", query))\n\n\tif s.prometheusAPI == nil {\n\t\treturn nil, ErrNoPrometheusInstance\n\t}\n\n\t// single data point (aka summary) query\n\tres, warn, err := s.prometheusAPI.Query(ctx, query, time.Time{})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Query failed: %q: %w\", query, err)\n\t}\n\tif warn != nil {\n\t\tlog.Warnf(\"%v\", warn)\n\t}\n\tlog.Debugf(\"Query response:\\n\\t%+v\", res)\n\n\tif res.Type() != model.ValVector {\n\t\treturn nil, fmt.Errorf(\"Unexpected query result type (expected Vector): %s\", res.Type())\n\t}\n\n\treturn res.(model.Vector), nil\n}\n\n// insert a not-nil check into a LabelSet to verify that data for a specified\n// label name exists. due to the `!=` this must be inserted as a string. the\n// structure of this code is taken from the Prometheus labelset.go library.\nfunc generateLabelStringWithExclusion(l model.LabelSet, labelNames ...string) string {\n\tlstrs := make([]string, 0, len(l))\n\tfor l, v := range l {\n\t\tlstrs = append(lstrs, fmt.Sprintf(\"%s=%q\", l, v))\n\t}\n\tfor _, labelName := range labelNames {\n\t\tlstrs = append(lstrs, fmt.Sprintf(`%s!=\"\"`, labelName))\n\t}\n\n\tsort.Strings(lstrs)\n\treturn fmt.Sprintf(\"{%s}\", strings.Join(lstrs, \", \"))\n}\n\n// insert a regex-match check into a LabelSet for labels that match the provided\n// string. this is modeled on generateLabelStringWithExclusion().\nfunc generateLabelStringWithRegex(l model.LabelSet, labelName string, stringToMatch string) string {\n\tlstrs := make([]string, 0, len(l))\n\tfor l, v := range l {\n\t\tlstrs = append(lstrs, fmt.Sprintf(\"%s=%q\", l, v))\n\t}\n\tlstrs = append(lstrs, fmt.Sprintf(`%s=~\"^%s.*\"`, labelName, stringToMatch))\n\n\tsort.Strings(lstrs)\n\treturn fmt.Sprintf(\"{%s}\", strings.Join(lstrs, \", \"))\n}\n\n// generate Prometheus queries for latency quantiles, based on a quantile query\n// template, query labels, a time window and grouping.\nfunc generateQuantileQueries(quantileQuery, labels, timeWindow, groupBy string) map[promType]string {\n\treturn map[promType]string{\n\t\tpromLatencyP50: fmt.Sprintf(quantileQuery, promLatencyP50, labels, timeWindow, groupBy),\n\t\tpromLatencyP95: fmt.Sprintf(quantileQuery, promLatencyP95, labels, timeWindow, groupBy),\n\t\tpromLatencyP99: fmt.Sprintf(quantileQuery, promLatencyP99, labels, timeWindow, groupBy),\n\t}\n}\n\n// query for inbound or outbound requests\nfunc promDirectionLabels(direction string) model.LabelSet {\n\treturn model.LabelSet{\n\t\tmodel.LabelName(\"direction\"): model.LabelValue(direction),\n\t}\n}\n\nfunc promPeerLabel(peer string) model.LabelSet {\n\treturn model.LabelSet{\n\t\tmodel.LabelName(\"peer\"): model.LabelValue(peer),\n\t}\n}\n\nfunc (s *grpcServer) getPrometheusMetrics(ctx context.Context, requestQueries map[promType]string, latencyQueries map[promType]string) ([]promResult, error) {\n\tresultChan := make(chan promResult)\n\n\tfor pt, query := range requestQueries {\n\t\tgo func(typ promType, promQuery string) {\n\t\t\tresultVector, err := s.queryProm(ctx, promQuery)\n\t\t\tresultChan <- promResult{\n\t\t\t\tprom: typ,\n\t\t\t\tvec:  resultVector,\n\t\t\t\terr:  err,\n\t\t\t}\n\t\t}(pt, query)\n\t}\n\n\tfor quantile, query := range latencyQueries {\n\t\tgo func(qt promType, promQuery string) {\n\t\t\tresultVector, err := s.queryProm(ctx, promQuery)\n\t\t\tresultChan <- promResult{\n\t\t\t\tprom: qt,\n\t\t\t\tvec:  resultVector,\n\t\t\t\terr:  err,\n\t\t\t}\n\t\t}(quantile, query)\n\t}\n\t// process results, receive one message per prometheus query type\n\tvar err error\n\tresults := []promResult{}\n\tfor i := 0; i < len(latencyQueries)+len(requestQueries); i++ {\n\t\tresult := <-resultChan\n\t\tif result.err != nil {\n\t\t\tlog.Errorf(\"queryProm failed with: %s\", result.err)\n\t\t\terr = result.err\n\t\t} else {\n\t\t\tresults = append(results, result)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn results, nil\n}\n"
  },
  {
    "path": "viz/metrics-api/prometheus_test.go",
    "content": "package api\n\nimport (\n\t\"testing\"\n\n\t\"github.com/prometheus/common/model\"\n)\n\nfunc TestGenerateLabelStringWithRegex(t *testing.T) {\n\tquery := generateLabelStringWithRegex(model.LabelSet{}, \"key\", \"value\")\n\tif query != \"{key=~\\\"^value.*\\\"}\" {\n\t\tt.Errorf(\"Expected 'key=~\\\"^value.+\\\"', got '%s'\", query)\n\t}\n}\n"
  },
  {
    "path": "viz/metrics-api/proto/viz.proto",
    "content": "syntax = \"proto3\";\n\npackage linkerd2.viz;\n\nimport \"google/protobuf/duration.proto\";\n\noption go_package = \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\";\n\nmessage Empty {}\n\nenum CheckStatus {\n    OK = 0;\n    FAIL = 1;\n    ERROR = 2;\n}\n\nmessage CheckResult {\n    string SubsystemName = 1;\n    string CheckDescription = 2;\n    CheckStatus Status = 3;\n    string FriendlyMessageToUser = 4;\n}\n\nmessage SelfCheckRequest {}\n\nmessage SelfCheckResponse {\n    repeated CheckResult results = 1;\n}\n\nmessage ListServicesRequest {\n  string namespace = 1;\n}\nmessage ListServicesResponse {\n  repeated Service services = 1;\n}\nmessage Service {\n  string name = 1;\n  string namespace = 2;\n}\n\nmessage ListPodsRequest {\n  ResourceSelection selector = 2;\n}\nmessage ListPodsResponse {\n  repeated Pod pods = 1;\n}\n\nmessage Pod {\n  string name = 1;\n  string podIP = 2;\n  oneof owner {\n    string deployment = 3;\n    string replica_set = 10;\n    string replication_controller = 11;\n    string stateful_set = 12;\n    string daemon_set = 13;\n    string job = 14;\n  }\n  string status = 4;\n  bool added = 5; // true if this pod has a proxy sidecar (data plane)\n  google.protobuf.Duration sinceLastReport = 6;\n  string controllerNamespace = 7; // namespace of controller this pod reports to\n  bool controlPlane = 8; // true if this pod is part of the control plane\n  google.protobuf.Duration uptime = 9; // uptime of this pod\n  bool proxyReady = 15; // true if this pod has proxy container and that one is in ready state\n  string proxyVersion = 16; // version of the proxy if present\n  string resourceVersion = 17; // resource version in the Kubernetes API\n}\n\nmessage HttpMethod {\n  enum Registered {\n    GET = 0;\n    POST = 1;\n    PUT = 2;\n    DELETE = 3;\n    PATCH = 4;\n    OPTIONS = 5;\n    CONNECT = 6;\n    HEAD = 7;\n    TRACE = 8;\n  }\n\n  oneof type {\n    Registered registered = 1;\n    string unregistered = 2;\n  }\n}\n\nmessage Scheme {\n  enum Registered {\n    HTTP = 0;\n    HTTPS = 1;\n  }\n\n  oneof type {\n    Registered registered = 1;\n    string unregistered = 2;\n  }\n}\n\nmessage Headers {\n  message Header {\n    // The name of a header in a request.\n    string name = 1;\n    // The value of a header in a request. If the value consists entirely of\n    // UTF-8 encodings, `value` will be set; otherwise a binary value is\n    // assumed and `value_bin` will be set.\n    oneof value {\n      string value_str = 2;\n      bytes value_bin = 3;\n    }\n  }\n\n  repeated Header headers = 1;\n}\n\nmessage Eos {\n  oneof end {\n    uint32 grpc_status_code = 1;\n    uint32 reset_error_code = 2;\n  }\n}\n\nmessage ApiError {\n  string error = 1;\n}\n\nmessage PodErrors {\n  repeated PodError errors = 1;\n\n  message PodError {\n    oneof error {\n      ContainerError container = 1;\n    }\n\n    // To report init-container and container failures\n    message ContainerError {\n      string message = 1;\n      string container = 2;\n      string image = 3;\n      string reason = 4;\n    }\n  }\n}\n\nmessage Resource {\n  // The namespace the resource is in.\n  //\n  // If empty, indicates all namespaces should be considered.\n  string namespace = 1;\n\n  // The type of resource.\n  //\n  // This can be:\n  // - \"all\" -- includes all Kubernetes resource types only\n  // - \"authority\" -- a special resource type derived from request `:authority` values\n  // - Otherwise, the resource type may be any Kubernetes resource (e.g. \"namespace\", \"deployment\").\n  string type = 2;\n\n  // An optional resource name.\n  string name = 3;\n}\n\nmessage ResourceSelection {\n  // Identifies a Kubernetes resource.\n  Resource resource = 1;\n\n  // A string-formatted Kubernetes label selector as passed to `kubectl get\n  // --selector`.\n  //\n  // XXX in the future this may be superseded by a data structure that more\n  // richly describes a parsed label selector.\n  string label_selector = 2;\n}\n\nmessage ResourceError {\n  Resource resource = 1;\n  string error = 2;\n}\n\nmessage StatSummaryRequest {\n  ResourceSelection selector = 1;\n  string time_window = 2;\n\n  oneof outbound {\n    Empty none = 3;\n    Resource to_resource   = 4;\n    Resource from_resource = 5;\n  }\n\n  bool skip_stats = 6;  // true if we want to skip stats from Prometheus\n  bool tcp_stats = 7;\n}\n\nmessage StatSummaryResponse {\n  oneof response {\n    Ok ok = 1;\n    ResourceError error = 2;\n  }\n\n  message Ok {\n    repeated StatTable stat_tables = 1;\n  }\n}\n\nmessage AuthzRequest {\n  Resource resource = 1;\n  string time_window = 2;\n}\n\nmessage AuthzResponse {\n  oneof response {\n    Ok ok = 1;\n    ResourceError error = 2;\n  }\n\n  message Ok {\n    StatTable stat_table = 1;\n  }\n}\n\nmessage BasicStats {\n  uint64 success_count = 1;\n  uint64 failure_count = 2;\n  uint64 latency_ms_p50 = 3;\n  uint64 latency_ms_p95 = 4;\n  uint64 latency_ms_p99 = 5;\n  uint64 actual_success_count = 6;\n  uint64 actual_failure_count = 7;\n}\n\nmessage TcpStats {\n  // number of currently open connections\n  uint64 open_connections = 1;\n  // total count of bytes read from peers\n  uint64 read_bytes_total = 2;\n  // total count of bytes written to peers\n  uint64 write_bytes_total = 3;\n}\n\nmessage TrafficSplitStats {\n  string apex = 2;\n  string leaf = 3;\n  string weight = 4;\n}\n\nmessage ServerStats {\n  uint64 allowed_count = 1;\n  uint64 denied_count = 2;\n  Resource srv = 3;\n  Resource route = 4;\n  Resource authz = 5;\n}\n\nmessage StatTable {\n  oneof table {\n    PodGroup pod_group = 1;\n  }\n\n  message PodGroup {\n    repeated Row rows = 1;\n\n    message Row {\n      Resource resource = 1;\n      string time_window = 2;\n\n      // pod status on Kubernetes\n      string status = 9;\n      // number of pending or running pods in this resource that have linkerd injected\n      uint64 meshed_pod_count = 3;\n      // number of pending or running pods in this resource\n      uint64 running_pod_count = 4;\n      // number of pods in this resource that have Phase PodFailed\n      uint64 failed_pod_count = 6;\n\n      BasicStats stats = 5;\n      TcpStats tcp_stats = 8;\n      TrafficSplitStats ts_stats = 10;\n      ServerStats srv_stats = 11;\n\n      // Stores a set of errors for each pod name. If a pod has no errors, it may be omitted.\n      map<string, PodErrors> errors_by_pod = 7;\n    }\n  }\n}\n\nmessage EdgesRequest {\n  ResourceSelection selector = 1;\n}\n\nmessage EdgesResponse {\n  oneof response {\n    Ok ok = 1;\n    ResourceError error = 2;\n  }\n\n  message Ok {\n    repeated Edge edges = 1;\n  }\n}\n\nmessage Edge {\n  Resource src = 1;\n  Resource dst = 2;\n  string client_id = 3;\n  string server_id = 4;\n  string no_identity_msg = 5;\n}\n\nmessage TopRoutesRequest {\n  ResourceSelection selector = 1;\n  string time_window = 2;\n\n  oneof outbound {\n    Empty none = 3;\n    Resource to_resource = 7;\n  }\n}\n\nmessage TopRoutesResponse {\n  oneof response {\n    ResourceError error = 2;\n    Ok ok = 3;\n  }\n\n  message Ok {\n    repeated RouteTable routes = 1;\n  }\n}\n\nmessage RouteTable {\n  repeated Row rows = 1;\n  string resource = 2;\n\n  message Row {\n    string route = 1;\n    string time_window = 2;\n    string authority = 6;\n\n    BasicStats stats = 5;\n  }\n}\n\n\nmessage GatewaysTable {\n  repeated Row rows = 1;\n\n  message Row {\n    string namespace = 1;\n    string name = 2;\n    string cluster_name = 3;\n    uint64 paired_services = 4;\n    bool alive = 5;\n    uint64 latency_ms_p50 = 6;\n    uint64 latency_ms_p95 = 7;\n    uint64 latency_ms_p99 = 8;\n  }\n}\n\nmessage GatewaysRequest {\n  string remote_cluster_name = 1;\n  string gateway_namespace = 2;\n  string time_window = 3;\n}\n\nmessage GatewaysResponse {\n  oneof response {\n    Ok ok = 1;\n    ResourceError error = 2;\n  }\n\n  message Ok {\n    GatewaysTable gateways_table = 1;\n  }\n}\n\nservice Api {\n  rpc StatSummary(StatSummaryRequest) returns (StatSummaryResponse) {}\n\n  rpc Edges(EdgesRequest) returns (EdgesResponse) {}\n\n  rpc Gateways(GatewaysRequest) returns (GatewaysResponse) {}\n\n  rpc TopRoutes(TopRoutesRequest) returns (TopRoutesResponse) {}\n\n  rpc ListPods(ListPodsRequest) returns (ListPodsResponse) {}\n\n  rpc ListServices(ListServicesRequest) returns (ListServicesResponse) {}\n\n  rpc SelfCheck(SelfCheckRequest) returns (SelfCheckResponse) {}\n\n  rpc Authz(AuthzRequest) returns (AuthzResponse) {}\n}\n"
  },
  {
    "path": "viz/metrics-api/stat_summary.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/prometheus\"\n\tvizutil \"github.com/linkerd/linkerd2/viz/pkg/util\"\n\t\"github.com/prometheus/common/model\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"google.golang.org/protobuf/proto\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n)\n\ntype resourceResult struct {\n\tres *pb.StatTable\n\terr error\n}\ntype k8sStat struct {\n\tobject   metav1.Object\n\tpodStats *podStats\n}\n\ntype rKey struct {\n\tNamespace string\n\tType      string\n\tName      string\n}\n\ntype dstKey struct {\n\tNamespace string\n\tService   string\n\tDst       string\n\tWeight    string\n}\n\nconst (\n\tsuccess = \"success\"\n\tfailure = \"failure\"\n\n\treqQuery             = \"sum(increase(response_total%s[%s])) by (%s, classification, tls)\"\n\tlatencyQuantileQuery = \"histogram_quantile(%s, sum(irate(response_latency_ms_bucket%s[%s])) by (le, %s))\"\n\ttcpConnectionsQuery  = \"sum(tcp_open_connections%s) by (%s)\"\n\ttcpReadBytesQuery    = \"sum(increase(tcp_read_bytes_total%s[%s])) by (%s)\"\n\ttcpWriteBytesQuery   = \"sum(increase(tcp_write_bytes_total%s[%s])) by (%s)\"\n\n\tregexAny = \".+\"\n)\n\ntype podStats struct {\n\tstatus string\n\tinMesh uint64\n\ttotal  uint64\n\tfailed uint64\n\terrors map[string]*pb.PodErrors\n}\n\nfunc (s *grpcServer) StatSummary(ctx context.Context, req *pb.StatSummaryRequest) (*pb.StatSummaryResponse, error) {\n\n\t// check for well-formed request\n\tif req.GetSelector().GetResource() == nil {\n\t\treturn statSummaryError(req, \"StatSummary request missing Selector Resource\"), nil\n\t}\n\n\t// err if --from is a service\n\tif req.GetFromResource() != nil && req.GetFromResource().GetType() == k8s.Service {\n\t\treturn statSummaryError(req, \"service is not supported as a target on 'from' queries, or as a target with 'to' queries\"), nil\n\t}\n\n\t// err if --from is added with policy resources\n\tif req.GetFromResource() != nil {\n\t\tif isPolicyResource(req.GetSelector().GetResource()) ||\n\t\t\tisPolicyResource(req.GetFromResource()) {\n\t\t\treturn statSummaryError(req, \"'from' queries are not supported with policy resources, as they have inbound metrics only\"), nil\n\t\t}\n\t}\n\n\tif req.GetToResource() != nil {\n\t\tif isPolicyResource(req.GetSelector().GetResource()) ||\n\t\t\tisPolicyResource(req.GetToResource()) {\n\t\t\treturn statSummaryError(req, \"'to' queries are not supported with policy resources, as they have inbound metrics only\"), nil\n\t\t}\n\t}\n\n\tswitch ob := req.Outbound.(type) {\n\tcase *pb.StatSummaryRequest_ToResource:\n\t\tif ob.ToResource.Type == k8s.All {\n\t\t\treturn statSummaryError(req, \"resource type 'all' is not supported as a filter\"), nil\n\t\t}\n\tcase *pb.StatSummaryRequest_FromResource:\n\t\tif ob.FromResource.Type == k8s.All {\n\t\t\treturn statSummaryError(req, \"resource type 'all' is not supported as a filter\"), nil\n\t\t}\n\t}\n\n\terr := s.validateTimeWindow(ctx, req.TimeWindow)\n\tif err != nil {\n\t\treturn statSummaryError(req, fmt.Sprintf(\"invalid time window: %s\", err)), nil\n\t}\n\n\tstatTables := make([]*pb.StatTable, 0)\n\n\tvar resourcesToQuery []string\n\tif req.Selector.Resource.Type == k8s.All {\n\t\tresourcesToQuery = k8s.StatAllResourceTypes\n\t} else {\n\t\tresourcesToQuery = []string{req.Selector.Resource.Type}\n\t}\n\n\t// request stats for the resourcesToQuery, in parallel\n\tresultChan := make(chan resourceResult)\n\n\tfor _, resource := range resourcesToQuery {\n\t\tstatReq := proto.Clone(req).(*pb.StatSummaryRequest)\n\t\tstatReq.Selector.Resource.Type = resource\n\n\t\tgo func() {\n\t\t\tif statReq.GetSelector().GetResource().GetType() == k8s.Service {\n\t\t\t\tresultChan <- s.serviceResourceQuery(ctx, statReq)\n\t\t\t} else if isPolicyResource(statReq.GetSelector().GetResource()) {\n\t\t\t\tresultChan <- s.policyResourceQuery(ctx, statReq)\n\t\t\t} else {\n\t\t\t\tresultChan <- s.k8sResourceQuery(ctx, statReq)\n\t\t\t}\n\t\t}()\n\t}\n\n\tfor i := 0; i < len(resourcesToQuery); i++ {\n\t\tresult := <-resultChan\n\t\tif result.err != nil {\n\t\t\treturn nil, vizutil.GRPCError(result.err)\n\t\t}\n\t\tstatTables = append(statTables, result.res)\n\t}\n\n\trsp := pb.StatSummaryResponse{\n\t\tResponse: &pb.StatSummaryResponse_Ok_{ // https://github.com/golang/protobuf/issues/205\n\t\t\tOk: &pb.StatSummaryResponse_Ok{\n\t\t\t\tStatTables: statTables,\n\t\t\t},\n\t\t},\n\t}\n\n\tlog.Debugf(\"Sent response as %+v\\n\", statTables)\n\treturn &rsp, nil\n}\n\nfunc statSummaryError(req *pb.StatSummaryRequest, message string) *pb.StatSummaryResponse {\n\treturn &pb.StatSummaryResponse{\n\t\tResponse: &pb.StatSummaryResponse_Error{\n\t\t\tError: &pb.ResourceError{\n\t\t\t\tResource: req.GetSelector().GetResource(),\n\t\t\t\tError:    message,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc (s *grpcServer) getKubernetesObjectStats(req *pb.StatSummaryRequest) (map[rKey]k8sStat, error) {\n\trequestedResource := req.GetSelector().GetResource()\n\n\tlabelSelector, err := getLabelSelector(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobjects, err := s.k8sAPI.GetObjects(requestedResource.Namespace, requestedResource.Type, requestedResource.Name, labelSelector)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tobjectMap := map[rKey]k8sStat{}\n\n\tfor _, object := range objects {\n\t\tmetaObj, err := meta.Accessor(object)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tkey := rKey{\n\t\t\tName:      metaObj.GetName(),\n\t\t\tNamespace: metaObj.GetNamespace(),\n\t\t\tType:      requestedResource.GetType(),\n\t\t}\n\n\t\tpodStats, err := s.getPodStats(object)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tobjectMap[key] = k8sStat{\n\t\t\tobject:   metaObj,\n\t\t\tpodStats: podStats,\n\t\t}\n\t}\n\treturn objectMap, nil\n}\n\nfunc (s *grpcServer) k8sResourceQuery(ctx context.Context, req *pb.StatSummaryRequest) resourceResult {\n\n\tk8sObjects, err := s.getKubernetesObjectStats(req)\n\tif err != nil {\n\t\treturn resourceResult{res: nil, err: err}\n\t}\n\n\tvar requestMetrics map[rKey]*pb.BasicStats\n\tvar tcpMetrics map[rKey]*pb.TcpStats\n\tif !req.SkipStats {\n\t\trequestMetrics, tcpMetrics, err = s.getStatMetrics(ctx, req, req.TimeWindow)\n\t\tif err != nil {\n\t\t\treturn resourceResult{res: nil, err: err}\n\t\t}\n\t}\n\n\trows := make([]*pb.StatTable_PodGroup_Row, 0)\n\tkeys := getResultKeys(req, k8sObjects, requestMetrics)\n\n\tfor _, key := range keys {\n\t\tobjInfo, ok := k8sObjects[key]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar tcpStats *pb.TcpStats\n\t\tif req.TcpStats {\n\t\t\ttcpStats = tcpMetrics[key]\n\t\t}\n\n\t\tvar basicStats *pb.BasicStats\n\t\tif !reflect.DeepEqual(requestMetrics[key], &pb.BasicStats{}) {\n\t\t\tbasicStats = requestMetrics[key]\n\t\t}\n\n\t\tk8sResource := objInfo.object\n\t\trow := pb.StatTable_PodGroup_Row{\n\t\t\tResource: &pb.Resource{\n\t\t\t\tName:      k8sResource.GetName(),\n\t\t\t\tNamespace: k8sResource.GetNamespace(),\n\t\t\t\tType:      req.GetSelector().GetResource().GetType(),\n\t\t\t},\n\t\t\tTimeWindow: req.TimeWindow,\n\t\t\tStats:      basicStats,\n\t\t\tTcpStats:   tcpStats,\n\t\t}\n\n\t\tpodStat := objInfo.podStats\n\t\trow.Status = podStat.status\n\t\trow.MeshedPodCount = podStat.inMesh\n\t\trow.RunningPodCount = podStat.total\n\t\trow.FailedPodCount = podStat.failed\n\t\trow.ErrorsByPod = podStat.errors\n\n\t\trows = append(rows, &row)\n\t}\n\n\trsp := pb.StatTable{\n\t\tTable: &pb.StatTable_PodGroup_{\n\t\t\tPodGroup: &pb.StatTable_PodGroup{\n\t\t\t\tRows: rows,\n\t\t\t},\n\t\t},\n\t}\n\n\treturn resourceResult{res: &rsp, err: nil}\n}\n\nfunc (s *grpcServer) serviceResourceQuery(ctx context.Context, req *pb.StatSummaryRequest) resourceResult {\n\n\trows := make([]*pb.StatTable_PodGroup_Row, 0)\n\tdstBasicStats := make(map[dstKey]*pb.BasicStats)\n\tdstTCPStats := make(map[dstKey]*pb.TcpStats)\n\n\tif !req.SkipStats {\n\t\tvar err error\n\t\tdstBasicStats, dstTCPStats, err = s.getServiceMetrics(ctx, req, req.TimeWindow)\n\t\tif err != nil {\n\t\t\treturn resourceResult{res: nil, err: err}\n\t\t}\n\t}\n\n\tweights := make(map[dstKey]string)\n\tfor k := range dstBasicStats {\n\t\tweights[k] = \"\"\n\t}\n\n\tname := req.GetSelector().GetResource().GetName()\n\tnamespace := req.GetSelector().GetResource().GetNamespace()\n\n\t// Check if a ServiceProfile exists for the Service\n\tspName := fmt.Sprintf(\"%s.%s.svc.%s\", name, namespace, s.clusterDomain)\n\tsp, err := s.k8sAPI.SP().Lister().ServiceProfiles(namespace).Get(spName)\n\tif err == nil {\n\t\tfor _, weightedDst := range sp.Spec.DstOverrides {\n\t\t\tweights[dstKey{\n\t\t\t\tNamespace: namespace,\n\t\t\t\tService:   name,\n\t\t\t\tDst:       dstFromAuthority(weightedDst.Authority),\n\t\t\t}] = weightedDst.Weight.String()\n\t\t}\n\t} else if !kerrors.IsNotFound(err) {\n\t\tlog.Errorf(\"Failed to get weights from ServiceProfile %q: %v\", spName, err)\n\t}\n\n\tfor k, weight := range weights {\n\t\trow := pb.StatTable_PodGroup_Row{\n\t\t\tResource: &pb.Resource{\n\t\t\t\tName:      k.Service,\n\t\t\t\tNamespace: k.Namespace,\n\t\t\t\tType:      req.GetSelector().GetResource().GetType(),\n\t\t\t},\n\t\t\tTimeWindow: req.TimeWindow,\n\t\t\tStats:      dstBasicStats[k],\n\t\t\tTcpStats:   dstTCPStats[k],\n\t\t}\n\n\t\t// Set TrafficSplitStats only when weight is not empty\n\t\tif weight != \"\" {\n\t\t\trow.TsStats = &pb.TrafficSplitStats{\n\t\t\t\tApex:   k.Service,\n\t\t\t\tLeaf:   k.Dst,\n\t\t\t\tWeight: weight,\n\t\t\t}\n\t\t}\n\t\trows = append(rows, &row)\n\t}\n\n\t// sort rows before returning in order to have a consistent order for tests\n\trows = sortTrafficSplitRows(rows)\n\n\trsp := pb.StatTable{\n\t\tTable: &pb.StatTable_PodGroup_{\n\t\t\tPodGroup: &pb.StatTable_PodGroup{\n\t\t\t\tRows: rows,\n\t\t\t},\n\t\t},\n\t}\n\n\treturn resourceResult{res: &rsp, err: nil}\n}\n\nfunc sortTrafficSplitRows(rows []*pb.StatTable_PodGroup_Row) []*pb.StatTable_PodGroup_Row {\n\tsort.Slice(rows, func(i, j int) bool {\n\t\tif rows[i].TsStats != nil && rows[j].TsStats != nil {\n\t\t\tkey1 := rows[i].TsStats.Apex + rows[i].TsStats.Leaf\n\t\t\tkey2 := rows[j].TsStats.Apex + rows[j].TsStats.Leaf\n\t\t\treturn key1 < key2\n\t\t}\n\t\treturn false\n\t})\n\treturn rows\n}\n\n// get the list of objects for which we want to return results\nfunc getResultKeys(\n\treq *pb.StatSummaryRequest,\n\tk8sObjects map[rKey]k8sStat,\n\tmetricResults map[rKey]*pb.BasicStats,\n) []rKey {\n\tvar keys []rKey\n\n\tif req.GetOutbound() == nil || req.GetNone() != nil {\n\t\t// if the request doesn't have outbound filtering, return all rows\n\t\tfor key := range k8sObjects {\n\t\t\tkeys = append(keys, key)\n\t\t}\n\t} else {\n\t\t// if the request does have outbound filtering,\n\t\t// only return rows for which we have stats\n\t\tfor key := range metricResults {\n\t\t\tkeys = append(keys, key)\n\t\t}\n\t}\n\treturn keys\n}\n\nfunc buildRequestLabels(req *pb.StatSummaryRequest) (labels model.LabelSet, labelNames model.LabelNames) {\n\t// labelNames: the group by in the prometheus query\n\t// labels: the labels for the resource we want to query for\n\n\tswitch out := req.Outbound.(type) {\n\tcase *pb.StatSummaryRequest_ToResource:\n\t\tlabelNames = prometheus.GroupByLabelNames(req.Selector.Resource)\n\n\t\tlabels = labels.Merge(prometheus.DstQueryLabels(out.ToResource))\n\t\tlabels = labels.Merge(prometheus.QueryLabels(req.Selector.Resource))\n\t\tlabels = labels.Merge(promDirectionLabels(\"outbound\"))\n\n\tcase *pb.StatSummaryRequest_FromResource:\n\t\tlabelNames = prometheus.DstGroupByLabelNames(req.Selector.Resource)\n\n\t\tlabels = labels.Merge(prometheus.QueryLabels(out.FromResource))\n\t\tlabels = labels.Merge(prometheus.DstQueryLabels(req.Selector.Resource))\n\t\tlabels = labels.Merge(promDirectionLabels(\"outbound\"))\n\n\tdefault:\n\t\tlabelNames = prometheus.GroupByLabelNames(req.Selector.Resource)\n\n\t\tlabels = labels.Merge(prometheus.QueryLabels(req.Selector.Resource))\n\t\tlabels = labels.Merge(promDirectionLabels(\"inbound\"))\n\t}\n\n\treturn\n}\n\nfunc buildServiceRequestLabels(req *pb.StatSummaryRequest) (labels model.LabelSet, labelNames model.LabelNames) {\n\t// Service Request labels are always direction=\"outbound\". If the --from or --to flags were used,\n\t// we merge an additional ToResource or FromResource label. Service metrics results are\n\t// always grouped by dst_service, and dst_namespace (to avoid conflicts) .\n\tlabels = model.LabelSet{\n\t\t\"direction\": model.LabelValue(\"outbound\"),\n\t}\n\n\tswitch out := req.Outbound.(type) {\n\tcase *pb.StatSummaryRequest_ToResource:\n\t\t// if --to flag is passed, Calculate traffic sent to the service\n\t\t// with additional filtering narrowing down to the workload\n\t\t// it is sent to.\n\t\tlabels = labels.Merge(prometheus.DstQueryLabels(out.ToResource))\n\n\tcase *pb.StatSummaryRequest_FromResource:\n\t\t// if --from flag is passed, FromResource is never a service here\n\t\tlabels = labels.Merge(prometheus.QueryLabels(out.FromResource))\n\n\tdefault:\n\t\t// no extra labels needed\n\t}\n\n\tgroupBy := model.LabelNames{model.LabelName(\"dst_namespace\"), model.LabelName(\"dst_service\")}\n\n\treturn labels, groupBy\n}\n\nfunc buildTCPStatsRequestLabels(req *pb.StatSummaryRequest, reqLabels model.LabelSet) string {\n\tswitch req.Outbound.(type) {\n\tcase *pb.StatSummaryRequest_ToResource, *pb.StatSummaryRequest_FromResource:\n\t\t// If TCP stats are queried from a resource to another one (i.e outbound -- from/to), then append peer='dst'\n\t\treqLabels = reqLabels.Merge(promPeerLabel(\"dst\"))\n\n\tdefault:\n\t\t// If TCP stats are not queried from a specific resource (i.e inbound -- no to/from), then append peer='src'\n\t\treqLabels = reqLabels.Merge(promPeerLabel(\"src\"))\n\t}\n\treturn reqLabels.String()\n}\n\nfunc (s *grpcServer) getStatMetrics(ctx context.Context, req *pb.StatSummaryRequest, timeWindow string) (map[rKey]*pb.BasicStats, map[rKey]*pb.TcpStats, error) {\n\treqLabels, groupBy := buildRequestLabels(req)\n\tpromQueries := map[promType]string{\n\t\tpromRequests: fmt.Sprintf(reqQuery, reqLabels.String(), timeWindow, groupBy.String()),\n\t}\n\n\tif req.TcpStats {\n\t\tpromQueries[promTCPConnections] = fmt.Sprintf(tcpConnectionsQuery, reqLabels.String(), groupBy.String())\n\t\t// For TCP read/write bytes total we add an additional 'peer' label with a value of either 'src' or 'dst'\n\t\ttcpLabels := buildTCPStatsRequestLabels(req, reqLabels)\n\t\tpromQueries[promTCPReadBytes] = fmt.Sprintf(tcpReadBytesQuery, tcpLabels, timeWindow, groupBy.String())\n\t\tpromQueries[promTCPWriteBytes] = fmt.Sprintf(tcpWriteBytesQuery, tcpLabels, timeWindow, groupBy.String())\n\t}\n\n\tquantileQueries := generateQuantileQueries(latencyQuantileQuery, reqLabels.String(), timeWindow, groupBy.String())\n\tresults, err := s.getPrometheusMetrics(ctx, promQueries, quantileQueries)\n\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tbasicStats, tcpStats, _ := processPrometheusMetrics(req, results, groupBy)\n\treturn basicStats, tcpStats, nil\n}\n\nfunc (s *grpcServer) getServiceMetrics(ctx context.Context, req *pb.StatSummaryRequest, timeWindow string) (map[dstKey]*pb.BasicStats, map[dstKey]*pb.TcpStats, error) {\n\tdstBasicStats := make(map[dstKey]*pb.BasicStats)\n\tdstTCPStats := make(map[dstKey]*pb.TcpStats)\n\tlabels, groupBy := buildServiceRequestLabels(req)\n\n\tservice := req.GetSelector().GetResource().GetName()\n\tnamespace := req.GetSelector().GetResource().GetNamespace()\n\n\tif service == \"\" {\n\t\tservice = regexAny\n\t}\n\tauthority := fmt.Sprintf(\"%s.%s.svc.%s\", service, namespace, s.clusterDomain)\n\n\treqLabels := generateLabelStringWithRegex(labels, string(prometheus.AuthorityLabel), authority)\n\n\tpromQueries := map[promType]string{\n\t\tpromRequests: fmt.Sprintf(reqQuery, reqLabels, timeWindow, groupBy.String()),\n\t}\n\n\tif req.TcpStats {\n\t\t// Service stats always need to have `peer=dst`, cuz there is no `src` with `authority` label\n\t\ttcpLabels := labels.Merge(promPeerLabel(\"dst\"))\n\t\ttcpLabelString := generateLabelStringWithRegex(tcpLabels, string(prometheus.AuthorityLabel), authority)\n\t\tpromQueries[promTCPConnections] = fmt.Sprintf(tcpConnectionsQuery, tcpLabelString, groupBy.String())\n\t\tpromQueries[promTCPReadBytes] = fmt.Sprintf(tcpReadBytesQuery, tcpLabelString, timeWindow, groupBy.String())\n\t\tpromQueries[promTCPWriteBytes] = fmt.Sprintf(tcpWriteBytesQuery, tcpLabelString, timeWindow, groupBy.String())\n\t}\n\n\tquantileQueries := generateQuantileQueries(latencyQuantileQuery, reqLabels, timeWindow, groupBy.String())\n\tresults, err := s.getPrometheusMetrics(ctx, promQueries, quantileQueries)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tbasicStats, tcpStats, _ := processPrometheusMetrics(req, results, groupBy)\n\n\tfor rKey, basicStatsVal := range basicStats {\n\n\t\t// Use the returned `dst_service` in the `all` svc case\n\t\tsvcName := service\n\t\tif svcName == regexAny {\n\t\t\tsvcName = rKey.Name\n\t\t}\n\n\t\tdstBasicStats[dstKey{\n\t\t\tNamespace: rKey.Namespace,\n\t\t\tService:   svcName,\n\t\t\tDst:       rKey.Name,\n\t\t}] = basicStatsVal\n\t}\n\n\tfor rKey, tcpStatsVal := range tcpStats {\n\n\t\t// Use the returned `dst_service` in the `all` svc case\n\t\tsvcName := service\n\t\tif svcName == regexAny {\n\t\t\tsvcName = rKey.Name\n\t\t}\n\n\t\tdstTCPStats[dstKey{\n\t\t\tNamespace: rKey.Namespace,\n\t\t\tService:   svcName,\n\t\t\tDst:       rKey.Name,\n\t\t}] = tcpStatsVal\n\t}\n\n\treturn dstBasicStats, dstTCPStats, nil\n}\n\nfunc processPrometheusMetrics(req *pb.StatSummaryRequest, results []promResult, groupBy model.LabelNames) (map[rKey]*pb.BasicStats, map[rKey]*pb.TcpStats, map[rKey]*pb.ServerStats) {\n\tbasicStats := make(map[rKey]*pb.BasicStats)\n\ttcpStats := make(map[rKey]*pb.TcpStats)\n\tauthzStats := make(map[rKey]*pb.ServerStats)\n\n\tfor _, result := range results {\n\t\tfor _, sample := range result.vec {\n\t\t\tresource := metricToKey(req, sample.Metric, groupBy)\n\n\t\t\taddBasicStats := func() {\n\t\t\t\tif basicStats[resource] == nil {\n\t\t\t\t\tbasicStats[resource] = &pb.BasicStats{}\n\t\t\t\t}\n\t\t\t}\n\t\t\taddTCPStats := func() {\n\t\t\t\tif tcpStats[resource] == nil {\n\t\t\t\t\ttcpStats[resource] = &pb.TcpStats{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif authzStats[resource] == nil {\n\t\t\t\tsrv := pb.Resource{\n\t\t\t\t\tType: string(sample.Metric[prometheus.ServerKindLabel]),\n\t\t\t\t\tName: string(sample.Metric[prometheus.ServerNameLabel]),\n\t\t\t\t}\n\t\t\t\troute := pb.Resource{\n\t\t\t\t\tType: string(sample.Metric[prometheus.RouteKindLabel]),\n\t\t\t\t\tName: string(sample.Metric[prometheus.RouteNameLabel]),\n\t\t\t\t}\n\t\t\t\tauthz := pb.Resource{\n\t\t\t\t\tType: string(sample.Metric[prometheus.AuthorizationKindLabel]),\n\t\t\t\t\tName: string(sample.Metric[prometheus.AuthorizationNameLabel]),\n\t\t\t\t}\n\t\t\t\tauthzStats[resource] = &pb.ServerStats{\n\t\t\t\t\tSrv:   &srv,\n\t\t\t\t\tRoute: &route,\n\t\t\t\t\tAuthz: &authz,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvalue := extractSampleValue(sample)\n\n\t\t\tswitch result.prom {\n\t\t\tcase promRequests:\n\t\t\t\taddBasicStats()\n\t\t\t\tswitch string(sample.Metric[model.LabelName(\"classification\")]) {\n\t\t\t\tcase success:\n\t\t\t\t\tbasicStats[resource].SuccessCount += value\n\t\t\t\tcase failure:\n\t\t\t\t\tbasicStats[resource].FailureCount += value\n\t\t\t\t}\n\t\t\tcase promLatencyP50:\n\t\t\t\taddBasicStats()\n\t\t\t\tbasicStats[resource].LatencyMsP50 = value\n\t\t\tcase promLatencyP95:\n\t\t\t\taddBasicStats()\n\t\t\t\tbasicStats[resource].LatencyMsP95 = value\n\t\t\tcase promLatencyP99:\n\t\t\t\taddBasicStats()\n\t\t\t\tbasicStats[resource].LatencyMsP99 = value\n\t\t\tcase promTCPConnections:\n\t\t\t\taddTCPStats()\n\t\t\t\ttcpStats[resource].OpenConnections = value\n\t\t\tcase promTCPReadBytes:\n\t\t\t\taddTCPStats()\n\t\t\t\ttcpStats[resource].ReadBytesTotal = value\n\t\t\tcase promTCPWriteBytes:\n\t\t\t\taddTCPStats()\n\t\t\t\ttcpStats[resource].WriteBytesTotal = value\n\t\t\tcase promAllowedRequests:\n\t\t\t\tauthzStats[resource].AllowedCount = value\n\t\t\tcase promDeniedRequests:\n\t\t\t\tauthzStats[resource].DeniedCount = value\n\t\t\t}\n\t\t}\n\t}\n\n\treturn basicStats, tcpStats, authzStats\n}\n\nfunc metricToKey(req *pb.StatSummaryRequest, metric model.Metric, groupBy model.LabelNames) rKey {\n\t// this key is used to match the metric stats we queried from prometheus\n\t// with the k8s object stats we queried from k8s\n\t// ASSUMPTION: this code assumes that groupBy is always ordered (..., namespace, name)\n\tkey := rKey{\n\t\tType: req.GetSelector().GetResource().GetType(),\n\t\tName: string(metric[groupBy[len(groupBy)-1]]),\n\t}\n\n\tif len(groupBy) >= 2 {\n\t\tkey.Namespace = string(metric[groupBy[len(groupBy)-2]])\n\t}\n\n\treturn key\n}\n\nfunc (s *grpcServer) getPodStats(obj runtime.Object) (*podStats, error) {\n\tpods, err := s.k8sAPI.GetPodsFor(obj, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpodErrors := make(map[string]*pb.PodErrors)\n\tmeshCount := &podStats{}\n\n\tif pod, ok := obj.(*corev1.Pod); ok {\n\t\tmeshCount.status = k8s.GetPodStatus(*pod)\n\t}\n\n\tfor _, pod := range pods {\n\t\tif pod.Status.Phase == corev1.PodFailed {\n\t\t\tmeshCount.failed++\n\t\t} else {\n\t\t\tmeshCount.total++\n\t\t\tif k8s.IsMeshed(pod, s.controllerNamespace) {\n\t\t\t\tmeshCount.inMesh++\n\t\t\t}\n\t\t}\n\n\t\terrors := checkContainerErrors(pod.Status.ContainerStatuses)\n\t\terrors = append(errors, checkContainerErrors(pod.Status.InitContainerStatuses)...)\n\n\t\tif len(errors) > 0 {\n\t\t\tpodErrors[pod.Name] = &pb.PodErrors{Errors: errors}\n\t\t}\n\t}\n\tmeshCount.errors = podErrors\n\treturn meshCount, nil\n}\n\nfunc toPodError(container, image, reason, message string) *pb.PodErrors_PodError {\n\treturn &pb.PodErrors_PodError{\n\t\tError: &pb.PodErrors_PodError_Container{\n\t\t\tContainer: &pb.PodErrors_PodError_ContainerError{\n\t\t\t\tMessage:   message,\n\t\t\t\tContainer: container,\n\t\t\t\tImage:     image,\n\t\t\t\tReason:    reason,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc checkContainerErrors(containerStatuses []corev1.ContainerStatus) []*pb.PodErrors_PodError {\n\terrors := []*pb.PodErrors_PodError{}\n\tfor _, st := range containerStatuses {\n\t\tif !st.Ready {\n\t\t\tif st.State.Waiting != nil {\n\t\t\t\terrors = append(errors, toPodError(st.Name, st.Image, st.State.Waiting.Reason, st.State.Waiting.Message))\n\t\t\t}\n\n\t\t\tif st.State.Terminated != nil && (st.State.Terminated.ExitCode != 0 || st.State.Terminated.Signal != 0) {\n\t\t\t\terrors = append(errors, toPodError(st.Name, st.Image, st.State.Terminated.Reason, st.State.Terminated.Message))\n\t\t\t}\n\n\t\t\tif st.LastTerminationState.Waiting != nil {\n\t\t\t\terrors = append(errors, toPodError(st.Name, st.Image, st.LastTerminationState.Waiting.Reason, st.LastTerminationState.Waiting.Message))\n\t\t\t}\n\n\t\t\tif st.LastTerminationState.Terminated != nil {\n\t\t\t\terrors = append(errors, toPodError(st.Name, st.Image, st.LastTerminationState.Terminated.Reason, st.LastTerminationState.Terminated.Message))\n\t\t\t}\n\t\t}\n\t}\n\treturn errors\n}\n\nfunc getLabelSelector(req *pb.StatSummaryRequest) (labels.Selector, error) {\n\tlabelSelector := labels.Everything()\n\tif s := req.GetSelector().GetLabelSelector(); s != \"\" {\n\t\tvar err error\n\t\tlabelSelector, err = labels.Parse(s)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid label selector %q: %w\", s, err)\n\t\t}\n\t}\n\treturn labelSelector, nil\n}\n\nfunc dstFromAuthority(authority string) string {\n\t// name.namespace.svc.suffix\n\tlabels := strings.Split(authority, \".\")\n\tif len(labels) >= 3 && labels[2] == \"svc\" {\n\t\t// name\n\t\treturn labels[0]\n\t}\n\treturn authority\n}\n"
  },
  {
    "path": "viz/metrics-api/stat_summary_test.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tpkgK8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/prometheus\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/prometheus/common/model\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\ntype statSumExpected struct {\n\texpectedStatRPC\n\treq              *pb.StatSummaryRequest  // the request we would like to test\n\texpectedResponse *pb.StatSummaryResponse // the stat response we expect\n}\n\nfunc prometheusMetric(resName string, resType string) model.Vector {\n\treturn model.Vector{\n\t\tgenPromSample(resName, resType, false),\n\t}\n}\n\nfunc genPromSample(resName string, resType string, isDst bool) *model.Sample {\n\tlabelName := model.LabelName(resType)\n\tnamespaceLabel := model.LabelName(\"namespace\")\n\n\tif isDst {\n\t\tlabelName = \"dst_\" + labelName\n\t\tnamespaceLabel = \"dst_\" + namespaceLabel\n\t}\n\n\treturn &model.Sample{\n\t\tMetric: model.Metric{\n\t\t\tlabelName:        model.LabelValue(resName),\n\t\t\tnamespaceLabel:   model.LabelValue(\"emojivoto\"),\n\t\t\t\"classification\": model.LabelValue(\"success\"),\n\t\t\t\"tls\":            model.LabelValue(\"true\"),\n\t\t},\n\t\tValue:     123,\n\t\tTimestamp: 456,\n\t}\n}\n\nfunc genEmptyResponse() *pb.StatSummaryResponse {\n\treturn &pb.StatSummaryResponse{\n\t\tResponse: &pb.StatSummaryResponse_Ok_{ // https://github.com/golang/protobuf/issues/205\n\t\t\tOk: &pb.StatSummaryResponse_Ok{\n\t\t\t\tStatTables: []*pb.StatTable{\n\t\t\t\t\t{\n\t\t\t\t\t\tTable: &pb.StatTable_PodGroup_{\n\t\t\t\t\t\t\tPodGroup: &pb.StatTable_PodGroup{},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc testStatSummary(t *testing.T, expectations []statSumExpected) {\n\tfor _, exp := range expectations {\n\t\tmockProm, fakeGrpcServer, err := newMockGrpcServer(exp.expectedStatRPC)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error creating mock grpc server: %s\", err)\n\t\t}\n\n\t\trsp, err := fakeGrpcServer.StatSummary(context.TODO(), exp.req)\n\t\tif !errors.Is(err, exp.err) {\n\t\t\tt.Fatalf(\"Expected error: %s, Got: %s\", exp.err, err)\n\t\t}\n\n\t\terr = exp.verifyPromQueries(mockProm)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trspStatTables := rsp.GetOk().StatTables\n\t\tsort.Sort(byStatResult(rspStatTables))\n\n\t\tif len(rspStatTables) != len(exp.expectedResponse.GetOk().StatTables) {\n\t\t\tt.Fatalf(\n\t\t\t\t\"Expected [%d] stat tables, got [%d].\\nExpected:\\n%s\\nGot:\\n%s\",\n\t\t\t\tlen(exp.expectedResponse.GetOk().StatTables),\n\t\t\t\tlen(rspStatTables),\n\t\t\t\texp.expectedResponse.GetOk().StatTables,\n\t\t\t\trspStatTables,\n\t\t\t)\n\t\t}\n\n\t\tstatOkRsp := &pb.StatSummaryResponse_Ok{\n\t\t\tStatTables: rspStatTables,\n\t\t}\n\n\t\tfor i, st := range rspStatTables {\n\t\t\texpected := exp.expectedResponse.GetOk().StatTables[i]\n\t\t\tif !proto.Equal(st, expected) {\n\t\t\t\tt.Fatalf(\"Expected: %+v\\n Got: %+v\", expected, st)\n\t\t\t}\n\t\t}\n\n\t\tif !proto.Equal(exp.expectedResponse.GetOk(), statOkRsp) {\n\t\t\tt.Fatalf(\"Expected: %+v\\n Got: %+v\", &exp.expectedResponse, rsp)\n\t\t}\n\t}\n}\n\ntype byStatResult []*pb.StatTable\n\nfunc (s byStatResult) Len() int {\n\treturn len(s)\n}\n\nfunc (s byStatResult) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc (s byStatResult) Less(i, j int) bool {\n\tif len(s[i].GetPodGroup().Rows) == 0 {\n\t\treturn true\n\t}\n\tif len(s[j].GetPodGroup().Rows) == 0 {\n\t\treturn false\n\t}\n\n\treturn s[i].GetPodGroup().Rows[0].Resource.Type < s[j].GetPodGroup().Rows[0].Resource.Type\n}\n\nfunc TestStatSummary(t *testing.T) {\n\tt.Run(\"Successfully performs a query based on resource type Pod\", func(t *testing.T) {\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: prometheusMetric(\"emoji\", \"pod\"),\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenStatSummaryResponse(\"emoji\", pkgK8s.Pod, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\tStatus:      \"Running\",\n\t\t\t\t\tMeshedPods:  1,\n\t\t\t\t\tRunningPods: 1,\n\t\t\t\t\tFailedPods:  0,\n\t\t\t\t}, true, false),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Successfully performs a query based on resource type Pod when pod Reason is filled\", func(t *testing.T) {\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Pending\n  reason: podReason\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: prometheusMetric(\"emoji\", \"pod\"),\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenStatSummaryResponse(\"emoji\", pkgK8s.Pod, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\tStatus:      \"podReason\",\n\t\t\t\t\tMeshedPods:  1,\n\t\t\t\t\tRunningPods: 1,\n\t\t\t\t\tFailedPods:  0,\n\t\t\t\t}, true, false),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Successfully performs a query based on resource type Pod when pod init container is initializing\", func(t *testing.T) {\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nspec:\n    initContainers:\n    - name: foo\nstatus:\n  phase: Pending\n  initContainerStatuses:\n  - name: foo\n    state:\n      waiting:\n        reason: PodInitializing\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: prometheusMetric(\"emoji\", \"pod\"),\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenStatSummaryResponse(\"emoji\", pkgK8s.Pod, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\tStatus:      \"Init:0/1\",\n\t\t\t\t\tMeshedPods:  1,\n\t\t\t\t\tRunningPods: 1,\n\t\t\t\t\tFailedPods:  0,\n\t\t\t\t\tErrors: map[string]*pb.PodErrors{\n\t\t\t\t\t\t\"emoji\": {\n\t\t\t\t\t\t\tErrors: []*pb.PodErrors_PodError{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tError: &pb.PodErrors_PodError_Container{\n\t\t\t\t\t\t\t\t\t\tContainer: &pb.PodErrors_PodError_ContainerError{\n\t\t\t\t\t\t\t\t\t\t\tContainer: \"foo\",\n\t\t\t\t\t\t\t\t\t\t\tReason:    \"PodInitializing\",\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}, true, false),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Successfully performs a query based on resource type Deployment\", func(t *testing.T) {\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  uid: a1b2c3\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc\n  strategy: {}\n  template:\n    spec:\n      containers:\n      - image: buoyantio/emojivoto-emoji-svc:v10\n`, `\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  uid: a1b2c3d4\n  annotations:\n    deployment.kubernetes.io/revision: \"2\"\n  name: emojivoto-meshed_2\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    pod-template-hash: 3c2b1a\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: a1b2c3\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc\n      pod-template-hash: 3c2b1a\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\n    pod-template-hash: 3c2b1a\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: a1b2c3d4\nstatus:\n  phase: Running\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-not-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    pod-template-hash: 3c2b1a\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: a1b2c3d4\nstatus:\n  phase: Running\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed-not-running\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\n    pod-template-hash: 3c2b1a\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: a1b2c3d4\nstatus:\n  phase: Completed\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: prometheusMetric(\"emoji\", \"deployment\"),\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Deployment,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenStatSummaryResponse(\"emoji\", pkgK8s.Deployment, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\tMeshedPods:  1,\n\t\t\t\t\tRunningPods: 2,\n\t\t\t\t\tFailedPods:  0,\n\t\t\t\t}, true, false),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Successfully performs a query based on resource type DaemonSet\", func(t *testing.T) {\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: emoji\n  namespace: emojivoto\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc\n  strategy: {}\n  template:\n    spec:\n      containers:\n      - image: buoyantio/emojivoto-emoji-svc:v10\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-not-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\nstatus:\n  phase: Running\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed-not-running\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Completed\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: prometheusMetric(\"emoji\", \"daemonset\"),\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.DaemonSet,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenStatSummaryResponse(\"emoji\", pkgK8s.DaemonSet, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\tMeshedPods:  1,\n\t\t\t\t\tRunningPods: 2,\n\t\t\t\t\tFailedPods:  0,\n\t\t\t\t}, true, false),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Successfully performs a query based on resource type Job\", func(t *testing.T) {\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: emoji\n  namespace: emojivoto\nspec:\n  selector:\n    matchLabels:\n      app: emoji-job\n  strategy: {}\n  template:\n    spec:\n      containers:\n      - image: buoyantio/emojivoto-emoji-svc:v10\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-job\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-not-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-job\nstatus:\n  phase: Running\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed-not-running\n  namespace: emojivoto\n  labels:\n    app: emoji-job\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Completed\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: prometheusMetric(\"emoji\", \"k8s_job\"),\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Job,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenStatSummaryResponse(\"emoji\", pkgK8s.Job, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\tMeshedPods:  1,\n\t\t\t\t\tRunningPods: 2,\n\t\t\t\t\tFailedPods:  0,\n\t\t\t\t}, true, false),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Successfully performs a query based on resource type StatefulSet\", func(t *testing.T) {\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: redis\n  namespace: emojivoto\n  labels:\n    app: redis\n    linkerd.io/control-plane-ns: linkerd\nspec:\n  replicas: 3\n  serviceName: redis\n  selector:\n    matchLabels:\n      app: redis\n  template:\n    metadata:\n      labels:\n        app: redis\n    spec:\n      containers:\n      - image: redis\n        volumeMounts:\n        - name: data\n          mountPath: /var/lib/redis\n  volumeClaimTemplates:\n  - metadata:\n      name: data\n    spec:\n      accessModes: [\"ReadWriteOnce\"]\n      resources:\n        requests:\n          storage: 10Gi\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: redis-0\n  namespace: emojivoto\n  labels:\n    app: redis\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: redis-1\n  namespace: emojivoto\n  labels:\n    app: redis\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: redis-2\n  namespace: emojivoto\n  labels:\n    app: redis\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: prometheusMetric(\"redis\", \"statefulset\"),\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.StatefulSet,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenStatSummaryResponse(\"redis\", pkgK8s.StatefulSet, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\tMeshedPods:  3,\n\t\t\t\t\tRunningPods: 3,\n\t\t\t\t\tFailedPods:  0,\n\t\t\t\t}, true, false),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Queries prometheus for TCP stats when requested\", func(t *testing.T) {\n\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-1\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: prometheusMetric(\"emojivoto-1\", \"pod\"),\n\t\t\t\t\texpectedPrometheusQueries: []string{\n\t\t\t\t\t\t`histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction=\"inbound\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (le, namespace, pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction=\"inbound\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (le, namespace, pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction=\"inbound\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (le, namespace, pod))`,\n\t\t\t\t\t\t`sum(increase(response_total{direction=\"inbound\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (namespace, pod, classification, tls)`,\n\t\t\t\t\t\t`sum(tcp_open_connections{direction=\"inbound\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}) by (namespace, pod)`,\n\t\t\t\t\t\t`sum(increase(tcp_read_bytes_total{direction=\"inbound\", namespace=\"emojivoto\", peer=\"src\", pod=\"emojivoto-1\"}[1m])) by (namespace, pod)`,\n\t\t\t\t\t\t`sum(increase(tcp_write_bytes_total{direction=\"inbound\", namespace=\"emojivoto\", peer=\"src\", pod=\"emojivoto-1\"}[1m])) by (namespace, pod)`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tName:      \"emojivoto-1\",\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t\tTcpStats:   true,\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenStatSummaryResponse(\"emojivoto-1\", pkgK8s.Pod, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\tStatus:      \"Running\",\n\t\t\t\t\tMeshedPods:  1,\n\t\t\t\t\tRunningPods: 1,\n\t\t\t\t\tFailedPods:  0,\n\t\t\t\t}, true, true),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Queries prometheus for outbound TCP stats if --to resource is specified\", func(t *testing.T) {\n\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-1\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: prometheusMetric(\"emojivoto-1\", \"pod\"),\n\t\t\t\t\texpectedPrometheusQueries: []string{\n\t\t\t\t\t\t`histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-2\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (le, namespace, pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-2\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (le, namespace, pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-2\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (le, namespace, pod))`,\n\t\t\t\t\t\t`sum(increase(response_total{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-2\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (namespace, pod, classification, tls)`,\n\t\t\t\t\t\t`sum(tcp_open_connections{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-2\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}) by (namespace, pod)`,\n\t\t\t\t\t\t`sum(increase(tcp_read_bytes_total{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-2\", namespace=\"emojivoto\", peer=\"dst\", pod=\"emojivoto-1\"}[1m])) by (namespace, pod)`,\n\t\t\t\t\t\t`sum(increase(tcp_write_bytes_total{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-2\", namespace=\"emojivoto\", peer=\"dst\", pod=\"emojivoto-1\"}[1m])) by (namespace, pod)`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tName:      \"emojivoto-1\",\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t\tTcpStats:   true,\n\t\t\t\t\tOutbound: &pb.StatSummaryRequest_ToResource{\n\t\t\t\t\t\tToResource: &pb.Resource{\n\t\t\t\t\t\t\tName:      \"emojivoto-2\",\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenStatSummaryResponse(\"emojivoto-1\", pkgK8s.Pod, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\tStatus:      \"Running\",\n\t\t\t\t\tMeshedPods:  1,\n\t\t\t\t\tRunningPods: 1,\n\t\t\t\t\tFailedPods:  0,\n\t\t\t\t}, true, true),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Queries prometheus for a specific resource if name is specified\", func(t *testing.T) {\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-1\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: prometheusMetric(\"emojivoto-1\", \"pod\"),\n\t\t\t\t\texpectedPrometheusQueries: []string{\n\t\t\t\t\t\t`histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction=\"inbound\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (le, namespace, pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction=\"inbound\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (le, namespace, pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction=\"inbound\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (le, namespace, pod))`,\n\t\t\t\t\t\t`sum(increase(response_total{direction=\"inbound\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (namespace, pod, classification, tls)`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tName:      \"emojivoto-1\",\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenStatSummaryResponse(\"emojivoto-1\", pkgK8s.Pod, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\tStatus:      \"Running\",\n\t\t\t\t\tMeshedPods:  1,\n\t\t\t\t\tRunningPods: 1,\n\t\t\t\t\tFailedPods:  0,\n\t\t\t\t}, true, false),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Queries prometheus for outbound metrics if from resource is specified, ignores resource name\", func(t *testing.T) {\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-1\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: prometheusMetric(\"emojivoto-2\", \"pod\"),\n\t\t\t\t\texpectedPrometheusQueries: []string{\n\t\t\t\t\t\t`histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-1\", namespace=\"emojivoto\", pod=\"emojivoto-2\"}[1m])) by (le, dst_namespace, dst_pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-1\", namespace=\"emojivoto\", pod=\"emojivoto-2\"}[1m])) by (le, dst_namespace, dst_pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-1\", namespace=\"emojivoto\", pod=\"emojivoto-2\"}[1m])) by (le, dst_namespace, dst_pod))`,\n\t\t\t\t\t\t`sum(increase(response_total{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-1\", namespace=\"emojivoto\", pod=\"emojivoto-2\"}[1m])) by (dst_namespace, dst_pod, classification, tls)`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tName:      \"emojivoto-1\",\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t\tOutbound: &pb.StatSummaryRequest_FromResource{\n\t\t\t\t\t\tFromResource: &pb.Resource{\n\t\t\t\t\t\t\tName:      \"emojivoto-2\",\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedResponse: genEmptyResponse(),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Queries prometheus for outbound metrics if --to resource is specified\", func(t *testing.T) {\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-1\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: model.Vector{\n\t\t\t\t\t\tgenPromSample(\"emojivoto-1\", \"pod\", false),\n\t\t\t\t\t},\n\t\t\t\t\texpectedPrometheusQueries: []string{\n\t\t\t\t\t\t`histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-2\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (le, namespace, pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-2\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (le, namespace, pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-2\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (le, namespace, pod))`,\n\t\t\t\t\t\t`sum(increase(response_total{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-2\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (namespace, pod, classification, tls)`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tName:      \"emojivoto-1\",\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t\tOutbound: &pb.StatSummaryRequest_ToResource{\n\t\t\t\t\t\tToResource: &pb.Resource{\n\t\t\t\t\t\t\tName:      \"emojivoto-2\",\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenStatSummaryResponse(\"emojivoto-1\", pkgK8s.Pod, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\tStatus:      \"Running\",\n\t\t\t\t\tMeshedPods:  1,\n\t\t\t\t\tRunningPods: 1,\n\t\t\t\t\tFailedPods:  0,\n\t\t\t\t}, true, false),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Queries prometheus for outbound metrics if --to resource is specified and --to-namespace is different from the resource namespace\", func(t *testing.T) {\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-1\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: model.Vector{\n\t\t\t\t\t\tgenPromSample(\"emojivoto-1\", \"pod\", false),\n\t\t\t\t\t},\n\t\t\t\t\texpectedPrometheusQueries: []string{\n\t\t\t\t\t\t`histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction=\"outbound\", dst_namespace=\"totallydifferent\", dst_pod=\"emojivoto-2\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (le, namespace, pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction=\"outbound\", dst_namespace=\"totallydifferent\", dst_pod=\"emojivoto-2\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (le, namespace, pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction=\"outbound\", dst_namespace=\"totallydifferent\", dst_pod=\"emojivoto-2\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (le, namespace, pod))`,\n\t\t\t\t\t\t`sum(increase(response_total{direction=\"outbound\", dst_namespace=\"totallydifferent\", dst_pod=\"emojivoto-2\", namespace=\"emojivoto\", pod=\"emojivoto-1\"}[1m])) by (namespace, pod, classification, tls)`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tName:      \"emojivoto-1\",\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t\tOutbound: &pb.StatSummaryRequest_ToResource{\n\t\t\t\t\t\tToResource: &pb.Resource{\n\t\t\t\t\t\t\tName:      \"emojivoto-2\",\n\t\t\t\t\t\t\tNamespace: \"totallydifferent\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenStatSummaryResponse(\"emojivoto-1\", pkgK8s.Pod, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\tStatus:      \"Running\",\n\t\t\t\t\tMeshedPods:  1,\n\t\t\t\t\tRunningPods: 1,\n\t\t\t\t\tFailedPods:  0,\n\t\t\t\t}, true, false),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Queries prometheus for outbound metrics if --from resource is specified\", func(t *testing.T) {\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-1\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-2\n  namespace: totallydifferent\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: model.Vector{\n\t\t\t\t\t\tgenPromSample(\"emojivoto-1\", \"pod\", true),\n\t\t\t\t\t},\n\t\t\t\t\texpectedPrometheusQueries: []string{\n\t\t\t\t\t\t`histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction=\"outbound\", pod=\"emojivoto-2\"}[1m])) by (le, dst_namespace, dst_pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction=\"outbound\", pod=\"emojivoto-2\"}[1m])) by (le, dst_namespace, dst_pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction=\"outbound\", pod=\"emojivoto-2\"}[1m])) by (le, dst_namespace, dst_pod))`,\n\t\t\t\t\t\t`sum(increase(response_total{direction=\"outbound\", pod=\"emojivoto-2\"}[1m])) by (dst_namespace, dst_pod, classification, tls)`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tName:      \"\",\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t\tOutbound: &pb.StatSummaryRequest_FromResource{\n\t\t\t\t\t\tFromResource: &pb.Resource{\n\t\t\t\t\t\t\tName:      \"emojivoto-2\",\n\t\t\t\t\t\t\tNamespace: \"\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenStatSummaryResponse(\"emojivoto-1\", pkgK8s.Pod, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\tStatus:      \"Running\",\n\t\t\t\t\tMeshedPods:  1,\n\t\t\t\t\tRunningPods: 1,\n\t\t\t\t\tFailedPods:  0,\n\t\t\t\t}, true, false),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Queries prometheus for outbound metrics if --from resource is specified and --from-namespace is different from the resource namespace\", func(t *testing.T) {\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-1\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-2\n  namespace: totallydifferent\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: model.Vector{\n\t\t\t\t\t\tgenPromSample(\"emojivoto-1\", \"pod\", true),\n\t\t\t\t\t},\n\t\t\t\t\texpectedPrometheusQueries: []string{\n\t\t\t\t\t\t`histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-1\", namespace=\"totallydifferent\", pod=\"emojivoto-2\"}[1m])) by (le, dst_namespace, dst_pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-1\", namespace=\"totallydifferent\", pod=\"emojivoto-2\"}[1m])) by (le, dst_namespace, dst_pod))`,\n\t\t\t\t\t\t`histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-1\", namespace=\"totallydifferent\", pod=\"emojivoto-2\"}[1m])) by (le, dst_namespace, dst_pod))`,\n\t\t\t\t\t\t`sum(increase(response_total{direction=\"outbound\", dst_namespace=\"emojivoto\", dst_pod=\"emojivoto-1\", namespace=\"totallydifferent\", pod=\"emojivoto-2\"}[1m])) by (dst_namespace, dst_pod, classification, tls)`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tName:      \"emojivoto-1\",\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t\tOutbound: &pb.StatSummaryRequest_FromResource{\n\t\t\t\t\t\tFromResource: &pb.Resource{\n\t\t\t\t\t\t\tName:      \"emojivoto-2\",\n\t\t\t\t\t\t\tNamespace: \"totallydifferent\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenStatSummaryResponse(\"emojivoto-1\", pkgK8s.Pod, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\tStatus:      \"Running\",\n\t\t\t\t\tMeshedPods:  1,\n\t\t\t\t\tRunningPods: 1,\n\t\t\t\t\tFailedPods:  0,\n\t\t\t\t}, true, false),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Successfully queries for resource type 'all'\", func(t *testing.T) {\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: emoji-deploy\n  namespace: emojivoto\n  uid: a1b2c3\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc\n  strategy: {}\n  template:\n    spec:\n      containers:\n      - image: buoyantio/emojivoto-emoji-svc:v10\n`, `\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  uid: a1b2c3d4\n  annotations:\n    deployment.kubernetes.io/revision: \"2\"\n  name: emojivoto-meshed_2\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    pod-template-hash: 3c2b1a\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: a1b2c3\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc\n      pod-template-hash: 3c2b1a\n`, `\napiVersion: v1\nkind: Service\nmetadata:\n  name: emoji-svc\n  namespace: emojivoto\nspec:\n  clusterIP: None\n  selector:\n    app: emoji-svc\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-pod-1\n  namespace: not-right-emojivoto-namespace\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-pod-2\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\n    pod-template-hash: 3c2b1a\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: a1b2c3d4\nstatus:\n  phase: Running\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse: prometheusMetric(\"emoji-deploy\", \"deployment\"),\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.All,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t},\n\n\t\t\t\texpectedResponse: &pb.StatSummaryResponse{\n\t\t\t\t\tResponse: &pb.StatSummaryResponse_Ok_{ // https://github.com/golang/protobuf/issues/205\n\t\t\t\t\t\tOk: &pb.StatSummaryResponse_Ok{\n\t\t\t\t\t\t\tStatTables: []*pb.StatTable{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tTable: &pb.StatTable_PodGroup_{\n\t\t\t\t\t\t\t\t\t\tPodGroup: &pb.StatTable_PodGroup{\n\t\t\t\t\t\t\t\t\t\t\tRows: []*pb.StatTable_PodGroup_Row{},\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\t{\n\t\t\t\t\t\t\t\t\tTable: &pb.StatTable_PodGroup_{\n\t\t\t\t\t\t\t\t\t\tPodGroup: &pb.StatTable_PodGroup{\n\t\t\t\t\t\t\t\t\t\t\tRows: []*pb.StatTable_PodGroup_Row{},\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\t{\n\t\t\t\t\t\t\t\t\tTable: &pb.StatTable_PodGroup_{\n\t\t\t\t\t\t\t\t\t\tPodGroup: &pb.StatTable_PodGroup{\n\t\t\t\t\t\t\t\t\t\t\tRows: []*pb.StatTable_PodGroup_Row{},\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\t{\n\t\t\t\t\t\t\t\t\tTable: &pb.StatTable_PodGroup_{\n\t\t\t\t\t\t\t\t\t\tPodGroup: &pb.StatTable_PodGroup{\n\t\t\t\t\t\t\t\t\t\t\tRows: []*pb.StatTable_PodGroup_Row{},\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\t{\n\t\t\t\t\t\t\t\t\tTable: &pb.StatTable_PodGroup_{\n\t\t\t\t\t\t\t\t\t\tPodGroup: &pb.StatTable_PodGroup{\n\t\t\t\t\t\t\t\t\t\t\tRows: []*pb.StatTable_PodGroup_Row{},\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\t{\n\t\t\t\t\t\t\t\t\tTable: &pb.StatTable_PodGroup_{\n\t\t\t\t\t\t\t\t\t\tPodGroup: &pb.StatTable_PodGroup{\n\t\t\t\t\t\t\t\t\t\t\tRows: []*pb.StatTable_PodGroup_Row{\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tType:      pkgK8s.Deployment,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tName:      \"emoji-deploy\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tStats: &pb.BasicStats{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tSuccessCount: 123,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tFailureCount: 0,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tLatencyMsP50: 123,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tLatencyMsP95: 123,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tLatencyMsP99: 123,\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tTimeWindow:      \"1m\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tMeshedPodCount:  1,\n\t\t\t\t\t\t\t\t\t\t\t\t\tRunningPodCount: 1,\n\t\t\t\t\t\t\t\t\t\t\t\t},\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\t{\n\t\t\t\t\t\t\t\t\tTable: &pb.StatTable_PodGroup_{\n\t\t\t\t\t\t\t\t\t\tPodGroup: &pb.StatTable_PodGroup{\n\t\t\t\t\t\t\t\t\t\t\tRows: []*pb.StatTable_PodGroup_Row{\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tName:      \"emojivoto-pod-2\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tStatus:          \"Running\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tTimeWindow:      \"1m\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tMeshedPodCount:  1,\n\t\t\t\t\t\t\t\t\t\t\t\t\tRunningPodCount: 1,\n\t\t\t\t\t\t\t\t\t\t\t\t},\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\t{\n\t\t\t\t\t\t\t\t\tTable: &pb.StatTable_PodGroup_{\n\t\t\t\t\t\t\t\t\t\tPodGroup: &pb.StatTable_PodGroup{\n\t\t\t\t\t\t\t\t\t\t\tRows: []*pb.StatTable_PodGroup_Row{\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tType:      pkgK8s.ReplicaSet,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tName:      \"emojivoto-meshed_2\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tTimeWindow:      \"1m\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tMeshedPodCount:  1,\n\t\t\t\t\t\t\t\t\t\t\t\t\tRunningPodCount: 1,\n\t\t\t\t\t\t\t\t\t\t\t\t},\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\t{\n\t\t\t\t\t\t\t\t\tTable: &pb.StatTable_PodGroup_{\n\t\t\t\t\t\t\t\t\t\tPodGroup: &pb.StatTable_PodGroup{\n\t\t\t\t\t\t\t\t\t\t\tRows: []*pb.StatTable_PodGroup_Row{\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tType: pkgK8s.Service,\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tStats: &pb.BasicStats{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tSuccessCount: 123,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tFailureCount: 0,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tLatencyMsP50: 123,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tLatencyMsP95: 123,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tLatencyMsP99: 123,\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\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\n\t\ttestStatSummary(t, expectations)\n\t})\n\n\tt.Run(\"Given an invalid resource type, returns error\", func(t *testing.T) {\n\t\tk8sAPI, err := k8s.NewFakeAPI()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t}\n\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: errors.New(\"rpc error: code = Unimplemented desc = unimplemented resource type: badtype\"),\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tType: \"badtype\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: errors.New(\"rpc error: code = Unimplemented desc = unimplemented resource type: deployments\"),\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tType: \"deployments\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: errors.New(\"rpc error: code = Unimplemented desc = unimplemented resource type: po\"),\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tType: \"po\",\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\n\t\tfor _, exp := range expectations {\n\t\t\tfakeGrpcServer := grpcServer{\n\t\t\t\tprometheusAPI:       &prometheus.MockProm{Res: exp.mockPromResponse},\n\t\t\t\tk8sAPI:              k8sAPI,\n\t\t\t\tcontrollerNamespace: \"linkerd\",\n\t\t\t\tclusterDomain:       \"mycluster.local\",\n\t\t\t\tignoredNamespaces:   []string{},\n\t\t\t}\n\n\t\t\t_, err := fakeGrpcServer.StatSummary(context.TODO(), exp.req)\n\t\t\tif err != nil || exp.err != nil {\n\t\t\t\tif (err == nil && exp.err != nil) ||\n\t\t\t\t\t(err != nil && exp.err == nil) ||\n\t\t\t\t\t(err.Error() != exp.err.Error()) {\n\t\t\t\t\tt.Fatalf(\"Unexpected error (Expected: %s, Got: %s)\", exp.err, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Validates service stat requests\", func(t *testing.T) {\n\t\tk8sAPI, err := k8s.NewFakeAPI()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t}\n\t\tfakeGrpcServer := grpcServer{\n\t\t\tprometheusAPI:       &prometheus.MockProm{Res: model.Vector{}},\n\t\t\tk8sAPI:              k8sAPI,\n\t\t\tcontrollerNamespace: \"linkerd\",\n\t\t\tclusterDomain:       \"mycluster.local\",\n\t\t\tignoredNamespaces:   []string{},\n\t\t}\n\n\t\tinvalidRequests := []statSumExpected{\n\t\t\t{\n\t\t\t\treq: &pb.StatSummaryRequest{},\n\t\t\t},\n\t\t\t{\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tType: pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tOutbound: &pb.StatSummaryRequest_FromResource{\n\t\t\t\t\t\tFromResource: &pb.Resource{\n\t\t\t\t\t\t\tType: pkgK8s.Service,\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\n\t\tfor _, invalid := range invalidRequests {\n\t\t\trsp, err := fakeGrpcServer.StatSummary(context.TODO(), invalid.req)\n\n\t\t\tif err != nil || rsp.GetError() == nil {\n\t\t\t\tt.Fatalf(\"Expected validation error on StatSummaryResponse, got %v, %v\", rsp, err)\n\t\t\t}\n\t\t}\n\n\t\tvalidRequests := []statSumExpected{\n\t\t\t{\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tType: pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tOutbound: &pb.StatSummaryRequest_ToResource{\n\t\t\t\t\t\tToResource: &pb.Resource{\n\t\t\t\t\t\t\tType: pkgK8s.Service,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tType: pkgK8s.Service,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tType: pkgK8s.Service,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tOutbound: &pb.StatSummaryRequest_ToResource{\n\t\t\t\t\t\tToResource: &pb.Resource{\n\t\t\t\t\t\t\tType: pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tType: pkgK8s.Service,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tOutbound: &pb.StatSummaryRequest_FromResource{\n\t\t\t\t\t\tFromResource: &pb.Resource{\n\t\t\t\t\t\t\tType: pkgK8s.Pod,\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\n\t\tfor _, valid := range validRequests {\n\t\t\trsp, err := fakeGrpcServer.StatSummary(context.TODO(), valid.req)\n\n\t\t\tif err != nil || rsp.GetError() != nil {\n\t\t\t\tt.Fatalf(\"Did not expect validation error on StatSummaryResponse, got %v, %v\", rsp, err)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Return empty stats summary response\", func(t *testing.T) {\n\t\tt.Run(\"when pod phase is succeeded or failed\", func(t *testing.T) {\n\t\t\texpectations := []statSumExpected{\n\t\t\t\t{\n\t\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-00\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Succeeded\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-01\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Failed\n`},\n\t\t\t\t\t\tmockPromResponse: model.Vector{},\n\t\t\t\t\t\texpectedPrometheusQueries: []string{\n\t\t\t\t\t\t\t`histogram_quantile(0.5, sum(irate(response_latency_ms_bucket{direction=\"inbound\", namespace=\"emojivoto\"}[])) by (le, namespace, pod))`,\n\t\t\t\t\t\t\t`histogram_quantile(0.95, sum(irate(response_latency_ms_bucket{direction=\"inbound\", namespace=\"emojivoto\"}[])) by (le, namespace, pod))`,\n\t\t\t\t\t\t\t`histogram_quantile(0.99, sum(irate(response_latency_ms_bucket{direction=\"inbound\", namespace=\"emojivoto\"}[])) by (le, namespace, pod))`,\n\t\t\t\t\t\t\t`sum(increase(response_total{direction=\"inbound\", namespace=\"emojivoto\"}[])) by (namespace, pod, classification, tls)`,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\t\tType:      pkgK8s.Pod,\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\texpectedResponse: genEmptyResponse(),\n\t\t\t\t},\n\t\t\t}\n\n\t\t\ttestStatSummary(t, expectations)\n\t\t})\n\n\t\tt.Run(\"for succeeded or failed replicas of a deployment\", func(t *testing.T) {\n\t\t\texpectations := []statSumExpected{\n\t\t\t\t{\n\t\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\t\terr: nil,\n\t\t\t\t\t\tk8sConfigs: []string{`\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: emoji\n  namespace: emojivoto\n  uid: a1b2c3\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc\n  strategy: {}\n  template:\n    spec:\n      containers:\n      - image: buoyantio/emojivoto-emoji-svc:v10\n`, `\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  uid: a1b2c3d4\n  annotations:\n    deployment.kubernetes.io/revision: \"2\"\n  name: emojivoto-meshed_2\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    pod-template-hash: 3c2b1a\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: a1b2c3\nspec:\n  selector:\n    matchLabels:\n      app: emoji-svc\n      pod-template-hash: 3c2b1a\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-00\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\n    pod-template-hash: 3c2b1a\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: a1b2c3d4\nstatus:\n  phase: Running\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-01\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    pod-template-hash: 3c2b1a\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: a1b2c3d4\nstatus:\n  phase: Running\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-02\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\n    pod-template-hash: 3c2b1a\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: a1b2c3d4\nstatus:\n  phase: Failed\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-03\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\n    pod-template-hash: 3c2b1a\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: a1b2c3d4\nstatus:\n  phase: Succeeded\n`},\n\t\t\t\t\t\tmockPromResponse: prometheusMetric(\"emoji\", \"deployment\"),\n\t\t\t\t\t},\n\t\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\t\tType:      pkgK8s.Deployment,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t\t},\n\t\t\t\t\texpectedResponse: GenStatSummaryResponse(\"emoji\", pkgK8s.Deployment, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\t\tMeshedPods:  1,\n\t\t\t\t\t\tRunningPods: 2,\n\t\t\t\t\t\tFailedPods:  1,\n\t\t\t\t\t}, true, false),\n\t\t\t\t},\n\t\t\t}\n\n\t\t\ttestStatSummary(t, expectations)\n\t\t})\n\t})\n\n\tt.Run(\"Stats returned are nil when SkipStats is true\", func(t *testing.T) {\n\t\texpectations := []statSumExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr: nil,\n\t\t\t\t\tk8sConfigs: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-1\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: linkerd\nstatus:\n  phase: Running\n`,\n\t\t\t\t\t},\n\t\t\t\t\tmockPromResponse:          model.Vector{},\n\t\t\t\t\texpectedPrometheusQueries: []string{},\n\t\t\t\t},\n\t\t\t\treq: &pb.StatSummaryRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t\tSkipStats:  true,\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenStatSummaryResponse(\"emojivoto-1\", pkgK8s.Pod, []string{\"emojivoto\"}, &PodCounts{\n\t\t\t\t\tStatus:      \"Running\",\n\t\t\t\t\tMeshedPods:  1,\n\t\t\t\t\tRunningPods: 1,\n\t\t\t\t\tFailedPods:  0,\n\t\t\t\t}, false, false),\n\t\t\t},\n\t\t}\n\n\t\ttestStatSummary(t, expectations)\n\t})\n}\n"
  },
  {
    "path": "viz/metrics-api/test_helper.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sort\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/prometheus\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/prometheus/common/model\"\n\t\"google.golang.org/grpc\"\n)\n\n// MockAPIClient satisfies the metrics-api gRPC interfaces\ntype MockAPIClient struct {\n\tErrorToReturn                error\n\tListPodsResponseToReturn     *pb.ListPodsResponse\n\tListServicesResponseToReturn *pb.ListServicesResponse\n\tStatSummaryResponseToReturn  *pb.StatSummaryResponse\n\tAuthzResponseToReturn        *pb.AuthzResponse\n\tGatewaysResponseToReturn     *pb.GatewaysResponse\n\tTopRoutesResponseToReturn    *pb.TopRoutesResponse\n\tEdgesResponseToReturn        *pb.EdgesResponse\n\tSelfCheckResponseToReturn    *pb.SelfCheckResponse\n}\n\n// StatSummary provides a mock of a metrics-api method.\nfunc (c *MockAPIClient) StatSummary(ctx context.Context, in *pb.StatSummaryRequest, opts ...grpc.CallOption) (*pb.StatSummaryResponse, error) {\n\treturn c.StatSummaryResponseToReturn, c.ErrorToReturn\n}\n\n// Authz provides a mock of a metrics-api method.\nfunc (c *MockAPIClient) Authz(ctx context.Context, in *pb.AuthzRequest, opts ...grpc.CallOption) (*pb.AuthzResponse, error) {\n\treturn c.AuthzResponseToReturn, c.ErrorToReturn\n}\n\n// Gateways provides a mock of a metrics-api method.\nfunc (c *MockAPIClient) Gateways(ctx context.Context, in *pb.GatewaysRequest, opts ...grpc.CallOption) (*pb.GatewaysResponse, error) {\n\treturn c.GatewaysResponseToReturn, c.ErrorToReturn\n}\n\n// TopRoutes provides a mock of a metrics-api method.\nfunc (c *MockAPIClient) TopRoutes(ctx context.Context, in *pb.TopRoutesRequest, opts ...grpc.CallOption) (*pb.TopRoutesResponse, error) {\n\treturn c.TopRoutesResponseToReturn, c.ErrorToReturn\n}\n\n// Edges provides a mock of a metrics-api method.\nfunc (c *MockAPIClient) Edges(ctx context.Context, in *pb.EdgesRequest, opts ...grpc.CallOption) (*pb.EdgesResponse, error) {\n\treturn c.EdgesResponseToReturn, c.ErrorToReturn\n}\n\n// ListPods provides a mock of a metrics-api method.\nfunc (c *MockAPIClient) ListPods(ctx context.Context, in *pb.ListPodsRequest, opts ...grpc.CallOption) (*pb.ListPodsResponse, error) {\n\treturn c.ListPodsResponseToReturn, c.ErrorToReturn\n}\n\n// ListServices provides a mock of a metrics-api method.\nfunc (c *MockAPIClient) ListServices(ctx context.Context, in *pb.ListServicesRequest, opts ...grpc.CallOption) (*pb.ListServicesResponse, error) {\n\treturn c.ListServicesResponseToReturn, c.ErrorToReturn\n}\n\n// SelfCheck provides a mock of a metrics-api method.\nfunc (c *MockAPIClient) SelfCheck(ctx context.Context, in *pb.SelfCheckRequest, _ ...grpc.CallOption) (*pb.SelfCheckResponse, error) {\n\treturn c.SelfCheckResponseToReturn, c.ErrorToReturn\n}\n\n// PodCounts is a test helper struct that is used for representing data in a\n// StatTable.PodGroup.Row.\ntype PodCounts struct {\n\tStatus      string\n\tMeshedPods  uint64\n\tRunningPods uint64\n\tFailedPods  uint64\n\tErrors      map[string]*pb.PodErrors\n}\n\n// GenStatSummaryResponse generates a mock metrics-api StatSummaryResponse\n// object.\nfunc GenStatSummaryResponse(resName, resType string, resNs []string, counts *PodCounts, basicStats bool, tcpStats bool) *pb.StatSummaryResponse {\n\trows := []*pb.StatTable_PodGroup_Row{}\n\tfor _, ns := range resNs {\n\t\tstatTableRow := &pb.StatTable_PodGroup_Row{\n\t\t\tResource: &pb.Resource{\n\t\t\t\tNamespace: ns,\n\t\t\t\tType:      resType,\n\t\t\t\tName:      resName,\n\t\t\t},\n\t\t\tTimeWindow: \"1m\",\n\t\t}\n\n\t\tif basicStats {\n\t\t\tstatTableRow.Stats = &pb.BasicStats{\n\t\t\t\tSuccessCount: 123,\n\t\t\t\tFailureCount: 0,\n\t\t\t\tLatencyMsP50: 123,\n\t\t\t\tLatencyMsP95: 123,\n\t\t\t\tLatencyMsP99: 123,\n\t\t\t}\n\t\t}\n\n\t\tif tcpStats {\n\t\t\tstatTableRow.TcpStats = &pb.TcpStats{\n\t\t\t\tOpenConnections: 123,\n\t\t\t\tReadBytesTotal:  123,\n\t\t\t\tWriteBytesTotal: 123,\n\t\t\t}\n\t\t}\n\n\t\tif counts != nil {\n\t\t\tstatTableRow.MeshedPodCount = counts.MeshedPods\n\t\t\tstatTableRow.RunningPodCount = counts.RunningPods\n\t\t\tstatTableRow.FailedPodCount = counts.FailedPods\n\t\t\tstatTableRow.Status = counts.Status\n\t\t\tstatTableRow.ErrorsByPod = counts.Errors\n\t\t}\n\n\t\trows = append(rows, statTableRow)\n\t}\n\n\tresp := &pb.StatSummaryResponse{\n\t\tResponse: &pb.StatSummaryResponse_Ok_{ // https://github.com/golang/protobuf/issues/205\n\t\t\tOk: &pb.StatSummaryResponse_Ok{\n\t\t\t\tStatTables: []*pb.StatTable{\n\t\t\t\t\t{\n\t\t\t\t\t\tTable: &pb.StatTable_PodGroup_{\n\t\t\t\t\t\t\tPodGroup: &pb.StatTable_PodGroup{\n\t\t\t\t\t\t\t\tRows: rows,\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\treturn resp\n}\n\ntype mockEdgeRow struct {\n\tresourceType string\n\tsrc          string\n\tdst          string\n\tsrcNamespace string\n\tdstNamespace string\n\tclientID     string\n\tserverID     string\n\tmsg          string\n}\n\n// a slice of edge rows to generate mock results\nvar emojivotoEdgeRows = []*mockEdgeRow{\n\t{\n\t\tresourceType: \"deployment\",\n\t\tsrc:          \"web\",\n\t\tdst:          \"voting\",\n\t\tsrcNamespace: \"emojivoto\",\n\t\tdstNamespace: \"emojivoto\",\n\t\tclientID:     \"web.emojivoto.serviceaccount.identity.linkerd.cluster.local\",\n\t\tserverID:     \"voting.emojivoto.serviceaccount.identity.linkerd.cluster.local\",\n\t\tmsg:          \"\",\n\t},\n\t{\n\t\tresourceType: \"deployment\",\n\t\tsrc:          \"vote-bot\",\n\t\tdst:          \"web\",\n\t\tsrcNamespace: \"emojivoto\",\n\t\tdstNamespace: \"emojivoto\",\n\t\tclientID:     \"default.emojivoto.serviceaccount.identity.linkerd.cluster.local\",\n\t\tserverID:     \"web.emojivoto.serviceaccount.identity.linkerd.cluster.local\",\n\t\tmsg:          \"\",\n\t},\n\t{\n\t\tresourceType: \"deployment\",\n\t\tsrc:          \"web\",\n\t\tdst:          \"emoji\",\n\t\tsrcNamespace: \"emojivoto\",\n\t\tdstNamespace: \"emojivoto\",\n\t\tclientID:     \"web.emojivoto.serviceaccount.identity.linkerd.cluster.local\",\n\t\tserverID:     \"emoji.emojivoto.serviceaccount.identity.linkerd.cluster.local\",\n\t\tmsg:          \"\",\n\t},\n}\n\n// a slice of edge rows to generate mock results\nvar linkerdEdgeRows = []*mockEdgeRow{\n\t{\n\t\tresourceType: \"deployment\",\n\t\tsrc:          \"linkerd-identity\",\n\t\tdst:          \"linkerd-prometheus\",\n\t\tsrcNamespace: \"linkerd\",\n\t\tdstNamespace: \"linkerd\",\n\t\tclientID:     \"linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local\",\n\t\tserverID:     \"linkerd-prometheus.linkerd.serviceaccount.identity.linkerd.cluster.local\",\n\t\tmsg:          \"\",\n\t},\n}\n\n// GenEdgesResponse generates a mock metrics-api EdgesResponse\n// object.\nfunc GenEdgesResponse(resourceType string, edgeRowNamespace string) *pb.EdgesResponse {\n\tedgeRows := emojivotoEdgeRows\n\n\tif edgeRowNamespace == \"linkerd\" {\n\t\tedgeRows = linkerdEdgeRows\n\t} else if edgeRowNamespace == \"all\" {\n\t\t// combine emojivotoEdgeRows and linkerdEdgeRows\n\t\tedgeRows = append(edgeRows, linkerdEdgeRows...)\n\t}\n\n\tedges := []*pb.Edge{}\n\tfor _, row := range edgeRows {\n\t\tedge := &pb.Edge{\n\t\t\tSrc: &pb.Resource{\n\t\t\t\tName:      row.src,\n\t\t\t\tNamespace: row.srcNamespace,\n\t\t\t\tType:      row.resourceType,\n\t\t\t},\n\t\t\tDst: &pb.Resource{\n\t\t\t\tName:      row.dst,\n\t\t\t\tNamespace: row.dstNamespace,\n\t\t\t\tType:      row.resourceType,\n\t\t\t},\n\t\t\tClientId:      row.clientID,\n\t\t\tServerId:      row.serverID,\n\t\t\tNoIdentityMsg: row.msg,\n\t\t}\n\t\tedges = append(edges, edge)\n\t}\n\n\t// sorting to retain consistent order for tests\n\tedges = sortEdgeRows(edges)\n\n\tresp := &pb.EdgesResponse{\n\t\tResponse: &pb.EdgesResponse_Ok_{\n\t\t\tOk: &pb.EdgesResponse_Ok{\n\t\t\t\tEdges: edges,\n\t\t\t},\n\t\t},\n\t}\n\treturn resp\n}\n\n// GenTopRoutesResponse generates a mock metrics-api TopRoutesResponse object.\nfunc GenTopRoutesResponse(routes []string, counts []uint64, outbound bool, authority string) *pb.TopRoutesResponse {\n\trows := []*pb.RouteTable_Row{}\n\tfor i, route := range routes {\n\t\trow := &pb.RouteTable_Row{\n\t\t\tRoute:     route,\n\t\t\tAuthority: authority,\n\t\t\tStats: &pb.BasicStats{\n\t\t\t\tSuccessCount: counts[i],\n\t\t\t\tFailureCount: 0,\n\t\t\t\tLatencyMsP50: 123,\n\t\t\t\tLatencyMsP95: 123,\n\t\t\t\tLatencyMsP99: 123,\n\t\t\t},\n\t\t\tTimeWindow: \"1m\",\n\t\t}\n\t\tif outbound {\n\t\t\trow.Stats.ActualSuccessCount = counts[i]\n\t\t}\n\t\trows = append(rows, row)\n\t}\n\tdefaultRow := &pb.RouteTable_Row{\n\t\tRoute:     \"[DEFAULT]\",\n\t\tAuthority: authority,\n\t\tStats: &pb.BasicStats{\n\t\t\tSuccessCount: counts[len(counts)-1],\n\t\t\tFailureCount: 0,\n\t\t\tLatencyMsP50: 123,\n\t\t\tLatencyMsP95: 123,\n\t\t\tLatencyMsP99: 123,\n\t\t},\n\t\tTimeWindow: \"1m\",\n\t}\n\tif outbound {\n\t\tdefaultRow.Stats.ActualSuccessCount = counts[len(counts)-1]\n\t}\n\trows = append(rows, defaultRow)\n\n\tresp := &pb.TopRoutesResponse{\n\t\tResponse: &pb.TopRoutesResponse_Ok_{\n\t\t\tOk: &pb.TopRoutesResponse_Ok{\n\t\t\t\tRoutes: []*pb.RouteTable{\n\t\t\t\t\t{\n\t\t\t\t\t\tRows:     rows,\n\t\t\t\t\t\tResource: \"deploy/foobar\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\treturn resp\n}\n\ntype expectedStatRPC struct {\n\terr                       error\n\tk8sConfigs                []string    // k8s objects to seed the API\n\tmockPromResponse          model.Value // mock out a prometheus query response\n\texpectedPrometheusQueries []string    // queries we expect metrics-api to issue to prometheus\n}\n\nfunc newMockGrpcServer(exp expectedStatRPC) (*prometheus.MockProm, *grpcServer, error) {\n\tk8sAPI, err := k8s.NewFakeAPI(exp.k8sConfigs...)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tmockProm := &prometheus.MockProm{Res: exp.mockPromResponse}\n\tfakeGrpcServer := &grpcServer{\n\t\tprometheusAPI:       mockProm,\n\t\tk8sAPI:              k8sAPI,\n\t\tcontrollerNamespace: \"linkerd\",\n\t\tclusterDomain:       \"cluster.local\",\n\t\tignoredNamespaces:   []string{},\n\t}\n\n\tk8sAPI.Sync(nil)\n\n\treturn mockProm, fakeGrpcServer, nil\n}\n\nfunc (exp expectedStatRPC) verifyPromQueries(mockProm *prometheus.MockProm) error {\n\t// if exp.expectedPrometheusQueries is an empty slice we still want to check no queries were executed.\n\tif exp.expectedPrometheusQueries != nil {\n\t\tsort.Strings(exp.expectedPrometheusQueries)\n\t\tsort.Strings(mockProm.QueriesExecuted)\n\n\t\t// because reflect.DeepEqual([]string{}, nil) is false\n\t\tif len(exp.expectedPrometheusQueries) == 0 && len(mockProm.QueriesExecuted) == 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\tif diff := deep.Equal(exp.expectedPrometheusQueries, mockProm.QueriesExecuted); diff != nil {\n\t\t\treturn fmt.Errorf(\"Prometheus queries incorrect: %+v\", diff)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "viz/metrics-api/top_routes.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\n\tsp \"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\tapi \"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/prometheus\"\n\t\"github.com/prometheus/common/model\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n)\n\nconst (\n\trouteReqQuery             = \"sum(increase(route_response_total%s[%s])) by (%s, dst, classification)\"\n\tactualRouteReqQuery       = \"sum(increase(route_actual_response_total%s[%s])) by (%s, dst, classification)\"\n\trouteLatencyQuantileQuery = \"histogram_quantile(%s, sum(irate(route_response_latency_ms_bucket%s[%s])) by (le, dst, %s))\"\n\tdstLabel                  = `dst=~\"(%s)(:\\\\d+)?\"`\n\t// DefaultRouteName is the name to display for requests that don't match any routes.\n\tDefaultRouteName = \"[DEFAULT]\"\n)\n\ntype dstAndRoute struct {\n\tdst   string\n\troute string\n}\n\ntype indexedTable = map[dstAndRoute]*pb.RouteTable_Row\n\ntype resourceTable struct {\n\tresource string\n\ttable    indexedTable\n}\n\nfunc (s *grpcServer) TopRoutes(ctx context.Context, req *pb.TopRoutesRequest) (*pb.TopRoutesResponse, error) {\n\tlog.Debugf(\"TopRoutes request: %+v\", req)\n\n\tif !s.k8sAPI.SPAvailable() {\n\t\treturn topRoutesError(req, \"Routes are not available\"), nil\n\t}\n\n\terrRsp := validateRequest(req)\n\tif errRsp != nil {\n\t\treturn errRsp, nil\n\t}\n\n\t// TopRoutes will return one table for each resource object requested.\n\ttables := make([]resourceTable, 0)\n\ttargetResource := req.GetSelector().GetResource()\n\tlabelSelector, err := getTopLabelSelector(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = s.validateTimeWindow(ctx, req.TimeWindow)\n\tif err != nil {\n\t\treturn topRoutesError(req, fmt.Sprintf(\"invalid time window: %s\", err)), nil\n\t}\n\n\t// Non-authority resource\n\tobjects, err := s.k8sAPI.GetObjects(targetResource.Namespace, targetResource.Type, targetResource.Name, labelSelector)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Create a table for each object in the resource.\n\tfor _, obj := range objects {\n\t\ttable, err := s.topRoutesFor(ctx, req, obj)\n\t\tif err != nil {\n\t\t\t// No samples for this object, skip it.\n\t\t\tcontinue\n\t\t}\n\t\ttables = append(tables, *table)\n\t}\n\n\tif len(tables) == 0 {\n\t\treturn topRoutesError(req, \"No Service Profiles found for selected resources\"), nil\n\t}\n\n\t// Construct response.\n\trouteTables := make([]*pb.RouteTable, 0)\n\n\tfor _, t := range tables {\n\t\trows := make([]*pb.RouteTable_Row, 0)\n\t\tfor _, row := range t.table {\n\t\t\trows = append(rows, row)\n\t\t}\n\t\trouteTables = append(routeTables, &pb.RouteTable{\n\t\t\tResource: t.resource,\n\t\t\tRows:     rows,\n\t\t})\n\t}\n\n\treturn &pb.TopRoutesResponse{\n\t\tResponse: &pb.TopRoutesResponse_Ok_{\n\t\t\tOk: &pb.TopRoutesResponse_Ok{\n\t\t\t\tRoutes: routeTables,\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\n// topRoutesFor constructs a resource table for the given resource object.\nfunc (s *grpcServer) topRoutesFor(ctx context.Context, req *pb.TopRoutesRequest, object runtime.Object) (*resourceTable, error) {\n\t// requestedResource is the destination resource.  For inbound queries, it is the target resource.\n\t// For outbound (i.e. --to) queries, it is the ToResource.  We will look at the service profiles\n\t// of this destination resource.\n\tname, err := api.GetNameOf(object)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tclientNs := req.GetSelector().GetResource().GetNamespace()\n\ttyp := req.GetSelector().GetResource().GetType()\n\tlabelSelector, err := getTopLabelSelector(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttargetResource := &pb.Resource{\n\t\tName:      name,\n\t\tNamespace: req.GetSelector().GetResource().GetNamespace(),\n\t\tType:      typ,\n\t}\n\trequestedResource := targetResource\n\tif req.GetToResource() != nil {\n\t\trequestedResource = req.GetToResource()\n\t}\n\n\tprofiles := make(map[string]*sp.ServiceProfile)\n\n\t// Lookup individual resource objects.\n\tobjects, err := s.k8sAPI.GetObjects(requestedResource.Namespace, requestedResource.Type, requestedResource.Name, labelSelector)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// Find service profiles for all services in all objects in the resource.\n\tfor _, obj := range objects {\n\t\t// Lookup services for each object.\n\t\tservices, err := s.k8sAPI.GetServicesFor(obj, false)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, svc := range services {\n\t\t\tp := s.k8sAPI.GetServiceProfileFor(svc, clientNs, s.clusterDomain)\n\t\t\tprofiles[svc.GetName()] = p\n\t\t}\n\t}\n\n\tmetrics, err := s.getRouteMetrics(ctx, req, profiles, targetResource)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &resourceTable{\n\t\tresource: fmt.Sprintf(\"%s/%s\", typ, name),\n\t\ttable:    metrics,\n\t}, nil\n}\n\nfunc topRoutesError(req *pb.TopRoutesRequest, message string) *pb.TopRoutesResponse {\n\treturn &pb.TopRoutesResponse{\n\t\tResponse: &pb.TopRoutesResponse_Error{\n\t\t\tError: &pb.ResourceError{\n\t\t\t\tResource: req.GetSelector().GetResource(),\n\t\t\t\tError:    message,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc validateRequest(req *pb.TopRoutesRequest) *pb.TopRoutesResponse {\n\tif req.GetSelector().GetResource() == nil {\n\t\treturn topRoutesError(req, \"TopRoutes request missing Selector Resource\")\n\t}\n\n\tif req.GetNone() == nil {\n\t\t// This is an outbound (--to) request.\n\t\ttargetType := req.GetSelector().GetResource().GetType()\n\t\tif targetType == k8s.Service {\n\t\t\treturn topRoutesError(req, fmt.Sprintf(\"The %s resource type is not supported with 'to' queries\", targetType))\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *grpcServer) getRouteMetrics(ctx context.Context, req *pb.TopRoutesRequest, profiles map[string]*sp.ServiceProfile, resource *pb.Resource) (indexedTable, error) {\n\ttimeWindow := req.TimeWindow\n\n\tdsts := make([]string, 0)\n\tfor _, p := range profiles {\n\t\tdsts = append(dsts, p.GetName())\n\t}\n\n\treqLabels := s.buildRouteLabels(req, dsts, resource)\n\tgroupBy := \"rt_route\"\n\n\tqueries := map[promType]string{\n\t\tpromRequests: fmt.Sprintf(routeReqQuery, reqLabels, timeWindow, groupBy),\n\t}\n\n\tif req.GetOutbound() != nil && req.GetNone() == nil {\n\t\t// If this req has an Outbound, then query the actual request counts as well.\n\t\tqueries[promActualRequests] = fmt.Sprintf(actualRouteReqQuery, reqLabels, timeWindow, groupBy)\n\t}\n\n\tquantileQueries := generateQuantileQueries(routeLatencyQuantileQuery, reqLabels, timeWindow, groupBy)\n\tresults, err := s.getPrometheusMetrics(ctx, queries, quantileQueries)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttable := make(indexedTable)\n\tfor service, profile := range profiles {\n\t\tfor _, route := range profile.Spec.Routes {\n\t\t\tkey := dstAndRoute{\n\t\t\t\tdst:   profile.GetName(),\n\t\t\t\troute: route.Name,\n\t\t\t}\n\t\t\ttable[key] = &pb.RouteTable_Row{\n\t\t\t\tAuthority: service,\n\t\t\t\tRoute:     route.Name,\n\t\t\t\tStats:     &pb.BasicStats{},\n\t\t\t}\n\t\t}\n\t\tdefaultKey := dstAndRoute{\n\t\t\tdst:   profile.GetName(),\n\t\t\troute: \"\",\n\t\t}\n\t\ttable[defaultKey] = &pb.RouteTable_Row{\n\t\t\tAuthority: service,\n\t\t\tRoute:     DefaultRouteName,\n\t\t\tStats:     &pb.BasicStats{},\n\t\t}\n\t}\n\n\tprocessRouteMetrics(results, timeWindow, table)\n\n\treturn table, nil\n}\n\nfunc (s *grpcServer) buildRouteLabels(req *pb.TopRoutesRequest, dsts []string, resource *pb.Resource) string {\n\t// labels: the labels for the resource we want to query for\n\tvar labels model.LabelSet\n\n\tswitch req.Outbound.(type) {\n\n\tcase *pb.TopRoutesRequest_ToResource:\n\t\tlabels = labels.Merge(prometheus.QueryLabels(resource))\n\t\tlabels = labels.Merge(promDirectionLabels(\"outbound\"))\n\t\treturn renderLabels(labels, dsts)\n\n\tdefault:\n\t\tlabels = labels.Merge(promDirectionLabels(\"inbound\"))\n\t\tlabels = labels.Merge(prometheus.QueryLabels(resource))\n\t\treturn renderLabels(labels, dsts)\n\t}\n}\n\nfunc renderLabels(labels model.LabelSet, services []string) string {\n\tpairs := make([]string, 0)\n\tfor k, v := range labels {\n\t\tpairs = append(pairs, fmt.Sprintf(\"%s=%q\", k, v))\n\t}\n\tif len(services) > 0 {\n\t\tpairs = append(pairs, fmt.Sprintf(dstLabel, strings.Join(services, \"|\")))\n\t}\n\tsort.Strings(pairs)\n\treturn fmt.Sprintf(\"{%s}\", strings.Join(pairs, \", \"))\n}\n\nfunc processRouteMetrics(results []promResult, timeWindow string, table indexedTable) {\n\tfor _, result := range results {\n\t\tfor _, sample := range result.vec {\n\t\t\troute := string(sample.Metric[model.LabelName(\"rt_route\")])\n\t\t\tdst := string(sample.Metric[model.LabelName(\"dst\")])\n\t\t\tdst = strings.Split(dst, \":\")[0] // Truncate port, if there is one.\n\n\t\t\tkey := dstAndRoute{dst, route}\n\n\t\t\tif table[key] == nil {\n\t\t\t\tlog.Warnf(\"Found stats for unknown route: %s:%s\", dst, route)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttable[key].TimeWindow = timeWindow\n\t\t\tvalue := extractSampleValue(sample)\n\n\t\t\tswitch result.prom {\n\t\t\tcase promRequests:\n\t\t\t\tswitch string(sample.Metric[model.LabelName(\"classification\")]) {\n\t\t\t\tcase success:\n\t\t\t\t\ttable[key].Stats.SuccessCount += value\n\t\t\t\tcase failure:\n\t\t\t\t\ttable[key].Stats.FailureCount += value\n\t\t\t\t}\n\t\t\tcase promActualRequests:\n\t\t\t\tswitch string(sample.Metric[model.LabelName(\"classification\")]) {\n\t\t\t\tcase success:\n\t\t\t\t\ttable[key].Stats.ActualSuccessCount += value\n\t\t\t\tcase failure:\n\t\t\t\t\ttable[key].Stats.ActualFailureCount += value\n\t\t\t\t}\n\t\t\tcase promLatencyP50:\n\t\t\t\ttable[key].Stats.LatencyMsP50 = value\n\t\t\tcase promLatencyP95:\n\t\t\t\ttable[key].Stats.LatencyMsP95 = value\n\t\t\tcase promLatencyP99:\n\t\t\t\ttable[key].Stats.LatencyMsP99 = value\n\t\t\t}\n\t\t}\n\t}\n}\n\n// generate correct label.Selector object according to the request\nfunc getTopLabelSelector(req *pb.TopRoutesRequest) (labels.Selector, error) {\n\tlabelSelector := labels.Everything()\n\tif s := req.GetSelector().GetLabelSelector(); s != \"\" {\n\t\tvar err error\n\t\tlabelSelector, err = labels.Parse(s)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid label selector %q: %w\", s, err)\n\t\t}\n\t}\n\treturn labelSelector, nil\n}\n"
  },
  {
    "path": "viz/metrics-api/top_routes_test.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sort\"\n\t\"testing\"\n\n\tpkgK8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/prometheus/common/model\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\n// deployment/books\nvar booksDeployConfig = []string{`kind: Deployment\napiVersion: apps/v1\nmetadata:\n  name: books\n  namespace: default\n  uid: a1b2c3\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: books\n  template:\n    metadata:\n      labels:\n        app: books\n    spec:\n      dnsPolicy: ClusterFirst\n      containers:\n      - image: buoyantio/booksapp:v0.0.2\n`, `\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  uid: a1b2c3d4\n  name: books\n  namespace: default\n  labels:\n    app: books\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: a1b2c3\nspec:\n  selector:\n    matchLabels:\n      app: books`,\n}\n\n// daemonset/books\nvar booksDaemonsetConfig = `kind: DaemonSet\napiVersion: apps/v1\nmetadata:\n  name: books\n  namespace: default\nspec:\n  selector:\n    matchLabels:\n      app: books\n  template:\n    metadata:\n      labels:\n        app: books\n    spec:\n      dnsPolicy: ClusterFirst\n      containers:\n      - image: buoyantio/booksapp:v0.0.2`\n\n// job/books\nvar booksJobConfig = `kind: Job\napiVersion: batch/v1\nmetadata:\n  name: books\n  namespace: default\nspec:\n  selector:\n    matchLabels:\n      app: books\n  template:\n    metadata:\n      labels:\n        app: books\n    spec:\n      dnsPolicy: ClusterFirst\n      containers:\n      - image: buoyantio/booksapp:v0.0.2`\n\nvar booksStatefulsetConfig = `kind: StatefulSet\napiVersion: apps/v1\nmetadata:\n  name: books\n  namespace: default\nspec:\n  selector:\n    matchLabels:\n      app: books\n  template:\n    serviceName: books\n    metadata:\n      labels:\n        app: books\n    spec:\n      containers:\n      - image: buoyantio/booksapp:v0.0.2\n        volumes:\n        - name: data\n          mountPath: /usr/src/app\n  volumeClaimTemplates:\n  - metadata:\n      name: data\n    spec:\n      accessModes: [\"ReadWriteOnce\"]\n      resources:\n        requests:\n          storage: 10Gi\n`\n\nvar booksServiceConfig = []string{\n\t// service/books\n\t`apiVersion: v1\nkind: Service\nmetadata:\n  name: books\n  namespace: default\nspec:\n  selector:\n    app: books`,\n\n\t// po/books-64c68d6d46-jrmmx\n\t`apiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    app: books\n  ownerReferences:\n  - apiVersion: apps/v1\n    uid: a1b2c3d4\n  name: books-64c68d6d46-jrmmx\n  namespace: default\nspec:\n  containers:\n  - image: buoyantio/booksapp:v0.0.2\nstatus:\n  phase: Running`,\n\n\t// serviceprofile/books.default.svc.cluster.local\n\t`apiVersion: linkerd.io/v1alpha2\nkind: ServiceProfile\nmetadata:\n  name: books.default.svc.cluster.local\n  namespace: default\nspec:\n  routes:\n  - condition:\n      method: GET\n      pathRegex: /a\n    name: /a\n`,\n}\n\nvar booksConfig = append(booksServiceConfig, booksDeployConfig...)\nvar booksDSConfig = append(booksServiceConfig, booksDaemonsetConfig)\nvar booksSSConfig = append(booksServiceConfig, booksStatefulsetConfig)\nvar booksJConfig = append(booksServiceConfig, booksJobConfig)\n\ntype topRoutesExpected struct {\n\texpectedStatRPC\n\treq              *pb.TopRoutesRequest  // the request we would like to test\n\texpectedResponse *pb.TopRoutesResponse // the routes response we expect\n}\n\nfunc routesMetric(routes []string) model.Vector {\n\tsamples := make(model.Vector, 0)\n\tfor _, route := range routes {\n\t\tsamples = append(samples, genRouteSample(route))\n\t}\n\tsamples = append(samples, genDefaultRouteSample())\n\treturn samples\n}\n\nfunc genRouteSample(route string) *model.Sample {\n\treturn &model.Sample{\n\t\tMetric: model.Metric{\n\t\t\t\"rt_route\":       model.LabelValue(route),\n\t\t\t\"dst\":            \"books.default.svc.cluster.local\",\n\t\t\t\"classification\": success,\n\t\t},\n\t\tValue:     123,\n\t\tTimestamp: 456,\n\t}\n}\n\nfunc genDefaultRouteSample() *model.Sample {\n\treturn &model.Sample{\n\t\tMetric: model.Metric{\n\t\t\t\"dst\":            \"books.default.svc.cluster.local\",\n\t\t\t\"classification\": success,\n\t\t},\n\t\tValue:     123,\n\t\tTimestamp: 456,\n\t}\n}\n\nfunc testTopRoutes(t *testing.T, expectations []topRoutesExpected) {\n\tfor id, exp := range expectations {\n\t\texp := exp // pin\n\t\tt.Run(fmt.Sprintf(\"%d\", id), func(t *testing.T) {\n\t\t\tmockProm, fakeGrpcServer, err := newMockGrpcServer(exp.expectedStatRPC)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Error creating mock grpc server: %s\", err)\n\t\t\t}\n\n\t\t\trsp, err := fakeGrpcServer.TopRoutes(context.TODO(), exp.req)\n\t\t\tif !errors.Is(err, exp.err) {\n\t\t\t\tt.Fatalf(\"Expected error: %s, Got: %s\", exp.err, err)\n\t\t\t}\n\n\t\t\terr = exp.verifyPromQueries(mockProm)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\trows := rsp.GetOk().GetRoutes()[0].Rows\n\n\t\t\tif len(rows) != len(exp.expectedResponse.GetOk().GetRoutes()[0].Rows) {\n\t\t\t\tt.Fatalf(\n\t\t\t\t\t\"Expected [%d] rows, got [%d].\\nExpected:\\n%s\\nGot:\\n%s\",\n\t\t\t\t\tlen(exp.expectedResponse.GetOk().GetRoutes()[0].Rows),\n\t\t\t\t\tlen(rows),\n\t\t\t\t\texp.expectedResponse.GetOk().GetRoutes()[0].Rows,\n\t\t\t\t\trows,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tsort.Slice(rows, func(i, j int) bool {\n\t\t\t\treturn rows[i].GetAuthority()+rows[i].GetRoute() < rows[j].GetAuthority()+rows[j].GetRoute()\n\t\t\t})\n\n\t\t\tfor i, row := range rows {\n\t\t\t\texpected := exp.expectedResponse.GetOk().GetRoutes()[0].Rows[i]\n\t\t\t\tif !proto.Equal(row, expected) {\n\t\t\t\t\tt.Fatalf(\"Expected: %+v\\n Got: %+v\", expected, row)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTopRoutes(t *testing.T) {\n\tt.Run(\"Successfully performs a routes query\", func(t *testing.T) {\n\t\troutes := []string{\"/a\"}\n\t\tcounts := []uint64{123}\n\t\texpectations := []topRoutesExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr:              nil,\n\t\t\t\t\tmockPromResponse: routesMetric([]string{\"/a\"}),\n\t\t\t\t\texpectedPrometheusQueries: []string{\n\t\t\t\t\t\t`histogram_quantile(0.5, sum(irate(route_response_latency_ms_bucket{deployment=\"books\", direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`histogram_quantile(0.95, sum(irate(route_response_latency_ms_bucket{deployment=\"books\", direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`histogram_quantile(0.99, sum(irate(route_response_latency_ms_bucket{deployment=\"books\", direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`sum(increase(route_response_total{deployment=\"books\", direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (rt_route, dst, classification)`,\n\t\t\t\t\t},\n\t\t\t\t\tk8sConfigs: booksConfig,\n\t\t\t\t},\n\t\t\t\treq: &pb.TopRoutesRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\tType:      pkgK8s.Deployment,\n\t\t\t\t\t\t\tName:      \"books\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t\tOutbound: &pb.TopRoutesRequest_None{\n\t\t\t\t\t\tNone: &pb.Empty{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenTopRoutesResponse(routes, counts, false, \"books\"),\n\t\t\t},\n\t\t}\n\n\t\ttestTopRoutes(t, expectations)\n\t})\n\n\tt.Run(\"Successfully performs a routes query for a service\", func(t *testing.T) {\n\t\troutes := []string{\"/a\"}\n\t\tcounts := []uint64{123}\n\t\texpectations := []topRoutesExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr:              nil,\n\t\t\t\t\tmockPromResponse: routesMetric([]string{\"/a\"}),\n\t\t\t\t\texpectedPrometheusQueries: []string{\n\t\t\t\t\t\t`histogram_quantile(0.5, sum(irate(route_response_latency_ms_bucket{direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`histogram_quantile(0.95, sum(irate(route_response_latency_ms_bucket{direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`histogram_quantile(0.99, sum(irate(route_response_latency_ms_bucket{direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`sum(increase(route_response_total{direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (rt_route, dst, classification)`,\n\t\t\t\t\t},\n\t\t\t\t\tk8sConfigs: booksConfig,\n\t\t\t\t},\n\t\t\t\treq: &pb.TopRoutesRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\tType:      pkgK8s.Service,\n\t\t\t\t\t\t\tName:      \"books\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t\tOutbound: &pb.TopRoutesRequest_None{\n\t\t\t\t\t\tNone: &pb.Empty{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenTopRoutesResponse(routes, counts, false, \"books\"),\n\t\t\t},\n\t\t}\n\n\t\ttestTopRoutes(t, expectations)\n\t})\n\n\tt.Run(\"Successfully performs a routes query for a daemonset\", func(t *testing.T) {\n\t\troutes := []string{\"/a\"}\n\t\tcounts := []uint64{123}\n\t\texpectations := []topRoutesExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr:              nil,\n\t\t\t\t\tmockPromResponse: routesMetric([]string{\"/a\"}),\n\t\t\t\t\texpectedPrometheusQueries: []string{\n\t\t\t\t\t\t`histogram_quantile(0.5, sum(irate(route_response_latency_ms_bucket{daemonset=\"books\", direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`histogram_quantile(0.95, sum(irate(route_response_latency_ms_bucket{daemonset=\"books\", direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`histogram_quantile(0.99, sum(irate(route_response_latency_ms_bucket{daemonset=\"books\", direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`sum(increase(route_response_total{daemonset=\"books\", direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (rt_route, dst, classification)`,\n\t\t\t\t\t},\n\t\t\t\t\tk8sConfigs: booksDSConfig,\n\t\t\t\t},\n\t\t\t\treq: &pb.TopRoutesRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\tType:      pkgK8s.DaemonSet,\n\t\t\t\t\t\t\tName:      \"books\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenTopRoutesResponse(routes, counts, false, \"books\"),\n\t\t\t},\n\t\t}\n\n\t\ttestTopRoutes(t, expectations)\n\t})\n\n\tt.Run(\"Successfully performs a routes query for a job\", func(t *testing.T) {\n\t\troutes := []string{\"/a\"}\n\t\tcounts := []uint64{123}\n\t\texpectations := []topRoutesExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr:              nil,\n\t\t\t\t\tmockPromResponse: routesMetric([]string{\"/a\"}),\n\t\t\t\t\texpectedPrometheusQueries: []string{\n\t\t\t\t\t\t`histogram_quantile(0.5, sum(irate(route_response_latency_ms_bucket{direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", k8s_job=\"books\", namespace=\"default\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`histogram_quantile(0.95, sum(irate(route_response_latency_ms_bucket{direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", k8s_job=\"books\", namespace=\"default\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`histogram_quantile(0.99, sum(irate(route_response_latency_ms_bucket{direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", k8s_job=\"books\", namespace=\"default\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`sum(increase(route_response_total{direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", k8s_job=\"books\", namespace=\"default\"}[1m])) by (rt_route, dst, classification)`,\n\t\t\t\t\t},\n\t\t\t\t\tk8sConfigs: booksJConfig,\n\t\t\t\t},\n\t\t\t\treq: &pb.TopRoutesRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\tType:      pkgK8s.Job,\n\t\t\t\t\t\t\tName:      \"books\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenTopRoutesResponse(routes, counts, false, \"books\"),\n\t\t\t},\n\t\t}\n\n\t\ttestTopRoutes(t, expectations)\n\t})\n\n\tt.Run(\"Successfully performs a routes query for a statefulset\", func(t *testing.T) {\n\t\troutes := []string{\"/a\"}\n\t\tcounts := []uint64{123}\n\t\texpectations := []topRoutesExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr:              nil,\n\t\t\t\t\tmockPromResponse: routesMetric([]string{\"/a\"}),\n\t\t\t\t\texpectedPrometheusQueries: []string{\n\t\t\t\t\t\t`histogram_quantile(0.5, sum(irate(route_response_latency_ms_bucket{direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\", statefulset=\"books\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`histogram_quantile(0.95, sum(irate(route_response_latency_ms_bucket{direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\", statefulset=\"books\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`histogram_quantile(0.99, sum(irate(route_response_latency_ms_bucket{direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\", statefulset=\"books\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`sum(increase(route_response_total{direction=\"inbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\", statefulset=\"books\"}[1m])) by (rt_route, dst, classification)`,\n\t\t\t\t\t},\n\t\t\t\t\tk8sConfigs: booksSSConfig,\n\t\t\t\t},\n\t\t\t\treq: &pb.TopRoutesRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\tType:      pkgK8s.StatefulSet,\n\t\t\t\t\t\t\tName:      \"books\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenTopRoutesResponse(routes, counts, false, \"books\"),\n\t\t\t},\n\t\t}\n\n\t\ttestTopRoutes(t, expectations)\n\t})\n\n\tt.Run(\"Successfully performs an outbound routes query\", func(t *testing.T) {\n\t\troutes := []string{\"/a\"}\n\t\tcounts := []uint64{123}\n\t\texpectations := []topRoutesExpected{\n\t\t\t{\n\t\t\t\texpectedStatRPC: expectedStatRPC{\n\t\t\t\t\terr:              nil,\n\t\t\t\t\tmockPromResponse: routesMetric([]string{\"/a\"}),\n\t\t\t\t\texpectedPrometheusQueries: []string{\n\t\t\t\t\t\t`histogram_quantile(0.5, sum(irate(route_response_latency_ms_bucket{deployment=\"books\", direction=\"outbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`histogram_quantile(0.95, sum(irate(route_response_latency_ms_bucket{deployment=\"books\", direction=\"outbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`histogram_quantile(0.99, sum(irate(route_response_latency_ms_bucket{deployment=\"books\", direction=\"outbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (le, dst, rt_route))`,\n\t\t\t\t\t\t`sum(increase(route_response_total{deployment=\"books\", direction=\"outbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (rt_route, dst, classification)`,\n\t\t\t\t\t\t`sum(increase(route_actual_response_total{deployment=\"books\", direction=\"outbound\", dst=~\"(books.default.svc.cluster.local)(:\\\\d+)?\", namespace=\"default\"}[1m])) by (rt_route, dst, classification)`,\n\t\t\t\t\t},\n\t\t\t\t\tk8sConfigs: booksConfig,\n\t\t\t\t},\n\t\t\t\treq: &pb.TopRoutesRequest{\n\t\t\t\t\tSelector: &pb.ResourceSelection{\n\t\t\t\t\t\tResource: &pb.Resource{\n\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\tType:      pkgK8s.Deployment,\n\t\t\t\t\t\t\tName:      \"books\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tOutbound: &pb.TopRoutesRequest_ToResource{\n\t\t\t\t\t\tToResource: &pb.Resource{\n\t\t\t\t\t\t\tType: pkgK8s.Service,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTimeWindow: \"1m\",\n\t\t\t\t},\n\t\t\t\texpectedResponse: GenTopRoutesResponse(routes, counts, true, \"books\"),\n\t\t\t},\n\t\t}\n\n\t\ttestTopRoutes(t, expectations)\n\t})\n}\n"
  },
  {
    "path": "viz/metrics-api/util/api_utils.go",
    "content": "package util\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\nvar (\n\tdefaultMetricTimeWindow    = \"1m\"\n\tmetricTimeWindowLowerBound = time.Second * 15 // the window value needs to equal or larger than that\n)\n\n// StatsBaseRequestParams contains parameters that are used to build requests\n// for metrics data.  This includes requests to StatSummary and TopRoutes.\ntype StatsBaseRequestParams struct {\n\tTimeWindow    string\n\tNamespace     string\n\tResourceType  string\n\tResourceName  string\n\tAllNamespaces bool\n}\n\n// StatsSummaryRequestParams contains parameters that are used to build\n// StatSummary requests.\ntype StatsSummaryRequestParams struct {\n\tStatsBaseRequestParams\n\tToNamespace   string\n\tToType        string\n\tToName        string\n\tFromNamespace string\n\tFromType      string\n\tFromName      string\n\tSkipStats     bool\n\tTCPStats      bool\n\tLabelSelector string\n}\n\n// EdgesRequestParams contains parameters that are used to build\n// Edges requests.\ntype EdgesRequestParams struct {\n\tNamespace     string\n\tResourceType  string\n\tAllNamespaces bool\n}\n\n// TopRoutesRequestParams contains parameters that are used to build TopRoutes\n// requests.\ntype TopRoutesRequestParams struct {\n\tStatsBaseRequestParams\n\tToNamespace   string\n\tToType        string\n\tToName        string\n\tLabelSelector string\n}\n\n// GatewayRequestParams contains parameters that are used to build a\n// GatewayRequest\ntype GatewayRequestParams struct {\n\tRemoteClusterName string\n\tGatewayNamespace  string\n\tTimeWindow        string\n}\n\n// BuildStatSummaryRequest builds a Public API StatSummaryRequest from a\n// StatsSummaryRequestParams.\nfunc BuildStatSummaryRequest(p StatsSummaryRequestParams) (*pb.StatSummaryRequest, error) {\n\twindow, err := ValidateTimeWindow(p.TimeWindow)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif p.AllNamespaces && p.ResourceName != \"\" {\n\t\treturn nil, errors.New(\"stats for a resource cannot be retrieved by name across all namespaces\")\n\t}\n\n\ttargetNamespace := p.Namespace\n\tif p.AllNamespaces {\n\t\ttargetNamespace = \"\"\n\t} else if p.Namespace == \"\" {\n\t\ttargetNamespace = corev1.NamespaceDefault\n\t}\n\n\tresourceType, err := k8s.CanonicalResourceNameFromFriendlyName(p.ResourceType)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstatRequest := &pb.StatSummaryRequest{\n\t\tSelector: &pb.ResourceSelection{\n\t\t\tResource: &pb.Resource{\n\t\t\t\tNamespace: targetNamespace,\n\t\t\t\tName:      p.ResourceName,\n\t\t\t\tType:      resourceType,\n\t\t\t},\n\t\t\tLabelSelector: p.LabelSelector,\n\t\t},\n\t\tTimeWindow: window,\n\t\tSkipStats:  p.SkipStats,\n\t\tTcpStats:   p.TCPStats,\n\t}\n\n\tif p.ToName != \"\" || p.ToType != \"\" || p.ToNamespace != \"\" {\n\t\tif p.ToNamespace == \"\" {\n\t\t\tp.ToNamespace = targetNamespace\n\t\t}\n\t\tif p.ToType == \"\" {\n\t\t\tp.ToType = resourceType\n\t\t}\n\n\t\ttoType, err := k8s.CanonicalResourceNameFromFriendlyName(p.ToType)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\ttoResource := pb.StatSummaryRequest_ToResource{\n\t\t\tToResource: &pb.Resource{\n\t\t\t\tNamespace: p.ToNamespace,\n\t\t\t\tType:      toType,\n\t\t\t\tName:      p.ToName,\n\t\t\t},\n\t\t}\n\t\tstatRequest.Outbound = &toResource\n\t}\n\n\tif p.FromName != \"\" || p.FromType != \"\" || p.FromNamespace != \"\" {\n\t\tif p.FromNamespace == \"\" {\n\t\t\tp.FromNamespace = targetNamespace\n\t\t}\n\t\tif p.FromType == \"\" {\n\t\t\tp.FromType = resourceType\n\t\t}\n\n\t\tfromType, err := validateFromResourceType(p.FromType)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfromResource := pb.StatSummaryRequest_FromResource{\n\t\t\tFromResource: &pb.Resource{\n\t\t\t\tNamespace: p.FromNamespace,\n\t\t\t\tType:      fromType,\n\t\t\t\tName:      p.FromName,\n\t\t\t},\n\t\t}\n\t\tstatRequest.Outbound = &fromResource\n\t}\n\n\treturn statRequest, nil\n}\n\n// BuildEdgesRequest builds a Public API EdgesRequest from a\n// EdgesRequestParams.\nfunc BuildEdgesRequest(p EdgesRequestParams) (*pb.EdgesRequest, error) {\n\tnamespace := p.Namespace\n\n\t// If all namespaces was specified, ignore namespace value.\n\tif p.AllNamespaces {\n\t\tnamespace = \"\"\n\t}\n\n\tresourceType, err := k8s.CanonicalResourceNameFromFriendlyName(p.ResourceType)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tedgesRequest := &pb.EdgesRequest{\n\t\tSelector: &pb.ResourceSelection{\n\t\t\tResource: &pb.Resource{\n\t\t\t\tNamespace: namespace,\n\t\t\t\tType:      resourceType,\n\t\t\t},\n\t\t},\n\t}\n\n\treturn edgesRequest, nil\n}\n\n// BuildTopRoutesRequest builds a Public API TopRoutesRequest from a\n// TopRoutesRequestParams.\nfunc BuildTopRoutesRequest(p TopRoutesRequestParams) (*pb.TopRoutesRequest, error) {\n\twindow := defaultMetricTimeWindow\n\tif p.TimeWindow != \"\" {\n\t\t_, err := time.ParseDuration(p.TimeWindow)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\twindow = p.TimeWindow\n\t}\n\n\tif p.AllNamespaces && p.ResourceName != \"\" {\n\t\treturn nil, errors.New(\"routes for a resource cannot be retrieved by name across all namespaces\")\n\t}\n\n\ttargetNamespace := p.Namespace\n\tif p.AllNamespaces {\n\t\ttargetNamespace = \"\"\n\t} else if p.Namespace == \"\" {\n\t\ttargetNamespace = corev1.NamespaceDefault\n\t}\n\n\tresourceType, err := k8s.CanonicalResourceNameFromFriendlyName(p.ResourceType)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttopRoutesRequest := &pb.TopRoutesRequest{\n\t\tSelector: &pb.ResourceSelection{\n\t\t\tResource: &pb.Resource{\n\t\t\t\tNamespace: targetNamespace,\n\t\t\t\tName:      p.ResourceName,\n\t\t\t\tType:      resourceType,\n\t\t\t},\n\t\t\tLabelSelector: p.LabelSelector,\n\t\t},\n\t\tTimeWindow: window,\n\t}\n\n\tif p.ToName != \"\" || p.ToType != \"\" || p.ToNamespace != \"\" {\n\t\tif p.ToNamespace == \"\" {\n\t\t\tp.ToNamespace = targetNamespace\n\t\t}\n\t\tif p.ToType == \"\" {\n\t\t\tp.ToType = resourceType\n\t\t}\n\n\t\ttoType, err := k8s.CanonicalResourceNameFromFriendlyName(p.ToType)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\ttoResource := pb.TopRoutesRequest_ToResource{\n\t\t\tToResource: &pb.Resource{\n\t\t\t\tNamespace: p.ToNamespace,\n\t\t\t\tType:      toType,\n\t\t\t\tName:      p.ToName,\n\t\t\t},\n\t\t}\n\t\ttopRoutesRequest.Outbound = &toResource\n\t} else {\n\t\ttopRoutesRequest.Outbound = &pb.TopRoutesRequest_None{\n\t\t\tNone: &pb.Empty{},\n\t\t}\n\t}\n\n\treturn topRoutesRequest, nil\n}\n\n// ValidateTimeWindow validates the given duration as a time window. If an empty\n// string is provided, the default time window is returned, otherwise, if\n// validation is successful, the original time window is returned.\nfunc ValidateTimeWindow(window string) (string, error) {\n\tif window == \"\" {\n\t\treturn defaultMetricTimeWindow, nil\n\t}\n\tw, err := time.ParseDuration(window)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif w < metricTimeWindowLowerBound {\n\t\treturn \"\", errors.New(\"metrics time window needs to be at least 15s\")\n\t}\n\n\treturn window, nil\n}\n\n// An authority can only receive traffic, not send it, so it can't be a --from\nfunc validateFromResourceType(resourceType string) (string, error) {\n\tname, err := k8s.CanonicalResourceNameFromFriendlyName(resourceType)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn name, nil\n}\n\n// K8sPodToPublicPod converts a Kubernetes Pod to a Public API Pod\nfunc K8sPodToPublicPod(pod corev1.Pod, ownerKind string, ownerName string) *pb.Pod {\n\tstatus := string(pod.Status.Phase)\n\tif pod.DeletionTimestamp != nil {\n\t\tstatus = \"Terminating\"\n\t}\n\n\tif pod.Status.Reason == \"Evicted\" {\n\t\tstatus = \"Evicted\"\n\t}\n\n\tcontrollerComponent := pod.Labels[k8s.ControllerComponentLabel]\n\tcontrollerNS := pod.Labels[k8s.ControllerNSLabel]\n\n\titem := &pb.Pod{\n\t\tName:                pod.Namespace + \"/\" + pod.Name,\n\t\tStatus:              status,\n\t\tPodIP:               pod.Status.PodIP,\n\t\tControllerNamespace: controllerNS,\n\t\tControlPlane:        controllerComponent != \"\",\n\t\tProxyReady:          k8s.GetProxyReady(pod),\n\t\tProxyVersion:        k8s.GetProxyVersion(pod),\n\t\tResourceVersion:     pod.ResourceVersion,\n\t}\n\n\tnamespacedOwnerName := pod.Namespace + \"/\" + ownerName\n\n\tswitch ownerKind {\n\tcase k8s.Deployment:\n\t\titem.Owner = &pb.Pod_Deployment{Deployment: namespacedOwnerName}\n\tcase k8s.DaemonSet:\n\t\titem.Owner = &pb.Pod_DaemonSet{DaemonSet: namespacedOwnerName}\n\tcase k8s.Job:\n\t\titem.Owner = &pb.Pod_Job{Job: namespacedOwnerName}\n\tcase k8s.ReplicaSet:\n\t\titem.Owner = &pb.Pod_ReplicaSet{ReplicaSet: namespacedOwnerName}\n\tcase k8s.ReplicationController:\n\t\titem.Owner = &pb.Pod_ReplicationController{ReplicationController: namespacedOwnerName}\n\tcase k8s.StatefulSet:\n\t\titem.Owner = &pb.Pod_StatefulSet{StatefulSet: namespacedOwnerName}\n\t}\n\n\treturn item\n}\n"
  },
  {
    "path": "viz/metrics-api/util/api_utils_test.go",
    "content": "package util\n\nimport (\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestBuildStatSummaryRequest(t *testing.T) {\n\tt.Run(\"Maps Kubernetes friendly names to canonical names\", func(t *testing.T) {\n\t\texpectations := map[string]string{\n\t\t\t\"deployments\": k8s.Deployment,\n\t\t\t\"deployment\":  k8s.Deployment,\n\t\t\t\"deploy\":      k8s.Deployment,\n\t\t\t\"pods\":        k8s.Pod,\n\t\t\t\"pod\":         k8s.Pod,\n\t\t\t\"po\":          k8s.Pod,\n\t\t}\n\n\t\tfor friendly, canonical := range expectations {\n\t\t\tstatSummaryRequest, err := BuildStatSummaryRequest(\n\t\t\t\tStatsSummaryRequestParams{\n\t\t\t\t\tStatsBaseRequestParams: StatsBaseRequestParams{\n\t\t\t\t\t\tResourceType: friendly,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error from BuildStatSummaryRequest [%s => %s]: %s\", friendly, canonical, err)\n\t\t\t}\n\t\t\tif statSummaryRequest.Selector.Resource.Type != canonical {\n\t\t\t\tt.Fatalf(\"Unexpected resource type from BuildStatSummaryRequest [%s => %s]: %s\", friendly, canonical, statSummaryRequest.Selector.Resource.Type)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Parses valid time windows\", func(t *testing.T) {\n\t\texpectations := []string{\n\t\t\t\"1m\",\n\t\t\t\"60s\",\n\t\t\t\"1m\",\n\t\t}\n\n\t\tfor _, timeWindow := range expectations {\n\t\t\tstatSummaryRequest, err := BuildStatSummaryRequest(\n\t\t\t\tStatsSummaryRequestParams{\n\t\t\t\t\tStatsBaseRequestParams: StatsBaseRequestParams{\n\t\t\t\t\t\tTimeWindow:   timeWindow,\n\t\t\t\t\t\tResourceType: k8s.Deployment,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error from BuildStatSummaryRequest [%s => %s]\", timeWindow, err)\n\t\t\t}\n\t\t\tif statSummaryRequest.TimeWindow != timeWindow {\n\t\t\t\tt.Fatalf(\"Unexpected TimeWindow from BuildStatSummaryRequest [%s => %s]\", timeWindow, statSummaryRequest.TimeWindow)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Rejects invalid time windows\", func(t *testing.T) {\n\t\texpectations := map[string]string{\n\t\t\t\"1\": \"time: missing unit in duration \\\"1\\\"\",\n\t\t\t\"s\": \"time: invalid duration \\\"s\\\"\",\n\t\t}\n\n\t\tfor timeWindow, msg := range expectations {\n\t\t\t_, err := BuildStatSummaryRequest(\n\t\t\t\tStatsSummaryRequestParams{\n\t\t\t\t\tStatsBaseRequestParams: StatsBaseRequestParams{\n\t\t\t\t\t\tTimeWindow: timeWindow,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatalf(\"BuildStatSummaryRequest(%s) unexpectedly succeeded, should have returned %s\", timeWindow, msg)\n\t\t\t}\n\t\t\tif err.Error() != msg {\n\t\t\t\tt.Fatalf(\"BuildStatSummaryRequest(%s) should have returned: %s but got unexpected message: %s\", timeWindow, msg, err)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Rejects invalid Kubernetes resource types\", func(t *testing.T) {\n\t\texpectations := map[string]string{\n\t\t\t\"foo\": \"cannot find Kubernetes canonical name from friendly name [foo]\",\n\t\t\t\"\":    \"cannot find Kubernetes canonical name from friendly name []\",\n\t\t}\n\n\t\tfor input, msg := range expectations {\n\t\t\t_, err := BuildStatSummaryRequest(\n\t\t\t\tStatsSummaryRequestParams{\n\t\t\t\t\tStatsBaseRequestParams: StatsBaseRequestParams{\n\t\t\t\t\t\tResourceType: input,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatalf(\"BuildStatSummaryRequest(%s) unexpectedly succeeded, should have returned %s\", input, msg)\n\t\t\t}\n\t\t\tif err.Error() != msg {\n\t\t\t\tt.Fatalf(\"BuildStatSummaryRequest(%s) should have returned: %s but got unexpected message: %s\", input, msg, err)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestBuildTopRoutesRequest(t *testing.T) {\n\tt.Run(\"Parses valid time windows\", func(t *testing.T) {\n\t\texpectations := []string{\n\t\t\t\"1m\",\n\t\t\t\"60s\",\n\t\t\t\"1m\",\n\t\t}\n\n\t\tfor _, timeWindow := range expectations {\n\t\t\ttopRoutesRequest, err := BuildTopRoutesRequest(\n\t\t\t\tTopRoutesRequestParams{\n\t\t\t\t\tStatsBaseRequestParams: StatsBaseRequestParams{\n\t\t\t\t\t\tTimeWindow:   timeWindow,\n\t\t\t\t\t\tResourceType: k8s.Deployment,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error from BuildTopRoutesRequest [%s => %s]\", timeWindow, err)\n\t\t\t}\n\t\t\tif topRoutesRequest.TimeWindow != timeWindow {\n\t\t\t\tt.Fatalf(\"Unexpected TimeWindow from BuildTopRoutesRequest [%s => %s]\", timeWindow, topRoutesRequest.TimeWindow)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Rejects invalid time windows\", func(t *testing.T) {\n\t\texpectations := map[string]string{\n\t\t\t\"1\": \"time: missing unit in duration \\\"1\\\"\",\n\t\t\t\"s\": \"time: invalid duration \\\"s\\\"\",\n\t\t}\n\n\t\tfor timeWindow, msg := range expectations {\n\t\t\t_, err := BuildTopRoutesRequest(\n\t\t\t\tTopRoutesRequestParams{\n\t\t\t\t\tStatsBaseRequestParams: StatsBaseRequestParams{\n\t\t\t\t\t\tTimeWindow:   timeWindow,\n\t\t\t\t\t\tResourceType: k8s.Deployment,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatalf(\"BuildTopRoutesRequest(%s) unexpectedly succeeded, should have returned %s\", timeWindow, msg)\n\t\t\t}\n\t\t\tif err.Error() != msg {\n\t\t\t\tt.Fatalf(\"BuildTopRoutesRequest(%s) should have returned: %s but got unexpected message: %s\", timeWindow, msg, err)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestK8sPodToPublicPod(t *testing.T) {\n\ttype podExp struct {\n\t\tk8sPod    corev1.Pod\n\t\townerKind string\n\t\townerName string\n\t\tpublicPod *pb.Pod\n\t}\n\n\tt.Run(\"Returns expected pods\", func(t *testing.T) {\n\t\texpectations := []podExp{\n\t\t\t{\n\t\t\t\tk8sPod: corev1.Pod{},\n\t\t\t\tpublicPod: &pb.Pod{\n\t\t\t\t\tName: \"/\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tk8sPod: corev1.Pod{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tNamespace:       \"ns\",\n\t\t\t\t\t\tName:            \"name\",\n\t\t\t\t\t\tResourceVersion: \"resource-version\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\tk8s.ControllerComponentLabel: \"controller-component\",\n\t\t\t\t\t\t\tk8s.ControllerNSLabel:        \"controller-ns\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tSpec: corev1.PodSpec{\n\t\t\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\t\tImage: \"linkerd-proxy:test-version\",\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\tStatus: corev1.PodStatus{\n\t\t\t\t\t\tPodIP: \"pod-ip\",\n\t\t\t\t\t\tPhase: \"status\",\n\t\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\t\tReady: true,\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\townerKind: k8s.Deployment,\n\t\t\t\townerName: \"owner-name\",\n\t\t\t\tpublicPod: &pb.Pod{\n\t\t\t\t\tName:                \"ns/name\",\n\t\t\t\t\tOwner:               &pb.Pod_Deployment{Deployment: \"ns/owner-name\"},\n\t\t\t\t\tResourceVersion:     \"resource-version\",\n\t\t\t\t\tControlPlane:        true,\n\t\t\t\t\tControllerNamespace: \"controller-ns\",\n\t\t\t\t\tStatus:              \"status\",\n\t\t\t\t\tProxyReady:          true,\n\t\t\t\t\tProxyVersion:        \"test-version\",\n\t\t\t\t\tPodIP:               \"pod-ip\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tk8sPod: corev1.Pod{\n\t\t\t\t\tStatus: corev1.PodStatus{\n\t\t\t\t\t\tPhase:  \"Failed\",\n\t\t\t\t\t\tReason: \"Evicted\",\n\t\t\t\t\t\tContainerStatuses: []corev1.ContainerStatus{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\t\tReady: true,\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\townerName: \"owner-name\",\n\t\t\t\tpublicPod: &pb.Pod{\n\t\t\t\t\tName:       \"/\",\n\t\t\t\t\tStatus:     \"Evicted\",\n\t\t\t\t\tProxyReady: true,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tfor _, exp := range expectations {\n\t\t\tres := K8sPodToPublicPod(exp.k8sPod, exp.ownerKind, exp.ownerName)\n\t\t\tif diff := deep.Equal(exp.publicPod, res); diff != nil {\n\t\t\t\tt.Errorf(\"%v\", diff)\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "viz/pkg/api/api.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\tvizHealthCheck \"github.com/linkerd/linkerd2/viz/pkg/healthcheck\"\n\tpromApi \"github.com/prometheus/client_golang/api\"\n\tpromv1 \"github.com/prometheus/client_golang/api/prometheus/v1\"\n)\n\n// CheckClientOrExit builds a new Viz API client and executes default status\n// checks to determine if the client can successfully perform cli commands. If the\n// checks fail, then CLI will print an error and exit.\nfunc CheckClientOrExit(hcOptions vizHealthCheck.VizOptions) pb.ApiClient {\n\thcOptions.RetryDeadline = time.Time{}\n\treturn CheckClientOrRetryOrExit(hcOptions, false)\n}\n\n// CheckClientOrRetryOrExit builds a new Viz API client and executes status\n// checks to determine if the client can successfully connect to the API. If the\n// checks fail, then CLI will print an error and exit. If the hcOptions.retryDeadline\n// param is specified, then the CLI will print a message to stderr and retry.\nfunc CheckClientOrRetryOrExit(hcOptions vizHealthCheck.VizOptions, apiChecks bool) pb.ApiClient {\n\tchecks := []healthcheck.CategoryID{\n\t\thealthcheck.KubernetesAPIChecks,\n\t}\n\n\tif apiChecks {\n\t\tchecks = append(checks, healthcheck.LinkerdControlPlaneExistenceChecks)\n\t}\n\n\thc := vizHealthCheck.NewHealthChecker(checks, &hcOptions)\n\n\thc.AppendCategories(hc.VizCategory(false))\n\n\thc.RunChecks(exitOnError)\n\treturn hc.VizAPIClient()\n}\n\nfunc exitOnError(result *healthcheck.CheckResult) {\n\tif result.Retry {\n\t\tfmt.Fprintln(os.Stderr, \"Waiting for linkerd-viz extension to become available\")\n\t\treturn\n\t}\n\n\tif result.Err != nil && !result.Warning {\n\t\tvar msg string\n\t\tswitch result.Category {\n\t\tcase healthcheck.KubernetesAPIChecks:\n\t\t\tmsg = \"Cannot connect to Kubernetes\"\n\t\tcase vizHealthCheck.LinkerdVizExtensionCheck:\n\t\t\tmsg = \"Cannot connect to Linkerd Viz\"\n\t\t}\n\t\tfmt.Fprintf(os.Stderr, \"%s: %s\\n\", msg, result.Err)\n\n\t\tcheckCmd := \"linkerd viz check\"\n\t\tfmt.Fprintf(os.Stderr, \"Validate the install with: %s\\n\", checkCmd)\n\n\t\tos.Exit(1)\n\t}\n}\n\nfunc NewPrometheusClient(ctx context.Context, hcOptions vizHealthCheck.VizOptions, addr string) (promv1.API, error) {\n\tif addr == \"\" {\n\t\tchecks := []healthcheck.CategoryID{\n\t\t\thealthcheck.KubernetesAPIChecks,\n\t\t}\n\t\thc := vizHealthCheck.NewHealthChecker(checks, &hcOptions)\n\t\thc.AppendCategories(hc.VizCategory(false))\n\t\thc.RunChecks(exitOnError)\n\n\t\tif hc.ExternalPrometheusURL() == \"\" {\n\t\t\tportforward, err := k8s.NewPortForward(\n\t\t\t\tctx,\n\t\t\t\thc.KubeAPIClient(),\n\t\t\t\thc.VizNamespace(),\n\t\t\t\t\"prometheus\",\n\t\t\t\t\"localhost\",\n\t\t\t\t0,\n\t\t\t\t9090,\n\t\t\t\tfalse,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\taddr = fmt.Sprintf(\"http://%s\", portforward.AddressAndPort())\n\n\t\t\tif err = portforward.Init(); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t} else {\n\t\t\taddr = hc.ExternalPrometheusURL()\n\t\t}\n\t}\n\n\tpromClient, err := promApi.NewClient(promApi.Config{Address: addr})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn promv1.NewAPI(promClient), nil\n}\n"
  },
  {
    "path": "viz/pkg/healthcheck/healthcheck.go",
    "content": "package healthcheck\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/tls\"\n\t\"github.com/linkerd/linkerd2/pkg/version\"\n\t\"github.com/linkerd/linkerd2/viz/metrics-api/client\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/labels\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tapiregistrationv1client \"k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1\"\n)\n\nconst (\n\t// VizExtensionName is the name of the viz extension\n\tVizExtensionName = \"viz\"\n\n\t// LinkerdVizExtensionCheck adds checks related to the Linkerd Viz extension\n\tLinkerdVizExtensionCheck healthcheck.CategoryID = \"linkerd-viz\"\n\n\t// LinkerdVizExtensionDataPlaneCheck adds checks related to dataplane for the linkerd-viz extension\n\tLinkerdVizExtensionDataPlaneCheck healthcheck.CategoryID = \"linkerd-viz-data-plane\"\n\n\ttapTLSSecretName    = \"tap-k8s-tls\"\n\ttapOldTLSSecretName = \"linkerd-tap-tls\"\n\n\t// linkerdTapAPIServiceName is the name of the tap api service\n\t// This key is passed to checkApiService method to check whether\n\t// the api service is available or not\n\tlinkerdTapAPIServiceName = \"v1alpha1.tap.linkerd.io\"\n)\n\ntype VizOptions struct {\n\t*healthcheck.Options\n\tVizNamespaceOverride string\n}\n\n// HealthChecker wraps Linkerd's main healthchecker, adding extra fields for Viz\ntype HealthChecker struct {\n\t*healthcheck.HealthChecker\n\tvizAPIClient          pb.ApiClient\n\tvizNamespace          string\n\texternalPrometheusURL string\n\tvizNamespaceOverride  string\n}\n\n// NewHealthChecker returns an initialized HealthChecker for Viz\n// The parentCheckIDs are the category IDs of the linkerd core checks that\n// are to be ran together with this instance\n// The returned instance does not contain any of the viz Categories and\n// to be explicitly added by using hc.AppendCategories\nfunc NewHealthChecker(parentCheckIDs []healthcheck.CategoryID, options *VizOptions) *HealthChecker {\n\tparentHC := healthcheck.NewHealthChecker(parentCheckIDs, options.Options)\n\treturn &HealthChecker{\n\t\tHealthChecker:        parentHC,\n\t\tvizNamespaceOverride: options.VizNamespaceOverride,\n\t}\n}\n\n// VizAPIClient returns a fully configured Viz API client\nfunc (hc *HealthChecker) VizAPIClient() pb.ApiClient {\n\treturn hc.vizAPIClient\n}\n\n// ExternalPrometheusURL returns URL of the external prometheus if one is\n// configured. Otherwise, it returns an empty string.\nfunc (hc *HealthChecker) ExternalPrometheusURL() string {\n\treturn hc.externalPrometheusURL\n}\n\n// VizNamespace returns the namespace where the viz extension is installed.\nfunc (hc *HealthChecker) VizNamespace() string {\n\treturn hc.vizNamespace\n}\n\n// RunChecks implements the healthcheck.Runner interface\nfunc (hc *HealthChecker) RunChecks(observer healthcheck.CheckObserver) (bool, bool) {\n\treturn hc.HealthChecker.RunChecks(observer)\n}\n\n// VizCategory returns a healthcheck.Category containing checkers\n// to verify the health of viz components\n// fullCheck parameter will decide to run a full or a smaller set of healthchecks.\nfunc (hc *HealthChecker) VizCategory(fullCheck bool) *healthcheck.Category {\n\tvizSelector := fmt.Sprintf(\"%s=%s\", k8s.LinkerdExtensionLabel, VizExtensionName)\n\n\tchecks := []healthcheck.Checker{\n\t\t*healthcheck.NewChecker(\"linkerd-viz Namespace exists\").\n\t\t\tWithHintAnchor(\"l5d-viz-ns-exists\").\n\t\t\tFatal().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\tif hc.vizNamespaceOverride == \"\" {\n\t\t\t\t\tvizNs, err := hc.KubeAPIClient().GetNamespaceWithExtensionLabel(ctx, \"viz\")\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\thc.vizNamespace = vizNs.Name\n\t\t\t\t\thc.externalPrometheusURL = vizNs.Annotations[labels.VizExternalPrometheus]\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tvizNs, err := hc.KubeAPIClient().GetNamespace(ctx, hc.vizNamespaceOverride)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tif vizNs.Labels[k8s.LinkerdExtensionLabel] != \"viz\" {\n\t\t\t\t\treturn errors.New(\"This is not a linkerd-viz namespace\")\n\t\t\t\t}\n\n\t\t\t\thc.vizNamespace = vizNs.Name\n\t\t\t\thc.externalPrometheusURL = vizNs.Annotations[labels.VizExternalPrometheus]\n\t\t\t\treturn nil\n\n\t\t\t}),\n\t\t*healthcheck.NewChecker(\"can initialize the client\").\n\t\t\tWithHintAnchor(\"l5d-viz-existence-client\").\n\t\t\tFatal().\n\t\t\tWithRetryDeadline(hc.RetryDeadline).\n\t\t\tWithCheck(func(ctx context.Context) (err error) {\n\t\t\t\tif hc.APIAddr != \"\" {\n\t\t\t\t\thc.vizAPIClient, err = client.NewInternalClient(hc.APIAddr)\n\t\t\t\t} else {\n\t\t\t\t\thc.vizAPIClient, err = client.NewExternalClient(ctx, hc.vizNamespace, hc.KubeAPIClient())\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t})}\n\n\tif !fullCheck {\n\t\treturn healthcheck.NewCategory(LinkerdVizExtensionCheck, checks, true)\n\t}\n\n\tchecks = append(checks,\n\t\t*healthcheck.NewChecker(\"linkerd-viz ClusterRoles exist\").\n\t\t\tWithHintAnchor(\"l5d-viz-cr-exists\").\n\t\t\tFatal().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\treturn healthcheck.CheckClusterRoles(ctx, hc.KubeAPIClient(), true, []string{fmt.Sprintf(\"linkerd-%s-tap\", hc.vizNamespace), fmt.Sprintf(\"linkerd-%s-metrics-api\", hc.vizNamespace), fmt.Sprintf(\"linkerd-%s-tap-admin\", hc.vizNamespace), \"linkerd-tap-injector\"}, \"\")\n\t\t\t}),\n\t\t*healthcheck.NewChecker(\"linkerd-viz ClusterRoleBindings exist\").\n\t\t\tWithHintAnchor(\"l5d-viz-crb-exists\").\n\t\t\tFatal().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\treturn healthcheck.CheckClusterRoleBindings(ctx, hc.KubeAPIClient(), true, []string{fmt.Sprintf(\"linkerd-%s-tap\", hc.vizNamespace), fmt.Sprintf(\"linkerd-%s-metrics-api\", hc.vizNamespace), fmt.Sprintf(\"linkerd-%s-tap-auth-delegator\", hc.vizNamespace), \"linkerd-tap-injector\"}, \"\")\n\t\t\t}),\n\t\t*healthcheck.NewChecker(\"tap API server has valid cert\").\n\t\t\tWithHintAnchor(\"l5d-tap-cert-valid\").\n\t\t\tFatal().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\tanchors, err := fetchTapCaBundle(ctx, hc.KubeAPIClient())\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tcert, err := hc.FetchCredsFromSecret(ctx, hc.vizNamespace, tapTLSSecretName)\n\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\tcert, err = hc.FetchCredsFromOldSecret(ctx, hc.vizNamespace, tapOldTLSSecretName)\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tidentityName := fmt.Sprintf(\"tap.%s.svc\", hc.vizNamespace)\n\t\t\t\treturn hc.CheckCertAndAnchors(cert, anchors, identityName)\n\t\t\t}),\n\t\t*healthcheck.NewChecker(\"tap API server cert is valid for at least 60 days\").\n\t\t\tWithHintAnchor(\"l5d-tap-cert-not-expiring-soon\").\n\t\t\tWarning().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\tcert, err := hc.FetchCredsFromSecret(ctx, hc.vizNamespace, tapTLSSecretName)\n\t\t\t\tif kerrors.IsNotFound(err) {\n\t\t\t\t\tcert, err = hc.FetchCredsFromOldSecret(ctx, hc.vizNamespace, tapOldTLSSecretName)\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn hc.CheckCertAndAnchorsExpiringSoon(cert)\n\t\t\t}),\n\t\t*healthcheck.NewChecker(\"tap API service is running\").\n\t\t\tWithHintAnchor(\"l5d-tap-api\").\n\t\t\tWarning().\n\t\t\tWithRetryDeadline(hc.RetryDeadline).\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\treturn hc.CheckAPIService(ctx, linkerdTapAPIServiceName)\n\t\t\t}),\n\t\t*healthcheck.NewChecker(\"linkerd-viz pods are injected\").\n\t\t\tWithHintAnchor(\"l5d-viz-pods-injection\").\n\t\t\tWarning().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\tpods, err := hc.KubeAPIClient().GetPodsByNamespace(ctx, hc.vizNamespace)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn healthcheck.CheckIfDataPlanePodsExist(pods)\n\t\t\t}),\n\t\t*healthcheck.NewChecker(\"viz extension pods are running\").\n\t\t\tWithHintAnchor(\"l5d-viz-pods-running\").\n\t\t\tWarning().\n\t\t\tWithRetryDeadline(hc.RetryDeadline).\n\t\t\tSurfaceErrorOnRetry().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\tpodList, err := hc.KubeAPIClient().CoreV1().Pods(hc.vizNamespace).List(ctx, metav1.ListOptions{\n\t\t\t\t\tLabelSelector: vizSelector,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\t// Check for relevant pods to be present\n\t\t\t\terr = healthcheck.CheckForPods(podList.Items, []string{\"web\", \"tap\", \"metrics-api\", \"tap-injector\"})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\treturn healthcheck.CheckPodsRunning(podList.Items, hc.vizNamespace)\n\t\t\t}),\n\t\t*healthcheck.NewChecker(\"viz extension proxies are healthy\").\n\t\t\tWithHintAnchor(\"l5d-viz-proxy-healthy\").\n\t\t\tWarning().\n\t\t\tWithCheck(func(ctx context.Context) (err error) {\n\t\t\t\treturn hc.CheckProxyHealth(ctx, hc.ControlPlaneNamespace, hc.vizNamespace)\n\t\t\t}),\n\t\t*healthcheck.NewChecker(\"viz extension proxies are up-to-date\").\n\t\t\tWithHintAnchor(\"l5d-viz-proxy-cp-version\").\n\t\t\tWarning().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\tvar err error\n\t\t\t\tif hc.VersionOverride != \"\" {\n\t\t\t\t\thc.LatestVersions, err = version.NewChannels(hc.VersionOverride)\n\t\t\t\t} else {\n\t\t\t\t\tuuid := \"unknown\"\n\t\t\t\t\tif hc.UUID() != \"\" {\n\t\t\t\t\t\tuuid = hc.UUID()\n\t\t\t\t\t}\n\t\t\t\t\thc.LatestVersions, err = version.GetLatestVersions(ctx, uuid, \"cli\")\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tpods, err := hc.KubeAPIClient().GetPodsByNamespace(ctx, hc.vizNamespace)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\treturn hc.CheckProxyVersionsUpToDate(pods)\n\t\t\t}),\n\t\t*healthcheck.NewChecker(\"viz extension proxies and cli versions match\").\n\t\t\tWithHintAnchor(\"l5d-viz-proxy-cli-version\").\n\t\t\tWarning().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\tpods, err := hc.KubeAPIClient().GetPodsByNamespace(ctx, hc.vizNamespace)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\treturn healthcheck.CheckIfProxyVersionsMatchWithCLI(pods)\n\t\t\t}),\n\t\t*healthcheck.NewChecker(\"prometheus is installed and configured correctly\").\n\t\t\tWithHintAnchor(\"l5d-viz-prometheus\").\n\t\t\tWarning().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\tif hc.externalPrometheusURL != \"\" {\n\t\t\t\t\treturn healthcheck.SkipError{Reason: \"prometheus is disabled\"}\n\t\t\t\t}\n\n\t\t\t\t// Check for ClusterRoles\n\t\t\t\terr := healthcheck.CheckClusterRoles(ctx, hc.KubeAPIClient(), true, []string{fmt.Sprintf(\"linkerd-%s-prometheus\", hc.vizNamespace)}, \"\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\t// Check for ClusterRoleBindings\n\t\t\t\terr = healthcheck.CheckClusterRoleBindings(ctx, hc.KubeAPIClient(), true, []string{fmt.Sprintf(\"linkerd-%s-prometheus\", hc.vizNamespace)}, \"\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\t// Check for ConfigMap\n\t\t\t\terr = healthcheck.CheckConfigMaps(ctx, hc.KubeAPIClient(), hc.vizNamespace, true, []string{\"prometheus-config\"}, \"\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\t// Check for relevant pods to be present\n\t\t\t\tpodList, err := hc.KubeAPIClient().CoreV1().Pods(hc.vizNamespace).List(ctx, metav1.ListOptions{\n\t\t\t\t\tLabelSelector: vizSelector,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\treturn healthcheck.CheckForPods(podList.Items, []string{\"prometheus\"})\n\t\t\t}),\n\t\t*healthcheck.NewChecker(\"viz extension self-check\").\n\t\t\tWithHintAnchor(\"l5d-viz-metrics-api\").\n\t\t\tFatal().\n\t\t\t// to avoid confusing users with a prometheus readiness error, we only show\n\t\t\t// \"waiting for check to complete\" while things converge. If after the timeout\n\t\t\t// it still hasn't converged, we show the real error (a 503 usually).\n\t\t\tWithRetryDeadline(hc.RetryDeadline).\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\tresults, err := hc.vizAPIClient.SelfCheck(ctx, &pb.SelfCheckRequest{})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tif len(results.GetResults()) == 0 {\n\t\t\t\t\treturn errors.New(\"No results returned\")\n\t\t\t\t}\n\n\t\t\t\terrs := []string{}\n\t\t\t\tfor _, res := range results.GetResults() {\n\t\t\t\t\tif res.GetStatus() != pb.CheckStatus_OK {\n\t\t\t\t\t\terrs = append(errs, res.GetFriendlyMessageToUser())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(errs) == 0 {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\terrsStr := strings.Join(errs, \"\\n    \")\n\t\t\t\treturn errors.New(errsStr)\n\t\t\t}),\n\t)\n\n\treturn healthcheck.NewCategory(LinkerdVizExtensionCheck, checks, true)\n}\n\n// VizDataPlaneCategory returns a healthcheck.Category containing checkers\n// to verify the data-plane metrics in prometheus and the tap injection\nfunc (hc *HealthChecker) VizDataPlaneCategory() *healthcheck.Category {\n\n\treturn healthcheck.NewCategory(LinkerdVizExtensionDataPlaneCheck, []healthcheck.Checker{\n\t\t*healthcheck.NewChecker(\"data plane namespace exists\").\n\t\t\tWithHintAnchor(\"l5d-data-plane-exists\").\n\t\t\tFatal().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\tif hc.DataPlaneNamespace == \"\" {\n\t\t\t\t\t// when checking proxies in all namespaces, this check is a no-op\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\treturn hc.CheckNamespace(ctx, hc.DataPlaneNamespace, true)\n\t\t\t}),\n\t\t*healthcheck.NewChecker(\"prometheus is authorized to scrape data plane pods\").\n\t\t\tWithHintAnchor(\"l5d-viz-data-plane-prom-authz\").\n\t\t\tWarning().\n\t\t\tWithCheck(func(ctx context.Context) error {\n\t\t\t\treturn hc.checkPromAuthorized(ctx)\n\t\t\t}),\n\t\t*healthcheck.NewChecker(\"data plane proxy metrics are present in Prometheus\").\n\t\t\tWithHintAnchor(\"l5d-data-plane-prom\").\n\t\t\tWarning().\n\t\t\tWithRetryDeadline(hc.RetryDeadline).\n\t\t\tWithCheck(func(ctx context.Context) (err error) {\n\t\t\t\tpods, err := hc.getDataPlanePodsFromVizAPI(ctx)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\treturn validateDataPlanePodReporting(pods)\n\t\t\t}),\n\t}, true)\n}\n\nfunc (hc *HealthChecker) getDataPlanePodsFromVizAPI(ctx context.Context) ([]*pb.Pod, error) {\n\n\treq := &pb.ListPodsRequest{}\n\tif hc.DataPlaneNamespace != \"\" {\n\t\treq.Selector = &pb.ResourceSelection{\n\t\t\tResource: &pb.Resource{\n\t\t\t\tNamespace: hc.DataPlaneNamespace,\n\t\t\t},\n\t\t}\n\t}\n\n\tresp, err := hc.VizAPIClient().ListPods(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpods := make([]*pb.Pod, 0)\n\tfor _, pod := range resp.GetPods() {\n\t\tif pod.ControllerNamespace == hc.ControlPlaneNamespace {\n\t\t\tpods = append(pods, pod)\n\t\t}\n\t}\n\n\treturn pods, nil\n}\n\nfunc validateDataPlanePodReporting(pods []*pb.Pod) error {\n\tnotInPrometheus := []string{}\n\n\tfor _, p := range pods {\n\t\t// the `Added` field indicates the pod was found in Prometheus\n\t\tif !p.Added {\n\t\t\tnotInPrometheus = append(notInPrometheus, p.Name)\n\t\t}\n\t}\n\n\terrMsg := \"\"\n\tif len(notInPrometheus) > 0 {\n\t\terrMsg = fmt.Sprintf(\"Data plane metrics not found for %s.\", strings.Join(notInPrometheus, \", \"))\n\t}\n\n\tif errMsg != \"\" {\n\t\treturn errors.New(errMsg)\n\t}\n\n\treturn nil\n}\n\nfunc fetchTapCaBundle(ctx context.Context, kubeAPI *k8s.KubernetesAPI) ([]*x509.Certificate, error) {\n\tapiServiceClient, err := apiregistrationv1client.NewForConfig(kubeAPI.Config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tapiService, err := apiServiceClient.APIServices().Get(ctx, linkerdTapAPIServiceName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcaBundle, err := tls.DecodePEMCertificates(string(apiService.Spec.CABundle))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn caBundle, nil\n}\n\nfunc (hc *HealthChecker) checkPromAuthorized(ctx context.Context) error {\n\tapi := hc.KubeAPIClient()\n\tnses, err := hc.getDataPlaneNamespaces(ctx, api)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tunauthorizedPods := []string{}\n\tfor _, ns := range nses {\n\t\t// first, let's see if this namespace has an `allow-scrapes` policy. if\n\t\t// it does, skip checking its pods --- prometheus will be able to scrape\n\t\t// them even if they are default-deny.\n\t\t_, err := api.L5dCrdClient.PolicyV1alpha1().AuthorizationPolicies(ns.GetName()).Get(ctx, \"prometheus-scrape\", metav1.GetOptions{})\n\t\tif kerrors.IsNotFound(err) {\n\t\t\t// no prometheus-scrape policy exists in this namespace\n\t\t} else if err != nil {\n\t\t\t// something went wrong while talking to the kube API\n\t\t\treturn fmt.Errorf(\"could not get AuthorizationPolicies in the %s namespace: %w\", ns.GetName(), err)\n\t\t} else {\n\t\t\t// allow-scrapes policy exists in this namespace, don't check the\n\t\t\t// pods.\n\t\t\tcontinue\n\t\t}\n\n\t\tpods, err := hc.KubeAPIClient().GetPodsByNamespace(ctx, ns.GetName())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not list pods in the %s namespace: %w\", ns.GetName(), err)\n\t\t}\n\n\t\tvar nsPrefix string\n\t\tif ns.GetName() == hc.DataPlaneNamespace {\n\t\t\t// if we're only checking one namespace, don't bother appending the\n\t\t\t// namespace name to the pod's name in the error output\n\t\t\tnsPrefix = \"\"\n\t\t} else {\n\t\t\t// otherwise, include the namespace name as well as the pod's\n\t\t\t// name, since we are checking all namespaces.\n\t\t\tnsPrefix = ns.GetName() + \"/\"\n\t\t}\n\n\t\tfor _, pod := range pods {\n\t\t\t// rather than checking the value of the pod's\n\t\t\t// `config.linkerd.io/default-inbound-policy` annotation, check the\n\t\t\t// proxy container's actual env variable. if the cluster-wide\n\t\t\t// default inbound policy is `deny`, there won't be an override\n\t\t\t// annotation, but the proxy-injector will have set the env variable\n\t\t\t// directly.\n\t\t\tcontainers := append(pod.Spec.InitContainers, pod.Spec.Containers...)\n\t\t\tfor _, c := range containers {\n\t\t\t\tif c.Name == k8s.ProxyContainerName {\n\t\t\t\t\tfor _, env := range c.Env {\n\t\t\t\t\t\tif env.Name == \"LINKERD2_PROXY_INBOUND_DEFAULT_POLICY\" && env.Value == \"deny\" {\n\t\t\t\t\t\t\tunauthorizedPods = append(unauthorizedPods, fmt.Sprintf(\"\\t* %s%s\", nsPrefix, pod.Name))\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\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(unauthorizedPods) > 0 {\n\t\tpodList := strings.Join(unauthorizedPods, \"\\n\")\n\t\treturn fmt.Errorf(\"prometheus may not be authorized to scrape the following pods:\\n%s\\n\"+\n\t\t\t\"    consider running `linkerd viz allow-scrapes` to authorize prometheus scrapes\",\n\t\t\tpodList)\n\t}\n\n\treturn nil\n}\n\nfunc (hc *HealthChecker) getDataPlaneNamespaces(ctx context.Context, api *k8s.KubernetesAPI) ([]corev1.Namespace, error) {\n\tif hc.DataPlaneNamespace != \"\" {\n\t\tns, err := api.GetNamespace(ctx, hc.DataPlaneNamespace)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []corev1.Namespace{*ns}, nil\n\t}\n\n\tnses, err := api.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn nses.Items, nil\n}\n"
  },
  {
    "path": "viz/pkg/healthcheck/healthcheck_test.go",
    "content": "package healthcheck\n\nimport (\n\t\"testing\"\n\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestHealthChecker(t *testing.T) {\n\tt.Run(\"Does not return an error if the pod is Evicted\", func(t *testing.T) {\n\t\tpods := []v1.Pod{\n\t\t\t{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"pod-1\"},\n\t\t\t\tStatus: v1.PodStatus{\n\t\t\t\t\tPhase: \"Evicted\",\n\t\t\t\t\tContainerStatuses: []v1.ContainerStatus{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  k8s.ProxyContainerName,\n\t\t\t\t\t\t\tReady: true,\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\terr := healthcheck.CheckPodsRunning(pods, \"\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Expected success, got %s\", err)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "viz/pkg/jsonpath/jsonpath.go",
    "content": "package jsonpath\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"k8s.io/client-go/util/jsonpath\"\n)\n\nvar jsonRegexp = regexp.MustCompile(`^\\{\\.?([^{}]+)\\}$|^\\.?([^{}]+)$`)\n\nfunc GetJsonPathFlagVal(flagVal string) (string, error) {\n\tval := strings.Split(flagVal, \"=\")\n\tif len(val) != 2 || len(val) == 2 && val[1] == \"\" {\n\t\treturn \"\", errors.New(\"{\\\"error jsonpath\\\": \\\"jsonpath filter not found\\\"}\")\n\t}\n\n\treturn val[1], nil\n\n}\n\n// GetFormatedJSONPathExpression get jsonpath filter from flag and attempts to be flexible with JSONPath expressions, it accepts:\n//   - metadata.name (no leading '.' or curly braces '{...}'\n//   - {metadata.name} (no leading '.')\n//   - .metadata.name (no curly braces '{...}')\n//   - {.metadata.name} (complete expression)\n//\n// And transforms them all into a valid jsonpath expression:\n//\n//\t{.metadata.name}\nfunc GetFormatedJSONPathExpression(pathExpression string) (string, error) {\n\tif len(pathExpression) == 0 {\n\t\treturn pathExpression, nil\n\t}\n\tsubmatches := jsonRegexp.FindStringSubmatch(pathExpression)\n\tif submatches == nil {\n\t\treturn \"\", errors.New(\"{\\\"error jsonpath\\\": \\\"unexpected path string, expected a 'name1.name2' or '.name1.name2' or '{name1.name2}' or '{.name1.name2}'\\\"}\")\n\t}\n\tif len(submatches) != 3 {\n\t\treturn \"\", fmt.Errorf(\"{\\\"error jsonpath\\\":\\\"unexpected submatch list: %v\\\"\", submatches)\n\t}\n\tvar fieldSpec string\n\tif len(submatches[1]) != 0 {\n\t\tfieldSpec = submatches[1]\n\t} else {\n\t\tfieldSpec = submatches[2]\n\t}\n\treturn fmt.Sprintf(\"{.%s}\", fieldSpec), nil\n}\n\nfunc GetJsonFilteredByJPath(event interface{}, jsonPath string) ([]string, error) {\n\tfields, err := GetFormatedJSONPathExpression(jsonPath)\n\tif err != nil {\n\t\treturn []string{}, err\n\t}\n\n\tj := jsonpath.New(\"EventParser\")\n\tif err := j.Parse(fields); err != nil {\n\t\treturn []string{}, fmt.Errorf(\"{\\\"error parsing jsonpath\\\":\\\" %s\\\" }\", err.Error())\n\t}\n\n\tresults, err := j.FindResults(event)\n\tif err != nil {\n\t\treturn []string{}, fmt.Errorf(\"{\\\"error jsonpath\\\":\\\" %s\\\" }\", err.Error())\n\t}\n\n\tfilteredEvent := []string{}\n\tif len(results) == 0 || len(results[0]) == 0 {\n\t\treturn filteredEvent, errors.New(\"{\\\"error filtering JSON\\\": \\\"couldn't find any results matching with jsonpath filter\\\"}\")\n\t}\n\n\tfor _, result := range results {\n\t\tfor _, match := range result {\n\t\t\te, err := json.MarshalIndent(match.Interface(), \"\", \"  \")\n\t\t\tif err != nil {\n\t\t\t\treturn []string{}, fmt.Errorf(\"{\\\"error marshalling JSON\\\": \\\"%s\\\"}\", err.Error())\n\t\t\t}\n\t\t\tfilteredEvent = append(filteredEvent, string(e))\n\t\t}\n\t}\n\n\treturn filteredEvent, nil\n}\n"
  },
  {
    "path": "viz/pkg/labels/labels.go",
    "content": "package labels\n\nimport (\n\t\"strconv\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nconst (\n\t// VizAnnotationsPrefix is the prefix of all viz-related annotations\n\tVizAnnotationsPrefix = \"viz.linkerd.io\"\n\n\t// VizTapEnabled is set by the tap-injector component when tap has been\n\t// enabled on a pod.\n\tVizTapEnabled = VizAnnotationsPrefix + \"/tap-enabled\"\n\n\t// VizTapDisabled can be used to disable tap on the injected proxy.\n\tVizTapDisabled = VizAnnotationsPrefix + \"/disable-tap\"\n\n\t// VizExternalPrometheus is only set on the namespace by the install\n\t// when a external prometheus is being used\n\tVizExternalPrometheus = VizAnnotationsPrefix + \"/external-prometheus\"\n)\n\n// IsTapEnabled returns true if a pod has an annotation indicating that tap\n// is enabled.\nfunc IsTapEnabled(pod *corev1.Pod) bool {\n\tvalStr := pod.GetAnnotations()[VizTapEnabled]\n\tif valStr != \"\" {\n\t\tvalBool, err := strconv.ParseBool(valStr)\n\t\tif err == nil && valBool {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// IsTapDisabled returns true if a namespace or pod has an annotation for\n// explicitly disabling tap\nfunc IsTapDisabled(obj metav1.Object) bool {\n\tvalStr := obj.GetAnnotations()[VizTapDisabled]\n\tif valStr != \"\" {\n\t\tvalBool, err := strconv.ParseBool(valStr)\n\t\tif err == nil && valBool {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "viz/pkg/prometheus/prometheus.go",
    "content": "package prometheus\n\nimport (\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/prometheus/common/model\"\n)\n\nconst (\n\tNamespaceLabel         = model.LabelName(\"namespace\")\n\tDstNamespaceLabel      = model.LabelName(\"dst_namespace\")\n\tGatewayNameLabel       = model.LabelName(\"gateway_name\")\n\tGatewayNamespaceLabel  = model.LabelName(\"gateway_namespace\")\n\tRemoteClusterNameLabel = model.LabelName(\"target_cluster_name\")\n\tAuthorityLabel         = model.LabelName(\"authority\")\n\tServerKindLabel        = model.LabelName(\"srv_kind\")\n\tServerNameLabel        = model.LabelName(\"srv_name\")\n\tAuthorizationKindLabel = model.LabelName(\"authz_kind\")\n\tAuthorizationNameLabel = model.LabelName(\"authz_name\")\n\tRouteKindLabel         = model.LabelName(\"route_kind\")\n\tRouteNameLabel         = model.LabelName(\"route_name\")\n)\n\n// add filtering by resource type\n// note that metricToKey assumes the label ordering (namespace, name)\nfunc GroupByLabelNames(resource *pb.Resource) model.LabelNames {\n\tnames := model.LabelNames{NamespaceLabel}\n\n\tif resource.Type != k8s.Namespace {\n\t\tnames = append(names, ResourceType(resource))\n\t}\n\treturn names\n}\n\n// query a named resource\nfunc QueryLabels(resource *pb.Resource) model.LabelSet {\n\tset := model.LabelSet{}\n\tif resource != nil {\n\t\tif resource.Name != \"\" {\n\t\t\tif resource.GetType() == k8s.Server {\n\t\t\t\tset[ServerKindLabel] = model.LabelValue(\"server\")\n\t\t\t\tset[ServerNameLabel] = model.LabelValue(resource.GetName())\n\t\t\t} else if resource.GetType() == k8s.ServerAuthorization {\n\t\t\t\tset[AuthorizationKindLabel] = model.LabelValue(\"serverauthorization\")\n\t\t\t\tset[AuthorizationNameLabel] = model.LabelValue(resource.GetName())\n\t\t\t} else if resource.GetType() == k8s.AuthorizationPolicy {\n\t\t\t\tset[AuthorizationKindLabel] = model.LabelValue(\"authorizationpolicy\")\n\t\t\t\tset[AuthorizationNameLabel] = model.LabelValue(resource.GetName())\n\t\t\t} else if resource.GetType() == k8s.HTTPRoute {\n\t\t\t\tset[RouteNameLabel] = model.LabelValue(resource.GetName())\n\t\t\t} else if resource.GetType() != k8s.Service {\n\t\t\t\tset[ResourceType(resource)] = model.LabelValue(resource.Name)\n\t\t\t}\n\t\t}\n\t\tif shouldAddNamespaceLabel(resource) {\n\t\t\tset[NamespaceLabel] = model.LabelValue(resource.Namespace)\n\t\t}\n\t}\n\treturn set\n}\n\n// add filtering by resource type\n// note that metricToKey assumes the label ordering (namespace, name)\nfunc DstGroupByLabelNames(resource *pb.Resource) model.LabelNames {\n\tnames := model.LabelNames{DstNamespaceLabel}\n\tif resource.Type != k8s.Namespace {\n\t\tnames = append(names, \"dst_\"+ResourceType(resource))\n\t}\n\treturn names\n}\n\n// query a named resource\nfunc DstQueryLabels(resource *pb.Resource) model.LabelSet {\n\tset := model.LabelSet{}\n\tif resource.Name != \"\" {\n\t\tset[\"dst_\"+ResourceType(resource)] = model.LabelValue(resource.Name)\n\t\tif shouldAddNamespaceLabel(resource) {\n\t\t\tset[DstNamespaceLabel] = model.LabelValue(resource.Namespace)\n\t\t}\n\n\t}\n\n\treturn set\n}\n\nfunc ResourceType(resource *pb.Resource) model.LabelName {\n\tl5dLabel := k8s.KindToL5DLabel(resource.Type)\n\treturn model.LabelName(l5dLabel)\n}\n\n// determine if we should add \"namespace=<namespace>\" to a named query\nfunc shouldAddNamespaceLabel(resource *pb.Resource) bool {\n\treturn resource.Type != k8s.Namespace && resource.Namespace != \"\"\n}\n"
  },
  {
    "path": "viz/pkg/util/util.go",
    "content": "package util\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\tk8sErrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// ValidTargets specifies resource types allowed as a target:\n// - target resource on an inbound query\n// - target resource on an outbound 'to' query\n// - destination resource on an outbound 'from' query\nvar ValidTargets = []string{\n\tk8s.CronJob,\n\tk8s.DaemonSet,\n\tk8s.Deployment,\n\tk8s.Job,\n\tk8s.Namespace,\n\tk8s.Pod,\n\tk8s.ReplicaSet,\n\tk8s.ReplicationController,\n\tk8s.StatefulSet,\n}\n\n/*\n  Shared utilities for interacting with the controller APIs\n*/\n\n// GRPCError generates a gRPC error code, as defined in\n// google.golang.org/grpc/status.\n// If the error is nil or already a gRPC error, return the error.\n// If the error is of type k8s.io/apimachinery/pkg/apis/meta/v1#StatusReason,\n// attempt to map the reason to a gRPC error.\nfunc GRPCError(err error) error {\n\tif err != nil && status.Code(err) == codes.Unknown {\n\t\tcode := codes.Internal\n\n\t\tswitch k8sErrors.ReasonForError(err) {\n\t\tcase metav1.StatusReasonUnknown:\n\t\t\tcode = codes.Unknown\n\t\tcase metav1.StatusReasonUnauthorized, metav1.StatusReasonForbidden:\n\t\t\tcode = codes.PermissionDenied\n\t\tcase metav1.StatusReasonNotFound:\n\t\t\tcode = codes.NotFound\n\t\tcase metav1.StatusReasonAlreadyExists:\n\t\t\tcode = codes.AlreadyExists\n\t\tcase metav1.StatusReasonInvalid:\n\t\t\tcode = codes.InvalidArgument\n\t\tcase metav1.StatusReasonExpired:\n\t\t\tcode = codes.DeadlineExceeded\n\t\tcase metav1.StatusReasonServiceUnavailable:\n\t\t\tcode = codes.Unavailable\n\t\t}\n\n\t\terr = status.Error(code, err.Error())\n\t}\n\n\treturn err\n}\n\n// BuildResource parses input strings, typically from CLI flags, to build a\n// Resource object for use in the protobuf API.\n// It's the same as BuildResources but only admits one arg and only returns one resource\nfunc BuildResource(namespace, arg string) (*pb.Resource, error) {\n\tres, err := BuildResources(namespace, []string{arg})\n\tif err != nil {\n\t\treturn &pb.Resource{}, err\n\t}\n\n\treturn res[0], err\n}\n\n// BuildResources parses input strings, typically from CLI flags, to build a\n// slice of Resource objects for use in the protobuf API.\n// It's the same as BuildResource but it admits any number of args and returns multiple resources\nfunc BuildResources(namespace string, args []string) ([]*pb.Resource, error) {\n\tswitch len(args) {\n\tcase 0:\n\t\treturn nil, errors.New(\"No resource arguments provided\")\n\tcase 1:\n\t\treturn parseResources(namespace, \"\", args)\n\tdefault:\n\t\tif res, err := k8s.CanonicalResourceNameFromFriendlyName(args[0]); err == nil && res != k8s.All {\n\t\t\t// --namespace my-ns deploy foo1 foo2 ...\n\t\t\treturn parseResources(namespace, args[0], args[1:])\n\t\t}\n\n\t\treturn parseResources(namespace, \"\", args)\n\t}\n}\n\nfunc parseResources(namespace string, resType string, args []string) ([]*pb.Resource, error) {\n\tif err := validateResources(args); err != nil {\n\t\treturn nil, err\n\t}\n\tresources := make([]*pb.Resource, 0)\n\tfor _, arg := range args {\n\t\tres, err := parseResource(namespace, resType, arg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresources = append(resources, res)\n\t}\n\treturn resources, nil\n}\n\nfunc validateResources(args []string) error {\n\tset := make(map[string]bool)\n\tall := false\n\tfor _, arg := range args {\n\t\tset[arg] = true\n\t\tif arg == k8s.All {\n\t\t\tall = true\n\t\t}\n\t}\n\tif len(set) < len(args) {\n\t\treturn errors.New(\"cannot supply duplicate resources\")\n\t}\n\tif all && len(args) > 1 {\n\t\treturn errors.New(\"'all' can't be supplied alongside other resources\")\n\t}\n\treturn nil\n}\n\nfunc parseResource(namespace, resType string, arg string) (*pb.Resource, error) {\n\tif resType != \"\" {\n\t\treturn buildResource(namespace, resType, arg)\n\t}\n\telems := strings.Split(arg, \"/\")\n\tswitch len(elems) {\n\tcase 1:\n\t\t// --namespace my-ns deploy\n\t\treturn buildResource(namespace, elems[0], \"\")\n\tcase 2:\n\t\t// --namespace my-ns deploy/foo\n\t\treturn buildResource(namespace, elems[0], elems[1])\n\tdefault:\n\t\treturn &pb.Resource{}, errors.New(\"Invalid resource string: \" + arg)\n\t}\n}\n\nfunc buildResource(namespace string, resType string, name string) (*pb.Resource, error) {\n\tcanonicalType, err := k8s.CanonicalResourceNameFromFriendlyName(resType)\n\tif err != nil {\n\t\treturn &pb.Resource{}, err\n\t}\n\tif canonicalType == k8s.Namespace {\n\t\t// ignore --namespace flags if type is namespace\n\t\tnamespace = \"\"\n\t}\n\n\treturn &pb.Resource{\n\t\tNamespace: namespace,\n\t\tType:      canonicalType,\n\t\tName:      name,\n\t}, nil\n}\n\n// HTTPMethodToString returns the HTTP request method\n// from the HTTPMethod protobuf type\nfunc HTTPMethodToString(method *pb.HttpMethod) string {\n\t// Check Unregistered first as Registered (being an enum) defaults\n\t// to GET when empty\n\tif method.GetUnregistered() != \"\" {\n\t\treturn method.GetUnregistered()\n\t}\n\treturn method.GetRegistered().String()\n}\n"
  },
  {
    "path": "viz/pkg/util/util_test.go",
    "content": "package util\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tk8sError \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\nfunc TestGRPCError(t *testing.T) {\n\tt.Run(\"Maps errors to gRPC errors\", func(t *testing.T) {\n\t\texpectations := map[error]error{\n\t\t\tnil:                        nil,\n\t\t\terrors.New(\"normal error\"): errors.New(\"rpc error: code = Unknown desc = normal error\"),\n\t\t\tstatus.Error(codes.NotFound, \"grpc not found\"):                                              errors.New(\"rpc error: code = NotFound desc = grpc not found\"),\n\t\t\tk8sError.NewNotFound(schema.GroupResource{Group: \"foo\", Resource: \"bar\"}, \"http not found\"): errors.New(\"rpc error: code = NotFound desc = bar.foo \\\"http not found\\\" not found\"),\n\t\t\tk8sError.NewServiceUnavailable(\"unavailable\"):                                               errors.New(\"rpc error: code = Unavailable desc = unavailable\"),\n\t\t\tk8sError.NewGone(\"gone\"):                                                                    errors.New(\"rpc error: code = Internal desc = gone\"),\n\t\t}\n\n\t\tfor in, out := range expectations {\n\t\t\terr := GRPCError(in)\n\t\t\tif err != nil || out != nil {\n\t\t\t\tif (err == nil && out != nil) ||\n\t\t\t\t\t(err != nil && out == nil) ||\n\t\t\t\t\t(err.Error() != out.Error()) {\n\t\t\t\t\tt.Fatalf(\"Expected GRPCError to return [%s], got: [%s]\", out, GRPCError(in))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n\ntype resourceExp struct {\n\tnamespace string\n\targs      []string\n\tresource  *pb.Resource\n}\n\nfunc (r *resourceExp) String() string {\n\treturn fmt.Sprintf(\"namespace: %s, args: %s, resource: %s\", r.namespace, r.args, r.resource.String())\n}\n\nfunc TestBuildResource(t *testing.T) {\n\n\tt.Run(\"Returns expected errors on invalid input\", func(t *testing.T) {\n\t\tmsg := \"cannot find Kubernetes canonical name from friendly name [invalid]\"\n\t\texpectations := []resourceExp{\n\t\t\t{\n\t\t\t\tnamespace: \"\",\n\t\t\t\targs:      []string{\"invalid\"},\n\t\t\t},\n\t\t}\n\n\t\tfor _, exp := range expectations {\n\t\t\t_, err := BuildResource(exp.namespace, exp.args[0])\n\t\t\tif err == nil {\n\t\t\t\tt.Fatalf(\"BuildResource called with invalid resources unexpectedly succeeded, should have returned %s\", msg)\n\t\t\t}\n\t\t\tif err.Error() != msg {\n\t\t\t\tt.Fatalf(\"BuildResource called with invalid resources should have returned: %s but got unexpected message: %s\", msg, err)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Correctly parses Kubernetes resources from the command line\", func(t *testing.T) {\n\t\texpectations := []resourceExp{\n\t\t\t{\n\t\t\t\tnamespace: \"test-ns\",\n\t\t\t\targs:      []string{\"deployments\"},\n\t\t\t\tresource: &pb.Resource{\n\t\t\t\t\tNamespace: \"test-ns\",\n\t\t\t\t\tType:      k8s.Deployment,\n\t\t\t\t\tName:      \"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tnamespace: \"\",\n\t\t\t\targs:      []string{\"deploy/foo\"},\n\t\t\t\tresource: &pb.Resource{\n\t\t\t\t\tNamespace: \"\",\n\t\t\t\t\tType:      k8s.Deployment,\n\t\t\t\t\tName:      \"foo\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tnamespace: \"foo-ns\",\n\t\t\t\targs:      []string{\"po\"},\n\t\t\t\tresource: &pb.Resource{\n\t\t\t\t\tNamespace: \"foo-ns\",\n\t\t\t\t\tType:      k8s.Pod,\n\t\t\t\t\tName:      \"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tnamespace: \"foo-ns\",\n\t\t\t\targs:      []string{\"ns\"},\n\t\t\t\tresource: &pb.Resource{\n\t\t\t\t\tNamespace: \"\",\n\t\t\t\t\tType:      k8s.Namespace,\n\t\t\t\t\tName:      \"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tnamespace: \"foo-ns\",\n\t\t\t\targs:      []string{\"ns/foo-ns2\"},\n\t\t\t\tresource: &pb.Resource{\n\t\t\t\t\tNamespace: \"\",\n\t\t\t\t\tType:      k8s.Namespace,\n\t\t\t\t\tName:      \"foo-ns2\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tfor _, exp := range expectations {\n\t\t\tres, err := BuildResource(exp.namespace, exp.args[0])\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error from BuildResource(%s) => %s\", exp.String(), err)\n\t\t\t}\n\t\t\tif diff := deep.Equal(exp.resource, res); diff != nil {\n\t\t\t\tt.Errorf(\"%+v\", diff)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestBuildResources(t *testing.T) {\n\tt.Run(\"Rejects duped resources\", func(t *testing.T) {\n\t\tmsg := \"cannot supply duplicate resources\"\n\t\texpectations := []resourceExp{\n\t\t\t{\n\t\t\t\tnamespace: \"test-ns\",\n\t\t\t\targs:      []string{\"foo\", \"foo\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tnamespace: \"test-ns\",\n\t\t\t\targs:      []string{\"all\", \"all\"},\n\t\t\t},\n\t\t}\n\n\t\tfor _, exp := range expectations {\n\t\t\t_, err := BuildResources(exp.namespace, exp.args)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatalf(\"BuildResources called with duped resources unexpectedly succeeded, should have returned %s\", msg)\n\t\t\t}\n\t\t\tif err.Error() != msg {\n\t\t\t\tt.Fatalf(\"BuildResources called with duped resources should have returned: %s but got unexpected message: %s\", msg, err)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Ensures 'all' can't be supplied alongside other resources\", func(t *testing.T) {\n\t\tmsg := \"'all' can't be supplied alongside other resources\"\n\t\texpectations := []resourceExp{\n\t\t\t{\n\t\t\t\tnamespace: \"test-ns\",\n\t\t\t\targs:      []string{\"po\", \"foo\", \"all\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tnamespace: \"test-ns\",\n\t\t\t\targs:      []string{\"foo\", \"all\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tnamespace: \"test-ns\",\n\t\t\t\targs:      []string{\"all\", \"foo\"},\n\t\t\t},\n\t\t}\n\n\t\tfor _, exp := range expectations {\n\t\t\t_, err := BuildResources(exp.namespace, exp.args)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatalf(\"BuildResources called with 'all' and another resource unexpectedly succeeded, should have returned %s\", msg)\n\t\t\t}\n\t\t\tif err.Error() != msg {\n\t\t\t\tt.Fatalf(\"BuildResources called with 'all' and another resource should have returned: %s but got unexpected message: %s\", msg, err)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"Correctly parses Kubernetes resources from the command line\", func(t *testing.T) {\n\t\texpectations := []resourceExp{\n\t\t\t{\n\t\t\t\tnamespace: \"test-ns\",\n\t\t\t\targs:      []string{\"deployments\"},\n\t\t\t\tresource: &pb.Resource{\n\t\t\t\t\tNamespace: \"test-ns\",\n\t\t\t\t\tType:      k8s.Deployment,\n\t\t\t\t\tName:      \"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tnamespace: \"\",\n\t\t\t\targs:      []string{\"deploy/foo\"},\n\t\t\t\tresource: &pb.Resource{\n\t\t\t\t\tNamespace: \"\",\n\t\t\t\t\tType:      k8s.Deployment,\n\t\t\t\t\tName:      \"foo\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tnamespace: \"foo-ns\",\n\t\t\t\targs:      []string{\"po\", \"foo\"},\n\t\t\t\tresource: &pb.Resource{\n\t\t\t\t\tNamespace: \"foo-ns\",\n\t\t\t\t\tType:      k8s.Pod,\n\t\t\t\t\tName:      \"foo\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tnamespace: \"foo-ns\",\n\t\t\t\targs:      []string{\"ns\", \"foo-ns2\"},\n\t\t\t\tresource: &pb.Resource{\n\t\t\t\t\tNamespace: \"\",\n\t\t\t\t\tType:      k8s.Namespace,\n\t\t\t\t\tName:      \"foo-ns2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tnamespace: \"foo-ns\",\n\t\t\t\targs:      []string{\"ns/foo-ns2\"},\n\t\t\t\tresource: &pb.Resource{\n\t\t\t\t\tNamespace: \"\",\n\t\t\t\t\tType:      k8s.Namespace,\n\t\t\t\t\tName:      \"foo-ns2\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tfor _, exp := range expectations {\n\t\t\tres, err := BuildResources(exp.namespace, exp.args)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error from BuildResources(%+v) => %s\", exp, err)\n\t\t\t}\n\t\t\tif diff := deep.Equal(exp.resource, res[0]); diff != nil {\n\t\t\t\tt.Errorf(\"%+v\", diff)\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "viz/tap/Dockerfile",
    "content": "ARG BUILDPLATFORM=linux/amd64\n\n# Precompile key slow-to-build dependencies\nFROM --platform=$BUILDPLATFORM golang:1.25-alpine AS go-deps\nWORKDIR /linkerd-build\nCOPY go.mod go.sum ./\nCOPY bin/install-deps bin/\nRUN go mod download\nARG TARGETARCH\nRUN ./bin/install-deps $TARGETARCH\n\n## compile tap\nFROM go-deps AS golang\nWORKDIR /linkerd-build\nCOPY pkg pkg\n# TODO: remove after https://github.com/linkerd/linkerd2/issues/5661\nCOPY controller controller\n# TODO: remove when BuildResource is refactored\n# https://github.com/linkerd/linkerd2/issues/5589\nCOPY viz/metrics-api/gen/viz viz/metrics-api/gen/viz\nCOPY viz/tap viz/tap\nCOPY viz/pkg viz/pkg\n\nARG TARGETARCH\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -o /out/tap -tags prod -mod=readonly -ldflags \"-s -w\" ./viz/tap/cmd\n\n## package runtime\nFROM scratch\nLABEL org.opencontainers.image.source=https://github.com/linkerd/linkerd2\nCOPY LICENSE /linkerd/LICENSE\nCOPY --from=golang /out/tap /tap\n\nENTRYPOINT [\"/tap\"]\n"
  },
  {
    "path": "viz/tap/api/client.go",
    "content": "package api\n\nimport (\n\tpb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\n// NewClient creates a client for the control-plane's Tap service.\nfunc NewClient(addr string) (pb.TapClient, *grpc.ClientConn, error) {\n\tconn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn pb.NewTapClient(conn), conn, nil\n}\n"
  },
  {
    "path": "viz/tap/api/grpc_server.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode/utf8\"\n\n\t\"k8s.io/apimachinery/pkg/labels\"\n\n\thttpPb \"github.com/linkerd/linkerd2-proxy-api/go/http_types\"\n\tproxy \"github.com/linkerd/linkerd2-proxy-api/go/tap\"\n\tnetPb \"github.com/linkerd/linkerd2/controller/gen/common/net\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/addr\"\n\tpkgK8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/prometheus\"\n\t\"github.com/linkerd/linkerd2/pkg/util\"\n\tmetricsPb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\tvizLabels \"github.com/linkerd/linkerd2/viz/pkg/labels\"\n\tpkgUtil \"github.com/linkerd/linkerd2/viz/pkg/util\"\n\ttapPb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n\tlog \"github.com/sirupsen/logrus\"\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/metadata\"\n\t\"google.golang.org/grpc/status\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\nconst ipIndex = \"ip\"\nconst defaultMaxRps = 100.0\n\n// GRPCTapServer describes the gRPC server implementing pb.TapServer\ntype GRPCTapServer struct {\n\ttapPb.UnimplementedTapServer\n\ttapPort             uint\n\tk8sAPI              *k8s.API\n\tcontrollerNamespace string\n\ttrustDomain         string\n\tignoreHeaders       map[string]bool\n}\n\nvar (\n\ttapInterval = 1 * time.Second\n)\n\n// Tap is deprecated, use TapByResource.\n// This API endpoint is marked as deprecated but it's still used.\n//\n//nolint:staticcheck\nfunc (s *GRPCTapServer) Tap(req *tapPb.TapRequest, stream tapPb.Tap_TapServer) error {\n\treturn status.Error(codes.Unimplemented, \"Tap is deprecated, use TapByResource\")\n}\n\n// TapByResource taps all resources matched by the request object.\nfunc (s *GRPCTapServer) TapByResource(req *tapPb.TapByResourceRequest, stream tapPb.Tap_TapByResourceServer) error {\n\tif req == nil {\n\t\treturn status.Error(codes.InvalidArgument, \"TapByResource received nil TapByResourceRequest\")\n\t}\n\tif req.GetTarget() == nil {\n\t\treturn status.Error(codes.InvalidArgument, \"TapByResource received nil target ResourceSelection\")\n\t}\n\tres := req.GetTarget().GetResource()\n\tlabelSelector, err := getLabelSelector(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif res == nil {\n\t\treturn status.Error(codes.InvalidArgument, \"TapByResource received nil target Resource\")\n\t}\n\tif req.GetMaxRps() == 0.0 {\n\t\treq.MaxRps = defaultMaxRps\n\t}\n\n\tobjects, err := s.k8sAPI.GetObjects(res.GetNamespace(), res.GetType(), res.GetName(), labelSelector)\n\tif err != nil {\n\t\treturn pkgUtil.GRPCError(err)\n\t}\n\n\tpods := []*corev1.Pod{}\n\ttapDisabled := []*corev1.Pod{}\n\ttapNotEnabled := []*corev1.Pod{}\n\tfor _, object := range objects {\n\t\tpodsFor, err := s.k8sAPI.GetPodsFor(object, false)\n\t\tif err != nil {\n\t\t\treturn pkgUtil.GRPCError(err)\n\t\t}\n\n\t\tfor _, pod := range podsFor {\n\t\t\tif pkgK8s.IsMeshed(pod, s.controllerNamespace) {\n\t\t\t\tif vizLabels.IsTapDisabled(pod) {\n\t\t\t\t\ttapDisabled = append(tapDisabled, pod)\n\t\t\t\t} else if !vizLabels.IsTapEnabled(pod) {\n\t\t\t\t\ttapNotEnabled = append(tapNotEnabled, pod)\n\t\t\t\t} else {\n\t\t\t\t\tpods = append(pods, pod)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(pods) == 0 {\n\t\tvar errs strings.Builder\n\t\tfmt.Fprintf(&errs, \"no pods to tap for type=%q name=%q\\n\", res.GetType(), res.GetName())\n\t\tif len(tapDisabled) > 0 {\n\t\t\tfmt.Fprintf(&errs, \"%d pods found with tap disabled via the %s annotation:\\n\", len(tapDisabled), vizLabels.VizTapDisabled)\n\t\t\tfor _, pod := range tapDisabled {\n\t\t\t\tfmt.Fprintf(&errs, \"\\t* %s\\n\", pod.Name)\n\t\t\t}\n\t\t\tfmt.Fprintln(&errs, \"remove this annotation to make these pods valid tap targets\")\n\t\t}\n\t\tif len(tapNotEnabled) > 0 {\n\t\t\tfmt.Fprintf(&errs, \"%d pods found with tap not enabled:\\n\", len(tapNotEnabled))\n\t\t\tfor _, pod := range tapNotEnabled {\n\t\t\t\tfmt.Fprintf(&errs, \"\\t* %s\\n\", pod.Name)\n\t\t\t}\n\t\t\tfmt.Fprintln(&errs, \"restart these pods to enable tap and make them valid tap targets\")\n\t\t}\n\t\treturn status.Error(codes.NotFound, errs.String())\n\t}\n\n\tlog.Infof(\"Tapping %d pods for target: %q\", len(pods), res.String())\n\n\tevents := make(chan *tapPb.TapEvent)\n\n\t// divide the rps evenly between all pods to tap\n\trpsPerPod := req.GetMaxRps() / float32(len(pods))\n\tif rpsPerPod < 1 {\n\t\trpsPerPod = 1\n\t}\n\n\tmatch, err := makeByResourceMatch(req.GetMatch())\n\tif err != nil {\n\t\treturn pkgUtil.GRPCError(err)\n\t}\n\n\textract := &proxy.ObserveRequest_Extract{}\n\n\t// HTTP is the only protocol supported for extracting metadata, so this is\n\t// the only field checked.\n\textractHTTP := req.GetExtract().GetHttp()\n\tif extractHTTP != nil {\n\t\textract = buildExtractHTTP(extractHTTP)\n\t}\n\n\tfor _, pod := range pods {\n\t\t// create the expected pod identity from the pod spec\n\t\tns := res.GetNamespace()\n\t\tif res.GetType() == pkgK8s.Namespace {\n\t\t\tns = res.GetName()\n\t\t}\n\t\tname := fmt.Sprintf(\"%s.%s.serviceaccount.identity.%s.%s\", pod.Spec.ServiceAccountName, ns, s.controllerNamespace, s.trustDomain)\n\t\tlog.Debugf(\"initiating tap request to %s with required name %s\", pod.Spec.ServiceAccountName, name)\n\n\t\t// pass the header metadata into the request context\n\t\tctx := stream.Context()\n\t\tctx = metadata.AppendToOutgoingContext(ctx, pkgK8s.RequireIDHeader, name)\n\n\t\t// initiate a tap on the pod\n\t\tgo s.tapProxy(ctx, rpsPerPod, match, extract, pod.Status.PodIP, events)\n\t}\n\n\t// read events from the taps and send them back\n\tfor {\n\t\tselect {\n\t\tcase <-stream.Context().Done():\n\t\t\treturn nil\n\t\tcase event := <-events:\n\t\t\terr := stream.Send(event)\n\t\t\tif err != nil {\n\t\t\t\treturn pkgUtil.GRPCError(err)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc makeByResourceMatch(match *tapPb.TapByResourceRequest_Match) (*proxy.ObserveRequest_Match, error) {\n\t// TODO: for now assume it's always a single, flat `All` match list\n\tseq := match.GetAll()\n\tif seq == nil {\n\t\treturn nil, status.Errorf(codes.Unimplemented, \"unexpected match specified: %+v\", match)\n\t}\n\n\tmatches := []*proxy.ObserveRequest_Match{}\n\n\tfor _, reqMatch := range seq.Matches {\n\t\tswitch typed := reqMatch.Match.(type) {\n\t\tcase *tapPb.TapByResourceRequest_Match_Destinations:\n\n\t\t\tfor k, v := range destinationLabels(typed.Destinations.Resource) {\n\t\t\t\tmatches = append(matches, &proxy.ObserveRequest_Match{\n\t\t\t\t\tMatch: &proxy.ObserveRequest_Match_DestinationLabel{\n\t\t\t\t\t\tDestinationLabel: &proxy.ObserveRequest_Match_Label{\n\t\t\t\t\t\t\tKey:   k,\n\t\t\t\t\t\t\tValue: v,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\n\t\tcase *tapPb.TapByResourceRequest_Match_Http_:\n\n\t\t\thttpMatch := proxy.ObserveRequest_Match_Http{}\n\n\t\t\tswitch httpTyped := typed.Http.Match.(type) {\n\t\t\tcase *tapPb.TapByResourceRequest_Match_Http_Scheme:\n\t\t\t\thttpMatch = proxy.ObserveRequest_Match_Http{\n\t\t\t\t\tMatch: &proxy.ObserveRequest_Match_Http_Scheme{\n\t\t\t\t\t\tScheme: util.ParseScheme(httpTyped.Scheme),\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\tcase *tapPb.TapByResourceRequest_Match_Http_Method:\n\t\t\t\thttpMatch = proxy.ObserveRequest_Match_Http{\n\t\t\t\t\tMatch: &proxy.ObserveRequest_Match_Http_Method{\n\t\t\t\t\t\tMethod: util.ParseMethod(httpTyped.Method),\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\tcase *tapPb.TapByResourceRequest_Match_Http_Authority:\n\t\t\t\thttpMatch = proxy.ObserveRequest_Match_Http{\n\t\t\t\t\tMatch: &proxy.ObserveRequest_Match_Http_Authority{\n\t\t\t\t\t\tAuthority: &proxy.ObserveRequest_Match_Http_StringMatch{\n\t\t\t\t\t\t\tMatch: &proxy.ObserveRequest_Match_Http_StringMatch_Exact{\n\t\t\t\t\t\t\t\tExact: httpTyped.Authority,\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\tcase *tapPb.TapByResourceRequest_Match_Http_Path:\n\t\t\t\thttpMatch = proxy.ObserveRequest_Match_Http{\n\t\t\t\t\tMatch: &proxy.ObserveRequest_Match_Http_Path{\n\t\t\t\t\t\tPath: &proxy.ObserveRequest_Match_Http_StringMatch{\n\t\t\t\t\t\t\tMatch: &proxy.ObserveRequest_Match_Http_StringMatch_Prefix{\n\t\t\t\t\t\t\t\tPrefix: httpTyped.Path,\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\tdefault:\n\t\t\t\treturn nil, status.Errorf(codes.Unimplemented, \"unknown HTTP match type: %v\", httpTyped)\n\t\t\t}\n\n\t\t\tmatches = append(matches, &proxy.ObserveRequest_Match{\n\t\t\t\tMatch: &proxy.ObserveRequest_Match_Http_{\n\t\t\t\t\tHttp: &httpMatch,\n\t\t\t\t},\n\t\t\t})\n\n\t\tdefault:\n\t\t\treturn nil, status.Errorf(codes.Unimplemented, \"unknown match type: %v\", typed)\n\t\t}\n\t}\n\n\treturn &proxy.ObserveRequest_Match{\n\t\tMatch: &proxy.ObserveRequest_Match_All{\n\t\t\tAll: &proxy.ObserveRequest_Match_Seq{\n\t\t\t\tMatches: matches,\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\n// TODO: factor out with `promLabels` in public-api\nfunc destinationLabels(resource *metricsPb.Resource) map[string]string {\n\tdstLabels := map[string]string{}\n\tif resource.Name != \"\" {\n\t\tl5dLabel := pkgK8s.KindToL5DLabel(resource.Type)\n\t\tdstLabels[l5dLabel] = resource.Name\n\t}\n\tif resource.Type != pkgK8s.Namespace && resource.Namespace != \"\" {\n\t\tdstLabels[\"namespace\"] = resource.Namespace\n\t}\n\treturn dstLabels\n}\n\nfunc buildExtractHTTP(extract *tapPb.TapByResourceRequest_Extract_Http) *proxy.ObserveRequest_Extract {\n\tif extract.GetHeaders() != nil {\n\t\treturn &proxy.ObserveRequest_Extract{\n\t\t\tExtract: &proxy.ObserveRequest_Extract_Http_{\n\t\t\t\tHttp: &proxy.ObserveRequest_Extract_Http{\n\t\t\t\t\tExtract: &proxy.ObserveRequest_Extract_Http_Headers_{\n\t\t\t\t\t\tHeaders: &proxy.ObserveRequest_Extract_Http_Headers{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\treturn nil\n}\n\n// Tap a pod.\n// This method will run continuously until an error is encountered or the\n// request is cancelled via the context.  Thus it should be called as a\n// go-routine.\n// To limit the rps to maxRps, this method calls Observe on the pod with a limit\n// of maxRps * 1s at most once per 1s window.  If this limit is reached in\n// less than 1s, we sleep until the end of the window before calling Observe\n// again.\nfunc (s *GRPCTapServer) tapProxy(ctx context.Context, maxRps float32, match *proxy.ObserveRequest_Match, extract *proxy.ObserveRequest_Extract, addr string, events chan *tapPb.TapEvent) {\n\tstrPort := strconv.Itoa(int(s.tapPort))\n\ttapAddr := net.JoinHostPort(addr, strPort)\n\tlog.Infof(\"Establishing tap on %s\", tapAddr)\n\tconn, err := grpc.NewClient(tapAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))\n\tif err != nil {\n\t\tlog.Error(err)\n\t\treturn\n\t}\n\tclient := proxy.NewTapClient(conn)\n\tdefer conn.Close()\n\n\treq := &proxy.ObserveRequest{\n\t\tLimit:   uint32(maxRps * float32(tapInterval.Seconds())),\n\t\tMatch:   match,\n\t\tExtract: extract,\n\t}\n\n\tfor { // Request loop\n\t\twindowStart := time.Now()\n\t\twindowEnd := windowStart.Add(tapInterval)\n\t\trsp, err := client.Observe(ctx, req)\n\t\tif err != nil {\n\t\t\tlog.Error(err)\n\t\t\treturn\n\t\t}\n\t\tfor { // Stream loop\n\t\t\tevent, err := rsp.Recv()\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\t\tlog.Debugf(\"[%s] proxy terminated the stream\", addr)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tlog.Errorf(\"[%s] encountered an error: %s\", addr, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\ttranslatedEvent := s.translateEvent(ctx, event)\n\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\tlog.Debugf(\"[%s] client terminated the stream\", addr)\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tevents <- translatedEvent\n\t\t\t}\n\t\t}\n\t\tif time.Now().Before(windowEnd) {\n\t\t\ttime.Sleep(time.Until(windowEnd))\n\t\t}\n\t}\n}\n\nfunc (s *GRPCTapServer) translateEvent(ctx context.Context, orig *proxy.TapEvent) *tapPb.TapEvent {\n\tdirection := func(orig proxy.TapEvent_ProxyDirection) tapPb.TapEvent_ProxyDirection {\n\t\tswitch orig {\n\t\tcase proxy.TapEvent_INBOUND:\n\t\t\treturn tapPb.TapEvent_INBOUND\n\t\tcase proxy.TapEvent_OUTBOUND:\n\t\t\treturn tapPb.TapEvent_OUTBOUND\n\t\tdefault:\n\t\t\treturn tapPb.TapEvent_UNKNOWN\n\t\t}\n\t}\n\n\tevent := func(orig *proxy.TapEvent_Http) *tapPb.TapEvent_Http_ {\n\t\tid := func(orig *proxy.TapEvent_Http_StreamId) *tapPb.TapEvent_Http_StreamId {\n\t\t\treturn &tapPb.TapEvent_Http_StreamId{\n\t\t\t\tBase:   orig.GetBase(),\n\t\t\t\tStream: orig.GetStream(),\n\t\t\t}\n\t\t}\n\n\t\tmethod := func(orig *httpPb.HttpMethod) *metricsPb.HttpMethod {\n\t\t\tswitch m := orig.GetType().(type) {\n\t\t\tcase *httpPb.HttpMethod_Registered_:\n\t\t\t\treturn &metricsPb.HttpMethod{\n\t\t\t\t\tType: &metricsPb.HttpMethod_Registered_{\n\t\t\t\t\t\tRegistered: metricsPb.HttpMethod_Registered(m.Registered),\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\tcase *httpPb.HttpMethod_Unregistered:\n\t\t\t\treturn &metricsPb.HttpMethod{\n\t\t\t\t\tType: &metricsPb.HttpMethod_Unregistered{\n\t\t\t\t\t\tUnregistered: m.Unregistered,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\n\t\tscheme := func(orig *httpPb.Scheme) *metricsPb.Scheme {\n\t\t\tswitch s := orig.GetType().(type) {\n\t\t\tcase *httpPb.Scheme_Registered_:\n\t\t\t\treturn &metricsPb.Scheme{\n\t\t\t\t\tType: &metricsPb.Scheme_Registered_{\n\t\t\t\t\t\tRegistered: metricsPb.Scheme_Registered(s.Registered),\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\tcase *httpPb.Scheme_Unregistered:\n\t\t\t\treturn &metricsPb.Scheme{\n\t\t\t\t\tType: &metricsPb.Scheme_Unregistered{\n\t\t\t\t\t\tUnregistered: s.Unregistered,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\n\t\theaders := func(orig *httpPb.Headers) *metricsPb.Headers {\n\t\t\tif orig == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tvar headers []*metricsPb.Headers_Header\n\t\t\tfor _, header := range orig.GetHeaders() {\n\t\t\t\tn := header.GetName()\n\t\t\t\tif s.ignoreHeaders[n] {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tb := header.GetValue()\n\t\t\t\th := metricsPb.Headers_Header{Name: n, Value: &metricsPb.Headers_Header_ValueBin{ValueBin: b}}\n\t\t\t\tif utf8.Valid(b) {\n\t\t\t\t\th = metricsPb.Headers_Header{Name: n, Value: &metricsPb.Headers_Header_ValueStr{ValueStr: string(b)}}\n\t\t\t\t}\n\t\t\t\theaders = append(headers, &h)\n\t\t\t}\n\t\t\treturn &metricsPb.Headers{\n\t\t\t\tHeaders: headers,\n\t\t\t}\n\t\t}\n\n\t\tswitch orig := orig.GetEvent().(type) {\n\t\tcase *proxy.TapEvent_Http_RequestInit_:\n\t\t\treturn &tapPb.TapEvent_Http_{\n\t\t\t\tHttp: &tapPb.TapEvent_Http{\n\t\t\t\t\tEvent: &tapPb.TapEvent_Http_RequestInit_{\n\t\t\t\t\t\tRequestInit: &tapPb.TapEvent_Http_RequestInit{\n\t\t\t\t\t\t\tId:        id(orig.RequestInit.GetId()),\n\t\t\t\t\t\t\tMethod:    method(orig.RequestInit.GetMethod()),\n\t\t\t\t\t\t\tScheme:    scheme(orig.RequestInit.GetScheme()),\n\t\t\t\t\t\t\tAuthority: orig.RequestInit.Authority,\n\t\t\t\t\t\t\tPath:      orig.RequestInit.Path,\n\t\t\t\t\t\t\tHeaders:   headers(orig.RequestInit.GetHeaders()),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\tcase *proxy.TapEvent_Http_ResponseInit_:\n\t\t\treturn &tapPb.TapEvent_Http_{\n\t\t\t\tHttp: &tapPb.TapEvent_Http{\n\t\t\t\t\tEvent: &tapPb.TapEvent_Http_ResponseInit_{\n\t\t\t\t\t\tResponseInit: &tapPb.TapEvent_Http_ResponseInit{\n\t\t\t\t\t\t\tId:               id(orig.ResponseInit.GetId()),\n\t\t\t\t\t\t\tSinceRequestInit: orig.ResponseInit.GetSinceRequestInit(),\n\t\t\t\t\t\t\tHttpStatus:       orig.ResponseInit.GetHttpStatus(),\n\t\t\t\t\t\t\tHeaders:          headers(orig.ResponseInit.GetHeaders()),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\tcase *proxy.TapEvent_Http_ResponseEnd_:\n\t\t\teos := func(orig *proxy.Eos) *metricsPb.Eos {\n\t\t\t\tswitch e := orig.GetEnd().(type) {\n\t\t\t\tcase *proxy.Eos_ResetErrorCode:\n\t\t\t\t\treturn &metricsPb.Eos{\n\t\t\t\t\t\tEnd: &metricsPb.Eos_ResetErrorCode{\n\t\t\t\t\t\t\tResetErrorCode: e.ResetErrorCode,\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\tcase *proxy.Eos_GrpcStatusCode:\n\t\t\t\t\treturn &metricsPb.Eos{\n\t\t\t\t\t\tEnd: &metricsPb.Eos_GrpcStatusCode{\n\t\t\t\t\t\t\tGrpcStatusCode: e.GrpcStatusCode,\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn &tapPb.TapEvent_Http_{\n\t\t\t\tHttp: &tapPb.TapEvent_Http{\n\t\t\t\t\tEvent: &tapPb.TapEvent_Http_ResponseEnd_{\n\t\t\t\t\t\tResponseEnd: &tapPb.TapEvent_Http_ResponseEnd{\n\t\t\t\t\t\t\tId:                id(orig.ResponseEnd.GetId()),\n\t\t\t\t\t\t\tSinceRequestInit:  orig.ResponseEnd.GetSinceRequestInit(),\n\t\t\t\t\t\t\tSinceResponseInit: orig.ResponseEnd.GetSinceResponseInit(),\n\t\t\t\t\t\t\tResponseBytes:     orig.ResponseEnd.GetResponseBytes(),\n\t\t\t\t\t\t\tEos:               eos(orig.ResponseEnd.GetEos()),\n\t\t\t\t\t\t\tTrailers:          headers(orig.ResponseEnd.GetTrailers()),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\tdefault:\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tsourceLabels := orig.GetSourceMeta().GetLabels()\n\tif sourceLabels == nil {\n\t\tsourceLabels = make(map[string]string)\n\t}\n\tdestinationLabels := orig.GetDestinationMeta().GetLabels()\n\tif destinationLabels == nil {\n\t\tdestinationLabels = make(map[string]string)\n\t}\n\n\tev := &tapPb.TapEvent{\n\t\tSource: addr.NetToPublic(orig.GetSource()),\n\t\tSourceMeta: &tapPb.TapEvent_EndpointMeta{\n\t\t\tLabels: sourceLabels,\n\t\t},\n\t\tDestination: addr.NetToPublic(orig.GetDestination()),\n\t\tDestinationMeta: &tapPb.TapEvent_EndpointMeta{\n\t\t\tLabels: destinationLabels,\n\t\t},\n\t\tRouteMeta: &tapPb.TapEvent_RouteMeta{\n\t\t\tLabels: orig.GetRouteMeta().GetLabels(),\n\t\t},\n\t\tProxyDirection: direction(orig.GetProxyDirection()),\n\t\tEvent:          event(orig.GetHttp()),\n\t}\n\n\ts.hydrateEventLabels(ctx, ev)\n\n\treturn ev\n}\n\n// NewGrpcTapServer creates a new gRPC Tap server\nfunc NewGrpcTapServer(\n\ttapPort uint,\n\tcontrollerNamespace string,\n\ttrustDomain string,\n\tk8sAPI *k8s.API,\n\tignoreHeaders map[string]bool,\n) (*GRPCTapServer, error) {\n\tif err := k8sAPI.Pod().Informer().AddIndexers(cache.Indexers{ipIndex: indexByIP}); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := k8sAPI.Node().Informer().AddIndexers(cache.Indexers{ipIndex: indexByIP}); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn newGRPCTapServer(tapPort, controllerNamespace, trustDomain, k8sAPI, ignoreHeaders), nil\n}\n\nfunc newGRPCTapServer(\n\ttapPort uint,\n\tcontrollerNamespace string,\n\ttrustDomain string,\n\tk8sAPI *k8s.API,\n\tignoreHeaders map[string]bool,\n) *GRPCTapServer {\n\tsrv := &GRPCTapServer{\n\t\ttapPort:             tapPort,\n\t\tk8sAPI:              k8sAPI,\n\t\tcontrollerNamespace: controllerNamespace,\n\t\ttrustDomain:         trustDomain,\n\t\tignoreHeaders:       ignoreHeaders,\n\t}\n\n\ts := prometheus.NewGrpcServer(grpc.MaxConcurrentStreams(0))\n\ttapPb.RegisterTapServer(s, srv)\n\n\treturn srv\n}\n\nfunc indexByIP(obj interface{}) ([]string, error) {\n\tswitch v := obj.(type) {\n\tcase *corev1.Pod:\n\t\treturn []string{v.Status.PodIP}, nil\n\tcase *corev1.Node:\n\t\taddresses := make([]string, 0)\n\t\tfor _, address := range v.Status.Addresses {\n\t\t\tif address.Type == corev1.NodeInternalIP {\n\t\t\t\tlog.Debugf(\"Indexing node address: %s\", address.Address)\n\t\t\t\taddresses = append(addresses, address.Address)\n\t\t\t}\n\t\t}\n\t\treturn addresses, nil\n\t}\n\treturn []string{\"\"}, fmt.Errorf(\"object is not a pod nor a node\")\n}\n\n// hydrateEventLabels attempts to hydrate the metadata labels for an event's\n// source and (if the event was reported by an inbound proxy) destination,\n// and adds them to the event's `SourceMeta` and `DestinationMeta` fields.\n//\n// Since errors encountered while hydrating metadata are non-fatal and result\n// only in missing labels, any errors are logged at the WARN level.\nfunc (s *GRPCTapServer) hydrateEventLabels(ctx context.Context, ev *tapPb.TapEvent) {\n\terr := s.hydrateIPLabels(ctx, ev.GetSource().GetIp(), ev.GetSourceMeta().GetLabels())\n\tif err != nil {\n\t\tlog.Warnf(\"error hydrating source labels: %s\", err)\n\t}\n\n\tif ev.ProxyDirection == tapPb.TapEvent_INBOUND {\n\t\t// Events emitted by an inbound proxies don't have destination labels,\n\t\t// since the inbound proxy _is_ the destination, and proxies don't know\n\t\t// their own labels.\n\t\terr = s.hydrateIPLabels(ctx, ev.GetDestination().GetIp(), ev.GetDestinationMeta().GetLabels())\n\t\tif err != nil {\n\t\t\tlog.Warnf(\"error hydrating destination labels: %s\", err)\n\t\t}\n\t}\n\n}\n\n// hydrateIPLabels attempts to determine the metadata labels for `ip` and, if\n// successful, adds them to `labels`.\nfunc (s *GRPCTapServer) hydrateIPLabels(ctx context.Context, ip *netPb.IPAddress, labels map[string]string) error {\n\tres, err := s.resourceForIP(ip)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch v := res.(type) {\n\tcase *corev1.Pod:\n\t\tif v == nil {\n\t\t\tlog.Debugf(\"no pod found for IP %s\", addr.PublicIPToString(ip))\n\t\t\treturn nil\n\t\t}\n\t\townerKind, ownerName := s.k8sAPI.GetOwnerKindAndName(ctx, v, false)\n\t\tpodLabels := pkgK8s.GetPodLabels(ownerKind, ownerName, v)\n\t\tfor key, value := range podLabels {\n\t\t\tlabels[key] = value\n\t\t}\n\t\tlabels[pkgK8s.Namespace] = v.Namespace\n\tcase *corev1.Node:\n\t\tlabels[pkgK8s.Node] = v.Name\n\t}\n\treturn nil\n}\n\n// resourceForIP returns the node or pod corresponding to a given IP address.\n//\n// First it checks if the IP corresponds to a Node's internal IP and returns the\n// node if that's the case. Otherwise it checks the running pods that match the\n// IP. If exactly one is found, it's returned. Otherwise it returns nil. Errors\n// are returned only in the event of an error searching the indices.\nfunc (s *GRPCTapServer) resourceForIP(ip *netPb.IPAddress) (runtime.Object, error) {\n\tipStr := addr.PublicIPToString(ip)\n\n\tnodes, err := s.k8sAPI.Node().Informer().GetIndexer().ByIndex(ipIndex, ipStr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 1 {\n\t\tlog.Debugf(\"found one node at IP %s\", ipStr)\n\t\treturn nodes[0].(*corev1.Node), nil\n\t}\n\n\tpods, err := s.k8sAPI.Pod().Informer().GetIndexer().ByIndex(ipIndex, ipStr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(pods) == 1 {\n\t\tlog.Debugf(\"found one pod at IP %s\", ipStr)\n\t\treturn pods[0].(*corev1.Pod), nil\n\t}\n\n\tvar singleRunningPod *corev1.Pod\n\tfor _, obj := range pods {\n\t\tpod := obj.(*corev1.Pod)\n\t\tif pod.Status.Phase == corev1.PodRunning {\n\t\t\tif singleRunningPod != nil {\n\t\t\t\tlog.Warnf(\n\t\t\t\t\t\"could not uniquely identify pod at %s (found %d pods)\",\n\t\t\t\t\tipStr,\n\t\t\t\t\tlen(pods),\n\t\t\t\t)\n\t\t\t\treturn nil, nil\n\t\t\t}\n\t\t\tsingleRunningPod = pod\n\t\t}\n\t}\n\n\treturn singleRunningPod, nil\n}\n\nfunc getLabelSelector(req *tapPb.TapByResourceRequest) (labels.Selector, error) {\n\tlabelSelector := labels.Everything()\n\tif s := req.GetTarget().GetLabelSelector(); s != \"\" {\n\t\tvar err error\n\t\tlabelSelector, err = labels.Parse(s)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid label selector \\\"%s\\\": %w\", s, err)\n\t\t}\n\t}\n\treturn labelSelector, nil\n}\n"
  },
  {
    "path": "viz/tap/api/grpc_server_test.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\tproxy \"github.com/linkerd/linkerd2-proxy-api/go/tap\"\n\t\"github.com/linkerd/linkerd2/controller/api/util\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/addr\"\n\tpkgK8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\tmetricsPb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\ttapPb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/metadata\"\n\t\"google.golang.org/grpc/status\"\n)\n\ntype tapExpected struct {\n\terr       error\n\tk8sRes    []string\n\treq       *tapPb.TapByResourceRequest\n\trequireID string\n}\n\n// mockTapByResourceServer satisfies controller.tap.Tap_TapByResourceServer\ntype mockTapByResourceServer struct {\n\tutil.MockServerStream\n}\n\nfunc (m *mockTapByResourceServer) Send(event *tapPb.TapEvent) error {\n\treturn nil\n}\n\n// mockProxyTapServer satisfies proxy.tap.TapServer\ntype mockProxyTapServer struct {\n\tproxy.UnimplementedTapServer\n\tmockControllerServer mockTapByResourceServer // for cancellation\n\tctx                  context.Context\n}\n\nfunc (m *mockProxyTapServer) Observe(req *proxy.ObserveRequest, obsSrv proxy.Tap_ObserveServer) error {\n\tm.ctx = obsSrv.Context()\n\tm.mockControllerServer.Cancel()\n\treturn nil\n}\n\nfunc TestTapByResource(t *testing.T) {\n\texpectations := []tapExpected{\n\t\t{\n\t\t\terr:    status.Error(codes.InvalidArgument, \"TapByResource received nil target ResourceSelection\"),\n\t\t\tk8sRes: []string{},\n\t\t\treq:    &tapPb.TapByResourceRequest{},\n\t\t},\n\t\t{\n\t\t\terr: status.Errorf(codes.Unimplemented, \"unexpected match specified: any:{}\"),\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: controller-ns\n  annotations:\n    viz.linkerd.io/tap-enabled: \"true\"\n    linkerd.io/proxy-version: testinjectversion\nstatus:\n  phase: Running\n  podIP: 127.0.0.1\n`,\n\t\t\t},\n\t\t\treq: &tapPb.TapByResourceRequest{\n\t\t\t\tTarget: &metricsPb.ResourceSelection{\n\t\t\t\t\tResource: &metricsPb.Resource{\n\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\tName:      \"emojivoto-meshed\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tMatch: &tapPb.TapByResourceRequest_Match{\n\t\t\t\t\tMatch: &tapPb.TapByResourceRequest_Match_Any{\n\t\t\t\t\t\tAny: &tapPb.TapByResourceRequest_Match_Seq{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\terr: status.Errorf(codes.NotFound, \"no pods to tap for type=\\\"pod\\\" name=\\\"emojivoto-not-meshed\\\"\\n\"),\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-not-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\nstatus:\n  phase: Running\n  podIP: 127.0.0.1\n`,\n\t\t\t},\n\t\t\treq: &tapPb.TapByResourceRequest{\n\t\t\t\tTarget: &metricsPb.ResourceSelection{\n\t\t\t\t\tResource: &metricsPb.Resource{\n\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\tName:      \"emojivoto-not-meshed\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\terr:    status.Errorf(codes.Unimplemented, \"unimplemented resource type: bad-type\"),\n\t\t\tk8sRes: []string{},\n\t\t\treq: &tapPb.TapByResourceRequest{\n\t\t\t\tTarget: &metricsPb.ResourceSelection{\n\t\t\t\t\tResource: &metricsPb.Resource{\n\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\tType:      \"bad-type\",\n\t\t\t\t\t\tName:      \"emojivoto-meshed-not-found\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\terr: status.Errorf(codes.NotFound, \"pod \\\"emojivoto-meshed-not-found\\\" not found\"),\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n  annotations:\n    viz.linkerd.io/tap-enabled: \"true\"\n    linkerd.io/proxy-version: testinjectversion\nstatus:\n  phase: Running\n  podIP: 127.0.0.1\n`,\n\t\t\t},\n\t\t\treq: &tapPb.TapByResourceRequest{\n\t\t\t\tTarget: &metricsPb.ResourceSelection{\n\t\t\t\t\tResource: &metricsPb.Resource{\n\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\tName:      \"emojivoto-meshed-not-found\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\terr: status.Errorf(codes.NotFound, \"no pods to tap for type=\\\"pod\\\" name=\\\"emojivoto-meshed\\\"\\n\"),\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n  annotations:\n    viz.linkerd.io/tap-enabled: \"true\"\n    linkerd.io/proxy-version: testinjectversion\nstatus:\n  phase: Finished\n  podIP: 127.0.0.1\n`,\n\t\t\t},\n\t\t\treq: &tapPb.TapByResourceRequest{\n\t\t\t\tTarget: &metricsPb.ResourceSelection{\n\t\t\t\t\tResource: &metricsPb.Resource{\n\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\tName:      \"emojivoto-meshed\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\terr: status.Errorf(codes.NotFound, `no pods to tap for type=\"pod\" name=\"emojivoto-meshed-tap-disabled\"\n1 pods found with tap disabled via the viz.linkerd.io/disable-tap annotation:\n\t* emojivoto-meshed-tap-disabled\nremove this annotation to make these pods valid tap targets\n`),\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed-tap-disabled\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: controller-ns\n  annotations:\n    viz.linkerd.io/disable-tap: \"true\"\n    linkerd.io/proxy-version: testinjectversion\nstatus:\n  phase: Running\n  podIP: 127.0.0.1\n    `,\n\t\t\t},\n\t\t\treq: &tapPb.TapByResourceRequest{\n\t\t\t\tTarget: &metricsPb.ResourceSelection{\n\t\t\t\t\tResource: &metricsPb.Resource{\n\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\tName:      \"emojivoto-meshed-tap-disabled\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tMatch: &tapPb.TapByResourceRequest_Match{\n\t\t\t\t\tMatch: &tapPb.TapByResourceRequest_Match_All{\n\t\t\t\t\t\tAll: &tapPb.TapByResourceRequest_Match_Seq{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\terr: status.Errorf(codes.NotFound, `no pods to tap for type=\"pod\" name=\"emojivoto-meshed-tap-not-enabled\"\n1 pods found with tap not enabled:\n\t* emojivoto-meshed-tap-not-enabled\nrestart these pods to enable tap and make them valid tap targets\n`),\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed-tap-not-enabled\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: controller-ns\n  annotations:\n    linkerd.io/proxy-version: testinjectversion\nstatus:\n  phase: Running\n  podIP: 127.0.0.1\n    `,\n\t\t\t},\n\t\t\treq: &tapPb.TapByResourceRequest{\n\t\t\t\tTarget: &metricsPb.ResourceSelection{\n\t\t\t\t\tResource: &metricsPb.Resource{\n\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\tName:      \"emojivoto-meshed-tap-not-enabled\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tMatch: &tapPb.TapByResourceRequest_Match{\n\t\t\t\t\tMatch: &tapPb.TapByResourceRequest_Match_All{\n\t\t\t\t\t\tAll: &tapPb.TapByResourceRequest_Match_Seq{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t// success, underlying tap events tested in http_server_test.go\n\t\t\terr: nil,\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: controller-ns\n  annotations:\n    viz.linkerd.io/tap-enabled: \"true\"\n    linkerd.io/proxy-version: testinjectversion\nstatus:\n  phase: Running\n  podIP: 127.0.0.1\n`,\n\t\t\t},\n\t\t\treq: &tapPb.TapByResourceRequest{\n\t\t\t\tTarget: &metricsPb.ResourceSelection{\n\t\t\t\t\tResource: &metricsPb.Resource{\n\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\tName:      \"emojivoto-meshed\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tMatch: &tapPb.TapByResourceRequest_Match{\n\t\t\t\t\tMatch: &tapPb.TapByResourceRequest_Match_All{\n\t\t\t\t\t\tAll: &tapPb.TapByResourceRequest_Match_Seq{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\trequireID: \".emojivoto.serviceaccount.identity.controller-ns.cluster.local\",\n\t\t},\n\t\t{\n\t\t\terr: nil,\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: controller-ns\n  annotations:\n    viz.linkerd.io/tap-enabled: \"true\"\n    linkerd.io/proxy-version: testinjectversion\nspec:\n  serviceAccountName: emojivoto-meshed-sa\nstatus:\n  phase: Running\n  podIP: 127.0.0.1\n`,\n\t\t\t},\n\t\t\treq: &tapPb.TapByResourceRequest{\n\t\t\t\tTarget: &metricsPb.ResourceSelection{\n\t\t\t\t\tResource: &metricsPb.Resource{\n\t\t\t\t\t\tNamespace: \"emojivoto\",\n\t\t\t\t\t\tType:      pkgK8s.Pod,\n\t\t\t\t\t\tName:      \"emojivoto-meshed\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tMatch: &tapPb.TapByResourceRequest_Match{\n\t\t\t\t\tMatch: &tapPb.TapByResourceRequest_Match_All{\n\t\t\t\t\t\tAll: &tapPb.TapByResourceRequest_Match_Seq{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\trequireID: \"emojivoto-meshed-sa.emojivoto.serviceaccount.identity.controller-ns.cluster.local\",\n\t\t},\n\t\t{\n\t\t\terr: nil,\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: emojivoto\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\n    linkerd.io/control-plane-ns: controller-ns\n  annotations:\n    viz.linkerd.io/tap-enabled: \"true\"\n    linkerd.io/proxy-version: testinjectversion\nspec:\n  serviceAccountName: emojivoto-meshed-sa\nstatus:\n  phase: Running\n  podIP: 127.0.0.1\n`,\n\t\t\t},\n\t\t\treq: &tapPb.TapByResourceRequest{\n\t\t\t\tTarget: &metricsPb.ResourceSelection{\n\t\t\t\t\tResource: &metricsPb.Resource{\n\t\t\t\t\t\tNamespace: \"\",\n\t\t\t\t\t\tType:      pkgK8s.Namespace,\n\t\t\t\t\t\tName:      \"emojivoto\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tMatch: &tapPb.TapByResourceRequest_Match{\n\t\t\t\t\tMatch: &tapPb.TapByResourceRequest_Match_All{\n\t\t\t\t\t\tAll: &tapPb.TapByResourceRequest_Match_Seq{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\trequireID: \"emojivoto-meshed-sa.emojivoto.serviceaccount.identity.controller-ns.cluster.local\",\n\t\t},\n\t}\n\n\tfor i, exp := range expectations {\n\t\texp := exp // pin\n\t\tt.Run(fmt.Sprintf(\"%d: Returns expected response\", i), func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(exp.k8sRes...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tstream := mockTapByResourceServer{\n\t\t\t\tMockServerStream: util.NewMockServerStream(),\n\t\t\t}\n\n\t\t\ts := grpc.NewServer()\n\n\t\t\tmockProxyTapServer := mockProxyTapServer{\n\t\t\t\tmockControllerServer: stream,\n\t\t\t}\n\t\t\tproxy.RegisterTapServer(s, &mockProxyTapServer)\n\n\t\t\tlis, err := net.Listen(\"tcp\", \"localhost:0\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to listen\")\n\t\t\t}\n\n\t\t\t// TODO: mock out the underlying grpc tap events\n\t\t\terrChan := make(chan error, 1)\n\t\t\tgo func() {\n\t\t\t\terrChan <- s.Serve(lis)\n\t\t\t}()\n\n\t\t\tdefer func() {\n\t\t\t\tif err := <-errChan; err != nil {\n\t\t\t\t\tt.Fatalf(\"Failed to serve on %+v: %s\", lis, err)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tdefer s.GracefulStop()\n\n\t\t\t_, port, err := net.SplitHostPort(lis.Addr().String())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err.Error())\n\t\t\t}\n\n\t\t\ttapPort, err := strconv.ParseUint(port, 10, 32)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Invalid port: %s\", port)\n\t\t\t}\n\n\t\t\tfakeGrpcServer := newGRPCTapServer(uint(tapPort), \"controller-ns\", \"cluster.local\", k8sAPI, nil)\n\n\t\t\tk8sAPI.Sync(nil)\n\n\t\t\terr = fakeGrpcServer.TapByResource(exp.req, &stream)\n\t\t\tif err != nil || exp.err != nil {\n\t\t\t\tcode := status.Code(err)\n\t\t\t\texpCode := status.Code(exp.err)\n\t\t\t\tif code != expCode {\n\t\t\t\t\tt.Fatalf(\"TapByResource returned unexpected error code: [%s], expected: [%s]\", code, expCode)\n\t\t\t\t}\n\t\t\t\tif err.Error() != exp.err.Error() {\n\t\t\t\t\tt.Fatalf(\"TapByResource returned unexpected error message: [%s], expected: [%s]\", err.Error(), exp.err.Error())\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif exp.requireID != \"\" {\n\t\t\t\tmd, ok := metadata.FromIncomingContext(mockProxyTapServer.ctx)\n\t\t\t\tif !ok {\n\t\t\t\t\tt.Fatalf(\"FromIncomingContext failed given: %+v\", mockProxyTapServer.ctx)\n\t\t\t\t}\n\t\t\t\tif diff := deep.Equal(md.Get(pkgK8s.RequireIDHeader), []string{exp.requireID}); diff != nil {\n\t\t\t\t\tt.Fatalf(\"Unexpected l5d-require-id header: %+v\", diff)\n\t\t\t\t}\n\t\t\t}\n\n\t\t})\n\t}\n}\n\nfunc TestHydrateIPLabels(t *testing.T) {\n\texpectations := []struct {\n\t\tk8sRes      []string\n\t\trequestedIP string\n\t\tlabels      map[string]string\n\t}{\n\t\t{\n\t\t\t// Requested IP that doesn't match node or any pod\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Node\nmetadata:\n  name: node1\nstatus:\n  addresses:\n  - address: 1.2.3.4\n    type: InternalIP\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\nstatus:\n  phase: Running\n  podIP: 5.6.7.8\n`,\n\t\t\t},\n\t\t\trequestedIP: \"10.20.30.40\",\n\t\t\tlabels:      map[string]string{},\n\t\t},\n\t\t{\n\t\t\t// Requested IP that matches node only\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Node\nmetadata:\n  name: node1\nstatus:\n  addresses:\n  - address: 1.2.3.4\n    type: InternalIP\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\nstatus:\n  phase: Running\n  podIP: 5.6.7.8\n`,\n\t\t\t},\n\t\t\trequestedIP: \"1.2.3.4\",\n\t\t\tlabels:      map[string]string{\"node\": \"node1\"},\n\t\t},\n\t\t{\n\t\t\t// Requested IP that matches node and pod\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Node\nmetadata:\n  name: node1\nstatus:\n  addresses:\n  - address: 1.2.3.4\n    type: InternalIP\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\nstatus:\n  phase: Running\n  podIP: 1.2.3.4\n`,\n\t\t\t},\n\t\t\trequestedIP: \"1.2.3.4\",\n\t\t\tlabels:      map[string]string{\"node\": \"node1\"},\n\t\t},\n\t\t{\n\t\t\t// Requested IP that doesn't match node and matches exactly one pod\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Node\nmetadata:\n  name: node1\nstatus:\n  addresses:\n  - address: 1.2.3.4\n    type: InternalIP\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\nstatus:\n  phase: Running\n  podIP: 5.6.7.8\n`,\n\t\t\t},\n\t\t\trequestedIP: \"5.6.7.8\",\n\t\t\tlabels: map[string]string{\n\t\t\t\t\"namespace\":      \"emojivoto\",\n\t\t\t\t\"pod\":            \"emojivoto-meshed\",\n\t\t\t\t\"serviceaccount\": \"default\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t// Requested IP that doesn't match node and matches exactly one running pod and one finished pod\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Node\nmetadata:\n  name: node1\nstatus:\n  addresses:\n  - address: 1.2.3.4\n    type: InternalIP\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\nstatus:\n  phase: Running\n  podIP: 5.6.7.8\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed-2\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\nstatus:\n  phase: Finished\n  podIP: 5.6.7.8\n`,\n\t\t\t},\n\t\t\trequestedIP: \"5.6.7.8\",\n\t\t\tlabels: map[string]string{\n\t\t\t\t\"namespace\":      \"emojivoto\",\n\t\t\t\t\"pod\":            \"emojivoto-meshed\",\n\t\t\t\t\"serviceaccount\": \"default\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t// Requested IP that doesn't match node and matches two running pods\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: Node\nmetadata:\n  name: node1\nstatus:\n  addresses:\n  - address: 1.2.3.4\n    type: InternalIP\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\nstatus:\n  phase: Running\n  podIP: 5.6.7.8\n`, `\napiVersion: v1\nkind: Pod\nmetadata:\n  name: emojivoto-meshed-2\n  namespace: emojivoto\n  labels:\n    app: emoji-svc\nstatus:\n  phase: Running\n  podIP: 5.6.7.8\n`,\n\t\t\t},\n\t\t\trequestedIP: \"5.6.7.8\",\n\t\t\tlabels:      map[string]string{},\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tfor i, exp := range expectations {\n\t\texp := exp // pin\n\t\tt.Run(fmt.Sprintf(\"%d: Returns expected response\", i), func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(exp.k8sRes...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\t\t\ts, _ := NewGrpcTapServer(4190, \"controller-ns\", \"cluster.local\", k8sAPI, nil)\n\t\t\tk8sAPI.Sync(nil)\n\n\t\t\tlabels := make(map[string]string)\n\t\t\tip, err := addr.ParsePublicIP(exp.requestedIP)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Error parsing IP %s: %s\", exp.requestedIP, err)\n\t\t\t}\n\t\t\ts.hydrateIPLabels(ctx, ip, labels)\n\t\t\tif diff := deep.Equal(labels, exp.labels); diff != nil {\n\t\t\t\tt.Fatalf(\"Unexpected labels: %+v\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "viz/tap/api/handlers.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/go-openapi/spec\"\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tpkgK8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/protohttp\"\n\tpb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n\t\"github.com/linkerd/linkerd2/viz/tap/pkg\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\t\"github.com/sirupsen/logrus\"\n\t\"google.golang.org/grpc/metadata\"\n\tauthV1 \"k8s.io/api/authorization/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/version\"\n)\n\ntype handler struct {\n\tk8sAPI            *k8s.API\n\tusernameHeader    string\n\tgroupHeader       string\n\textraHeaderPrefix string\n\tgrpcTapServer     pb.TapServer\n\tlog               *logrus.Entry\n}\n\n// TODO: share with api_handlers.go\ntype jsonError struct {\n\tError string `json:\"error\"`\n}\n\nvar (\n\tgvk = &schema.GroupVersionKind{\n\t\tGroup:   \"tap.linkerd.io\",\n\t\tVersion: \"v1alpha1\",\n\t\tKind:    \"Tap\",\n\t}\n\n\tgvfd = metav1.GroupVersionForDiscovery{\n\t\tGroupVersion: gvk.GroupVersion().String(),\n\t\tVersion:      gvk.Version,\n\t}\n\n\tapiGroup = metav1.APIGroup{\n\t\tName:             gvk.Group,\n\t\tVersions:         []metav1.GroupVersionForDiscovery{gvfd},\n\t\tPreferredVersion: gvfd,\n\t}\n\n\tresources = []struct {\n\t\tname       string\n\t\tnamespaced bool\n\t}{\n\t\t{\"namespaces\", false},\n\t\t{\"pods\", true},\n\t\t{\"replicationcontrollers\", true},\n\t\t{\"services\", true},\n\t\t{\"daemonsets\", true},\n\t\t{\"deployments\", true},\n\t\t{\"replicasets\", true},\n\t\t{\"statefulsets\", true},\n\t\t{\"jobs\", true},\n\t\t{\"cronjobs\", true},\n\t}\n)\n\nfunc initRouter(h *handler) *httprouter.Router {\n\trouter := &httprouter.Router{}\n\n\trouter.GET(\"/\", handleRoot)\n\trouter.GET(\"/apis\", handleAPIs)\n\trouter.GET(\"/apis/\"+gvk.Group, handleAPIGroup)\n\trouter.GET(\"/apis/\"+gvk.GroupVersion().String(), handleAPIResourceList)\n\trouter.GET(\"/healthz\", handleHealthz)\n\trouter.GET(\"/healthz/log\", handleHealthz)\n\trouter.GET(\"/healthz/ping\", handleHealthz)\n\trouter.GET(\"/metrics\", handleMetrics)\n\trouter.GET(\"/openapi/v2\", handleOpenAPI)\n\trouter.GET(\"/version\", handleVersion)\n\trouter.NotFound = handleNotFound()\n\n\tfor _, res := range resources {\n\t\troute := \"\"\n\t\tif !res.namespaced {\n\t\t\troute = fmt.Sprintf(\"/apis/%s/watch/%s/:namespace\", gvk.GroupVersion().String(), res.name)\n\t\t} else {\n\t\t\troute = fmt.Sprintf(\"/apis/%s/watch/namespaces/:namespace/%s/:name\", gvk.GroupVersion().String(), res.name)\n\t\t}\n\n\t\trouter.GET(route, handleRoot)\n\t\trouter.POST(route+\"/tap\", h.handleTap)\n\t}\n\n\treturn router\n}\n\n// POST /apis/tap.linkerd.io/v1alpha1/watch/namespaces/:namespace/tap\n// POST /apis/tap.linkerd.io/v1alpha1/watch/namespaces/:namespace/:resource/:name/tap\nfunc (h *handler) handleTap(w http.ResponseWriter, req *http.Request, p httprouter.Params) {\n\tnamespace := p.ByName(\"namespace\")\n\tname := p.ByName(\"name\")\n\tresource := \"\"\n\n\tpath := strings.Split(req.URL.Path, \"/\")\n\tif len(path) == 8 {\n\t\tresource = path[5]\n\t} else if len(path) == 10 {\n\t\tresource = path[7]\n\t} else {\n\t\terr := fmt.Errorf(\"invalid path: %q\", req.URL.Path)\n\t\th.log.Error(err)\n\t\trenderJSONError(w, err, http.StatusBadRequest)\n\t\treturn\n\t}\n\n\th.log.Debugf(\"SubjectAccessReview: namespace: %q, resource: %q, name: %q, user: %q, group: %q\",\n\t\tnamespace, resource, name, h.usernameHeader, h.groupHeader,\n\t)\n\n\textra := extractExtraHeaders(req.Header, h.extraHeaderPrefix)\n\n\t// TODO: it's possible this SubjectAccessReview is redundant, consider\n\t// removing, more info at https://github.com/linkerd/linkerd2/issues/3182\n\terr := pkgK8s.ResourceAuthzForUser(\n\t\treq.Context(),\n\t\th.k8sAPI.Client,\n\t\tnamespace,\n\t\t\"watch\",\n\t\tgvk.Group,\n\t\tgvk.Version,\n\t\tresource,\n\t\t\"tap\",\n\t\tname,\n\t\treq.Header.Get(h.usernameHeader),\n\t\treq.Header.Values(h.groupHeader),\n\t\textra,\n\t)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"tap authorization failed (%w), visit %s for more information\", err, pkg.TapRbacURL)\n\t\th.log.Error(err)\n\t\trenderJSONError(w, err, http.StatusForbidden)\n\t\treturn\n\t}\n\n\ttapReq := pb.TapByResourceRequest{}\n\terr = protohttp.HTTPRequestToProto(req, &tapReq)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"Error decoding Tap Request proto: %w\", err)\n\t\th.log.Error(err)\n\t\tprotohttp.WriteErrorToHTTPResponse(w, err)\n\t\treturn\n\t}\n\n\turl := pkg.TapReqToURL(&tapReq)\n\tif url != req.URL.Path {\n\t\terr = fmt.Errorf(\"tap request body did not match APIServer URL: %q != %q\", url, req.URL.Path)\n\t\th.log.Error(err)\n\t\tprotohttp.WriteErrorToHTTPResponse(w, err)\n\t\treturn\n\t}\n\n\tflushableWriter, err := protohttp.NewStreamingWriter(w)\n\tif err != nil {\n\t\th.log.Error(err)\n\t\tprotohttp.WriteErrorToHTTPResponse(w, err)\n\t\treturn\n\t}\n\n\tserverStream := serverStream{w: flushableWriter, req: req, log: h.log}\n\t// This API endpoint is marked as deprecated but it's still used.\n\t//nolint:staticcheck\n\terr = h.grpcTapServer.TapByResource(&tapReq, &serverStream)\n\tif err != nil {\n\t\th.log.Errorf(\"TapByResource failed: %q\", err)\n\t\tprotohttp.WriteErrorToHTTPResponse(flushableWriter, err)\n\t\treturn\n\t}\n}\n\nfunc extractExtraHeaders(header http.Header, extraHeaderPrefix string) map[string]authV1.ExtraValue {\n\tif extraHeaderPrefix == \"\" {\n\t\treturn nil\n\t}\n\n\textraHeaderPrefixLower := strings.ToLower(extraHeaderPrefix)\n\textraHeaderPrefixLen := len(extraHeaderPrefix)\n\textra := make(map[string]authV1.ExtraValue)\n\n\tfor key, values := range header {\n\t\tif !strings.HasPrefix(strings.ToLower(key), extraHeaderPrefixLower) {\n\t\t\tcontinue\n\t\t}\n\n\t\textraKey, err := url.QueryUnescape(key[extraHeaderPrefixLen:])\n\t\tif err == nil {\n\t\t\textra[extraKey] = authV1.ExtraValue(values)\n\t\t}\n\t}\n\n\treturn extra\n}\n\n// GET (not found)\nfunc handleNotFound() http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\thandlePaths(w, http.StatusNotFound)\n\t})\n\n}\n\n// GET /\n// GET /apis/tap.linkerd.io/v1alpha1/watch/namespaces/:namespace\n// GET /apis/tap.linkerd.io/v1alpha1/watch/namespaces/:namespace/:resource/:name\nfunc handleRoot(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {\n\thandlePaths(w, http.StatusOK)\n}\n\n// GET /\n// GET (not found)\nfunc handlePaths(w http.ResponseWriter, status int) {\n\tpaths := map[string][]string{\n\t\t\"paths\": {\n\t\t\t\"/apis\",\n\t\t\t\"/apis/\" + gvk.Group,\n\t\t\t\"/apis/\" + gvk.GroupVersion().String(),\n\t\t\t\"/healthz\",\n\t\t\t\"/healthz/log\",\n\t\t\t\"/healthz/ping\",\n\t\t\t\"/metrics\",\n\t\t\t\"/openapi/v2\",\n\t\t\t\"/version\",\n\t\t},\n\t}\n\n\trenderJSON(w, paths, status)\n}\n\n// GET /apis\nfunc handleAPIs(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {\n\tgroupList := metav1.APIGroupList{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind: \"APIGroupList\",\n\t\t},\n\t\tGroups: []metav1.APIGroup{\n\t\t\tapiGroup,\n\t\t},\n\t}\n\n\trenderJSON(w, groupList, http.StatusOK)\n}\n\n// GET /apis/tap.linkerd.io\nfunc handleAPIGroup(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {\n\tgroupWithType := apiGroup\n\tgroupWithType.TypeMeta = metav1.TypeMeta{\n\t\tKind:       \"APIGroup\",\n\t\tAPIVersion: \"v1\",\n\t}\n\n\trenderJSON(w, groupWithType, http.StatusOK)\n}\n\n// GET /apis/tap.linkerd.io/v1alpha1\n// this is required for `kubectl api-resources` to work\nfunc handleAPIResourceList(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {\n\tresList := metav1.APIResourceList{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tKind:       \"APIResourceList\",\n\t\t\tAPIVersion: \"v1\",\n\t\t},\n\t\tGroupVersion: gvk.GroupVersion().String(),\n\t\tAPIResources: []metav1.APIResource{},\n\t}\n\n\tfor _, res := range resources {\n\t\tresList.APIResources = append(resList.APIResources,\n\t\t\tmetav1.APIResource{\n\t\t\t\tName:       res.name,\n\t\t\t\tNamespaced: res.namespaced,\n\t\t\t\tKind:       gvk.Kind,\n\t\t\t\tVerbs:      metav1.Verbs{\"watch\"},\n\t\t\t})\n\t\tresList.APIResources = append(resList.APIResources,\n\t\t\tmetav1.APIResource{\n\t\t\t\tName:       fmt.Sprintf(\"%s/tap\", res.name),\n\t\t\t\tNamespaced: res.namespaced,\n\t\t\t\tKind:       gvk.Kind,\n\t\t\t\tVerbs:      metav1.Verbs{\"watch\"},\n\t\t\t})\n\t}\n\n\trenderJSON(w, resList, http.StatusOK)\n}\n\n// GET /healthz\n// GET /healthz/logs\n// GET /healthz/ping\nfunc handleHealthz(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {\n\tw.Header().Set(\"Content-Type\", \"text/plain; charset=utf-8\")\n\tw.Write([]byte(\"ok\"))\n}\n\n// GET /metrics\nfunc handleMetrics(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {\n\tpromhttp.Handler().ServeHTTP(w, req)\n}\n\n// GET /openapi/v2\nfunc handleOpenAPI(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {\n\tswagger := spec.Swagger{\n\t\tSwaggerProps: spec.SwaggerProps{\n\t\t\tSwagger: \"2.0\",\n\t\t\tInfo: &spec.Info{\n\t\t\t\tInfoProps: spec.InfoProps{\n\t\t\t\t\tTitle:   \"Api\",\n\t\t\t\t\tVersion: \"v0\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tPaths: &spec.Paths{\n\t\t\t\tPaths: map[string]spec.PathItem{\n\t\t\t\t\t\"/\":                                    mkPathItem(\"get all paths\"),\n\t\t\t\t\t\"/apis\":                                mkPathItem(\"get available API versions\"),\n\t\t\t\t\t\"/apis/\" + gvk.Group:                   mkPathItem(\"get information of a group\"),\n\t\t\t\t\t\"/apis/\" + gvk.GroupVersion().String(): mkPathItem(\"get available resources\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\trenderJSON(w, swagger, http.StatusOK)\n}\n\nfunc mkPathItem(desc string) spec.PathItem {\n\treturn spec.PathItem{\n\t\tPathItemProps: spec.PathItemProps{\n\t\t\tGet: &spec.Operation{\n\t\t\t\tOperationProps: spec.OperationProps{\n\t\t\t\t\tDescription: desc,\n\t\t\t\t\tConsumes:    []string{\"application/json\"},\n\t\t\t\t\tProduces:    []string{\"application/json\"},\n\t\t\t\t\tResponses: &spec.Responses{\n\t\t\t\t\t\tResponsesProps: spec.ResponsesProps{\n\t\t\t\t\t\t\tStatusCodeResponses: map[int]spec.Response{\n\t\t\t\t\t\t\t\t200: spec.Response{\n\t\t\t\t\t\t\t\t\tRefable: spec.Refable{Ref: spec.MustCreateRef(\"n/a\")},\n\t\t\t\t\t\t\t\t\tResponseProps: spec.ResponseProps{\n\t\t\t\t\t\t\t\t\t\tDescription: \"OK response\",\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\tID: \"tapResourceV0\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\n// GET /version\nfunc handleVersion(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {\n\trenderJSON(w, version.Info{}, http.StatusOK)\n}\n\nfunc renderJSON(w http.ResponseWriter, obj interface{}, status int) {\n\tbytes, err := json.MarshalIndent(obj, \"\", \"  \")\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.WriteHeader(status)\n\tw.Write(bytes)\n}\n\n// TODO: share with api_handlers.go\nfunc renderJSONError(w http.ResponseWriter, err error, status int) {\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\trsp, _ := json.Marshal(jsonError{Error: err.Error()})\n\tw.WriteHeader(status)\n\tw.Write(rsp)\n}\n\n// serverStream provides functionality that satisfies the\n// tap.Tap_TapByResourceServer. This allows the tap APIServer to call\n// GRPCTapServer.TapByResource() directly, rather than make the request to an\n// actual gRPC over the network.\n//\n// TODO: Share this code with streamServer and destinationServer in\n// http_server.go.\ntype serverStream struct {\n\tw   protohttp.FlushableResponseWriter\n\treq *http.Request\n\tlog *logrus.Entry\n}\n\n// Satisfy the grpc.ServerStream interface\nfunc (s serverStream) SetHeader(metadata.MD) error  { return nil }\nfunc (s serverStream) SendHeader(metadata.MD) error { return nil }\nfunc (s serverStream) SetTrailer(metadata.MD)       {}\nfunc (s serverStream) Context() context.Context     { return s.req.Context() }\nfunc (s serverStream) SendMsg(interface{}) error    { return nil }\nfunc (s serverStream) RecvMsg(interface{}) error    { return nil }\n\n// Satisfy the tap.Tap_TapByResourceServer interface\nfunc (s *serverStream) Send(m *pb.TapEvent) error {\n\terr := protohttp.WriteProtoToHTTPResponse(s.w, m)\n\tif err != nil {\n\t\ts.log.Errorf(\"Error writing proto to HTTP Response: %s\", err)\n\t\tprotohttp.WriteErrorToHTTPResponse(s.w, err)\n\t\treturn err\n\t}\n\n\ts.w.Flush()\n\treturn nil\n}\n"
  },
  {
    "path": "viz/tap/api/handlers_test.go",
    "content": "package api\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/sirupsen/logrus\"\n\tauthV1 \"k8s.io/api/authorization/v1\"\n\tk8sFake \"k8s.io/client-go/kubernetes/fake\"\n\tk8sTesting \"k8s.io/client-go/testing\"\n)\n\nfunc TestHandleTap(t *testing.T) {\n\texpectations := []struct {\n\t\treq    *http.Request\n\t\tparams httprouter.Params\n\t\tcode   int\n\t\theader http.Header\n\t\tbody   string\n\t}{\n\t\t{\n\t\t\treq: &http.Request{\n\t\t\t\tURL: &url.URL{\n\t\t\t\t\tPath: \"/apis\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tcode:   http.StatusBadRequest,\n\t\t\theader: http.Header{\"Content-Type\": []string{\"application/json\"}},\n\t\t\tbody:   `{\"error\":\"invalid path: \\\"/apis\\\"\"}`,\n\t\t},\n\t\t{\n\t\t\treq: &http.Request{\n\t\t\t\tURL: &url.URL{\n\t\t\t\t\tPath: \"/apis/tap.linkerd.io/v1alpha1/watch/namespaces/foo/tap\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tcode:   http.StatusForbidden,\n\t\t\theader: http.Header{\"Content-Type\": []string{\"application/json\"}},\n\t\t\tbody:   `{\"error\":\"tap authorization failed (not authorized to access namespaces.tap.linkerd.io), visit https://linkerd.io/tap-rbac for more information\"}`,\n\t\t},\n\t}\n\n\tfor i, exp := range expectations {\n\t\texp := exp // pin\n\n\t\tt.Run(fmt.Sprintf(\"%d handle the tap request\", i), func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\th := &handler{\n\t\t\t\tk8sAPI: k8sAPI,\n\t\t\t\tlog:    logrus.WithField(\"test\", t.Name()),\n\t\t\t}\n\t\t\trecorder := httptest.NewRecorder()\n\t\t\th.handleTap(recorder, exp.req, exp.params)\n\n\t\t\tif recorder.Code != exp.code {\n\t\t\t\tt.Errorf(\"Unexpected code: %d, expected: %d\", recorder.Code, exp.code)\n\t\t\t}\n\t\t\tif diff := deep.Equal(recorder.Header(), exp.header); diff != nil {\n\t\t\t\tt.Errorf(\"Unexpected header: %v\", diff)\n\t\t\t}\n\t\t\tif recorder.Body.String() != exp.body {\n\t\t\t\tt.Errorf(\"Unexpected body: %s, expected: %s\", recorder.Body.String(), exp.body)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHandleTap_ExtraHeaders(t *testing.T) {\n\tk8sAPI, err := k8s.NewFakeAPI()\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t}\n\n\th := &handler{\n\t\tk8sAPI:            k8sAPI,\n\t\tlog:               logrus.WithField(\"test\", t.Name()),\n\t\textraHeaderPrefix: \"X-Remote-Extra-\",\n\t}\n\n\trecorder := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"POST\", \"/apis/tap.linkerd.io/v1alpha1/watch/namespaces/foo/tap\", nil)\n\treq.Header.Set(\"X-Remote-Extra-Foo\", \"bar\")\n\treq.Header.Set(\"X-Remote-Extra-Baz\", \"qux\")\n\n\tparams := httprouter.Params{\n\t\t{Key: \"namespace\", Value: \"foo\"},\n\t}\n\n\th.handleTap(recorder, req, params)\n\n\tsar := getSubjectAccessReview(t, k8sAPI)\n\n\tif len(sar.Spec.Extra) != 2 {\n\t\tt.Errorf(\"Expected 2 extra headers, got %d\", len(sar.Spec.Extra))\n\t}\n\n\tif v, ok := sar.Spec.Extra[\"Foo\"]; !ok || v[0] != \"bar\" {\n\t\tt.Errorf(\"Expected Extra['Foo'] to be ['bar'], got %v\", v)\n\t}\n\tif v, ok := sar.Spec.Extra[\"Baz\"]; !ok || v[0] != \"qux\" {\n\t\tt.Errorf(\"Expected Extra['Baz'] to be ['qux'], got %v\", v)\n\t}\n}\n\nfunc TestHandleTap_ExtraHeadersPrefixCaseInsensitive(t *testing.T) {\n\tk8sAPI, err := k8s.NewFakeAPI()\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t}\n\n\th := &handler{\n\t\tk8sAPI:            k8sAPI,\n\t\tlog:               logrus.WithField(\"test\", t.Name()),\n\t\textraHeaderPrefix: \"x-remote-extra-\",\n\t}\n\n\trecorder := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"POST\", \"/apis/tap.linkerd.io/v1alpha1/watch/namespaces/foo/tap\", nil)\n\treq.Header.Set(\"X-Remote-Extra-Foo\", \"bar\")\n\n\tparams := httprouter.Params{\n\t\t{Key: \"namespace\", Value: \"foo\"},\n\t}\n\n\th.handleTap(recorder, req, params)\n\tsar := getSubjectAccessReview(t, k8sAPI)\n\n\tif v, ok := sar.Spec.Extra[\"Foo\"]; !ok || v[0] != \"bar\" {\n\t\tt.Errorf(\"Expected Extra['Foo'] to be ['bar'], got %v\", v)\n\t}\n}\n\nfunc TestHandleTap_ExtraHeadersEmptyPrefix(t *testing.T) {\n\tk8sAPI, err := k8s.NewFakeAPI()\n\tif err != nil {\n\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t}\n\n\th := &handler{\n\t\tk8sAPI: k8sAPI,\n\t\tlog:    logrus.WithField(\"test\", t.Name()),\n\t}\n\n\trecorder := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"POST\", \"/apis/tap.linkerd.io/v1alpha1/watch/namespaces/foo/tap\", nil)\n\treq.Header.Set(\"X-Remote-Extra-Foo\", \"bar\")\n\treq.Header.Set(\"X-Remote-User\", \"alice\")\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tparams := httprouter.Params{\n\t\t{Key: \"namespace\", Value: \"foo\"},\n\t}\n\n\th.handleTap(recorder, req, params)\n\tsar := getSubjectAccessReview(t, k8sAPI)\n\n\tif len(sar.Spec.Extra) != 0 {\n\t\tt.Errorf(\"Expected 0 extra headers, got %d\", len(sar.Spec.Extra))\n\t}\n}\n\nfunc getSubjectAccessReview(t *testing.T, k8sAPI *k8s.API) *authV1.SubjectAccessReview {\n\tt.Helper()\n\n\tclient := k8sAPI.Client.(*k8sFake.Clientset)\n\tactions := client.Actions()\n\n\tfor _, action := range actions {\n\t\tif action.GetVerb() == \"create\" && action.GetResource().Resource == \"subjectaccessreviews\" {\n\t\t\tcreateAction := action.(k8sTesting.CreateAction)\n\t\t\tobj := createAction.GetObject()\n\t\t\treturn obj.(*authV1.SubjectAccessReview)\n\t\t}\n\t}\n\n\tt.Fatal(\"Expected SubjectAccessReview to be created\")\n\treturn nil\n}\n"
  },
  {
    "path": "viz/tap/api/main.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/admin\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/trace\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nconst defaultDomain = \"cluster.local\"\n\n// Main executes the tap subcommand\nfunc Main(args []string) {\n\tcmd := flag.NewFlagSet(\"tap\", flag.ExitOnError)\n\tapiServerAddr := cmd.String(\"apiserver-addr\", \":8089\", \"address to serve the apiserver on\")\n\tmetricsAddr := cmd.String(\"metrics-addr\", \":9998\", \"address to serve scrapable metrics on\")\n\tkubeConfigPath := cmd.String(\"kubeconfig\", \"\", \"path to kube config\")\n\tapiNamespace := cmd.String(\"api-namespace\", \"linkerd\", \"namespace in which Linkerd is installed\")\n\ttapPort := cmd.Uint(\"tap-port\", 4190, \"proxy tap port to connect to\")\n\tdisableCommonNames := cmd.Bool(\"disable-common-names\", false, \"disable checks for Common Names (for development)\")\n\ttrustDomain := cmd.String(\"identity-trust-domain\", defaultDomain, \"configures the name suffix used for identities\")\n\tenablePprof := cmd.Bool(\"enable-pprof\", false, \"Enable pprof endpoints on the admin server\")\n\n\tvar ignoreHeaders = &stringMap{}\n\tcmd.Var(ignoreHeaders, \"ignore-headers\", \"list of headers to ignore\")\n\n\ttraceCollector := flags.AddTraceFlags(cmd)\n\tflags.ConfigureAndParse(cmd, args)\n\n\tready := false\n\tadminServer := admin.NewServer(*metricsAddr, *enablePprof, &ready)\n\n\tgo func() {\n\t\tlog.Infof(\"starting admin server on %s\", *metricsAddr)\n\t\tif err := adminServer.ListenAndServe(); err != nil {\n\t\t\tlog.Errorf(\"failed to start tap admin server: %s\", err)\n\t\t}\n\t}()\n\n\tctx := context.Background()\n\tstop := make(chan os.Signal, 1)\n\tsignal.Notify(stop, os.Interrupt, syscall.SIGTERM)\n\tk8sAPI, err := k8s.InitializeAPI(\n\t\tctx,\n\t\t*kubeConfigPath,\n\t\ttrue,\n\t\t\"local\",\n\t\tk8s.CJ,\n\t\tk8s.DS,\n\t\tk8s.SS,\n\t\tk8s.Deploy,\n\t\tk8s.Job,\n\t\tk8s.NS,\n\t\tk8s.Pod,\n\t\tk8s.RC,\n\t\tk8s.Svc,\n\t\tk8s.RS,\n\t\tk8s.Node,\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to initialize K8s API: %s\", err)\n\t}\n\tlog.Infof(\"Using trust domain: %s\", *trustDomain)\n\tif *traceCollector != \"\" {\n\t\tif err := trace.InitializeTracing(\"linkerd-tap\", *traceCollector); err != nil {\n\t\t\tlog.Warnf(\"failed to initialize tracing: %s\", err)\n\t\t}\n\t}\n\tgrpcTapServer, err := NewGrpcTapServer(*tapPort, *apiNamespace, *trustDomain, k8sAPI, *ignoreHeaders)\n\tif err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n\tapiServer, err := NewServer(ctx, *apiServerAddr, k8sAPI, grpcTapServer, *disableCommonNames)\n\tif err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n\tk8sAPI.Sync(nil)\n\tgo apiServer.Start(ctx)\n\n\tready = true\n\n\t<-stop\n\tlog.Infof(\"shutting down APIServer on %s\", *apiServerAddr)\n\tapiServer.Shutdown(ctx)\n\tadminServer.Shutdown(ctx)\n}\n\ntype stringMap map[string]bool\n\nfunc (m stringMap) String() string {\n\treturn fmt.Sprintf(\"%v\", map[string]bool(m))\n}\n\nfunc (m stringMap) Set(value string) error {\n\tparts := strings.Split(value, \",\")\n\tfor _, p := range parts {\n\t\tm[p] = true\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "viz/tap/api/server.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tpkgk8s \"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/prometheus\"\n\tpkgTls \"github.com/linkerd/linkerd2/pkg/tls\"\n\tpb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n\tlog \"github.com/sirupsen/logrus\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nconst defaultExtraHeaderPrefix = \"X-Remote-Extra-\"\n\n// Server holds the underlying http server and its config\ntype Server struct {\n\t*http.Server\n\tlistener     net.Listener\n\trouter       *httprouter.Router\n\tallowedNames []string\n\tcertValue    *atomic.Value\n\tlog          *log.Entry\n}\n\n// NewServer creates a new server that implements the Tap APIService.\nfunc NewServer(\n\tctx context.Context,\n\taddr string,\n\tk8sAPI *k8s.API,\n\tgrpcTapServer pb.TapServer,\n\tdisableCommonNames bool,\n) (*Server, error) {\n\tupdateEvent := make(chan struct{})\n\terrEvent := make(chan error)\n\twatcher := pkgTls.NewFsCredsWatcher(pkgk8s.MountPathTLSBase, updateEvent, errEvent).\n\t\tWithFilePaths(pkgk8s.MountPathTLSCrtPEM, pkgk8s.MountPathTLSKeyPEM)\n\tgo func() {\n\t\tif err := watcher.StartWatching(ctx); err != nil {\n\t\t\tlog.Fatalf(\"Failed to start creds watcher: %s\", err)\n\t\t}\n\t}()\n\n\tclientCAPem, allowedNames, usernameHeader, groupHeader, extraHeaderPrefix, err := serverAuth(ctx, k8sAPI)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// for development\n\tif disableCommonNames {\n\t\tallowedNames = []string{}\n\t}\n\n\tlog := log.WithFields(log.Fields{\n\t\t\"component\": \"tap\",\n\t\t\"addr\":      addr,\n\t})\n\n\tclientCertPool := x509.NewCertPool()\n\tclientCertPool.AppendCertsFromPEM([]byte(clientCAPem))\n\n\thttpServer := &http.Server{\n\t\tAddr:              addr,\n\t\tReadHeaderTimeout: 15 * time.Second,\n\t\tTLSConfig: &tls.Config{\n\t\t\tClientAuth: tls.VerifyClientCertIfGiven,\n\t\t\tClientCAs:  clientCertPool,\n\t\t\tMinVersion: tls.VersionTLS13,\n\t\t},\n\t}\n\n\tvar emptyCert atomic.Value\n\th := &handler{\n\t\tk8sAPI:            k8sAPI,\n\t\tusernameHeader:    usernameHeader,\n\t\tgroupHeader:       groupHeader,\n\t\textraHeaderPrefix: extraHeaderPrefix,\n\t\tgrpcTapServer:     grpcTapServer,\n\t\tlog:               log,\n\t}\n\n\tlis, err := net.Listen(\"tcp\", addr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"net.Listen failed with: %w\", err)\n\t}\n\n\ts := &Server{\n\t\tServer:       httpServer,\n\t\tlistener:     lis,\n\t\trouter:       initRouter(h),\n\t\tallowedNames: allowedNames,\n\t\tcertValue:    &emptyCert,\n\t\tlog:          log,\n\t}\n\ts.Handler = prometheus.WithTelemetry(s)\n\thttpServer.TLSConfig.GetCertificate = s.getCertificate\n\n\tif err := watcher.UpdateCert(s.certValue); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to initialized certificate: %w\", err)\n\t}\n\n\tgo watcher.ProcessEvents(log, s.certValue, updateEvent, errEvent)\n\n\treturn s, nil\n}\n\n// Start starts the https server\nfunc (a *Server) Start(ctx context.Context) {\n\ta.log.Infof(\"starting tap API server on %s\", a.Server.Addr)\n\tif err := a.ServeTLS(a.listener, \"\", \"\"); err != nil {\n\t\tif errors.Is(err, http.ErrServerClosed) {\n\t\t\treturn\n\t\t}\n\t\ta.log.Fatal(err)\n\t}\n}\n\nfunc (a *Server) getCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {\n\treturn a.certValue.Load().(*tls.Certificate), nil\n}\n\n// ServeHTTP handles all routes for the Server.\nfunc (a *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {\n\ta.log.Debugf(\"ServeHTTP(): %+v\", req)\n\tif err := a.validate(req); err != nil {\n\t\ta.log.Debug(err)\n\t\trenderJSONError(w, err, http.StatusBadRequest)\n\t} else {\n\t\ta.router.ServeHTTP(w, req)\n\t}\n}\n\n// validate ensures that the request should be honored returning an error otherwise.\nfunc (a *Server) validate(req *http.Request) error {\n\t// if `requestheader-allowed-names` was empty, allow any CN\n\tif len(a.allowedNames) > 0 {\n\t\tfor _, cn := range a.allowedNames {\n\t\t\tfor _, clientCert := range req.TLS.PeerCertificates {\n\t\t\t\t// Check Common Name and Subject Alternate Name(s)\n\t\t\t\tif cn == clientCert.Subject.CommonName || isSubjectAlternateName(clientCert, cn) {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Build the set of certificate names for the error message\n\t\tclientNames := []string{}\n\t\tfor _, clientCert := range req.TLS.PeerCertificates {\n\t\t\tclientNames = append(clientNames, clientCert.Subject.CommonName)\n\t\t}\n\t\treturn fmt.Errorf(\"no valid CN found. allowed names: %s, client names: %s\", a.allowedNames, clientNames)\n\t}\n\treturn nil\n}\n\n// serverAuth parses the relevant data out of a ConfigMap to enable client TLS\n// authentication.\n// kubectl -n kube-system get cm/extension-apiserver-authentication\n// accessible via the extension-apiserver-authentication-reader role\nfunc serverAuth(ctx context.Context, k8sAPI *k8s.API) (string, []string, string, string, string, error) {\n\n\tcm, err := k8sAPI.Client.CoreV1().\n\t\tConfigMaps(metav1.NamespaceSystem).\n\t\tGet(ctx, pkgk8s.ExtensionAPIServerAuthenticationConfigMapName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn \"\", nil, \"\", \"\", \"\", fmt.Errorf(\"failed to load [%s] config: %w\", pkgk8s.ExtensionAPIServerAuthenticationConfigMapName, err)\n\t}\n\n\tclientCAPem, ok := cm.Data[pkgk8s.ExtensionAPIServerAuthenticationRequestHeaderClientCAFileKey]\n\tif !ok {\n\t\treturn \"\", nil, \"\", \"\", \"\", fmt.Errorf(\"no client CA cert available for apiextension-server\")\n\t}\n\n\tallowedNames, err := deserializeStrings(cm.Data[\"requestheader-allowed-names\"])\n\tif err != nil {\n\t\treturn \"\", nil, \"\", \"\", \"\", err\n\t}\n\n\tusernameHeaders, err := deserializeStrings(cm.Data[\"requestheader-username-headers\"])\n\tif err != nil {\n\t\treturn \"\", nil, \"\", \"\", \"\", err\n\t}\n\tusernameHeader := \"\"\n\tif len(usernameHeaders) > 0 {\n\t\tusernameHeader = usernameHeaders[0]\n\t}\n\n\tgroupHeaders, err := deserializeStrings(cm.Data[\"requestheader-group-headers\"])\n\tif err != nil {\n\t\treturn \"\", nil, \"\", \"\", \"\", err\n\t}\n\tgroupHeader := \"\"\n\tif len(groupHeaders) > 0 {\n\t\tgroupHeader = groupHeaders[0]\n\t}\n\n\textraHeaderPrefixes, err := deserializeStrings(cm.Data[\"requestheader-extra-headers-prefix\"])\n\tif err != nil {\n\t\treturn \"\", nil, \"\", \"\", \"\", err\n\t}\n\t// The extra headers prefix is used to identify headers that contain additional\n\t// user attributes for authorization (e.g., \"X-Remote-Extra-\"). These headers are\n\t// forwarded by the Kubernetes API server when acting as an aggregating proxy.\n\t// The prefix is configurable via the --requestheader-extra-headers-prefix flag\n\t// on the API server (defaults to \"X-Remote-Extra-\").\n\textraHeaderPrefix := defaultExtraHeaderPrefix\n\tif len(extraHeaderPrefixes) > 0 && extraHeaderPrefixes[0] != \"\" {\n\t\textraHeaderPrefix = extraHeaderPrefixes[0]\n\t}\n\n\treturn clientCAPem, allowedNames, usernameHeader, groupHeader, extraHeaderPrefix, nil\n}\n\n// copied from https://github.com/kubernetes/apiserver/blob/781c3cd1b3dc5b6f79c68ab0d16fe544600421ef/pkg/server/options/authentication.go#L360\nfunc deserializeStrings(in string) ([]string, error) {\n\tif in == \"\" {\n\t\treturn nil, nil\n\t}\n\tvar ret []string\n\tif err := json.Unmarshal([]byte(in), &ret); err != nil {\n\t\treturn nil, err\n\t}\n\treturn ret, nil\n}\n\n// isSubjectAlternateName checks all applicable fields within the certificate for a match to the provided name.\n// See https://tools.ietf.org/html/rfc5280#section-4.2.1.6 for information about Subject Alternate Name.\nfunc isSubjectAlternateName(cert *x509.Certificate, name string) bool {\n\tfor _, dnsName := range cert.DNSNames {\n\t\tif dnsName == name {\n\t\t\treturn true\n\t\t}\n\t}\n\tfor _, emailAddress := range cert.EmailAddresses {\n\t\tif emailAddress == name {\n\t\t\treturn true\n\t\t}\n\t}\n\tfor _, ip := range cert.IPAddresses {\n\t\tif ip.String() == name {\n\t\t\treturn true\n\t\t}\n\t}\n\tfor _, url := range cert.URIs {\n\t\tif url.String() == name {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "viz/tap/api/server_test.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\tk8sutils \"github.com/linkerd/linkerd2/pkg/k8s\"\n)\n\nfunc TestAPIServerAuth(t *testing.T) {\n\texpectations := []struct {\n\t\tk8sRes            []string\n\t\tclientCAPem       string\n\t\tallowedNames      []string\n\t\tusernameHeader    string\n\t\tgroupHeader       string\n\t\textraHeaderPrefix string\n\t\terr               error\n\t}{\n\t\t{\n\t\t\terr: fmt.Errorf(\"failed to load [%s] config: configmaps %q not found\", k8sutils.ExtensionAPIServerAuthenticationConfigMapName, k8sutils.ExtensionAPIServerAuthenticationConfigMapName),\n\t\t},\n\t\t{\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: extension-apiserver-authentication\n  namespace: kube-system\ndata:\n  client-ca-file: 'client-ca-file'\n  requestheader-allowed-names: '[\"name1\", \"name2\"]'\n  requestheader-client-ca-file: 'requestheader-client-ca-file'\n  requestheader-extra-headers-prefix: '[\"X-Remote-Extra-\"]'\n  requestheader-group-headers: '[\"X-Remote-Group\"]'\n  requestheader-username-headers: '[\"X-Remote-User\"]'\n`,\n\t\t\t},\n\t\t\tclientCAPem:       \"requestheader-client-ca-file\",\n\t\t\tallowedNames:      []string{\"name1\", \"name2\"},\n\t\t\tusernameHeader:    \"X-Remote-User\",\n\t\t\tgroupHeader:       \"X-Remote-Group\",\n\t\t\textraHeaderPrefix: \"X-Remote-Extra-\",\n\t\t\terr:               nil,\n\t\t},\n\t\t{\n\t\t\tk8sRes: []string{`\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: extension-apiserver-authentication\n  namespace: kube-system\ndata:\n  requestheader-client-ca-file: 'requestheader-client-ca-file'\n`},\n\t\t\tclientCAPem:       \"requestheader-client-ca-file\",\n\t\t\tallowedNames:      nil,\n\t\t\tusernameHeader:    \"\",\n\t\t\tgroupHeader:       \"\",\n\t\t\textraHeaderPrefix: defaultExtraHeaderPrefix,\n\t\t\terr:               nil,\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tfor i, exp := range expectations {\n\t\texp := exp // pin\n\n\t\tt.Run(fmt.Sprintf(\"%d parses the apiServerAuth ConfigMap\", i), func(t *testing.T) {\n\t\t\tk8sAPI, err := k8s.NewFakeAPI(exp.k8sRes...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"NewFakeAPI returned an error: %s\", err)\n\t\t\t}\n\n\t\t\tclientCAPem, allowedNames, usernameHeader, groupHeader, extraHeaderPrefix, err := serverAuth(ctx, k8sAPI)\n\n\t\t\tif err != nil && exp.err != nil {\n\t\t\t\tif err.Error() != exp.err.Error() {\n\t\t\t\t\tt.Errorf(\"apiServerAuth returned unexpected error: %q, expected: %q\", err, exp.err)\n\t\t\t\t}\n\t\t\t} else if err != nil {\n\t\t\t\tt.Fatalf(\"Unexpected error: %s\", err)\n\t\t\t} else if exp.err != nil {\n\t\t\t\tt.Fatalf(\"Did not encounter expected error: %s\", err)\n\t\t\t}\n\n\t\t\tif clientCAPem != exp.clientCAPem {\n\t\t\t\tt.Errorf(\"apiServerAuth returned unexpected clientCAPem: %q, expected: %q\", clientCAPem, exp.clientCAPem)\n\t\t\t}\n\t\t\tif diff := deep.Equal(allowedNames, exp.allowedNames); diff != nil {\n\t\t\t\tt.Errorf(\"%v\", diff)\n\t\t\t}\n\t\t\tif usernameHeader != exp.usernameHeader {\n\t\t\t\tt.Errorf(\"apiServerAuth returned unexpected usernameHeader: %q, expected: %q\", usernameHeader, exp.usernameHeader)\n\t\t\t}\n\t\t\tif groupHeader != exp.groupHeader {\n\t\t\t\tt.Errorf(\"apiServerAuth returned unexpected groupHeader: %q, expected: %q\", groupHeader, exp.groupHeader)\n\t\t\t}\n\t\t\tif extraHeaderPrefix != exp.extraHeaderPrefix {\n\t\t\t\tt.Errorf(\"apiServerAuth returned unexpected extraHeaderPrefix: %q, expected: %q\", extraHeaderPrefix, exp.extraHeaderPrefix)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestValidate(t *testing.T) {\n\tcert := testCertificate()\n\tcert.Subject.CommonName = \"name-any\"\n\n\ttls := tls.ConnectionState{PeerCertificates: []*x509.Certificate{&cert}}\n\n\treq := http.Request{TLS: &tls}\n\n\tserver := Server{}\n\tif err := server.validate(&req); err != nil {\n\t\tt.Fatalf(\"No error expected for %q but encountered %q\", cert.Subject.CommonName, err.Error())\n\t}\n}\n\nfunc TestValidate_ClientAllowed(t *testing.T) {\n\tcert := testCertificate()\n\tcert.Subject.CommonName = \"name-trusted\"\n\n\ttls := tls.ConnectionState{PeerCertificates: []*x509.Certificate{&cert}}\n\n\treq := http.Request{TLS: &tls}\n\n\tserver := Server{allowedNames: []string{\"name-trusted\"}}\n\tif err := server.validate(&req); err != nil {\n\t\tt.Fatalf(\"No error expected for %q but encountered %q\", cert.Subject.CommonName, err.Error())\n\t}\n}\n\nfunc TestValidate_ClientAllowedViaSAN(t *testing.T) {\n\tcert := testCertificate()\n\tcert.Subject.CommonName = \"name-any\"\n\n\ttls := tls.ConnectionState{PeerCertificates: []*x509.Certificate{&cert}}\n\n\treq := http.Request{TLS: &tls}\n\n\tserver := Server{allowedNames: []string{\"linkerd.io\"}}\n\tif err := server.validate(&req); err != nil {\n\t\tt.Fatalf(\"No error expected for %q but encountered %q\", cert.Subject.CommonName, err.Error())\n\t}\n}\n\nfunc TestValidate_ClientNotAllowed(t *testing.T) {\n\tcert := testCertificate()\n\tcert.Subject.CommonName = \"name-untrusted\"\n\n\ttls := tls.ConnectionState{PeerCertificates: []*x509.Certificate{&cert}}\n\n\treq := http.Request{TLS: &tls}\n\n\tserver := Server{allowedNames: []string{\"name-trusted\"}}\n\tif err := server.validate(&req); err == nil {\n\t\tt.Fatalf(\"Expected request to be rejected for %q\", cert.Subject.CommonName)\n\t}\n}\n\nfunc TestIsSubjectAlternateName(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\texpected bool\n\t}{\n\t\t{\n\t\t\tname:     \"linkerd.io\",\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"root@localhost\",\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"192.168.1.1\",\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"http://localhost/api/test\",\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"mystique\",\n\t\t\texpected: false,\n\t\t},\n\t}\n\n\tcert := testCertificate()\n\tfor _, tc := range testCases {\n\t\ttc := tc // pin\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tactual := isSubjectAlternateName(&cert, tc.name)\n\t\t\tif actual != tc.expected {\n\t\t\t\tt.Fatalf(\"expected %t, but got %t\", tc.expected, actual)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc testCertificate() x509.Certificate {\n\turi, _ := url.Parse(\"http://localhost/api/test\")\n\tcert := x509.Certificate{\n\t\tSubject: pkix.Name{\n\t\t\tCommonName: \"linkerd-test\",\n\t\t},\n\t\tDNSNames: []string{\n\t\t\t\"localhost\",\n\t\t\t\"linkerd.io\",\n\t\t},\n\t\tEmailAddresses: []string{\n\t\t\t\"root@localhost\",\n\t\t},\n\t\tIPAddresses: []net.IP{\n\t\t\tnet.IPv4(127, 0, 0, 1),\n\t\t\tnet.IPv4(192, 168, 1, 1),\n\t\t},\n\t\tURIs: []*url.URL{\n\t\t\turi,\n\t\t},\n\t}\n\treturn cert\n}\n"
  },
  {
    "path": "viz/tap/cmd/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/linkerd/linkerd2/viz/tap/api\"\n\t\"github.com/linkerd/linkerd2/viz/tap/injector\"\n)\n\nfunc main() {\n\tif len(os.Args) < 2 {\n\t\tfmt.Println(\"expected a subcommand\")\n\t\tos.Exit(1)\n\t}\n\tswitch os.Args[1] {\n\tcase \"api\":\n\t\tapi.Main(os.Args[2:])\n\tcase \"injector\":\n\t\tinjector.Main(os.Args[2:])\n\t}\n}\n"
  },
  {
    "path": "viz/tap/gen/tap/viz_tap.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.35.2\n// \tprotoc        v6.32.1\n// source: viz_tap.proto\n\npackage tap\n\nimport (\n\tduration \"github.com/golang/protobuf/ptypes/duration\"\n\tnet \"github.com/linkerd/linkerd2/controller/gen/common/net\"\n\tviz \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\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 TapEvent_ProxyDirection int32\n\nconst (\n\tTapEvent_UNKNOWN  TapEvent_ProxyDirection = 0\n\tTapEvent_INBOUND  TapEvent_ProxyDirection = 1\n\tTapEvent_OUTBOUND TapEvent_ProxyDirection = 2\n)\n\n// Enum value maps for TapEvent_ProxyDirection.\nvar (\n\tTapEvent_ProxyDirection_name = map[int32]string{\n\t\t0: \"UNKNOWN\",\n\t\t1: \"INBOUND\",\n\t\t2: \"OUTBOUND\",\n\t}\n\tTapEvent_ProxyDirection_value = map[string]int32{\n\t\t\"UNKNOWN\":  0,\n\t\t\"INBOUND\":  1,\n\t\t\"OUTBOUND\": 2,\n\t}\n)\n\nfunc (x TapEvent_ProxyDirection) Enum() *TapEvent_ProxyDirection {\n\tp := new(TapEvent_ProxyDirection)\n\t*p = x\n\treturn p\n}\n\nfunc (x TapEvent_ProxyDirection) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (TapEvent_ProxyDirection) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_viz_tap_proto_enumTypes[0].Descriptor()\n}\n\nfunc (TapEvent_ProxyDirection) Type() protoreflect.EnumType {\n\treturn &file_viz_tap_proto_enumTypes[0]\n}\n\nfunc (x TapEvent_ProxyDirection) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use TapEvent_ProxyDirection.Descriptor instead.\nfunc (TapEvent_ProxyDirection) EnumDescriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{2, 0}\n}\n\n// Deprecated: Marked as deprecated in viz_tap.proto.\ntype TapRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Target:\n\t//\n\t//\t*TapRequest_Pod\n\t//\t*TapRequest_Deployment\n\tTarget isTapRequest_Target `protobuf_oneof:\"target\"`\n\t// validation of these fields happens on the server\n\tMaxRps    float32 `protobuf:\"fixed32,3,opt,name=maxRps,proto3\" json:\"maxRps,omitempty\"`\n\tToPort    uint32  `protobuf:\"varint,4,opt,name=toPort,proto3\" json:\"toPort,omitempty\"`\n\tToIP      string  `protobuf:\"bytes,5,opt,name=toIP,proto3\" json:\"toIP,omitempty\"`\n\tFromPort  uint32  `protobuf:\"varint,6,opt,name=fromPort,proto3\" json:\"fromPort,omitempty\"`\n\tFromIP    string  `protobuf:\"bytes,7,opt,name=fromIP,proto3\" json:\"fromIP,omitempty\"`\n\tScheme    string  `protobuf:\"bytes,8,opt,name=scheme,proto3\" json:\"scheme,omitempty\"`\n\tMethod    string  `protobuf:\"bytes,9,opt,name=method,proto3\" json:\"method,omitempty\"`\n\tAuthority string  `protobuf:\"bytes,10,opt,name=authority,proto3\" json:\"authority,omitempty\"`\n\tPath      string  `protobuf:\"bytes,11,opt,name=path,proto3\" json:\"path,omitempty\"`\n}\n\nfunc (x *TapRequest) Reset() {\n\t*x = TapRequest{}\n\tmi := &file_viz_tap_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapRequest) ProtoMessage() {}\n\nfunc (x *TapRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapRequest.ProtoReflect.Descriptor instead.\nfunc (*TapRequest) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (m *TapRequest) GetTarget() isTapRequest_Target {\n\tif m != nil {\n\t\treturn m.Target\n\t}\n\treturn nil\n}\n\nfunc (x *TapRequest) GetPod() string {\n\tif x, ok := x.GetTarget().(*TapRequest_Pod); ok {\n\t\treturn x.Pod\n\t}\n\treturn \"\"\n}\n\nfunc (x *TapRequest) GetDeployment() string {\n\tif x, ok := x.GetTarget().(*TapRequest_Deployment); ok {\n\t\treturn x.Deployment\n\t}\n\treturn \"\"\n}\n\nfunc (x *TapRequest) GetMaxRps() float32 {\n\tif x != nil {\n\t\treturn x.MaxRps\n\t}\n\treturn 0\n}\n\nfunc (x *TapRequest) GetToPort() uint32 {\n\tif x != nil {\n\t\treturn x.ToPort\n\t}\n\treturn 0\n}\n\nfunc (x *TapRequest) GetToIP() string {\n\tif x != nil {\n\t\treturn x.ToIP\n\t}\n\treturn \"\"\n}\n\nfunc (x *TapRequest) GetFromPort() uint32 {\n\tif x != nil {\n\t\treturn x.FromPort\n\t}\n\treturn 0\n}\n\nfunc (x *TapRequest) GetFromIP() string {\n\tif x != nil {\n\t\treturn x.FromIP\n\t}\n\treturn \"\"\n}\n\nfunc (x *TapRequest) GetScheme() string {\n\tif x != nil {\n\t\treturn x.Scheme\n\t}\n\treturn \"\"\n}\n\nfunc (x *TapRequest) GetMethod() string {\n\tif x != nil {\n\t\treturn x.Method\n\t}\n\treturn \"\"\n}\n\nfunc (x *TapRequest) GetAuthority() string {\n\tif x != nil {\n\t\treturn x.Authority\n\t}\n\treturn \"\"\n}\n\nfunc (x *TapRequest) GetPath() string {\n\tif x != nil {\n\t\treturn x.Path\n\t}\n\treturn \"\"\n}\n\ntype isTapRequest_Target interface {\n\tisTapRequest_Target()\n}\n\ntype TapRequest_Pod struct {\n\tPod string `protobuf:\"bytes,1,opt,name=pod,proto3,oneof\"`\n}\n\ntype TapRequest_Deployment struct {\n\tDeployment string `protobuf:\"bytes,2,opt,name=deployment,proto3,oneof\"`\n}\n\nfunc (*TapRequest_Pod) isTapRequest_Target() {}\n\nfunc (*TapRequest_Deployment) isTapRequest_Target() {}\n\n// A tap request over kubernetes resources.\n//\n// This is used only by the tap APIServer.\ntype TapByResourceRequest struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Describes the kubernetes pods that should be tapped.\n\tTarget *viz.ResourceSelection `protobuf:\"bytes,1,opt,name=target,proto3\" json:\"target,omitempty\"`\n\t// Selects over events to be reported.\n\tMatch *TapByResourceRequest_Match `protobuf:\"bytes,2,opt,name=match,proto3\" json:\"match,omitempty\"`\n\t// Limits the number of events to be inspected.\n\tMaxRps float32 `protobuf:\"fixed32,3,opt,name=maxRps,proto3\" json:\"maxRps,omitempty\"`\n\t// Conditionally extracts components from requests and responses to include\n\t// in tap events\n\tExtract *TapByResourceRequest_Extract `protobuf:\"bytes,4,opt,name=extract,proto3\" json:\"extract,omitempty\"`\n}\n\nfunc (x *TapByResourceRequest) Reset() {\n\t*x = TapByResourceRequest{}\n\tmi := &file_viz_tap_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapByResourceRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapByResourceRequest) ProtoMessage() {}\n\nfunc (x *TapByResourceRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapByResourceRequest.ProtoReflect.Descriptor instead.\nfunc (*TapByResourceRequest) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *TapByResourceRequest) GetTarget() *viz.ResourceSelection {\n\tif x != nil {\n\t\treturn x.Target\n\t}\n\treturn nil\n}\n\nfunc (x *TapByResourceRequest) GetMatch() *TapByResourceRequest_Match {\n\tif x != nil {\n\t\treturn x.Match\n\t}\n\treturn nil\n}\n\nfunc (x *TapByResourceRequest) GetMaxRps() float32 {\n\tif x != nil {\n\t\treturn x.MaxRps\n\t}\n\treturn 0\n}\n\nfunc (x *TapByResourceRequest) GetExtract() *TapByResourceRequest_Extract {\n\tif x != nil {\n\t\treturn x.Extract\n\t}\n\treturn nil\n}\n\n// This is used only by the tap APIServer.\ntype TapEvent struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSource          *net.TcpAddress         `protobuf:\"bytes,1,opt,name=source,proto3\" json:\"source,omitempty\"`\n\tSourceMeta      *TapEvent_EndpointMeta  `protobuf:\"bytes,5,opt,name=source_meta,json=sourceMeta,proto3\" json:\"source_meta,omitempty\"`\n\tDestination     *net.TcpAddress         `protobuf:\"bytes,2,opt,name=destination,proto3\" json:\"destination,omitempty\"`\n\tDestinationMeta *TapEvent_EndpointMeta  `protobuf:\"bytes,4,opt,name=destination_meta,json=destinationMeta,proto3\" json:\"destination_meta,omitempty\"`\n\tRouteMeta       *TapEvent_RouteMeta     `protobuf:\"bytes,7,opt,name=route_meta,json=routeMeta,proto3\" json:\"route_meta,omitempty\"`\n\tProxyDirection  TapEvent_ProxyDirection `protobuf:\"varint,6,opt,name=proxy_direction,json=proxyDirection,proto3,enum=linkerd2.tap.TapEvent_ProxyDirection\" json:\"proxy_direction,omitempty\"`\n\t// Types that are assignable to Event:\n\t//\n\t//\t*TapEvent_Http_\n\tEvent isTapEvent_Event `protobuf_oneof:\"event\"`\n}\n\nfunc (x *TapEvent) Reset() {\n\t*x = TapEvent{}\n\tmi := &file_viz_tap_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapEvent) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapEvent) ProtoMessage() {}\n\nfunc (x *TapEvent) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapEvent.ProtoReflect.Descriptor instead.\nfunc (*TapEvent) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *TapEvent) GetSource() *net.TcpAddress {\n\tif x != nil {\n\t\treturn x.Source\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent) GetSourceMeta() *TapEvent_EndpointMeta {\n\tif x != nil {\n\t\treturn x.SourceMeta\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent) GetDestination() *net.TcpAddress {\n\tif x != nil {\n\t\treturn x.Destination\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent) GetDestinationMeta() *TapEvent_EndpointMeta {\n\tif x != nil {\n\t\treturn x.DestinationMeta\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent) GetRouteMeta() *TapEvent_RouteMeta {\n\tif x != nil {\n\t\treturn x.RouteMeta\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent) GetProxyDirection() TapEvent_ProxyDirection {\n\tif x != nil {\n\t\treturn x.ProxyDirection\n\t}\n\treturn TapEvent_UNKNOWN\n}\n\nfunc (m *TapEvent) GetEvent() isTapEvent_Event {\n\tif m != nil {\n\t\treturn m.Event\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent) GetHttp() *TapEvent_Http {\n\tif x, ok := x.GetEvent().(*TapEvent_Http_); ok {\n\t\treturn x.Http\n\t}\n\treturn nil\n}\n\ntype isTapEvent_Event interface {\n\tisTapEvent_Event()\n}\n\ntype TapEvent_Http_ struct {\n\tHttp *TapEvent_Http `protobuf:\"bytes,3,opt,name=http,proto3,oneof\"`\n}\n\nfunc (*TapEvent_Http_) isTapEvent_Event() {}\n\ntype TapByResourceRequest_Match struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Match:\n\t//\n\t//\t*TapByResourceRequest_Match_All\n\t//\t*TapByResourceRequest_Match_Any\n\t//\t*TapByResourceRequest_Match_Not\n\t//\t*TapByResourceRequest_Match_Destinations\n\t//\t*TapByResourceRequest_Match_Http_\n\tMatch isTapByResourceRequest_Match_Match `protobuf_oneof:\"match\"`\n}\n\nfunc (x *TapByResourceRequest_Match) Reset() {\n\t*x = TapByResourceRequest_Match{}\n\tmi := &file_viz_tap_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapByResourceRequest_Match) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapByResourceRequest_Match) ProtoMessage() {}\n\nfunc (x *TapByResourceRequest_Match) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapByResourceRequest_Match.ProtoReflect.Descriptor instead.\nfunc (*TapByResourceRequest_Match) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{1, 0}\n}\n\nfunc (m *TapByResourceRequest_Match) GetMatch() isTapByResourceRequest_Match_Match {\n\tif m != nil {\n\t\treturn m.Match\n\t}\n\treturn nil\n}\n\nfunc (x *TapByResourceRequest_Match) GetAll() *TapByResourceRequest_Match_Seq {\n\tif x, ok := x.GetMatch().(*TapByResourceRequest_Match_All); ok {\n\t\treturn x.All\n\t}\n\treturn nil\n}\n\nfunc (x *TapByResourceRequest_Match) GetAny() *TapByResourceRequest_Match_Seq {\n\tif x, ok := x.GetMatch().(*TapByResourceRequest_Match_Any); ok {\n\t\treturn x.Any\n\t}\n\treturn nil\n}\n\nfunc (x *TapByResourceRequest_Match) GetNot() *TapByResourceRequest_Match {\n\tif x, ok := x.GetMatch().(*TapByResourceRequest_Match_Not); ok {\n\t\treturn x.Not\n\t}\n\treturn nil\n}\n\nfunc (x *TapByResourceRequest_Match) GetDestinations() *viz.ResourceSelection {\n\tif x, ok := x.GetMatch().(*TapByResourceRequest_Match_Destinations); ok {\n\t\treturn x.Destinations\n\t}\n\treturn nil\n}\n\nfunc (x *TapByResourceRequest_Match) GetHttp() *TapByResourceRequest_Match_Http {\n\tif x, ok := x.GetMatch().(*TapByResourceRequest_Match_Http_); ok {\n\t\treturn x.Http\n\t}\n\treturn nil\n}\n\ntype isTapByResourceRequest_Match_Match interface {\n\tisTapByResourceRequest_Match_Match()\n}\n\ntype TapByResourceRequest_Match_All struct {\n\t// If empty, matches all messages.\n\tAll *TapByResourceRequest_Match_Seq `protobuf:\"bytes,1,opt,name=all,proto3,oneof\"`\n}\n\ntype TapByResourceRequest_Match_Any struct {\n\t// If empty, matches no messages.\n\tAny *TapByResourceRequest_Match_Seq `protobuf:\"bytes,2,opt,name=any,proto3,oneof\"`\n}\n\ntype TapByResourceRequest_Match_Not struct {\n\t// Inverts the inner match.\n\tNot *TapByResourceRequest_Match `protobuf:\"bytes,3,opt,name=not,proto3,oneof\"`\n}\n\ntype TapByResourceRequest_Match_Destinations struct {\n\t// Matches events being sent to any of the selected destinations.\n\tDestinations *viz.ResourceSelection `protobuf:\"bytes,4,opt,name=destinations,proto3,oneof\"`\n}\n\ntype TapByResourceRequest_Match_Http_ struct {\n\t// Matches HTTP requests by their metadata.\n\tHttp *TapByResourceRequest_Match_Http `protobuf:\"bytes,5,opt,name=http,proto3,oneof\"`\n}\n\nfunc (*TapByResourceRequest_Match_All) isTapByResourceRequest_Match_Match() {}\n\nfunc (*TapByResourceRequest_Match_Any) isTapByResourceRequest_Match_Match() {}\n\nfunc (*TapByResourceRequest_Match_Not) isTapByResourceRequest_Match_Match() {}\n\nfunc (*TapByResourceRequest_Match_Destinations) isTapByResourceRequest_Match_Match() {}\n\nfunc (*TapByResourceRequest_Match_Http_) isTapByResourceRequest_Match_Match() {}\n\ntype TapByResourceRequest_Extract struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Extract:\n\t//\n\t//\t*TapByResourceRequest_Extract_Http_\n\tExtract isTapByResourceRequest_Extract_Extract `protobuf_oneof:\"extract\"`\n}\n\nfunc (x *TapByResourceRequest_Extract) Reset() {\n\t*x = TapByResourceRequest_Extract{}\n\tmi := &file_viz_tap_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapByResourceRequest_Extract) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapByResourceRequest_Extract) ProtoMessage() {}\n\nfunc (x *TapByResourceRequest_Extract) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapByResourceRequest_Extract.ProtoReflect.Descriptor instead.\nfunc (*TapByResourceRequest_Extract) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{1, 1}\n}\n\nfunc (m *TapByResourceRequest_Extract) GetExtract() isTapByResourceRequest_Extract_Extract {\n\tif m != nil {\n\t\treturn m.Extract\n\t}\n\treturn nil\n}\n\nfunc (x *TapByResourceRequest_Extract) GetHttp() *TapByResourceRequest_Extract_Http {\n\tif x, ok := x.GetExtract().(*TapByResourceRequest_Extract_Http_); ok {\n\t\treturn x.Http\n\t}\n\treturn nil\n}\n\ntype isTapByResourceRequest_Extract_Extract interface {\n\tisTapByResourceRequest_Extract_Extract()\n}\n\ntype TapByResourceRequest_Extract_Http_ struct {\n\tHttp *TapByResourceRequest_Extract_Http `protobuf:\"bytes,1,opt,name=http,proto3,oneof\"`\n}\n\nfunc (*TapByResourceRequest_Extract_Http_) isTapByResourceRequest_Extract_Extract() {}\n\ntype TapByResourceRequest_Match_Seq struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tMatches []*TapByResourceRequest_Match `protobuf:\"bytes,1,rep,name=matches,proto3\" json:\"matches,omitempty\"`\n}\n\nfunc (x *TapByResourceRequest_Match_Seq) Reset() {\n\t*x = TapByResourceRequest_Match_Seq{}\n\tmi := &file_viz_tap_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapByResourceRequest_Match_Seq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapByResourceRequest_Match_Seq) ProtoMessage() {}\n\nfunc (x *TapByResourceRequest_Match_Seq) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapByResourceRequest_Match_Seq.ProtoReflect.Descriptor instead.\nfunc (*TapByResourceRequest_Match_Seq) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{1, 0, 0}\n}\n\nfunc (x *TapByResourceRequest_Match_Seq) GetMatches() []*TapByResourceRequest_Match {\n\tif x != nil {\n\t\treturn x.Matches\n\t}\n\treturn nil\n}\n\ntype TapByResourceRequest_Match_Http struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Match:\n\t//\n\t//\t*TapByResourceRequest_Match_Http_Scheme\n\t//\t*TapByResourceRequest_Match_Http_Method\n\t//\t*TapByResourceRequest_Match_Http_Authority\n\t//\t*TapByResourceRequest_Match_Http_Path\n\tMatch isTapByResourceRequest_Match_Http_Match `protobuf_oneof:\"match\"`\n}\n\nfunc (x *TapByResourceRequest_Match_Http) Reset() {\n\t*x = TapByResourceRequest_Match_Http{}\n\tmi := &file_viz_tap_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapByResourceRequest_Match_Http) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapByResourceRequest_Match_Http) ProtoMessage() {}\n\nfunc (x *TapByResourceRequest_Match_Http) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapByResourceRequest_Match_Http.ProtoReflect.Descriptor instead.\nfunc (*TapByResourceRequest_Match_Http) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{1, 0, 1}\n}\n\nfunc (m *TapByResourceRequest_Match_Http) GetMatch() isTapByResourceRequest_Match_Http_Match {\n\tif m != nil {\n\t\treturn m.Match\n\t}\n\treturn nil\n}\n\nfunc (x *TapByResourceRequest_Match_Http) GetScheme() string {\n\tif x, ok := x.GetMatch().(*TapByResourceRequest_Match_Http_Scheme); ok {\n\t\treturn x.Scheme\n\t}\n\treturn \"\"\n}\n\nfunc (x *TapByResourceRequest_Match_Http) GetMethod() string {\n\tif x, ok := x.GetMatch().(*TapByResourceRequest_Match_Http_Method); ok {\n\t\treturn x.Method\n\t}\n\treturn \"\"\n}\n\nfunc (x *TapByResourceRequest_Match_Http) GetAuthority() string {\n\tif x, ok := x.GetMatch().(*TapByResourceRequest_Match_Http_Authority); ok {\n\t\treturn x.Authority\n\t}\n\treturn \"\"\n}\n\nfunc (x *TapByResourceRequest_Match_Http) GetPath() string {\n\tif x, ok := x.GetMatch().(*TapByResourceRequest_Match_Http_Path); ok {\n\t\treturn x.Path\n\t}\n\treturn \"\"\n}\n\ntype isTapByResourceRequest_Match_Http_Match interface {\n\tisTapByResourceRequest_Match_Http_Match()\n}\n\ntype TapByResourceRequest_Match_Http_Scheme struct {\n\tScheme string `protobuf:\"bytes,1,opt,name=scheme,proto3,oneof\"`\n}\n\ntype TapByResourceRequest_Match_Http_Method struct {\n\tMethod string `protobuf:\"bytes,2,opt,name=method,proto3,oneof\"`\n}\n\ntype TapByResourceRequest_Match_Http_Authority struct {\n\tAuthority string `protobuf:\"bytes,3,opt,name=authority,proto3,oneof\"`\n}\n\ntype TapByResourceRequest_Match_Http_Path struct {\n\tPath string `protobuf:\"bytes,4,opt,name=path,proto3,oneof\"`\n}\n\nfunc (*TapByResourceRequest_Match_Http_Scheme) isTapByResourceRequest_Match_Http_Match() {}\n\nfunc (*TapByResourceRequest_Match_Http_Method) isTapByResourceRequest_Match_Http_Match() {}\n\nfunc (*TapByResourceRequest_Match_Http_Authority) isTapByResourceRequest_Match_Http_Match() {}\n\nfunc (*TapByResourceRequest_Match_Http_Path) isTapByResourceRequest_Match_Http_Match() {}\n\ntype TapByResourceRequest_Extract_Http struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Extract:\n\t//\n\t//\t*TapByResourceRequest_Extract_Http_Headers_\n\tExtract isTapByResourceRequest_Extract_Http_Extract `protobuf_oneof:\"extract\"`\n}\n\nfunc (x *TapByResourceRequest_Extract_Http) Reset() {\n\t*x = TapByResourceRequest_Extract_Http{}\n\tmi := &file_viz_tap_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapByResourceRequest_Extract_Http) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapByResourceRequest_Extract_Http) ProtoMessage() {}\n\nfunc (x *TapByResourceRequest_Extract_Http) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapByResourceRequest_Extract_Http.ProtoReflect.Descriptor instead.\nfunc (*TapByResourceRequest_Extract_Http) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{1, 1, 0}\n}\n\nfunc (m *TapByResourceRequest_Extract_Http) GetExtract() isTapByResourceRequest_Extract_Http_Extract {\n\tif m != nil {\n\t\treturn m.Extract\n\t}\n\treturn nil\n}\n\nfunc (x *TapByResourceRequest_Extract_Http) GetHeaders() *TapByResourceRequest_Extract_Http_Headers {\n\tif x, ok := x.GetExtract().(*TapByResourceRequest_Extract_Http_Headers_); ok {\n\t\treturn x.Headers\n\t}\n\treturn nil\n}\n\ntype isTapByResourceRequest_Extract_Http_Extract interface {\n\tisTapByResourceRequest_Extract_Http_Extract()\n}\n\ntype TapByResourceRequest_Extract_Http_Headers_ struct {\n\tHeaders *TapByResourceRequest_Extract_Http_Headers `protobuf:\"bytes,1,opt,name=headers,proto3,oneof\"`\n}\n\nfunc (*TapByResourceRequest_Extract_Http_Headers_) isTapByResourceRequest_Extract_Http_Extract() {}\n\ntype TapByResourceRequest_Extract_Http_Headers struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *TapByResourceRequest_Extract_Http_Headers) Reset() {\n\t*x = TapByResourceRequest_Extract_Http_Headers{}\n\tmi := &file_viz_tap_proto_msgTypes[8]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapByResourceRequest_Extract_Http_Headers) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapByResourceRequest_Extract_Http_Headers) ProtoMessage() {}\n\nfunc (x *TapByResourceRequest_Extract_Http_Headers) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapByResourceRequest_Extract_Http_Headers.ProtoReflect.Descriptor instead.\nfunc (*TapByResourceRequest_Extract_Http_Headers) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{1, 1, 0, 0}\n}\n\ntype TapEvent_EndpointMeta struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLabels map[string]string `protobuf:\"bytes,1,rep,name=labels,proto3\" json:\"labels,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"bytes,2,opt,name=value,proto3\"`\n}\n\nfunc (x *TapEvent_EndpointMeta) Reset() {\n\t*x = TapEvent_EndpointMeta{}\n\tmi := &file_viz_tap_proto_msgTypes[9]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapEvent_EndpointMeta) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapEvent_EndpointMeta) ProtoMessage() {}\n\nfunc (x *TapEvent_EndpointMeta) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapEvent_EndpointMeta.ProtoReflect.Descriptor instead.\nfunc (*TapEvent_EndpointMeta) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{2, 0}\n}\n\nfunc (x *TapEvent_EndpointMeta) GetLabels() map[string]string {\n\tif x != nil {\n\t\treturn x.Labels\n\t}\n\treturn nil\n}\n\ntype TapEvent_RouteMeta struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLabels map[string]string `protobuf:\"bytes,1,rep,name=labels,proto3\" json:\"labels,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"bytes,2,opt,name=value,proto3\"`\n}\n\nfunc (x *TapEvent_RouteMeta) Reset() {\n\t*x = TapEvent_RouteMeta{}\n\tmi := &file_viz_tap_proto_msgTypes[10]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapEvent_RouteMeta) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapEvent_RouteMeta) ProtoMessage() {}\n\nfunc (x *TapEvent_RouteMeta) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapEvent_RouteMeta.ProtoReflect.Descriptor instead.\nfunc (*TapEvent_RouteMeta) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{2, 1}\n}\n\nfunc (x *TapEvent_RouteMeta) GetLabels() map[string]string {\n\tif x != nil {\n\t\treturn x.Labels\n\t}\n\treturn nil\n}\n\ntype TapEvent_Http struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Event:\n\t//\n\t//\t*TapEvent_Http_RequestInit_\n\t//\t*TapEvent_Http_ResponseInit_\n\t//\t*TapEvent_Http_ResponseEnd_\n\tEvent isTapEvent_Http_Event `protobuf_oneof:\"event\"`\n}\n\nfunc (x *TapEvent_Http) Reset() {\n\t*x = TapEvent_Http{}\n\tmi := &file_viz_tap_proto_msgTypes[11]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapEvent_Http) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapEvent_Http) ProtoMessage() {}\n\nfunc (x *TapEvent_Http) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapEvent_Http.ProtoReflect.Descriptor instead.\nfunc (*TapEvent_Http) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{2, 2}\n}\n\nfunc (m *TapEvent_Http) GetEvent() isTapEvent_Http_Event {\n\tif m != nil {\n\t\treturn m.Event\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent_Http) GetRequestInit() *TapEvent_Http_RequestInit {\n\tif x, ok := x.GetEvent().(*TapEvent_Http_RequestInit_); ok {\n\t\treturn x.RequestInit\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent_Http) GetResponseInit() *TapEvent_Http_ResponseInit {\n\tif x, ok := x.GetEvent().(*TapEvent_Http_ResponseInit_); ok {\n\t\treturn x.ResponseInit\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent_Http) GetResponseEnd() *TapEvent_Http_ResponseEnd {\n\tif x, ok := x.GetEvent().(*TapEvent_Http_ResponseEnd_); ok {\n\t\treturn x.ResponseEnd\n\t}\n\treturn nil\n}\n\ntype isTapEvent_Http_Event interface {\n\tisTapEvent_Http_Event()\n}\n\ntype TapEvent_Http_RequestInit_ struct {\n\tRequestInit *TapEvent_Http_RequestInit `protobuf:\"bytes,1,opt,name=request_init,json=requestInit,proto3,oneof\"`\n}\n\ntype TapEvent_Http_ResponseInit_ struct {\n\tResponseInit *TapEvent_Http_ResponseInit `protobuf:\"bytes,2,opt,name=response_init,json=responseInit,proto3,oneof\"`\n}\n\ntype TapEvent_Http_ResponseEnd_ struct {\n\tResponseEnd *TapEvent_Http_ResponseEnd `protobuf:\"bytes,3,opt,name=response_end,json=responseEnd,proto3,oneof\"`\n}\n\nfunc (*TapEvent_Http_RequestInit_) isTapEvent_Http_Event() {}\n\nfunc (*TapEvent_Http_ResponseInit_) isTapEvent_Http_Event() {}\n\nfunc (*TapEvent_Http_ResponseEnd_) isTapEvent_Http_Event() {}\n\ntype TapEvent_Http_StreamId struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// A randomized base (stable across a process's runtime)\n\tBase uint32 `protobuf:\"varint,1,opt,name=base,proto3\" json:\"base,omitempty\"`\n\t// A stream id unique within the lifetime of `base`.\n\tStream uint64 `protobuf:\"varint,2,opt,name=stream,proto3\" json:\"stream,omitempty\"`\n}\n\nfunc (x *TapEvent_Http_StreamId) Reset() {\n\t*x = TapEvent_Http_StreamId{}\n\tmi := &file_viz_tap_proto_msgTypes[14]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapEvent_Http_StreamId) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapEvent_Http_StreamId) ProtoMessage() {}\n\nfunc (x *TapEvent_Http_StreamId) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapEvent_Http_StreamId.ProtoReflect.Descriptor instead.\nfunc (*TapEvent_Http_StreamId) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{2, 2, 0}\n}\n\nfunc (x *TapEvent_Http_StreamId) GetBase() uint32 {\n\tif x != nil {\n\t\treturn x.Base\n\t}\n\treturn 0\n}\n\nfunc (x *TapEvent_Http_StreamId) GetStream() uint64 {\n\tif x != nil {\n\t\treturn x.Stream\n\t}\n\treturn 0\n}\n\ntype TapEvent_Http_RequestInit struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tId        *TapEvent_Http_StreamId `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tMethod    *viz.HttpMethod         `protobuf:\"bytes,2,opt,name=method,proto3\" json:\"method,omitempty\"`\n\tScheme    *viz.Scheme             `protobuf:\"bytes,3,opt,name=scheme,proto3\" json:\"scheme,omitempty\"`\n\tAuthority string                  `protobuf:\"bytes,4,opt,name=authority,proto3\" json:\"authority,omitempty\"`\n\tPath      string                  `protobuf:\"bytes,5,opt,name=path,proto3\" json:\"path,omitempty\"`\n\tHeaders   *viz.Headers            `protobuf:\"bytes,6,opt,name=headers,proto3\" json:\"headers,omitempty\"`\n}\n\nfunc (x *TapEvent_Http_RequestInit) Reset() {\n\t*x = TapEvent_Http_RequestInit{}\n\tmi := &file_viz_tap_proto_msgTypes[15]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapEvent_Http_RequestInit) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapEvent_Http_RequestInit) ProtoMessage() {}\n\nfunc (x *TapEvent_Http_RequestInit) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapEvent_Http_RequestInit.ProtoReflect.Descriptor instead.\nfunc (*TapEvent_Http_RequestInit) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{2, 2, 1}\n}\n\nfunc (x *TapEvent_Http_RequestInit) GetId() *TapEvent_Http_StreamId {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent_Http_RequestInit) GetMethod() *viz.HttpMethod {\n\tif x != nil {\n\t\treturn x.Method\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent_Http_RequestInit) GetScheme() *viz.Scheme {\n\tif x != nil {\n\t\treturn x.Scheme\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent_Http_RequestInit) GetAuthority() string {\n\tif x != nil {\n\t\treturn x.Authority\n\t}\n\treturn \"\"\n}\n\nfunc (x *TapEvent_Http_RequestInit) GetPath() string {\n\tif x != nil {\n\t\treturn x.Path\n\t}\n\treturn \"\"\n}\n\nfunc (x *TapEvent_Http_RequestInit) GetHeaders() *viz.Headers {\n\tif x != nil {\n\t\treturn x.Headers\n\t}\n\treturn nil\n}\n\ntype TapEvent_Http_ResponseInit struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tId               *TapEvent_Http_StreamId `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tSinceRequestInit *duration.Duration      `protobuf:\"bytes,2,opt,name=since_request_init,json=sinceRequestInit,proto3\" json:\"since_request_init,omitempty\"`\n\tHttpStatus       uint32                  `protobuf:\"varint,3,opt,name=http_status,json=httpStatus,proto3\" json:\"http_status,omitempty\"`\n\tHeaders          *viz.Headers            `protobuf:\"bytes,4,opt,name=headers,proto3\" json:\"headers,omitempty\"`\n}\n\nfunc (x *TapEvent_Http_ResponseInit) Reset() {\n\t*x = TapEvent_Http_ResponseInit{}\n\tmi := &file_viz_tap_proto_msgTypes[16]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapEvent_Http_ResponseInit) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapEvent_Http_ResponseInit) ProtoMessage() {}\n\nfunc (x *TapEvent_Http_ResponseInit) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapEvent_Http_ResponseInit.ProtoReflect.Descriptor instead.\nfunc (*TapEvent_Http_ResponseInit) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{2, 2, 2}\n}\n\nfunc (x *TapEvent_Http_ResponseInit) GetId() *TapEvent_Http_StreamId {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent_Http_ResponseInit) GetSinceRequestInit() *duration.Duration {\n\tif x != nil {\n\t\treturn x.SinceRequestInit\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent_Http_ResponseInit) GetHttpStatus() uint32 {\n\tif x != nil {\n\t\treturn x.HttpStatus\n\t}\n\treturn 0\n}\n\nfunc (x *TapEvent_Http_ResponseInit) GetHeaders() *viz.Headers {\n\tif x != nil {\n\t\treturn x.Headers\n\t}\n\treturn nil\n}\n\ntype TapEvent_Http_ResponseEnd struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tId                *TapEvent_Http_StreamId `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tSinceRequestInit  *duration.Duration      `protobuf:\"bytes,2,opt,name=since_request_init,json=sinceRequestInit,proto3\" json:\"since_request_init,omitempty\"`\n\tSinceResponseInit *duration.Duration      `protobuf:\"bytes,3,opt,name=since_response_init,json=sinceResponseInit,proto3\" json:\"since_response_init,omitempty\"`\n\tResponseBytes     uint64                  `protobuf:\"varint,4,opt,name=response_bytes,json=responseBytes,proto3\" json:\"response_bytes,omitempty\"`\n\tEos               *viz.Eos                `protobuf:\"bytes,5,opt,name=eos,proto3\" json:\"eos,omitempty\"`\n\tTrailers          *viz.Headers            `protobuf:\"bytes,6,opt,name=trailers,proto3\" json:\"trailers,omitempty\"`\n}\n\nfunc (x *TapEvent_Http_ResponseEnd) Reset() {\n\t*x = TapEvent_Http_ResponseEnd{}\n\tmi := &file_viz_tap_proto_msgTypes[17]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TapEvent_Http_ResponseEnd) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TapEvent_Http_ResponseEnd) ProtoMessage() {}\n\nfunc (x *TapEvent_Http_ResponseEnd) ProtoReflect() protoreflect.Message {\n\tmi := &file_viz_tap_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 TapEvent_Http_ResponseEnd.ProtoReflect.Descriptor instead.\nfunc (*TapEvent_Http_ResponseEnd) Descriptor() ([]byte, []int) {\n\treturn file_viz_tap_proto_rawDescGZIP(), []int{2, 2, 3}\n}\n\nfunc (x *TapEvent_Http_ResponseEnd) GetId() *TapEvent_Http_StreamId {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent_Http_ResponseEnd) GetSinceRequestInit() *duration.Duration {\n\tif x != nil {\n\t\treturn x.SinceRequestInit\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent_Http_ResponseEnd) GetSinceResponseInit() *duration.Duration {\n\tif x != nil {\n\t\treturn x.SinceResponseInit\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent_Http_ResponseEnd) GetResponseBytes() uint64 {\n\tif x != nil {\n\t\treturn x.ResponseBytes\n\t}\n\treturn 0\n}\n\nfunc (x *TapEvent_Http_ResponseEnd) GetEos() *viz.Eos {\n\tif x != nil {\n\t\treturn x.Eos\n\t}\n\treturn nil\n}\n\nfunc (x *TapEvent_Http_ResponseEnd) GetTrailers() *viz.Headers {\n\tif x != nil {\n\t\treturn x.Trailers\n\t}\n\treturn nil\n}\n\nvar File_viz_tap_proto protoreflect.FileDescriptor\n\nvar file_viz_tap_proto_rawDesc = []byte{\n\t0x0a, 0x0d, 0x76, 0x69, 0x7a, 0x5f, 0x74, 0x61, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,\n\t0x0c, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x1a, 0x1e, 0x67,\n\t0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64,\n\t0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x10, 0x63,\n\t0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,\n\t0x09, 0x76, 0x69, 0x7a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xaa, 0x02, 0x0a, 0x0a, 0x54,\n\t0x61, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x03, 0x70, 0x6f, 0x64,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x70, 0x6f, 0x64, 0x12, 0x20, 0x0a,\n\t0x0a, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x09, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12,\n\t0x16, 0x0a, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x70, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52,\n\t0x06, 0x6d, 0x61, 0x78, 0x52, 0x70, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x6f, 0x50, 0x6f, 0x72,\n\t0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x74, 0x6f, 0x50, 0x6f, 0x72, 0x74, 0x12,\n\t0x12, 0x0a, 0x04, 0x74, 0x6f, 0x49, 0x50, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74,\n\t0x6f, 0x49, 0x50, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x72, 0x74, 0x18,\n\t0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x72, 0x74, 0x12,\n\t0x16, 0x0a, 0x06, 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x50, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x06, 0x66, 0x72, 0x6f, 0x6d, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d,\n\t0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x12,\n\t0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f,\n\t0x72, 0x69, 0x74, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68,\n\t0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0b, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x3a, 0x02, 0x18, 0x01, 0x42, 0x08, 0x0a,\n\t0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0xe5, 0x07, 0x0a, 0x14, 0x54, 0x61, 0x70, 0x42,\n\t0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x12, 0x37, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x1f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e,\n\t0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f,\n\t0x6e, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x3e, 0x0a, 0x05, 0x6d, 0x61, 0x74,\n\t0x63, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,\n\t0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x42, 0x79, 0x52, 0x65, 0x73,\n\t0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x61, 0x74,\n\t0x63, 0x68, 0x52, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x61, 0x78,\n\t0x52, 0x70, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x70,\n\t0x73, 0x12, 0x44, 0x0a, 0x07, 0x65, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74, 0x61,\n\t0x70, 0x2e, 0x54, 0x61, 0x70, 0x42, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52,\n\t0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, 0x07,\n\t0x65, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x1a, 0xa4, 0x04, 0x0a, 0x05, 0x4d, 0x61, 0x74, 0x63,\n\t0x68, 0x12, 0x40, 0x0a, 0x03, 0x61, 0x6c, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c,\n\t0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61,\n\t0x70, 0x42, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,\n\t0x73, 0x74, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x53, 0x65, 0x71, 0x48, 0x00, 0x52, 0x03,\n\t0x61, 0x6c, 0x6c, 0x12, 0x40, 0x0a, 0x03, 0x61, 0x6e, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x2c, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e,\n\t0x54, 0x61, 0x70, 0x42, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71,\n\t0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x53, 0x65, 0x71, 0x48, 0x00,\n\t0x52, 0x03, 0x61, 0x6e, 0x79, 0x12, 0x3c, 0x0a, 0x03, 0x6e, 0x6f, 0x74, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74, 0x61,\n\t0x70, 0x2e, 0x54, 0x61, 0x70, 0x42, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52,\n\t0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x48, 0x00, 0x52, 0x03,\n\t0x6e, 0x6f, 0x74, 0x12, 0x45, 0x0a, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b,\n\t0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,\n\t0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0c, 0x64, 0x65,\n\t0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x04, 0x68, 0x74,\n\t0x74, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,\n\t0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x42, 0x79, 0x52, 0x65, 0x73,\n\t0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x61, 0x74,\n\t0x63, 0x68, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x48, 0x00, 0x52, 0x04, 0x68, 0x74, 0x74, 0x70, 0x1a,\n\t0x49, 0x0a, 0x03, 0x53, 0x65, 0x71, 0x12, 0x42, 0x0a, 0x07, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65,\n\t0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72,\n\t0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x42, 0x79, 0x52, 0x65, 0x73, 0x6f,\n\t0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x61, 0x74, 0x63,\n\t0x68, 0x52, 0x07, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x1a, 0x79, 0x0a, 0x04, 0x48, 0x74,\n\t0x74, 0x70, 0x12, 0x18, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x06,\n\t0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06,\n\t0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1e, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72,\n\t0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x61, 0x75, 0x74,\n\t0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04,\n\t0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x42, 0x07, 0x0a, 0x05,\n\t0x6d, 0x61, 0x74, 0x63, 0x68, 0x42, 0x07, 0x0a, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x1a, 0xce,\n\t0x01, 0x0a, 0x07, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x12, 0x45, 0x0a, 0x04, 0x68, 0x74,\n\t0x74, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,\n\t0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x42, 0x79, 0x52, 0x65, 0x73,\n\t0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x45, 0x78, 0x74,\n\t0x72, 0x61, 0x63, 0x74, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x48, 0x00, 0x52, 0x04, 0x68, 0x74, 0x74,\n\t0x70, 0x1a, 0x71, 0x0a, 0x04, 0x48, 0x74, 0x74, 0x70, 0x12, 0x53, 0x0a, 0x07, 0x68, 0x65, 0x61,\n\t0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x6c, 0x69, 0x6e,\n\t0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x42, 0x79, 0x52,\n\t0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x45,\n\t0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x2e, 0x48, 0x65, 0x61, 0x64,\n\t0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x09,\n\t0x0a, 0x07, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x65, 0x78, 0x74,\n\t0x72, 0x61, 0x63, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x65, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x22,\n\t0xc2, 0x0f, 0x0a, 0x08, 0x54, 0x61, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x37, 0x0a, 0x06,\n\t0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c,\n\t0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e,\n\t0x65, 0x74, 0x2e, 0x54, 0x63, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x06, 0x73,\n\t0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f,\n\t0x6d, 0x65, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6c, 0x69, 0x6e,\n\t0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x45, 0x76, 0x65,\n\t0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52,\n\t0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x41, 0x0a, 0x0b, 0x64,\n\t0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x1f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,\n\t0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x63, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,\n\t0x73, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4e,\n\t0x0a, 0x10, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x65,\n\t0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,\n\t0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74,\n\t0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0f, 0x64,\n\t0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x3f,\n\t0x0a, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74, 0x61,\n\t0x70, 0x2e, 0x54, 0x61, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65,\n\t0x4d, 0x65, 0x74, 0x61, 0x52, 0x09, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12,\n\t0x4e, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,\n\t0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74,\n\t0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,\n\t0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12,\n\t0x31, 0x0a, 0x04, 0x68, 0x74, 0x74, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70,\n\t0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x48, 0x00, 0x52, 0x04, 0x68, 0x74,\n\t0x74, 0x70, 0x1a, 0x92, 0x01, 0x0a, 0x0c, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x4d,\n\t0x65, 0x74, 0x61, 0x12, 0x47, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20,\n\t0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74,\n\t0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70,\n\t0x6f, 0x69, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45,\n\t0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39, 0x0a, 0x0b,\n\t0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,\n\t0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,\n\t0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61,\n\t0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x8c, 0x01, 0x0a, 0x09, 0x52, 0x6f, 0x75, 0x74,\n\t0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18,\n\t0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32,\n\t0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x6f,\n\t0x75, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e,\n\t0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c,\n\t0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,\n\t0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05,\n\t0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c,\n\t0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0xf8, 0x08, 0x0a, 0x04, 0x48, 0x74, 0x74, 0x70, 0x12,\n\t0x4c, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32,\n\t0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x74,\n\t0x74, 0x70, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x69, 0x74, 0x48, 0x00,\n\t0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x4f, 0x0a,\n\t0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,\n\t0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x74, 0x74,\n\t0x70, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x49, 0x6e, 0x69, 0x74, 0x48, 0x00,\n\t0x52, 0x0c, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x4c,\n\t0x0a, 0x0c, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,\n\t0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x74, 0x74,\n\t0x70, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x45, 0x6e, 0x64, 0x48, 0x00, 0x52,\n\t0x0b, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x45, 0x6e, 0x64, 0x1a, 0x36, 0x0a, 0x08,\n\t0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06,\n\t0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x73, 0x74,\n\t0x72, 0x65, 0x61, 0x6d, 0x1a, 0x86, 0x02, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x49, 0x6e, 0x69, 0x74, 0x12, 0x34, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x24, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e,\n\t0x54, 0x61, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x2e, 0x53, 0x74,\n\t0x72, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x52, 0x02, 0x69, 0x64, 0x12, 0x30, 0x0a, 0x06, 0x6d, 0x65,\n\t0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x69, 0x6e,\n\t0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x4d, 0x65,\n\t0x74, 0x68, 0x6f, 0x64, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x2c, 0x0a, 0x06,\n\t0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c,\n\t0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x53, 0x63, 0x68, 0x65,\n\t0x6d, 0x65, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x75,\n\t0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61,\n\t0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68,\n\t0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x2f, 0x0a, 0x07,\n\t0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x48, 0x65, 0x61,\n\t0x64, 0x65, 0x72, 0x73, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x1a, 0xdf, 0x01,\n\t0x0a, 0x0c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x34,\n\t0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6c, 0x69, 0x6e,\n\t0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x45, 0x76, 0x65,\n\t0x6e, 0x74, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x64,\n\t0x52, 0x02, 0x69, 0x64, 0x12, 0x47, 0x0a, 0x12, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x5f, 0x72, 0x65,\n\t0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,\n\t0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x10, 0x73, 0x69, 0x6e,\n\t0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x1f, 0x0a,\n\t0x0b, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x0d, 0x52, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2f,\n\t0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x15, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x48,\n\t0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x1a,\n\t0xd6, 0x02, 0x0a, 0x0b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x45, 0x6e, 0x64, 0x12,\n\t0x34, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6c, 0x69,\n\t0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x45, 0x76,\n\t0x65, 0x6e, 0x74, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49,\n\t0x64, 0x52, 0x02, 0x69, 0x64, 0x12, 0x47, 0x0a, 0x12, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x5f, 0x72,\n\t0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x10, 0x73, 0x69,\n\t0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x49,\n\t0x0a, 0x13, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,\n\t0x5f, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f,\n\t0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75,\n\t0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x73,\n\t0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28,\n\t0x04, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73,\n\t0x12, 0x23, 0x0a, 0x03, 0x65, 0x6f, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x45, 0x6f, 0x73,\n\t0x52, 0x03, 0x65, 0x6f, 0x73, 0x12, 0x31, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x65, 0x72,\n\t0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72,\n\t0x64, 0x32, 0x2e, 0x76, 0x69, 0x7a, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x08,\n\t0x74, 0x72, 0x61, 0x69, 0x6c, 0x65, 0x72, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e,\n\t0x74, 0x22, 0x38, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74,\n\t0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00,\n\t0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a,\n\t0x08, 0x4f, 0x55, 0x54, 0x42, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x02, 0x42, 0x07, 0x0a, 0x05, 0x65,\n\t0x76, 0x65, 0x6e, 0x74, 0x32, 0x99, 0x01, 0x0a, 0x03, 0x54, 0x61, 0x70, 0x12, 0x3e, 0x0a, 0x03,\n\t0x54, 0x61, 0x70, 0x12, 0x18, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74,\n\t0x61, 0x70, 0x2e, 0x54, 0x61, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70,\n\t0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x03, 0x88, 0x02, 0x01, 0x30, 0x01, 0x12, 0x52, 0x0a, 0x0d,\n\t0x54, 0x61, 0x70, 0x42, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x22, 0x2e,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70, 0x2e, 0x54, 0x61, 0x70,\n\t0x42, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x74, 0x61, 0x70,\n\t0x2e, 0x54, 0x61, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x03, 0x88, 0x02, 0x01, 0x30, 0x01,\n\t0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c,\n\t0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x2f, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2f,\n\t0x76, 0x69, 0x7a, 0x2f, 0x74, 0x61, 0x70, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x74, 0x61, 0x70, 0x62,\n\t0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_viz_tap_proto_rawDescOnce sync.Once\n\tfile_viz_tap_proto_rawDescData = file_viz_tap_proto_rawDesc\n)\n\nfunc file_viz_tap_proto_rawDescGZIP() []byte {\n\tfile_viz_tap_proto_rawDescOnce.Do(func() {\n\t\tfile_viz_tap_proto_rawDescData = protoimpl.X.CompressGZIP(file_viz_tap_proto_rawDescData)\n\t})\n\treturn file_viz_tap_proto_rawDescData\n}\n\nvar file_viz_tap_proto_enumTypes = make([]protoimpl.EnumInfo, 1)\nvar file_viz_tap_proto_msgTypes = make([]protoimpl.MessageInfo, 18)\nvar file_viz_tap_proto_goTypes = []any{\n\t(TapEvent_ProxyDirection)(0),                      // 0: linkerd2.tap.TapEvent.ProxyDirection\n\t(*TapRequest)(nil),                                // 1: linkerd2.tap.TapRequest\n\t(*TapByResourceRequest)(nil),                      // 2: linkerd2.tap.TapByResourceRequest\n\t(*TapEvent)(nil),                                  // 3: linkerd2.tap.TapEvent\n\t(*TapByResourceRequest_Match)(nil),                // 4: linkerd2.tap.TapByResourceRequest.Match\n\t(*TapByResourceRequest_Extract)(nil),              // 5: linkerd2.tap.TapByResourceRequest.Extract\n\t(*TapByResourceRequest_Match_Seq)(nil),            // 6: linkerd2.tap.TapByResourceRequest.Match.Seq\n\t(*TapByResourceRequest_Match_Http)(nil),           // 7: linkerd2.tap.TapByResourceRequest.Match.Http\n\t(*TapByResourceRequest_Extract_Http)(nil),         // 8: linkerd2.tap.TapByResourceRequest.Extract.Http\n\t(*TapByResourceRequest_Extract_Http_Headers)(nil), // 9: linkerd2.tap.TapByResourceRequest.Extract.Http.Headers\n\t(*TapEvent_EndpointMeta)(nil),                     // 10: linkerd2.tap.TapEvent.EndpointMeta\n\t(*TapEvent_RouteMeta)(nil),                        // 11: linkerd2.tap.TapEvent.RouteMeta\n\t(*TapEvent_Http)(nil),                             // 12: linkerd2.tap.TapEvent.Http\n\tnil,                                               // 13: linkerd2.tap.TapEvent.EndpointMeta.LabelsEntry\n\tnil,                                               // 14: linkerd2.tap.TapEvent.RouteMeta.LabelsEntry\n\t(*TapEvent_Http_StreamId)(nil),                    // 15: linkerd2.tap.TapEvent.Http.StreamId\n\t(*TapEvent_Http_RequestInit)(nil),                 // 16: linkerd2.tap.TapEvent.Http.RequestInit\n\t(*TapEvent_Http_ResponseInit)(nil),                // 17: linkerd2.tap.TapEvent.Http.ResponseInit\n\t(*TapEvent_Http_ResponseEnd)(nil),                 // 18: linkerd2.tap.TapEvent.Http.ResponseEnd\n\t(*viz.ResourceSelection)(nil),                     // 19: linkerd2.viz.ResourceSelection\n\t(*net.TcpAddress)(nil),                            // 20: linkerd2.common.net.TcpAddress\n\t(*viz.HttpMethod)(nil),                            // 21: linkerd2.viz.HttpMethod\n\t(*viz.Scheme)(nil),                                // 22: linkerd2.viz.Scheme\n\t(*viz.Headers)(nil),                               // 23: linkerd2.viz.Headers\n\t(*duration.Duration)(nil),                         // 24: google.protobuf.Duration\n\t(*viz.Eos)(nil),                                   // 25: linkerd2.viz.Eos\n}\nvar file_viz_tap_proto_depIdxs = []int32{\n\t19, // 0: linkerd2.tap.TapByResourceRequest.target:type_name -> linkerd2.viz.ResourceSelection\n\t4,  // 1: linkerd2.tap.TapByResourceRequest.match:type_name -> linkerd2.tap.TapByResourceRequest.Match\n\t5,  // 2: linkerd2.tap.TapByResourceRequest.extract:type_name -> linkerd2.tap.TapByResourceRequest.Extract\n\t20, // 3: linkerd2.tap.TapEvent.source:type_name -> linkerd2.common.net.TcpAddress\n\t10, // 4: linkerd2.tap.TapEvent.source_meta:type_name -> linkerd2.tap.TapEvent.EndpointMeta\n\t20, // 5: linkerd2.tap.TapEvent.destination:type_name -> linkerd2.common.net.TcpAddress\n\t10, // 6: linkerd2.tap.TapEvent.destination_meta:type_name -> linkerd2.tap.TapEvent.EndpointMeta\n\t11, // 7: linkerd2.tap.TapEvent.route_meta:type_name -> linkerd2.tap.TapEvent.RouteMeta\n\t0,  // 8: linkerd2.tap.TapEvent.proxy_direction:type_name -> linkerd2.tap.TapEvent.ProxyDirection\n\t12, // 9: linkerd2.tap.TapEvent.http:type_name -> linkerd2.tap.TapEvent.Http\n\t6,  // 10: linkerd2.tap.TapByResourceRequest.Match.all:type_name -> linkerd2.tap.TapByResourceRequest.Match.Seq\n\t6,  // 11: linkerd2.tap.TapByResourceRequest.Match.any:type_name -> linkerd2.tap.TapByResourceRequest.Match.Seq\n\t4,  // 12: linkerd2.tap.TapByResourceRequest.Match.not:type_name -> linkerd2.tap.TapByResourceRequest.Match\n\t19, // 13: linkerd2.tap.TapByResourceRequest.Match.destinations:type_name -> linkerd2.viz.ResourceSelection\n\t7,  // 14: linkerd2.tap.TapByResourceRequest.Match.http:type_name -> linkerd2.tap.TapByResourceRequest.Match.Http\n\t8,  // 15: linkerd2.tap.TapByResourceRequest.Extract.http:type_name -> linkerd2.tap.TapByResourceRequest.Extract.Http\n\t4,  // 16: linkerd2.tap.TapByResourceRequest.Match.Seq.matches:type_name -> linkerd2.tap.TapByResourceRequest.Match\n\t9,  // 17: linkerd2.tap.TapByResourceRequest.Extract.Http.headers:type_name -> linkerd2.tap.TapByResourceRequest.Extract.Http.Headers\n\t13, // 18: linkerd2.tap.TapEvent.EndpointMeta.labels:type_name -> linkerd2.tap.TapEvent.EndpointMeta.LabelsEntry\n\t14, // 19: linkerd2.tap.TapEvent.RouteMeta.labels:type_name -> linkerd2.tap.TapEvent.RouteMeta.LabelsEntry\n\t16, // 20: linkerd2.tap.TapEvent.Http.request_init:type_name -> linkerd2.tap.TapEvent.Http.RequestInit\n\t17, // 21: linkerd2.tap.TapEvent.Http.response_init:type_name -> linkerd2.tap.TapEvent.Http.ResponseInit\n\t18, // 22: linkerd2.tap.TapEvent.Http.response_end:type_name -> linkerd2.tap.TapEvent.Http.ResponseEnd\n\t15, // 23: linkerd2.tap.TapEvent.Http.RequestInit.id:type_name -> linkerd2.tap.TapEvent.Http.StreamId\n\t21, // 24: linkerd2.tap.TapEvent.Http.RequestInit.method:type_name -> linkerd2.viz.HttpMethod\n\t22, // 25: linkerd2.tap.TapEvent.Http.RequestInit.scheme:type_name -> linkerd2.viz.Scheme\n\t23, // 26: linkerd2.tap.TapEvent.Http.RequestInit.headers:type_name -> linkerd2.viz.Headers\n\t15, // 27: linkerd2.tap.TapEvent.Http.ResponseInit.id:type_name -> linkerd2.tap.TapEvent.Http.StreamId\n\t24, // 28: linkerd2.tap.TapEvent.Http.ResponseInit.since_request_init:type_name -> google.protobuf.Duration\n\t23, // 29: linkerd2.tap.TapEvent.Http.ResponseInit.headers:type_name -> linkerd2.viz.Headers\n\t15, // 30: linkerd2.tap.TapEvent.Http.ResponseEnd.id:type_name -> linkerd2.tap.TapEvent.Http.StreamId\n\t24, // 31: linkerd2.tap.TapEvent.Http.ResponseEnd.since_request_init:type_name -> google.protobuf.Duration\n\t24, // 32: linkerd2.tap.TapEvent.Http.ResponseEnd.since_response_init:type_name -> google.protobuf.Duration\n\t25, // 33: linkerd2.tap.TapEvent.Http.ResponseEnd.eos:type_name -> linkerd2.viz.Eos\n\t23, // 34: linkerd2.tap.TapEvent.Http.ResponseEnd.trailers:type_name -> linkerd2.viz.Headers\n\t1,  // 35: linkerd2.tap.Tap.Tap:input_type -> linkerd2.tap.TapRequest\n\t2,  // 36: linkerd2.tap.Tap.TapByResource:input_type -> linkerd2.tap.TapByResourceRequest\n\t3,  // 37: linkerd2.tap.Tap.Tap:output_type -> linkerd2.tap.TapEvent\n\t3,  // 38: linkerd2.tap.Tap.TapByResource:output_type -> linkerd2.tap.TapEvent\n\t37, // [37:39] is the sub-list for method output_type\n\t35, // [35:37] is the sub-list for method input_type\n\t35, // [35:35] is the sub-list for extension type_name\n\t35, // [35:35] is the sub-list for extension extendee\n\t0,  // [0:35] is the sub-list for field type_name\n}\n\nfunc init() { file_viz_tap_proto_init() }\nfunc file_viz_tap_proto_init() {\n\tif File_viz_tap_proto != nil {\n\t\treturn\n\t}\n\tfile_viz_tap_proto_msgTypes[0].OneofWrappers = []any{\n\t\t(*TapRequest_Pod)(nil),\n\t\t(*TapRequest_Deployment)(nil),\n\t}\n\tfile_viz_tap_proto_msgTypes[2].OneofWrappers = []any{\n\t\t(*TapEvent_Http_)(nil),\n\t}\n\tfile_viz_tap_proto_msgTypes[3].OneofWrappers = []any{\n\t\t(*TapByResourceRequest_Match_All)(nil),\n\t\t(*TapByResourceRequest_Match_Any)(nil),\n\t\t(*TapByResourceRequest_Match_Not)(nil),\n\t\t(*TapByResourceRequest_Match_Destinations)(nil),\n\t\t(*TapByResourceRequest_Match_Http_)(nil),\n\t}\n\tfile_viz_tap_proto_msgTypes[4].OneofWrappers = []any{\n\t\t(*TapByResourceRequest_Extract_Http_)(nil),\n\t}\n\tfile_viz_tap_proto_msgTypes[6].OneofWrappers = []any{\n\t\t(*TapByResourceRequest_Match_Http_Scheme)(nil),\n\t\t(*TapByResourceRequest_Match_Http_Method)(nil),\n\t\t(*TapByResourceRequest_Match_Http_Authority)(nil),\n\t\t(*TapByResourceRequest_Match_Http_Path)(nil),\n\t}\n\tfile_viz_tap_proto_msgTypes[7].OneofWrappers = []any{\n\t\t(*TapByResourceRequest_Extract_Http_Headers_)(nil),\n\t}\n\tfile_viz_tap_proto_msgTypes[11].OneofWrappers = []any{\n\t\t(*TapEvent_Http_RequestInit_)(nil),\n\t\t(*TapEvent_Http_ResponseInit_)(nil),\n\t\t(*TapEvent_Http_ResponseEnd_)(nil),\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: file_viz_tap_proto_rawDesc,\n\t\t\tNumEnums:      1,\n\t\t\tNumMessages:   18,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_viz_tap_proto_goTypes,\n\t\tDependencyIndexes: file_viz_tap_proto_depIdxs,\n\t\tEnumInfos:         file_viz_tap_proto_enumTypes,\n\t\tMessageInfos:      file_viz_tap_proto_msgTypes,\n\t}.Build()\n\tFile_viz_tap_proto = out.File\n\tfile_viz_tap_proto_rawDesc = nil\n\tfile_viz_tap_proto_goTypes = nil\n\tfile_viz_tap_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "viz/tap/gen/tap/viz_tap_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.5.1\n// - protoc             v6.32.1\n// source: viz_tap.proto\n\npackage tap\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\tTap_Tap_FullMethodName           = \"/linkerd2.tap.Tap/Tap\"\n\tTap_TapByResource_FullMethodName = \"/linkerd2.tap.Tap/TapByResource\"\n)\n\n// TapClient is the client API for Tap 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 TapClient interface {\n\t// Deprecated: Do not use.\n\tTap(ctx context.Context, in *TapRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TapEvent], error)\n\t// Deprecated: Do not use.\n\tTapByResource(ctx context.Context, in *TapByResourceRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TapEvent], error)\n}\n\ntype tapClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewTapClient(cc grpc.ClientConnInterface) TapClient {\n\treturn &tapClient{cc}\n}\n\n// Deprecated: Do not use.\nfunc (c *tapClient) Tap(ctx context.Context, in *TapRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TapEvent], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Tap_ServiceDesc.Streams[0], Tap_Tap_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[TapRequest, TapEvent]{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 Tap_TapClient = grpc.ServerStreamingClient[TapEvent]\n\n// Deprecated: Do not use.\nfunc (c *tapClient) TapByResource(ctx context.Context, in *TapByResourceRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TapEvent], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Tap_ServiceDesc.Streams[1], Tap_TapByResource_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[TapByResourceRequest, TapEvent]{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 Tap_TapByResourceClient = grpc.ServerStreamingClient[TapEvent]\n\n// TapServer is the server API for Tap service.\n// All implementations must embed UnimplementedTapServer\n// for forward compatibility.\ntype TapServer interface {\n\t// Deprecated: Do not use.\n\tTap(*TapRequest, grpc.ServerStreamingServer[TapEvent]) error\n\t// Deprecated: Do not use.\n\tTapByResource(*TapByResourceRequest, grpc.ServerStreamingServer[TapEvent]) error\n\tmustEmbedUnimplementedTapServer()\n}\n\n// UnimplementedTapServer 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 UnimplementedTapServer struct{}\n\nfunc (UnimplementedTapServer) Tap(*TapRequest, grpc.ServerStreamingServer[TapEvent]) error {\n\treturn status.Errorf(codes.Unimplemented, \"method Tap not implemented\")\n}\nfunc (UnimplementedTapServer) TapByResource(*TapByResourceRequest, grpc.ServerStreamingServer[TapEvent]) error {\n\treturn status.Errorf(codes.Unimplemented, \"method TapByResource not implemented\")\n}\nfunc (UnimplementedTapServer) mustEmbedUnimplementedTapServer() {}\nfunc (UnimplementedTapServer) testEmbeddedByValue()             {}\n\n// UnsafeTapServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to TapServer will\n// result in compilation errors.\ntype UnsafeTapServer interface {\n\tmustEmbedUnimplementedTapServer()\n}\n\nfunc RegisterTapServer(s grpc.ServiceRegistrar, srv TapServer) {\n\t// If the following call pancis, it indicates UnimplementedTapServer 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(&Tap_ServiceDesc, srv)\n}\n\nfunc _Tap_Tap_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(TapRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(TapServer).Tap(m, &grpc.GenericServerStream[TapRequest, TapEvent]{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 Tap_TapServer = grpc.ServerStreamingServer[TapEvent]\n\nfunc _Tap_TapByResource_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(TapByResourceRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(TapServer).TapByResource(m, &grpc.GenericServerStream[TapByResourceRequest, TapEvent]{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 Tap_TapByResourceServer = grpc.ServerStreamingServer[TapEvent]\n\n// Tap_ServiceDesc is the grpc.ServiceDesc for Tap 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 Tap_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"linkerd2.tap.Tap\",\n\tHandlerType: (*TapServer)(nil),\n\tMethods:     []grpc.MethodDesc{},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"Tap\",\n\t\t\tHandler:       _Tap_Tap_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"TapByResource\",\n\t\t\tHandler:       _Tap_TapByResource_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t},\n\tMetadata: \"viz_tap.proto\",\n}\n"
  },
  {
    "path": "viz/tap/injector/main.go",
    "content": "package injector\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/controller/webhook\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n)\n\n// Main executes the tap-injector subcommand\nfunc Main(args []string) {\n\tcmd := flag.NewFlagSet(\"tap-injector\", flag.ExitOnError)\n\tmetricsAddr := cmd.String(\"metrics-addr\", fmt.Sprintf(\":%d\", 9995), \"address to serve scrapable metrics on\")\n\taddr := cmd.String(\"addr\", \":8443\", \"address to serve on\")\n\tkubeconfig := cmd.String(\"kubeconfig\", \"\", \"path to kubeconfig\")\n\ttapSvcName := cmd.String(\"tap-service-name\", \"\", \"name of the tap service\")\n\tenablePprof := cmd.Bool(\"enable-pprof\", false, \"Enable pprof endpoints on the admin server\")\n\tflags.ConfigureAndParse(cmd, args)\n\twebhook.Launch(\n\t\tcontext.Background(),\n\t\t[]k8s.APIResource{k8s.NS},\n\t\tMutate(*tapSvcName),\n\t\t\"tap-injector\",\n\t\t*metricsAddr,\n\t\t*addr,\n\t\t*kubeconfig,\n\t\t*enablePprof,\n\t)\n}\n"
  },
  {
    "path": "viz/tap/injector/patch.go",
    "content": "package injector\n\nvar tpl = `[\n  {\n    \"op\": \"add\",\n    \"path\": \"/metadata/annotations/viz.linkerd.io~1tap-enabled\",\n    \"value\": \"true\"\n  },\n  {\n    \"op\": \"add\",\n    \"path\": \"/spec/{{.ProxyPath}}/env/-\",\n    \"value\": {\n      \"name\": \"LINKERD2_PROXY_TAP_SVC_NAME\",\n      \"value\": \"{{.ProxyTapSvcName}}\"\n    }\n  }\n]`\n"
  },
  {
    "path": "viz/tap/injector/webhook.go",
    "content": "package injector\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"html/template\"\n\n\t\"github.com/linkerd/linkerd2/controller/k8s\"\n\t\"github.com/linkerd/linkerd2/controller/webhook\"\n\tvizLabels \"github.com/linkerd/linkerd2/viz/pkg/labels\"\n\tlog \"github.com/sirupsen/logrus\"\n\tadmissionv1beta1 \"k8s.io/api/admission/v1beta1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/tools/record\"\n\t\"sigs.k8s.io/yaml\"\n)\n\n// Params holds the values used in the patch template.\ntype Params struct {\n\tProxyPath       string\n\tProxyTapSvcName string\n}\n\n// Mutate mutates an AdmissionRequest and adds the LINKERD2_PROXY_TAP_SVC_NAME\n// env var to a pod's proxy container if tap is not disabled via annotation on the\n// pod or the namespace.\nfunc Mutate(tapSvcName string) webhook.Handler {\n\treturn func(\n\t\t_ context.Context,\n\t\tk8sAPI *k8s.MetadataAPI,\n\t\trequest *admissionv1beta1.AdmissionRequest,\n\t\t_ record.EventRecorder,\n\t) (*admissionv1beta1.AdmissionResponse, error) {\n\t\tlog.Debugf(\"request object bytes: %s\", request.Object.Raw)\n\t\tadmissionResponse := &admissionv1beta1.AdmissionResponse{\n\t\t\tUID:     request.UID,\n\t\t\tAllowed: true,\n\t\t}\n\t\tvar pod *corev1.Pod\n\t\tif err := yaml.Unmarshal(request.Object.Raw, &pod); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tparams := Params{\n\t\t\tProxyPath:       webhook.GetProxyContainerPath(pod.Spec),\n\t\t\tProxyTapSvcName: tapSvcName,\n\t\t}\n\t\tif params.ProxyPath == \"\" || vizLabels.IsTapEnabled(pod) {\n\t\t\treturn admissionResponse, nil\n\t\t}\n\t\tnamespace, err := k8sAPI.Get(k8s.NS, request.Namespace)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvar t *template.Template\n\t\tif vizLabels.IsTapDisabled(namespace) || vizLabels.IsTapDisabled(pod) {\n\t\t\treturn admissionResponse, nil\n\t\t}\n\t\tt, err = template.New(\"tpl\").Parse(tpl)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvar patchJSON bytes.Buffer\n\t\tif err = t.Execute(&patchJSON, params); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpatchType := admissionv1beta1.PatchTypeJSONPatch\n\t\tadmissionResponse.Patch = patchJSON.Bytes()\n\t\tadmissionResponse.PatchType = &patchType\n\t\treturn admissionResponse, nil\n\t}\n}\n"
  },
  {
    "path": "viz/tap/pkg/events.go",
    "content": "package pkg\n\nimport (\n\t\"encoding/binary\"\n\n\tnetPb \"github.com/linkerd/linkerd2/controller/gen/common/net\"\n\ttapPb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n)\n\n// CreateTapEvent generates tap events for use in tests\nfunc CreateTapEvent(eventHTTP *tapPb.TapEvent_Http, dstMeta map[string]string, proxyDirection tapPb.TapEvent_ProxyDirection) *tapPb.TapEvent {\n\tevent := &tapPb.TapEvent{\n\t\tProxyDirection: proxyDirection,\n\t\tSource: &netPb.TcpAddress{\n\t\t\tIp: &netPb.IPAddress{\n\t\t\t\tIp: &netPb.IPAddress_Ipv4{\n\t\t\t\t\tIpv4: uint32(1),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tDestination: &netPb.TcpAddress{\n\t\t\tIp: &netPb.IPAddress{\n\t\t\t\tIp: &netPb.IPAddress_Ipv6{\n\t\t\t\t\tIpv6: &netPb.IPv6{\n\t\t\t\t\t\t// All nodes address: https://www.iana.org/assignments/ipv6-multicast-addresses/ipv6-multicast-addresses.xhtml\n\t\t\t\t\t\tFirst: binary.BigEndian.Uint64([]byte{0xff, 0x01, 0, 0, 0, 0, 0, 0}),\n\t\t\t\t\t\tLast:  binary.BigEndian.Uint64([]byte{0, 0, 0, 0, 0, 0, 0, 0x01}),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tEvent: &tapPb.TapEvent_Http_{\n\t\t\tHttp: eventHTTP,\n\t\t},\n\t\tDestinationMeta: &tapPb.TapEvent_EndpointMeta{\n\t\t\tLabels: dstMeta,\n\t\t},\n\t}\n\treturn event\n}\n"
  },
  {
    "path": "viz/tap/pkg/protohttp.go",
    "content": "package pkg\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\ttapPb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n)\n\n// TapReqToURL converts a TapByResourceRequest protobuf object to a URL for use\n// with the Kubernetes tap.linkerd.io APIService.\nfunc TapReqToURL(req *tapPb.TapByResourceRequest) string {\n\tres := req.GetTarget().GetResource()\n\n\t// non-namespaced\n\tif res.GetType() == k8s.Namespace {\n\t\treturn fmt.Sprintf(\n\t\t\t\"/apis/tap.linkerd.io/v1alpha1/watch/namespaces/%s/tap\",\n\t\t\turl.PathEscape(res.GetName()),\n\t\t)\n\t}\n\n\t// namespaced\n\treturn fmt.Sprintf(\n\t\t\"/apis/tap.linkerd.io/v1alpha1/watch/namespaces/%s/%s/%s/tap\",\n\t\turl.PathEscape(res.GetNamespace()),\n\t\t// FIXME(olix0r): This pluralization is probably not correct for all\n\t\t// resource types.\n\t\turl.PathEscape(res.GetType()+\"s\"),\n\t\turl.PathEscape(res.GetName()),\n\t)\n}\n"
  },
  {
    "path": "viz/tap/pkg/protohttp_test.go",
    "content": "package pkg\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\tmetricsPb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\ttapPb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n)\n\nfunc TestTapReqToURL(t *testing.T) {\n\texpectations := []struct {\n\t\treq *tapPb.TapByResourceRequest\n\t\turl string\n\t}{\n\t\t{\n\t\t\treq: &tapPb.TapByResourceRequest{},\n\t\t\turl: \"/apis/tap.linkerd.io/v1alpha1/watch/namespaces//s//tap\",\n\t\t},\n\t\t{\n\t\t\treq: &tapPb.TapByResourceRequest{\n\t\t\t\tTarget: &metricsPb.ResourceSelection{\n\t\t\t\t\tResource: &metricsPb.Resource{\n\t\t\t\t\t\tType: \"namespace\",\n\t\t\t\t\t\tName: \"test-name\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\turl: \"/apis/tap.linkerd.io/v1alpha1/watch/namespaces/test-name/tap\",\n\t\t},\n\t\t{\n\t\t\treq: &tapPb.TapByResourceRequest{\n\t\t\t\tTarget: &metricsPb.ResourceSelection{\n\t\t\t\t\tResource: &metricsPb.Resource{\n\t\t\t\t\t\tNamespace: \"test-ns\",\n\t\t\t\t\t\tType:      \"test-type\",\n\t\t\t\t\t\tName:      \"test-name\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\turl: \"/apis/tap.linkerd.io/v1alpha1/watch/namespaces/test-ns/test-types/test-name/tap\",\n\t\t},\n\t}\n\tfor i, exp := range expectations {\n\t\texp := exp // pin\n\n\t\tt.Run(fmt.Sprintf(\"%d constructs the expected URL from a TapRequest\", i), func(t *testing.T) {\n\t\t\turl := TapReqToURL(exp.req)\n\t\t\tif url != exp.url {\n\t\t\t\tt.Fatalf(\"Unexpected url: %s, Expected: %s\", url, exp.url)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "viz/tap/pkg/reader.go",
    "content": "package pkg\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/protohttp\"\n\tpb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nconst (\n\t// ErrClosedResponseBody is returned when response body is closed in http2\n\tErrClosedResponseBody = \"http2: response body closed\"\n)\n\n// TapRbacURL is the link users should visit to remedy issues when attempting\n// to tap resources with missing authorizations\nconst TapRbacURL = \"https://linkerd.io/tap-rbac\"\n\n// Reader initiates a TapByResourceRequest and returns a buffered Reader.\n// It is the caller's responsibility to call Close() on the io.ReadCloser.\nfunc Reader(ctx context.Context, k8sAPI *k8s.KubernetesAPI, req *pb.TapByResourceRequest) (*bufio.Reader, io.ReadCloser, error) {\n\tclient, err := k8sAPI.NewClient()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treqBytes, err := proto.Marshal(req)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\turl, err := url.Parse(k8sAPI.Host)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\turl.Path = fmt.Sprintf(\"%s%s\", url.Path, TapReqToURL(req))\n\n\thttpReq, err := http.NewRequest(\n\t\thttp.MethodPost,\n\t\turl.String(),\n\t\tbytes.NewReader(reqBytes),\n\t)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\thttpRsp, err := client.Do(httpReq.WithContext(ctx))\n\tif err != nil {\n\t\tlog.Debugf(\"Error invoking [%s]: %v\", url, err)\n\t\treturn nil, nil, err\n\t}\n\n\tlog.Debugf(\"Response from [%s] had headers: %v\", url, httpRsp.Header)\n\n\tif err := protohttp.CheckIfResponseHasError(httpRsp); err != nil {\n\t\thttpRsp.Body.Close()\n\t\treturn nil, nil, err\n\t}\n\n\treader := bufio.NewReader(httpRsp.Body)\n\n\treturn reader, httpRsp.Body, nil\n}\n"
  },
  {
    "path": "viz/tap/pkg/requests.go",
    "content": "package pkg\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tmetricsPb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/linkerd/linkerd2/viz/pkg/util\"\n\ttapPb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n)\n\n// ValidTapDestinations specifies resource types allowed as a tap destination:\n// - destination resource on an outbound 'to' query\nvar ValidTapDestinations = []string{\n\tk8s.CronJob,\n\tk8s.DaemonSet,\n\tk8s.Deployment,\n\tk8s.Job,\n\tk8s.Namespace,\n\tk8s.Pod,\n\tk8s.ReplicaSet,\n\tk8s.ReplicationController,\n\tk8s.Service,\n\tk8s.StatefulSet,\n}\n\n// TapRequestParams contains parameters that are used to build a\n// TapByResourceRequest.\ntype TapRequestParams struct {\n\tResource      string\n\tNamespace     string\n\tToResource    string\n\tToNamespace   string\n\tMaxRps        float32\n\tScheme        string\n\tMethod        string\n\tAuthority     string\n\tPath          string\n\tExtract       bool\n\tLabelSelector string\n}\n\n// BuildTapByResourceRequest builds a Public API TapByResourceRequest from a\n// TapRequestParams.\nfunc BuildTapByResourceRequest(params TapRequestParams) (*tapPb.TapByResourceRequest, error) {\n\ttarget, err := util.BuildResource(params.Namespace, params.Resource)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"target resource invalid: %w\", err)\n\t}\n\tif !contains(util.ValidTargets, target.Type) {\n\t\treturn nil, fmt.Errorf(\"unsupported resource type [%s]\", target.Type)\n\t}\n\n\tmatches := []*tapPb.TapByResourceRequest_Match{}\n\n\tif params.ToResource != \"\" {\n\t\tdestination, err := util.BuildResource(params.ToNamespace, params.ToResource)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"destination resource invalid: %w\", err)\n\t\t}\n\t\tif !contains(ValidTapDestinations, destination.Type) {\n\t\t\treturn nil, fmt.Errorf(\"unsupported resource type [%s]\", destination.Type)\n\t\t}\n\n\t\tmatch := tapPb.TapByResourceRequest_Match{\n\t\t\tMatch: &tapPb.TapByResourceRequest_Match_Destinations{\n\t\t\t\tDestinations: &metricsPb.ResourceSelection{\n\t\t\t\t\tResource: destination,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tmatches = append(matches, &match)\n\t}\n\n\tif params.Scheme != \"\" {\n\t\tmatch := buildMatchHTTP(&tapPb.TapByResourceRequest_Match_Http{\n\t\t\tMatch: &tapPb.TapByResourceRequest_Match_Http_Scheme{Scheme: params.Scheme},\n\t\t})\n\t\tmatches = append(matches, &match)\n\t}\n\tif params.Method != \"\" {\n\t\tmatch := buildMatchHTTP(&tapPb.TapByResourceRequest_Match_Http{\n\t\t\tMatch: &tapPb.TapByResourceRequest_Match_Http_Method{Method: params.Method},\n\t\t})\n\t\tmatches = append(matches, &match)\n\t}\n\tif params.Authority != \"\" {\n\t\tmatch := buildMatchHTTP(&tapPb.TapByResourceRequest_Match_Http{\n\t\t\tMatch: &tapPb.TapByResourceRequest_Match_Http_Authority{Authority: params.Authority},\n\t\t})\n\t\tmatches = append(matches, &match)\n\t}\n\tif params.Path != \"\" {\n\t\tmatch := buildMatchHTTP(&tapPb.TapByResourceRequest_Match_Http{\n\t\t\tMatch: &tapPb.TapByResourceRequest_Match_Http_Path{Path: params.Path},\n\t\t})\n\t\tmatches = append(matches, &match)\n\t}\n\n\textract := &tapPb.TapByResourceRequest_Extract{}\n\tif params.Extract {\n\t\textract = buildExtractHTTP(&tapPb.TapByResourceRequest_Extract_Http{\n\t\t\tExtract: &tapPb.TapByResourceRequest_Extract_Http_Headers_{\n\t\t\t\tHeaders: &tapPb.TapByResourceRequest_Extract_Http_Headers{},\n\t\t\t},\n\t\t})\n\t}\n\n\treturn &tapPb.TapByResourceRequest{\n\t\tTarget: &metricsPb.ResourceSelection{\n\t\t\tResource:      target,\n\t\t\tLabelSelector: params.LabelSelector,\n\t\t},\n\t\tMaxRps: params.MaxRps,\n\t\tMatch: &tapPb.TapByResourceRequest_Match{\n\t\t\tMatch: &tapPb.TapByResourceRequest_Match_All{\n\t\t\t\tAll: &tapPb.TapByResourceRequest_Match_Seq{\n\t\t\t\t\tMatches: matches,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tExtract: extract,\n\t}, nil\n}\n\nfunc buildMatchHTTP(match *tapPb.TapByResourceRequest_Match_Http) tapPb.TapByResourceRequest_Match {\n\treturn tapPb.TapByResourceRequest_Match{\n\t\tMatch: &tapPb.TapByResourceRequest_Match_Http_{\n\t\t\tHttp: match,\n\t\t},\n\t}\n}\n\nfunc buildExtractHTTP(extract *tapPb.TapByResourceRequest_Extract_Http) *tapPb.TapByResourceRequest_Extract {\n\treturn &tapPb.TapByResourceRequest_Extract{\n\t\tExtract: &tapPb.TapByResourceRequest_Extract_Http_{\n\t\t\tHttp: extract,\n\t\t},\n\t}\n}\n\nfunc contains(list []string, s string) bool {\n\tfor _, elem := range list {\n\t\tif s == elem {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "viz/tap/proto/viz_tap.proto",
    "content": "syntax = \"proto3\";\n\npackage linkerd2.tap;\n\nimport \"google/protobuf/duration.proto\";\nimport \"common/net.proto\";\nimport \"viz.proto\";\n\noption go_package = \"github.com/linkerd/linkerd2/viz/tap/gen/tap\";\n\nmessage TapRequest {\n  option deprecated = true;\n\n  oneof target {\n    string pod = 1;\n    string deployment = 2;\n  }\n  // validation of these fields happens on the server\n  float maxRps = 3;\n  uint32 toPort = 4;\n  string toIP = 5;\n  uint32 fromPort = 6;\n  string fromIP = 7;\n  string scheme = 8;\n  string method = 9;\n  string authority = 10;\n  string path = 11;\n}\n\n// A tap request over kubernetes resources.\n//\n// This is used only by the tap APIServer.\nmessage TapByResourceRequest {\n  // Describes the kubernetes pods that should be tapped.\n  viz.ResourceSelection target = 1;\n\n  // Selects over events to be reported.\n  Match match = 2;\n\n  // Limits the number of events to be inspected.\n  float maxRps = 3;\n\n  message Match {\n    oneof match {\n      // If empty, matches all messages.\n      Seq all = 1;\n\n      // If empty, matches no messages.\n      Seq any = 2;\n\n      // Inverts the inner match.\n      Match not = 3;\n\n      // Matches events being sent to any of the selected destinations.\n      viz.ResourceSelection destinations = 4;\n\n      // Matches HTTP requests by their metadata.\n      Http http = 5;\n    }\n\n    message Seq {\n      repeated Match matches = 1;\n    }\n\n    message Http {\n      oneof match {\n        string scheme = 1;\n        string method = 2;\n        string authority = 3;\n        string path = 4;\n      }\n    }\n  }\n\n  // Conditionally extracts components from requests and responses to include\n  // in tap events\n  Extract extract = 4;\n\n  message Extract {\n    oneof extract {\n      Http http = 1;\n    }\n\n    message Http {\n      oneof extract {\n        Headers headers = 1;\n      }\n\n      message Headers {}\n    }\n  }\n}\n\n// This is used only by the tap APIServer.\nmessage TapEvent {\n  linkerd2.common.net.TcpAddress source = 1;\n  EndpointMeta source_meta = 5;\n\n  linkerd2.common.net.TcpAddress   destination      = 2;\n  EndpointMeta destination_meta = 4;\n\n  RouteMeta route_meta = 7;\n\n  ProxyDirection proxy_direction = 6;\n  enum ProxyDirection {\n    UNKNOWN = 0;\n    INBOUND = 1;\n    OUTBOUND = 2;\n  }\n\n  oneof event {\n    Http http = 3;\n  }\n\n  message EndpointMeta {\n    map<string, string> labels = 1;\n  }\n\n  message RouteMeta {\n    map<string, string> labels = 1;\n  }\n\n  message Http {\n    oneof event {\n      RequestInit  request_init  = 1;\n      ResponseInit response_init = 2;\n      ResponseEnd  response_end  = 3;\n    }\n\n    message StreamId {\n      // A randomized base (stable across a process's runtime)\n      uint32 base = 1;\n\n      // A stream id unique within the lifetime of `base`.\n      uint64 stream = 2;\n    }\n\n    message RequestInit {\n      StreamId id = 1;\n      viz.HttpMethod method =  2;\n      viz.Scheme scheme = 3;\n      string authority = 4;\n      string path = 5;\n      viz.Headers headers = 6;\n    }\n\n    message ResponseInit {\n      StreamId id = 1;\n\n      google.protobuf.Duration since_request_init = 2;\n\n      uint32 http_status = 3;\n      viz.Headers headers = 4;\n    }\n\n    message ResponseEnd {\n      StreamId id = 1;\n\n      google.protobuf.Duration since_request_init = 2;\n      google.protobuf.Duration since_response_init = 3;\n      uint64 response_bytes = 4;\n\n      viz.Eos eos = 5;\n      viz.Headers trailers = 6;\n    }\n  }\n}\n\nservice Tap {\n  rpc Tap(TapRequest) returns (stream TapEvent) { option deprecated = true; }\n  rpc TapByResource(TapByResourceRequest) returns (stream TapEvent) { option deprecated = true; }\n}\n"
  },
  {
    "path": "web/Dockerfile",
    "content": "ARG BUILDPLATFORM=linux/amd64\n\n# Precompile key slow-to-build dependencies\nFROM --platform=$BUILDPLATFORM golang:1.25-alpine AS go-deps\nWORKDIR /linkerd-build\nCOPY go.mod go.sum ./\nCOPY bin/install-deps bin/\nRUN go mod download\nARG TARGETARCH\nRUN ./bin/install-deps $TARGETARCH\n\n## bundle web assets\nFROM --platform=$BUILDPLATFORM node:20-bookworm AS webpack-bundle\nRUN bin/scurl --retry=2 https://yarnpkg.com/install.sh | bash -s -- --version 1.22.10 --network-concurrency 1\n\nENV PATH /root/.yarn/bin:$PATH\nENV ROOT /linkerd-build\nWORKDIR $ROOT\n\n# copy build script\nCOPY bin/web ./bin/web\n\n# install yarn dependencies\nCOPY web/app/package.json web/app/yarn.lock ./web/app/\nRUN ./bin/web setup install --frozen-lockfile\n\n# build frontend assets\n# set the env to production *after* yarn has done an install, to make sure all\n# libraries required for building are included.\nENV NODE_ENV production\nCOPY web/app ./web/app\nRUN ./bin/web build\n\n## compile go server\nFROM go-deps AS golang\nWORKDIR /linkerd-build\nRUN mkdir -p web\nCOPY web/main.go web\nCOPY web/srv web/srv\nCOPY controller controller\nCOPY viz/metrics-api viz/metrics-api\nCOPY viz/pkg viz/pkg\nCOPY viz/tap/gen/tap viz/tap/gen/tap\nCOPY viz/tap/pkg viz/tap/pkg\nCOPY pkg pkg\nCOPY charts charts\nARG TARGETARCH\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -mod=readonly -o web/web -ldflags \"-s -w\" ./web\n\n## package it all up\nFROM scratch\nLABEL org.opencontainers.image.source=https://github.com/linkerd/linkerd2\nWORKDIR /linkerd\n\nCOPY LICENSE .\nCOPY --from=golang /linkerd-build/web/web .\nCOPY --from=webpack-bundle /linkerd-build/web/app/dist app/dist\nCOPY web/templates templates\nCOPY --from=golang /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/\n\nARG LINKERD_VERSION\nENV LINKERD_CONTAINER_VERSION_OVERRIDE=${LINKERD_VERSION}\n\nENTRYPOINT [\"./web\"]\n"
  },
  {
    "path": "web/app/.eslintrc",
    "content": "{\n  \"plugins\": [\n    \"react\",\n    \"promise\"\n  ],\n  \"parser\": \"@babel/eslint-parser\",\n  \"parserOptions\": {\n    \"sourceType\": \"module\",\n    \"ecmaFeatures\": {\n      \"jsx\": true\n    }\n  },\n  \"env\": {\n    \"browser\": true,\n    \"es6\": true,\n    \"mocha\": true\n  },\n  \"extends\": [\n    \"airbnb\",\n    \"airbnb/hooks\",\n    \"eslint:recommended\",\n    \"plugin:promise/recommended\",\n    \"plugin:react/recommended\"\n  ],\n  \"rules\": {\n    \"arrow-parens\": [2, \"as-needed\"],\n    \"curly\": [2, \"all\"],\n    \"eqeqeq\": [2, \"smart\"],\n    \"import/order\": [2, { \"groups\": [[\"builtin\", \"external\", \"parent\", \"sibling\", \"index\"]] }],\n    \"indent\": 2,\n    \"no-console\": [1, { \"allow\": [\"warn\", \"error\"] }], // minor offense\n    \"no-restricted-imports\": [2, \"lodash\", \"@material-ui/core\", \"@material-ui/icons\"],\n    \"react/destructuring-assignment\": [0],\n    \"react/jsx-closing-bracket-location\": [2, { \"nonEmpty\": \"after-props\", \"selfClosing\": \"after-props\" }],\n    \"react/jsx-curly-spacing\": [2, \"never\", {\"allowMultiline\": false}],\n    \"react/jsx-no-duplicate-props\": [2, {\"ignoreCase\": false}],\n    \"react/jsx-wrap-multilines\": [\n      2,\n      {\n        \"declaration\": \"parens-new-line\",\n        \"assignment\": \"parens-new-line\",\n        \"return\": \"parens-new-line\",\n        \"arrow\": \"parens-new-line\"\n      }\n    ],\n    \"react/prop-types\": [2, {\n      \"ignore\": [\"classes\"],\n      \"customValidators\": [],\n      \"skipUndeclared\": false\n    }],\n    \"react/no-unstable-nested-components\": [\n      \"error\",\n      {\"allowAsProps\": true}\n    ],\n    \"semi\": [2, \"always\"],\n    \"sort-imports\": [0],\n    \"space-before-blocks\": [2, \"always\"],\n    \"space-before-function-paren\": [2, \"never\"],\n    \"strict\": [2, \"safe\"],\n\n    // The rest of this file disables airbnb defaults that don't work for our project\n\n    // These rules are stylistic in nature, and don't work with the existing project style\n    \"arrow-body-style\": 0,\n    \"import/extensions\": 0,\n    \"import/prefer-default-export\": 0,\n    \"max-len\": 0,\n    \"no-confusing-arrow\": 0,\n    \"no-else-return\": 0,\n    \"no-nested-ternary\": 0,\n    \"object-curly-newline\": 0,\n    \"operator-linebreak\": 0,\n    \"prefer-destructuring\": 0,\n    \"react/display-name\": 0,\n    \"react/jsx-curly-newline\": 0,\n    \"react/jsx-filename-extension\": 0,\n    \"react/jsx-fragments\": 0,\n    \"react/jsx-one-expression-per-line\": 0,\n    \"react/sort-comp\": 0,\n\n    // These rules are disabled to support specific features/conventions that we use\n    \"no-bitwise\": 0,\n    \"no-tabs\": 0,\n    \"no-throw-literal\": 0,\n    \"no-underscore-dangle\": 0,\n    \"func-names\": [\"error\", \"as-needed\"],\n    \"prefer-promise-reject-errors\": 0,\n    \"promise/always-return\": 0,\n    \"react/jsx-props-no-spreading\": 0,\n\n    // These rules represent best practices that we're not adhering to, and should be enabled\n    \"no-param-reassign\": [\"error\", { \"props\": false }]\n  }\n}\n"
  },
  {
    "path": "web/app/.linguirc",
    "content": "{\n   \"localeDir\": \"js/locales/\",\n   \"srcPathDirs\": [\"js/\"],\n   \"format\": \"minimal\"\n}\n"
  },
  {
    "path": "web/app/__mocks__/fileMock.js",
    "content": "module.exports = 'test-file-stub';\n"
  },
  {
    "path": "web/app/__mocks__/styleMock.js",
    "content": "module.exports = {};\n"
  },
  {
    "path": "web/app/babel.config.json",
    "content": "{\n  \"plugins\": [\n    \"@babel/plugin-proposal-class-properties\",\n    \"babel-plugin-macros\"\n  ],\n  \"env\": {\n    \"production\": {\n      \"plugins\": [\"transform-react-remove-prop-types\"]\n    }\n  },\n  \"presets\": [\n    [\"@babel/preset-env\",\n      { // Copied from https://newbedev.com/babel-7-referenceerror-regeneratorruntime-is-not-defined\n        \"useBuiltIns\": \"entry\",\n        \"corejs\": 3,\n        \"targets\": {\"node\": \"current\"}\n      }\n    ], \"@babel/preset-react\"]\n}\n"
  },
  {
    "path": "web/app/css/styles.css",
    "content": ":root {\n  --linkblue: #2196f3;\n  --font-stack: 'Roboto', helvetica, arial, sans-serif;\n}\n\nbody {\n  font-family: var(--font-stack);\n  text-rendering: optimizeLegibility;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\na {\n  text-decoration: none;\n  color: var(--linkblue);\n}\na:focus, a:hover, a:visited, a:link, a:active {\n  text-decoration: none;\n}\n\n.breadcrumb-link a {\n  color: white;\n}\n\n.tapGrayed {\n  color: lightgray;\n}\n\n.errorMessage a {\n  color: white;\n}\n"
  },
  {
    "path": "web/app/css/svg-wrappers.css",
    "content": "/* image styles for images in SvgWrappers.jsx */\n.grafana-st0{fill:url(#SVGID_1_grafana);}\n\n/* linkerd logo only */\n.linkerd-logo-only .st0{fill:url(#SVGID_1_);}\n.linkerd-logo-only .st1{fill:url(#SVGID_2_);}\n.linkerd-logo-only .st2{fill:#2BEDA7;}\n.linkerd-logo-only .st3{fill:url(#SVGID_3_);}\n.linkerd-logo-only .st4{fill:url(#SVGID_4_);}\n.linkerd-logo-only .st5{fill:url(#SVGID_5_);}\n.linkerd-logo-only .st6{fill:url(#SVGID_6_);}\n.linkerd-logo-only .st7{fill:url(#SVGID_7_);}\n.linkerd-logo-only .st8{fill:url(#SVGID_8_);}\n\n\n/* linkerd word logo  */\n.linkerd-word-logo .st0{fill:#FF576B;}\n.linkerd-word-logo .st1{fill:#6D6E71;}\n.linkerd-word-logo .st2{fill:#FFFFFF;}\n.linkerd-word-logo .st3{fill:url(#SVGID_1_);}\n.linkerd-word-logo .st4{fill:url(#SVGID_2_);}\n.linkerd-word-logo .st5{fill:#2BEDA7;}\n.linkerd-word-logo .st6{fill:url(#SVGID_3_);}\n.linkerd-word-logo .st7{fill:url(#SVGID_4_);}\n.linkerd-word-logo .st8{fill:url(#SVGID_5_);}\n.linkerd-word-logo .st9{fill:url(#SVGID_6_);}\n.linkerd-word-logo .st10{fill:url(#SVGID_7_);}\n.linkerd-word-logo .st11{fill:url(#SVGID_8_);}\n.linkerd-word-logo .st12{fill:url(#SVGID_9_);}\n.linkerd-word-logo .st13{fill:url(#SVGID_10_);}\n.linkerd-word-logo .st14{fill:url(#SVGID_11_);}\n.linkerd-word-logo .st15{fill:url(#SVGID_12_);}\n.linkerd-word-logo .st16{fill:url(#SVGID_13_);}\n.linkerd-word-logo .st17{fill:url(#SVGID_14_);}\n.linkerd-word-logo .st18{fill:url(#SVGID_15_);}\n.linkerd-word-logo .st19{fill:url(#SVGID_16_);}\n.linkerd-word-logo .st20{fill:url(#SVGID_17_);}\n.linkerd-word-logo .st21{fill:url(#SVGID_18_);}\n.linkerd-word-logo .st22{fill:url(#SVGID_19_);}\n.linkerd-word-logo .st23{fill:url(#SVGID_20_);}\n.linkerd-word-logo .st24{fill:url(#SVGID_21_);}\n.linkerd-word-logo .st25{fill:url(#SVGID_22_);}\n.linkerd-word-logo .st26{fill:url(#SVGID_23_);}\n.linkerd-word-logo .st27{fill:url(#SVGID_24_);}\n.linkerd-word-logo .st28{filter:url(#Adobe_OpacityMaskFilter);}\n.linkerd-word-logo .st29{mask:url(#mask-2_6_);}\n.linkerd-word-logo .st30{fill:#018AFD;}\n.linkerd-word-logo .st31{fill:#00A2FD;}\n.linkerd-word-logo .st32{fill:#06C0FD;}\n.linkerd-word-logo .st33{fill:#03B5FD;}\n.linkerd-word-logo .st34{fill:#0074FD;}\n.linkerd-word-logo .st35{fill:#005CFD;}\n.linkerd-word-logo .st36{filter:url(#Adobe_OpacityMaskFilter_1_);}\n.linkerd-word-logo .st37{mask:url(#mask-4_6_);fill:#003CFD;}\n.linkerd-word-logo .st38{fill:#0E13FD;}\n.linkerd-word-logo .st39{filter:url(#Adobe_OpacityMaskFilter_2_);}\n.linkerd-word-logo .st40{mask:url(#mask-2_5_);fill:#FFFFFF;}\n.linkerd-word-logo .st41{filter:url(#Adobe_OpacityMaskFilter_3_);}\n.linkerd-word-logo .st42{mask:url(#mask-4_5_);fill:#003CFD;}\n.linkerd-word-logo .st43{filter:url(#Adobe_OpacityMaskFilter_4_);}\n.linkerd-word-logo .st44{mask:url(#mask-2_1_);fill:#FFFFFF;}\n.linkerd-word-logo .st45{filter:url(#Adobe_OpacityMaskFilter_5_);}\n.linkerd-word-logo .st46{mask:url(#mask-4_4_);fill:#FFFFFF;}\n.linkerd-word-logo .st47{fill:#018BFE;}\n.linkerd-word-logo .st48{fill:#02C2FE;}\n.linkerd-word-logo .st49{fill:#04B6FE;}\n.linkerd-word-logo .st50{fill:#0095FE;}\n.linkerd-word-logo .st51{fill:#0074FE;}\n.linkerd-word-logo .st52{fill:#005CFE;}\n.linkerd-word-logo .st53{fill:#003DFE;}\n.linkerd-word-logo .st54{fill:#0E13FE;}\n.linkerd-word-logo .st55{filter:url(#Adobe_OpacityMaskFilter_6_);}\n.linkerd-word-logo .st56{mask:url(#mask-2_7_);}\n.linkerd-word-logo .st57{filter:url(#Adobe_OpacityMaskFilter_7_);}\n.linkerd-word-logo .st58{mask:url(#mask-2_2_);fill:#FFFFFF;}\n.linkerd-word-logo .st59{filter:url(#Adobe_OpacityMaskFilter_8_);}\n.linkerd-word-logo .st60{mask:url(#mask-4_3_);fill:#003CFD;}\n.linkerd-word-logo .st61{filter:url(#Adobe_OpacityMaskFilter_9_);}\n.linkerd-word-logo .st62{mask:url(#mask-4_7_);fill:#003CFD;}\n.linkerd-word-logo .st63{filter:url(#Adobe_OpacityMaskFilter_10_);}\n.linkerd-word-logo .st64{mask:url(#mask-4_1_);fill:#003CFD;}\n.linkerd-word-logo .st65{fill:url(#SVGID_25_);}\n.linkerd-word-logo .st66{fill:url(#SVGID_26_);}\n.linkerd-word-logo .st67{fill:url(#SVGID_27_);}\n.linkerd-word-logo .st68{fill:url(#SVGID_28_);}\n.linkerd-word-logo .st69{fill:url(#SVGID_29_);}\n.linkerd-word-logo .st70{fill:url(#SVGID_30_);}\n.linkerd-word-logo .st71{fill:url(#SVGID_31_);}\n.linkerd-word-logo .st72{fill:url(#SVGID_32_);}\n.linkerd-word-logo .st73{fill:url(#SVGID_33_);}\n.linkerd-word-logo .st74{fill:url(#SVGID_34_);}\n.linkerd-word-logo .st75{fill:url(#SVGID_35_);}\n.linkerd-word-logo .st76{fill:url(#SVGID_36_);}\n.linkerd-word-logo .st77{fill:url(#SVGID_37_);}\n.linkerd-word-logo .st78{fill:url(#SVGID_38_);}\n.linkerd-word-logo .st79{fill:url(#SVGID_39_);}\n.linkerd-word-logo .st80{fill:url(#SVGID_40_);}\n.linkerd-word-logo .st81{fill:url(#SVGID_41_);}\n.linkerd-word-logo .st82{fill:url(#SVGID_42_);}\n.linkerd-word-logo .st83{fill:url(#SVGID_43_);}\n.linkerd-word-logo .st84{fill:url(#SVGID_44_);}\n.linkerd-word-logo .st85{fill:url(#SVGID_45_);}\n.linkerd-word-logo .st86{fill:url(#SVGID_46_);}\n.linkerd-word-logo .st87{fill:url(#SVGID_47_);}\n.linkerd-word-logo .st88{fill:url(#SVGID_48_);}\n.linkerd-word-logo .st89{fill:url(#SVGID_49_);}\n.linkerd-word-logo .st90{fill:url(#SVGID_50_);}\n.linkerd-word-logo .st91{fill:url(#SVGID_51_);}\n.linkerd-word-logo .st92{fill:url(#SVGID_52_);}\n.linkerd-word-logo .st93{fill:url(#SVGID_53_);}\n.linkerd-word-logo .st94{fill:url(#SVGID_54_);}\n.linkerd-word-logo .st95{fill:url(#SVGID_55_);}\n.linkerd-word-logo .st96{fill:url(#SVGID_56_);}\n.linkerd-word-logo .st97{fill:url(#SVGID_57_);}\n.linkerd-word-logo .st98{fill:url(#SVGID_58_);}\n.linkerd-word-logo .st99{fill:url(#SVGID_59_);}\n.linkerd-word-logo .st100{fill:url(#SVGID_60_);}\n.linkerd-word-logo .st101{fill:url(#SVGID_61_);}\n.linkerd-word-logo .st102{fill:url(#SVGID_62_);}\n.linkerd-word-logo .st103{fill:url(#SVGID_63_);}\n.linkerd-word-logo .st104{fill:url(#SVGID_64_);}\n.linkerd-word-logo .st105{display:none;}\n.linkerd-word-logo .st106{display:inline;}\n"
  },
  {
    "path": "web/app/gh_ann_reporter.js",
    "content": "class GhAnnReporter {\n  /* eslint-disable class-methods-use-this */\n  onRunComplete(contexts, results) {\n    // only pick the first line of the error\n    const msgRe = /^(.+)$/m;\n    const pathRe = /^.+\\/linkerd2\\/(.+)$/;\n    results.testResults.forEach(item => {\n      if (item.numFailingTests > 0) {\n        const msgArr = msgRe.exec(item.failureMessage);\n        let msg = 'Unknown';\n        if (msgArr.length > 0) {\n          msg = msgArr[1];\n        }\n        const pathArr = pathRe.exec(item.testFilePath);\n        let path = 'Unknown';\n        if (pathArr.length > 0) {\n          path = pathArr[1];\n        }\n        /* eslint-disable no-console */\n        console.log(`::error file=${path}::${msg}`);\n      }\n    });\n  }\n}\n\nmodule.exports = GhAnnReporter;\n"
  },
  {
    "path": "web/app/index_bundle.js.lodash.tmpl",
    "content": "// based on https://gist.github.com/necolas/1025811\n(function(doc) {\n  let js,\n    fjs = doc.getElementById('bundle'),\n    add = function(url, id) {\n      if (doc.getElementById(id)) {return;}\n      js = doc.createElement('script');\n      js.src = url;\n      id && (js.id = id);\n      fjs.parentNode.insertBefore(js, fjs);\n    };\n<% for (let chunk in htmlWebpackPlugin.files.js) { %>\n  <% if (compilation.compiler.outputFileSystem.constructor.name === 'MemoryFileSystem') { %>\n    add('http://localhost:8080/<%= htmlWebpackPlugin.files.js[chunk] %>');\n  <% } else { %>\n    let root = fjs.src.substring(0, fjs.src.indexOf('dist'));\n    add(root+'<%= htmlWebpackPlugin.files.js[chunk] %>');\n  <% } %>\n<% } %>\n}(document));\n"
  },
  {
    "path": "web/app/js/components/AddResources.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\nimport { friendlyTitle } from './util/Utils.js';\nimport { incompleteMeshMessage } from './util/CopyUtils.jsx';\n\nconst AddResources = function({ resourceName, resourceType }) {\n  return (\n    <div className=\"mesh-completion-message\">\n      {friendlyTitle(resourceType).singular} {resourceName} is not in the mesh.\n      {incompleteMeshMessage()}\n    </div>\n  );\n};\n\nAddResources.propTypes = {\n  resourceName: PropTypes.string.isRequired,\n  resourceType: PropTypes.string.isRequired,\n};\n\nexport default AddResources;\n"
  },
  {
    "path": "web/app/js/components/BaseTable.jsx",
    "content": "import CloseIcon from '@material-ui/icons/Close';\nimport FilterListIcon from '@material-ui/icons/FilterList';\nimport Hidden from '@material-ui/core/Hidden';\nimport Paper from '@material-ui/core/Paper';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Table from '@material-ui/core/Table';\nimport TableBody from '@material-ui/core/TableBody';\nimport TableCell from '@material-ui/core/TableCell';\nimport TableHead from '@material-ui/core/TableHead';\nimport TableRow from '@material-ui/core/TableRow';\nimport TableSortLabel from '@material-ui/core/TableSortLabel';\nimport TextField from '@material-ui/core/TextField';\nimport Toolbar from '@material-ui/core/Toolbar';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport Typography from '@material-ui/core/Typography';\nimport _find from 'lodash/find';\nimport _get from 'lodash/get';\nimport _isNil from 'lodash/isNil';\nimport _orderBy from 'lodash/orderBy';\nimport classNames from 'classnames';\nimport { withStyles } from '@material-ui/core/styles';\nimport { regexFilterString } from './util/Utils.js';\nimport EmptyCard from './EmptyCard.jsx';\n\nconst styles = theme => ({\n  root: {\n    width: '100%',\n    marginTop: theme.spacing(3),\n    marginBottom: theme.spacing(3),\n    overflowX: 'auto',\n  },\n  tableHeader: {\n    fontSize: '12px',\n    opacity: 0.6,\n    lineHeight: 1,\n  },\n  tableHeaderActive: {\n    fontSize: '12px',\n    opacity: 1,\n    lineHeight: 1,\n  },\n  activeSortIcon: {\n    opacity: 1,\n  },\n  toolbar: {\n    paddingLeft: '24px',\n  },\n  toolbarIcon: {\n    cursor: 'pointer',\n    opacity: 0.8,\n  },\n  sortIcon: {\n    fontSize: '16px',\n    opacity: 0.4,\n  },\n  denseTable: {\n    paddingRight: '8px',\n    '&:last-child': {\n      paddingRight: '24px',\n    },\n  },\n  title: {\n    flexGrow: 1,\n  },\n});\n\nclass BaseTable extends React.Component {\n  constructor(props) {\n    super(props);\n\n    const { defaultOrder, defaultOrderBy } = props;\n    this.state = {\n      order: defaultOrder || 'asc',\n      orderBy: defaultOrderBy,\n      filterBy: '',\n    };\n    this.handleFilterInputChange = this.handleFilterInputChange.bind(this);\n    this.handleFilterToggle = this.handleFilterToggle.bind(this);\n  }\n\n  createSortHandler = col => () => {\n    const { order, orderBy } = this.state;\n\n    const newOrderBy = col.dataIndex;\n    let newOrder = col.defaultSortOrder || 'asc';\n\n    if (orderBy === newOrderBy && order === newOrder) {\n      newOrder = newOrder === 'asc' ? 'desc' : 'asc';\n    }\n\n    this.setState({ order: newOrder, orderBy: newOrderBy });\n  };\n\n  handleFilterInputChange = event => {\n    this.setState({ filterBy: regexFilterString(event.target.value) });\n  };\n\n  handleFilterToggle = () => {\n    const { showFilter } = this.state;\n    this.setState({ showFilter: !showFilter, filterBy: '' });\n  };\n\n  static generateRows(tableRows, tableColumns, order, orderBy, filterBy) {\n    let rows = tableRows;\n    const col = _find(tableColumns, d => d.dataIndex === orderBy);\n\n    if (orderBy && col.sorter) {\n      rows = _orderBy(rows, row => col.sorter(row), order);\n    }\n    if (filterBy) {\n      const columnsToFilter = tableColumns.filter(c => c.filter);\n      const filteredRows = rows.filter(row => {\n        return columnsToFilter.some(c => {\n          const rowText = c.filter(row);\n          return rowText.match(filterBy);\n        });\n      });\n      rows = filteredRows;\n    }\n\n    return rows;\n  }\n\n  renderHeaderCell = (col, order, orderBy) => {\n    const active = orderBy === col.dataIndex;\n    const { classes, padding } = this.props;\n    let tableCell;\n\n    if (col.sorter) {\n      tableCell = (\n        <TableCell\n          key={col.key || col.dataIndex}\n          align={col.isNumeric ? 'right' : 'left'}\n          sortDirection={orderBy === col.dataIndex ? order : false}\n          classes={{\n            root: active ? classes.tableHeaderActive : classes.tableHeader,\n          }}\n          className={classNames({ [classes.denseTable]: padding === 'dense' })}>\n          <TableSortLabel\n            active={active}\n            direction={active ? order : col.defaultSortOrder || 'asc'}\n            classes={{\n              icon: classes.sortIcon,\n              active: classes.activeSortIcon,\n            }}\n            onClick={this.createSortHandler(col)}>\n            {col.title}\n          </TableSortLabel>\n        </TableCell>\n      );\n    } else {\n      tableCell = (\n        <TableCell\n          key={col.key || col.dataIndex}\n          align={col.isNumeric ? 'right' : 'left'}\n          className={classNames(\n            { [classes.denseTable]: padding === 'dense' },\n            classes.tableHeader,\n          )}>\n          {col.title}\n        </TableCell>\n      );\n    }\n\n    return _isNil(col.tooltip) ? tableCell :\n    <Tooltip key={col.key || col.dataIndex} placement=\"top\" title={col.tooltip}>{tableCell}</Tooltip>;\n  };\n\n  renderToolbar = (classes, title) => {\n    const { showFilter } = this.state;\n\n    return (\n      <Toolbar className={classes.toolbar}>\n        <Typography\n          className={classes.title}\n          variant=\"h5\">\n          {title}\n        </Typography>\n        {showFilter &&\n          <TextField\n            id=\"input-with-icon-textfield\"\n            onChange={this.handleFilterInputChange}\n            placeholder=\"Filter by text\"\n            autoFocus />}\n        {!showFilter &&\n        <Hidden smDown>\n          <FilterListIcon\n            className={classes.toolbarIcon}\n            onClick={this.handleFilterToggle} />\n        </Hidden>}\n        {showFilter &&\n          <CloseIcon\n            className={classes.toolbarIcon}\n            onClick={this.handleFilterToggle} />}\n      </Toolbar>\n    );\n  };\n\n  render() {\n    const { classes, enableFilter, tableRows, tableColumns, tableClassName, title, rowKey, padding } = this.props;\n    const { order, orderBy, filterBy } = this.state;\n    const sortedTableRows = tableRows.length > 0 ? BaseTable.generateRows(tableRows, tableColumns, order, orderBy, filterBy) : tableRows;\n\n    return (\n      <Paper className={classes.root} elevation={3}>\n        {enableFilter &&\n          this.renderToolbar(classes, title)}\n        <Table className={`${classes.table} ${tableClassName}`}>\n          <TableHead>\n            <TableRow>\n              { tableColumns.map(c => (\n                this.renderHeaderCell(c, order, orderBy)\n              ))}\n            </TableRow>\n          </TableHead>\n          <TableBody>\n            { sortedTableRows.length > 0 && (\n              <React.Fragment>\n                {\n                  sortedTableRows.map(d => {\n                    const key = !rowKey ? d.key : rowKey(d);\n                    const tableRow = (\n                      <TableRow key={key}>\n                        {tableColumns.map(c => (\n                          <TableCell\n                            className={classNames({ [classes.denseTable]: padding === 'dense' })}\n                            key={`table-${key}-${c.key || c.dataIndex}`}\n                            align={c.isNumeric ? 'right' : 'left'}>\n                            {c.render ? c.render(d) : _get(d, c.dataIndex)}\n                          </TableCell>\n                        ))}\n                      </TableRow>\n                    );\n                    return _isNil(d.tooltip) ? tableRow :\n                    <Tooltip key={`table-row-${key}`} placement=\"left\" title={d.tooltip}>{tableRow}</Tooltip>;\n                  })}\n              </React.Fragment>\n            )}\n          </TableBody>\n        </Table>\n\n        { sortedTableRows.length === 0 && (\n          <EmptyCard />\n        )}\n      </Paper>\n    );\n  }\n}\n\nBaseTable.propTypes = {\n  defaultOrder: PropTypes.string,\n  defaultOrderBy: PropTypes.string,\n  enableFilter: PropTypes.bool,\n  padding: PropTypes.string,\n  rowKey: PropTypes.func,\n  tableClassName: PropTypes.string,\n  tableColumns: PropTypes.arrayOf(PropTypes.shape({\n    dataIndex: PropTypes.string,\n    defaultSortOrder: PropTypes.string,\n    isNumeric: PropTypes.bool,\n    render: PropTypes.func,\n    sorter: PropTypes.func,\n    title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),\n  })).isRequired,\n  tableRows: PropTypes.arrayOf(PropTypes.shape({})),\n  title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),\n};\n\nBaseTable.defaultProps = {\n  defaultOrder: 'asc',\n  defaultOrderBy: null,\n  enableFilter: false,\n  padding: 'default',\n  rowKey: null,\n  tableClassName: '',\n  tableRows: [],\n  title: '',\n};\n\nexport default withStyles(styles)(BaseTable);\n"
  },
  {
    "path": "web/app/js/components/BaseTable.test.js",
    "content": "import _merge from 'lodash/merge';\nimport ApiHelpers from './util/ApiHelpers.jsx';\nimport BaseTable from './BaseTable.jsx';\nimport TableBody from '@material-ui/core/TableBody';\nimport FilterListIcon from '@material-ui/icons/FilterList';\nimport TableRow from '@material-ui/core/TableRow';\nimport { routerWrap, i18nWrap } from '../../test/testHelpers.jsx';\nimport { mount, shallow } from 'enzyme';\nimport React from 'react';\n\ndescribe(\"Tests for <BaseTable>\", () => {\n  const defaultProps = {\n    api: ApiHelpers(\"\"),\n  };\n  const tableColumns = [{\n    dataIndex: \"pods.totalPods\",\n    title: \"Meshed\"\n  },\n  {\n    dataIndex: \"deployment\",\n    title: \"Name\"\n  },\n  {\n    dataIndex: \"namespace\",\n    title: \"Namespace\"\n  }];\n\n  it(\"renders the table with sample data\", () => {\n\n    let extraProps = _merge({}, defaultProps, {\n      tableRows: [{\n        deployment: \"authors\",\n        namespace: \"default\",\n        key: \"default-deployment-authors\",\n        pods: {totalPods: \"1\", meshedPods: \"1\"}\n      }],\n      tableColumns: tableColumns,\n    });\n\n    const component = mount(routerWrap(BaseTable, extraProps));\n    const table = component.find(\"BaseTable\");\n    expect(table).toBeDefined();\n    expect(table.props().tableRows).toHaveLength(1);\n    expect(table.props().tableColumns).toHaveLength(3);\n    expect(table.find(\"td\")).toHaveLength(3);\n    expect(table.find(\"tr\")).toHaveLength(2);\n  });\n\n  it(\"if enableFilter is true, user is shown the filter dialog\", () => {\n\n    let extraProps = _merge({}, defaultProps, {\n      tableRows: [{\n        deployment: \"authors\",\n        namespace: \"default\",\n        key: \"default-deployment-authors\",\n        pods: {totalPods: \"1\", meshedPods: \"1\"}\n      },\n      {\n        deployment: \"books\",\n        namespace: \"default\",\n        key: \"default-deployment-books\",\n        pods: {totalPods: \"2\", meshedPods: \"1\"}\n      }],\n      tableColumns: tableColumns,\n      enableFilter: true\n    });\n\n    const component = mount(routerWrap(BaseTable, extraProps));\n    const table = component.find(\"BaseTable\");\n    const enableFilter = table.prop(\"enableFilter\");\n    const filterIcon = table.find(\"FilterListIcon\");\n    expect(enableFilter).toEqual(true);\n    expect(filterIcon).toBeDefined();\n  });\n\n  it(\"renders the table without data\", () => {\n    let extraProps = _merge({}, defaultProps, {\n      tableRows: [],\n      tableColumns: tableColumns,\n    });\n\n    const component = mount(i18nWrap(routerWrap(BaseTable, extraProps)));\n    const table = component.find(\"BaseTable\");\n    expect(table).toBeDefined();\n    const emptyCard = table.find(\"EmptyCard\");\n    expect(emptyCard).toBeDefined();\n    expect(emptyCard).toHaveLength(1);\n  });\n\n  it(\"if enableFilter is true, user can filter rows by search term\", () => {\n    let extraProps = _merge({}, defaultProps, {\n      tableRows: [{\n        deployment: \"authors\",\n        namespace: \"default\",\n        key: \"default-deployment-authors\",\n        pods: {totalPods: \"1\", meshedPods: \"1\"}\n      },\n      {\n        deployment: \"books\",\n        namespace: \"default\",\n        key: \"default-deployment-books\",\n        pods: {totalPods: \"2\", meshedPods: \"1\"}\n      }],\n      tableColumns: tableColumns,\n      enableFilter: true\n    });\n\n    const component = shallow(<BaseTable {...extraProps} />);\n    const table = component.dive();\n    expect(table.find(TableBody).find(TableRow)).toHaveLength(2);\n    const enableFilter = component.prop(\"enableFilter\");\n    const filterIcon = table.find(FilterListIcon);\n    expect(enableFilter).toEqual(true);\n    expect(filterIcon).toHaveLength(1);\n\n    filterIcon.simulate(\"click\");\n    setTimeout(() => {\n      const input = table.find(\"input\");\n      input.simulate(\"change\", {target: {value: \"authors\"}});\n      expect(table.html()).not.toContain('books');\n      expect(table.html()).toContain('authors');\n      expect(table.find(TableBody).find(TableRow)).toHaveLength(1);\n    }, 100);\n  });\n});\n"
  },
  {
    "path": "web/app/js/components/BreadcrumbHeader.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\nimport ReactRouterPropTypes from 'react-router-prop-types';\nimport { Trans } from '@lingui/macro';\nimport _chunk from 'lodash/chunk';\nimport _takeWhile from 'lodash/takeWhile';\nimport { friendlyTitle, isResource, singularResource } from './util/Utils.js';\nimport { withContext } from './util/AppContext.jsx';\n\nconst routeToCrumbTitle = {\n  controlplane: <Trans>menuItemControlPlane</Trans>,\n  tap: <Trans>menuItemTap</Trans>,\n  top: <Trans>menuItemTop</Trans>,\n  routes: <Trans>menuItemRoutes</Trans>,\n  community: <Trans>menuItemCommunity</Trans>,\n  gateways: <Trans>menuItemGateway</Trans>,\n  extensions: <Trans>menuItemExtension</Trans>,\n};\n\nclass BreadcrumbHeader extends React.Component {\n  static processResourceDetailURL(segments) {\n    if (segments.length === 4) {\n      const splitSegments = _chunk(segments, 2);\n      const resourceNameSegment = splitSegments[1];\n      resourceNameSegment[0] = singularResource(resourceNameSegment[0]);\n      return splitSegments[0].concat(resourceNameSegment.join('/'));\n    } else {\n      return segments;\n    }\n  }\n\n  static convertURLToBreadcrumbs(location) {\n    if (location.length === 0) {\n      return [];\n    } else {\n      const segments = location.split('/').slice(1);\n      const finalSegments = BreadcrumbHeader.processResourceDetailURL(segments);\n\n      return finalSegments.map(segment => {\n        const partialUrl = _takeWhile(segments, s => {\n          return s !== segment;\n        });\n\n        if (partialUrl.length !== segments.length) {\n          partialUrl.push(segment);\n        }\n\n        return {\n          link: `/${partialUrl.join('/')}`,\n          segment,\n        };\n      });\n    }\n  }\n\n  static segmentToFriendlyTitle(segment, isResourceType) {\n    if (isResourceType) {\n      return routeToCrumbTitle[segment] || friendlyTitle(segment).plural;\n    } else {\n      return routeToCrumbTitle[segment] || segment;\n    }\n  }\n\n  static renderBreadcrumbSegment(segment, numCrumbs, index) {\n    const isMeshResource = isResource(segment);\n\n    if (isMeshResource) {\n      if (numCrumbs === 1 || index !== 0) {\n        // If the segment is a K8s resource type, it should be pluralized if\n        // the complete breadcrumb group describes a single-word list of\n        // resources (\"Namespaces\") OR if the breadcrumb group describes a list\n        // of resources within a specific namespace (\"Namespace > linkerd >\n        // Deployments\")\n        return BreadcrumbHeader.segmentToFriendlyTitle(segment, true);\n      }\n      return friendlyTitle(segment).singular;\n    }\n    return BreadcrumbHeader.segmentToFriendlyTitle(segment, false);\n  }\n\n  render() {\n    const { pathPrefix, location } = this.props;\n    const { pathname } = location;\n    const breadcrumbs = BreadcrumbHeader.convertURLToBreadcrumbs(pathname.replace(pathPrefix, ''));\n\n    return breadcrumbs.map((pathSegment, index) => {\n      return (\n        <span key={pathSegment.segment}>\n          {BreadcrumbHeader.renderBreadcrumbSegment(pathSegment.segment, breadcrumbs.length, index)}\n          { index < breadcrumbs.length - 1 ? ' > ' : null }\n        </span>\n      );\n    });\n  }\n}\n\nBreadcrumbHeader.propTypes = {\n  api: PropTypes.shape({\n    PrefixedLink: PropTypes.func.isRequired,\n  }).isRequired,\n  location: ReactRouterPropTypes.location.isRequired,\n  pathPrefix: PropTypes.string.isRequired,\n};\n\nexport default withContext(BreadcrumbHeader);\n"
  },
  {
    "path": "web/app/js/components/BreadcrumbHeader.test.jsx",
    "content": "import { BrowserRouter } from 'react-router-dom';\nimport BreadcrumbHeader from './BreadcrumbHeader.jsx';\nimport React from 'react';\nimport { mount } from 'enzyme';\nimport { i18nWrap } from '../../test/testHelpers.jsx';\n\nconst loc = {\n  pathname: '',\n  hash: '',\n  pathPrefix: '/path/prefix',\n  search: '',\n};\n\ndescribe('Tests for <BreadcrumbHeader>', () => {\n\n  it(\"renders breadcrumbs for a pathname\", () => {\n\n    loc.pathname = \"/namespaces/emojivoto/deployments/web\";\n    const component = mount(\n      <BrowserRouter>\n        <BreadcrumbHeader\n          location={loc}\n          pathPrefix=\"\" />\n      </BrowserRouter>\n    );\n\n    const crumbs = component.find(\"span\");\n    expect(crumbs).toHaveLength(3);\n  });\n\n  it(\"renders correct breadcrumb text for top-level pages [Namespaces]\", () => {\n    loc.pathname = \"/namespaces\";\n    const component = mount(\n      <BrowserRouter>\n        <BreadcrumbHeader\n          location={loc}\n          pathPrefix=\"\" />\n      </BrowserRouter>\n    );\n    const crumbs = component.find(\"span\");\n    expect(crumbs).toHaveLength(1);\n    const crumbText = crumbs.reduce((acc, crumb) => {\n      acc+= crumb.text();\n      return acc;\n    }, \"\")\n    expect(crumbText).toEqual(\"Namespaces\");\n  });\n\n  it(\"renders correct breadcrumb text for top-level pages [Control Plane]\",\n    () => {\n    loc.pathname = \"/controlplane\";\n    const component = mount(i18nWrap(\n      <BrowserRouter>\n        <BreadcrumbHeader\n          location={loc}\n          pathPrefix=\"\" />\n      </BrowserRouter>\n    ));\n    const crumbs = component.find(\"span\");\n    expect(crumbs).toHaveLength(1);\n    const crumbText = crumbs.reduce((acc, crumb) => {\n      acc+= crumb.text();\n      return acc;\n    }, \"\")\n    expect(crumbText).toEqual(\"Control Plane\");\n  });\n\n  it(`renders correct breadcrumb text for resource list page\n    [Namespace > emojivoto > Deployments`, () => {\n\n    loc.pathname = \"/namespaces/emojivoto/deployments\";\n    const component = mount(\n      <BrowserRouter>\n        <BreadcrumbHeader\n          location={loc}\n          pathPrefix=\"\" />\n      </BrowserRouter>\n    );\n\n    const crumbs = component.find(\"span\");\n    expect(crumbs).toHaveLength(3);\n    const crumbText = crumbs.reduce((acc, crumb) => {\n      acc+= crumb.text();\n      return acc;\n    }, \"\")\n    expect(crumbText).toEqual(\"Namespace > emojivoto > Deployments\")\n  });\n\n  it(`renders correct breadcrumb text for resource detail page\n    [Namespace > emojivoto > deployment/web`, () => {\n\n    loc.pathname = \"/namespaces/emojivoto/deployments/web\";\n    const component = mount(\n      <BrowserRouter>\n        <BreadcrumbHeader\n          location={loc}\n          pathPrefix=\"\" />\n      </BrowserRouter>\n    );\n\n    const crumbs = component.find(\"span\");\n    expect(crumbs).toHaveLength(3);\n    const crumbText = crumbs.reduce((acc, crumb) => {\n      acc+= crumb.text();\n      return acc;\n    }, \"\")\n    expect(crumbText).toEqual(\"Namespace > emojivoto > deployment/web\")\n\n  });\n});\n"
  },
  {
    "path": "web/app/js/components/CallToAction.jsx",
    "content": "import { Plural, Trans } from '@lingui/macro';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Step from '@material-ui/core/Step';\nimport StepContent from '@material-ui/core/StepContent';\nimport StepLabel from '@material-ui/core/StepLabel';\nimport Stepper from '@material-ui/core/Stepper';\nimport Typography from '@material-ui/core/Typography';\nimport { withStyles } from '@material-ui/core/styles';\nimport { incompleteMeshMessage } from './util/CopyUtils.jsx';\n\nconst styles = theme => ({\n  instructions: {\n    marginTop: theme.spacing(1),\n    marginBottom: theme.spacing(1),\n  },\n});\n\nfunction getSteps(numResources, resource) {\n  return [\n    { label: <Trans>controllerInstalledMsg</Trans>, key: 'installedMsg' },\n    { label: <Plural value={numResources} zero={resource} one={resource} other={resource} />, key: 'resourceMsg' },\n    { label: <Trans>connectResourceMsg {resource}</Trans>, content: incompleteMeshMessage(), key: 'connectMsg' },\n  ];\n}\n\nconst CallToAction = function({ resource, numResources, classes }) {\n  const steps = getSteps(numResources, resource);\n  const lastStep = steps.length - 1; // hardcode the last step as the active step\n\n  return (\n    <React.Fragment>\n      <Typography><Trans>serviceMeshInstalledMsg</Trans></Typography>\n      <Stepper\n        activeStep={lastStep}\n        className={classes.instructions}\n        orientation=\"vertical\">\n        {\n          steps.map((step, i) => {\n            const props = {};\n            props.completed = i < lastStep; // select the last step as the currently active one\n\n            return (\n              <Step key={step.key} {...props}>\n                <StepLabel>{step.label}</StepLabel>\n                <StepContent>{step.content}</StepContent>\n              </Step>\n            );\n          })\n        }\n      </Stepper>\n    </React.Fragment>\n  );\n};\n\nCallToAction.propTypes = {\n  numResources: PropTypes.number,\n  resource: PropTypes.string.isRequired,\n};\n\nCallToAction.defaultProps = {\n  numResources: null,\n};\n\nexport default withStyles(styles)(CallToAction);\n"
  },
  {
    "path": "web/app/js/components/CheckModal.jsx",
    "content": "import Button from '@material-ui/core/Button';\nimport CheckIcon from '@material-ui/icons/Check';\nimport CircularProgress from '@material-ui/core/CircularProgress';\nimport CloseIcon from '@material-ui/icons/Close';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport Grid from '@material-ui/core/Grid';\nimport Paper from '@material-ui/core/Paper';\nimport PriorityHigh from '@material-ui/icons/PriorityHigh';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Slide from '@material-ui/core/Slide';\nimport { Trans } from '@lingui/macro';\nimport Typography from '@material-ui/core/Typography';\nimport withMobileDialog from '@material-ui/core/withMobileDialog';\nimport { withStyles } from '@material-ui/core/styles';\nimport SimpleChip from './util/Chip.jsx';\nimport ErrorBanner from './ErrorBanner.jsx';\n\nconst styles = theme => ({\n  wrapper: {\n    marginBottom: '20px',\n  },\n  spinner: {\n    color: theme.status.dark.default,\n    position: 'absolute',\n    top: '50%',\n    left: '50%',\n    marginTop: '-40px',\n    marginLeft: '-40px',\n  },\n  dialog: {\n    minWidth: '60%',\n  },\n  dialogContent: {\n    overflow: 'hidden',\n    display: 'flex',\n    height: '860px',\n  },\n  contentWrapper: {\n    backgroundColor: 'black',\n    borderRadius: '5px',\n    maxHeight: '100%',\n    width: '100%',\n    overflowY: 'auto',\n  },\n  content: {\n    fontSize: '14px',\n    padding: theme.spacing(2),\n    color: 'white',\n  },\n  title: {\n    fontFamily: '\\'Roboto Mono\\', monospace',\n    textDecoration: 'underline',\n    color: 'white',\n    '&:not(:first-child)': {\n      paddingTop: '20px',\n    },\n  },\n  result: {\n    marginLeft: '10px',\n    fontFamily: '\\'Roboto Mono\\', monospace',\n  },\n  resultError: {\n    marginLeft: '20px',\n    fontFamily: '\\'Roboto Mono\\', monospace',\n    fontSize: '12px',\n  },\n  icon: {\n    marginLeft: '10px',\n    verticalAlign: 'bottom',\n  },\n  iconError: {\n    color: theme.status.dark.danger,\n  },\n  iconSuccess: {\n    color: theme.status.dark.good,\n  },\n  iconWarning: {\n    color: theme.status.dark.warning,\n  },\n  link: {\n    padding: '0px 5px',\n    color: 'white',\n  },\n});\n\nconst Transition = React.forwardRef((props, ref) => {\n  return <Slide direction=\"down\" ref={ref} {...props} />;\n});\n\nconst Results = function({ title, results, classes }) {\n  const getResultType = (error, warning) => {\n    if (error) {\n      if (warning) {\n        return 'warning';\n      } else {\n        return 'error';\n      }\n    } else {\n      return 'success';\n    }\n  };\n\n  return (\n    <React.Fragment>\n      <Typography className={classes.title}>\n        {title}\n      </Typography>\n\n      {results.map((result, index) => {\n        const resultType = getResultType(result.Err, result.Warning);\n\n        return (\n          <Grid container direction=\"row\" alignItems=\"flex-start\" key={result.Description}>\n            <Grid item>\n              <Icon type={resultType} classes={classes} />\n            </Grid>\n            <Grid item xs>\n              <Typography className={classes.result} variant=\"body2\" color=\"inherit\" data-i18n={`${title}_${index + 1}`}>\n                {result.Description}\n              </Typography>\n\n              {resultType !== 'success' && (\n                <React.Fragment>\n                  <Typography className={classes.resultError} variant=\"body2\" color=\"inherit\">\n                    {result.ErrMsg}\n                  </Typography>\n\n                  <Typography className={classes.resultError} variant=\"body2\" color=\"inherit\" gutterBottom>\n                    see\n                    <a\n                      className={classes.link}\n                      href={result.HintURL}\n                      target=\"_blank\"\n                      rel=\"noopener noreferrer\">\n                      {result.HintURL}\n                    </a>\n                    for hints\n                  </Typography>\n                </React.Fragment>\n              )}\n            </Grid>\n          </Grid>\n        );\n      })}\n    </React.Fragment>\n  );\n};\n\nResults.propTypes = {\n  results: PropTypes.arrayOf(PropTypes.shape({\n    Description: PropTypes.string.isRequired,\n    Err: PropTypes.shape({}),\n    ErrMsg: PropTypes.string,\n    HintURL: PropTypes.string,\n    Warning: PropTypes.bool,\n  })).isRequired,\n  title: PropTypes.string.isRequired,\n};\n\nconst Icon = function({ type, classes }) {\n  return (\n    <React.Fragment>\n      {(() => {\n        switch (type) {\n          case 'success':\n            return <CheckIcon className={`${classes.icon} ${classes.iconSuccess}`} fontSize=\"small\" />;\n          case 'error':\n            return <CloseIcon className={`${classes.icon} ${classes.iconError}`} fontSize=\"small\" />;\n          case 'warning':\n            return <PriorityHigh className={`${classes.icon} ${classes.iconWarning}`} fontSize=\"small\" />;\n          default:\n            return null;\n        }\n      })()}\n    </React.Fragment>\n  );\n};\n\nIcon.propTypes = {\n  classes: PropTypes.shape({}).isRequired,\n  type: PropTypes.oneOf(['success', 'error', 'warning']).isRequired,\n};\n\nclass CheckModal extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.api = props.api;\n    this.handleOpenChange = this.handleOpenChange.bind(this);\n    this.runCheck = this.runCheck.bind(this);\n    this.handleApiError = this.handleApiError.bind(this);\n\n    this.state = {\n      open: false,\n      running: false,\n      success: undefined,\n      results: {},\n      error: undefined,\n    };\n  }\n\n  handleOpenChange = () => {\n    this.setState(prevState => {\n      return {\n        open: !prevState.open,\n      };\n    });\n  };\n\n  runCheck = () => {\n    this.setState({\n      running: true,\n      open: true,\n    });\n\n    this.api.setCurrentRequests([this.api.fetchCheck()]);\n    Promise.all(this.api.getCurrentPromises())\n      .then(([response]) => {\n        this.setState({\n          running: false,\n          success: response.success,\n          results: response.results,\n          error: undefined,\n        });\n      })\n      .catch(this.handleApiError);\n  };\n\n  handleApiError = error => {\n    this.setState({\n      running: false,\n      error,\n    });\n  };\n\n  render() {\n    const { open, running, success, results, error } = this.state;\n    const { classes, fullScreen } = this.props;\n\n    return (\n      <React.Fragment>\n        <Grid\n          container\n          direction=\"row\"\n          justifyContent=\"center\"\n          alignItems=\"center\"\n          spacing={3}>\n          <Grid className={classes.wrapper} item>\n            <Button\n              variant=\"outlined\"\n              color=\"primary\"\n              disabled={running}\n              onClick={this.runCheck}>\n              <Trans>buttonRunLinkerdCheck</Trans>\n            </Button>\n          </Grid>\n        </Grid>\n\n        { error !== undefined && <ErrorBanner message={error} /> }\n\n        <Dialog\n          className={classes.dialog}\n          open={open}\n          scroll=\"paper\"\n          TransitionComponent={Transition}\n          fullScreen={fullScreen}\n          maxWidth=\"md\"\n          fullWidth\n          keepMounted\n          onClose={this.handleOpenChange}>\n          <DialogTitle>\n            <Grid\n              container\n              direction=\"row\"\n              justifyContent=\"space-between\"\n              alignItems=\"center\">\n              <Grid item>\n                Linkerd Check\n              </Grid>\n\n              {success !== undefined &&\n                <Grid item>\n                  <SimpleChip\n                    label={success ? <Trans>labelSuccess</Trans> : <Trans>labelError</Trans>}\n                    type={success ? 'good' : 'bad'} />\n                </Grid>\n              }\n            </Grid>\n          </DialogTitle>\n\n          <DialogContent className={classes.dialogContent}>\n            <Paper className={classes.contentWrapper} elevation={3}>\n              <div className={classes.content}>\n                { running ? (\n                  <CircularProgress size={80} className={classes.spinner} />\n                ) : (\n                  <React.Fragment>\n                    {Object.keys(results).map(title => {\n                      return (\n                        <Results\n                          key={title}\n                          title={title}\n                          results={results[title]}\n                          classes={classes} />\n                      );\n                    })}\n                  </React.Fragment>\n                )}\n              </div>\n            </Paper>\n          </DialogContent>\n\n          <DialogActions>\n            <Button onClick={this.runCheck} color=\"primary\">\n              <Trans>buttonReRunCheck</Trans>\n            </Button>\n\n            <Button onClick={this.handleOpenChange} color=\"primary\">\n              <Trans>buttonClose</Trans>\n            </Button>\n          </DialogActions>\n        </Dialog>\n      </React.Fragment>\n    );\n  }\n}\n\nCheckModal.propTypes = {\n  api: PropTypes.shape({\n    fetchCheck: PropTypes.func.isRequired,\n    getCurrentPromises: PropTypes.func.isRequired,\n    setCurrentRequests: PropTypes.func.isRequired,\n  }).isRequired,\n  fullScreen: PropTypes.bool.isRequired,\n  theme: PropTypes.shape({}).isRequired,\n};\n\nexport default withMobileDialog()(withStyles(styles, { withTheme: true })(CheckModal));\n"
  },
  {
    "path": "web/app/js/components/Community.jsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport _has from 'lodash/has';\nimport { withStyles } from '@material-ui/core/styles';\n\nconst styles = () => ({\n  iframe: {\n    border: '0px',\n    width: '100%',\n    overflow: 'hidden',\n  },\n});\n\nconst Community = function({ classes }) {\n  const [iframeHeight, setIframeHeight] = useState(0);\n  useEffect(() => {\n    // We add 5px to avoid cutting box shadow\n    const setFromIframeEvent = e => {\n      if (!_has(e.data, 'dashboardHeight')) {\n        return;\n      }\n      setIframeHeight(e.data.dashboardHeight + 5);\n    };\n    window.addEventListener('message', setFromIframeEvent);\n    return () => {\n      window.removeEventListener('message', setFromIframeEvent);\n    };\n  }, []);\n\n  return (\n    <iframe\n      title=\"Community\"\n      src=\"https://linkerd.io/dashboard/\"\n      scrolling=\"no\"\n      style={{ height: iframeHeight > 0 ? iframeHeight : '100vh' }}\n      className={classes.iframe} />\n  );\n};\n\nexport default withStyles(styles)(Community);\n"
  },
  {
    "path": "web/app/js/components/Community.test.jsx",
    "content": "import Community from './Community.jsx';\nimport { routerWrap } from '../../test/testHelpers.jsx';\nimport { mount } from 'enzyme';\n\ndescribe('Community', () => {\n  it('makes a iframe', () => {\n    let component = mount(routerWrap(Community));\n    expect(component.find(\"Community\")).toHaveLength(1);\n\n    const src = component.find(\"iframe\").props().src;\n    expect(src).toEqual(\"https://linkerd.io/dashboard/\");\n    \n    const title = component.find(\"iframe\").props().title;\n    expect(title).toEqual(\"Community\");\n  });\n});\n"
  },
  {
    "path": "web/app/js/components/ConfigureProfilesMsg.jsx",
    "content": "import Button from '@material-ui/core/Button';\nimport CardContent from '@material-ui/core/CardContent';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogContentText from '@material-ui/core/DialogContentText';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport FormControl from '@material-ui/core/FormControl';\nimport FormHelperText from '@material-ui/core/FormHelperText';\nimport IconButton from '@material-ui/core/IconButton';\nimport Input from '@material-ui/core/Input';\nimport InputLabel from '@material-ui/core/InputLabel';\nimport NoteAddIcon from '@material-ui/icons/NoteAdd';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Trans } from '@lingui/macro';\nimport Typography from '@material-ui/core/Typography';\nimport _isEmpty from 'lodash/isEmpty';\nimport { withStyles } from '@material-ui/core/styles';\nimport { withContext } from './util/AppContext.jsx';\n\nconst styles = theme => ({\n  button: {\n    margin: theme.spacing(1),\n  },\n  margin: {\n    marginRight: theme.spacing(1),\n  },\n  container: {\n    display: 'flex',\n    flexWrap: 'wrap',\n  },\n  textField: {\n    marginLeft: theme.spacing(1),\n    marginRight: theme.spacing(1),\n    marginTop: theme.spacing(2),\n    width: 200,\n  },\n  root: {\n    marginTop: theme.spacing(3),\n    marginBottom: theme.spacing(3),\n  },\n});\n\nconst dns1035ServiceFmt = '^[a-z]([-a-z0-9]*[a-z0-9])?$';\nconst dns1123NamespaceFmt = '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$';\nconst serviceNameRegexp = RegExp(dns1035ServiceFmt);\nconst namespaceNameRegexp = RegExp(dns1123NamespaceFmt);\n\nclass ConfigureProfilesMsg extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      open: false,\n      error: {\n        service: false,\n        namespace: false,\n      },\n      query: {\n        service: '',\n        namespace: '',\n      },\n    };\n  }\n\n  handleClickOpen = () => {\n    this.setState({ open: true });\n  };\n\n  handleClose = () => {\n    this.setState({\n      open: false,\n      error: {\n        service: false,\n        namespace: false,\n      },\n      query: {\n        service: '',\n        namespace: '',\n      },\n    });\n  };\n\n  handleChange = name => {\n    const state = this.state;\n\n    return e => {\n      state.query[name] = e.target.value;\n      state.error[name] = false;\n      this.setState(state);\n    };\n  };\n\n  validateFields = (type, name) => {\n    const { error } = this.state;\n\n    if (_isEmpty(name)) {\n      error[type] = false;\n    } else {\n      const match = type === 'service' ?\n        serviceNameRegexp.test(name) :\n        namespaceNameRegexp.test(name);\n\n      error[type] = !match;\n    }\n\n    this.setState({ error });\n  };\n\n  renderDownloadProfileForm = () => {\n    const { api, classes, showAsIcon } = this.props;\n    const { query, error, open, service, name } = this.state;\n\n    const downloadUrl = api.prefixedUrl(`/profiles/new?service=${query.service}&namespace=${query.namespace}`);\n    let button;\n\n    if (showAsIcon) {\n      button = (\n        <IconButton\n          onClick={this.handleClickOpen}\n          aria-label=\"Add\"\n          className={classes.margin}\n          variant=\"outlined\">\n          <NoteAddIcon fontSize=\"small\" />\n        </IconButton>\n      );\n    } else {\n      button = (\n        <Button\n          className={classes.button}\n          variant=\"outlined\"\n          color=\"primary\"\n          size=\"small\"\n          onClick={this.handleClickOpen}>\n          <Trans>buttonCreateServiceProfile</Trans>\n        </Button>\n      );\n    }\n\n    const disableDownloadButton = _isEmpty(query.service) || _isEmpty(query.namespace) ||\n      error.service || error.namespace;\n    const downloadButton = (\n      <Button\n        disabled={disableDownloadButton}\n        onClick={() => this.handleClose(downloadUrl)}\n        color=\"primary\">\n        <Trans>buttonDownload</Trans>\n      </Button>\n    );\n\n    return (\n      <React.Fragment>\n        {button}\n        <Dialog\n          open={open}\n          onClose={this.handleClose}\n          aria-labelledby=\"form-dialog-title\">\n          <DialogTitle id=\"form-dialog-title\">\n            <Trans>New service profile</Trans>\n          </DialogTitle>\n          <DialogContent>\n            <DialogContentText>\n              <Trans>formCreateServiceProfileHelpText</Trans>\n            </DialogContentText>\n            <FormControl\n              className={classes.textField}\n              onBlur={() => this.validateFields('service', query.service)}\n              error={error.service}>\n              <InputLabel htmlFor=\"component-error\">Service</InputLabel>\n              <Input\n                id=\"component-error\"\n                value={service}\n                onChange={this.handleChange('service')}\n                aria-describedby=\"component-error-text\" />\n              {error.service && (\n                <FormHelperText id=\"component-error-text\">\n                  <Trans>formServiceNameErrorText</Trans>\n                </FormHelperText>\n              )}\n            </FormControl>\n            <FormControl\n              className={classes.textField}\n              onBlur={() => this.validateFields('namespace', query.namespace)}\n              error={error.namespace}>\n              <InputLabel htmlFor=\"component-error\">Namespace</InputLabel>\n              <Input\n                id=\"component-error\"\n                value={name}\n                onChange={this.handleChange('namespace')}\n                aria-describedby=\"component-error-text\" />\n              {error.namespace && (\n                <FormHelperText id=\"component-error-text\">\n                  <Trans>formNamespaceErrorText</Trans>\n                </FormHelperText>\n              )}\n            </FormControl>\n          </DialogContent>\n          <DialogActions>\n            <Button onClick={this.handleClose} color=\"primary\">\n              <Trans>buttonCancel</Trans>\n            </Button>\n            {disableDownloadButton ?\n              downloadButton :\n              <a\n                href={downloadUrl}\n                style={{ textDecoration: 'none' }}>{downloadButton}\n              </a>\n            }\n          </DialogActions>\n        </Dialog>\n      </React.Fragment>\n    );\n  };\n\n  render() {\n    const { showAsIcon } = this.props;\n    if (showAsIcon) {\n      return this.renderDownloadProfileForm();\n    } else {\n      return (\n        <CardContent>\n          <Typography component=\"div\">\n            <Trans>formNoNamedRouteTrafficFound</Trans>\n            {this.renderDownloadProfileForm()}\n          </Typography>\n        </CardContent>\n      );\n    }\n  }\n}\n\nConfigureProfilesMsg.propTypes = {\n  api: PropTypes.shape({\n    prefixedUrl: PropTypes.func.isRequired,\n  }).isRequired,\n  showAsIcon: PropTypes.bool,\n};\n\nConfigureProfilesMsg.defaultProps = {\n  showAsIcon: false,\n};\n\nexport default withContext(withStyles(styles, { withTheme: true })(ConfigureProfilesMsg));\n"
  },
  {
    "path": "web/app/js/components/EdgesTable.jsx",
    "content": "import CheckCircleOutline from '@material-ui/icons/CheckCircleOutline';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport { Trans } from '@lingui/macro';\nimport WarningIcon from '@material-ui/icons/Warning';\nimport { withStyles } from '@material-ui/core/styles';\nimport { directionColumn } from './util/TapUtils.jsx';\nimport { processedEdgesPropType } from './util/EdgesUtils.jsx';\nimport BaseTable from './BaseTable.jsx';\n\nconst styles = theme => ({\n  secure: {\n    color: theme.status.dark.good,\n  },\n  warning: {\n    color: theme.status.dark.warning,\n  },\n});\n\nconst edgesColumnDefinitions = (PrefixedLink, namespace, type, classes) => {\n  return [\n    {\n      title: ' ',\n      dataIndex: 'direction',\n      render: d => directionColumn(d.direction),\n    },\n    {\n      title: <Trans>columnTitleNamespace</Trans>,\n      dataIndex: 'namespace',\n      isNumeric: false,\n      filter: d => d.namespace,\n      render: d => (\n        <PrefixedLink to={`/namespaces/${d.namespace}`}>\n          {d.namespace}\n        </PrefixedLink>\n      ),\n      sorter: d => d.namespace,\n    },\n    {\n      title: <Trans>columnTitleName</Trans>,\n      dataIndex: 'name',\n      isNumeric: false,\n      filter: d => d.name,\n      render: d => {\n        // check that the resource is a k8s resource with a name we can link to\n        if (namespace && type && d.name) {\n          return (\n            <PrefixedLink to={`/namespaces/${namespace}/${type}s/${d.name}`}>\n              {d.name}\n            </PrefixedLink>\n          );\n        } else {\n          return d.name;\n        }\n      },\n      sorter: d => d.name,\n    },\n    {\n      title: <Trans>columnTitleIdentity</Trans>,\n      dataIndex: 'identity',\n      isNumeric: false,\n      filter: d => d.identity,\n      render: d => d.identity !== '' ? `${d.identity.split('.')[0]}.${d.identity.split('.')[1]}` : null,\n      sorter: d => d.identity,\n    },\n    {\n      title: <Trans>columnTitleSecured</Trans>,\n      dataIndex: 'message',\n      isNumeric: true,\n      render: d => {\n        if (d.noIdentityMsg === '') {\n          return <CheckCircleOutline className={classes.secure} />;\n        } else {\n          return (\n            <Tooltip title={d.noIdentityMsg}>\n              <WarningIcon className={classes.warning} />\n            </Tooltip>\n          );\n        }\n      },\n    },\n  ];\n};\n\nconst generateEdgesTableTitle = edges => {\n  let title = <Trans>tableTitleEdgesEmpty</Trans>;\n  if (edges.length > 0) {\n    let identity = edges[0].direction === 'INBOUND' ? edges[0].serverId : edges[0].clientId;\n    if (identity) {\n      identity = `${identity.split('.')[0]}.${identity.split('.')[1]}`;\n      title = <Trans>tableTitleEdgesWithIdentity {identity}</Trans>;\n    }\n  }\n  return title;\n};\n\nconst EdgesTable = function({ edges, api, namespace, type, classes }) {\n  const edgesColumns = edgesColumnDefinitions(api.PrefixedLink, namespace, type, classes);\n  const edgesTableTitle = generateEdgesTableTitle(edges);\n\n  return (\n    <BaseTable\n      defaultOrderBy=\"name\"\n      enableFilter\n      tableRows={edges}\n      tableColumns={edgesColumns}\n      tableClassName=\"metric-table\"\n      title={edgesTableTitle}\n      padding=\"dense\" />\n  );\n};\n\nEdgesTable.propTypes = {\n  api: PropTypes.shape({\n    PrefixedLink: PropTypes.func.isRequired,\n  }).isRequired,\n  edges: PropTypes.arrayOf(processedEdgesPropType),\n  namespace: PropTypes.string.isRequired,\n  type: PropTypes.string.isRequired,\n};\n\nEdgesTable.defaultProps = {\n  edges: [],\n};\n\nexport default withStyles(styles)(EdgesTable);\n"
  },
  {
    "path": "web/app/js/components/EmptyCard.jsx",
    "content": "import Card from '@material-ui/core/Card';\nimport CardContent from '@material-ui/core/CardContent';\nimport React from 'react';\nimport { Trans } from '@lingui/macro';\nimport Typography from '@material-ui/core/Typography';\nimport { withStyles } from '@material-ui/core/styles';\n\nconst styles = () => ({\n  card: {\n    textAlign: 'center',\n    paddingTop: '8px',\n  },\n});\n\nconst EmptyCard = function({ classes }) {\n  return (\n    <Card className={classes.card} elevation={3}>\n      <CardContent>\n        <Typography>\n          <Trans>NoDataToDisplayMsg</Trans>\n        </Typography>\n      </CardContent>\n    </Card>\n  );\n};\n\nexport default withStyles(styles)(EmptyCard);\n"
  },
  {
    "path": "web/app/js/components/ErrorBanner.jsx",
    "content": "import CloseIcon from '@material-ui/icons/Close';\nimport IconButton from '@material-ui/core/IconButton';\nimport Linkify from 'react-linkify';\nimport React from 'react';\nimport Slide from '@material-ui/core/Slide';\nimport Snackbar from '@material-ui/core/Snackbar';\nimport WarningIcon from '@material-ui/icons/Warning';\nimport _isEmpty from 'lodash/isEmpty';\nimport classNames from 'classnames';\nimport { withStyles } from '@material-ui/core/styles';\nimport { apiErrorPropType } from './util/ApiHelpers.jsx';\n\nconst defaultMessage = 'An error has occurred.';\n\nconst styles = theme => ({\n  close: {\n    padding: theme.spacing(2),\n  },\n  error: {\n    backgroundColor: theme.palette.error.dark,\n  },\n  backgroundColor: theme.palette.error.dark,\n  iconVariant: {\n    opacity: 0.9,\n    marginRight: theme.spacing(1),\n  },\n  margin: {\n    margin: theme.spacing(1),\n  },\n  message: {\n    display: 'flex',\n    alignItems: 'center',\n  },\n});\n\nclass ErrorSnackbar extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      open: true,\n    };\n  }\n\n  handleClose = () => {\n    this.setState({ open: false });\n  };\n\n  render() {\n    const { open } = this.state;\n    const { message, classes } = this.props;\n    const { statusText, error, url, status } = message;\n\n    return (\n      <Snackbar\n        anchorOrigin={{\n          vertical: 'bottom',\n          horizontal: 'left',\n        }}\n        open={open}\n        autoHideDuration={6000}\n        onClose={this.handleClose}\n        TransitionComponent={props => <Slide direction=\"up\" {...props} />}\n        ContentProps={{\n          'aria-describedby': 'message-id',\n          className: classNames(classes.error, classes.margin),\n        }}\n        message={(\n          <div id=\"message-id\" className=\"errorMessage\">\n            <div className={classes.message}>\n              <WarningIcon className={classNames(classes.icon, classes.iconVariant)} />\n              { !status ? null : `${status} ` }{ _isEmpty(statusText) ? defaultMessage : statusText }\n            </div>\n            <Linkify>{ !error ? null : <div>{error}</div> }</Linkify>\n            { !url ? null : <div>{url}</div> }\n          </div>\n          )}\n        action={[\n          <IconButton\n            key=\"close\"\n            aria-label=\"Close\"\n            color=\"inherit\"\n            className={classes.close}\n            onClick={this.handleClose}>\n            <CloseIcon />\n          </IconButton>,\n        ]} />\n    );\n  }\n}\n\nErrorSnackbar.propTypes = {\n  message: apiErrorPropType,\n};\n\nErrorSnackbar.defaultProps = {\n  message: {\n    status: null,\n    statusText: defaultMessage,\n    url: '',\n    error: '',\n  },\n};\n\nexport default withStyles(styles)(ErrorSnackbar);\n"
  },
  {
    "path": "web/app/js/components/ErrorModal.jsx",
    "content": "import Button from '@material-ui/core/Button';\nimport CircularProgress from '@material-ui/core/CircularProgress';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport ErrorIcon from '@material-ui/icons/Error';\nimport Grid from '@material-ui/core/Grid';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Switch from '@material-ui/core/Switch';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport { Trans } from '@lingui/macro';\nimport Typography from '@material-ui/core/Typography';\nimport _each from 'lodash/each';\nimport _get from 'lodash/get';\nimport _isEmpty from 'lodash/isEmpty';\nimport _map from 'lodash/map';\nimport _reduce from 'lodash/reduce';\nimport _take from 'lodash/take';\nimport { friendlyTitle } from './util/Utils.js';\n\n// max characters we display for error messages before truncating them\nconst maxErrorLength = 500;\n\nclass ErrorModal extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      truncateErrors: true,\n      open: false,\n      scroll: 'paper',\n    };\n  }\n\n  handleClickOpen = () => {\n    this.setState({ open: true });\n  };\n\n  handleClose = () => {\n    this.setState({ open: false });\n  };\n\n  toggleTruncateErrors = () => {\n    const { truncateErrors } = this.state;\n    this.setState({\n      truncateErrors: !truncateErrors,\n    });\n  };\n\n  static processErrorData(errorsByPod) {\n    let shouldTruncate = false;\n\n    const byPodAndContainer = _map(errorsByPod, (podErrors, pod) => {\n      const byContainer = _reduce(podErrors.errors, (errors, err) => {\n        if (!_isEmpty(err.container)) {\n          const c = err.container;\n          if (_isEmpty(errors[c.container])) {\n            errors[c.container] = [];\n          }\n\n          const errMsg = c.message;\n          if (errMsg.length > maxErrorLength) {\n            shouldTruncate = true;\n            c.truncatedMessage = `${_take(errMsg, maxErrorLength).join('')}...`;\n          }\n          errors[c.container].push(c);\n        }\n        return errors;\n      }, {});\n\n      return {\n        pod,\n        byContainer,\n      };\n    });\n\n    return {\n      byPodAndContainer,\n      shouldTruncate,\n    };\n  }\n\n  renderContainerErrors = (pod, errorsByContainer) => {\n    const { truncateErrors } = this.state;\n\n    if (_isEmpty(errorsByContainer)) {\n      return 'No messages to display';\n    }\n\n    return _map(errorsByContainer, (errors, container) => (\n      <div key={`error-${container}`}>\n        <Grid\n          container\n          direction=\"row\"\n          justifyContent=\"space-between\"\n          alignItems=\"center\">\n          <Grid item>\n            <Typography variant=\"subtitle1\" gutterBottom>{container}</Typography>\n          </Grid>\n          <Grid item>\n            <Typography variant=\"subtitle1\" gutterBottom align=\"right\">\n              {_get(errors, [0, 'image'])}\n            </Typography>\n          </Grid>\n        </Grid>\n\n        <div>\n          {\n            _map(errors, (er, i) => {\n              if (_isEmpty(er.message)) {\n                return null;\n              }\n\n              const message = !truncateErrors ? er.message :\n                er.truncatedMessage || er.message;\n\n              return (\n                <React.Fragment key={`error-msg-long-${i}`}>\n                  <code>{!er.reason ? null : `${er.reason}: `} {message}</code><br /><br />\n                </React.Fragment>\n              );\n            })\n          }\n        </div>\n      </div>\n    ));\n  };\n\n  renderPodErrors = errors => {\n    return _map(errors, err => {\n      return (\n        <div key={err.pod}>\n          <Typography variant=\"h6\" gutterBottom>{err.pod}</Typography>\n          {this.renderContainerErrors(err.pod, err.byContainer)}\n        </div>\n      );\n    });\n  };\n\n  renderStatusIcon = errors => {\n    let showInit = true;\n\n    _each(errors.byPodAndContainer, container => {\n      _each(container.byContainer, con => {\n        if (con[0].reason !== 'PodInitializing') {\n          showInit = false;\n        }\n      });\n    });\n\n    if (showInit) {\n      return (\n        <Tooltip title={<Trans>podsAreInitializingMsg</Trans>}>\n          <CircularProgress size={20} thickness={4} />\n        </Tooltip>\n      );\n    } else {\n      return (\n        <ErrorIcon color=\"error\" fontSize=\"small\" onClick={this.handleClickOpen} />\n      );\n    }\n  };\n\n  render() {\n    const { open, scroll, truncateErrors } = this.state;\n    const { resourceType, resourceName, errors } = this.props;\n    const errorData = ErrorModal.processErrorData(errors);\n\n    return (\n      <React.Fragment>\n        {this.renderStatusIcon(errors)}\n        <Dialog\n          open={open}\n          onClose={this.handleClose}\n          scroll={scroll}\n          aria-labelledby=\"scroll-dialog-title\">\n\n          <DialogTitle id=\"scroll-dialog-title\">Errors in {friendlyTitle(resourceType).singular} {resourceName}</DialogTitle>\n\n          <DialogContent>\n            {\n              !errorData.shouldTruncate ? null :\n              <React.Fragment>\n                Some of these error messages are very long. Show full error text?\n                <Switch\n                  checked={!truncateErrors}\n                  onChange={this.toggleTruncateErrors}\n                  color=\"primary\" />\n              </React.Fragment>\n            }\n            {this.renderPodErrors(errorData.byPodAndContainer)}\n\n          </DialogContent>\n\n          <DialogActions>\n            <Button onClick={this.handleClose} color=\"primary\">Close</Button>\n          </DialogActions>\n        </Dialog>\n      </React.Fragment>\n    );\n  }\n}\n\nErrorModal.propTypes = {\n  errors: PropTypes.shape({}),\n  resourceName: PropTypes.string.isRequired,\n  resourceType: PropTypes.string.isRequired,\n};\n\nErrorModal.defaultProps = {\n  errors: {},\n};\n\nexport default ErrorModal;\n"
  },
  {
    "path": "web/app/js/components/ExpandableTable.jsx",
    "content": "import Button from '@material-ui/core/Button';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport ExpandMoreIcon from '@material-ui/icons/ExpandMore';\nimport IconButton from '@material-ui/core/IconButton';\nimport Paper from '@material-ui/core/Paper';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Table from '@material-ui/core/Table';\nimport TableBody from '@material-ui/core/TableBody';\nimport TableCell from '@material-ui/core/TableCell';\nimport TableHead from '@material-ui/core/TableHead';\nimport TableRow from '@material-ui/core/TableRow';\nimport { Trans } from '@lingui/macro';\nimport { withStyles } from '@material-ui/core/styles';\nimport EmptyCard from './EmptyCard.jsx';\n\nconst styles = theme => ({\n  root: {\n    width: '100%',\n    marginTop: theme.spacing(3),\n    overflowX: 'auto',\n  },\n  expandedWrap: {\n    wordBreak: 'break-word',\n    paddingTop: '10px',\n  },\n  table: {\n    minWidth: 700,\n  },\n  tableHeader: {\n    fontSize: '12px',\n    opacity: 0.6,\n    lineHeight: 1,\n  },\n  denseTable: {\n    paddingRight: '8px',\n    '&:last-child': {\n      paddingRight: '24px',\n    },\n  },\n});\n\nclass ExpandableTable extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      open: false,\n      datum: {},\n    };\n  }\n\n  handleDialogOpen = d => () => {\n    this.setState({ open: true, datum: d });\n  };\n\n  handleDialogClose = () => {\n    this.setState({ open: false, datum: {} });\n  };\n\n  render() {\n    const { datum, open } = this.state;\n    const { expandedRowRender, classes, tableRows, tableColumns, tableClassName } = this.props;\n    const columns = [{\n      title: ' ',\n      key: 'expansion',\n      render: d => {\n        return (\n          <IconButton onClick={this.handleDialogOpen(d)}><ExpandMoreIcon /></IconButton>\n        );\n      },\n    }].concat(tableColumns);\n\n    return (\n      <Paper className={classes.root} elevation={3}>\n        <Table\n          className={`${classes.table} ${tableClassName}`}>\n          <TableHead>\n            <TableRow>\n              {\n                columns.map(c => (\n                  <TableCell\n                    key={c.key}\n                    className={`${classes.tableHeader} ${classes.denseTable}`}\n                    align={c.isNumeric ? 'right' : 'left'}>{c.title}\n                  </TableCell>\n                ))\n              }\n            </TableRow>\n          </TableHead>\n          <TableBody>\n            { tableRows.length > 0 && (\n              <React.Fragment>\n                { tableRows.map(d => {\n                  return (\n                    <React.Fragment key={`frag-${d.key}`}>\n                      <TableRow\n                        key={d.key}\n                        onClick={this.handleClick}>\n                        {\n                            columns.map(c => (\n                              <TableCell\n                                key={`table-${d.key}-${c.key}`}\n                                className={classes.denseTable}\n                                align={c.isNumeric ? 'right' : 'left'}>\n                                {c.render(d)}\n                              </TableCell>\n                            ))\n                          }\n                      </TableRow>\n                    </React.Fragment>\n                  );\n                })}\n              </React.Fragment>\n            )}\n          </TableBody>\n        </Table>\n\n        { tableRows.length === 0 && (\n          <EmptyCard />\n        )}\n\n        <Dialog\n          maxWidth=\"md\"\n          fullWidth\n          open={open}\n          onClose={this.handleDialogClose}\n          aria-labelledby=\"form-dialog-title\">\n          <DialogTitle id=\"form-dialog-title\"><Trans>tableTitleRequestDetails</Trans></DialogTitle>\n          <DialogContent>\n            {expandedRowRender(datum, classes.expandedWrap)}\n          </DialogContent>\n          <DialogActions>\n            <Button onClick={this.handleDialogClose} color=\"primary\">Close</Button>\n          </DialogActions>\n        </Dialog>\n      </Paper>\n    );\n  }\n}\n\nExpandableTable.propTypes = {\n  expandedRowRender: PropTypes.func.isRequired,\n  tableClassName: PropTypes.string,\n  tableColumns: PropTypes.arrayOf(PropTypes.shape({\n    title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),\n    isNumeric: PropTypes.bool,\n    render: PropTypes.func,\n  })).isRequired,\n  tableRows: PropTypes.arrayOf(PropTypes.shape({})),\n};\n\nExpandableTable.defaultProps = {\n  tableClassName: '',\n  tableRows: [],\n};\n\nexport default withStyles(styles)(ExpandableTable);\n"
  },
  {
    "path": "web/app/js/components/Extensions.jsx",
    "content": "import 'whatwg-fetch';\n\nimport React from 'react';\nimport Button from '@material-ui/core/Button';\nimport Paper from '@material-ui/core/Paper';\nimport Grid from '@material-ui/core/Grid';\nimport { withStyles } from '@material-ui/core/styles';\nimport Typography from '@material-ui/core/Typography';\nimport { Trans } from '@lingui/macro';\nimport ErrorBanner from './ErrorBanner.jsx';\n\nconst styles = theme => ({\n  extensionCard: {\n    padding: theme.spacing(2),\n    maxWidth: '1200px',\n  },\n  screenshot: {\n    margin: 'auto',\n    display: 'block',\n    height: '100%',\n    width: '100%',\n    borderRadius: '8px',\n  },\n});\n\nclass Extensions extends React.Component {\n  constructor(props) {\n    super(props);\n    this.loadFromServer = this.loadFromServer.bind(this);\n    this.state = {\n      extensions: [],\n    };\n  }\n\n  componentDidMount() {\n    this.loadFromServer();\n  }\n\n  loadFromServer() {\n    fetch('https://linkerd.io/extensions/index.json', { cache: 'no-store' })\n      .then(res => res.json())\n      .then(json => {\n        this.setState({ extensions: json.data });\n      }).catch(err => this.handleApiError(err));\n  }\n\n  handleApiError = e => {\n    if (e.isCanceled) {\n      return;\n    }\n    this.setState({\n      error: e,\n    });\n  };\n\n  render() {\n    const { classes } = this.props;\n    const { extensions, error } = this.state;\n\n    if (error) {\n      return <ErrorBanner message={error} onHideMessage={() => this.setState({ error: null })} />;\n    }\n\n    return (\n      <Grid container direction=\"column\" spacing={3} justifyContent=\"center\" alignItems=\"center\">\n        <Grid item xs={12}>\n          <Typography variant=\"h6\">\n            <Trans>extensionsPageMsg</Trans>\n          </Typography>\n        </Grid>\n        {extensions.map(ext => (\n          <Grid key={ext.name} item xs={12}>\n            <Paper elevation={3} className={classes.extensionCard}>\n              <Grid container spacing={1} direction=\"row\" justifyContent=\"space-between\" alignItems=\"center\">\n                <Grid container spacing={3} item xs={8} direction=\"column\" justifyContent=\"center\">\n                  <Grid item><Typography variant=\"h5\">{ext.name}</Typography></Grid>\n                  <Grid item><Typography variant=\"body1\">{ext.description}</Typography></Grid>\n                  <Grid item>\n                    <Button\n                      color=\"primary\"\n                      href={ext.docLink}\n                      target=\"_blank\">\n                      <Trans>buttonLearnMore</Trans>\n                    </Button>\n                  </Grid>\n                </Grid>\n                <Grid item xs={4}>\n                  <img className={classes.screenshot} src={ext.screenshotURL} alt={`${ext.name} extension screenshot`} />\n                </Grid>\n              </Grid>\n            </Paper>\n          </Grid>\n        ))}\n      </Grid>\n    );\n  }\n}\n\nexport default withStyles(styles)(Extensions);\n"
  },
  {
    "path": "web/app/js/components/Gateway.jsx",
    "content": "import Card from '@material-ui/core/Card';\nimport CardContent from '@material-ui/core/CardContent';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Trans } from '@lingui/macro';\nimport Typography from '@material-ui/core/Typography';\nimport _isEmpty from 'lodash/isEmpty';\nimport Spinner from './util/Spinner.jsx';\nimport MetricsTable from './MetricsTable.jsx';\nimport ErrorBanner from './ErrorBanner.jsx';\nimport { handlePageVisibility, withPageVisibility } from './util/PageVisibility.jsx';\nimport { processGatewayResults } from './util/MetricUtils.jsx';\nimport { withContext } from './util/AppContext.jsx';\n\nconst multiclusterExtensionName = 'linkerd-multicluster';\n\nclass Gateways extends React.Component {\n  constructor(props) {\n    super(props);\n    this.api = props.api;\n    this.handleApiError = this.handleApiError.bind(this);\n    this.loadFromServer = this.loadFromServer.bind(this);\n    this.state = {\n      pollingInterval: 2000,\n      metrics: {},\n      multiclusterExists: false,\n      pendingRequests: false,\n      loaded: false,\n      error: null,\n    };\n  }\n\n  componentDidMount() {\n    this.startServerPolling();\n    this.checkMulticlusterExtension();\n  }\n\n  componentDidUpdate(prevProps) {\n    const { isPageVisible } = this.props;\n\n    handlePageVisibility({\n      prevVisibilityState: prevProps.isPageVisible,\n      currentVisibilityState: isPageVisible,\n      onVisible: () => this.startServerPolling(),\n      onHidden: () => this.stopServerPolling(),\n    });\n  }\n\n  componentWillUnmount() {\n    this.stopServerPolling();\n  }\n\n  startServerPolling() {\n    const { pollingInterval } = this.state;\n\n    this.loadFromServer();\n    this.timerId = window.setInterval(this.loadFromServer, pollingInterval);\n  }\n\n  stopServerPolling() {\n    window.clearInterval(this.timerId);\n    this.api.cancelCurrentRequests();\n    this.setState({ pendingRequests: false });\n  }\n\n  loadFromServer() {\n    const { pendingRequests } = this.state;\n    if (pendingRequests) {\n      return; // don't make more requests if the ones we sent haven't completed\n    }\n    this.setState({ pendingRequests: true });\n\n    this.api.setCurrentRequests([this.api.fetchGateways()]);\n\n    Promise.all(this.api.getCurrentPromises())\n      .then(([gatewayMetrics]) => {\n        const metrics = processGatewayResults(gatewayMetrics);\n        this.setState({\n          metrics,\n          loaded: true,\n          pendingRequests: false,\n          error: null,\n        });\n      })\n      .catch(this.handleApiError);\n  }\n\n  checkMulticlusterExtension() {\n    this.api.setCurrentRequests([this.api.fetchExtension(multiclusterExtensionName)]);\n\n    Promise.all(this.api.getCurrentPromises())\n      .then(([extension]) => {\n        this.setState({ multiclusterExists: !_isEmpty(extension) });\n      })\n      .catch(this.handleApiError);\n  }\n\n  handleApiError = e => {\n    if (e.isCanceled) {\n      return;\n    }\n\n    this.setState({\n      pendingRequests: false,\n      error: e,\n    });\n  };\n\n  render() {\n    const { metrics, loaded, multiclusterExists, error } = this.state;\n    const noMetrics = _isEmpty(metrics);\n    return (\n      <div className=\"page-content\">\n        {!error ? null : <ErrorBanner message={error} />}\n        {!loaded ? (\n          <Spinner />\n        ) : (\n          <div>\n            {noMetrics && multiclusterExists ? <div><Trans>noResourcesDetectedMsg</Trans></div> : null}\n            {(noMetrics && !multiclusterExists) &&\n            <Card>\n              <CardContent>\n                <Typography><Trans>installMulticlusterMsg</Trans></Typography>\n                <br />\n                <code>linkerd multicluster install | kubectl apply -f -</code>\n              </CardContent>\n            </Card>}\n            {noMetrics ? null : (\n              <div className=\"page-section\">\n                <MetricsTable\n                  title={<Trans>tableTitleGateways</Trans>}\n                  resource=\"gateway\"\n                  metrics={metrics} />\n              </div>\n            )}\n          </div>\n        )}\n      </div>\n    );\n  }\n}\n\nGateways.propTypes = {\n  api: PropTypes.shape({\n    cancelCurrentRequests: PropTypes.func.isRequired,\n    fetchGateways: PropTypes.func.isRequired,\n    getCurrentPromises: PropTypes.func.isRequired,\n    setCurrentRequests: PropTypes.func.isRequired,\n  }).isRequired,\n  isPageVisible: PropTypes.bool.isRequired,\n};\n\nexport default withPageVisibility(withContext(Gateways));\n"
  },
  {
    "path": "web/app/js/components/GrafanaLink.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\nimport _isEmpty from 'lodash/isEmpty';\nimport { grafanaIcon } from './util/SvgWrappers.jsx';\n\nconst GrafanaLink = function({ PrefixedLink, name, namespace, resource, grafanaExternalUrl, grafanaPrefix }) {\n  let link = '/grafana/d/';\n\n  if (grafanaExternalUrl !== '') {\n    let baseUrl = grafanaExternalUrl;\n    // strip trailing slash if present, to avoid double slash in final URL\n    if (grafanaExternalUrl.charAt(grafanaExternalUrl.length - 1) === '/') {\n      baseUrl = grafanaExternalUrl.slice(0, grafanaExternalUrl.length - 1);\n    }\n\n    // grafanaPrefix is used for externally hosted Grafana instances, which do not use the /grafana proxy.\n    // When dashboards for multiple Linkerd instances are deployed to the same Grafana host,\n    // the dashboard UID needs a user-specified prefix to be unique.\n    link = `${baseUrl}/d/${grafanaPrefix}`;\n  }\n\n  link += `linkerd-${resource}?var-${resource}=${name}`;\n\n  if (!_isEmpty(namespace)) {\n    link += `&var-namespace=${namespace}`;\n  }\n\n  if (grafanaExternalUrl !== '') {\n    return (\n      // <a> instead of <PrefixedLink> because <Link> doesn't work with external URL's\n      <a\n        href={link}\n        rel=\"noreferrer\"\n        target=\"_blank\">\n        &nbsp;&nbsp;\n        {grafanaIcon}\n      </a>\n    );\n  } else {\n    return (\n      <PrefixedLink\n        to={link}\n        targetBlank>\n        &nbsp;&nbsp;\n        {grafanaIcon}\n      </PrefixedLink>\n    );\n  }\n};\n\nGrafanaLink.propTypes = {\n  name: PropTypes.string.isRequired,\n  namespace: PropTypes.string,\n  PrefixedLink: PropTypes.func.isRequired,\n  resource: PropTypes.string.isRequired,\n  grafanaExternalUrl: PropTypes.string,\n  grafanaPrefix: PropTypes.string,\n};\n\nGrafanaLink.defaultProps = {\n  namespace: '',\n  grafanaExternalUrl: '',\n  grafanaPrefix: '',\n};\n\nexport default GrafanaLink;\n"
  },
  {
    "path": "web/app/js/components/GrafanaLink.test.jsx",
    "content": "import ApiHelpers from './util/ApiHelpers.jsx';\nimport GrafanaLink from './GrafanaLink.jsx';\nimport { routerWrap } from '../../test/testHelpers.jsx';\nimport { mount } from 'enzyme';\n\ndescribe('GrafanaLink', () => {\n  it('makes a link', () => {\n    let api = ApiHelpers('');\n    let linkProps = {\n      resource: \"replicationcontroller\",\n      name: \"aldksf-3409823049823\",\n      namespace: \"myns\",\n      PrefixedLink: api.PrefixedLink\n    };\n    let component = mount(routerWrap(GrafanaLink, linkProps));\n\n    let expectedDashboardNameStr = \"/linkerd-replicationcontroller\";\n    let expectedNsStr = \"var-namespace=myns\";\n    let expectedVarNameStr = \"var-replicationcontroller=aldksf-3409823049823\";\n\n    expect(component.find(\"GrafanaLink\")).toHaveLength(1);\n\n    const href = component.find('a').props().href;\n\n    expect(href).toContain(expectedDashboardNameStr);\n    expect(href).toContain(expectedNsStr);\n    expect(href).toContain(expectedVarNameStr);\n  });\n\n  it('makes a link without a namespace', () => {\n    let api = ApiHelpers('');\n    let linkProps = {\n      resource: \"replicationcontroller\",\n      name: \"aldksf-3409823049823\",\n      PrefixedLink: api.PrefixedLink\n    };\n    let component = mount(routerWrap(GrafanaLink, linkProps));\n\n    let expectedDashboardNameStr = \"/linkerd-replicationcontroller\";\n    let expectedVarNameStr = \"var-replicationcontroller=aldksf-3409823049823\";\n\n    expect(component.find(\"GrafanaLink\")).toHaveLength(1);\n\n    const href = component.find('a').props().href;\n\n    expect(href).toContain(expectedDashboardNameStr);\n    expect(href).not.toContain(\"namespace\");\n    expect(href).toContain(expectedVarNameStr);\n  });\n\n  it('Use grafana.externalUrl', () => {\n    let grafanaPrefix = \"test-prefix\";\n    let grafanaExternalUrl = \"https://example.com\";\n    let api = ApiHelpers('');\n    let linkProps = {\n      resource: \"replicationcontroller\",\n      name: \"aldksf-3409823049823\",\n      namespace: \"myns\",\n      grafanaPrefix: grafanaPrefix,\n      grafanaExternalUrl: grafanaExternalUrl,\n    };\n    let component = mount(routerWrap(GrafanaLink, linkProps));\n\n    expect(component.find(\"GrafanaLink\")).toHaveLength(1);\n\n    const href = component.find('a').props().href;\n\n    expect(href).toContain(grafanaPrefix);\n    expect(href).toContain(grafanaExternalUrl);\n  });\n});\n"
  },
  {
    "path": "web/app/js/components/JaegerLink.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\nimport _isEmpty from 'lodash/isEmpty';\nimport { jaegerIcon } from './util/SvgWrappers.jsx';\n\nfunction jaegerQuery(name, namespace, resource) {\n  if (_isEmpty(namespace)) {\n    return `{\"linkerd.io/workload-ns\"%3A\"${name}\"}`;\n  } else if (resource === 'pod') {\n    return `{\"host.name\"%3A\"${name}\"%2C\"linkerd.io/workload-ns\"%3A\"${namespace}\"}`;\n  } else {\n    return `{\"linkerd.io%2Fproxy-${resource}\"%3A\"${name}\"%2C\"linkerd.io/workload-ns\"%3A\"${namespace}\"}`;\n  }\n}\n\nconst JaegerLink = function({ PrefixedLink, name, namespace, resource }) {\n  const link = `/jaeger/search?service=linkerd-proxy&tags=${jaegerQuery(name, namespace, resource)}`;\n\n  return (\n    <PrefixedLink\n      to={link}\n      targetBlank>\n      &nbsp;&nbsp;\n      {jaegerIcon}\n    </PrefixedLink>\n  );\n};\n\nJaegerLink.propTypes = {\n  name: PropTypes.string.isRequired,\n  namespace: PropTypes.string,\n  PrefixedLink: PropTypes.func.isRequired,\n  resource: PropTypes.string.isRequired,\n};\n\nJaegerLink.defaultProps = {\n  namespace: '',\n};\n\nexport default JaegerLink;\n"
  },
  {
    "path": "web/app/js/components/MeshedStatusTable.jsx",
    "content": "import Grid from '@material-ui/core/Grid';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport { Trans } from '@lingui/macro';\nimport _isEmpty from 'lodash/isEmpty';\nimport { StyledProgress } from './util/Progress.jsx';\nimport ErrorModal from './ErrorModal.jsx';\nimport BaseTable from './BaseTable.jsx';\nimport { withContext } from './util/AppContext.jsx';\n\nconst getClassification = (meshedPodCount, failedPodCount) => {\n  if (failedPodCount > 0) {\n    return 'poor';\n  } else if (meshedPodCount === 0) {\n    return 'default';\n  } else {\n    return 'good';\n  }\n};\n\nconst namespacesColumns = PrefixedLink => [\n  {\n    title: <Trans>columnTitleNamespace</Trans>,\n    dataIndex: 'namespace',\n    sorter: d => d.namespace,\n    render: d => {\n      return (\n        <Grid container alignItems=\"center\" spacing={1}>\n          <Grid item><PrefixedLink to={`/namespaces/${d.namespace}`}>{d.namespace}</PrefixedLink></Grid>\n          { _isEmpty(d.errors) ? null :\n          <Grid item><ErrorModal errors={d.errors} resourceName={d.namespace} resourceType=\"namespace\" /></Grid>\n          }\n        </Grid>\n      );\n    },\n  },\n  {\n    title: <Trans>columnTitleMeshedPods</Trans>,\n    dataIndex: 'meshedPodsStr',\n    isNumeric: true,\n  },\n  {\n    title: <Trans>columnTitleMeshedStatus</Trans>,\n    key: 'meshification',\n    render: row => {\n      const percent = row.meshedPercent.get();\n      const barType = _isEmpty(row.errors) ?\n        getClassification(row.meshedPods, row.failedPods) : 'warning';\n      const Progress = StyledProgress(barType);\n\n      let percentMeshedMsg = '';\n      if (row.meshedPercent.get() >= 0) {\n        percentMeshedMsg = `(${row.meshedPercent.prettyRate()})`;\n      }\n      return (\n        <Tooltip\n          title={(\n            <div>\n              <div>\n                {`${row.meshedPods} out of ${row.totalPods} running or pending pods are in the mesh ${percentMeshedMsg}`}\n              </div>\n              {row.failedPods === 0 ? null : <div>{ `${row.failedPods} failed pods` }</div>}\n            </div>\n            )}>\n          <Progress variant=\"determinate\" value={Math.round(percent * 100)} />\n        </Tooltip>\n      );\n    },\n  },\n];\n\nconst MeshedStatusTable = function({ api, tableRows }) {\n  return (\n    <BaseTable\n      tableClassName=\"metric-table mesh-completion-table\"\n      tableRows={tableRows}\n      tableColumns={namespacesColumns(api.PrefixedLink)}\n      defaultOrderBy=\"namespace\"\n      rowKey={d => d.namespace} />\n  );\n};\n\nMeshedStatusTable.propTypes = {\n  api: PropTypes.shape({\n    PrefixedLink: PropTypes.func.isRequired,\n  }).isRequired,\n  tableRows: PropTypes.arrayOf(PropTypes.shape({})),\n};\n\nMeshedStatusTable.defaultProps = {\n  tableRows: [],\n};\n\nexport default withContext(MeshedStatusTable);\n"
  },
  {
    "path": "web/app/js/components/MetricsTable.jsx",
    "content": "import Grid from '@material-ui/core/Grid';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Trans } from '@lingui/macro';\nimport _cloneDeep from 'lodash/cloneDeep';\nimport _each from 'lodash/each';\nimport _some from 'lodash/some';\nimport _get from 'lodash/get';\nimport _isEmpty from 'lodash/isEmpty';\nimport SuccessRateMiniChart from './util/SuccessRateMiniChart.jsx';\nimport JaegerLink from './JaegerLink.jsx';\nimport GrafanaLink from './GrafanaLink.jsx';\nimport ErrorModal from './ErrorModal.jsx';\nimport BaseTable from './BaseTable.jsx';\nimport { displayName, friendlyTitle, metricToFormatter } from './util/Utils.js';\nimport { processedMetricsPropType } from './util/MetricUtils.jsx';\nimport { withContext } from './util/AppContext.jsx';\n\nconst tcpStatColumns = [\n  {\n    title: <Trans>columnTitleOpenConnections</Trans>,\n    dataIndex: 'tcp.openConnections',\n    isNumeric: true,\n    render: d => metricToFormatter.NO_UNIT(d.tcp.openConnections),\n    sorter: d => d.tcp.openConnections,\n  },\n  {\n    title: <Trans>columnTitleReadRate</Trans>,\n    dataIndex: 'tcp.readRate',\n    isNumeric: true,\n    render: d => metricToFormatter.BYTES(d.tcp.readRate),\n    sorter: d => d.tcp.readRate,\n  },\n  {\n    title: <Trans>columnTitleWriteRate</Trans>,\n    dataIndex: 'tcp.writeRate',\n    isNumeric: true,\n    render: d => metricToFormatter.BYTES(d.tcp.writeRate),\n    sorter: d => d.tcp.writeRate,\n  },\n];\n\nconst httpStatColumns = [\n  {\n    title: <Trans>columnTitleSuccessRate</Trans>,\n    dataIndex: 'successRate',\n    isNumeric: true,\n    render: d => <SuccessRateMiniChart sr={d.successRate} />,\n    sorter: d => d.successRate,\n  },\n  {\n    title: <Trans>columnTitleRPS</Trans>,\n    dataIndex: 'requestRate',\n    isNumeric: true,\n    render: d => metricToFormatter.NO_UNIT(d.requestRate),\n    sorter: d => d.requestRate,\n  },\n  {\n    title: <Trans>columnTitleP50Latency</Trans>,\n    dataIndex: 'P50',\n    isNumeric: true,\n    render: d => metricToFormatter.LATENCY(d.P50),\n    sorter: d => d.P50,\n  },\n  {\n    title: <Trans>columnTitleP95Latency</Trans>,\n    dataIndex: 'P95',\n    isNumeric: true,\n    render: d => metricToFormatter.LATENCY(d.P95),\n    sorter: d => d.P95,\n  },\n  {\n    title: <Trans>columnTitleP99Latency</Trans>,\n    dataIndex: 'P99',\n    isNumeric: true,\n    render: d => metricToFormatter.LATENCY(d.P99),\n    sorter: d => d.P99,\n  },\n\n];\n\nconst trafficSplitWeightColumn = {\n  title: <Trans>columnTitleWeight</Trans>,\n  dataIndex: 'weight',\n  isNumeric: true,\n  filter: d => !d.tsStats ? null : d.tsStats.weight,\n  render: d => !d.tsStats ? null : d.tsStats.weight,\n  sorter: d => {\n    if (!d.tsStats) { return -1; }\n    if (parseInt(d.tsStats.weight, 10)) {\n      return parseInt(d.tsStats.weight, 10);\n    } else {\n      return d.tsStats.weight;\n    }\n  },\n};\n\nconst serviceDetailsColumns = PrefixedLink => [\n  {\n    title: <Trans>columnTitleDestination</Trans>,\n    dataIndex: 'DST',\n    isNumeric: false,\n    filter: d => !d.tsStats ? null : d.tsStats.leaf,\n    render: d => {\n      if (!d.tsStats) {\n        return null;\n      }\n      const nameContents = (\n        <PrefixedLink to={`/namespaces/${d.namespace}/services/${d.tsStats.leaf}`}>\n          {d.tsStats.leaf}\n        </PrefixedLink>\n      );\n      return (\n        <Grid container alignItems=\"center\" spacing={1}>\n          <Grid item>{nameContents}</Grid>\n          {_isEmpty(d.errors) ? null : <Grid item><ErrorModal errors={d.errors} resourceName={d.name} resourceType={d.type} /></Grid>}\n        </Grid>\n      );\n    },\n    sorter: d => !d.tsStats ? null : d.tsStats.leaf,\n  },\n  trafficSplitWeightColumn,\n];\n\nconst gatewayColumns = [\n  {\n    title: <Trans>columnTitleClusterName</Trans>,\n    dataIndex: 'clusterName',\n    isNumeric: false,\n    render: d => !d.clusterName ? '---' : d.clusterName,\n    sorter: d => !d.clusterName ? '---' : d.clusterName,\n  },\n  {\n    title: <Trans>columnTitleAlive</Trans>,\n    dataIndex: 'alive',\n    isNumeric: false,\n    render: d => !d.alive ? 'FALSE' : 'TRUE',\n    sorter: d => d.alive,\n  },\n  {\n    title: <Trans>columnTitlePairedServices</Trans>,\n    dataIndex: 'pairedServices',\n    isNumeric: false,\n    render: d => d.pairedServices,\n    sorter: d => d.pairedServices,\n  },\n  {\n    title: <Trans>columnTitleP50Latency</Trans>,\n    dataIndex: 'P50',\n    isNumeric: true,\n    render: d => metricToFormatter.LATENCY(d.P50),\n    sorter: d => d.P50,\n  },\n  {\n    title: <Trans>columnTitleP95Latency</Trans>,\n    dataIndex: 'P95',\n    isNumeric: true,\n    render: d => metricToFormatter.LATENCY(d.P95),\n    sorter: d => d.P95,\n  },\n  {\n    title: <Trans>columnTitleP99Latency</Trans>,\n    dataIndex: 'P99',\n    isNumeric: true,\n    render: d => metricToFormatter.LATENCY(d.P99),\n    sorter: d => d.P99,\n  },\n];\n\nconst columnDefinitions = (resource, showNamespaceColumn, PrefixedLink, isTcpTable, hasTsStats, grafana, grafanaExternalUrl, grafanaPrefix, jaeger) => {\n  const isAuthorityTable = resource === 'authority';\n  const isServicesTable = resource === 'service';\n  const isMultiResourceTable = resource === 'multi_resource';\n  const isGatewayTable = resource === 'gateway';\n  const getResourceDisplayName = isMultiResourceTable ? displayName : d => d.name;\n\n  const nsColumn = [\n    {\n      title: <Trans>columnTitleNamespace</Trans>,\n      dataIndex: 'namespace',\n      filter: d => d.namespace,\n      isNumeric: false,\n      render: d => !d.namespace ? '---' : <PrefixedLink to={`/namespaces/${d.namespace}`}>{d.namespace}</PrefixedLink>,\n      sorter: d => !d.namespace ? '---' : d.namespace,\n    },\n  ];\n\n  const meshedColumn = {\n    title: <Trans>columnTitleMeshed</Trans>,\n    dataIndex: 'pods.totalPods',\n    isNumeric: true,\n    render: d => !d.pods ? null : `${d.pods.meshedPods}/${d.pods.totalPods}`,\n    sorter: d => !d.pods ? -1 : d.pods.totalPods,\n  };\n\n  const grafanaColumn = {\n    title: <Trans>columnTitleGrafana</Trans>,\n    key: 'grafanaDashboard',\n    isNumeric: true,\n    render: row => {\n      if (!isAuthorityTable && !isServicesTable && (!row.added || _get(row, 'pods.totalPods') === '0')) {\n        return null;\n      }\n\n      return (\n        <GrafanaLink\n          name={row.name}\n          namespace={row.namespace}\n          resource={row.type}\n          grafanaExternalUrl={grafanaExternalUrl}\n          grafanaPrefix={grafanaPrefix}\n          PrefixedLink={PrefixedLink} />\n      );\n    },\n  };\n\n  const jaegerColumn = {\n    title: <Trans>columnTitleJaeger</Trans>,\n    key: 'JaegerDashboard',\n    isNumeric: true,\n    render: row => {\n      if (!isAuthorityTable && (!row.added || _get(row, 'pods.totalPods') === '0')) {\n        return null;\n      }\n\n      return (\n        <JaegerLink\n          name={row.name}\n          namespace={row.namespace}\n          resource={row.type}\n          PrefixedLink={PrefixedLink} />\n      );\n    },\n  };\n\n  const nameColumn = {\n    title: isMultiResourceTable ? 'Resource' : friendlyTitle(resource).singular,\n    dataIndex: 'name',\n    isNumeric: false,\n    filter: d => d.name,\n    render: d => {\n      let nameContents;\n      if (resource === 'namespace') {\n        nameContents = <PrefixedLink to={`/namespaces/${d.name}`}>{d.name}</PrefixedLink>;\n      } else if (!d.added && !isServicesTable) {\n        nameContents = getResourceDisplayName(d);\n      } else {\n        nameContents = (\n          <PrefixedLink to={`/namespaces/${d.namespace}/${d.type}s/${d.name}`}>\n            {getResourceDisplayName(d)}\n          </PrefixedLink>\n        );\n      }\n      return (\n        <Grid container alignItems=\"center\" spacing={1}>\n          <Grid item>{nameContents}</Grid>\n          {_isEmpty(d.errors) ? null :\n          <Grid item><ErrorModal errors={d.errors} resourceName={d.name} resourceType={d.type} /></Grid>}\n        </Grid>\n      );\n    },\n    sorter: d => getResourceDisplayName(d) || -1,\n  };\n\n  let columns = [nameColumn];\n  if (isServicesTable && hasTsStats) {\n    columns = columns.concat(serviceDetailsColumns(PrefixedLink));\n  }\n  if (isTcpTable) {\n    columns = columns.concat(tcpStatColumns);\n  } else if (isGatewayTable) {\n    columns = columns.concat(gatewayColumns);\n  } else {\n    columns = columns.concat(httpStatColumns);\n  }\n\n  if (!isAuthorityTable && !isGatewayTable && !isServicesTable) {\n    columns.splice(1, 0, meshedColumn);\n  }\n\n  if (grafana !== '' || grafanaExternalUrl !== '') {\n    columns = columns.concat(grafanaColumn);\n  }\n  if (jaeger !== '') {\n    columns = columns.concat(jaegerColumn);\n  }\n\n  if (!showNamespaceColumn) {\n    return columns;\n  } else {\n    return nsColumn.concat(columns);\n  }\n};\n\nconst preprocessMetrics = metrics => {\n  const tableData = _cloneDeep(metrics);\n\n  _each(tableData, datum => {\n    _each(datum.latency, (value, quantile) => {\n      datum[quantile] = value;\n    });\n  });\n\n  return tableData;\n};\n\nconst MetricsTable = function({ metrics, resource, showNamespaceColumn, title, api, isTcpTable, selectedNamespace, grafana, grafanaExternalUrl, grafanaPrefix, jaeger }) {\n  const showNsColumn = resource === 'namespace' || selectedNamespace !== '_all' ? false : showNamespaceColumn;\n  const hasTsStats = _some(metrics, m => m.tsStats);\n  const columns = columnDefinitions(resource, showNsColumn, api.PrefixedLink, isTcpTable, hasTsStats, grafana, grafanaExternalUrl, grafanaPrefix, jaeger);\n  const rows = preprocessMetrics(metrics);\n  return (\n    <BaseTable\n      enableFilter\n      tableRows={rows}\n      tableColumns={columns}\n      tableClassName=\"metric-table\"\n      title={title}\n      defaultOrderBy=\"name\"\n      padding=\"dense\" />\n  );\n};\n\nMetricsTable.propTypes = {\n  api: PropTypes.shape({\n    PrefixedLink: PropTypes.func.isRequired,\n  }).isRequired,\n  isTcpTable: PropTypes.bool,\n  metrics: PropTypes.arrayOf(processedMetricsPropType),\n  resource: PropTypes.string.isRequired,\n  selectedNamespace: PropTypes.string.isRequired,\n  showNamespaceColumn: PropTypes.bool,\n  title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),\n  grafana: PropTypes.string,\n  grafanaExternalUrl: PropTypes.string,\n  grafanaPrefix: PropTypes.string,\n  jaeger: PropTypes.string,\n};\n\nMetricsTable.defaultProps = {\n  showNamespaceColumn: true,\n  title: '',\n  grafana: '',\n  grafanaExternalUrl: '',\n  grafanaPrefix: '',\n  jaeger: '',\n  isTcpTable: false,\n  metrics: [],\n};\n\nexport default withContext(MetricsTable);\n"
  },
  {
    "path": "web/app/js/components/MetricsTable.test.js",
    "content": "import _merge from 'lodash/merge';\nimport ApiHelpers from './util/ApiHelpers.jsx';\nimport MetricsTable from './MetricsTable.jsx';\nimport { i18nAndRouterWrap } from '../../test/testHelpers.jsx';\nimport { mount } from 'enzyme';\n\ndescribe('Tests for <MetricsTable>', () => {\n  const defaultProps = {\n    api: ApiHelpers(''),\n    selectedNamespace: \"default\",\n  };\n\n  it('renders the table with all columns', () => {\n    let extraProps = _merge({}, defaultProps, {\n      metrics: [{\n        name: 'web',\n        namespace: 'default',\n        key: 'web-default-deploy',\n        totalRequests: 0,\n      }],\n      resource: \"deployment\"\n    });\n    const component = mount(i18nAndRouterWrap(MetricsTable, extraProps));\n\n    const table = component.find(\"BaseTable\");\n\n    expect(table).toBeDefined();\n    expect(table.props().tableRows).toHaveLength(1);\n    expect(table.props().tableColumns).toHaveLength(7);\n  });\n\n  it('if enableFilter is true, user can filter rows by search term', () => {\n    let extraProps = _merge({}, defaultProps, {\n      metrics: [{\n        name: 'authors',\n        namespace: 'default',\n        key: 'authors-default-deploy',\n        totalRequests: 0,\n      }, {\n        name: 'books',\n        namespace: 'default',\n        key: 'books-default-deploy',\n        totalRequests: 0,\n      }],\n      resource: 'deployment',\n      enableFilter: true\n    });\n    const component = mount(i18nAndRouterWrap(MetricsTable, extraProps));\n\n    const table = component.find('BaseTable');\n\n    const enableFilter = table.prop('enableFilter');\n\n    expect(enableFilter).toEqual(true);\n    expect(table.html()).toContain('books');\n    expect(table.html()).toContain('authors');\n    table.instance().setState({ showFilter: true, filterBy: /authors/ });\n    component.update();\n    expect(table.html()).not.toContain('books');\n    expect(table.html()).toContain('authors');\n  });\n\n  it('omits the namespace column for the namespace resource', () => {\n    let extraProps = _merge({}, defaultProps, { metrics: [], resource: \"namespace\"});\n    const component = mount(i18nAndRouterWrap(MetricsTable, extraProps));\n\n    const table = component.find(\"BaseTable\");\n\n    expect(table).toBeDefined();\n    expect(table.props().tableColumns).toHaveLength(7);\n  });\n\n  it('omits the namespace column when showNamespaceColumn is false', () => {\n    let extraProps = _merge({}, defaultProps, {\n      metrics: [],\n      resource: \"deployment\",\n      showNamespaceColumn: false\n    });\n    const component = mount(i18nAndRouterWrap(MetricsTable, extraProps));\n\n    const table = component.find(\"BaseTable\");\n\n    expect(table).toBeDefined();\n    expect(table.props().tableColumns).toHaveLength(7);\n  });\n\n  it('render table columns including jaeger', () => {\n    let extraProps = _merge({}, defaultProps, {\n      metrics: [],\n      resource: \"deployment\",\n      showNamespaceColumn: false,\n      jaeger: 'jaeger.xyz'\n    });\n    const component = mount(i18nAndRouterWrap(MetricsTable, extraProps));\n    const table = component.find(\"BaseTable\");\n\n    expect(table).toBeDefined();\n    expect(table.props().tableColumns).toHaveLength(8);\n  });\n\n  it('render table columns including grafana', () => {\n    let extraProps = _merge({}, defaultProps, {\n      metrics: [],\n      resource: \"deployment\",\n      showNamespaceColumn: false,\n      grafana: 'grafana.xyz'\n    });\n    const component = mount(i18nAndRouterWrap(MetricsTable, extraProps));\n    const table = component.find(\"BaseTable\");\n\n    expect(table).toBeDefined();\n    expect(table.props().tableColumns).toHaveLength(8);\n  });\n\n  it('render all table columns', () => {\n    let extraProps = _merge({}, defaultProps, {\n      metrics: [],\n      resource: \"deployment\",\n      showNamespaceColumn: false,\n      jaeger: 'jaeger.xyz',\n      grafana: 'grafana.xyz'\n    });\n    const component = mount(i18nAndRouterWrap(MetricsTable, extraProps));\n    const table = component.find(\"BaseTable\");\n\n    expect(table).toBeDefined();\n    expect(table.props().tableColumns).toHaveLength(9);\n  });\n});\n"
  },
  {
    "path": "web/app/js/components/Namespace.jsx",
    "content": "import 'whatwg-fetch';\n\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Trans } from '@lingui/macro';\nimport _filter from 'lodash/filter';\nimport _get from 'lodash/get';\nimport _isEmpty from 'lodash/isEmpty';\nimport _isEqual from 'lodash/isEqual';\nimport Spinner from './util/Spinner.jsx';\nimport NetworkGraph from './NetworkGraph.jsx';\nimport MetricsTable from './MetricsTable.jsx';\nimport ErrorBanner from './ErrorBanner.jsx';\nimport { handlePageVisibility, withPageVisibility } from './util/PageVisibility.jsx';\nimport { friendlyTitle } from './util/Utils.js';\nimport { processMultiResourceRollup } from './util/MetricUtils.jsx';\nimport { withContext } from './util/AppContext.jsx';\n\nclass Namespaces extends React.Component {\n  constructor(props) {\n    super(props);\n    this.api = props.api;\n    this.handleApiError = this.handleApiError.bind(this);\n    this.loadFromServer = this.loadFromServer.bind(this);\n    this.state = this.getInitialState(props.match.params);\n  }\n\n  getInitialState(params) {\n    const ns = _get(params, 'namespace', 'default');\n\n    return {\n      ns,\n      pollingInterval: 2000,\n      metrics: {},\n      pendingRequests: false,\n      loaded: false,\n      error: null,\n    };\n  }\n\n  componentDidMount() {\n    this.startServerPolling();\n    this.checkNamespaceMatch();\n  }\n\n  componentDidUpdate(prevProps) {\n    const { match, isPageVisible } = this.props;\n    const { params } = match;\n    if (!_isEqual(prevProps.match.params.namespace, params.namespace)) {\n      // React won't unmount this component when switching resource pages so we need to clear state\n      this.api.cancelCurrentRequests();\n      this.resetState(params);\n    }\n\n    handlePageVisibility({\n      prevVisibilityState: prevProps.isPageVisible,\n      currentVisibilityState: isPageVisible,\n      onVisible: () => this.startServerPolling(),\n      onHidden: () => this.stopServerPolling(),\n    });\n  }\n\n  resetState(params) {\n    this.setState(this.getInitialState(params));\n  }\n\n  componentWillUnmount() {\n    this.stopServerPolling();\n  }\n\n  startServerPolling() {\n    const { pollingInterval } = this.state;\n    this.loadFromServer();\n    this.timerId = window.setInterval(this.loadFromServer, pollingInterval);\n  }\n\n  stopServerPolling() {\n    window.clearInterval(this.timerId);\n    this.api.cancelCurrentRequests();\n    this.setState({ pendingRequests: false });\n  }\n\n  loadFromServer() {\n    const { pendingRequests, ns } = this.state;\n    if (pendingRequests) {\n      return; // don't make more requests if the ones we sent haven't completed\n    }\n    this.setState({ pendingRequests: true });\n\n    this.api.setCurrentRequests([this.api.fetchMetrics(this.api.urlsForResource('all', ns, true))]);\n\n    Promise.all(this.api.getCurrentPromises())\n      .then(([allRollup]) => {\n        const metrics = processMultiResourceRollup(allRollup, 'all');\n\n        this.setState({\n          metrics,\n          loaded: true,\n          pendingRequests: false,\n          error: null,\n        });\n      })\n      .catch(this.handleApiError);\n  }\n\n  handleApiError = e => {\n    if (e.isCanceled) {\n      return;\n    }\n\n    this.setState({\n      pendingRequests: false,\n      error: e,\n    });\n  };\n\n  checkNamespaceMatch = () => {\n    const { ns } = this.state;\n    const { selectedNamespace, updateNamespaceInContext } = this.props;\n\n    if (ns !== selectedNamespace) {\n      updateNamespaceInContext(ns);\n    }\n  };\n\n  static renderResourceSection(resource, metrics) {\n    if (_isEmpty(metrics)) {\n      return null;\n    }\n    return (\n      <div className=\"page-section\">\n        <MetricsTable\n          title={friendlyTitle(resource).plural}\n          resource={resource}\n          metrics={metrics}\n          showNamespaceColumn={false} />\n      </div>\n    );\n  }\n\n  render() {\n    const { metrics, ns, loaded, error } = this.state;\n    const noMetrics = _isEmpty(metrics.pod);\n    const deploymentsWithMetrics = _filter(metrics.deployment, d => d.requestRate > 0);\n\n    return (\n      <div className=\"page-content\">\n        {!error ? null : <ErrorBanner message={error} />}\n        {!loaded ? <Spinner /> : (\n          <div>\n            {noMetrics ? <div><Trans>noResourcesDetectedMsg</Trans></div> : null}\n            {\n              _isEmpty(deploymentsWithMetrics) ? null :\n              <NetworkGraph namespace={ns} deployments={metrics.deployment} />\n            }\n            {Namespaces.renderResourceSection('deployment', metrics.deployment)}\n            {Namespaces.renderResourceSection('daemonset', metrics.daemonset)}\n            {Namespaces.renderResourceSection('pod', metrics.pod)}\n            {Namespaces.renderResourceSection('replicationcontroller', metrics.replicationcontroller)}\n            {Namespaces.renderResourceSection('statefulset', metrics.statefulset)}\n            {Namespaces.renderResourceSection('job', metrics.job)}\n            {Namespaces.renderResourceSection('cronjob', metrics.cronjob)}\n            {Namespaces.renderResourceSection('replicaset', metrics.replicaset)}\n\n            {\n              noMetrics ? null :\n              <div className=\"page-section\">\n                <MetricsTable\n                  title={<Trans>tableTitleTCP</Trans>}\n                  resource=\"pod\"\n                  metrics={metrics.pod}\n                  isTcpTable />\n              </div>\n            }\n          </div>\n        )}\n      </div>\n    );\n  }\n}\n\nNamespaces.propTypes = {\n  api: PropTypes.shape({\n    cancelCurrentRequests: PropTypes.func.isRequired,\n    fetchMetrics: PropTypes.func.isRequired,\n    getCurrentPromises: PropTypes.func.isRequired,\n    setCurrentRequests: PropTypes.func.isRequired,\n    urlsForResource: PropTypes.func.isRequired,\n  }).isRequired,\n  isPageVisible: PropTypes.bool.isRequired,\n  match: PropTypes.shape({\n    params: PropTypes.shape({\n      namespace: PropTypes.string,\n    }),\n  }),\n  selectedNamespace: PropTypes.string.isRequired,\n  updateNamespaceInContext: PropTypes.func.isRequired,\n};\n\nNamespaces.defaultProps = {\n  match: {\n    params: {\n      namespace: 'default',\n    },\n  },\n};\n\nexport default withPageVisibility(withContext(Namespaces));\n"
  },
  {
    "path": "web/app/js/components/NamespaceConfirmationModal.jsx",
    "content": "import Button from '@material-ui/core/Button';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogContentText from '@material-ui/core/DialogContentText';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport PropTypes from 'prop-types';\nimport React from 'react';\n\nclass NamespaceConfirmationModal extends React.Component {\n  render() {\n    const { open, selectedNamespace, newNamespace, handleConfirmNamespaceChange, handleDialogCancel } = this.props;\n\n    return (\n      <Dialog\n        open={open}\n        onClose={this.handleClose}>\n        <DialogTitle>\n          Change namespace?\n        </DialogTitle>\n        <DialogContent>\n          <DialogContentText>\n            The resource you are viewing is in a different namespace than the namespace you have selected. Do you want to change the namespace from { selectedNamespace } to { newNamespace }?\n          </DialogContentText>\n        </DialogContent>\n        <DialogActions>\n          <Button onClick={handleConfirmNamespaceChange} color=\"primary\">\n            Yes\n          </Button>\n          <Button onClick={handleDialogCancel} variant=\"text\">\n            No\n          </Button>\n        </DialogActions>\n      </Dialog>\n    );\n  }\n}\n\nNamespaceConfirmationModal.propTypes = {\n  handleConfirmNamespaceChange: PropTypes.func.isRequired,\n  handleDialogCancel: PropTypes.func.isRequired,\n  newNamespace: PropTypes.string.isRequired,\n  open: PropTypes.bool.isRequired,\n  selectedNamespace: PropTypes.string.isRequired,\n};\n\nexport default NamespaceConfirmationModal;\n"
  },
  {
    "path": "web/app/js/components/Navigation.jsx",
    "content": "import AppBar from '@material-ui/core/AppBar';\nimport Autocomplete from '@material-ui/lab/Autocomplete';\nimport Badge from '@material-ui/core/Badge';\nimport Divider from '@material-ui/core/Divider';\nimport Drawer from '@material-ui/core/Drawer';\nimport EmailIcon from '@material-ui/icons/Email';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport Hidden from '@material-ui/core/Hidden';\nimport IconButton from '@material-ui/core/IconButton';\nimport LibraryBooksIcon from '@material-ui/icons/LibraryBooks';\nimport { Link } from 'react-router-dom';\nimport ListItemIcon from '@material-ui/core/ListItemIcon';\nimport ListItemText from '@material-ui/core/ListItemText';\nimport MenuItem from '@material-ui/core/MenuItem';\nimport MenuList from '@material-ui/core/MenuList';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport ReactRouterPropTypes from 'react-router-prop-types';\nimport TextField from '@material-ui/core/TextField';\nimport Toolbar from '@material-ui/core/Toolbar';\nimport { Trans } from '@lingui/macro';\nimport Typography from '@material-ui/core/Typography';\nimport _isEmpty from 'lodash/isEmpty';\nimport _maxBy from 'lodash/maxBy';\nimport { faBars } from '@fortawesome/free-solid-svg-icons/faBars';\nimport { faCloud } from '@fortawesome/free-solid-svg-icons/faCloud';\nimport { faDungeon } from '@fortawesome/free-solid-svg-icons/faDungeon';\nimport { faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons/faExternalLinkAlt';\nimport { faMicroscope } from '@fortawesome/free-solid-svg-icons/faMicroscope';\nimport { faRandom } from '@fortawesome/free-solid-svg-icons/faRandom';\nimport { faSmile } from '@fortawesome/free-regular-svg-icons/faSmile';\nimport { faStream } from '@fortawesome/free-solid-svg-icons/faStream';\nimport { faPuzzlePiece } from '@fortawesome/free-solid-svg-icons/faPuzzlePiece';\nimport grey from '@material-ui/core/colors/grey';\nimport { withStyles } from '@material-ui/core/styles';\nimport yellow from '@material-ui/core/colors/yellow';\nimport { processSingleResourceRollup } from './util/MetricUtils.jsx';\nimport { regexFilterString } from './util/Utils.js';\nimport { withContext } from './util/AppContext.jsx';\nimport Version from './Version.jsx';\nimport NamespaceConfirmationModal from './NamespaceConfirmationModal.jsx';\nimport BreadcrumbHeader from './BreadcrumbHeader.jsx';\nimport { handlePageVisibility, withPageVisibility } from './util/PageVisibility.jsx';\nimport {\n  cronJobIcon,\n  daemonsetIcon,\n  deploymentIcon,\n  githubIcon,\n  jobIcon,\n  linkerdWordLogo,\n  namespaceIcon,\n  podIcon,\n  replicaSetIcon,\n  serviceIcon,\n  slackIcon,\n  statefulSetIcon,\n} from './util/SvgWrappers.jsx';\n\nconst jsonFeedUrl = 'https://linkerd.io/dashboard/index.json';\nconst multiclusterExtensionName = 'multicluster';\nconst localStorageKey = 'linkerd-updates-last-clicked';\nconst minBrowserWidth = 960;\n\nconst styles = theme => {\n  const drawerWidth = theme.spacing(36);\n  const navLogoWidth = theme.spacing(22.5);\n  const contentPadding = theme.spacing(3);\n\n  const enteringFn = prop => theme.transitions.create(prop, {\n    easing: theme.transitions.easing.sharp,\n    duration: theme.transitions.duration.enteringScreen,\n  });\n  const leavingFn = prop => theme.transitions.create(prop, {\n    easing: theme.transitions.easing.sharp,\n    duration: theme.transitions.duration.leavingScreen,\n  });\n\n  const entering = enteringFn('width');\n  const leaving = leavingFn('width');\n\n  return {\n    root: {\n      display: 'flex',\n    },\n    appBar: {\n      alignItems: 'center',\n      position: 'permanent',\n      color: 'white',\n      transition: leaving,\n    },\n    bars: {\n      color: 'white',\n      position: 'fixed',\n      left: theme.spacing(2.5),\n    },\n    breadcrumbs: {\n      color: 'white',\n      marginLeft: `${drawerWidth}px`,\n    },\n    drawer: {\n      width: drawerWidth,\n      transition: entering,\n    },\n    drawerPaper: {\n      width: 'inherit',\n    },\n    toolbar: theme.mixins.toolbar,\n    navToolbar: {\n      display: 'flex',\n      alignItems: 'center',\n      padding: `0 0 0 ${theme.spacing(2)}px`,\n      boxShadow: theme.shadows[4], // to match elevation == 4 on main AppBar\n      ...theme.mixins.toolbar,\n      backgroundColor: theme.palette.primary.main,\n    },\n    content: {\n      flexGrow: 1,\n      width: `calc(100% - ${drawerWidth}px)`,\n      backgroundColor: theme.palette.background.default,\n      padding: contentPadding,\n      transition: entering,\n    },\n    linkerdNavLogo: {\n      margin: 'auto',\n      width: `${navLogoWidth}px`,\n      transition: enteringFn(['margin', 'opacity']),\n    },\n    linkerdMobileLogo: {\n      width: `${navLogoWidth}px`,\n    },\n    namespaceChangeButton: {\n      borderRadius: '5px',\n      backgroundColor: grey[400],\n      marginLeft: `${drawerWidth * 0.075}px`,\n      marginRight: `${drawerWidth * 0.075}px`,\n      marginTop: '11px',\n      width: `${drawerWidth * 0.85}px`,\n    },\n    namespaceChangeButtonInputRoot: {\n      backgroundColor: grey[300],\n      boxShadow: 'rgba(0, 0, 0, 0.2) 0px 3px 1px -2px, rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px',\n      padding: '4px 12px !important',\n      border: 0,\n      '&:hover': {\n        borderColor: 'transparent',\n      },\n    },\n    namespaceChangeButtonInput: {\n      textAlign: 'center',\n    },\n    namespaceChangeButtonInputFocused: {\n      textAlign: 'center',\n    },\n    namespaceChangeButtonPopupIndicator: {\n      backgroundColor: 'transparent',\n      '&:hover': {\n        backgroundColor: 'transparent',\n      },\n    },\n    navMenuItem: {\n      paddingLeft: `${contentPadding}px`,\n      paddingRight: `${contentPadding}px`,\n    },\n    shrinkIcon: {\n      fontSize: '24px',\n      paddingLeft: '3px',\n      paddingRight: '3px',\n    },\n    shrinkCloudIcon: {\n      fontSize: '18px',\n      paddingLeft: '1px',\n    },\n    // color is consistent with Octopus Graph coloring\n    externalLinkIcon: {\n      color: grey[500],\n    },\n    sidebarHeading: {\n      color: grey[500],\n      outline: 'none',\n      paddingTop: '9px',\n      paddingBottom: '9px',\n      marginLeft: `${drawerWidth * 0.09}px`,\n    },\n    badge: {\n      backgroundColor: yellow[500],\n    },\n    inputBase: {\n      boxSizing: 'border-box',\n    },\n  };\n};\n\nclass NavigationBase extends React.Component {\n  constructor(props) {\n    super(props);\n    this.api = props.api;\n    this.handleApiError = this.handleApiError.bind(this);\n    this.handleConfirmNamespaceChange = this.handleConfirmNamespaceChange.bind(this);\n    this.handleCommunityClick = this.handleCommunityClick.bind(this);\n    this.handleDialogCancel = this.handleDialogCancel.bind(this);\n    this.handleFilterInputChange = this.handleFilterInputChange.bind(this);\n    this.handleNamespaceMenuClick = this.handleNamespaceMenuClick.bind(this);\n    this.updateWindowDimensions = this.updateWindowDimensions.bind(this);\n\n    this.state = this.getInitialState();\n    this.loadFromServer = this.loadFromServer.bind(this);\n  }\n\n  getInitialState() {\n    return {\n      mobileSidebarOpen: false,\n      newNamespace: '',\n      formattedNamespaceFilter: '',\n      hideUpdateBadge: true,\n      latestVersion: '',\n      isLatest: true,\n      namespaces: [],\n      pendingRequests: false,\n      pollingInterval: 10000,\n      loaded: false,\n      error: null,\n      showNamespaceChangeDialog: false,\n      showGatewayLink: false,\n    };\n  }\n\n  componentDidMount() {\n    this.startServerPolling();\n    this.fetchVersion();\n    this.checkMulticlusterExtension();\n    this.fetchLatestCommunityUpdate();\n    this.updateWindowDimensions();\n    window.addEventListener('resize', this.updateWindowDimensions);\n  }\n\n  componentDidUpdate(prevProps) {\n    const { history, checkNamespaceMatch, isPageVisible } = this.props;\n    if (history) {\n      checkNamespaceMatch(history.location.pathname);\n    }\n\n    handlePageVisibility({\n      prevVisibilityState: prevProps.isPageVisible,\n      currentVisibilityState: isPageVisible,\n      onVisible: () => this.startServerPolling(),\n      onHidden: () => this.stopServerPolling(),\n    });\n  }\n\n  componentWillUnmount() {\n    window.removeEventListener('resize', this.updateWindowDimensions);\n    this.stopServerPolling();\n  }\n\n  startServerPolling() {\n    const { pollingInterval } = this.state;\n    this.loadFromServer();\n    this.timerId = window.setInterval(this.loadFromServer, pollingInterval);\n  }\n\n  stopServerPolling() {\n    window.clearInterval(this.timerId);\n    this.api.cancelCurrentRequests();\n    this.setState({ pendingRequests: false });\n  }\n\n  // API returns namespaces for namespace select button. No metrics returned.\n  loadFromServer() {\n    const { pendingRequests } = this.state;\n\n    if (pendingRequests) {\n      return;\n    }\n    this.setState({ pendingRequests: true });\n\n    const apiRequests = [\n      this.api.fetchMetrics(this.api.urlsForResourceNoStats('namespace')),\n    ];\n\n    this.api.setCurrentRequests(apiRequests);\n\n    Promise.all(this.api.getCurrentPromises())\n      .then(([allNs]) => {\n        // add \"All Namespaces\" to the options\n        let namespaces = [{ name: '_all', key: 'ns-all' }];\n        namespaces = namespaces.concat(processSingleResourceRollup(allNs));\n        this.setState({\n          namespaces,\n          pendingRequests: false,\n          error: null,\n        });\n      })\n      .catch(this.handleApiError);\n  }\n\n  fetchVersion() {\n    const { releaseVersion, uuid } = this.props;\n\n    const versionUrl = `https://versioncheck.linkerd.io/version.json?version=${releaseVersion}&uuid=${uuid}&source=web`;\n\n    // expose for testing\n    // eslint-disable-next-line react/no-unused-class-component-methods\n    this.versionPromise = fetch(versionUrl, { credentials: 'include' })\n      .then(rsp => rsp.json())\n      .then(versionRsp => {\n        let latestVersion;\n        const parts = releaseVersion.split('-', 2);\n        if (parts.length === 2) {\n          latestVersion = versionRsp[parts[0]];\n        }\n        this.setState({\n          latestVersion,\n          isLatest: latestVersion === releaseVersion,\n        });\n      }).catch(this.handleApiError);\n  }\n\n  fetchLatestCommunityUpdate() {\n    fetch(jsonFeedUrl)\n      .then(rsp => rsp.json())\n      .then(rsp => rsp.data.date)\n      .then(rsp => {\n        if (rsp.length > 0) {\n          const lastClicked = localStorage[localStorageKey];\n          if (!lastClicked) {\n            this.setState({ hideUpdateBadge: false });\n          } else {\n            const lastClickedDateObject = new Date(lastClicked);\n            const latestArticle = _maxBy(rsp, update => update.date);\n            const latestArticleDateObject = new Date(latestArticle);\n            if (latestArticleDateObject > lastClickedDateObject) {\n              this.setState({ hideUpdateBadge: false });\n            }\n          }\n        }\n      })\n      .catch(this.handleApiError);\n  }\n\n  checkMulticlusterExtension() {\n    this.api.setCurrentRequests([this.api.fetchExtension(multiclusterExtensionName)]);\n\n    // expose for testing\n    // eslint-disable-next-line react/no-unused-class-component-methods\n    this.serverPromise = Promise.all(this.api.getCurrentPromises())\n      .then(([extension]) => {\n        this.setState({ showGatewayLink: !_isEmpty(extension) });\n      })\n      .catch(this.handleApiError);\n  }\n\n  handleApiError(e) {\n    this.setState({\n      error: e,\n    });\n  }\n\n  handleCommunityClick = () => {\n    const lastClicked = new Date();\n    localStorage.setItem(localStorageKey, lastClicked);\n    this.setState({ hideUpdateBadge: true });\n  };\n\n  handleDialogCancel = () => {\n    this.setState({ showNamespaceChangeDialog: false });\n  };\n\n  handleDrawerClick = () => {\n    const { mobileSidebarOpen } = this.state;\n    if (!mobileSidebarOpen) {\n      this.setState({ mobileSidebarOpen: true });\n    } else {\n      this.setState({ mobileSidebarOpen: false });\n      window.setTimeout(() => {\n        const linkerdHash = document.querySelector('.linkerd-word-logo #linkerd-hash');\n        linkerdHash.style.display = 'none';\n        window.setTimeout(() => {\n          linkerdHash.style.display = '';\n        }, 15);\n      }, 300);\n    }\n  };\n\n  handleConfirmNamespaceChange = () => {\n    const { newNamespace } = this.state;\n    const { updateNamespaceInContext, history } = this.props;\n    this.setState({ showNamespaceChangeDialog: false });\n    updateNamespaceInContext(newNamespace);\n    history.push(`/namespaces/${newNamespace}`);\n  };\n\n  handleFilterInputChange = event => {\n    this.setState({\n      formattedNamespaceFilter: regexFilterString(event.target.value),\n    });\n  };\n\n  static handleAutocompleteClick(event) {\n    // This is necessary for the mobile sidebar, otherwise the sidebar\n    // would close upon click of the namespace change input.\n    event.stopPropagation();\n  }\n\n  handleNamespaceChange = (event, values) => {\n    const { history, updateNamespaceInContext, selectedNamespace } = this.props;\n\n    // event.stopPropagation();\n    const namespace = values.name;\n    if (namespace === selectedNamespace) {\n      return;\n    }\n    let path = history.location.pathname;\n    const pathParts = path.split('/');\n    if (pathParts.length === 3 || pathParts.length === 4) {\n      // path is /namespaces/someNamespace/resourceType\n      //      or /namespaces/someNamespace\n      path = path.replace(selectedNamespace, namespace);\n      history.push(path);\n      updateNamespaceInContext(namespace);\n    } else if (pathParts.length === 5) {\n      // path is /namespace/someNamespace/resourceType/someResource\n      this.setState({\n        showNamespaceChangeDialog: true,\n        newNamespace: namespace,\n      });\n    } else {\n      // update the selectedNamespace in context with no path changes\n      updateNamespaceInContext(namespace);\n    }\n  };\n\n  handleNamespaceMenuClick = event => {\n    // ensure that mobile drawer will not close on click\n    event.stopPropagation();\n    this.setState({ formattedNamespaceFilter: '' });\n  };\n\n  menuItem(path, title, icon, onClick) {\n    const { classes, location, pathPrefix } = this.props;\n    const normalizedPath = location.pathname.replace(pathPrefix, '');\n\n    return (\n      <MenuItem\n        component={Link}\n        onClick={onClick}\n        to={this.api.prefixLink(path)}\n        className={classes.navMenuItem}\n        selected={path === normalizedPath}>\n        <ListItemIcon>{icon}</ListItemIcon>\n        <ListItemText primary={title} />\n      </MenuItem>\n    );\n  }\n\n  updateWindowDimensions() {\n    const browserWidth = window.innerWidth;\n    if (browserWidth > minBrowserWidth) {\n      this.setState({ mobileSidebarOpen: false });\n    }\n  }\n\n  render() {\n    const { api, classes, selectedNamespace, ChildComponent, uuid, releaseVersion, ...otherProps } = this.props;\n    const { namespaces, formattedNamespaceFilter, hideUpdateBadge, isLatest, latestVersion,\n      showNamespaceChangeDialog, newNamespace, mobileSidebarOpen, error, showGatewayLink } = this.state;\n    const filteredNamespaces = namespaces.filter(ns => {\n      return ns.name.match(formattedNamespaceFilter);\n    });\n    let formattedNamespaceName = selectedNamespace;\n    if (formattedNamespaceName === '_all') {\n      formattedNamespaceName = 'All Namespaces';\n    }\n\n    const drawer = (\n      <div>\n        { !mobileSidebarOpen &&\n          <div className={classes.navToolbar}>\n            <div className={classes.linkerdNavLogo}>\n              <Link to=\"/namespaces\">{linkerdWordLogo}</Link>\n            </div>\n          </div>\n        }\n        <Divider />\n        <MenuList>\n          <Typography variant=\"button\" component=\"div\" className={classes.sidebarHeading}>\n            <Trans>sidebarHeadingCluster</Trans>\n          </Typography>\n          { this.menuItem('/namespaces', <Trans>menuItemNamespaces</Trans>, namespaceIcon) }\n\n          { this.menuItem(\n            '/controlplane',\n            <Trans>menuItemControlPlane</Trans>,\n            <FontAwesomeIcon icon={faCloud} className={classes.shrinkCloudIcon} />,\n          ) }\n\n          { showGatewayLink && this.menuItem(\n            '/gateways',\n            <Trans>menuItemGateway</Trans>,\n            <FontAwesomeIcon icon={faDungeon} className={classes.shrinkIcon} />,\n          ) }\n\n        </MenuList>\n\n        <Divider />\n\n        <Autocomplete\n          id=\"namespace-autocomplete\"\n          onClick={NavigationBase.handleAutocompleteClick}\n          disableClearable\n          value={{ name: formattedNamespaceName.toUpperCase() }}\n          options={filteredNamespaces}\n          autoSelect\n          getOptionSelected={option => option.name === selectedNamespace}\n          getOptionLabel={option => { if (option.name !== '_all') { return option.name; } else { return 'All Namespaces'; } }}\n          onChange={this.handleNamespaceChange}\n          size=\"small\"\n          classes={{\n            root: classes.namespaceChangeButton,\n            inputRoot: classes.namespaceChangeButtonInputRoot,\n            input: classes.namespaceChangeButtonInput,\n            popupIndicator: classes.namespaceChangeButtonPopupIndicator,\n          }}\n          className={classes.namespaceChangeButton}\n          renderInput={params => (\n            <TextField\n              {...params}\n              key={params.name}\n              variant=\"outlined\"\n              fullWidth />\n          )} />\n\n        <NamespaceConfirmationModal\n          open={showNamespaceChangeDialog}\n          selectedNamespace={selectedNamespace}\n          newNamespace={newNamespace}\n          handleDialogCancel={this.handleDialogCancel}\n          handleConfirmNamespaceChange={this.handleConfirmNamespaceChange} />\n\n        <MenuList>\n          <Typography variant=\"button\" component=\"div\" className={classes.sidebarHeading}>\n            <Trans>sidebarHeadingWorkloads</Trans>\n          </Typography>\n\n          { this.menuItem(`/namespaces/${selectedNamespace}/cronjobs`, <Trans>menuItemCronJobs</Trans>, cronJobIcon) }\n\n          { this.menuItem(`/namespaces/${selectedNamespace}/daemonsets`, <Trans>menuItemDaemonSets</Trans>, daemonsetIcon) }\n\n          { this.menuItem(`/namespaces/${selectedNamespace}/deployments`, <Trans>menuItemDeployments</Trans>, deploymentIcon) }\n\n          { this.menuItem(`/namespaces/${selectedNamespace}/services`, <Trans>menuItemServices</Trans>, serviceIcon) }\n\n          { this.menuItem(`/namespaces/${selectedNamespace}/jobs`, <Trans>menuItemJobs</Trans>, jobIcon) }\n\n          { this.menuItem(`/namespaces/${selectedNamespace}/pods`, <Trans>menuItemPods</Trans>, podIcon) }\n\n          { this.menuItem(`/namespaces/${selectedNamespace}/replicasets`, <Trans>menuItemReplicaSets</Trans>, replicaSetIcon) }\n\n          { this.menuItem(`/namespaces/${selectedNamespace}/replicationcontrollers`, <Trans>menuItemReplicationControllers</Trans>, replicaSetIcon) }\n\n          { this.menuItem(`/namespaces/${selectedNamespace}/statefulsets`, <Trans>menuItemStatefulSets</Trans>, statefulSetIcon) }\n        </MenuList>\n\n        <Divider />\n        <MenuList>\n          <Typography variant=\"button\" component=\"div\" className={classes.sidebarHeading}>\n            <Trans>sidebarHeadingTools</Trans>\n          </Typography>\n\n          { this.menuItem('/tap', <Trans>menuItemTap</Trans>, <FontAwesomeIcon icon={faMicroscope} className={classes.shrinkIcon} />) }\n          { this.menuItem('/top', <Trans>menuItemTop</Trans>, <FontAwesomeIcon icon={faStream} className={classes.shrinkIcon} />) }\n          { this.menuItem('/routes', <Trans>menuItemRoutes</Trans>, <FontAwesomeIcon icon={faRandom} className={classes.shrinkIcon} />) }\n\n        </MenuList>\n        <Divider />\n        <MenuList>\n          { this.menuItem(\n            '/community',\n            <Trans>menuItemCommunity</Trans>,\n            <Badge\n              classes={{ badge: classes.badge }}\n              invisible={hideUpdateBadge}\n              badgeContent=\"1\">\n              <FontAwesomeIcon icon={faSmile} className={classes.shrinkIcon} />\n            </Badge>,\n            this.handleCommunityClick,\n          ) }\n\n          { this.menuItem(\n            '/extensions',\n            <Trans>menuItemExtension</Trans>,\n            <Badge classes={{ badge: classes.badge }}><FontAwesomeIcon icon={faPuzzlePiece} className={classes.shrinkIcon} /></Badge>,\n          )\n          }\n\n          <MenuItem component=\"a\" href=\"https://linkerd.io/2/overview/\" target=\"_blank\" className={classes.navMenuItem}>\n            <ListItemIcon><LibraryBooksIcon className={classes.shrinkIcon} /></ListItemIcon>\n            <ListItemText primary={<Trans>menuItemDocumentation</Trans>} />\n            <FontAwesomeIcon icon={faExternalLinkAlt} className={classes.externalLinkIcon} size=\"xs\" />\n          </MenuItem>\n\n          <MenuItem component=\"a\" href=\"https://github.com/linkerd/linkerd2/issues/new/choose\" target=\"_blank\" className={classes.navMenuItem}>\n            <ListItemIcon>{githubIcon}</ListItemIcon>\n            <ListItemText primary={<Trans>menuItemGitHub</Trans>} />\n            <FontAwesomeIcon icon={faExternalLinkAlt} className={classes.externalLinkIcon} size=\"xs\" />\n          </MenuItem>\n\n          <MenuItem component=\"a\" href=\"https://lists.cncf.io/g/cncf-linkerd-users\" target=\"_blank\" className={classes.navMenuItem}>\n            <ListItemIcon><EmailIcon className={classes.shrinkIcon} /></ListItemIcon>\n            <ListItemText primary={<Trans>menuItemMailingList</Trans>} />\n            <FontAwesomeIcon icon={faExternalLinkAlt} className={classes.externalLinkIcon} size=\"xs\" />\n          </MenuItem>\n\n          <MenuItem component=\"a\" href=\"https://slack.linkerd.io\" target=\"_blank\" className={classes.navMenuItem}>\n            <ListItemIcon>{slackIcon}</ListItemIcon>\n            <ListItemText primary={<Trans>menuItemSlack</Trans>} />\n            <FontAwesomeIcon icon={faExternalLinkAlt} className={classes.externalLinkIcon} size=\"xs\" />\n          </MenuItem>\n\n          <Version\n            isLatest={isLatest}\n            latestVersion={latestVersion}\n            releaseVersion={releaseVersion}\n            error={error}\n            uuid={uuid} />\n\n        </MenuList>\n\n      </div>\n    );\n\n    return (\n      <div className={classes.root}>\n\n        <Hidden smDown>\n          <Drawer\n            className={classes.drawer}\n            classes={{ paper: classes.drawerPaper }}\n            variant=\"permanent\">\n            {drawer}\n          </Drawer>\n          <AppBar>\n            <Toolbar>\n              <Typography variant=\"h6\" color=\"inherit\" className={classes.breadcrumbs} noWrap>\n                <BreadcrumbHeader {...this.props} />\n              </Typography>\n            </Toolbar>\n          </AppBar>\n        </Hidden>\n\n        <Hidden mdUp>\n          <AppBar className={classes.appBar}>\n            <Toolbar>\n              <div className={classes.linkerdMobileLogo}>\n                {linkerdWordLogo}\n              </div>\n              { !mobileSidebarOpen && // mobile view but no sidebar\n                <IconButton onClick={this.handleDrawerClick} className={classes.bars}>\n                  <FontAwesomeIcon icon={faBars} />\n                </IconButton>\n              }\n            </Toolbar>\n          </AppBar>\n          <Drawer\n            className={classes.drawer}\n            classes={{ paper: classes.drawerPaper }}\n            variant=\"temporary\"\n            onClick={this.handleDrawerClick}\n            onClose={this.handleDrawerClick}\n            open={mobileSidebarOpen}>\n            {drawer}\n          </Drawer>\n        </Hidden>\n\n        <main className={classes.content}>\n          <div className={classes.toolbar} />\n          <div>\n            <ChildComponent {...otherProps} />\n          </div>\n        </main>\n      </div>\n    );\n  }\n}\n\nNavigationBase.propTypes = {\n  api: PropTypes.shape({}).isRequired,\n  checkNamespaceMatch: PropTypes.func.isRequired,\n  ChildComponent: PropTypes.oneOfType([\n    PropTypes.func,\n    PropTypes.object,\n  ]).isRequired,\n  isPageVisible: PropTypes.bool.isRequired,\n  history: ReactRouterPropTypes.history.isRequired,\n  location: ReactRouterPropTypes.location.isRequired,\n  pathPrefix: PropTypes.string.isRequired,\n  releaseVersion: PropTypes.string.isRequired,\n  selectedNamespace: PropTypes.string.isRequired,\n  theme: PropTypes.shape({}).isRequired,\n  updateNamespaceInContext: PropTypes.func.isRequired,\n  uuid: PropTypes.string.isRequired,\n};\n\nexport default withPageVisibility(withContext(withStyles(styles, { withTheme: true })(NavigationBase)));\n"
  },
  {
    "path": "web/app/js/components/Navigation.test.jsx",
    "content": "import _merge from 'lodash/merge';\nimport ApiHelpers from './util/ApiHelpers.jsx';\nimport { BrowserRouter } from 'react-router-dom';\nimport React from 'react';\nimport mediaQuery from 'css-mediaquery';\nimport Navigation from './Navigation.jsx';\nimport sinon from 'sinon';\nimport sinonStubPromise from 'sinon-stub-promise';\nimport { mount } from 'enzyme';\nimport { createMemoryHistory } from 'history';\nimport { i18nWrap } from '../../test/testHelpers.jsx';\n\nfunction createMatchMedia(width) {\n  return query => ({\n    matches: mediaQuery.match(query, { width }),\n    addListener: () => {},\n    removeListener: () => {},\n  });\n}\n\nsinonStubPromise(sinon);\n\nconst loc = {\n  pathname: '',\n  hash: '',\n  pathPrefix: '',\n  search: '',\n};\n\nconst curVer = \"edge-1.2.3\";\nconst newVer = \"edge-2.3.4\";\n\nconst defaultProps = {\n  api: ApiHelpers(''),\n  checkNamespaceMatch: () => {},\n  ChildComponent: () => null,\n  classes: {},\n  history: createMemoryHistory('/namespaces'),\n  location: loc,\n  pathPrefix: '',\n  releaseVersion: curVer,\n  selectedNamespace: 'emojivoto',\n  theme: {},\n  updateNamespaceInContext: () => {},\n  uuid: 'fakeuuid',\n};\n\ndescribe('Navigation', () => {\n  let component, fetchStub;\n\n  function withPromise(fn) {\n    return component.find(\"NavigationBase\").instance().versionPromise.then(fn);\n  }\n\n  beforeEach(() => {\n    fetchStub = sinon.stub(window, 'fetch');\n  });\n\n  afterEach(() => {\n    component = null;\n    window.fetch.restore();\n  });\n\n  it('checks state when versions match', () => {\n    fetchStub.resolves({\n      ok: true,\n      json: () => Promise.resolve({ edge: curVer })\n    });\n\n    component = mount(\n      <BrowserRouter>\n        <Navigation {...defaultProps} />\n      </BrowserRouter>\n    );\n\n    return withPromise(() => {\n      expect(component.find(\"NavigationBase\").state(\"isLatest\")).toBeTruthy();\n      expect(component.find(\"NavigationBase\").state(\"latestVersion\")).toBe(curVer);\n    });\n  });\n\n  it('checks state when versions do not match', () => {\n    fetchStub.resolves({\n      ok: true,\n      json: () => Promise.resolve({ edge: newVer })\n    });\n\n    component = mount(\n      <BrowserRouter>\n        <Navigation {...defaultProps} />\n      </BrowserRouter>\n    );\n\n    return withPromise(() => {\n      expect(component.find(\"NavigationBase\").state(\"isLatest\")).toBeFalsy();\n      expect(component.find(\"NavigationBase\").state(\"latestVersion\")).toBe(newVer);\n    });\n  });\n\n  it('checks state when version check fails', () => {\n    let errMsg = \"Fake error\";\n\n    fetchStub.rejects({\n      ok: false,\n      json: () => Promise.resolve({\n        error: {},\n      }),\n      statusText: errMsg,\n    });\n\n    component = mount(\n      <BrowserRouter>\n        <Navigation {...defaultProps} />\n      </BrowserRouter>\n    );\n\n    return withPromise(() => {\n      expect(component.find(\"NavigationBase\").state(\"error\")).toBeDefined();\n      expect(component.find(\"NavigationBase\").state(\"error\").statusText).toBe(\"Fake error\");\n    });\n  });\n});\n\ndescribe('Namespace Select Button', () => {\n  beforeEach(() => {\n    // https://material-ui.com/components/use-media-query/#testing\n    window.matchMedia = createMatchMedia(window.innerWidth);\n  });\n\n  it('displays All Namespaces as button text if the selected namespace is _all', () => {\n    const extraProps = _merge({}, defaultProps, {\n      selectedNamespace: '_all',\n    });\n\n    const component = mount(\n      i18nWrap(\n        <BrowserRouter>\n          <Navigation {...extraProps} />\n        </BrowserRouter>\n      )\n    );\n\n    const input = component.find(\"input\");\n    expect(input.instance().value).toEqual(\"ALL NAMESPACES\");\n  });\n\n  it('renders emojivoto text', () => {\n    const component = mount(\n      i18nWrap(\n        <BrowserRouter>\n          <Navigation {...defaultProps} />\n        </BrowserRouter>\n      )\n    );\n\n    const input = component.find(\"input\");\n    expect(input.instance().value).toEqual(\"EMOJIVOTO\");\n  });\n});\n"
  },
  {
    "path": "web/app/js/components/NetworkGraph.jsx",
    "content": "import 'whatwg-fetch';\nimport 'regenerator-runtime/runtime';\nimport 'core-js/stable';\nimport { forceCenter, forceLink, forceManyBody, forceSimulation } from 'd3-force';\nimport { select, selectAll } from 'd3-selection';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport _get from 'lodash/get';\nimport _isEmpty from 'lodash/isEmpty';\nimport _map from 'lodash/map';\nimport _uniq from 'lodash/uniq';\nimport { drag } from 'd3-drag';\nimport { format } from 'd3-format';\nimport { metricsPropType } from './util/MetricUtils.jsx';\nimport { withContext } from './util/AppContext.jsx';\nimport withREST from './util/withREST.jsx';\n\n// create a Object with only the subset of functions/submodules/plugins that we need\nconst d3 = {\n  drag,\n  forceCenter,\n  forceLink,\n  forceManyBody,\n  forceSimulation,\n  select,\n  selectAll,\n  format,\n};\n\nconst defaultSvgWidth = 524;\nconst defaultSvgHeight = 325;\nconst defaultNodeRadius = 15;\nconst margin = { top: 0, right: 0, bottom: 10, left: 0 };\n\nconst simulation = d3.forceSimulation()\n  .force(\n    'link',\n    d3.forceLink()\n      .id(d => d.id)\n      .distance(140),\n  )\n  .force('charge', d3.forceManyBody().strength(-20))\n  .force('center', d3.forceCenter(defaultSvgWidth / 2, defaultSvgHeight / 2));\n\nexport class NetworkGraphBase extends React.Component {\n  constructor(props) {\n    super(props);\n\n    // https://github.com/d3/d3-zoom/issues/32\n    d3.getEvent = (() => require('d3-selection').event); // eslint-disable-line global-require\n  }\n\n  componentDidMount() {\n    const container = document.getElementsByClassName('network-graph-container')[0];\n    const width = !container ? defaultSvgWidth : container.getBoundingClientRect().width;\n\n    this.svg = d3.select('.network-graph-container')\n      .append('svg')\n      .attr('class', 'network-graph')\n      .attr('width', width)\n      .attr('height', width)\n      .append('g')\n      .attr('transform', `translate(${margin.left},${margin.top})`);\n  }\n\n  componentDidUpdate() {\n    simulation.alpha(1).restart();\n    this.drawGraph();\n  }\n\n  getGraphData() {\n    const { data, deployments } = this.props;\n    const links = [];\n    const nodeList = [];\n\n    _map(data, (resp, i) => {\n      const rows = _get(resp, ['ok', 'statTables', 0, 'podGroup', 'rows']);\n      const dst = deployments[i].name;\n      _map(rows, row => {\n        links.push({\n          source: row.resource.name,\n          target: dst,\n        });\n        nodeList.push(row.resource.name);\n        nodeList.push(dst);\n      });\n    });\n\n    const nodes = _map(_uniq(nodeList), n => ({ id: n }));\n    return {\n      links,\n      nodes,\n    };\n  }\n\n  drawGraph() {\n    const graphData = this.getGraphData();\n\n    // check if graph is present to prevent drawing of multiple graphs\n    if (this.svg.select('circle')._groups[0][0]) {\n      return;\n    }\n    this.drawGraphComponents(graphData.links, graphData.nodes);\n  }\n\n  drawGraphComponents(links, nodes) {\n    if (_isEmpty(nodes)) {\n      d3.select('.network-graph-container').select('svg').attr('height', 0);\n      return;\n    } else {\n      d3.select('.network-graph-container').select('svg').attr('height', defaultSvgHeight);\n    }\n\n    this.svg.append('svg:defs').selectAll('marker')\n      .data(links) // Different link/path types can be defined here\n      .enter()\n      .append('svg:marker') // This section adds in the arrows\n      .attr('id', node => `${node.source}/${node.target}`)\n      .attr('viewBox', '0 -5 10 10')\n      .attr('refX', 24)\n      .attr('refY', -0.25)\n      .attr('markerWidth', 3)\n      .attr('markerHeight', 3)\n      .attr('fill', '#454242')\n      .attr('orient', 'auto')\n      .append('svg:path')\n      .attr('d', 'M0,-5L10,0L0,5');\n\n    // add the links and the arrows\n    const path = this.svg.append('svg:g').selectAll('path')\n      .data(links)\n      .enter()\n      .append('svg:path')\n      .attr('stroke-width', 3)\n      .attr('stroke', '#454242')\n      .attr('marker-end', node => `url(#${node.source}/${node.target})`);\n\n    const nodeElements = this.svg.append('g')\n      .selectAll('circle')\n      .data(nodes)\n      .enter()\n      .append('circle')\n      .attr('r', defaultNodeRadius)\n      .attr('fill', 'steelblue')\n      .call(d3.drag()\n        .on('start', NetworkGraphBase.dragstarted)\n        .on('drag', NetworkGraphBase.dragged)\n        .on('end', NetworkGraphBase.dragended));\n\n    const textElements = this.svg.append('g')\n      .selectAll('text')\n      .data(nodes)\n      .enter()\n      .append('text')\n      .text(node => node.id)\n      .attr('font-size', 15)\n      .attr('dx', 20)\n      .attr('dy', 4);\n\n    simulation.nodes(nodes).on('tick', () => {\n      path\n        .attr('d', node => `M${node.source.x} ${node.source.y} L ${node.target.x} ${node.target.y}`);\n\n      nodeElements\n        .attr('cx', node => node.x)\n        .attr('cy', node => node.y);\n\n      textElements\n        .attr('x', node => node.x)\n        .attr('y', node => node.y);\n    });\n\n    simulation.force('link')\n      .links(links);\n  }\n\n  static dragstarted(event, d) {\n    if (!event.active) {\n      simulation.alphaTarget(0.3).restart();\n    }\n    d.x = event.x;\n    d.y = event.y;\n  }\n\n  static dragged(event, d) {\n    d.x = event.x;\n    d.y = event.y;\n  }\n\n  static dragended(event, d) {\n    if (!event.active) {\n      simulation.alphaTarget(0);\n    }\n    d.x = event.x;\n    d.y = event.y;\n  }\n\n  render() {\n    return (\n      <div>\n        <div className=\"network-graph-container\" />\n      </div>\n    );\n  }\n}\n\nNetworkGraphBase.defaultProps = {\n  deployments: [],\n};\n\nNetworkGraphBase.propTypes = {\n  data: PropTypes.arrayOf(metricsPropType.isRequired).isRequired,\n  /* these objects have a lot of stuff */\n  /* eslint-disable react/forbid-prop-types */\n  deployments: PropTypes.arrayOf(PropTypes.object),\n};\n\nexport default withREST(\n  withContext(NetworkGraphBase),\n  ({ api, namespace, deployments }) => {\n    return _map(deployments, d => {\n      return api.fetchMetrics(`${api.urlsForResource('deployment', namespace)}&to_name=${d.name}`);\n    });\n  },\n  {\n    poll: false,\n    resetProps: ['deployment'],\n  },\n);\n"
  },
  {
    "path": "web/app/js/components/NetworkGraph.test.jsx",
    "content": "import emojivotoPodFixtures from '../../test/fixtures/emojivotoPods.json';\nimport { NetworkGraphBase } from './NetworkGraph.jsx';\nimport React from 'react';\nimport { shallow } from 'enzyme';\n\nconst deploys = [\n  {name: \"emoji\", namespace: \"emojivoto\", totalRequests: 120, requestRate: 2, successRate: 1},\n  {name: \"vote-bot\", namespace: \"emojivoto\", totalRequests: 0, requestRate: null, successRate: null},\n  {name: \"voting\", namespace: \"emojivoto\", totalRequests: 59, requestRate: 0.9833333333333333, successRate: 0.7288135593220338},\n  {name: \"web\", namespace: \"emojivoto\", totalRequests: 117, requestRate: 1.95, successRate: 0.8803418803418803}\n];\n\ndescribe(\"NetworkGraph\", () => {\n\n  it(\"checks graph data\", () => {\n    const component = shallow(\n      <NetworkGraphBase\n        data={emojivotoPodFixtures}\n        deployments={deploys} />\n    );\n\n    const data = component.instance().getGraphData();\n    expect(data.links).toHaveLength(3);\n    expect(data.nodes).toHaveLength(4);\n    expect(data.links[0]).toEqual({source: \"web\", target: \"emoji\"});\n    expect(data.nodes[0]).toEqual({ id: \"web\"});\n  });\n});\n"
  },
  {
    "path": "web/app/js/components/NoMatch.jsx",
    "content": "import React from 'react';\nimport { Trans } from '@lingui/macro';\n\nconst NoMatch = function() {\n  return (\n    <div>\n      <h3>404</h3>\n      <div>\n        <Trans>\n          404Msg\n        </Trans>\n      </div>\n    </div>\n  );\n};\n\nexport default NoMatch;\n"
  },
  {
    "path": "web/app/js/components/Octopus.jsx",
    "content": "import Card from '@material-ui/core/Card';\nimport CardContent from '@material-ui/core/CardContent';\nimport Grid from '@material-ui/core/Grid';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport RootRef from '@material-ui/core/RootRef';\nimport Table from '@material-ui/core/Table';\nimport TableBody from '@material-ui/core/TableBody';\nimport TableCell from '@material-ui/core/TableCell';\nimport TableRow from '@material-ui/core/TableRow';\nimport Typography from '@material-ui/core/Typography';\nimport _get from 'lodash/get';\nimport _isEmpty from 'lodash/isEmpty';\nimport _isNil from 'lodash/isNil';\nimport _size from 'lodash/size';\nimport _slice from 'lodash/slice';\nimport _sortBy from 'lodash/sortBy';\nimport _take from 'lodash/take';\nimport { withStyles } from '@material-ui/core/styles';\nimport { getSuccessRateClassification } from './util/MetricUtils.jsx';\nimport { StyledProgress } from './util/Progress.jsx';\nimport { displayName, metricToFormatter } from './util/Utils.js';\nimport { OctopusArms, inboundAlignment } from './util/OctopusArms.jsx';\n\nconst maxNumNeighbors = 6; // max number of neighbor nodes to show in the octopus graph\n\nconst styles = () => ({\n  graphContainer: {\n    overflowX: 'auto',\n    padding: '16px 0',\n  },\n  graph: {\n    maxWidth: '974px',\n    minWidth: '974px',\n    marginLeft: 'auto',\n    marginRight: 'auto',\n  },\n  centerNode: {\n    width: '244px',\n  },\n  neighborNode: {\n    width: '220px',\n  },\n  collapsedNeighborName: {\n    paddingTop: '10px',\n  },\n});\nclass Octopus extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.upstreamsContainer = React.createRef();\n    this.downstreamsContainer = React.createRef();\n    this.upstreamsRefs = [];\n    this.downstreamsRefs = [];\n  }\n\n  static getNeighborDisplayData(neighbors) {\n    // only display maxNumNeighbors neighboring nodes in the octopus graph,\n    // otherwise it will be really tall\n    // even though _sortBy is a stable sort, the order that this data is returned by the API\n    // can change, so the original order of items can change; this means we have to sort by\n    // name and by SR to ensure an actual stable sort\n    const upstreams = _sortBy(_sortBy(neighbors.upstream, displayName), n => n.successRate);\n    const downstreams = _sortBy(_sortBy(neighbors.downstream, displayName), n => n.successRate);\n\n    const display = {\n      upstreams: {\n        displayed: upstreams,\n        collapsed: [],\n      },\n      downstreams: {\n        displayed: downstreams,\n        collapsed: [],\n      },\n    };\n\n    if (_size(upstreams) > maxNumNeighbors) {\n      display.upstreams.displayed = _take(upstreams, maxNumNeighbors);\n      display.upstreams.collapsed = _slice(upstreams, maxNumNeighbors, _size(upstreams));\n    }\n\n    if (_size(downstreams) > maxNumNeighbors) {\n      display.downstreams.displayed = _take(downstreams, maxNumNeighbors);\n      display.downstreams.collapsed = _slice(downstreams, maxNumNeighbors, _size(downstreams));\n    }\n\n    return display;\n  }\n\n  addElementToRefsList = (element, index, isOutbound, type = 'neighbor') => {\n    if (element && (type !== 'main')) {\n      if (isOutbound) {\n        this.downstreamsRefs[index] = element;\n      } else {\n        this.upstreamsRefs[index] = element;\n      }\n    }\n  };\n\n  linkedResourceTitle = (resource, display) => {\n    // trafficsplit leaf resources cannot be linked\n    if (_isNil(resource.namespace) || resource.isLeafService) { return display; }\n\n    const { api: { ResourceLink } } = this.props;\n    return <ResourceLink resource={resource} linkText={display} />;\n  };\n\n  renderResourceCard(resource, type, index, isOutbound) {\n    const { classes } = this.props;\n    const display = displayName(resource);\n    const classification = getSuccessRateClassification(resource.successRate);\n    const Progress = StyledProgress(classification);\n\n    // if the resource only has TCP stats, display those instead\n    let showTcp = false;\n    // trafficsplit leaf with zero traffic should still show HTTP stats\n    if (_isNil(resource.successRate) && _isNil(resource.requestRate) &&\n      resource.type !== 'service') {\n      showTcp = true;\n    }\n\n    return (\n      <RootRef rootRef={el => this.addElementToRefsList(el, index, isOutbound, type)} key={`${resource.type}-${resource.name}`}>\n        <Grid item>\n          <Card className={type === 'neighbor' ? classes.neighborNode : classes.centerNode} title={display}>\n            <CardContent>\n\n              <Typography variant={type === 'neighbor' ? 'subtitle1' : 'h6'} align=\"center\">\n                {this.linkedResourceTitle(resource, display)}\n              </Typography>\n\n              <Progress variant=\"determinate\" value={resource.successRate * 100} />\n\n              <Table>\n                {showTcp ? Octopus.renderTCPStats(resource) : Octopus.renderHttpStats(resource)}\n              </Table>\n            </CardContent>\n          </Card>\n        </Grid>\n      </RootRef>\n    );\n  }\n\n  static renderHttpStats(resource) {\n    return (\n      <TableBody>\n        {resource.isLeafService &&\n          <TableRow>\n            <TableCell><Typography>Weight</Typography></TableCell>\n            <TableCell align=\"right\"><Typography>{resource.tsStats.weight}</Typography></TableCell>\n          </TableRow>\n        }\n        <TableRow>\n          <TableCell><Typography>SR</Typography></TableCell>\n          <TableCell align=\"right\"><Typography>{metricToFormatter.SUCCESS_RATE(resource.successRate)}</Typography></TableCell>\n        </TableRow>\n        <TableRow>\n          <TableCell><Typography>RPS</Typography></TableCell>\n          <TableCell align=\"right\"><Typography>{metricToFormatter.NO_UNIT(resource.requestRate)}</Typography></TableCell>\n        </TableRow>\n        {!resource.isApexService &&\n          <TableRow>\n            <TableCell><Typography>P99</Typography></TableCell>\n            <TableCell align=\"right\"><Typography>{metricToFormatter.LATENCY(_get(resource, 'latency.P99'))}</Typography></TableCell>\n          </TableRow>\n        }\n      </TableBody>\n    );\n  }\n\n  static renderTCPStats(resource) {\n    const { tcp } = resource;\n    return (\n      <TableBody>\n        <TableRow>\n          <TableCell><Typography>Conn</Typography></TableCell>\n          <TableCell align=\"right\"><Typography>{metricToFormatter.NO_UNIT(tcp.openConnections)}</Typography></TableCell>\n        </TableRow>\n        <TableRow>\n          <TableCell><Typography>Read</Typography></TableCell>\n          <TableCell align=\"right\"><Typography>{metricToFormatter.BYTES(tcp.readRate)}</Typography></TableCell>\n        </TableRow>\n        <TableRow>\n          <TableCell><Typography>Write</Typography></TableCell>\n          <TableCell align=\"right\"><Typography>{metricToFormatter.BYTES(tcp.writeRate)}</Typography></TableCell>\n        </TableRow>\n      </TableBody>\n    );\n  }\n\n  renderUnmeshedResources = (unmeshedResources, index, isOutbound) => {\n    const { classes } = this.props;\n    return (\n      <RootRef rootRef={el => this.addElementToRefsList(el, index, isOutbound)}>\n        <Grid item>\n          <Card key=\"unmeshed-resources\" className={classes.neighborNode}>\n            <CardContent>\n              <Typography variant=\"subtitle1\">Unmeshed</Typography>\n              {\n                unmeshedResources.map(r => {\n                  const display = displayName(r);\n                  return <Typography key={display} variant=\"body2\" title={display}>{display}</Typography>;\n                })\n              }\n            </CardContent>\n          </Card>\n        </Grid>\n      </RootRef>\n    );\n  };\n\n  renderCollapsedNeighbors = (neighbors, index, isOutbound) => {\n    const { classes } = this.props;\n    const Progress = StyledProgress();\n    return (\n      <RootRef rootRef={el => this.addElementToRefsList(el, index, isOutbound)}>\n        <Grid item>\n          <Card className={classes.neighborNode}>\n            <CardContent>\n              <Typography variant=\"subtitle1\">\n                + {neighbors.length} more...\n              </Typography>\n              <Progress variant=\"determinate\" value={100} />\n              {\n                neighbors.map(r => {\n                  const display = displayName(r);\n                  return <Typography key={display} className={classes.collapsedNeighborName}>{this.linkedResourceTitle(r, display)}</Typography>;\n                })\n              }\n            </CardContent>\n          </Card>\n        </Grid>\n      </RootRef>\n    );\n  };\n\n  renderArrowCol = (numNeighbors, isOutbound) => {\n    const container = !isOutbound ? this.upstreamsContainer : this.downstreamsContainer;\n    const refs = !isOutbound ? this.upstreamsRefs : this.downstreamsRefs;\n    if (refs.length === 0 || container.current === undefined) {\n      return null;\n    }\n\n    const width = 80;\n    const fullHeight = container.current.offsetHeight;\n    let arrowTypes = [];\n    arrowTypes = refs.slice(0, numNeighbors).map(element => {\n      const elementTop = element.offsetTop - container.current.offsetTop;\n      const elementHeight = element.offsetHeight;\n      const halfElement = elementHeight / 2;\n\n      // Middle element\n      if (Math.round(fullHeight / 2) === Math.round(elementTop + halfElement)) {\n        const height = elementTop + halfElement;\n        return { type: 'flat', inboundType: 'flat', height };\n\n        // Elements underneath main element\n      } else if (elementTop + halfElement >= fullHeight / 2) {\n        const height = !isOutbound ? elementTop - fullHeight / 2 + halfElement - inboundAlignment : fullHeight - elementTop - halfElement + inboundAlignment;\n        return { type: 'down', inboundType: 'up', height, elementHeight };\n\n        // Elements over main element\n      } else {\n        const height = !isOutbound ? elementTop + halfElement + inboundAlignment : fullHeight / 2 - elementTop - halfElement - inboundAlignment;\n        return { type: 'up', inboundType: 'down', height, elementHeight };\n      }\n    });\n\n    const svg = (\n      <svg height={fullHeight} width={width} version=\"1.1\" viewBox={`0 0 ${width} ${fullHeight}`}>\n        <defs />\n        {\n          arrowTypes.map(arrow => {\n            const arrowType = isOutbound ? arrow.type : arrow.inboundType;\n            return OctopusArms[arrowType](width, fullHeight, arrow.height, isOutbound, arrow.elementHeight);\n          })\n        }\n      </svg>\n    );\n\n    return svg;\n  };\n\n  render() {\n    const { resource, neighbors, unmeshedSources, classes } = this.props;\n\n    if (_isEmpty(resource)) {\n      return null;\n    }\n\n    const display = Octopus.getNeighborDisplayData(neighbors);\n\n    const numUpstreams = _size(display.upstreams.displayed) + (_isEmpty(unmeshedSources) ? 0 : 1) +\n      (_isEmpty(display.upstreams.collapsed) ? 0 : 1);\n\n    const numDownstreams = _size(display.downstreams.displayed) + (_isEmpty(display.downstreams.collapsed) ? 0 : 1);\n\n    return (\n      <div className={classes.graphContainer}>\n        <div className={classes.graph}>\n          <Grid\n            container\n            direction=\"row\"\n            justifyContent=\"center\"\n            alignItems=\"center\">\n\n            <RootRef rootRef={this.upstreamsContainer}>\n              <Grid\n                container\n                spacing={3}\n                direction=\"column\"\n                justifyContent=\"center\"\n                alignItems=\"center\"\n                item\n                xs={3}>\n                {display.upstreams.displayed.map((n, index) => this.renderResourceCard(n, 'neighbor', index, false))}\n                {_isEmpty(unmeshedSources) ? null : this.renderUnmeshedResources(unmeshedSources, display.upstreams.displayed.length, false)}\n                {_isEmpty(display.upstreams.collapsed) ? null : this.renderCollapsedNeighbors(display.upstreams.collapsed, _isEmpty(unmeshedSources) ? display.upstreams.displayed.length : display.upstreams.displayed.length + 1, false)}\n              </Grid>\n            </RootRef>\n\n            <Grid item xs={1}>\n              {this.renderArrowCol(numUpstreams, false)}\n            </Grid>\n\n            <Grid item xs={3}>\n              {this.renderResourceCard(resource, 'main')}\n            </Grid>\n\n            <Grid item xs={1}>\n              {this.renderArrowCol(numDownstreams, true)}\n            </Grid>\n\n            <RootRef rootRef={this.downstreamsContainer}>\n              <Grid\n                container\n                spacing={3}\n                direction=\"column\"\n                justifyContent=\"center\"\n                alignItems=\"center\"\n                item\n                xs={3}>\n                {display.downstreams.displayed.map((n, index) => this.renderResourceCard(n, 'neighbor', index, true))}\n                {_isEmpty(display.downstreams.collapsed) ? null : this.renderCollapsedNeighbors(display.downstreams.collapsed, display.downstreams.displayed.length, true)}\n              </Grid>\n            </RootRef>\n          </Grid>\n        </div>\n      </div>\n    );\n  }\n}\n\nOctopus.propTypes = {\n  api: PropTypes.shape({\n    ResourceLink: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),\n  }),\n  neighbors: PropTypes.shape({}),\n  resource: PropTypes.shape({}),\n  unmeshedSources: PropTypes.arrayOf(PropTypes.shape({})),\n};\n\nOctopus.defaultProps = {\n  api: null,\n  neighbors: {},\n  resource: {},\n  unmeshedSources: [],\n};\n\nexport default withStyles(styles)(Octopus);\n"
  },
  {
    "path": "web/app/js/components/Popover.jsx",
    "content": "import Popover from '@material-ui/core/Popover';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { withStyles } from '@material-ui/core/styles';\n\nconst styles = theme => ({\n  paper: {\n    padding: theme.spacing(1),\n  },\n});\n\nclass ClickablePopover extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      anchorEl: null,\n    };\n  }\n\n  handleClick = event => {\n    this.setState({ anchorEl: event.currentTarget });\n  };\n\n  handleKeyPress = event => {\n    if (event.key === 'Enter') {\n      this.setState({ anchorEl: event.currentTarget });\n    }\n  };\n\n  handleClose = () => {\n    this.setState({ anchorEl: null });\n  };\n\n  render() {\n    const { classes, baseContent, popoverContent } = this.props;\n    const { anchorEl } = this.state;\n    const open = Boolean(anchorEl);\n\n    return (\n      <React.Fragment>\n        <span\n          aria-owns={open ? 'clickable-popover' : null}\n          aria-haspopup=\"true\"\n          onClick={this.handleClick}\n          onKeyPress={this.handleKeyPress}\n          role=\"button\"\n          tabIndex={0}>\n          {baseContent}\n        </span>\n        <Popover\n          id=\"clickable-popover\"\n          classes={{\n            paper: classes.paper,\n          }}\n          open={open}\n          anchorEl={anchorEl}\n          anchorOrigin={{\n            vertical: 'bottom',\n            horizontal: 'left',\n          }}\n          transformOrigin={{\n            vertical: 'top',\n            horizontal: 'left',\n          }}\n          onClose={this.handleClose}>\n          {popoverContent}\n        </Popover>\n      </React.Fragment>\n    );\n  }\n}\n\nClickablePopover.propTypes = {\n  baseContent: PropTypes.node.isRequired,\n  popoverContent: PropTypes.node.isRequired,\n};\n\nexport default withStyles(styles)(ClickablePopover);\n"
  },
  {
    "path": "web/app/js/components/QueryToCliCmd.jsx",
    "content": "import CardContent from '@material-ui/core/CardContent';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Trans } from '@lingui/macro';\nimport Typography from '@material-ui/core/Typography';\nimport _isEmpty from 'lodash/isEmpty';\nimport _startCase from 'lodash/startCase';\nimport { displayOrder } from './util/CliQueryUtils.js';\nimport { withContext } from './util/AppContext.jsx';\n\nconst toCliParam = {\n  namespace: '--namespace',\n  toResource: '--to',\n  toNamespace: '--to-namespace',\n  method: '--method',\n  path: '--path',\n  scheme: '--scheme',\n  authority: '--authority',\n  maxRps: '--max-rps',\n  from: '--from',\n  from_namespace: '--from-namespace',\n};\n\n/*\n  prints a given linkerd api query in an equivalent CLI format, such that it\n  could be pasted into a terminal\n*/\nclass QueryToCliCmd extends React.Component {\n  static renderCliItem(queryLabel, queryVal) {\n    return _isEmpty(queryVal) ? null : ` ${queryLabel} ${queryVal}`;\n  }\n\n  render() {\n    const { cmdName, query, resource, controllerNamespace } = this.props;\n\n    const cmdNameDisplay = _startCase(cmdName);\n\n    return (\n      _isEmpty(resource) ? null :\n      <CardContent>\n        <Typography variant=\"caption\" gutterBottom>\n          <Trans>Current {cmdNameDisplay} query</Trans>\n        </Typography>\n\n        <br />\n\n        <code>\n          linkerd viz {cmdName} {resource}\n          { displayOrder(cmdName, query).map(item => {\n            return !toCliParam[item] ? null : QueryToCliCmd.renderCliItem(toCliParam[item], query[item]);\n          })}\n          { controllerNamespace === 'linkerd' ? null : ` --linkerd-namespace ${controllerNamespace}`}\n        </code>\n      </CardContent>\n    );\n  }\n}\n\nQueryToCliCmd.propTypes = {\n  cmdName: PropTypes.string.isRequired,\n  controllerNamespace: PropTypes.string.isRequired,\n  query: PropTypes.shape({}).isRequired,\n  resource: PropTypes.string.isRequired,\n};\n\nexport default withContext(QueryToCliCmd);\n"
  },
  {
    "path": "web/app/js/components/QueryToCliCmd.test.jsx",
    "content": "import QueryToCliCmd from './QueryToCliCmd.jsx';\nimport React from 'react';\nimport { expect } from 'chai';\nimport { mount } from 'enzyme';\nimport { i18nWrap } from '../../test/testHelpers.jsx';\n\ndescribe('QueryToCliCmd', () => {\n  it('renders a query as a linkerd CLI command', () => {\n    let query = {\n      \"resource\": \"deploy/controller\",\n      \"namespace\": \"linkerd\",\n      \"scheme\": \"\"\n    }\n\n    let component = mount(i18nWrap(\n      <QueryToCliCmd\n        cmdName=\"routes\"\n        query={query}\n        resource={query.resource}\n        controllerNamespace=\"linkerd\" />)\n    );\n\n    expect(component.html()).to.include(\"Current Routes query\");\n    expect(component.html()).to.include(\"linkerd viz routes deploy/controller --namespace linkerd\");\n  });\n\n  it('shows the linkerd namespace if the controller is not in the default namespace', () => {\n    let query = {\n      \"resource\": \"deploy/controller\",\n      \"namespace\": \"linkerd\"\n    }\n\n    let component = mount(i18nWrap(\n      <QueryToCliCmd\n        cmdName=\"routes\"\n        query={query}\n        resource={query.resource}\n        controllerNamespace=\"my-linkerd-ns\" />)\n    );\n\n    expect(component.html()).to.include(\"Current Routes query\");\n    expect(component.html()).to.include(\"linkerd viz routes deploy/controller --namespace linkerd --linkerd-namespace my-linkerd-ns\");\n  });\n\n  it('does not render flags for items that are not populated in the query', () => {\n    let query = {\n      \"resource\": \"deploy/controller\",\n      \"namespace\": \"linkerd\",\n      \"scheme\": \"\",\n      \"maxRps\": \"\",\n      \"authority\": \"foo.bar:8080\"\n    }\n\n    let component = mount(i18nWrap(\n      <QueryToCliCmd\n        cmdName=\"tap\"\n        query={query}\n        resource={query.resource}\n        controllerNamespace=\"linkerd\" />)\n    );\n\n    expect(component.html()).to.include(\"Current Tap query\");\n    expect(component.html()).to.include(\"linkerd viz tap deploy/controller --namespace linkerd --authority foo.bar:8080\");\n  });\n\n  it('displays the flags in the specified order per cli command', () => {\n    let query = {\n      \"resource\": \"deploy/controller\",\n      \"namespace\": \"linkerd\",\n      \"scheme\": \"HTTPS\",\n      \"maxRps\": \"\",\n      \"toResource\": \"deploy/prometheus\",\n      \"authority\": \"foo.bar:8080\"\n    }\n\n    let component = mount(i18nWrap(\n      <QueryToCliCmd\n        cmdName=\"tap\"\n        query={query}\n        resource={query.resource}\n        controllerNamespace=\"linkerd\" />)\n    );\n\n    expect(component.html()).to.include(\"Current Tap query\");\n    expect(component.html()).to.include(\"linkerd viz tap deploy/controller --namespace linkerd --to deploy/prometheus --scheme HTTPS --authority foo.bar:8080\");\n  });\n\n  it(\"doesn't render a namespace flag when the resource is a namespace\", () => {\n    let query = {\n      \"resource\": \"namespace/linkerd\",\n      \"namespace\": \"linkerd\"\n    }\n\n    let component = mount(i18nWrap(\n      <QueryToCliCmd\n        cmdName=\"top\"\n        query={query}\n        resource={query.resource}\n        controllerNamespace=\"linkerd\" />)\n    );\n\n    expect(component.html()).to.include(\"Current Top query\");\n    expect(component.html()).to.include(\"linkerd viz top namespace/linkerd\");\n  });\n\n  it(\"doesn't render commands for which a flag is not defined\", () => {\n      let query = {\n        \"resource\": \"deploy/controller\",\n        \"namespace\": \"linkerd\",\n        \"scheme\": \"HTTPS\",\n        \"theLimitDoesNotExist\": 999\n      }\n\n      let component = mount(i18nWrap(\n        <QueryToCliCmd\n          cmdName=\"tap\"\n          query={query}\n          resource={query.resource}\n          controllerNamespace=\"linkerd\" />)\n      );\n\n      expect(component.html()).to.include(\"Current Tap query\");\n      expect(component.html()).to.include(\"linkerd viz tap deploy/controller --namespace linkerd --scheme HTTPS\");\n  });\n});\n"
  },
  {
    "path": "web/app/js/components/ResourceDetail.jsx",
    "content": "import 'whatwg-fetch';\nimport Grid from '@material-ui/core/Grid';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Trans } from '@lingui/macro';\nimport Typography from '@material-ui/core/Typography';\nimport _filter from 'lodash/filter';\nimport _get from 'lodash/get';\nimport _indexOf from 'lodash/indexOf';\nimport _isEmpty from 'lodash/isEmpty';\nimport _isEqual from 'lodash/isEqual';\nimport _isNil from 'lodash/isNil';\nimport _merge from 'lodash/merge';\nimport _reduce from 'lodash/reduce';\nimport TopRoutesTabs from './TopRoutesTabs.jsx';\nimport Spinner from './util/Spinner.jsx';\nimport SimpleChip from './util/Chip.jsx';\nimport ServiceDetail from './ServiceDetail.jsx';\nimport Octopus from './Octopus.jsx';\nimport MetricsTable from './MetricsTable.jsx';\nimport ErrorBanner from './ErrorBanner.jsx';\nimport EdgesTable from './EdgesTable.jsx';\nimport AddResources from './AddResources.jsx';\nimport { resourceTypeToCamelCase, singularResource } from './util/Utils.js';\nimport { handlePageVisibility, withPageVisibility } from './util/PageVisibility.jsx';\nimport { emptyMetric, processMultiResourceRollup, processSingleResourceRollup } from './util/MetricUtils.jsx';\nimport { processEdges } from './util/EdgesUtils.jsx';\nimport { withContext } from './util/AppContext.jsx';\n\n// if there has been no traffic for some time, show a warning\nconst showNoTrafficMsgDelayMs = 6000;\n// resource types supported when querying API for edge data\nconst edgeDataAvailable = ['cronjob', 'daemonset', 'deployment', 'job', 'pod', 'replicaset', 'replicationcontroller', 'statefulset'];\n\nconst getResourceFromUrl = (match, pathPrefix) => {\n  const resource = {\n    namespace: match.params.namespace,\n  };\n  const regExp = RegExp(`${pathPrefix || ''}/namespaces/${match.params.namespace}/([^/]+)/([^/]+)`);\n  const urlParts = match.url.match(regExp);\n\n  resource.type = singularResource(urlParts[1]);\n  resource.name = urlParts[2];\n\n  if (match.params[resource.type] !== resource.name) {\n    console.error('Failed to extract resource from URL');\n  }\n  return resource;\n};\n\nexport class ResourceDetailBase extends React.Component {\n  constructor(props) {\n    super(props);\n    this.api = props.api;\n    this.unmeshedSources = {};\n    this.handleApiError = this.handleApiError.bind(this);\n    this.loadFromServer = this.loadFromServer.bind(this);\n    this.state = this.getInitialState(props.match, props.pathPrefix);\n  }\n\n  getInitialState(match, pathPrefix) {\n    const resource = getResourceFromUrl(match, pathPrefix);\n    return {\n      namespace: resource.namespace,\n      resourceName: resource.name,\n      resourceType: resource.type,\n      lastMetricReceivedTime: Date.now(),\n      isTcpOnly: false, // whether this resource only has TCP traffic\n      pollingInterval: 2000,\n      resourceMetrics: [],\n      podMetrics: [], // metrics for all pods whose owner is this resource\n      upstreamMetrics: {}, // metrics for resources who send traffic to this resource\n      downstreamMetrics: {}, // metrics for resources who this resource sends traffic to\n      unmeshedSources: {},\n      resourceIsMeshed: true,\n      pendingRequests: false,\n      loaded: false,\n      error: null,\n      resourceDefinition: null,\n      // queryForDefinition is set to false now due to we are not currently using\n      // resource definition. This can change in the future\n      queryForDefinition: false,\n    };\n  }\n\n  componentDidMount() {\n    this.startServerPolling();\n  }\n\n  componentDidUpdate(prevProps) {\n    const { match, pathPrefix, isPageVisible } = this.props;\n\n    if (!_isEqual(prevProps.match.url, match.url)) {\n      // React won't unmount this component when switching resource pages so we need to clear state\n      this.api.cancelCurrentRequests();\n      this.unmeshedSources = {};\n      this.resetState(match, pathPrefix);\n    }\n\n    handlePageVisibility({\n      prevVisibilityState: prevProps.isPageVisible,\n      currentVisibilityState: isPageVisible,\n      onVisible: () => this.startServerPolling(),\n      onHidden: () => this.stopServerPolling(),\n    });\n  }\n\n  resetState(match, pathPrefix) {\n    this.setState(this.getInitialState(match, pathPrefix));\n  }\n\n  componentWillUnmount() {\n    this.stopServerPolling();\n  }\n\n  // if we're displaying a pod detail page, only display pod metrics\n  // if we're displaying another type of resource page, display metrics for\n  // rcs, deploys, replicasets, etc but not pods or authorities\n  getDisplayMetrics(metricsByResource) {\n    const { resourceType } = this.state;\n    const shouldExclude = resourceType === 'pod' ?\n      r => r !== 'pod' :\n      r => r === 'pod' || r === 'authority' || r === 'service';\n    return _reduce(metricsByResource, (mem, resourceMetrics, resource) => {\n      if (shouldExclude(resource)) {\n        return mem;\n      }\n      return mem.concat(resourceMetrics);\n    }, []);\n  }\n\n  startServerPolling() {\n    const { pollingInterval } = this.state;\n    this.loadFromServer();\n    this.timerId = window.setInterval(this.loadFromServer, pollingInterval);\n  }\n\n  stopServerPolling() {\n    window.clearInterval(this.timerId);\n    this.api.cancelCurrentRequests();\n    this.setState({ pendingRequests: false });\n  }\n\n  loadFromServer() {\n    const { pendingRequests, queryForDefinition, resourceType, namespace, resourceName, resourceDefinition, lastMetricReceivedTime } = this.state;\n\n    if (pendingRequests) {\n      return; // don't make more requests if the ones we sent haven't completed\n    }\n    this.setState({ pendingRequests: true });\n\n    let apiRequests =\n      [\n        // inbound stats for this resource\n        this.api.fetchMetrics(\n          `${this.api.urlsForResource(resourceType, namespace, true)}&resource_name=${resourceName}`,\n        ),\n        // upstream resources of this resource (meshed traffic only)\n        this.api.fetchMetrics(\n          `${this.api.urlsForResource('all')}&to_name=${resourceName}&to_type=${resourceType}&to_namespace=${namespace}`,\n        ),\n        // downstream resources of this resource (meshed traffic only)\n        this.api.fetchMetrics(\n          `${this.api.urlsForResource('all')}&from_name=${resourceName}&from_type=${resourceType}&from_namespace=${namespace}`,\n        ),\n      ];\n\n    // Fetch pods in a resource and their metrics (except when resource type is pod)\n    if (resourceType !== 'pod') {\n      // list of all pods in this namespace (hack since we can't currently query for all pods in a resource)\n      apiRequests.push(this.api.fetchPods(namespace));\n      // metrics for all pods in this namespace (hack, continued)\n      apiRequests.push(this.api.fetchMetrics(`${this.api.urlsForResource('pod', namespace, true)}`));\n    }\n\n    if (queryForDefinition) {\n      // definition for this resource\n      apiRequests.push(this.api.fetchResourceDefinition(namespace, resourceType, resourceName));\n    }\n\n    if (_indexOf(edgeDataAvailable, resourceType) > 0) {\n      apiRequests = apiRequests.concat([\n        this.api.fetchEdges(namespace, resourceType),\n      ]);\n    }\n\n    this.api.setCurrentRequests(apiRequests);\n\n    Promise.all(this.api.getCurrentPromises())\n      .then(apiResponses => {\n        let podMetrics;\n        let resourceRsp;\n        let upstreamRsp;\n        let downstreamRsp;\n        let podListRsp;\n        let podMetricsRsp;\n        let rsp;\n\n        if (resourceType === 'pod') {\n          [resourceRsp, upstreamRsp, downstreamRsp, ...rsp] = [...apiResponses];\n        } else {\n          [resourceRsp, upstreamRsp, downstreamRsp, podListRsp, podMetricsRsp, ...rsp] = [...apiResponses];\n          podMetrics = processSingleResourceRollup(podMetricsRsp, resourceType);\n        }\n\n        const resourceMetrics = processSingleResourceRollup(resourceRsp, resourceType);\n        const upstreamMetrics = processMultiResourceRollup(upstreamRsp, resourceType);\n        const downstreamMetrics = processMultiResourceRollup(downstreamRsp, resourceType);\n        const newResourceDefinition = queryForDefinition ? rsp[0] : resourceDefinition;\n\n        let edges = [];\n        if (_indexOf(edgeDataAvailable, resourceType) > 0) {\n          const edgesRsp = rsp[rsp.length - 1];\n          edges = processEdges(edgesRsp, resourceName);\n        }\n\n        // INEFFICIENT: get metrics for all the pods belonging to this resource.\n        // Do this by querying for metrics for all pods in this namespace and then filtering\n        // out those pods whose owner is not this resource\n        // TODO: fix (#1467)\n        const resourceKey = `${namespace}/${resourceName}`;\n        let podMetricsForResource;\n\n        if (resourceType === 'pod') {\n          podMetricsForResource = resourceMetrics;\n        } else {\n          const podBelongsToResource = _reduce(podListRsp.pods, (mem, pod) => {\n            if (_get(pod, resourceTypeToCamelCase(resourceType)) === resourceKey) {\n              // pod.name in podListRsp is of the form `namespace/pod-name`\n              mem[pod.name] = true;\n            }\n\n            return mem;\n          }, {});\n\n          // get all pods whose owner is this resource\n          podMetricsForResource = _filter(podMetrics, pod => podBelongsToResource[`${pod.namespace}/${pod.name}`]);\n        }\n\n        let resourceIsMeshed = true;\n        if (!_isEmpty(resourceMetrics)) {\n          resourceIsMeshed = _get(resourceMetrics, '[0].pods.meshedPods') > 0;\n        }\n\n        let hasHttp = false;\n        let hasTcp = false;\n        const metric = resourceMetrics[0];\n        if (!_isEmpty(metric)) {\n          hasHttp = !_isNil(metric.requestRate) && !_isEmpty(metric.latency);\n\n          if (!_isEmpty(metric.tcp)) {\n            const { tcp } = metric;\n            hasTcp = tcp.openConnections > 0 || tcp.readBytes > 0 || tcp.writeBytes > 0;\n          }\n        }\n\n        const isTcpOnly = !hasHttp && hasTcp;\n\n        // figure out when the last traffic this resource received was so we can show a no traffic message\n        let newLastMetricReceivedTime = lastMetricReceivedTime;\n        if (hasHttp || hasTcp) {\n          newLastMetricReceivedTime = Date.now();\n        }\n\n        this.setState({\n          resourceMetrics,\n          resourceIsMeshed,\n          podMetrics: podMetricsForResource,\n          upstreamMetrics,\n          downstreamMetrics,\n          edges,\n          lastMetricReceivedTime: newLastMetricReceivedTime,\n          isTcpOnly,\n          loaded: true,\n          pendingRequests: false,\n          error: null,\n          unmeshedSources: this.unmeshedSources, // in place of debouncing, just update this when we update the rest of the state\n          resourceDefinition: newResourceDefinition,\n        });\n      })\n      .catch(this.handleApiError);\n  }\n\n  handleApiError = e => {\n    if (e.isCanceled) {\n      return;\n    }\n\n    this.setState({\n      loaded: true,\n      pendingRequests: false,\n      error: e,\n    });\n  };\n\n  updateUnmeshedSources = obj => {\n    this.unmeshedSources = obj;\n  };\n\n  banner = () => {\n    const { error } = this.state;\n    return error ? <ErrorBanner message={error} /> : null;\n  };\n\n  content = () => {\n    const {\n      resourceName,\n      resourceType,\n      namespace,\n      resourceMetrics,\n      edges,\n      unmeshedSources,\n      resourceIsMeshed,\n      lastMetricReceivedTime,\n      isTcpOnly,\n      loaded,\n      error,\n      upstreamMetrics,\n      downstreamMetrics,\n      podMetrics,\n    } = this.state;\n    const { pathPrefix } = this.props;\n\n    if (!loaded && !error) {\n      return <Spinner />;\n    }\n\n    const query = {\n      resourceName,\n      resourceType,\n      namespace,\n    };\n\n    const unmeshed = _filter(unmeshedSources, d => d.type !== 'pod')\n      .map(d => _merge({}, emptyMetric, d, {\n        unmeshed: true,\n        pods: {\n          totalPods: d.pods.length,\n          meshedPods: 0,\n        },\n      }));\n\n    const upstreamDisplayMetrics = this.getDisplayMetrics(upstreamMetrics);\n    const downstreamDisplayMetrics = this.getDisplayMetrics(downstreamMetrics);\n\n    const upstreams = upstreamDisplayMetrics.concat(unmeshed);\n\n    const showNoTrafficMsg = resourceIsMeshed && (Date.now() - lastMetricReceivedTime > showNoTrafficMsgDelayMs);\n\n    if (resourceType === 'service') {\n      return (\n        <ServiceDetail\n          resourceName={resourceName}\n          upstreams={upstreams}\n          api={this.api}\n          upstreamDisplayMetrics={upstreamDisplayMetrics}\n          pathPrefix={pathPrefix}\n          isTcpOnly={isTcpOnly}\n          resourceMetrics={resourceMetrics}\n          unmeshedSources={unmeshedSources}\n          query={query}\n          updateUnmeshedSources={this.updateUnmeshedSources} />\n      );\n    }\n    return (\n      <div>\n        <Grid container justifyContent=\"space-between\" alignItems=\"center\">\n          <Grid item><Typography variant=\"h5\">{resourceType}/{resourceName}</Typography></Grid>\n          <Grid item>\n            <Grid container spacing={1}>\n              {showNoTrafficMsg ? <Grid item><SimpleChip label={<Trans>columnTitleNoTraffic</Trans>} type=\"warning\" /></Grid> : null}\n              <Grid item>\n                {resourceIsMeshed ?\n                  <SimpleChip label={<Trans>columnTitleMeshed</Trans>} type=\"good\" /> :\n                  <SimpleChip label={<Trans>columnTitleUnmeshed</Trans>} type=\"bad\" />\n                }\n              </Grid>\n            </Grid>\n          </Grid>\n        </Grid>\n\n        {\n          resourceIsMeshed ? null :\n          <AddResources\n            resourceName={resourceName}\n            resourceType={resourceType} />\n        }\n\n        <Octopus\n          resource={resourceMetrics[0]}\n          neighbors={{ upstream: upstreamDisplayMetrics, downstream: downstreamDisplayMetrics }}\n          unmeshedSources={Object.values(unmeshedSources)}\n          api={this.api} />\n\n        {isTcpOnly ? null : <TopRoutesTabs\n          query={query}\n          pathPrefix={pathPrefix}\n          updateUnmeshedSources={this.updateUnmeshedSources}\n          disableTop={!resourceIsMeshed} />\n        }\n\n        {_isEmpty(upstreams) ? null :\n        <MetricsTable\n          resource=\"multi_resource\"\n          title={<Trans>tableTitleInbound</Trans>}\n          metrics={upstreamDisplayMetrics} />\n        }\n\n        {_isEmpty(downstreamDisplayMetrics) ? null :\n        <MetricsTable\n          resource=\"multi_resource\"\n          title={<Trans>tableTitleOutbound</Trans>}\n          metrics={downstreamDisplayMetrics} />\n        }\n\n        {\n          resourceType === 'pod' || isTcpOnly ? null :\n          <MetricsTable\n            resource=\"pod\"\n            title={<Trans>tableTitlePods</Trans>}\n            metrics={podMetrics} />\n        }\n\n        <MetricsTable\n          resource=\"pod\"\n          title={<Trans>tableTitleTCP</Trans>}\n          isTcpTable\n          metrics={podMetrics} />\n\n        <EdgesTable\n          api={this.api}\n          namespace={namespace}\n          type={resourceType}\n          edges={edges} />\n\n      </div>\n    );\n  };\n\n  render() {\n    return (\n      <div className=\"page-content\">\n        <div>\n          {this.banner()}\n          {this.content()}\n        </div>\n      </div>\n    );\n  }\n}\n\nResourceDetailBase.propTypes = {\n  api: PropTypes.shape({\n    PrefixedLink: PropTypes.func.isRequired,\n  }).isRequired,\n  isPageVisible: PropTypes.bool.isRequired,\n  match: PropTypes.shape({\n    url: PropTypes.string.isRequired,\n  }).isRequired,\n  pathPrefix: PropTypes.string.isRequired,\n};\n\nexport default withPageVisibility(withContext(ResourceDetailBase));\n"
  },
  {
    "path": "web/app/js/components/ResourceList.jsx",
    "content": "import 'whatwg-fetch';\n\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Trans } from '@lingui/macro';\nimport { metricsPropType, processSingleResourceRollup } from './util/MetricUtils.jsx';\n\nimport ErrorBanner from './ErrorBanner.jsx';\nimport MetricsTable from './MetricsTable.jsx';\nimport Spinner from './util/Spinner.jsx';\nimport { apiErrorPropType } from './util/ApiHelpers.jsx';\nimport withREST from './util/withREST.jsx';\n\nexport class ResourceListBase extends React.Component {\n  banner = () => {\n    const { error } = this.props;\n    return error ? <ErrorBanner message={error} /> : null;\n  };\n\n  content = () => {\n    const { data, loading, error, resource } = this.props;\n\n    if (loading && !error) {\n      return <Spinner />;\n    }\n\n    let processedMetrics = [];\n    if (data.length === 1) {\n      processedMetrics = processSingleResourceRollup(data[0], resource);\n    }\n\n    return (\n      <React.Fragment>\n        <MetricsTable\n          resource={resource}\n          metrics={processedMetrics}\n          title={<Trans>tableTitleHTTPMetrics</Trans>} />\n\n        <MetricsTable\n          resource={resource}\n          isTcpTable\n          metrics={processedMetrics}\n          title={<Trans>tableTitleTCPMetrics</Trans>} />\n      </React.Fragment>\n    );\n  };\n\n  render() {\n    return (\n      <div className=\"page-content\">\n        <div>\n          {this.banner()}\n          {this.content()}\n        </div>\n      </div>\n    );\n  }\n}\n\nResourceListBase.propTypes = {\n  data: PropTypes.arrayOf(metricsPropType.isRequired).isRequired,\n  error: apiErrorPropType,\n  loading: PropTypes.bool.isRequired,\n  resource: PropTypes.string.isRequired,\n};\n\nResourceListBase.defaultProps = {\n  error: null,\n};\n\n// When constructing a ResourceList for type \"namespace\", we query the API for metrics for all namespaces. For all other resource types, we limit our API query to the selectedNamespace.\nexport default withREST(\n  ResourceListBase,\n  ({ api, resource, selectedNamespace }) => [api.fetchMetrics(api.urlsForResource(resource, resource === 'namespace' ? 'all' : selectedNamespace, true))],\n  {\n    resetProps: ['resource', 'selectedNamespace'],\n  },\n);\n"
  },
  {
    "path": "web/app/js/components/ResourceList.test.jsx",
    "content": "import deployRollup from '../../test/fixtures/deployRollup.json';\nimport ErrorBanner from './ErrorBanner.jsx';\nimport MetricsTable from './MetricsTable.jsx';\nimport React from 'react';\nimport { ResourceListBase } from './ResourceList.jsx';\nimport Spinner from './util/Spinner.jsx';\nimport { shallow } from 'enzyme';\n\ndescribe('Tests for <ResourceListBase>', () => {\n  const defaultProps = {\n    resource: 'pods',\n  };\n\n  it('displays an error if the api call fails', () => {\n    const msg = 'foobar';\n\n    const component = shallow(\n      <ResourceListBase\n        {...defaultProps}\n        data={[]}\n        error={{ statusText: msg}}\n        loading={false} />\n    );\n\n    const err = component.find(ErrorBanner);\n    expect(err).toHaveLength(1);\n    expect(component.find(Spinner)).toHaveLength(0);\n    expect(component.find(MetricsTable)).toHaveLength(2);\n    expect(err.props().message.statusText).toEqual(msg);\n  });\n\n  it('shows a loading spinner', () => {\n    const component = shallow(\n      <ResourceListBase\n        {...defaultProps}\n        data={[]}\n        loading={true} />\n    );\n\n    expect(component.find(ErrorBanner)).toHaveLength(0);\n    expect(component.find(Spinner)).toHaveLength(1);\n    expect(component.find(MetricsTable)).toHaveLength(0);\n  });\n\n  it('handles empty content', () => {\n    const component = shallow(\n      <ResourceListBase\n        {...defaultProps}\n        data={[]}\n        loading={false} />\n    );\n\n    expect(component.find(ErrorBanner)).toHaveLength(0);\n    expect(component.find(Spinner)).toHaveLength(0);\n    expect(component.find(MetricsTable)).toHaveLength(2);\n  });\n\n  it('renders a metrics table', () => {\n    const resource = 'deployment';\n    const component = shallow(\n      <ResourceListBase\n        {...defaultProps}\n        data={[deployRollup]}\n        loading={false}\n        resource={resource} />\n    );\n\n    const metrics = component.find(MetricsTable);\n\n    expect(component.find(ErrorBanner)).toHaveLength(0);\n    expect(component.find(Spinner)).toHaveLength(0);\n    expect(metrics).toHaveLength(2);\n\n    expect(metrics.at(0).props().resource).toEqual(resource);\n    expect(metrics.at(1).props().resource).toEqual(resource);\n    expect(metrics.at(0).props().metrics).toHaveLength(1);\n    expect(metrics.at(1).props().metrics).toHaveLength(1);\n  });\n});\n"
  },
  {
    "path": "web/app/js/components/ServiceDetail.jsx",
    "content": "import Grid from '@material-ui/core/Grid';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Trans } from '@lingui/macro';\nimport Typography from '@material-ui/core/Typography';\nimport _isEmpty from 'lodash/isEmpty';\nimport _filter from 'lodash/filter';\nimport TopRoutesTabs, { topRoutesQueryPropType } from './TopRoutesTabs.jsx';\nimport Octopus from './Octopus.jsx';\nimport MetricsTable from './MetricsTable.jsx';\n\nconst getResourceForService = (resourceMetrics, serviceName) => {\n  if (resourceMetrics.length === 1) {\n    return resourceMetrics[0];\n  }\n  const relevantResources = _filter(resourceMetrics, rm => {\n    return !rm.tsStats || rm.tsStats.leaf === serviceName;\n  });\n\n  if (relevantResources.length >= 1) {\n    return relevantResources[0];\n  }\n\n  return resourceMetrics[0];\n};\n\nconst ServiceDetail = function({\n  api,\n  resourceMetrics,\n  resourceName,\n  query,\n  isTcpOnly,\n  pathPrefix,\n  upstreamDisplayMetrics,\n  unmeshedSources,\n  updateUnmeshedSources,\n  upstreams,\n}) {\n  return (\n    <div>\n      <Grid container justifyContent=\"space-between\" alignItems=\"center\">\n        <Grid item><Typography variant=\"h5\">service/{resourceName}</Typography></Grid>\n      </Grid>\n\n      <Octopus\n        resource={getResourceForService(resourceMetrics)}\n        neighbors={{ upstream: upstreamDisplayMetrics }}\n        unmeshedSources={Object.values(unmeshedSources)}\n        api={api} />\n\n      {isTcpOnly ? null : <TopRoutesTabs\n        query={query}\n        pathPrefix={pathPrefix}\n        updateUnmeshedSources={updateUnmeshedSources}\n        disableTop />\n      }\n\n      {_isEmpty(upstreams) ? null :\n      <MetricsTable\n        resource=\"multi_resource\"\n        title={<Trans>tableTitleInbound</Trans>}\n        metrics={upstreamDisplayMetrics} />\n      }\n\n      {resourceMetrics.length <= 1 ? null :\n      <MetricsTable\n        resource=\"service\"\n        title={<Trans>tableTitleOutbound</Trans>}\n        metrics={resourceMetrics} />\n       }\n\n    </div>\n  );\n};\n\nServiceDetail.propTypes = {\n  api: PropTypes.shape({\n    PrefixedLink: PropTypes.func.isRequired,\n  }).isRequired,\n  resourceMetrics: PropTypes.arrayOf(PropTypes.shape({})).isRequired,\n  resourceName: PropTypes.string.isRequired,\n  query: topRoutesQueryPropType.isRequired,\n  isTcpOnly: PropTypes.bool.isRequired,\n  pathPrefix: PropTypes.string.isRequired,\n  upstreamDisplayMetrics: PropTypes.arrayOf(PropTypes.shape({})).isRequired,\n  unmeshedSources: PropTypes.shape({}).isRequired,\n  updateUnmeshedSources: PropTypes.func.isRequired,\n  upstreams: PropTypes.arrayOf(PropTypes.shape({})).isRequired,\n};\n\nexport default ServiceDetail;\n"
  },
  {
    "path": "web/app/js/components/ServiceMesh.jsx",
    "content": "import { Plural, Trans } from '@lingui/macro';\nimport { formatDistanceToNow, subSeconds } from 'date-fns';\nimport Card from '@material-ui/core/Card';\nimport CardContent from '@material-ui/core/CardContent';\nimport Grid from '@material-ui/core/Grid';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Typography from '@material-ui/core/Typography';\nimport _compact from 'lodash/compact';\nimport _filter from 'lodash/filter';\nimport _get from 'lodash/get';\nimport _groupBy from 'lodash/groupBy';\nimport _isEmpty from 'lodash/isEmpty';\nimport _map from 'lodash/map';\nimport _mapKeys from 'lodash/mapKeys';\nimport _sumBy from 'lodash/sumBy';\nimport { withStyles } from '@material-ui/core/styles';\nimport { incompleteMeshMessage } from './util/CopyUtils.jsx';\nimport { withContext } from './util/AppContext.jsx';\nimport StatusTable from './StatusTable.jsx';\nimport Spinner from './util/Spinner.jsx';\nimport Percentage from './util/Percentage.js';\nimport MeshedStatusTable from './MeshedStatusTable.jsx';\nimport ErrorBanner from './ErrorBanner.jsx';\nimport CheckModal from './CheckModal.jsx';\nimport CallToAction from './CallToAction.jsx';\nimport BaseTable from './BaseTable.jsx';\nimport { handlePageVisibility, withPageVisibility } from './util/PageVisibility.jsx';\n\nconst styles = {\n  checkModalWrapper: {\n    width: '100%',\n  },\n};\n\nconst installedExtensionsColumn = [\n  {\n    title: <Trans>columnTitleName</Trans>,\n    dataIndex: 'name',\n  },\n  {\n    title: <Trans>columnTitleNamespace</Trans>,\n    dataIndex: 'namespace',\n  },\n];\n\nconst serviceMeshDetailsColumns = [\n  {\n    title: <Trans>columnTitleName</Trans>,\n    dataIndex: 'name',\n  },\n  {\n    title: <Trans>columnTitleValue</Trans>,\n    dataIndex: 'value',\n    isNumeric: true,\n  },\n];\n\nconst getPodClassification = pod => {\n  if (pod.status === 'Running') {\n    return 'good';\n  } else if (pod.status === 'Waiting') {\n    return 'default';\n  } else {\n    return 'poor';\n  }\n};\n\nconst componentsToDeployNames = {\n  Destination: 'linkerd-destination',\n  Identity: 'linkerd-identity',\n  'Proxy Injector': 'linkerd-proxy-injector',\n};\n\nclass ServiceMesh extends React.Component {\n  constructor(props) {\n    super(props);\n    this.loadFromServer = this.loadFromServer.bind(this);\n    this.handleApiError = this.handleApiError.bind(this);\n    this.api = props.api;\n\n    this.state = {\n      pollingInterval: 2000,\n      components: [],\n      extensions: [],\n      nsStatuses: [],\n      pendingRequests: false,\n      loaded: false,\n      error: null,\n    };\n  }\n\n  componentDidMount() {\n    this.startServerPolling();\n    this.fetchAllInstalledExtensions();\n  }\n\n  componentDidUpdate(prevProps) {\n    const { isPageVisible } = this.props;\n    handlePageVisibility({\n      prevVisibilityState: prevProps.isPageVisible,\n      currentVisibilityState: isPageVisible,\n      onVisible: () => this.startServerPolling(),\n      onHidden: () => this.stopServerPolling(),\n    });\n  }\n\n  componentWillUnmount() {\n    this.stopServerPolling();\n  }\n\n  getServiceMeshDetails() {\n    const { components } = this.state;\n    const { productName, releaseVersion, controllerNamespace } = this.props;\n\n    return [\n      { key: 1, name: <Trans>{productName} version</Trans>, value: releaseVersion },\n      { key: 2, name: <Trans>{productName} namespace</Trans>, value: controllerNamespace },\n      { key: 3, name: <Trans>Control plane components</Trans>, value: components.length },\n      { key: 4, name: <Trans>Data plane proxies</Trans>, value: this.proxyCount() },\n    ];\n  }\n\n  getInstalledExtensions() {\n    const { extensions } = this.state;\n    const extensionList = !_isEmpty(extensions.extensions) ? extensions.extensions : [];\n    return extensionList;\n  }\n\n  static getControllerComponentData(podData) {\n    const podDataByDeploy = _groupBy(_filter(podData.pods, d => d.controlPlane), p => p.deployment);\n    const byDeployName = _mapKeys(podDataByDeploy, (_pods, dep) => dep.split('/')[1]);\n\n    return _map(componentsToDeployNames, (deployName, component) => {\n      return {\n        name: component,\n        pods: _map(byDeployName[deployName], p => {\n          const uptimeSec = !p.uptime ? 0 : parseInt(p.uptime.split('.')[0], 10);\n          const uptime = formatDistanceToNow(subSeconds(Date.now(), uptimeSec));\n\n          return {\n            name: p.name,\n            value: getPodClassification(p),\n            uptime,\n            uptimeSec,\n          };\n        }),\n      };\n    });\n  }\n\n  startServerPolling() {\n    const { pollingInterval } = this.state;\n    this.loadFromServer();\n    this.timerId = window.setInterval(this.loadFromServer, pollingInterval);\n  }\n\n  stopServerPolling() {\n    window.clearInterval(this.timerId);\n    this.api.cancelCurrentRequests();\n    this.setState({ pendingRequests: false });\n  }\n\n  static extractNsStatuses(nsData) {\n    const podsByNs = _get(nsData, ['ok', 'statTables', 0, 'podGroup', 'rows'], []);\n    const dataPlaneNamespaces = podsByNs.map(ns => {\n      const meshedPods = parseInt(ns.meshedPodCount, 10);\n      const totalPods = parseInt(ns.runningPodCount, 10);\n      const failedPods = parseInt(ns.failedPodCount, 10);\n\n      return {\n        namespace: ns.resource.name,\n        meshedPodsStr: `${ns.meshedPodCount}/${ns.runningPodCount}`,\n        meshedPercent: new Percentage(meshedPods, totalPods),\n        meshedPods,\n        totalPods,\n        failedPods,\n        errors: ns.errorsByPod,\n      };\n    });\n    return _compact(dataPlaneNamespaces);\n  }\n\n  loadFromServer() {\n    const { pendingRequests } = this.state;\n    const { controllerNamespace } = this.props;\n\n    if (pendingRequests) {\n      return; // don't make more requests if the ones we sent haven't completed\n    }\n    this.setState({ pendingRequests: true });\n\n    this.api.setCurrentRequests([\n      this.api.fetchPods(controllerNamespace),\n      this.api.fetchMetrics(this.api.urlsForResourceNoStats('namespace')),\n    ]);\n\n    // expose serverPromise for testing\n    // eslint-disable-next-line react/no-unused-class-component-methods\n    this.serverPromise = Promise.all(this.api.getCurrentPromises())\n      .then(([pods, nsStats]) => {\n        this.setState({\n          components: ServiceMesh.getControllerComponentData(pods),\n          nsStatuses: ServiceMesh.extractNsStatuses(nsStats),\n          pendingRequests: false,\n          loaded: true,\n          error: null,\n        });\n      })\n      .catch(this.handleApiError);\n  }\n\n  fetchAllInstalledExtensions() {\n    this.api.setCurrentRequests([this.api.fetchExtension()]);\n\n    // expose serverPromise for testing\n    // eslint-disable-next-line react/no-unused-class-component-methods\n    this.serverPromise = Promise.all(this.api.getCurrentPromises())\n      .then(([extensions]) => {\n        this.setState({ extensions });\n      })\n      .catch(this.handleApiError);\n  }\n\n  handleApiError(e) {\n    if (e.isCanceled) {\n      return;\n    }\n\n    this.setState({\n      pendingRequests: false,\n      loaded: true,\n      error: e,\n    });\n  }\n\n  proxyCount() {\n    const { nsStatuses } = this.state;\n    const { controllerNamespace } = this.props;\n\n    return _sumBy(nsStatuses, d => {\n      return d.namespace === controllerNamespace ? 0 : d.meshedPods;\n    });\n  }\n\n  renderControlPlaneDetails() {\n    const { components } = this.state;\n\n    return (\n      <React.Fragment>\n        <Grid container justifyContent=\"space-between\">\n          <Grid item xs={3}>\n            <Typography variant=\"h6\"><Trans>Control plane</Trans></Typography>\n          </Grid>\n          <Grid item xs={3}>\n            <Typography align=\"right\"><Trans>componentsMsg</Trans></Typography>\n            <Typography align=\"right\">{components.length}</Typography>\n          </Grid>\n        </Grid>\n\n        <StatusTable\n          data={components}\n          statusColumnTitle=\"Pod Status\"\n          shouldLink={false} />\n      </React.Fragment>\n    );\n  }\n\n  renderInstalledExtensions() {\n    return (\n      <React.Fragment>\n        <Grid container justifyContent=\"space-between\">\n          <Grid item xs={3}>\n            <Typography variant=\"h6\"><Trans>Installed Extensions</Trans></Typography>\n          </Grid>\n        </Grid>\n        <BaseTable\n          tableClassName=\"metric-table\"\n          tableRows={this.getInstalledExtensions()}\n          tableColumns={installedExtensionsColumn}\n          rowKey={d => d.uid} />\n      </React.Fragment>\n    );\n  }\n\n  renderServiceMeshDetails() {\n    return (\n      <React.Fragment>\n        <Typography variant=\"h6\"><Trans>Service mesh details</Trans></Typography>\n\n        <BaseTable\n          tableClassName=\"metric-table\"\n          tableRows={this.getServiceMeshDetails()}\n          tableColumns={serviceMeshDetailsColumns}\n          rowKey={d => d.key} />\n\n      </React.Fragment>\n    );\n  }\n\n  renderAddResourcesMessage() {\n    const { nsStatuses } = this.state;\n    const { productName } = this.props;\n\n    let message = '';\n    let numUnadded = 0;\n\n    if (_isEmpty(nsStatuses)) {\n      message = <Trans>noNamespacesDetectedMsg</Trans>;\n    } else {\n      const unmeshedNamespaces = nsStatuses\n        .filter(pod => pod.meshedPercent.get() <= 0)\n        .filter(pod => pod.totalPods > 0)\n        .filter(pod => pod.namespace !== 'kube-system');\n      numUnadded = unmeshedNamespaces.length ?? 0;\n      message = numUnadded === 0 ? <Trans>All namespaces have a {productName} install.</Trans> : (\n        <Plural\n          value={numUnadded}\n          one=\"# namespace has no meshed resources\"\n          other=\"# namespaces have no meshed resources\" />\n      );\n    }\n\n    return (\n      <Card elevation={3}>\n        <CardContent>\n          <Typography variant=\"body2\">{message}</Typography>\n          {numUnadded > 0 ? incompleteMeshMessage() : null}\n        </CardContent>\n      </Card>\n    );\n  }\n\n  render() {\n    const { error, loaded, nsStatuses } = this.state;\n    const { classes } = this.props;\n\n    return (\n      <div className=\"page-content\">\n        {!error ? null : <ErrorBanner message={error} />}\n        {!loaded ? <Spinner /> : (\n          <div>\n            {this.proxyCount() === 0 ?\n              <CallToAction\n                numResources={nsStatuses.length}\n                resource=\"namespace\" /> : null}\n\n            <Grid container spacing={3}>\n              <Grid item xs={8} container direction=\"column\">\n                <Grid item>{this.renderControlPlaneDetails()}</Grid>\n                <Grid item>{this.renderInstalledExtensions()}</Grid>\n                <Grid item>\n                  <MeshedStatusTable tableRows={nsStatuses} />\n                </Grid>\n              </Grid>\n\n              <Grid item xs={4} container direction=\"column\" spacing={3}>\n                <Grid item>{this.renderServiceMeshDetails()}</Grid>\n                <Grid className={classes.checkModalWrapper} item><CheckModal api={this.api} /></Grid>\n                <Grid item>{this.renderAddResourcesMessage()}</Grid>\n              </Grid>\n            </Grid>\n          </div>\n        )}\n      </div>\n    );\n  }\n}\n\nServiceMesh.propTypes = {\n  api: PropTypes.shape({\n    cancelCurrentRequests: PropTypes.func.isRequired,\n    PrefixedLink: PropTypes.func.isRequired,\n    fetchMetrics: PropTypes.func.isRequired,\n    getCurrentPromises: PropTypes.func.isRequired,\n    setCurrentRequests: PropTypes.func.isRequired,\n    urlsForResourceNoStats: PropTypes.func.isRequired,\n  }).isRequired,\n  controllerNamespace: PropTypes.string.isRequired,\n  isPageVisible: PropTypes.bool.isRequired,\n  productName: PropTypes.string,\n  releaseVersion: PropTypes.string.isRequired,\n};\n\nServiceMesh.defaultProps = {\n  productName: 'controller',\n};\n\nexport default withPageVisibility(withStyles(styles)(withContext(ServiceMesh)));\n"
  },
  {
    "path": "web/app/js/components/ServiceMesh.test.jsx",
    "content": "import _cloneDeep from 'lodash/cloneDeep';\nimport _each from 'lodash/each';\nimport { expect } from 'chai';\nimport nsFixtures from '../../test/fixtures/namespaces.json';\nimport podFixtures from '../../test/fixtures/podRollup.json';\nimport { i18nAndRouterWrap } from '../../test/testHelpers.jsx';\nimport ServiceMesh from './ServiceMesh.jsx';\nimport sinon from 'sinon';\nimport sinonStubPromise from 'sinon-stub-promise';\nimport Spinner from './util/Spinner.jsx';\nimport { mount } from 'enzyme';\n\nsinonStubPromise(sinon);\n\ndescribe('ServiceMesh', () => {\n  let component, fetchStub;\n\n  // https://material-ui.com/style/typography/#migration-to-typography-v2\n  window.__MUI_USE_NEXT_TYPOGRAPHY_VARIANTS__ = true;\n\n  function withPromise(fn) {\n    return component.find(\"ServiceMesh\").instance().serverPromise.then(fn);\n  }\n\n  beforeEach(() => {\n    fetchStub = sinon.stub(window, 'fetch');\n  });\n\n  afterEach(() => {\n    component = null;\n    window.fetch.restore();\n  });\n\n  it(\"displays an error if the api call didn't go well\", () => {\n    let errorMsg = \"Something went wrong!\";\n\n    fetchStub.resolves({\n      ok: false,\n      json: () => Promise.resolve({\n        error: errorMsg\n      }),\n      statusText: errorMsg\n    });\n\n    component = mount(i18nAndRouterWrap(ServiceMesh));\n\n    return withPromise(() => {\n      expect(component.html()).to.include(errorMsg);\n    });\n  });\n\n  it(\"renders the spinner before metrics are loaded\", () => {\n    component = mount(i18nAndRouterWrap(ServiceMesh));\n\n    expect(component.find(Spinner)).to.have.length(1);\n    expect(component.find(\"ServiceMesh\")).to.have.length(1);\n    expect(component.find(\"CallToAction\")).to.have.length(0);\n  });\n\n  it(\"renders a call to action if no metrics are received\", () => {\n    fetchStub.resolves({\n      ok: true,\n      json: () => Promise.resolve({ metrics: [] })\n    });\n    component = mount(i18nAndRouterWrap(ServiceMesh));\n\n    return withPromise(() => {\n      component.update();\n\n      expect(component.find(\"ServiceMesh\")).to.have.length(1);\n      expect(component.find(Spinner)).to.have.length(0);\n      expect(component.find(\"CallToAction\")).to.have.length(1);\n    });\n  });\n\n  it(\"renders controller component summaries\", () => {\n    fetchStub.resolves({\n      ok: true,\n      json: () => Promise.resolve(podFixtures)\n    });\n    component = mount(i18nAndRouterWrap(ServiceMesh));\n\n    return withPromise(() => {\n      component.update();\n      expect(component.find(\"ServiceMesh\")).to.have.length(1);\n      expect(component.find(Spinner)).to.have.length(0);\n    });\n  });\n\n  it(\"renders service mesh details section\", () => {\n    fetchStub.resolves({\n      ok: true,\n      json: () => Promise.resolve({ metrics: [] })\n    });\n    component = mount(i18nAndRouterWrap(ServiceMesh));\n\n    return withPromise(() => {\n      component.update();\n      expect(component.find(\"ServiceMesh\")).to.have.length(1);\n      expect(component.find(Spinner)).to.have.length(0);\n      expect(component.html()).to.include(\"Service mesh details\");\n      expect(component.html()).to.include(\"ShinyProductName version\");\n    });\n  });\n\n  it(\"renders control plane section\", () => {\n    fetchStub.resolves({\n      ok: true,\n      json: () => Promise.resolve({ metrics: [] })\n    });\n    component = mount(i18nAndRouterWrap(ServiceMesh));\n\n    return withPromise(() => {\n      component.update();\n      expect(component.find(\"ServiceMesh\")).to.have.length(1);\n      expect(component.find(Spinner)).to.have.length(0);\n      expect(component.html()).to.include(\"Control plane\");\n    });\n  });\n\n  it(\"renders data plane section\", () => {\n    fetchStub.resolves({\n      ok: true,\n      json: () => Promise.resolve({ metrics: [] })\n    });\n    component = mount(i18nAndRouterWrap(ServiceMesh));\n\n    return withPromise(() => {\n      component.update();\n      expect(component.find(\"ServiceMesh\")).to.have.length(1);\n      expect(component.find(Spinner)).to.have.length(0);\n      expect(component.html()).to.include(\"Data plane\");\n    });\n  });\n\n  describe(\"renderAddDeploymentsMessage\", () => {\n    it(\"displays when no resources are in the mesh\", () => {\n      fetchStub.resolves({\n        ok: true,\n        json: () => Promise.resolve({})\n      });\n      component = mount(i18nAndRouterWrap(ServiceMesh));\n\n      return withPromise(() => {\n        expect(component.html()).to.include(\"No namespaces detected\");\n      });\n    });\n\n    it(\"displays a message if >1 resource has not been added to the mesh\", () => {\n      let nsAllResourcesAdded = _cloneDeep(nsFixtures);\n      nsAllResourcesAdded.ok.statTables[0].podGroup.rows.push({\n        \"resource\": {\n          \"namespace\": \"\",\n          \"type\": \"namespace\",\n          \"name\": \"test-1\"\n        },\n        \"timeWindow\": \"1m\",\n        \"meshedPodCount\": \"0\",\n        \"runningPodCount\": \"5\",\n        \"stats\": null\n      });\n\n      fetchStub.resolves({\n        ok: true,\n        json: () => Promise.resolve(nsAllResourcesAdded)\n      });\n      component = mount(i18nAndRouterWrap(ServiceMesh));\n\n      return withPromise(() => {\n        expect(component.html()).to.include(\"2 namespaces have no meshed resources.\");\n      });\n    });\n\n    it(\"displays a message if 1 resource has not added to servicemesh\", () => {\n      let nsOneResourceNotAdded = _cloneDeep(nsFixtures);\n      nsOneResourceNotAdded.ok.statTables[0].podGroup.rows.forEach(row => {\n        // set all namespaces to have fully meshed pod counts, except one\n        if (row.resource.name !== \"shiny-product-unmeshed\") {\n          row.meshedPodCount = \"10\";\n          row.runningPodCount = \"10\";\n        }\n      })\n      fetchStub.resolves({\n        ok: true,\n        json: () => Promise.resolve(nsOneResourceNotAdded)\n      });\n      component = mount(i18nAndRouterWrap(ServiceMesh));\n\n      return withPromise(() => {\n        expect(component.html()).to.include(\"1 namespace has no meshed resources.\");\n      });\n    });\n\n    it(\"displays a message if all resources have been added to servicemesh\", () => {\n      let nsAllResourcesAdded = _cloneDeep(nsFixtures);\n      _each(nsAllResourcesAdded.ok.statTables[0].podGroup.rows, row => {\n        row.meshedPodCount = \"10\";\n        row.runningPodCount = \"10\";\n      });\n      fetchStub.resolves({\n        ok: true,\n        json: () => Promise.resolve(nsAllResourcesAdded)\n      });\n      component = mount(i18nAndRouterWrap(ServiceMesh));\n\n      return withPromise(() => {\n        expect(component.html()).to.include(\"All namespaces have a ShinyProductName install.\");\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "web/app/js/components/StatusTable.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport { Trans } from '@lingui/macro';\nimport _get from 'lodash/get';\nimport _merge from 'lodash/merge';\nimport classNames from 'classnames';\nimport { withStyles } from '@material-ui/core/styles';\nimport { statusClassNames } from './util/theme.js';\nimport BaseTable from './BaseTable.jsx';\n\nconst styles = theme => _merge({}, statusClassNames(theme), {\n  statusTableDot: {\n    width: theme.spacing(2),\n    height: theme.spacing(2),\n    minWidth: theme.spacing(2),\n    borderRadius: '50%',\n    display: 'inline-block',\n    marginRight: theme.spacing(1),\n  },\n});\n\nconst columnConfig = {\n  'Pod Status': {\n    width: 200,\n    wrapDotsAt: 7, // dots take up more than one line in the table; space them out\n    dotExplanation: status => {\n      return status.value === 'good' ? <Trans>statusExplanationGood</Trans> : <Trans>statusExplanationNotStarted</Trans>;\n    },\n  },\n  'Proxy Status': {\n    width: 250,\n    wrapDotsAt: 9,\n    dotExplanation: pod => {\n      const addedStatus = !pod.added ? <Trans>statusExplanationNotInMesh</Trans> : <Trans>statusExplanationInMesh</Trans>;\n\n      return (\n        <React.Fragment>\n          <div><Trans>Pod status: {pod.status}</Trans></div>\n          <div>{addedStatus}</div>\n        </React.Fragment>\n      );\n    },\n  },\n};\n\nconst StatusDot = function({ status, columnName, classes }) {\n  return (\n    <Tooltip\n      placement=\"top\"\n      title={(\n        <div>\n          <div>{status.name}</div>\n          <div>{_get(columnConfig, [columnName, 'dotExplanation'])(status)}</div>\n          <div>Uptime: {status.uptime} ({status.uptimeSec}s)</div>\n        </div>\n    )}>\n      <div\n        className={classNames(\n          classes.statusTableDot,\n          classes[status.value],\n        )}\n        key={status.name}>&nbsp;\n      </div>\n    </Tooltip>\n  );\n};\n\nStatusDot.propTypes = {\n  columnName: PropTypes.string.isRequired,\n  status: PropTypes.shape({\n    name: PropTypes.string.isRequired,\n    uptime: PropTypes.string.isRequired,\n    uptimeSec: PropTypes.number.isRequired,\n    value: PropTypes.string.isRequired,\n  }).isRequired,\n};\n\nconst columns = {\n  resourceName: {\n    title: <Trans>columnTitleDeployment</Trans>,\n    dataIndex: 'name',\n  },\n  pods: {\n    title: <Trans>columnTitlePods</Trans>,\n    key: 'numEntities',\n    isNumeric: true,\n    render: d => d.pods.length,\n  },\n  status: (name, classes) => {\n    return {\n      title: <Trans>columnTitlePodStatus</Trans>,\n      key: 'status',\n      render: d => {\n        return d.pods.map(status => (\n          <StatusDot\n            status={status}\n            columnName={name}\n            classes={classes}\n            key={`${status.name}-pod-status`} />\n        ));\n      },\n    };\n  },\n};\n\nconst StatusTable = function({ classes, statusColumnTitle, data }) {\n  const tableCols = [\n    columns.resourceName,\n    columns.pods,\n    columns.status(statusColumnTitle, classes),\n  ];\n\n  return (\n    <BaseTable\n      tableRows={data}\n      tableColumns={tableCols}\n      tableClassName=\"metric-table\"\n      defaultOrderBy=\"name\"\n      rowKey={r => r.name} />\n  );\n};\n\nStatusTable.propTypes = {\n  data: PropTypes.arrayOf(PropTypes.shape({\n    name: PropTypes.string.isRequired,\n    pods: PropTypes.shape({\n      name: PropTypes.string.isRequired,\n      value: PropTypes.string.isRequired,\n      uptime: PropTypes.string.isRequired,\n      uptimeSec: PropTypes.number.isRequired,\n    }).isRequired,\n    added: PropTypes.bool,\n  })).isRequired,\n  statusColumnTitle: PropTypes.string.isRequired,\n};\n\nexport default withStyles(styles, { withTheme: true })(StatusTable);\n"
  },
  {
    "path": "web/app/js/components/Tap.jsx",
    "content": "import { StringParam, withQueryParams } from 'use-query-params';\n\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport _cloneDeep from 'lodash/cloneDeep';\nimport _each from 'lodash/each';\nimport _isNil from 'lodash/isNil';\nimport _orderBy from 'lodash/orderBy';\nimport _size from 'lodash/size';\nimport _throttle from 'lodash/throttle';\nimport _values from 'lodash/values';\nimport TapQueryForm from './TapQueryForm.jsx';\nimport TapEventTable from './TapEventTable.jsx';\nimport TapEnabledWarning from './TapEnabledWarning.jsx';\nimport ErrorBanner from './ErrorBanner.jsx';\nimport { handlePageVisibility, withPageVisibility } from './util/PageVisibility.jsx';\nimport { WS_ABNORMAL_CLOSURE, WS_NORMAL_CLOSURE, WS_POLICY_VIOLATION, emptyTapQuery, processTapEvent, setMaxRps, wsCloseCodes } from './util/TapUtils.jsx';\nimport { groupResourcesByNs } from './util/MetricUtils.jsx';\nimport { withContext } from './util/AppContext.jsx';\n\nconst urlPropsQueryConfig = {\n  autostart: StringParam,\n};\n\nclass Tap extends React.Component {\n  constructor(props) {\n    super(props);\n    this.api = props.api;\n    this.tapResultsById = {};\n    this.throttledWebsocketRecvHandler = _throttle(this.updateTapResults, 500);\n    this.loadFromServer = this.loadFromServer.bind(this);\n\n    this.state = {\n      tapResultsById: this.tapResultsById,\n      error: null,\n      resourcesByNs: {},\n      query: {\n        resource: '',\n        namespace: '',\n        toResource: '',\n        toNamespace: '',\n        method: '',\n        path: '',\n        scheme: '',\n        maxRps: '',\n      },\n      maxLinesToDisplay: 40,\n      tapRequestInProgress: false,\n      tapIsClosing: false,\n      pollingInterval: 10000,\n      pendingRequests: false,\n      showTapEnabledWarning: false,\n    };\n  }\n\n  componentDidMount() {\n    const { autostart } = this.props;\n    this._isMounted = true; // https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html\n    this.startServerPolling();\n    if (autostart === 'true') {\n      this.startTapStreaming();\n    }\n  }\n\n  componentDidUpdate(prevProps) {\n    const { isPageVisible, autostart } = this.props;\n    handlePageVisibility({\n      prevVisibilityState: prevProps.isPageVisible,\n      currentVisibilityState: isPageVisible,\n      onVisible: () => {\n        this.startServerPolling();\n        if (autostart === 'true') {\n          this.startTapStreaming();\n        }\n      },\n      onHidden: () => {\n        if (this.ws) {\n          this.ws.close(1000);\n        }\n        this.throttledWebsocketRecvHandler.cancel();\n        this.stopServerPolling();\n      },\n    });\n  }\n\n  componentWillUnmount() {\n    this._isMounted = false;\n    if (this.ws) {\n      this.ws.close(1000);\n    }\n    this.throttledWebsocketRecvHandler.cancel();\n    this.stopServerPolling();\n  }\n\n  onWebsocketOpen = () => {\n    const { query } = this.state;\n    const tapQuery = _cloneDeep(query);\n    setMaxRps(tapQuery);\n\n    this.ws.send(JSON.stringify({\n      id: 'tap-web',\n      ...tapQuery,\n      extract: true,\n    }));\n    this.setState({\n      error: null,\n    });\n  };\n\n  onWebsocketRecv = e => {\n    this.indexTapResult(e.data);\n    this.throttledWebsocketRecvHandler();\n  };\n\n  onWebsocketClose = e => {\n    this.stopTapStreaming();\n    /* We ignore any abnormal closure since it doesn't matter as long as\n    the connection to the websocket is closed. This is also a workaround\n    where Chrome browsers incorrectly displays a 1006 close code\n    https://github.com/linkerd/linkerd2/issues/1630\n    */\n    if (e.code !== WS_NORMAL_CLOSURE && e.code !== WS_ABNORMAL_CLOSURE && this._isMounted) {\n      if (e.code === WS_POLICY_VIOLATION) {\n        this.setState({\n          error: {\n            error: e.reason,\n          },\n        });\n      } else if (e.reason.includes('no pods to tap')) {\n        this.setState({\n          showTapEnabledWarning: true,\n        });\n      } else {\n        this.setState({\n          error: {\n            error: `Websocket close error [${e.code}: ${wsCloseCodes[e.code]}] ${e.reason ? ':' : ''} ${e.reason}`,\n          },\n        });\n      }\n    }\n  };\n\n  onWebsocketError = e => {\n    this.setState({\n      error: { error: `Websocket error: ${e.message}` },\n    });\n\n    this.stopTapStreaming();\n  };\n\n  // keep an index of tap request rows by id. this allows us to collate\n  // requestInit/responseInit/responseEnd into one single table row,\n  // as opposed to three separate rows as in the CLI\n  indexTapResult = data => {\n    const { maxLinesToDisplay } = this.state;\n    const resultIndex = this.tapResultsById;\n    const d = processTapEvent(data);\n\n    if (_isNil(resultIndex[d.id])) {\n      // don't let tapResultsById grow unbounded\n      if (_size(resultIndex) > maxLinesToDisplay) {\n        Tap.deleteOldestTapResult(resultIndex);\n      }\n\n      resultIndex[d.id] = {};\n    }\n    resultIndex[d.id][d.eventType] = d;\n    // assumption: requests of a given id all share the same high level metadata\n    resultIndex[d.id].base = d;\n    resultIndex[d.id].key = d.id;\n    resultIndex[d.id].lastUpdated = Date.now();\n  };\n\n  updateTapResults = () => {\n    this.setState({\n      tapResultsById: this.tapResultsById,\n    });\n  };\n\n  static deleteOldestTapResult(resultIndex) {\n    let oldest = Date.now();\n    let oldestId = '';\n\n    _each(resultIndex, (res, id) => {\n      if (res.lastUpdated < oldest) {\n        oldest = res.lastUpdated;\n        oldestId = id;\n      }\n    });\n\n    delete resultIndex[oldestId];\n  }\n\n  startServerPolling() {\n    const { pollingInterval } = this.state;\n    this.loadFromServer();\n    this.timerId = window.setInterval(this.loadFromServer, pollingInterval);\n  }\n\n  stopServerPolling() {\n    window.clearInterval(this.timerId);\n    this.api.cancelCurrentRequests();\n    this.setState({ pendingRequests: false });\n  }\n\n  startTapStreaming() {\n    const { pathPrefix } = this.props;\n    this.tapResultsById = {};\n\n    this.setState({\n      tapRequestInProgress: true,\n      tapResultsById: this.tapResultsById,\n    });\n\n    const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';\n    const tapWebSocket = `${protocol}://${window.location.host}${pathPrefix}/api/tap`;\n\n    this.ws = new WebSocket(tapWebSocket);\n    this.ws.onmessage = this.onWebsocketRecv;\n    this.ws.onclose = this.onWebsocketClose;\n    this.ws.onopen = this.onWebsocketOpen;\n    this.ws.onerror = this.onWebsocketError;\n  }\n\n  stopTapStreaming() {\n    if (!this._isMounted) {\n      return;\n    }\n\n    this.setState({\n      tapRequestInProgress: false,\n      tapIsClosing: false,\n    });\n  }\n\n  handleTapStart = e => {\n    e.preventDefault();\n    this.startTapStreaming();\n  };\n\n  handleTapStop = () => {\n    this.ws.close(1000);\n    this.setState({ tapIsClosing: true });\n  };\n\n  handleTapClear = () => {\n    this.resetTapResults();\n  };\n\n  resetTapResults = () => {\n    this.tapResultsById = {};\n    this.setState({\n      tapResultsById: {},\n      query: emptyTapQuery(),\n      showTapEnabledWarning: false,\n    });\n  };\n\n  loadFromServer() {\n    const { pendingRequests } = this.state;\n\n    if (pendingRequests) {\n      return; // don't make more requests if the ones we sent haven't completed\n    }\n    this.setState({\n      pendingRequests: true,\n    });\n\n    const url = this.api.urlsForResourceNoStats('all');\n    this.api.setCurrentRequests([this.api.fetchMetrics(url)]);\n    Promise.all(this.api.getCurrentPromises())\n      .then(rsp => {\n        const { resourcesByNs } = groupResourcesByNs(rsp[0]);\n        this.setState({\n          resourcesByNs,\n          pendingRequests: false,\n        });\n      })\n      .catch(this.handleApiError);\n  }\n\n  updateQuery = query => {\n    this.setState({\n      query,\n      showTapEnabledWarning: false,\n    });\n  };\n\n  render() {\n    const { tapResultsById, tapRequestInProgress, tapIsClosing, resourcesByNs, query, showTapEnabledWarning, error } = this.state;\n    const tableRows = _orderBy(_values(tapResultsById), r => r.lastUpdated, 'desc');\n\n    return (\n      <div>\n        {!error ? null :\n        <ErrorBanner message={error} onHideMessage={() => this.setState({ error: null })} />}\n\n        <TapQueryForm\n          cmdName=\"tap\"\n          tapRequestInProgress={tapRequestInProgress}\n          tapIsClosing={tapIsClosing}\n          handleTapStart={this.handleTapStart}\n          handleTapStop={this.handleTapStop}\n          handleTapClear={this.handleTapClear}\n          resourcesByNs={resourcesByNs}\n          updateQuery={this.updateQuery}\n          currentQuery={query} />\n        {showTapEnabledWarning &&\n          <TapEnabledWarning\n            resource={query.resource}\n            namespace={query.namespace}\n            cardComponent />\n        }\n        {!showTapEnabledWarning &&\n          <TapEventTable\n            resource={query.resource}\n            tableRows={tableRows} />\n        }\n      </div>\n    );\n  }\n}\n\nTap.propTypes = {\n  api: PropTypes.shape({\n    PrefixedLink: PropTypes.func.isRequired,\n  }).isRequired,\n  autostart: PropTypes.string,\n  isPageVisible: PropTypes.bool.isRequired,\n  pathPrefix: PropTypes.string.isRequired,\n};\n\nTap.defaultProps = {\n  autostart: '',\n};\n\nexport default withPageVisibility(withQueryParams(urlPropsQueryConfig, withContext(Tap)));\n"
  },
  {
    "path": "web/app/js/components/TapEnabledWarning.jsx",
    "content": "import Card from '@material-ui/core/Card';\nimport CardContent from '@material-ui/core/CardContent';\nimport Grid from '@material-ui/core/Grid';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Trans } from '@lingui/macro';\nimport Warning from '@material-ui/icons/Warning';\nimport { withStyles } from '@material-ui/core/styles';\n\nconst styles = theme => ({\n  cardContainer: {\n    marginTop: theme.spacing(1.5),\n  },\n  container: {\n    margin: theme.spacing(1),\n  },\n  iconWarning: {\n    color: theme.status.dark.warning,\n  },\n});\n\nconst TapEnabledWarning = function({ resource, cardComponent, namespace, classes }) {\n  const component = (\n    <Grid className={classes.container} container spacing={1} alignItems=\"center\">\n      <Grid item><Warning className={classes.iconWarning} /></Grid>\n      <Grid item>\n        <Trans>Pods under the resource {resource} in the {namespace} namespace are missing tap configurations (restart these pods to enable tap)</Trans>\n      </Grid>\n    </Grid>\n  );\n\n  if (cardComponent) {\n    return (\n      <Card className={classes.cardContainer}>\n        <CardContent>{component}</CardContent>\n      </Card>\n    );\n  } else {\n    return component;\n  }\n};\n\nTapEnabledWarning.propTypes = {\n  resource: PropTypes.string.isRequired,\n  namespace: PropTypes.string.isRequired,\n  cardComponent: PropTypes.bool,\n};\n\nTapEnabledWarning.defaultProps = {\n  cardComponent: false,\n};\n\nexport default withStyles(styles)(TapEnabledWarning);\n"
  },
  {
    "path": "web/app/js/components/TapEventHeadersTable.jsx",
    "content": "import ListItem from '@material-ui/core/ListItem';\nimport ListItemText from '@material-ui/core/ListItemText';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Typography from '@material-ui/core/Typography';\nimport { withStyles } from '@material-ui/core/styles';\n\nconst headersStyles = {\n  headerName: {\n    fontSize: '12px',\n    marginTop: '5px',\n  },\n};\n\nconst HeadersContentBase = function({ headers, classes }) {\n  return (\n    <React.Fragment>\n      {headers.map(header => {\n        return (\n          <React.Fragment key={`${header.name}_${header.valueStr}`}>\n            <Typography\n              className={classes.headerName}\n              variant=\"inherit\"\n              display=\"block\"\n              color=\"textPrimary\">\n              {header.name}\n            </Typography>\n            <Typography\n              variant=\"inherit\"\n              color=\"textSecondary\">\n              {header.valueStr}\n            </Typography>\n          </React.Fragment>\n        );\n      })}\n    </React.Fragment>\n  );\n};\n\nHeadersContentBase.propTypes = {\n  headers: PropTypes.arrayOf(PropTypes.shape({\n    name: PropTypes.string.isRequired,\n    valueStr: PropTypes.string.isRequired,\n  })).isRequired,\n};\n\nconst HeadersContentDisplay = withStyles(headersStyles)(HeadersContentBase);\n\nexport const headersDisplay = (title, value) => {\n  if (!value) {\n    return null;\n  }\n\n  return (\n    <ListItem disableGutters>\n      <ListItemText\n        primary={title}\n        secondary={'headers' in value ? <HeadersContentDisplay headers={value.headers} /> : '-'} />\n    </ListItem>\n  );\n};\n"
  },
  {
    "path": "web/app/js/components/TapEventTable.jsx",
    "content": "import Card from '@material-ui/core/Card';\nimport CardContent from '@material-ui/core/CardContent';\nimport CircularProgress from '@material-ui/core/CircularProgress';\nimport Grid from '@material-ui/core/Grid';\nimport List from '@material-ui/core/List';\nimport ListItem from '@material-ui/core/ListItem';\nimport ListItemText from '@material-ui/core/ListItemText';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Trans } from '@lingui/macro';\nimport Typography from '@material-ui/core/Typography';\nimport _get from 'lodash/get';\nimport _isEmpty from 'lodash/isEmpty';\nimport _isNull from 'lodash/isNull';\nimport { withStyles } from '@material-ui/core/styles';\nimport { headersDisplay } from './TapEventHeadersTable.jsx';\nimport { withContext } from './util/AppContext.jsx';\nimport ExpandableTable from './ExpandableTable.jsx';\nimport { formatLatencySec, formatWithComma } from './util/Utils.js';\nimport { directionColumn, srcDstColumn } from './util/TapUtils.jsx';\n\n// https://godoc.org/google.golang.org/grpc/codes#Code\nconst grpcStatusCodes = {\n  0: 'OK',\n  1: 'Canceled',\n  2: 'Unknown',\n  3: 'InvalidArgument',\n  4: 'DeadlineExceeded',\n  5: 'NotFound',\n  6: 'AlreadyExists',\n  7: 'PermissionDenied',\n  8: 'ResourceExhausted',\n  9: 'FailedPrecondition',\n  10: 'Aborted',\n  11: 'OutOfRange',\n  12: 'Unimplemented',\n  13: 'Internal',\n  14: 'Unavailable',\n  15: 'DataLoss',\n  16: 'Unauthenticated',\n};\n\nconst spinnerStyles = theme => ({\n  progress: {\n    margin: theme.spacing(2),\n  },\n});\nconst SpinnerBase = function() {\n  return <CircularProgress size={20} />;\n};\nconst Spinner = withStyles(spinnerStyles)(SpinnerBase);\n\nconst formatTapLatency = str => {\n  return formatLatencySec(str.replace('s', ''));\n};\n\nconst httpStatusCol = {\n  title: <Trans>columnTitleHTTPStatus</Trans>,\n  key: 'http-status',\n  render: datum => {\n    const d = _get(datum, 'responseInit.http.responseInit');\n    return !d ? <Spinner /> : d.httpStatus;\n  },\n};\n\nconst responseInitLatencyCol = {\n  title: <Trans>columnTitleLatency</Trans>,\n  key: 'rsp-latency',\n  isNumeric: true,\n  render: datum => {\n    const d = _get(datum, 'responseInit.http.responseInit');\n    return !d ? <Spinner /> : formatTapLatency(d.sinceRequestInit);\n  },\n};\n\nconst grpcStatusCol = {\n  title: <Trans>columnTitleGRPCStatus</Trans>,\n  key: 'grpc-status',\n  render: datum => {\n    const d = _get(datum, 'responseEnd.http.responseEnd');\n    return !d ? <Spinner /> :\n      _isNull(d.eos) ? '---' : grpcStatusCodes[_get(d, 'eos.grpcStatusCode')];\n  },\n};\n\nconst pathCol = {\n  title: <Trans>columnTitlePath</Trans>,\n  key: 'path',\n  render: datum => {\n    const d = _get(datum, 'requestInit.http.requestInit');\n    return !d ? <Spinner /> : d.path;\n  },\n};\n\nconst methodCol = {\n  title: <Trans>columnTitleMethod</Trans>,\n  key: 'method',\n  render: datum => {\n    const d = _get(datum, 'requestInit.http.requestInit');\n    return !d ? <Spinner /> : _get(d, 'method.registered');\n  },\n};\n\nconst topLevelColumns = (resourceType, ResourceLink) => [\n  {\n    title: <Trans>columnTitleDirection</Trans>,\n    key: 'direction',\n    render: d => directionColumn(d.base.proxyDirection),\n  },\n  {\n    title: <Trans>columnTitleName</Trans>,\n    key: 'src-dst',\n    render: d => {\n      const datum = {\n        direction: _get(d, 'base.proxyDirection'),\n        source: _get(d, 'base.source'),\n        destination: _get(d, 'base.destination'),\n        sourceLabels: _get(d, 'base.sourceMeta.labels', {}),\n        destinationLabels: _get(d, 'base.destinationMeta.labels', {}),\n      };\n      return srcDstColumn(datum, resourceType, ResourceLink);\n    },\n  },\n];\n\nconst tapColumns = (resourceType, ResourceLink) => {\n  return topLevelColumns(resourceType, ResourceLink).concat(\n    [methodCol, pathCol, responseInitLatencyCol, httpStatusCol, grpcStatusCol],\n  );\n};\n\nconst itemDisplay = (title, value) => {\n  return (\n    <ListItem disableGutters>\n      <ListItemText primary={title} secondary={value} />\n    </ListItem>\n  );\n};\n\nconst requestInitSection = d => (\n  <React.Fragment>\n    <Typography variant=\"subtitle2\"><Trans>tableTitleRequestInit</Trans></Typography>\n    <br />\n    <List dense>\n      {itemDisplay(<Trans>formAuthority</Trans>, _get(d, 'requestInit.http.requestInit.authority'))}\n      {itemDisplay(<Trans>formPath</Trans>, _get(d, 'requestInit.http.requestInit.path'))}\n      {itemDisplay(<Trans>formScheme</Trans>, _get(d, 'requestInit.http.requestInit.scheme.registered'))}\n      {itemDisplay(<Trans>formMethod</Trans>, _get(d, 'requestInit.http.requestInit.method.registered'))}\n      {headersDisplay(<Trans>formHeaders</Trans>, _get(d, 'requestInit.http.requestInit.headers'))}\n    </List>\n  </React.Fragment>\n);\n\nconst responseInitSection = d => _isEmpty(d.responseInit) ? null : (\n  <React.Fragment>\n    <Typography variant=\"subtitle2\"><Trans>tableTitleResponseInit</Trans></Typography>\n    <br />\n    <List dense>\n      {itemDisplay(<Trans>formHTTPStatus</Trans>, _get(d, 'responseInit.http.responseInit.httpStatus'))}\n      {itemDisplay(<Trans>formLatency</Trans>, formatTapLatency(_get(d, 'responseInit.http.responseInit.sinceRequestInit')))}\n      {headersDisplay(<Trans>formHeaders</Trans>, _get(d, 'responseInit.http.responseInit.headers'))}\n    </List>\n  </React.Fragment>\n);\n\nconst responseEndSection = d => _isEmpty(d.responseEnd) ? null : (\n  <React.Fragment>\n    <Typography variant=\"subtitle2\"><Trans>tableTitleResponseEnd</Trans></Typography>\n    <br />\n\n    <List dense>\n      {itemDisplay(<Trans>formGRPCStatus</Trans>, _isNull(_get(d, 'responseEnd.http.responseEnd.eos')) ? 'N/A' : grpcStatusCodes[_get(d, 'responseEnd.http.responseEnd.eos.grpcStatusCode')])}\n      {itemDisplay(<Trans>formLatency</Trans>, formatTapLatency(_get(d, 'responseEnd.http.responseEnd.sinceResponseInit')))}\n      {itemDisplay(<Trans>formResponseLengthB</Trans>, formatWithComma(_get(d, 'responseEnd.http.responseEnd.responseBytes')))}\n    </List>\n  </React.Fragment>\n);\n\n// hide verbose information\nconst expandedRowRender = (d, expandedWrapStyle) => {\n  return (\n    <Grid container spacing={2} className={expandedWrapStyle}>\n      <Grid item xs={4}>\n        <Card elevation={3}>\n          <CardContent>{requestInitSection(d)}</CardContent>\n        </Card>\n      </Grid>\n      <Grid item xs={4}>\n        <Card elevation={3}>\n          <CardContent>{responseInitSection(d)}</CardContent>\n        </Card>\n      </Grid>\n      <Grid item xs={4}>\n        <Card elevation={3}>\n          <CardContent>{responseEndSection(d)}</CardContent>\n        </Card>\n      </Grid>\n    </Grid>\n  );\n};\n\nconst TapEventTable = function({ tableRows, resource, api }) {\n  const resourceType = resource.split('/')[0];\n  const columns = tapColumns(resourceType, api.ResourceLink);\n\n  return (\n    <ExpandableTable\n      tableRows={tableRows}\n      tableColumns={columns}\n      expandedRowRender={expandedRowRender}\n      tableClassName=\"metric-table\" />\n  );\n};\n\nTapEventTable.propTypes = {\n  api: PropTypes.shape({\n    ResourceLink: PropTypes.func.isRequired,\n  }).isRequired,\n  resource: PropTypes.string,\n  tableRows: PropTypes.arrayOf(PropTypes.shape({})),\n};\n\nTapEventTable.defaultProps = {\n  resource: '',\n  tableRows: [],\n};\n\nexport default withContext(TapEventTable);\n"
  },
  {
    "path": "web/app/js/components/TapLink.jsx",
    "content": "import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { faMicroscope } from '@fortawesome/free-solid-svg-icons/faMicroscope';\n\nconst TapLink = function({ PrefixedLink, namespace, resource, toNamespace, toResource, path, disabled }) {\n  if (disabled || namespace === '') {\n    return <FontAwesomeIcon icon={faMicroscope} className=\"tapGrayed\" />;\n  }\n  const params = {\n    autostart: 'true',\n    namespace,\n    resource,\n    toNamespace,\n    toResource,\n    path,\n  };\n  const queryStr = Object.entries(params).map(([k, v]) => `${k}=${v}`).join('&');\n\n  return (\n    <PrefixedLink to={`/tap?${queryStr}`}>\n      <FontAwesomeIcon icon={faMicroscope} />\n    </PrefixedLink>\n  );\n};\n\nTapLink.propTypes = {\n  disabled: PropTypes.bool,\n  namespace: PropTypes.string,\n  path: PropTypes.string.isRequired,\n  PrefixedLink: PropTypes.func.isRequired,\n  resource: PropTypes.string.isRequired,\n  toNamespace: PropTypes.string.isRequired,\n  toResource: PropTypes.string.isRequired,\n};\n\nTapLink.defaultProps = {\n  disabled: false,\n  namespace: '',\n};\n\nexport default TapLink;\n"
  },
  {
    "path": "web/app/js/components/TapQueryForm.jsx",
    "content": "import { StringParam, withQueryParams } from 'use-query-params';\nimport Accordion from '@material-ui/core/Accordion';\nimport AccordionDetails from '@material-ui/core/AccordionDetails';\nimport AccordionSummary from '@material-ui/core/AccordionSummary';\nimport Button from '@material-ui/core/Button';\nimport Card from '@material-ui/core/Card';\nimport CardContent from '@material-ui/core/CardContent';\nimport ExpandMoreIcon from '@material-ui/icons/ExpandMore';\nimport FormControl from '@material-ui/core/FormControl';\nimport FormHelperText from '@material-ui/core/FormHelperText';\nimport Grid from '@material-ui/core/Grid';\nimport InputLabel from '@material-ui/core/InputLabel';\nimport MenuItem from '@material-ui/core/MenuItem';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Select from '@material-ui/core/Select';\nimport TextField from '@material-ui/core/TextField';\nimport { Trans } from '@lingui/macro';\nimport Typography from '@material-ui/core/Typography';\nimport _flatten from 'lodash/flatten';\nimport _isEmpty from 'lodash/isEmpty';\nimport _isEqual from 'lodash/isEqual';\nimport _isNil from 'lodash/isNil';\nimport _map from 'lodash/map';\nimport _merge from 'lodash/merge';\nimport _noop from 'lodash/noop';\nimport _omit from 'lodash/omit';\nimport _pick from 'lodash/pick';\nimport _some from 'lodash/some';\nimport _uniq from 'lodash/uniq';\nimport _values from 'lodash/values';\nimport { withStyles } from '@material-ui/core/styles';\nimport QueryToCliCmd from './QueryToCliCmd.jsx';\nimport {\n  defaultMaxRps,\n  emptyTapQuery,\n  httpMethods,\n  tapQueryPropType,\n  tapQueryProps,\n  tapResourceTypes,\n} from './util/TapUtils.jsx';\n\nconst getResourceList = (resourcesByNs, ns) => {\n  return resourcesByNs[ns] || _uniq(_flatten(_values(resourcesByNs)));\n};\n\nconst urlPropsQueryConfig = {};\nObject.keys(tapQueryProps).forEach(value => {\n  urlPropsQueryConfig[value] = StringParam;\n});\n\nconst styles = theme => ({\n  root: {\n    display: 'flex',\n    flexWrap: 'wrap',\n  },\n  formControlWrapper: {\n    minWidth: 200,\n  },\n  formControl: {\n    padding: theme.spacing(1),\n    paddingLeft: 0,\n    margin: 0,\n    minWidth: 'inherit',\n    maxWidth: '100%',\n    width: 'auto',\n  },\n  selectEmpty: {\n    'margin-top': '32px',\n  },\n  card: {\n    maxWidth: '100%',\n  },\n  actions: {\n    display: 'flex',\n    'padding-left': '32px',\n  },\n  expand: {\n    transform: 'rotate(0deg)',\n    transition: theme.transitions.create('transform', {\n      duration: theme.transitions.duration.shortest,\n    }),\n    marginLeft: 'auto',\n    [theme.breakpoints.up('sm')]: {\n      marginRight: -8,\n    },\n  },\n  expandOpen: {\n    transform: 'rotate(180deg)',\n  },\n  resetButton: {\n    marginLeft: theme.spacing(1),\n  },\n});\n\nclass TapQueryForm extends React.Component {\n  static getDerivedStateFromProps(props, state) {\n    if (!_isEqual(props.resourcesByNs, state.resourcesByNs)) {\n      const resourcesByNs = props.resourcesByNs;\n      const namespaces = Object.keys(resourcesByNs).sort();\n      const resourceNames = getResourceList(resourcesByNs, state.query.namespace);\n      const toResourceNames = getResourceList(resourcesByNs, state.query.toNamespace);\n\n      return _merge(state, {\n        resourcesByNs,\n        autocomplete: {\n          namespace: namespaces,\n          resource: resourceNames,\n          toNamespace: namespaces,\n          toResource: toResourceNames,\n        },\n      });\n    } else {\n      return null;\n    }\n  }\n\n  constructor(props) {\n    super(props);\n\n    const query = _merge({}, props.currentQuery, _pick(props.query, Object.keys(tapQueryProps)));\n    props.updateQuery(query);\n\n    const advancedFormExpanded = _some(\n      _omit(query, ['namespace', 'resource']),\n      v => !_isEmpty(v),\n    );\n\n    this.state = {\n      query,\n      advancedFormExpanded,\n      resourcesByNs: {},\n      autocomplete: {\n        namespace: [],\n        resource: [],\n        toNamespace: [],\n        toResource: [],\n      },\n    };\n  }\n\n  handleFormChange = (name, scopeResource) => {\n    const { query, autocomplete, resourcesByNs } = this.state;\n    const { updateQuery } = this.props;\n\n    const state = {\n      query,\n      autocomplete,\n    };\n\n    const newQueryValues = {};\n\n    return event => {\n      const formVal = event.target.value;\n      state.query[name] = formVal;\n      newQueryValues[name] = formVal;\n\n      if (!_isNil(scopeResource)) {\n        // scope the available typeahead resources to the selected namespace\n        state.autocomplete[scopeResource] = resourcesByNs[formVal];\n        state.query[scopeResource] = `namespace/${formVal}`;\n        newQueryValues[scopeResource] = `namespace/${formVal}`;\n      }\n\n      this.setState(state);\n      updateQuery(state.query);\n      this.handleUrlUpdate(newQueryValues);\n    };\n  };\n\n  // Each time state.query is updated, this method calls setQuery provided\n  // by useQueryParams HOC to partially update url query params that have\n  // changed\n  handleUrlUpdate = query => {\n    const { setQuery } = this.props;\n    setQuery({ ...query });\n  };\n\n  handleFormEvent = name => {\n    const { query } = this.state;\n    const { updateQuery } = this.props;\n\n    const state = {\n      query,\n    };\n\n    return event => {\n      state.query[name] = event.target.value;\n      this.handleUrlUpdate(state.query);\n      this.setState(state);\n      updateQuery(state.query);\n    };\n  };\n\n  handleAdvancedFormExpandClick = () => {\n    const { advancedFormExpanded } = this.state;\n    this.setState({ advancedFormExpanded: !advancedFormExpanded });\n  };\n\n  resetTapForm = () => {\n    const { updateQuery, handleTapClear } = this.props;\n\n    this.setState({\n      query: emptyTapQuery(),\n    });\n\n    this.handleUrlUpdate(emptyTapQuery());\n\n    updateQuery(emptyTapQuery(), true);\n    handleTapClear();\n  };\n\n  renderResourceSelect = (resourceKey, namespaceKey) => {\n    const { autocomplete, query } = this.state;\n    const { classes } = this.props;\n\n    const selectedNs = query[namespaceKey];\n    const nsEmpty = _isNil(selectedNs) || _isEmpty(selectedNs);\n\n    const resourceOptions = tapResourceTypes.concat(\n      autocomplete[resourceKey] || [],\n      nsEmpty ? [] : [`namespace/${selectedNs}`],\n    ).sort();\n\n    return (\n      <React.Fragment>\n        <InputLabel htmlFor={resourceKey}>{resourceKey === 'resource' ? <Trans>formResource</Trans> : <Trans>formToResource</Trans>}</InputLabel>\n        <Select\n          value={!nsEmpty && resourceOptions.includes(query[resourceKey]) ? query[resourceKey] : ''}\n          onChange={this.handleFormChange(resourceKey)}\n          inputProps={{ name: resourceKey, id: resourceKey }}\n          className={classes.selectEmpty}>\n          {\n            resourceOptions.map(resource => (\n              <MenuItem key={`${namespaceKey}-${resourceKey}-${resource}`} value={resource}>{resource}</MenuItem>\n            ))\n          }\n        </Select>\n      </React.Fragment>\n    );\n  };\n\n  renderNamespaceSelect = (title, namespaceKey, resourceKey) => {\n    const { autocomplete, query } = this.state;\n    const { classes } = this.props;\n\n    return (\n      <React.Fragment>\n        <InputLabel htmlFor={namespaceKey}>{title}</InputLabel>\n        <Select\n          value={autocomplete[namespaceKey].includes(query[namespaceKey]) ? query[namespaceKey] : ''}\n          onChange={this.handleFormChange(namespaceKey, resourceKey)}\n          inputProps={{ name: namespaceKey, id: namespaceKey }}\n          className={classes.selectEmpty}>\n          {\n            _map(autocomplete[namespaceKey], (n, i) => (\n              <MenuItem key={`ns-dr-${i}`} value={n}>{n}</MenuItem>\n            ))\n          }\n        </Select>\n      </React.Fragment>\n    );\n  };\n\n  renderTapButton = (tapInProgress, tapIsClosing) => {\n    const { query } = this.state;\n    const { handleTapStart, handleTapStop } = this.props;\n\n    if (tapIsClosing) {\n      return (\n        <Button variant=\"outlined\" color=\"primary\" className=\"tap-ctrl tap-stop\" disabled>\n          <Trans>buttonStop</Trans>\n        </Button>\n      );\n    } else if (tapInProgress) {\n      return (\n        <Button variant=\"outlined\" color=\"primary\" className=\"tap-ctrl tap-stop\" onClick={handleTapStop}>\n          <Trans>buttonStop</Trans>\n        </Button>\n      );\n    } else {\n      return (\n        <Button\n          color=\"primary\"\n          variant=\"outlined\"\n          className=\"tap-ctrl tap-start\"\n          disabled={!query.namespace || !query.resource}\n          onClick={handleTapStart}>\n          <Trans>buttonStart</Trans>\n        </Button>\n      );\n    }\n  };\n\n  renderTextInput = (title, key, helperText) => {\n    const { query } = this.state;\n    const { classes } = this.props;\n\n    return (\n      <TextField\n        id={key}\n        label={title}\n        className={classes.formControl}\n        value={query[key]}\n        onChange={this.handleFormEvent(key)}\n        helperText={helperText}\n        margin=\"normal\" />\n    );\n  };\n\n  renderAdvancedTapFormContent() {\n    const { query } = this.state;\n    const { classes } = this.props;\n\n    return (\n      <Grid container>\n\n        <Grid container spacing={3}>\n          <Grid item xs={6} md={3} className={classes.formControlWrapper}>\n            <FormControl className={classes.formControl}>\n              {this.renderNamespaceSelect(<Trans>formToNamespace</Trans>, 'toNamespace', 'toResource')}\n            </FormControl>\n          </Grid>\n          <Grid item xs={6} md={3} className={classes.formControlWrapper}>\n            <FormControl className={classes.formControl} disabled={_isEmpty(query.toNamespace)}>\n              {this.renderResourceSelect('toResource', 'toNamespace')}\n            </FormControl>\n          </Grid>\n        </Grid>\n\n        <Grid container spacing={3}>\n          <Grid item xs={6} md={3} className={classes.formControlWrapper}>\n            {this.renderTextInput(<Trans>formScheme</Trans>, 'scheme', <Trans>formSchemeHelpText</Trans>)}\n          </Grid>\n          <Grid item xs={6} md={3} className={classes.formControlWrapper}>\n            {this.renderTextInput(<Trans>formMaxRPS</Trans>, 'maxRps', <Trans>formMaxRPSHelpText {defaultMaxRps}</Trans>)}\n          </Grid>\n          <Grid item xs={6} md={3} className={classes.formControlWrapper}>\n            <FormControl className={classes.formControl}>\n              <InputLabel htmlFor=\"method\"><Trans>formHTTPMethod</Trans></InputLabel>\n              <Select\n                value={query.method}\n                onChange={this.handleFormChange('method')}\n                inputProps={{ name: 'method', id: 'method' }}\n                className={classes.selectEmpty}>\n                {\n                  _map(httpMethods, d => (\n                    <MenuItem key={`method-${d}`} value={d}>{d}</MenuItem>\n                  ))\n                }\n              </Select>\n              <FormHelperText><Trans>formHTTPMethodHelpText</Trans></FormHelperText>\n            </FormControl>\n          </Grid>\n        </Grid>\n\n      </Grid>\n    );\n  }\n\n  renderAdvancedTapForm() {\n    const { advancedFormExpanded } = this.state;\n\n    return (\n      <Accordion expanded={advancedFormExpanded} onChange={this.handleAdvancedFormExpandClick} elevation={3}>\n        <AccordionSummary expandIcon={<ExpandMoreIcon />}>\n          <Typography variant=\"caption\" gutterBottom>\n            {advancedFormExpanded ? <Trans>formHideFilters</Trans> : <Trans>formShowFilters</Trans>}\n          </Typography>\n        </AccordionSummary>\n\n        <AccordionDetails>\n          {this.renderAdvancedTapFormContent()}\n        </AccordionDetails>\n      </Accordion>\n    );\n  }\n\n  render() {\n    const { query } = this.state;\n    const { tapRequestInProgress, tapIsClosing, cmdName, enableAdvancedForm, classes } = this.props;\n\n    return (\n      <Card className={classes.card} elevation={3}>\n        <CardContent>\n          <Grid container spacing={3}>\n            <Grid item xs={6} md=\"auto\" className={classes.formControlWrapper}>\n              <FormControl className={classes.formControl} fullWidth>\n                {this.renderNamespaceSelect(<Trans>formNamespace</Trans>, 'namespace', 'resource')}\n              </FormControl>\n            </Grid>\n\n            <Grid item xs={6} md=\"auto\" className={classes.formControlWrapper}>\n              <FormControl className={classes.formControl} disabled={_isEmpty(query.namespace)} fullWidth>\n                {this.renderResourceSelect('resource', 'namespace')}\n              </FormControl>\n            </Grid>\n\n            <Grid item>\n              {this.renderTapButton(tapRequestInProgress, tapIsClosing)}\n              <Button onClick={this.resetTapForm} disabled={tapRequestInProgress} className={classes.resetButton}>\n                <Trans>buttonReset</Trans>\n              </Button>\n            </Grid>\n          </Grid>\n        </CardContent>\n\n        <QueryToCliCmd cmdName={cmdName} query={query} resource={query.resource} />\n\n        {!enableAdvancedForm ? null : this.renderAdvancedTapForm()}\n\n      </Card>\n    );\n  }\n}\n\nTapQueryForm.propTypes = {\n  cmdName: PropTypes.string.isRequired,\n  currentQuery: tapQueryPropType.isRequired,\n  enableAdvancedForm: PropTypes.bool,\n  handleTapClear: PropTypes.func,\n  handleTapStart: PropTypes.func.isRequired,\n  handleTapStop: PropTypes.func.isRequired,\n  query: tapQueryPropType.isRequired,\n  resourcesByNs: PropTypes.shape({}).isRequired,\n  setQuery: PropTypes.func.isRequired,\n  tapIsClosing: PropTypes.bool,\n  tapRequestInProgress: PropTypes.bool.isRequired,\n  updateQuery: PropTypes.func.isRequired,\n};\n\nTapQueryForm.defaultProps = {\n  enableAdvancedForm: true,\n  handleTapClear: _noop,\n  tapIsClosing: false,\n};\n\nexport default withQueryParams(urlPropsQueryConfig, withStyles(styles)(TapQueryForm));\n"
  },
  {
    "path": "web/app/js/components/Top.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\nimport _each from 'lodash/each';\nimport _get from 'lodash/get';\nimport _reduce from 'lodash/reduce';\nimport { handlePageVisibility, withPageVisibility } from './util/PageVisibility.jsx';\nimport ErrorBanner from './ErrorBanner.jsx';\nimport TapEnabledWarning from './TapEnabledWarning.jsx';\nimport TapQueryForm from './TapQueryForm.jsx';\nimport TopModule from './TopModule.jsx';\nimport { emptyTapQuery } from './util/TapUtils.jsx';\nimport { withContext } from './util/AppContext.jsx';\n\nclass Top extends React.Component {\n  constructor(props) {\n    super(props);\n    this.api = props.api;\n    this.loadFromServer = this.loadFromServer.bind(this);\n    this.updateTapClosingState = this.updateTapClosingState.bind(this);\n\n    this.state = {\n      error: null,\n      resourcesByNs: {},\n      authoritiesByNs: {},\n      query: emptyTapQuery(),\n      pollingInterval: 10000,\n      tapRequestInProgress: false,\n      pendingRequests: false,\n    };\n  }\n\n  componentDidMount() {\n    this.startServerPolling();\n  }\n\n  componentDidUpdate(prevProps) {\n    const { isPageVisible } = this.props;\n    handlePageVisibility({\n      prevVisibilityState: prevProps.isPageVisible,\n      currentVisibilityState: isPageVisible,\n      onVisible: () => this.startServerPolling(),\n      onHidden: () => this.stopServerPolling(),\n    });\n  }\n\n  componentWillUnmount() {\n    this.stopServerPolling();\n  }\n\n  static getResourcesByNs(rsp) {\n    const statTables = _get(rsp, [0, 'ok', 'statTables']);\n    const authoritiesByNs = {};\n    const resourcesByNs = _reduce(statTables, (mem, table) => {\n      _each(table.podGroup.rows, row => {\n        if (row.meshedPodCount === '0') {\n          return;\n        }\n\n        if (!mem[row.resource.namespace]) {\n          mem[row.resource.namespace] = [];\n          authoritiesByNs[row.resource.namespace] = [];\n        }\n\n        switch (row.resource.type.toLowerCase()) {\n          case 'service':\n            break;\n          case 'authority':\n            authoritiesByNs[row.resource.namespace].push(row.resource.name);\n            break;\n          default:\n            mem[row.resource.namespace].push(`${row.resource.type}/${row.resource.name}`);\n        }\n      });\n      return mem;\n    }, {});\n    return {\n      authoritiesByNs,\n      resourcesByNs,\n    };\n  }\n\n  startServerPolling() {\n    const { pollingInterval } = this.state;\n    this.loadFromServer();\n    this.timerId = window.setInterval(this.loadFromServer, pollingInterval);\n  }\n\n  stopServerPolling() {\n    window.clearInterval(this.timerId);\n    this.api.cancelCurrentRequests();\n    this.setState({ pendingRequests: false });\n  }\n\n  loadFromServer() {\n    const { pendingRequests } = this.state;\n\n    if (pendingRequests) {\n      return; // don't make more requests if the ones we sent haven't completed\n    }\n    this.setState({\n      pendingRequests: true,\n    });\n\n    const url = this.api.urlsForResourceNoStats('all');\n    this.api.setCurrentRequests([this.api.fetchMetrics(url)]);\n    Promise.all(this.api.getCurrentPromises())\n      .then(rsp => {\n        const { resourcesByNs, authoritiesByNs } = Top.getResourcesByNs(rsp);\n\n        this.setState({\n          resourcesByNs,\n          authoritiesByNs,\n          pendingRequests: false,\n        });\n      })\n      .catch(this.handleApiError);\n  }\n\n  updateQuery = query => {\n    this.setState({\n      query,\n    });\n  };\n\n  handleTapStart = () => {\n    this.setState({\n      tapRequestInProgress: true,\n    });\n  };\n\n  handleTapStop = () => {\n    this.setState({\n      tapRequestInProgress: false,\n      tapIsClosing: true,\n    });\n  };\n\n  handleTapClear = () => {\n    this.setState({\n      error: null,\n      query: emptyTapQuery(),\n    });\n  };\n\n  updateTapClosingState() {\n    this.setState({\n      tapRequestInProgress: false,\n      tapIsClosing: false,\n    });\n  }\n\n  render() {\n    const { query, resourcesByNs, authoritiesByNs, tapRequestInProgress, tapIsClosing, error } = this.state;\n    const { pathPrefix } = this.props;\n\n    return (\n      <div>\n        {!error ? null :\n        <ErrorBanner message={error} onHideMessage={() => this.setState({ error: null })} />}\n        <TapQueryForm\n          enableAdvancedForm={false}\n          cmdName=\"top\"\n          handleTapStart={this.handleTapStart}\n          handleTapStop={this.handleTapStop}\n          handleTapClear={this.handleTapClear}\n          resourcesByNs={resourcesByNs}\n          authoritiesByNs={authoritiesByNs}\n          tapRequestInProgress={tapRequestInProgress}\n          tapIsClosing={tapIsClosing}\n          updateQuery={this.updateQuery}\n          currentQuery={query} />\n        <TopModule\n          pathPrefix={pathPrefix}\n          query={query}\n          startTap={tapRequestInProgress}\n          tapEnabledWarningComponent={<TapEnabledWarning\n            resource={query.resource}\n            namespace={query.namespace}\n            cardComponent />}\n          updateTapClosingState={this.updateTapClosingState} />\n      </div>\n    );\n  }\n}\n\nTop.propTypes = {\n  api: PropTypes.shape({\n    PrefixedLink: PropTypes.func.isRequired,\n  }).isRequired,\n  isPageVisible: PropTypes.bool.isRequired,\n  pathPrefix: PropTypes.string.isRequired,\n};\n\nexport default withPageVisibility(withContext(Top));\n"
  },
  {
    "path": "web/app/js/components/TopEventTable.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\nimport { Trans } from '@lingui/macro';\nimport _isEmpty from 'lodash/isEmpty';\nimport _isNil from 'lodash/isNil';\nimport BaseTable from './BaseTable.jsx';\nimport SuccessRateMiniChart from './util/SuccessRateMiniChart.jsx';\nimport { formatLatencySec, toShortResourceName } from './util/Utils.js';\nimport { directionColumn, extractDisplayName, srcDstColumn, tapLink } from './util/TapUtils.jsx';\nimport { withContext } from './util/AppContext.jsx';\n\nconst topColumns = (resourceType, ResourceLink, PrefixedLink) => [\n  {\n    title: ' ',\n    dataIndex: 'direction',\n    render: d => directionColumn(d.direction),\n  },\n  {\n    title: <Trans>columnTitleName</Trans>,\n    filter: d => {\n      const [labels, display] = extractDisplayName(d);\n      return _isEmpty(labels[resourceType]) ?\n        display.str :\n        `${toShortResourceName(resourceType)}/${labels[resourceType]}`;\n    },\n    key: 'src-dst',\n    render: d => srcDstColumn(d, resourceType, ResourceLink),\n  },\n  {\n    title: <Trans>columnTitleMethod</Trans>,\n    dataIndex: 'httpMethod',\n    filter: d => d.httpMethod,\n    sorter: d => d.httpMethod,\n  },\n  {\n    title: <Trans>columnTitlePath</Trans>,\n    dataIndex: 'path',\n    filter: d => d.path,\n    sorter: d => d.path,\n  },\n  {\n    title: <Trans>columnTitleCount</Trans>,\n    dataIndex: 'count',\n    isNumeric: true,\n    defaultSortOrder: 'desc',\n    sorter: d => d.count,\n  },\n  {\n    title: <Trans>columnTitleBest</Trans>,\n    dataIndex: 'best',\n    isNumeric: true,\n    render: d => formatLatencySec(d.best),\n    sorter: d => d.best,\n  },\n  {\n    title: <Trans>columnTitleWorst</Trans>,\n    dataIndex: 'worst',\n    isNumeric: true,\n    defaultSortOrder: 'desc',\n    render: d => formatLatencySec(d.worst),\n    sorter: d => d.worst,\n  },\n  {\n    title: <Trans>columnTitleLast</Trans>,\n    dataIndex: 'last',\n    isNumeric: true,\n    render: d => formatLatencySec(d.last),\n    sorter: d => d.last,\n  },\n  {\n    title: <Trans>columnTitleSuccessRate</Trans>,\n    dataIndex: 'successRate',\n    isNumeric: true,\n    render: d => _isNil(d) || _isNil(d.successRate) ? '---' :\n    <SuccessRateMiniChart sr={d.successRate.get()} />,\n    sorter: d => d.successRate.get(),\n  },\n  {\n    title: <Trans>columnTitleTap</Trans>,\n    key: 'tap',\n    isNumeric: true,\n    render: d => tapLink(d, resourceType, PrefixedLink),\n  },\n];\n\nconst TopEventTable = function({ tableRows, resourceType, api }) {\n  const columns = topColumns(resourceType, api.ResourceLink, api.PrefixedLink);\n\n  return (\n    <BaseTable\n      enableFilter\n      tableRows={tableRows}\n      tableColumns={columns}\n      tableClassName=\"metric-table\"\n      defaultOrderBy=\"count\"\n      defaultOrder=\"desc\"\n      padding=\"dense\" />\n  );\n};\n\nTopEventTable.propTypes = {\n  api: PropTypes.shape({\n    PrefixedLink: PropTypes.func.isRequired,\n    ResourceLink: PropTypes.func.isRequired,\n  }).isRequired,\n  resourceType: PropTypes.string.isRequired,\n  tableRows: PropTypes.arrayOf(PropTypes.shape({})),\n};\n\nTopEventTable.defaultProps = {\n  tableRows: [],\n};\n\nexport default withContext(TopEventTable);\n"
  },
  {
    "path": "web/app/js/components/TopModule.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\nimport _cloneDeep from 'lodash/cloneDeep';\nimport _each from 'lodash/each';\nimport _get from 'lodash/get';\nimport _has from 'lodash/has';\nimport _includes from 'lodash/includes';\nimport _isEmpty from 'lodash/isEmpty';\nimport _isEqual from 'lodash/isEqual';\nimport _isNil from 'lodash/isNil';\nimport _noop from 'lodash/noop';\nimport _size from 'lodash/size';\nimport _take from 'lodash/take';\nimport _throttle from 'lodash/throttle';\nimport _values from 'lodash/values';\nimport TopEventTable from './TopEventTable.jsx';\nimport ErrorBanner from './ErrorBanner.jsx';\nimport Percentage from './util/Percentage.js';\nimport { WS_ABNORMAL_CLOSURE, WS_NORMAL_CLOSURE, processNeighborData, processTapEvent, setMaxRps, wsCloseCodes } from './util/TapUtils.jsx';\nimport { withContext } from './util/AppContext.jsx';\n\n// https://github.com/grpc/grpc/blob/master/doc/statuscodes.md\nconst grpcErrorStatusCodes = [2, 4, 13, 14, 15];\n\nclass TopModule extends React.Component {\n  constructor(props) {\n    super(props);\n    this.tapResultsById = {};\n    this.topEventIndex = {};\n    this.throttledWebsocketRecvHandler = _throttle(this.updateTapEventIndexState, 500);\n    this.updateTapClosingState = props.updateTapClosingState;\n    this.unmeshedSources = {};\n\n    this.state = {\n      error: null,\n      topEventIndex: {},\n      showTapEnabledWarning: false,\n    };\n  }\n\n  componentDidMount() {\n    const { startTap } = this.props;\n    if (startTap) {\n      this.startTapStreaming();\n    }\n  }\n\n  componentDidUpdate(prevProps) {\n    const { startTap, query } = this.props;\n    if (startTap && !prevProps.startTap) {\n      this.startTapStreaming();\n    }\n    if (!startTap && prevProps.startTap) {\n      this.stopTapStreaming();\n    }\n    if (!_isEqual(query, prevProps.query)) {\n      this.clearTopTable();\n    }\n  }\n\n  componentWillUnmount() {\n    this.throttledWebsocketRecvHandler.cancel();\n    this.stopTapStreaming();\n    this.updateTapClosingState = _noop;\n  }\n\n  onWebsocketOpen = () => {\n    const { query } = this.props;\n    const tapQuery = _cloneDeep(query);\n    setMaxRps(tapQuery);\n\n    this.ws.send(JSON.stringify({\n      id: 'top-web',\n      ...tapQuery,\n    }));\n    this.setState({\n      error: null,\n    });\n  };\n\n  onWebsocketRecv = e => {\n    this.indexTapResult(e.data);\n    this.throttledWebsocketRecvHandler();\n  };\n\n  onWebsocketClose = e => {\n    this.updateTapClosingState();\n    /* We ignore any abnormal closure since it doesn't matter as long as\n    the connection to the websocket is closed. This is also a workaround\n    where Chrome browsers incorrectly displays a 1006 close code\n    https://github.com/linkerd/linkerd2/issues/1630\n    */\n    if (e.code !== WS_NORMAL_CLOSURE && e.code !== WS_ABNORMAL_CLOSURE) {\n      if (e.reason.includes('no pods to tap')) {\n        this.setState({\n          showTapEnabledWarning: true,\n        });\n      } else {\n        this.setState({\n          error: {\n            error: `Websocket close error [${e.code}: ${wsCloseCodes[e.code]}] ${e.reason ? ':' : ''} ${e.reason}`,\n          },\n        });\n      }\n    }\n  };\n\n  onWebsocketError = e => {\n    this.setState({\n      error: { error: `Websocket error: ${e.message}` },\n    });\n  };\n\n  closeWebSocket = () => {\n    if (this.ws) {\n      this.ws.close(1000);\n    }\n  };\n\n  static parseTapResult(data) {\n    const d = processTapEvent(data);\n\n    if (d.eventType === 'responseEnd') {\n      d.latency = parseFloat(d.http.responseEnd.sinceRequestInit.replace('s', ''));\n      d.completed = true;\n    }\n\n    return d;\n  }\n\n  static topEventKey(event) {\n    const sourceKey = event.source.owner || event.source.pod || event.source.str;\n    const dstKey = event.destination.owner || event.destination.pod || event.destination.str;\n\n    return [sourceKey, dstKey, _get(event, 'http.requestInit.method.registered'), event.http.requestInit.path].join('_');\n  }\n\n  static initialTopResult(d, eventKey) {\n    // in the event that we key on resources with multiple pods/ips, store them so we can display\n    const sourceDisplay = {\n      ips: {},\n      pods: {},\n    };\n    sourceDisplay.ips[d.base.source.str] = true;\n    if (!_isNil(d.base.source.pod)) {\n      sourceDisplay.pods[d.base.source.pod] = d.base.source.namespace;\n    }\n\n    const destinationDisplay = {\n      ips: {},\n      pods: {},\n    };\n    destinationDisplay.ips[d.base.destination.str] = true;\n    if (!_isNil(d.base.destination.pod)) {\n      destinationDisplay.pods[d.base.destination.pod] = d.base.destination.namespace;\n    }\n\n    return {\n      count: 1,\n      best: d.responseEnd.latency,\n      worst: d.responseEnd.latency,\n      last: d.responseEnd.latency,\n      success: !d.success ? 0 : 1,\n      failure: !d.success ? 1 : 0,\n      meshed: true,\n      successRate: !d.success ? new Percentage(0, 1) : new Percentage(1, 1),\n      direction: d.base.proxyDirection,\n      source: d.requestInit.source,\n      sourceLabels: d.requestInit.sourceMeta.labels,\n      sourceDisplay,\n      destination: d.requestInit.destination,\n      destinationLabels: d.requestInit.destinationMeta.labels,\n      destinationDisplay,\n      httpMethod: _get(d, 'requestInit.http.requestInit.method.registered'),\n      path: d.requestInit.http.requestInit.path,\n      key: eventKey,\n      lastUpdated: Date.now(),\n    };\n  }\n\n  static incrementTopResult(d, result) {\n    result.count += 1;\n    if (!d.success) {\n      result.failure += 1;\n    } else {\n      result.success += 1;\n    }\n    result.successRate = new Percentage(result.success, result.success + result.failure);\n\n    result.last = d.responseEnd.latency;\n    if (d.responseEnd.latency < result.best) {\n      result.best = d.responseEnd.latency;\n    }\n    if (d.responseEnd.latency > result.worst) {\n      result.worst = d.responseEnd.latency;\n    }\n\n    result.sourceDisplay.ips[d.base.source.str] = true;\n    if (!_isNil(d.requestInit.sourceMeta.labels.pod)) {\n      result.sourceDisplay.pods[d.requestInit.sourceMeta.labels.pod] = d.requestInit.sourceMeta.labels.namespace;\n    }\n    result.destinationDisplay.ips[d.base.destination.str] = true;\n    if (!_isNil(d.requestInit.destinationMeta.labels.pod)) {\n      result.destinationDisplay.pods[d.requestInit.destinationMeta.labels.pod] = d.requestInit.destinationMeta.labels.namespace;\n    }\n\n    result.lastUpdated = Date.now();\n  }\n\n  indexTopResult = (d, topResults) => {\n    const { query, maxRowsToStore } = this.props;\n\n    // only index if have the full request (i.e. init and end)\n    if (!d.requestInit) {\n      return topResults;\n    }\n\n    const eventKey = TopModule.topEventKey(d.requestInit);\n    TopModule.addSuccessCount(d);\n\n    if (!topResults[eventKey]) {\n      topResults[eventKey] = TopModule.initialTopResult(d, eventKey);\n    } else {\n      TopModule.incrementTopResult(d, topResults[eventKey]);\n    }\n\n    if (_size(topResults) > maxRowsToStore) {\n      TopModule.deleteOldestIndexedResult(topResults);\n    }\n\n    if (d.base.proxyDirection === 'INBOUND') {\n      this.updateNeighborsFromTapData(d.requestInit.source, _get(d, 'requestInit.sourceMeta.labels'));\n      const isPod = query.resource.split('/')[0] === 'pod';\n      if (isPod) {\n        topResults[eventKey].meshed = !_has(this.unmeshedSources, `pod/${d.base.source.pod}`);\n      } else {\n        topResults[eventKey].meshed = !_isEmpty(d.base.source.owner) && !_has(this.unmeshedSources, d.base.source.owner);\n      }\n    }\n\n    return topResults;\n  };\n\n  updateTapEventIndexState = () => {\n    // tap websocket events come in at a really high, bursty rate\n    // calling setState every time an event comes in causes a lot of re-rendering\n    // and causes the page to freeze. To fix this, limit the times we\n    // update the state (and thus trigger a render)\n    this.setState({\n      topEventIndex: this.topEventIndex,\n    });\n  };\n\n  updateNeighborsFromTapData = (source, sourceLabels) => {\n    const { query, updateUnmeshedSources } = this.props;\n\n    // store this outside of state, as updating the state upon every websocket event received\n    // is very costly and causes the page to freeze up\n    const resourceType = _isNil(query.resource) ? '' : query.resource.split('/')[0];\n    this.unmeshedSources = processNeighborData(source, sourceLabels, this.unmeshedSources, resourceType);\n    updateUnmeshedSources(this.unmeshedSources);\n  };\n\n  // keep an index of tap results by id until the request is complete.\n  // when the request has completed, add it to the aggregated Top counts and\n  // discard the individual tap result\n  indexTapResult = data => {\n    const { maxRowsToStore } = this.props;\n\n    const resultIndex = this.tapResultsById;\n    const d = TopModule.parseTapResult(data);\n\n    if (_isNil(resultIndex[d.id])) {\n      // don't let tapResultsById grow unbounded\n      if (_size(resultIndex) > maxRowsToStore) {\n        TopModule.deleteOldestIndexedResult(resultIndex);\n      }\n\n      resultIndex[d.id] = {};\n    }\n    resultIndex[d.id][d.eventType] = d;\n\n    // assumption: requests of a given id all share the same high level metadata\n    resultIndex[d.id].base = d;\n    resultIndex[d.id].lastUpdated = Date.now();\n\n    if (d.completed) {\n      // only add results into top if the request has completed\n      // we can also now delete this result from the Tap result index\n      this.topEventIndex = this.indexTopResult(resultIndex[d.id], this.topEventIndex);\n      delete resultIndex[d.id];\n    }\n  };\n\n  static addSuccessCount(d) {\n    // cope with the fact that gRPC failures are returned with HTTP status 200\n    // and correctly classify gRPC failures as failures\n    let success = parseInt(_get(d, 'responseInit.http.responseInit.httpStatus'), 10) < 500;\n    if (success) {\n      const grpcStatusCode = _get(d, 'responseEnd.http.responseEnd.eos.grpcStatusCode');\n      if (!_isNil(grpcStatusCode)) {\n        success = !_includes(grpcErrorStatusCodes, grpcStatusCode);\n      } else if (!_isNil(_get(d, 'responseEnd.http.responseEnd.eos.resetErrorCode'))) {\n        success = false;\n      }\n    }\n\n    d.success = success;\n  }\n\n  static deleteOldestIndexedResult(resultIndex) {\n    let oldest = Date.now();\n    let oldestId = '';\n\n    _each(resultIndex, (res, id) => {\n      if (res.lastUpdated < oldest) {\n        oldest = res.lastUpdated;\n        oldestId = id;\n      }\n    });\n\n    delete resultIndex[oldestId];\n  }\n\n  clearTopTable() {\n    this.tapResultsById = {};\n    this.topEventIndex = {};\n    this.setState({\n      topEventIndex: {},\n      showTapEnabledWarning: false,\n    });\n  }\n\n  startTapStreaming() {\n    const { pathPrefix } = this.props;\n\n    this.clearTopTable();\n\n    const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';\n    const tapWebSocket = `${protocol}://${window.location.host}${pathPrefix}/api/tap`;\n\n    this.ws = new WebSocket(tapWebSocket);\n    this.ws.onmessage = this.onWebsocketRecv;\n    this.ws.onclose = this.onWebsocketClose;\n    this.ws.onopen = this.onWebsocketOpen;\n    this.ws.onerror = this.onWebsocketError;\n  }\n\n  stopTapStreaming() {\n    this.closeWebSocket();\n  }\n\n  banner = () => {\n    const { error } = this.state;\n    return error ? <ErrorBanner message={error} /> : null;\n  };\n\n  render() {\n    const { topEventIndex, showTapEnabledWarning } = this.state;\n    const { query, maxRowsToDisplay, tapEnabledWarningComponent } = this.props;\n\n    const tableRows = _take(_values(topEventIndex), maxRowsToDisplay);\n    const resourceType = _isNil(query.resource) ? '' : query.resource.split('/')[0];\n\n    return (\n      <React.Fragment>\n        {this.banner()}\n        {showTapEnabledWarning && tapEnabledWarningComponent}\n        {!showTapEnabledWarning && <TopEventTable resourceType={resourceType} tableRows={tableRows} />}\n      </React.Fragment>\n    );\n  }\n}\n\nTopModule.propTypes = {\n  maxRowsToDisplay: PropTypes.number,\n  maxRowsToStore: PropTypes.number,\n  pathPrefix: PropTypes.string.isRequired,\n  query: PropTypes.shape({\n    resource: PropTypes.string,\n    namespace: PropTypes.string,\n  }),\n  startTap: PropTypes.bool.isRequired,\n  tapEnabledWarningComponent: PropTypes.node,\n  updateTapClosingState: PropTypes.func,\n  updateUnmeshedSources: PropTypes.func,\n};\n\nTopModule.defaultProps = {\n  // max aggregated top rows to index and display in table\n  maxRowsToDisplay: 40,\n  // max rows to keep in index. there are two indexes we keep:\n  // - un-ended tap results, pre-aggregation into the top counts\n  // - aggregated top rows\n  maxRowsToStore: 50,\n  updateTapClosingState: _noop,\n  updateUnmeshedSources: _noop,\n  query: {\n    resource: '',\n    namespace: '',\n  },\n  tapEnabledWarningComponent: null,\n};\n\nexport default withContext(TopModule);\n"
  },
  {
    "path": "web/app/js/components/TopRoutes.jsx",
    "content": "import { StringParam, withQueryParams } from 'use-query-params';\n\nimport Button from '@material-ui/core/Button';\nimport Card from '@material-ui/core/Card';\nimport CardContent from '@material-ui/core/CardContent';\nimport Divider from '@material-ui/core/Divider';\nimport FormControl from '@material-ui/core/FormControl';\nimport FormHelperText from '@material-ui/core/FormHelperText';\nimport Grid from '@material-ui/core/Grid';\nimport InputLabel from '@material-ui/core/InputLabel';\nimport MenuItem from '@material-ui/core/MenuItem';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Select from '@material-ui/core/Select';\nimport { Trans } from '@lingui/macro';\nimport Typography from '@material-ui/core/Typography';\nimport _get from 'lodash/get';\nimport _isEmpty from 'lodash/isEmpty';\nimport _merge from 'lodash/merge';\nimport _pick from 'lodash/pick';\nimport _uniq from 'lodash/uniq';\nimport { withStyles } from '@material-ui/core/styles';\nimport { groupResourcesByNs } from './util/MetricUtils.jsx';\nimport { tapResourceTypes } from './util/TapUtils.jsx';\nimport { withContext } from './util/AppContext.jsx';\nimport TopRoutesModule from './TopRoutesModule.jsx';\nimport QueryToCliCmd from './QueryToCliCmd.jsx';\nimport ErrorBanner from './ErrorBanner.jsx';\nimport ConfigureProfilesMsg from './ConfigureProfilesMsg.jsx';\nimport { handlePageVisibility, withPageVisibility } from './util/PageVisibility.jsx';\n\nconst topRoutesQueryProps = {\n  resource_name: PropTypes.string,\n  resource_type: PropTypes.string,\n  namespace: PropTypes.string,\n  to_name: PropTypes.string,\n  to_type: PropTypes.string,\n  to_namespace: PropTypes.string,\n};\nconst topRoutesQueryPropType = PropTypes.shape(topRoutesQueryProps);\n\nconst topRoutesQueryConfig = {};\nObject.keys(topRoutesQueryProps).forEach(value => {\n  topRoutesQueryConfig[value] = StringParam;\n});\n\nconst toResourceName = (query, typeKey, nameKey) => {\n  return `${query[typeKey] || ''}${!query[nameKey] ? '' : '/'}${query[nameKey] || ''}`;\n};\n\nconst styles = theme => ({\n  root: {\n    marginTop: theme.spacing(3),\n    marginBottom: theme.spacing(1),\n  },\n  formControl: {\n    minWidth: 200,\n  },\n});\n\nclass TopRoutes extends React.Component {\n  constructor(props) {\n    super(props);\n    this.api = props.api;\n\n    const query = _merge({\n      resource_name: '',\n      resource_type: '',\n      namespace: '',\n      to_name: '',\n      to_type: '',\n      to_namespace: '',\n    }, _pick(props.query, Object.keys(topRoutesQueryProps)));\n\n    this.state = {\n      query,\n      error: null,\n      services: [],\n      namespaces: ['default'],\n      resourcesByNs: {},\n      pollingInterval: 5000,\n      pendingRequests: false,\n      requestInProgress: false,\n    };\n  }\n\n  componentDidMount() {\n    this._isMounted = true; // https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html\n    this.startServerPolling();\n  }\n\n  componentDidUpdate(prevProps) {\n    const { isPageVisible } = this.props;\n    handlePageVisibility({\n      prevVisibilityState: prevProps.isPageVisible,\n      currentVisibilityState: isPageVisible,\n      onVisible: () => this.startServerPolling(),\n      onHidden: () => this.stopServerPolling(),\n    });\n  }\n\n  componentWillUnmount() {\n    this._isMounted = false;\n    this.stopServerPolling();\n  }\n\n  loadFromServer = () => {\n    const { pendingRequests } = this.state;\n\n    if (pendingRequests) {\n      return; // don't make more requests if the ones we sent haven't completed\n    }\n    this.setState({ pendingRequests: true });\n\n    const allMetricsUrl = this.api.urlsForResourceNoStats('all');\n    this.api.setCurrentRequests([\n      this.api.fetchServices(),\n      this.api.fetchMetrics(allMetricsUrl),\n    ]);\n\n    Promise.all(this.api.getCurrentPromises())\n      .then(([svcList, allMetrics]) => {\n        const services = _get(svcList, 'services', []);\n        const namespaces = _uniq(services.map(s => s.namespace));\n        const { resourcesByNs } = groupResourcesByNs(allMetrics);\n\n        this.setState({\n          services,\n          namespaces,\n          resourcesByNs,\n          pendingRequests: false,\n          error: null,\n        });\n      })\n      .catch(this.handleApiError);\n  };\n\n  startServerPolling = () => {\n    const { pollingInterval } = this.state;\n    this.loadFromServer();\n    this.timerId = window.setInterval(this.loadFromServer, pollingInterval);\n  };\n\n  stopServerPolling = () => {\n    window.clearInterval(this.timerId);\n    this.api.cancelCurrentRequests();\n    this.setState({ pendingRequests: false });\n  };\n\n  handleBtnClick = inProgress => () => {\n    this.setState({\n      requestInProgress: inProgress,\n    });\n  };\n\n  // Each time state.query is updated, this method calls setQuery provided\n  // by useQueryParams HOC to partially update url query params that have\n  // changed\n  handleUrlUpdate = query => {\n    const { setQuery } = this.props;\n    setQuery({ ...query });\n  };\n\n  handleNamespaceSelect = nsKey => e => {\n    const { query } = this.state;\n    const newQuery = query;\n    const formVal = _get(e, 'target.value');\n    newQuery[nsKey] = formVal;\n    this.handleUrlUpdate(newQuery);\n    this.setState({ query: newQuery });\n  };\n\n  handleResourceSelect = (nameKey, typeKey) => e => {\n    const { query } = this.state;\n    const resource = _get(e, 'target.value');\n    const [resourceType, resourceName] = resource.split('/');\n\n    query[nameKey] = resourceName || '';\n    query[typeKey] = resourceType;\n\n    this.handleUrlUpdate(query);\n    this.setState({ query });\n  };\n\n  renderRoutesQueryForm = () => {\n    const { query, requestInProgress } = this.state;\n    const { classes } = this.props;\n\n    return (\n      <CardContent>\n        <Grid container direction=\"column\" spacing={2}>\n          <Grid item container spacing={4} alignItems=\"center\" justifyContent=\"flex-start\">\n            <Grid item>\n              {this.renderNamespaceDropdown(<Trans>formNamespace</Trans>, 'namespace', <Trans>formNamespaceHelpText</Trans>)}\n            </Grid>\n\n            <Grid item>\n              {this.renderResourceDropdown(<Trans>formResource</Trans>, 'resource_name', 'resource_type', <Trans>formResourceHelpText</Trans>)}\n            </Grid>\n\n            <Grid item>\n              <Button\n                color=\"primary\"\n                variant=\"outlined\"\n                disabled={requestInProgress || !query.namespace || !query.resource_type}\n                onClick={this.handleBtnClick(true)}>\n                <Trans>buttonStart</Trans>\n              </Button>\n            </Grid>\n\n            <Grid item>\n              <Button\n                color=\"default\"\n                variant=\"outlined\"\n                disabled={!requestInProgress}\n                onClick={this.handleBtnClick(false)}>\n                <Trans>buttonStop</Trans>\n              </Button>\n            </Grid>\n          </Grid>\n\n          <Grid item container spacing={4} alignItems=\"center\" justifyContent=\"flex-start\">\n            <Grid item>\n              {this.renderNamespaceDropdown(<Trans>formToNamespace</Trans>, 'to_namespace', <Trans>formToNamespaceHelpText</Trans>)}\n            </Grid>\n\n            <Grid item>\n              {this.renderResourceDropdown(<Trans>formToResource</Trans>, 'to_name', 'to_type', <Trans>formToResourceHelpText</Trans>)}\n            </Grid>\n          </Grid>\n        </Grid>\n        <Divider light className={classes.root} />\n        <Typography variant=\"caption\"><Trans>createNewProfileMsg</Trans> <ConfigureProfilesMsg showAsIcon /></Typography>\n      </CardContent>\n    );\n  };\n\n  renderNamespaceDropdown = (title, key, helperText) => {\n    const { query, namespaces } = this.state;\n    const { classes } = this.props;\n\n    return (\n      <FormControl className={classes.formControl}>\n        <InputLabel htmlFor={`${key}-dropdown`}>{title}</InputLabel>\n        <Select\n          value={namespaces.includes(query[key]) ? query[key] : ''}\n          onChange={this.handleNamespaceSelect(key)}\n          inputProps={{\n            name: key,\n            id: `${key}-dropdown`,\n          }}\n          name={key}>\n          {\n            namespaces.sort().map(ns => {\n              return <MenuItem key={`namespace-${ns}`} value={ns}>{ns}</MenuItem>;\n            })\n          }\n        </Select>\n        <FormHelperText>{helperText}</FormHelperText>\n      </FormControl>\n    );\n  };\n\n  renderResourceDropdown = (title, nameKey, typeKey, helperText) => {\n    const { query, services, resourcesByNs } = this.state;\n    const { classes } = this.props;\n\n    let nsFilterKey = 'namespace';\n    if (typeKey === 'to_type') {\n      nsFilterKey = 'to_namespace';\n    }\n\n    const servicesWithPrefix = services\n      .filter(s => s[nsFilterKey] === query[nsFilterKey])\n      .map(svc => `service/${svc.name}`);\n    const otherResources = resourcesByNs[query[nsFilterKey]] || [];\n\n    let dropdownOptions = servicesWithPrefix\n      .concat(otherResources)\n      .concat(tapResourceTypes)\n      .sort();\n\n    const dropdownVal = toResourceName(query, typeKey, nameKey);\n\n    if (_isEmpty(dropdownOptions) && !_isEmpty(dropdownVal)) {\n      dropdownOptions = [dropdownVal]; // populate from url if autocomplete hasn't loaded\n    }\n\n    return (\n      <FormControl className={classes.formControl}>\n        <InputLabel htmlFor={`${nameKey}-dropdown`}>{title}</InputLabel>\n        <Select\n          value={dropdownOptions.includes(dropdownVal) ? dropdownVal : ''}\n          onChange={this.handleResourceSelect(nameKey, typeKey)}\n          disabled={_isEmpty(query.namespace)}\n          inputProps={{\n            name: nameKey,\n            id: `${nameKey}-dropdown`,\n          }}\n          name={nameKey}>\n          {\n            dropdownOptions.map(resource => <MenuItem key={resource} value={resource}>{resource}</MenuItem>)\n          }\n        </Select>\n        <FormHelperText>{helperText}</FormHelperText>\n      </FormControl>\n    );\n  };\n\n  render() {\n    const { query, requestInProgress, error } = this.state;\n    const emptyQuery = _isEmpty(query.resource_type);\n    const cliQueryToDisplay = _merge({}, query, { toResource: toResourceName(query, 'to_type', 'to_name'), toNamespace: query.to_namespace });\n\n    return (\n      <div>\n        {\n          !error ? null :\n          <ErrorBanner message={error} onHideMessage={() => this.setState({ error: null })} />\n        }\n        <Card elevation={3}>\n          {this.renderRoutesQueryForm()}\n          {\n            emptyQuery ? null :\n            <QueryToCliCmd\n              cmdName=\"routes\"\n              query={cliQueryToDisplay}\n              resource={toResourceName(query, 'resource_type', 'resource_name')} />\n          }\n          {!requestInProgress || !this._isMounted ? null : <TopRoutesModule query={query} />}\n        </Card>\n      </div>\n    );\n  }\n}\n\nTopRoutes.propTypes = {\n  api: PropTypes.shape({\n    PrefixedLink: PropTypes.func.isRequired,\n  }).isRequired,\n  isPageVisible: PropTypes.bool.isRequired,\n  query: topRoutesQueryPropType.isRequired,\n  setQuery: PropTypes.func.isRequired,\n};\n\nexport default withPageVisibility(withQueryParams(topRoutesQueryConfig, (withContext(withStyles(styles, { withTheme: true })(TopRoutes)))));\n"
  },
  {
    "path": "web/app/js/components/TopRoutesModule.jsx",
    "content": "import CardContent from '@material-ui/core/CardContent';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Typography from '@material-ui/core/Typography';\nimport _get from 'lodash/get';\nimport _isEmpty from 'lodash/isEmpty';\nimport _sortBy from 'lodash/sortBy';\nimport TopRoutesTable from './TopRoutesTable.jsx';\nimport Spinner from './util/Spinner.jsx';\nimport ErrorBanner from './ErrorBanner.jsx';\nimport ConfigureProfilesMsg from './ConfigureProfilesMsg.jsx';\nimport { apiErrorPropType } from './util/ApiHelpers.jsx';\nimport { processTopRoutesResults } from './util/MetricUtils.jsx';\nimport withREST from './util/withREST.jsx';\n\nconst TopRoutesBase = function({ data, loading, error }) {\n  let results = _get(data, '[0].ok.routes', []);\n  results = _sortBy(results, o => o.resource);\n\n  const metricsByResource = results.map(r => {\n    return {\n      resource: r.resource,\n      rows: processTopRoutesResults(r.rows),\n    };\n  });\n\n  return (\n    <React.Fragment>\n      {loading ? <Spinner /> : null}\n      {error ? <ErrorBanner message={error} /> : null}\n      {\n        metricsByResource.map(metric => {\n          return (\n            <CardContent key={metric.resource}>\n              <Typography variant=\"h5\">{metric.resource}</Typography>\n              <TopRoutesTable rows={metric.rows} />\n            </CardContent>\n          );\n        })\n      }\n      { !loading && _isEmpty(metricsByResource) ? <ConfigureProfilesMsg /> : null }\n    </React.Fragment>\n  );\n};\n\nTopRoutesBase.defaultProps = {\n  error: null,\n};\n\nTopRoutesBase.propTypes = {\n  data: PropTypes.arrayOf(PropTypes.shape({})).isRequired,\n  error: apiErrorPropType,\n  loading: PropTypes.bool.isRequired,\n  query: PropTypes.shape({}).isRequired,\n};\n\nexport default withREST(\n  TopRoutesBase,\n  ({ api, query }) => {\n    const queryParams = new URLSearchParams(query).toString();\n    return [api.fetchMetrics(`/api/routes?${queryParams}`)];\n  },\n);\n"
  },
  {
    "path": "web/app/js/components/TopRoutesTable.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\nimport { Trans } from '@lingui/macro';\nimport SuccessRateMiniChart from './util/SuccessRateMiniChart.jsx';\nimport BaseTable from './BaseTable.jsx';\nimport { metricToFormatter } from './util/Utils.js';\n\nconst routesColumns = [\n  {\n    title: <Trans>columnTitleRoute</Trans>,\n    dataIndex: 'route',\n    filter: d => d.route,\n    sorter: d => d.route,\n  },\n  {\n    title: <Trans>columnTitleService</Trans>,\n    tooltip: 'hostname:port used when communicating with this target',\n    dataIndex: 'authority',\n    filter: d => d.authority,\n    sorter: d => d.authority,\n  },\n  {\n    title: <Trans>columnTitleSuccessRate</Trans>,\n    dataIndex: 'successRate',\n    isNumeric: true,\n    render: d => <SuccessRateMiniChart sr={d.successRate} />,\n    sorter: d => d.successRate,\n  },\n  {\n    title: <Trans>columnTitleRPS</Trans>,\n    dataIndex: 'requestRate',\n    isNumeric: true,\n    render: d => metricToFormatter.NO_UNIT(d.requestRate),\n    sorter: d => d.requestRate,\n  },\n  {\n    title: <Trans>columnTitleP50Latency</Trans>,\n    dataIndex: 'latency.P50',\n    isNumeric: true,\n    render: d => metricToFormatter.LATENCY(d.latency.P50),\n    sorter: d => d.latency.P50,\n  },\n  {\n    title: <Trans>columnTitleP95Latency</Trans>,\n    dataIndex: 'latency.P95',\n    isNumeric: true,\n    render: d => metricToFormatter.LATENCY(d.latency.P95),\n    sorter: d => d.latency.P95,\n  },\n  {\n    title: <Trans>columnTitleP99Latency</Trans>,\n    dataIndex: 'latency.P99',\n    isNumeric: true,\n    render: d => metricToFormatter.LATENCY(d.latency.P99),\n    sorter: d => d.latency.P99,\n  },\n];\n\nconst TopRoutesTable = function TopRoutesTable({ rows }) {\n  return (\n    <BaseTable\n      enableFilter\n      tableRows={rows}\n      tableColumns={routesColumns}\n      tableClassName=\"metric-table\"\n      defaultOrderBy=\"route\"\n      rowKey={r => r.route + r.authority}\n      padding=\"dense\" />\n  );\n};\n\nTopRoutesTable.propTypes = {\n  rows: PropTypes.arrayOf(PropTypes.shape({})),\n};\n\nTopRoutesTable.defaultProps = {\n  rows: [],\n};\n\nexport default TopRoutesTable;\n"
  },
  {
    "path": "web/app/js/components/TopRoutesTable.test.js",
    "content": "import _merge from 'lodash/merge';\nimport ApiHelpers from './util/ApiHelpers.jsx';\nimport TopRoutesTable from './TopRoutesTable.jsx';\nimport { i18nAndRouterWrap } from '../../test/testHelpers.jsx';\nimport { mount } from 'enzyme';\n\ndescribe(\"Tests for <TopRoutesTable>\", () => {\n  const defaultProps = {\n    api: ApiHelpers(\"\"),\n  };\n\n  it(\"renders the table with all columns\", () => {\n    let extraProps = _merge({}, defaultProps, {\n      rows: [{\n        route: \"[DEFAULT]\",\n        latency: {\n          P50: 133,\n          P95: 291,\n          P99: 188\n        },\n        authority: \"webapp\",\n        name: \"authors:7001\"\n      }],\n    });\n    const component = mount(i18nAndRouterWrap(TopRoutesTable, extraProps));\n\n    const table = component.find(\"BaseTable\");\n    expect(table).toBeDefined();\n    expect(table.html()).toContain(\"[DEFAULT]\");\n    expect(table.props().tableRows).toHaveLength(1);\n    expect(table.props().tableColumns).toHaveLength(7);\n  });\n\n  it(\"if enableFilter is true, user can filter rows by search term\", () => {\n    let extraProps = _merge({}, defaultProps, {\n      rows: [{\n        route: \"[DEFAULT]\",\n        latency: {\n          P50: 133,\n          P95: 291,\n          P99: 188\n        },\n        authority: \"authors:7001\"\n      },{\n        route: \"[DEFAULT]\",\n        latency: {\n          P50: 500,\n          P95: 1,\n          P99: 2\n        },\n        authority: \"localhost:6443\"\n      }],\n      enableFilter: true\n    });\n    const component = mount(i18nAndRouterWrap(TopRoutesTable, extraProps));\n\n    const table = component.find(\"BaseTable\");\n\n    const enableFilter = table.prop(\"enableFilter\");\n\n    expect(enableFilter).toEqual(true);\n    expect(table.html()).toContain(\"authors:7001\");\n    expect(table.html()).toContain(\"localhost\");\n    table.setState({ showFilter: true, filterBy: /localhost/ });\n    component.update();\n    expect(table.html()).not.toContain(\"authors:7001\");\n    expect(table.html()).toContain(\"localhost\");\n  });\n});\n"
  },
  {
    "path": "web/app/js/components/TopRoutesTabs.jsx",
    "content": "import AppBar from '@material-ui/core/AppBar';\nimport Paper from '@material-ui/core/Paper';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Tab from '@material-ui/core/Tab';\nimport Tabs from '@material-ui/core/Tabs';\nimport { Trans } from '@lingui/macro';\nimport _isEmpty from 'lodash/isEmpty';\nimport _noop from 'lodash/noop';\nimport { withStyles } from '@material-ui/core/styles';\nimport TopRoutesModule from './TopRoutesModule.jsx';\nimport TopModule from './TopModule.jsx';\nimport TapEnabledWarning from './TapEnabledWarning.jsx';\nimport QueryToCliCmd from './QueryToCliCmd.jsx';\nimport ConfigureProfilesMsg from './ConfigureProfilesMsg.jsx';\n\nconst styles = theme => ({\n  root: {\n    flexGrow: 1,\n    backgroundColor: theme.palette.background.paper,\n    marginBottom: theme.spacing(3),\n  },\n});\n\nclass TopRoutesTabs extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      value: 0,\n    };\n  }\n\n  handleChange = (_, value) => {\n    this.setState({ value });\n  };\n\n  renderTopComponent() {\n    const { disableTop, query, pathPrefix, updateUnmeshedSources } = this.props;\n    if (disableTop) {\n      return null;\n    }\n\n    const topQuery = {\n      resource: `${query.resourceType}/${query.resourceName}`,\n      namespace: query.namespace,\n    };\n\n    return (\n      <React.Fragment>\n        <TopModule\n          pathPrefix={pathPrefix}\n          query={topQuery}\n          startTap\n          updateUnmeshedSources={updateUnmeshedSources}\n          maxRowsToDisplay={10}\n          tapEnabledWarningComponent={<TapEnabledWarning\n            resource={topQuery.resource}\n            namespace={topQuery.namespace} />} />\n        <QueryToCliCmd cmdName=\"top\" query={topQuery} resource={topQuery.resource} />\n      </React.Fragment>\n    );\n  }\n\n  renderRoutesComponent() {\n    const { query } = this.props;\n\n    if (_isEmpty(query)) {\n      return <ConfigureProfilesMsg />;\n    }\n\n    const routesQuery = {\n      resource_name: query.resourceName,\n      resource_type: query.resourceType,\n      namespace: query.namespace,\n    };\n    const resource = `${query.resourceType}/${query.resourceName}`;\n\n    return (\n      <React.Fragment>\n        <TopRoutesModule query={routesQuery} />\n        <QueryToCliCmd cmdName=\"routes\" query={routesQuery} resource={resource} />\n      </React.Fragment>\n    );\n  }\n\n  render() {\n    const { classes } = this.props;\n    const { value } = this.state;\n\n    return (\n      <Paper className={classes.root} elevation={3}>\n        <AppBar position=\"static\" className={classes.root}>\n\n          <Tabs\n            value={value}\n            onChange={this.handleChange}\n            indicatorColor=\"primary\"\n            textColor=\"primary\">\n            <Tab label={<Trans>tabLiveCalls</Trans>} />\n            <Tab label={<Trans>tabRouteMetrics</Trans>} />\n          </Tabs>\n        </AppBar>\n        {value === 0 && this.renderTopComponent()}\n        {value === 1 && this.renderRoutesComponent()}\n      </Paper>\n    );\n  }\n}\n\nTopRoutesTabs.defaultProps = {\n  disableTop: false,\n  query: {},\n  updateUnmeshedSources: _noop,\n};\n\nexport const topRoutesQueryPropType = PropTypes.shape({\n  namespace: PropTypes.string,\n  resourceType: PropTypes.string,\n  resourceName: PropTypes.string,\n});\n\nTopRoutesTabs.propTypes = {\n  disableTop: PropTypes.bool,\n  pathPrefix: PropTypes.string.isRequired,\n  query: topRoutesQueryPropType,\n  theme: PropTypes.shape({}).isRequired,\n  updateUnmeshedSources: PropTypes.func,\n};\n\nexport default withStyles(styles, { withTheme: true })(TopRoutesTabs);\n"
  },
  {
    "path": "web/app/js/components/Version.jsx",
    "content": "import Button from '@material-ui/core/Button';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { Trans } from '@lingui/macro';\nimport Typography from '@material-ui/core/Typography';\nimport { withStyles } from '@material-ui/core/styles';\nimport { apiErrorPropType } from './util/ApiHelpers.jsx';\nimport { withContext } from './util/AppContext.jsx';\n\nconst styles = theme => ({\n  version: {\n    maxWidth: '250px',\n    padding: theme.spacing(3),\n  },\n  versionMsg: {\n    fontSize: '12px',\n  },\n  updateBtn: {\n    marginTop: theme.spacing(1),\n  },\n});\nclass Version extends React.Component {\n  static numericVersion(version) {\n    const parts = version.split('-', 2);\n    if (parts.length === 2) {\n      return parts[1];\n    } else {\n      return version;\n    }\n  }\n\n  static versionChannel(version) {\n    const parts = version.split('-', 2);\n    return parts.length === 2 ? parts[0] : null;\n  }\n\n  renderVersionCheck = () => {\n    const { classes, latestVersion, error, isLatest } = this.props;\n\n    if (!latestVersion) {\n      return (\n        <Typography className={classes.versionMsg}>\n          <Trans>Version check failed{error ? `: ${error.statusText}` : ''}.</Trans>\n        </Typography>\n      );\n    }\n\n    if (isLatest) {\n      return <Typography className={classes.versionMsg}><Trans>LinkerdIsUpToDateMsg</Trans></Typography>;\n    }\n\n    const versionText = Version.numericVersion(latestVersion);\n\n    return (\n      <div>\n        <Typography className={classes.versionMsg}>\n          <Trans>\n            A new version ({versionText}) is available.\n          </Trans>\n        </Typography>\n        <Button\n          className={classes.updateBtn}\n          variant=\"contained\"\n          color=\"primary\"\n          target=\"_blank\"\n          href=\"https://versioncheck.linkerd.io/update\">\n          <Trans>Update Now</Trans>\n        </Button>\n      </div>\n    );\n  };\n\n  render() {\n    const { classes, releaseVersion, productName } = this.props;\n    const channel = Version.versionChannel(releaseVersion);\n    let message = ` ${productName || 'controller'}`;\n    message += ` ${Version.numericVersion(releaseVersion)}`;\n    if (channel) {\n      message += ` (${channel})`;\n    }\n    message += '.';\n\n    return (\n      <div className={classes.version}>\n        <Typography className={classes.versionMsg}><Trans>Running</Trans>{message}</Typography>\n        {this.renderVersionCheck()}\n      </div>\n    );\n  }\n}\n\nVersion.propTypes = {\n  error: apiErrorPropType,\n  isLatest: PropTypes.bool.isRequired,\n  latestVersion: PropTypes.string,\n  productName: PropTypes.string,\n  releaseVersion: PropTypes.string.isRequired,\n};\n\nVersion.defaultProps = {\n  error: null,\n  latestVersion: '',\n  productName: 'controller',\n};\n\nexport default withStyles(styles, { withTheme: true })(withContext(Version));\n"
  },
  {
    "path": "web/app/js/components/Version.test.js",
    "content": "import React from 'react';\nimport Version from './Version.jsx';\nimport { expect } from 'chai';\nimport { mount } from 'enzyme';\nimport { i18nWrap } from '../../test/testHelpers.jsx';\n\ndescribe('Version', () => {\n  let curVer = \"edge-1.2.3\";\n  let newVer = \"edge-2.3.4\";\n\n  it('renders up to date message when versions match', () => {\n    const component = mount(i18nWrap(\n      <Version\n        classes={{}}\n        isLatest\n        latestVersion={curVer}\n        releaseVersion={curVer} />)\n    );\n\n    expect(component.html()).to.include(\"Linkerd is up to date\");\n  });\n\n  it('renders update message when versions do not match', () => {\n    const component = mount(i18nWrap(\n      <Version\n        classes={{}}\n        isLatest={false}\n        latestVersion={newVer}\n        releaseVersion={curVer} />)\n    );\n\n    expect(component.html()).to.include(\"A new version (2.3.4) is available.\");\n  });\n\n  it('renders error when version check fails', () => {\n    let errMsg = \"Fake error\";\n\n    const component = mount(i18nWrap(\n      <Version\n        classes={{}}\n        error={{\n          statusText: errMsg,\n        }}\n        isLatest={false}\n        releaseVersion={curVer} />)\n    );\n\n    expect(component.html()).to.include(\"Version check failed: Fake error.\");\n    expect(component.html()).to.include(errMsg);\n  });\n});\n"
  },
  {
    "path": "web/app/js/components/util/ApiHelpers.jsx",
    "content": "import 'whatwg-fetch';\n\nimport { Link } from 'react-router-dom';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport _each from 'lodash/each';\nimport _isEmpty from 'lodash/isEmpty';\nimport _isNil from 'lodash/isNil';\nimport _map from 'lodash/map';\n\nconst checkFetchOk = resp => {\n  if (resp.ok) {\n    return resp;\n  }\n\n  return resp.json().then(error => {\n    throw {\n      status: resp.status,\n      url: resp.url,\n      statusText: resp.statusText,\n      error: error.error,\n    };\n  });\n};\n\n// makeCancelable from @istarkov\n// https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html\nconst makeCancelable = (promise, onSuccess) => {\n  let hasCanceled_ = false;\n\n  const wrappedPromise = new Promise((resolve, reject) => {\n    promise.then(\n      result => hasCanceled_ ? reject({ isCanceled: true }) : resolve(result),\n      error => hasCanceled_ ? reject({ isCanceled: true }) : reject(error),\n    )\n      .catch(error => { reject(error); });\n  })\n    .then(checkFetchOk)\n    .then(onSuccess);\n\n  return {\n    promise: wrappedPromise,\n    cancel: () => {\n      hasCanceled_ = true;\n    },\n    status: () => {\n      return hasCanceled_;\n    },\n  };\n};\n\nexport const apiErrorPropType = PropTypes.shape({\n  status: PropTypes.number,\n  url: PropTypes.string,\n  statusText: PropTypes.string,\n  error: PropTypes.string,\n});\n\nconst ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {\n  let metricsWindow = defaultMetricsWindow;\n  const podsPath = '/api/pods';\n  const servicesPath = '/api/services';\n  const edgesPath = '/api/edges';\n  const gatewaysPath = '/api/gateways';\n  const l5dExtensionsPath = '/api/extensions';\n\n  const validMetricsWindows = {\n    '10s': '10 seconds',\n    '1m': '1 minute',\n    '10m': '10 minutes',\n    '1h': '1 hour',\n  };\n\n  // for getting json api results\n  const apiFetch = path => {\n    let path_ = path;\n    if (!_isEmpty(pathPrefix)) {\n      path_ = `${pathPrefix}${path_}`;\n    }\n\n    return makeCancelable(fetch(path_), r => r.json());\n  };\n\n  // for getting yaml api results\n  const apiFetchYAML = path => {\n    let path_ = path;\n    if (!_isEmpty(pathPrefix)) {\n      path_ = `${pathPrefix}${path_}`;\n    }\n\n    return makeCancelable(fetch(path_), r => r.text());\n  };\n\n  // for getting non-json results\n  const prefixedUrl = path => {\n    let path_ = path;\n    if (!_isEmpty(pathPrefix)) {\n      path_ = `${pathPrefix}${path_}`;\n    }\n\n    return path_;\n  };\n\n  const getMetricsWindow = () => metricsWindow;\n  const getMetricsWindowDisplayText = () => validMetricsWindows[metricsWindow];\n\n  const setMetricsWindow = window => {\n    if (!validMetricsWindows[window]) { return; }\n    metricsWindow = window;\n  };\n\n  const fetchMetrics = path => {\n    let path_ = path;\n    if (path_.indexOf('window') === -1) {\n      if (path_.indexOf('?') === -1) {\n        path_ = `${path_}?window=${getMetricsWindow()}`;\n      } else {\n        path_ = `${path_}&window=${getMetricsWindow()}`;\n      }\n    }\n    return apiFetch(path_);\n  };\n\n  const fetchPods = namespace => {\n    if (!_isNil(namespace)) {\n      return apiFetch(`${podsPath}?namespace=${namespace}`);\n    }\n    return apiFetch(podsPath);\n  };\n\n  const fetchServices = namespace => {\n    if (!_isNil(namespace)) {\n      return apiFetch(`${servicesPath}?namespace=${namespace}`);\n    }\n    return apiFetch(servicesPath);\n  };\n\n  const fetchEdges = (namespace, resourceType) => {\n    return apiFetch(`${edgesPath}?resource_type=${resourceType}&namespace=${namespace}`);\n  };\n\n  const fetchGateways = () => {\n    return apiFetch(gatewaysPath);\n  };\n\n  const fetchCheck = () => {\n    return apiFetch('/api/check');\n  };\n\n  const fetchExtension = name => {\n    let extensionPath = l5dExtensionsPath;\n    if (name) {\n      extensionPath += `?extension_name=${name}`;\n    }\n    return apiFetch(extensionPath);\n  };\n\n  const fetchResourceDefinition = (namespace, resourceType, resourceName) => {\n    return apiFetchYAML(`/api/resource-definition?namespace=${namespace}&resource_type=${resourceType}&resource_name=${resourceName}`);\n  };\n\n  const urlsForResource = (type, namespace, includeTcp) => {\n    // Traffic Performance Summary. This retrieves stats for the given resource.\n    let resourceUrl = `/api/tps-reports?resource_type=${type}`;\n\n    if (_isEmpty(namespace) || namespace === '_all') {\n      resourceUrl += '&all_namespaces=true';\n    } else {\n      resourceUrl += `&namespace=${namespace}`;\n    }\n    if (includeTcp) {\n      resourceUrl += '&tcp_stats=true';\n    }\n\n    return resourceUrl;\n  };\n\n  const urlsForResourceNoStats = (type, namespace) => {\n    // Traffic Performance Summary. This retrieves (non-Prometheus) stats for the given resource.\n    let resourceUrl = `/api/tps-reports?skip_stats=true&resource_type=${type}`;\n\n    if (_isEmpty(namespace) || namespace === '_all') {\n      resourceUrl += '&all_namespaces=true';\n    } else {\n      resourceUrl += `&namespace=${namespace}`;\n    }\n\n    return resourceUrl;\n  };\n\n  // maintain a list of a component's requests,\n  // convenient for providing a cancel() functionality\n  let currentRequests = [];\n  const setCurrentRequests = cancelablePromises => {\n    currentRequests = cancelablePromises;\n  };\n  const getCurrentPromises = () => {\n    return _map(currentRequests, r => r.promise);\n  };\n  const cancelCurrentRequests = () => {\n    _each(currentRequests, promise => {\n      promise.cancel();\n    });\n  };\n\n  const prefixLink = to => `${pathPrefix}${to}`;\n\n  // prefix all links in the app with `pathPrefix`\n  const PrefixedLink = function PrefixedLink({ to, targetBlank, children }) {\n    const url = prefixLink(to);\n\n    return (\n      <Link\n        to={url}\n        {...(targetBlank ? { target: '_blank' } : {})}>\n        {children}\n      </Link>\n    );\n  };\n\n  PrefixedLink.propTypes = {\n    children: PropTypes.node.isRequired,\n    targetBlank: PropTypes.bool,\n    to: PropTypes.string.isRequired,\n  };\n\n  PrefixedLink.defaultProps = {\n    targetBlank: false,\n  };\n\n  const generateResourceURL = r => {\n    if (r.type === 'namespace') {\n      return `/namespaces/${r.namespace || r.name}`;\n    }\n\n    return `/namespaces/${r.namespace}/${r.type}s/${r.name}`;\n  };\n\n  // a prefixed link to a Resource Detail page\n  const ResourceLink = function({ resource, linkText }) {\n    return (\n      <PrefixedLink to={generateResourceURL(resource)}>\n        {linkText || `${resource.type}/${resource.name}`}\n      </PrefixedLink>\n    );\n  };\n  ResourceLink.propTypes = {\n    linkText: PropTypes.string,\n    resource: PropTypes.shape({\n      name: PropTypes.string,\n      namespace: PropTypes.string,\n      type: PropTypes.string,\n    }),\n  };\n  ResourceLink.defaultProps = {\n    resource: {},\n    linkText: '',\n  };\n\n  return {\n    fetch: apiFetch,\n    prefixedUrl,\n    fetchMetrics,\n    fetchPods,\n    fetchServices,\n    fetchEdges,\n    fetchGateways,\n    fetchExtension,\n    fetchCheck,\n    fetchResourceDefinition,\n    getMetricsWindow,\n    setMetricsWindow,\n    getValidMetricsWindows: () => Object.keys(validMetricsWindows),\n    getMetricsWindowDisplayText,\n    urlsForResource,\n    urlsForResourceNoStats,\n    PrefixedLink,\n    prefixLink,\n    ResourceLink,\n    setCurrentRequests,\n    getCurrentPromises,\n    generateResourceURL,\n    cancelCurrentRequests,\n    // DO NOT USE makeCancelable, use fetch, this is only exposed for testing\n    makeCancelable,\n  };\n};\n\nexport default ApiHelpers;\n"
  },
  {
    "path": "web/app/js/components/util/ApiHelpers.test.jsx",
    "content": "/* eslint-disable */\nimport 'raf/polyfill'; // the polyfill import must be first\nimport ApiHelpers from './ApiHelpers.jsx';\nimport { expect } from 'chai';\nimport { mount } from 'enzyme';\nimport { routerWrap } from '../../../test/testHelpers.jsx';\nimport sinon from 'sinon';\nimport sinonStubPromise from 'sinon-stub-promise';\n/* eslint-enable */\n\nsinonStubPromise(sinon);\n\ndescribe('ApiHelpers', () => {\n  let api, fetchStub;\n\n  beforeEach(() => {\n    fetchStub = sinon.stub(window, 'fetch');\n    fetchStub.resolves({\n      ok: true,\n      json: () => Promise.resolve({}),\n      text: () => Promise.resolve({})\n    });\n    api = ApiHelpers('');\n  });\n\n  afterEach(() => {\n    api = null;\n    window.fetch.restore();\n  });\n\n  describe('getMetricsWindow/setMetricsWindow', () => {\n    it('sets a default metricsWindow', () => {\n      expect(api.getMetricsWindow()).to.equal('1m');\n    });\n\n    it('changes the metricsWindow on valid window input', () => {\n      expect(api.getMetricsWindow()).to.equal('1m');\n\n      api.setMetricsWindow('10s');\n      expect(api.getMetricsWindow()).to.equal('10s');\n\n      api.setMetricsWindow('1m');\n      expect(api.getMetricsWindow()).to.equal('1m');\n\n      api.setMetricsWindow('10m');\n      expect(api.getMetricsWindow()).to.equal('10m');\n\n      api.setMetricsWindow('1h');\n      expect(api.getMetricsWindow()).to.equal('1h');\n    });\n\n    it('does not change metricsWindow on invalid window size', () => {\n      expect(api.getMetricsWindow()).to.equal('1m');\n\n      api.setMetricsWindow('10h');\n      expect(api.getMetricsWindow()).to.equal('1m');\n    });\n  });\n\n  describe('PrefixedLink', () => {\n    it('respects default values', () => {\n      api = ApiHelpers('/my/path/prefix/linkerd-web:/foo');\n      let linkProps = { to: \"/myrelpath\", children: [\"Informative Link Title\"] };\n      let prefixedLink = mount(routerWrap(api.PrefixedLink, linkProps));\n\n      expect(prefixedLink.find(\"Link\")).to.have.length(1);\n      expect(prefixedLink.html()).to.contain('href=\"/my/path/prefix/linkerd-web:/foo/myrelpath\"');\n      expect(prefixedLink.html()).to.not.contain('target=\"_blank\"');\n      expect(prefixedLink.html()).to.contain(linkProps.children[0]);\n    });\n\n    it('wraps a relative link with the pathPrefix', () => {\n      api = ApiHelpers('/my/path/prefix');\n      let linkProps = { to: \"/myrelpath\", children: [\"Informative Link Title\"] };\n      let prefixedLink = mount(routerWrap(api.PrefixedLink, linkProps));\n\n      expect(prefixedLink.find(\"Link\")).to.have.length(1);\n      expect(prefixedLink.html()).to.contain('href=\"/my/path/prefix/myrelpath\"');\n      expect(prefixedLink.html()).to.include(linkProps.children[0]);\n    });\n\n    it('wraps a relative link with no pathPrefix', () => {\n      api = ApiHelpers('');\n      let linkProps = { to: \"/myrelpath\", children: [\"Informative Link Title\"] };\n      let prefixedLink = mount(routerWrap(api.PrefixedLink, linkProps));\n\n      expect(prefixedLink.find(\"Link\")).to.have.length(1);\n      expect(prefixedLink.html()).to.contain('href=\"/myrelpath\"');\n      expect(prefixedLink.html()).to.include(linkProps.children[0]);\n    });\n\n    it('sets target=blank', () => {\n      api = ApiHelpers('/my/path/prefix');\n      let linkProps = { targetBlank: true, to: \"/myrelpath\", children: [\"Informative Link Title\"] };\n      let prefixedLink = mount(routerWrap(api.PrefixedLink, linkProps));\n\n      expect(prefixedLink.find(\"Link\")).to.have.length(1);\n      expect(prefixedLink.html()).to.contain('target=\"_blank\"');\n      expect(prefixedLink.html()).to.include(linkProps.children[0]);\n    });\n  });\n\n  describe('makeCancelable', () => {\n    it('wraps the original promise', () => {\n      let p = Promise.resolve({ result: 'my response', ok: true });\n      let cancelablePromise = api.makeCancelable(p);\n\n      return cancelablePromise.promise\n        .then(resp => {\n          expect(resp.result).to.equal('my response');\n        });\n    });\n\n    it('returns an error on original promise rejection', () => {\n      let p = Promise.reject({ rejectionReason: 'it is bad' });\n      let cancelablePromise = api.makeCancelable(p);\n\n      return cancelablePromise.promise\n        .then(() => {\n          return Promise.reject('Expected method to reject.');\n        })\n        .catch(e => {\n          expect(e).to.deep.equal({ rejectionReason: 'it is bad' });\n        });\n    });\n\n    it('returns an error if the fetch did not go well', () => {\n      let reason = { rejectionReason: 'it is bad' };\n      let p = Promise.reject(reason);\n      let cancelablePromise = api.makeCancelable(p);\n\n      return cancelablePromise.promise\n        .then(() => {\n          return Promise.reject('Expected method to reject.');\n        })\n        .catch(e => {\n          expect(e).to.equal(reason);\n        });\n    });\n\n    it('calls the provided success handler on response success', () => {\n      let onSuccess = sinon.spy();\n      let fakeFetchResults = { result: 5, ok: true };\n      let p = Promise.resolve(fakeFetchResults);\n      let cancelablePromise = api.makeCancelable(p, onSuccess);\n\n      return cancelablePromise.promise\n        .then(() => {\n          expect(onSuccess.calledOnce).to.be.true;\n          expect(onSuccess.args[0][0]).to.equal(fakeFetchResults);\n        });\n    });\n\n    it('allows you to cancel a promise', () => {\n      let p = Promise.resolve({ result: 'my response', ok: true });\n      let cancelablePromise = api.makeCancelable(p);\n      cancelablePromise.cancel();\n\n      return cancelablePromise.promise\n        .then(() => {\n          return Promise.reject('Expected method to reject.');\n        }).catch(resp => {\n          expect(resp.isCanceled).to.be.true;\n        });\n    });\n  });\n\n  describe('fetch', () => {\n    it('adds pathPrefix to a metrics request', () => {\n      api = ApiHelpers('/the/path/prefix');\n      api.fetch('/resource/foo');\n\n      expect(fetchStub.calledOnce).to.be.true;\n      expect(fetchStub.args[0][0]).to.equal('/the/path/prefix/resource/foo');\n    });\n\n    it('requests from / when there is no path prefix', () => {\n      api = ApiHelpers('');\n      api.fetch('/resource/foo');\n\n      expect(fetchStub.calledOnce).to.be.true;\n      expect(fetchStub.args[0][0]).to.equal('/resource/foo');\n    });\n\n    it('throws an error if response status is not \"ok\"', () => {\n      let errorMessage = \"do or do not. there is no try.\";\n      fetchStub.resolves({\n        ok: false,\n        json: () => Promise.resolve({\n          error: errorMessage\n        }),\n      });\n\n      api = ApiHelpers('');\n      let errorHandler = sinon.spy();\n\n      let f = api.fetch('/resource/foo');\n\n      return f.promise\n        .then(() => {\n          return Promise.reject('Expected method to reject.');\n        }, errorHandler)\n        .then(() => {\n          expect(errorHandler.args[0][0].error).to.equal(errorMessage);\n          expect(errorHandler.calledOnce).to.be.true;\n        });\n    });\n\n    it('correctly passes through rejection messages', () => {\n      let rejectionMessage = \"hm, an error\";\n      fetchStub.rejects({\n        myReason: rejectionMessage\n      });\n\n      api = ApiHelpers('');\n      let rejectHandler = sinon.spy();\n\n      let f = api.fetch('/resource/foo');\n\n      return f.promise\n        .then(() => {\n          return Promise.reject('Expected method to reject.');\n        }, rejectHandler)\n        .then(() => {\n          expect(rejectHandler.args[0][0]).to.have.own.property('myReason');\n          expect(rejectHandler.args[0][0].myReason).to.equal(rejectionMessage);\n          expect(rejectHandler.calledOnce).to.be.true;\n        });\n    });\n  });\n\n  describe('fetchMetrics', () => {\n    it('adds pathPrefix and metricsWindow to a metrics request', () => {\n      api = ApiHelpers('/the/prefix');\n      api.fetchMetrics('/my/path');\n\n      expect(fetchStub.calledOnce).to.be.true;\n      expect(fetchStub.args[0][0]).to.equal('/the/prefix/my/path?window=1m');\n    });\n\n    it('adds a ?window= if metricsWindow is the only param', () => {\n      api.fetchMetrics('/api/tps-reports');\n\n      expect(fetchStub.calledOnce).to.be.true;\n      expect(fetchStub.args[0][0]).to.equal('/api/tps-reports?window=1m');\n    });\n\n    it('adds &window= if metricsWindow is not the only param', () => {\n      api.fetchMetrics('/api/tps-reports?foo=3&bar=\"me\"');\n\n      expect(fetchStub.calledOnce).to.be.true;\n      expect(fetchStub.args[0][0]).to.equal('/api/tps-reports?foo=3&bar=\"me\"&window=1m');\n    });\n\n    it('does not add another &window= if there is already a window param', () => {\n      api.fetchMetrics('/api/tps-reports?foo=3&window=24h&bar=\"me\"');\n\n      expect(fetchStub.calledOnce).to.be.true;\n      expect(fetchStub.args[0][0]).to.equal('/api/tps-reports?foo=3&window=24h&bar=\"me\"');\n    });\n  });\n\n  describe('fetchPods', () => {\n    it('fetches the pods from the api', () => {\n      api = ApiHelpers(\"/random/prefix\");\n      api.fetchPods();\n\n      expect(fetchStub.calledOnce).to.be.true;\n      expect(fetchStub.args[0][0]).to.equal('/random/prefix/api/pods');\n    });\n  });\n\n  describe('urlsForResource', () => {\n    it('returns the correct rollup url for deployment overviews', () => {\n      api = ApiHelpers('/go/my/own/way');\n      let deploymentUrl = api.urlsForResource(\"deployment\");\n      expect(deploymentUrl).to.equal('/api/tps-reports?resource_type=deployment&all_namespaces=true');\n    });\n\n    it('returns the correct rollup url for pod overviews', () => {\n      api = ApiHelpers('/go/my/own/way');\n      let deploymentUrls = api.urlsForResource(\"pod\");\n      expect(deploymentUrls).to.equal('/api/tps-reports?resource_type=pod&all_namespaces=true');\n    });\n\n    it('scopes the query to the provided namespace', () => {\n      api = ApiHelpers('/go/my/own/way');\n      let deploymentUrls = api.urlsForResource(\"pod\", \"my-ns\");\n      expect(deploymentUrls).to.equal('/api/tps-reports?resource_type=pod&namespace=my-ns');\n    });\n\n    it('queries for TCP stats when specified', () => {\n      api = ApiHelpers();\n      let url = api.urlsForResource('sts', '', true);\n      expect(url).to.equal('/api/tps-reports?resource_type=sts&all_namespaces=true&tcp_stats=true');\n    })\n  });\n\n  describe('fetchCheck', () => {\n    it('fetches checks from the api', () => {\n      api = ApiHelpers();\n      api.fetchCheck();\n\n      expect(fetchStub.calledOnce).to.be.true;\n      expect(fetchStub.args[0][0]).to.equal('/api/check');\n    });\n  });\n\n  describe('fetchResourceDefinition', () => {\n    it('fetches the resource definition from the api', () => {\n      const [namespace, type, name] = [\"namespace\", \"type\", \"name\"];\n      api = ApiHelpers();\n      api.fetchResourceDefinition(namespace, type, name);\n\n      expect(fetchStub.calledOnce).to.be.true;\n      expect(fetchStub.args[0][0]).to.equal(`/api/resource-definition?namespace=${namespace}&resource_type=${type}&resource_name=${name}`);\n    });\n  });\n\n  describe('fetchGateways', () => {\n    it('fetches the gateways from the api', () => {\n      api = ApiHelpers();\n      api.fetchGateways();\n\n      expect(fetchStub.calledOnce).to.be.true;\n      expect(fetchStub.args[0][0]).to.equal('/api/gateways');\n    });\n  });\n});\n"
  },
  {
    "path": "web/app/js/components/util/AppContext.jsx",
    "content": "import React from 'react';\nimport ApiHelpers from './ApiHelpers.jsx';\n\nconst Context = React.createContext({\n  api: ApiHelpers(''),\n});\n\nexport const withContext = Component => function componentWithContext(props) {\n  return (\n    <Context.Consumer>\n      {ctx => <Component {...props} {...ctx} />}\n    </Context.Consumer>\n  );\n};\n\nexport default Context;\n"
  },
  {
    "path": "web/app/js/components/util/Chip.jsx",
    "content": "import Chip from '@material-ui/core/Chip';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport { withStyles } from '@material-ui/core/styles';\n\nconst styles = theme => ({\n  good: {\n    color: theme.status.dark.good,\n    border: `1px solid ${theme.status.dark.good}`,\n  },\n  warning: {\n    color: theme.status.dark.warning,\n    border: `1px solid ${theme.status.dark.warning}`,\n  },\n  bad: {\n    color: theme.status.dark.danger,\n    border: `1px solid ${theme.status.dark.danger}`,\n  },\n});\n\nconst SimpleChip = function SimpleChip(props) {\n  const { classes, label, type } = props;\n\n  return (\n    <Chip\n      className={classes[type]}\n      label={label}\n      variant=\"outlined\" />\n  );\n};\n\nSimpleChip.propTypes = {\n  label: PropTypes.shape({}).isRequired,\n  type: PropTypes.string.isRequired,\n};\n\nexport default withStyles(styles, { withTheme: true })(SimpleChip);\n"
  },
  {
    "path": "web/app/js/components/util/CliQueryUtils.js",
    "content": "import _compact from 'lodash/compact';\nimport _isNil from 'lodash/isNil';\n\nconst topRoutesDisplayOrder = query => _compact([\n  'namespace',\n  'toResource',\n  _isNil(query.to_type) ? null : 'toNamespace',\n]);\n\nconst tapDisplayOrder = query => _compact([\n  _isNil(query.resource) ? null : query.resource.indexOf('namespace') === 0 ? null : 'namespace',\n  'toResource',\n  _isNil(query.toResource) ? null : query.toResource.indexOf('namespace') === 0 ? null : 'toNamespace',\n  'method',\n  'path',\n  'scheme',\n  'authority',\n  'maxRps',\n]);\n\nexport const displayOrder = (cmd, query) => {\n  if (cmd === 'tap' || cmd === 'top') {\n    return tapDisplayOrder(query);\n  }\n  if (cmd === 'routes') {\n    return topRoutesDisplayOrder(query);\n  }\n  return [];\n};\n"
  },
  {
    "path": "web/app/js/components/util/CopyUtils.jsx",
    "content": "import React from 'react';\nimport { Trans } from '@lingui/macro';\nimport Typography from '@material-ui/core/Typography';\n\n/*\n* Instructions for adding resources to service mesh\n*/\nexport const incompleteMeshMessage = name => {\n  const unspecifiedResources = <Trans>unspecifiedResourcesMsg</Trans>;\n  const inject = <code>linkerd inject k8s.yml | kubectl apply -f -</code>;\n\n  return (\n    <Typography variant=\"body2\">\n      <Trans>\n        Add {name || unspecifiedResources } to the k8s.yml file<br /><br />\n        Then run {inject} to add it to the service mesh\n      </Trans>\n    </Typography>\n  );\n};\n"
  },
  {
    "path": "web/app/js/components/util/EdgesUtils.jsx",
    "content": "import PropTypes from 'prop-types';\nimport _each from 'lodash/each';\nimport _isEmpty from 'lodash/isEmpty';\n\nexport const processEdges = (rawEdges, resourceName) => {\n  const edges = [];\n  if (_isEmpty(rawEdges) || _isEmpty(rawEdges.ok) || _isEmpty(rawEdges.ok.edges)) {\n    return edges;\n  }\n  _each(rawEdges.ok.edges, edge => {\n    if (_isEmpty(edge)) {\n      return;\n    }\n    // check if any of the returned edges match the current resourceName\n    if (edge.src.name === resourceName) {\n      // current resource is SRC\n      edge.direction = 'OUTBOUND';\n      edge.identity = edge.serverId;\n      edge.name = edge.dst.name;\n      edge.namespace = edge.dst.namespace;\n      edge.key = edge.src.name + edge.dst.name;\n      edges.push(edge);\n    } else if (edge.dst.name === resourceName) {\n      // current resource is DST\n      edge.direction = 'INBOUND';\n      edge.identity = edge.clientId;\n      edge.name = edge.src.name;\n      edge.namespace = edge.src.namespace;\n      edge.key = edge.src.name + edge.dst.name;\n      edges.push(edge);\n    }\n  });\n  return edges;\n};\n\nexport const processedEdgesPropType = PropTypes.shape({\n  dst: PropTypes.shape({\n    name: PropTypes.string,\n    namespace: PropTypes.string,\n    type: PropTypes.string,\n  }),\n  clientId: PropTypes.string,\n  direction: PropTypes.string,\n  identity: PropTypes.string,\n  key: PropTypes.string.isRequired,\n  name: PropTypes.string,\n  noIdentityMsg: PropTypes.string,\n  serverId: PropTypes.string,\n  src: PropTypes.shape({\n    name: PropTypes.string,\n    namespace: PropTypes.string,\n    type: PropTypes.string,\n  }),\n});\n"
  },
  {
    "path": "web/app/js/components/util/MetricUtils.jsx",
    "content": "import PropTypes from 'prop-types';\nimport _compact from 'lodash/compact';\nimport _each from 'lodash/each';\nimport _get from 'lodash/get';\nimport _isEmpty from 'lodash/isEmpty';\nimport _isNull from 'lodash/isNull';\nimport _map from 'lodash/map';\nimport _orderBy from 'lodash/orderBy';\nimport _reduce from 'lodash/reduce';\nimport _size from 'lodash/size';\nimport _values from 'lodash/values';\nimport Percentage from './Percentage.js';\n\nconst srArcClassLabels = {\n  good: 'good',\n  warning: 'warning',\n  poor: 'poor',\n  default: 'default',\n};\n\nexport const getSuccessRateClassification = (rate, successRateLabels = srArcClassLabels) => {\n  if (_isNull(rate)) {\n    return successRateLabels.default;\n  }\n\n  if (rate < 0.9) {\n    return successRateLabels.poor;\n  } else if (rate < 0.95) {\n    return successRateLabels.warning;\n  } else {\n    return successRateLabels.good;\n  }\n};\n\nconst getTotalRequests = row => {\n  const success = parseInt(_get(row, ['stats', 'successCount'], 0), 10);\n  const failure = parseInt(_get(row, ['stats', 'failureCount'], 0), 10);\n\n  return success + failure;\n};\n\nconst timeWindowSeconds = timeWindow => {\n  let seconds = 0;\n\n  if (timeWindow === '10s') { seconds = 10; }\n  if (timeWindow === '1m') { seconds = 60; }\n  if (timeWindow === '10m') { seconds = 600; }\n  if (timeWindow === '1h') { seconds = 3600; }\n\n  return seconds;\n};\n\nconst getRequestRate = row => {\n  if (_isEmpty(row.stats)) {\n    return null;\n  }\n\n  const seconds = timeWindowSeconds(row.timeWindow);\n\n  if (seconds === 0) {\n    return null;\n  } else {\n    return getTotalRequests(row) / seconds;\n  }\n};\n\nconst getSuccessRate = row => {\n  if (_isEmpty(row.stats)) {\n    return null;\n  }\n\n  const success = parseInt(row.stats.successCount, 10);\n  const failure = parseInt(row.stats.failureCount, 10);\n\n  if (success + failure === 0) {\n    return null;\n  } else {\n    return success / (success + failure);\n  }\n};\n\nconst getLatency = row => {\n  if (_isEmpty(row.stats)) {\n    return {};\n  } else {\n    return {\n      P50: parseInt(row.stats.latencyMsP50, 10),\n      P95: parseInt(row.stats.latencyMsP95, 10),\n      P99: parseInt(row.stats.latencyMsP99, 10),\n    };\n  }\n};\n\nconst getTcpStats = row => {\n  if (_isEmpty(row.tcpStats)) {\n    return {};\n  } else {\n    const seconds = timeWindowSeconds(row.timeWindow);\n    const readBytes = parseInt(row.tcpStats.readBytesTotal, 10);\n    const writeBytes = parseInt(row.tcpStats.writeBytesTotal, 10);\n    return {\n      openConnections: parseInt(row.tcpStats.openConnections, 10),\n      readBytes,\n      writeBytes,\n      readRate: seconds === 0 ? null : readBytes / seconds,\n      writeRate: seconds === 0 ? null : writeBytes / seconds,\n    };\n  }\n};\n\nconst processStatTable = table => {\n  const rows = _compact(table.podGroup.rows.map(row => {\n    const runningPodCount = parseInt(row.runningPodCount, 10);\n    const meshedPodCount = parseInt(row.meshedPodCount, 10);\n    let rowKey = `${row.resource.namespace}-${row.resource.type}-${row.resource.name}`;\n    if (row.tsStats) {\n      rowKey = `${row.resource.namespace}-${row.resource.type}-${row.resource.name}-${row.tsStats.leaf}`;\n    }\n    return {\n      key: rowKey,\n      name: row.resource.name,\n      namespace: row.resource.namespace,\n      type: row.resource.type,\n      totalRequests: getTotalRequests(row),\n      requestRate: getRequestRate(row),\n      successRate: getSuccessRate(row),\n      latency: getLatency(row),\n      tcp: getTcpStats(row),\n      tsStats: row.tsStats,\n      added: runningPodCount > 0 && meshedPodCount > 0,\n      pods: {\n        totalPods: row.runningPodCount,\n        meshedPods: row.meshedPodCount,\n        meshedPercentage: new Percentage(meshedPodCount, runningPodCount),\n      },\n      errors: row.errorsByPod,\n    };\n  }));\n\n  return _orderBy(rows, r => r.name);\n};\n\nexport const DefaultRoute = '[DEFAULT]';\nexport const processTopRoutesResults = rows => {\n  return _map(rows, row => ({\n    route: row.route || DefaultRoute,\n    tooltip: !_isEmpty(row.route) ? null : 'Traffic does not match any configured routes',\n    authority: row.authority,\n    totalRequests: getTotalRequests(row),\n    requestRate: getRequestRate(row),\n    successRate: getSuccessRate(row),\n    latency: getLatency(row),\n  }\n  ));\n};\n\nconst processGatewayTable = table => {\n  const rows = _compact(table.rows.map(row => {\n    const rowKey = `${row.namespace}-gateway-${row.name}`;\n    return {\n      key: rowKey,\n      name: row.name,\n      namespace: row.namespace,\n      clusterName: row.clusterName,\n      alive: row.alive,\n      pairedServices: row.pairedServices,\n      latency: {\n        P50: parseInt(row.latencyMsP50, 10),\n        P95: parseInt(row.latencyMsP95, 10),\n        P99: parseInt(row.latencyMsP99, 10),\n      },\n    };\n  }));\n  return _orderBy(rows, r => r.name);\n};\nexport const processGatewayResults = rawMetrics => {\n  if (_isEmpty(rawMetrics.ok) || _isEmpty(rawMetrics.ok.gatewaysTable)) {\n    return {};\n  }\n  return processGatewayTable(rawMetrics.ok.gatewaysTable);\n};\nexport const processMultiResourceRollup = (rawMetrics, resourceType) => {\n  if (_isEmpty(rawMetrics.ok) || _isEmpty(rawMetrics.ok.statTables)) {\n    return {};\n  }\n\n  const metricsByResource = {};\n  _each(rawMetrics.ok.statTables, table => {\n    if (_isEmpty(table.podGroup.rows)) {\n      return;\n    }\n\n    // Because trafficsplit metrics are now being returned from the Stat API\n    // endpoint, `processMultiResourceRollup` can erroneously\n    // return trafficsplit leaf services as \"upstream\" or \"downstream\" of a\n    // selected resource in the Octopus graph.\n\n    // The below line checks if the statTable contains trafficsplit data and\n    // returns early if the selected resource is not \"all\" or \"service\",\n    // since any other resource should not include trafficsplit metrics.\n    const isTrafficSplit = table.podGroup.rows[0].resource.type === 'trafficsplit';\n    if (isTrafficSplit && resourceType !== 'all') {\n      return;\n    }\n\n    // assumes all rows in a podGroup have the same resource type\n    const resource = _get(table, ['podGroup', 'rows', 0, 'resource', 'type']);\n\n    metricsByResource[resource] = processStatTable(table);\n  });\n  return metricsByResource;\n};\n\nexport const processSingleResourceRollup = (rawMetrics, resourceType) => {\n  const result = processMultiResourceRollup(rawMetrics, resourceType);\n  if (_size(result) > 1) {\n    console.error('Multi metric returned; expected single metric.');\n  }\n  if (_isEmpty(result)) {\n    return [];\n  }\n  return _values(result)[0];\n};\n\nexport const groupResourcesByNs = apiRsp => {\n  const statTables = _get(apiRsp, ['ok', 'statTables']);\n  const authoritiesByNs = {};\n  const resourcesByNs = _reduce(statTables, (mem, table) => {\n    _each(table.podGroup.rows, row => {\n      // filter out resources that aren't meshed. note that authorities don't\n      // have pod counts and therefore can't be filtered out here\n      if (row.meshedPodCount === '0' && row.resource.type !== 'authority') {\n        return;\n      }\n\n      if (!mem[row.resource.namespace]) {\n        mem[row.resource.namespace] = [];\n        authoritiesByNs[row.resource.namespace] = [];\n      }\n\n      switch (row.resource.type.toLowerCase()) {\n        case 'service':\n          break;\n        case 'authority':\n          authoritiesByNs[row.resource.namespace].push(row.resource.name);\n          break;\n        default:\n          mem[row.resource.namespace].push(`${row.resource.type}/${row.resource.name}`);\n      }\n    });\n    return mem;\n  }, {});\n  return {\n    authoritiesByNs,\n    resourcesByNs,\n  };\n};\n\nexport const excludeResourcesFromRollup = (rollupMetrics, resourcesToExclude) => {\n  _each(resourcesToExclude, resource => {\n    delete rollupMetrics[resource];\n  });\n  return rollupMetrics;\n};\n\nexport const emptyMetric = {\n  key: '',\n  name: '',\n  namespace: '',\n  type: '',\n  totalRequests: null,\n  requestRate: null,\n  successRate: null,\n  latency: null,\n  added: false,\n  pods: {\n    totalPods: null,\n    meshedPods: null,\n    meshedPercentage: null,\n  },\n};\n\nexport const metricsPropType = PropTypes.shape({\n  ok: PropTypes.shape({\n    statTables: PropTypes.arrayOf(PropTypes.shape({\n      podGroup: PropTypes.shape({\n        rows: PropTypes.arrayOf(PropTypes.shape({\n          failedPodCount: PropTypes.string,\n          meshedPodCount: PropTypes.string,\n          resource: PropTypes.shape({\n            name: PropTypes.string,\n            namespace: PropTypes.string,\n            type: PropTypes.string,\n          }).isRequired,\n          runningPodCount: PropTypes.string,\n          stats: PropTypes.shape({\n            failureCount: PropTypes.string,\n            latencyMsP50: PropTypes.string,\n            latencyMsP95: PropTypes.string,\n            latencyMsP99: PropTypes.string,\n            successCount: PropTypes.string,\n          }),\n          timeWindow: PropTypes.string,\n        }).isRequired),\n      }),\n    }).isRequired).isRequired,\n  }),\n});\n\nexport const processedMetricsPropType = PropTypes.shape({\n  name: PropTypes.string.isRequired,\n  namespace: PropTypes.string.isRequired,\n  totalRequests: PropTypes.number,\n  requestRate: PropTypes.number,\n  successRate: PropTypes.number,\n  tsStats: PropTypes.shape({\n    weight: PropTypes.string,\n  }),\n});\n"
  },
  {
    "path": "web/app/js/components/util/MetricUtils.test.js",
    "content": "import deployRollupFixtures from '../../../test/fixtures/deployRollup.json';\nimport multiDeployRollupFixtures from '../../../test/fixtures/multiDeployRollup.json';\nimport multiResourceRollupFixtures from '../../../test/fixtures/allRollup.json';\nimport gatewayFixtures from '../../../test/fixtures/gateway.json';\nimport Percentage from './Percentage';\nimport {\n  processGatewayResults,\n  processMultiResourceRollup,\n  processSingleResourceRollup\n} from './MetricUtils.jsx';\n\ndescribe('MetricUtils', () => {\n  describe('processSingleResourceRollup', () => {\n    it('Extracts deploy metrics from a single response', () => {\n      let result = processSingleResourceRollup(deployRollupFixtures);\n      let expectedResult = [\n        {\n          name: 'voting',\n          namespace: 'emojivoto',\n          type: 'deployment',\n          key: \"emojivoto-deployment-voting\",\n          requestRate: 2.5,\n          successRate: 0.9,\n          tcp: {\n            openConnections: 221,\n            readBytes: 4421,\n            writeBytes: 4421,\n            readRate: 73.68333333333334,\n            writeRate: 73.68333333333334\n          },\n          totalRequests: 150,\n          latency: {\n            P50: 1,\n            P95: 2,\n            P99: 7\n          },\n          pods: { totalPods: \"1\", meshedPods: \"1\", meshedPercentage: new Percentage(1, 1) },\n          added: true,\n          errors: {}\n        }\n      ];\n      expect(result).toHaveLength(1);\n      expect(result).toEqual(expectedResult);\n    });\n\n    it('Extracts and sorts multiple deploys from a single response', () => {\n      let result = processSingleResourceRollup(multiDeployRollupFixtures);\n      expect(result).toHaveLength(4);\n      expect(result[0].name).toEqual(\"emoji\");\n      expect(result[0].namespace).toEqual(\"emojivoto\");\n      expect(result[1].name).toEqual(\"vote-bot\");\n      expect(result[1].namespace).toEqual(\"emojivoto\");\n      expect(result[2].name).toEqual(\"voting\");\n      expect(result[2].namespace).toEqual(\"emojivoto\");\n      expect(result[3].name).toEqual(\"web\");\n      expect(result[3].namespace).toEqual(\"emojivoto\");\n    });\n  });\n\n  describe('processMultiResourceRollup', () => {\n    it('Extracts metrics and groups them by resource type', () => {\n      let result = processMultiResourceRollup(multiResourceRollupFixtures);\n      expect(Object.keys(result)).toHaveLength(2);\n\n      expect(result[\"deployment\"]).toHaveLength(1);\n      expect(result[\"pod\"]).toHaveLength(4);\n      expect(result[\"replicationcontroller\"]).toBeUndefined;\n    });\n  });\n  describe('processGatewayResults', () => {\n    it('Extracts and sorts gateway metrics from a response', () => {\n      let result = processGatewayResults(gatewayFixtures);\n      let expectedResult = [\n        {\n          key: 'test_namespace-gateway-default_gateway',\n          name: 'default_gateway',\n          namespace: 'test_namespace',\n          clusterName: 'multi_cluster',\n          alive: true,\n          pairedServices: '0',\n          latency: { P50: 0, P95: 0, P99: 0 }\n        },\n        {\n          key: 'test_namespace-gateway-test_gateway',\n          name: 'test_gateway',\n          namespace: 'test_namespace',\n          clusterName: 'multi_cluster',\n          alive: true,\n          pairedServices: '0',\n          latency: { P50: 0, P95: 0, P99: 0 }\n        }\n      ];\n      expect(result).toHaveLength(2);\n      expect(result).toEqual(expectedResult);\n    });\n  })\n});\n"
  },
  {
    "path": "web/app/js/components/util/OctopusArms.jsx",
    "content": "import React from 'react';\nimport grey from '@material-ui/core/colors/grey';\n\nconst strokeOpacity = '0.7';\nconst arrowColor = grey[500];\n\nconst controlPoint = 10; // width and height of the control points for the bezier curves\nexport const inboundAlignment = controlPoint * 2;\n\nconst generateSvgComponents = (y1, width, height) => {\n  const segmentWidth = width / 2 - controlPoint; // width of each horizontal arrow segment\n\n  const x1 = 0;\n\n  const x2 = x1 + segmentWidth;\n  const x3 = x2 + controlPoint;\n\n  const y2 = y1 - controlPoint;\n  const y3 = y2 - height;\n  const y4 = y3 - controlPoint;\n\n  const x4 = x3 + controlPoint;\n  const x5 = x4 + segmentWidth;\n\n  const start = `M ${x1},${y1}`;\n  const horizLine1 = `L ${x2},${y1}`;\n  const curve1 = `C ${x3},${y1} ${x3},${y1}`;\n  const curve1End = `${x3},${y2}`;\n  const verticalLineEnd = `L ${x3},${y3}`;\n  const curve2 = `C ${x3},${y4} ${x3},${y4}`;\n  const curve2End = `${x4},${y4}`;\n  const horizLine2 = `L ${x5},${y4}`;\n\n  const arrowPath = [start, horizLine1, curve1, curve1End, verticalLineEnd, curve2, curve2End, horizLine2].join(' ');\n\n  const arrowEndX = width;\n  const arrowEndY = y4;\n  const arrowHead = `${arrowEndX - 4} ${arrowEndY - 4} ${arrowEndX} ${arrowEndY} ${arrowEndX - 4} ${arrowEndY + 4}`;\n\n  const circle = { cx: x1, cy: y1 };\n\n  return {\n    arrowPath,\n    circle,\n    arrowHead,\n  };\n};\n\nconst arrowG = (id, arm, transform) => {\n  return (\n    <g key={id} id={id} fill=\"none\" strokeWidth=\"1\">\n      <path\n        d={arm.arrowPath}\n        stroke={arrowColor}\n        transform={transform}\n        strokeOpacity={strokeOpacity} />\n      <circle\n        cx={arm.circle.cx}\n        cy={arm.circle.cy}\n        transform={transform}\n        fill={arrowColor}\n        r=\"4\" />\n      <polyline\n        points={arm.arrowHead}\n        stroke={arrowColor}\n        strokeLinecap=\"round\"\n        transform={transform} />\n    </g>\n  );\n};\n\nconst up = (width, svgHeight, arrowHeight, isOutbound) => {\n  const height = arrowHeight;\n\n  // up arrows start and the center of the middle node for outbound arms,\n  // and at the noce position for inbound arms\n  const y1 = isOutbound ? svgHeight / 2 : arrowHeight;\n  const arm = generateSvgComponents(y1, width, height);\n\n  const translate = isOutbound ? null : `translate(0, ${svgHeight / 2 + inboundAlignment})`;\n\n  return arrowG(`up-arrow-${height}`, arm, translate);\n};\n\nconst flat = (width, height) => {\n  const arrowY = height / 2;\n  const arrowEndX = width;\n  const polylinePoints = `${arrowEndX - 4} ${arrowY - 4} ${arrowEndX} ${arrowY} ${arrowEndX - 4} ${arrowY + 4}`;\n\n  return (\n    <g key=\"flat-arrow\" id=\"downstream-flat\" fill=\"none\" stroke=\"none\" strokeWidth=\"1\">\n      <path\n        d={`M0,${arrowY} L${arrowEndX},${arrowY}`}\n        stroke={arrowColor}\n        strokeOpacity={strokeOpacity} />\n      <circle cx=\"0\" cy={arrowY} fill={arrowColor} r=\"4\" />\n      <polyline points={polylinePoints} stroke={arrowColor} strokeLinecap=\"round\" />\n    </g>\n  );\n};\n\nconst down = (width, svgHeight, arrowHeight, isOutbound, elementHeight) => {\n  // down outbound arrows start at the middle of the svg's height, and\n  // have end of block n at (1/2 block height) + (block height * n-1)\n  const height = (svgHeight / 2) - arrowHeight;\n\n  // inbound arrows start at the offset of the card, and end in the center of the middle card\n  // outbound arrows start in the center of the middle card, and end at the card's height\n  const y1 = isOutbound ? svgHeight / 2 : elementHeight / 2;\n\n  const arm = generateSvgComponents(y1, width, height);\n\n  const translate = `translate(0, ${isOutbound ? svgHeight : svgHeight / 2 - height + elementHeight / 2 - inboundAlignment})`;\n  const reflect = 'scale(1, -1)';\n  const transform = `${translate} ${reflect}`;\n\n  return arrowG(`down-arrow-${height}`, arm, transform);\n};\n\nexport const OctopusArms = {\n  up,\n  flat,\n  down,\n};\n"
  },
  {
    "path": "web/app/js/components/util/PageVisibility.jsx",
    "content": "import React from 'react';\nimport _isFunction from 'lodash/isFunction';\nimport _isUndefined from 'lodash/isUndefined';\nimport { usePageVisibility } from 'react-page-visibility';\n\nexport function handlePageVisibility(params) {\n  const { prevVisibilityState, currentVisibilityState, onVisible, onHidden } = params;\n  if (_isUndefined(prevVisibilityState) || _isUndefined(currentVisibilityState)) {\n    return;\n  }\n\n  if (prevVisibilityState && !currentVisibilityState && _isFunction(onHidden)) {\n    onHidden();\n  }\n\n  if (!prevVisibilityState && currentVisibilityState && _isFunction(onVisible)) {\n    onVisible();\n  }\n}\n\nexport const withPageVisibility = WrappedComponent => {\n  const Component = function(props) {\n    const isVisible = usePageVisibility();\n    return <WrappedComponent {...props} isPageVisible={isVisible} />;\n  };\n\n  return Component;\n};\n"
  },
  {
    "path": "web/app/js/components/util/Percentage.js",
    "content": "export default function Percentage(numerator, denominator) {\n  this.decimal = -1;\n  if (denominator !== 0) {\n    this.decimal = numerator / denominator;\n  }\n}\n\nPercentage.prototype.get = function() { // eslint-disable-line func-names\n  return this.decimal;\n};\n\nPercentage.prototype.prettyRate = function() { // eslint-disable-line func-names\n  if (this.decimal < 0) {\n    return 'N/A';\n  } else {\n    return `${(100 * this.decimal).toFixed(1)}%`;\n  }\n};\n"
  },
  {
    "path": "web/app/js/components/util/Progress.jsx",
    "content": "import LinearProgress from '@material-ui/core/LinearProgress';\nimport { withStyles } from '@material-ui/core/styles';\nimport { dashboardTheme } from './theme.js';\n\nconst colorLookup = {\n  good: {\n    colorPrimary: dashboardTheme.status.light.good, // background bar color (lighter)\n    barColorPrimary: dashboardTheme.status.dark.good, // inner bar color (darker)\n  },\n  warning: {\n    colorPrimary: dashboardTheme.status.light.warning,\n    barColorPrimary: dashboardTheme.status.dark.warning,\n  },\n  poor: {\n    colorPrimary: dashboardTheme.status.light.danger,\n    barColorPrimary: dashboardTheme.status.dark.danger,\n  },\n  default: {\n    colorPrimary: dashboardTheme.status.light.default,\n    barColorPrimary: dashboardTheme.status.dark.default,\n  },\n};\n\nexport const StyledProgress = (classification = 'default') => withStyles({\n  root: {\n    flexGrow: 1,\n  },\n  colorPrimary: {\n    backgroundColor: colorLookup[classification].colorPrimary,\n  },\n  barColorPrimary: {\n    backgroundColor: colorLookup[classification].barColorPrimary,\n  },\n})(LinearProgress);\n"
  },
  {
    "path": "web/app/js/components/util/Spinner.jsx",
    "content": "import CircularProgress from '@material-ui/core/CircularProgress';\nimport Grid from '@material-ui/core/Grid';\nimport React from 'react';\nimport { withStyles } from '@material-ui/core/styles';\n\nconst styles = theme => ({\n  progress: {\n    margin: 'auto',\n    color: theme.palette.primary.main,\n  },\n});\n\nconst CircularIndeterminate = function CircularIndeterminate(props) {\n  const { classes } = props;\n  return (\n    <Grid container justifyContent=\"center\">\n      <CircularProgress className={classes.progress} />\n    </Grid>\n  );\n};\n\nexport default withStyles(styles, { withTheme: true })(CircularIndeterminate);\n"
  },
  {
    "path": "web/app/js/components/util/SuccessRateDot.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\nimport _merge from 'lodash/merge';\nimport classNames from 'classnames';\nimport { withStyles } from '@material-ui/core/styles';\nimport { getSuccessRateClassification } from './MetricUtils.jsx';\nimport { statusClassNames } from './theme.js';\n\nconst styles = theme => _merge({}, statusClassNames(theme), {\n  successRateDot: {\n    width: theme.spacing(1),\n    height: theme.spacing(1),\n    minWidth: theme.spacing(1),\n    borderRadius: '50%',\n  },\n});\n\nconst SuccessRateDot = function({ sr, classes }) {\n  return <div className={classNames(classes.successRateDot, classes[getSuccessRateClassification(sr)])} />;\n};\n\nSuccessRateDot.propTypes = {\n  sr: PropTypes.number,\n};\n\nSuccessRateDot.defaultProps = {\n  sr: null,\n};\n\nexport default withStyles(styles, { withTheme: true })(SuccessRateDot);\n"
  },
  {
    "path": "web/app/js/components/util/SuccessRateMiniChart.jsx",
    "content": "import Grid from '@material-ui/core/Grid';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport _isNil from 'lodash/isNil';\nimport SuccessRateDot from './SuccessRateDot.jsx';\nimport { metricToFormatter } from './Utils.js';\n\nconst SuccessRateMiniChart = function({ sr }) {\n  return (\n    <Grid container justifyContent=\"flex-end\" alignItems=\"center\" spacing={1}>\n      <Grid item>{metricToFormatter.SUCCESS_RATE(sr)}</Grid>\n      <Grid item>{_isNil(sr) ? null :\n      <SuccessRateDot sr={sr} />\n    }\n      </Grid>\n    </Grid>\n  );\n};\n\nSuccessRateMiniChart.propTypes = {\n  sr: PropTypes.number,\n};\n\nSuccessRateMiniChart.defaultProps = {\n  sr: null,\n};\n\nexport default SuccessRateMiniChart;\n"
  },
  {
    "path": "web/app/js/components/util/SvgWrappers.jsx",
    "content": "import '../../../css/svg-wrappers.css';\n\nimport React from 'react';\n\nexport const slackIcon = (\n  <svg style={{ width: '23px', height: '23px' }} viewBox=\"-1 0 24 24\">\n    <path fill=\"#757575\" d=\"M10.23,11.16L12.91,10.27L13.77,12.84L11.09,13.73L10.23,11.16M17.69,13.71C18.23,13.53 18.5,12.94 18.34,12.4C18.16,11.86 17.57,11.56 17.03,11.75L15.73,12.18L14.87,9.61L16.17,9.17C16.71,9 17,8.4 16.82,7.86C16.64,7.32 16.05,7 15.5,7.21L14.21,7.64L13.76,6.3C13.58,5.76 13,5.46 12.45,5.65C11.91,5.83 11.62,6.42 11.8,6.96L12.25,8.3L9.57,9.19L9.12,7.85C8.94,7.31 8.36,7 7.81,7.2C7.27,7.38 7,7.97 7.16,8.5L7.61,9.85L6.31,10.29C5.77,10.47 5.5,11.06 5.66,11.6C5.8,12 6.19,12.3 6.61,12.31L6.97,12.25L8.27,11.82L9.13,14.39L7.83,14.83C7.29,15 7,15.6 7.18,16.14C7.32,16.56 7.71,16.84 8.13,16.85L8.5,16.79L9.79,16.36L10.24,17.7C10.38,18.13 10.77,18.4 11.19,18.41L11.55,18.35C12.09,18.17 12.38,17.59 12.2,17.04L11.75,15.7L14.43,14.81L14.88,16.15C15,16.57 15.41,16.84 15.83,16.85L16.19,16.8C16.73,16.62 17,16.03 16.84,15.5L16.39,14.15L17.69,13.71M21.17,9.25C23.23,16.12 21.62,19.1 14.75,21.17C7.88,23.23 4.9,21.62 2.83,14.75C0.77,7.88 2.38,4.9 9.25,2.83C16.12,0.77 19.1,2.38 21.17,9.25Z\" />\n  </svg>\n);\n\nexport const githubIcon = (\n  <svg style={{ width: '23px', height: '23px' }} viewBox=\"-1 0 24 24\">\n    <path fill=\"#757575\" d=\"M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z\" />\n  </svg>\n);\n\nexport const grafanaIcon = (\n  <svg\n    version=\"1.1\"\n    id=\"Layer_1\"\n    x=\"0px\"\n    y=\"0px\"\n    width=\"15px\"\n    height=\"15px\"\n    viewBox=\"0 0 351 365\"\n    style={{ 'enableBackground:': 'new 0 0 351 365' }}\n    xmlSpace=\"preserve\">\n    <g id=\"Layer_1_1_\" />\n    <linearGradient id=\"SVGID_1_grafana\" gradientUnits=\"userSpaceOnUse\" x1=\"175.5\" y1=\"445.4948\" x2=\"175.5\" y2=\"114.0346\">\n      <stop offset=\"0\" style={{ stopColor: '#FFF100' }} />\n      <stop offset=\"1\" style={{ stopColor: '#F05A28' }} />\n    </linearGradient>\n    <path\n      className=\"grafana-st0\"\n      d=\"M342,161.2c-0.6-6.1-1.6-13.1-3.6-20.9c-2-7.7-5-16.2-9.4-25c-4.4-8.8-10.1-17.9-17.5-26.8\tc-2.9-3.5-6.1-6.9-9.5-10.2c5.1-20.3-6.2-37.9-6.2-37.9c-19.5-1.2-31.9,6.1-36.5,9.4c-0.8-0.3-1.5-0.7-2.3-1\tc-3.3-1.3-6.7-2.6-10.3-3.7c-3.5-1.1-7.1-2.1-10.8-3c-3.7-0.9-7.4-1.6-11.2-2.2c-0.7-0.1-1.3-0.2-2-0.3\tc-8.5-27.2-32.9-38.6-32.9-38.6c-27.3,17.3-32.4,41.5-32.4,41.5s-0.1,0.5-0.3,1.4c-1.5,0.4-3,0.9-4.5,1.3c-2.1,0.6-4.2,1.4-6.2,2.2\tc-2.1,0.8-4.1,1.6-6.2,2.5c-4.1,1.8-8.2,3.8-12.2,6c-3.9,2.2-7.7,4.6-11.4,7.1c-0.5-0.2-1-0.4-1-0.4c-37.8-14.4-71.3,2.9-71.3,2.9\tc-3.1,40.2,15.1,65.5,18.7,70.1c-0.9,2.5-1.7,5-2.5,7.5c-2.8,9.1-4.9,18.4-6.2,28.1c-0.2,1.4-0.4,2.8-0.5,4.2\tC18.8,192.7,8.5,228,8.5,228c29.1,33.5,63.1,35.6,63.1,35.6c0,0,0.1-0.1,0.1-0.1c4.3,7.7,9.3,15,14.9,21.9c2.4,2.9,4.8,5.6,7.4,8.3\tc-10.6,30.4,1.5,55.6,1.5,55.6c32.4,1.2,53.7-14.2,58.2-17.7c3.2,1.1,6.5,2.1,9.8,2.9c10,2.6,20.2,4.1,30.4,4.5\tc2.5,0.1,5.1,0.2,7.6,0.1l1.2,0l0.8,0l1.6,0l1.6-0.1l0,0.1c15.3,21.8,42.1,24.9,42.1,24.9c19.1-20.1,20.2-40.1,20.2-44.4l0,0\tc0,0,0-0.1,0-0.3c0-0.4,0-0.6,0-0.6l0,0c0-0.3,0-0.6,0-0.9c4-2.8,7.8-5.8,11.4-9.1c7.6-6.9,14.3-14.8,19.9-23.3\tc0.5-0.8,1-1.6,1.5-2.4c21.6,1.2,36.9-13.4,36.9-13.4c-3.6-22.5-16.4-33.5-19.1-35.6l0,0c0,0-0.1-0.1-0.3-0.2\tc-0.2-0.1-0.2-0.2-0.2-0.2c0,0,0,0,0,0c-0.1-0.1-0.3-0.2-0.5-0.3c0.1-1.4,0.2-2.7,0.3-4.1c0.2-2.4,0.2-4.9,0.2-7.3l0-1.8l0-0.9\tl0-0.5c0-0.6,0-0.4,0-0.6l-0.1-1.5l-0.1-2c0-0.7-0.1-1.3-0.2-1.9c-0.1-0.6-0.1-1.3-0.2-1.9l-0.2-1.9l-0.3-1.9\tc-0.4-2.5-0.8-4.9-1.4-7.4c-2.3-9.7-6.1-18.9-11-27.2c-5-8.3-11.2-15.6-18.3-21.8c-7-6.2-14.9-11.2-23.1-14.9\tc-8.3-3.7-16.9-6.1-25.5-7.2c-4.3-0.6-8.6-0.8-12.9-0.7l-1.6,0l-0.4,0c-0.1,0-0.6,0-0.5,0l-0.7,0l-1.6,0.1c-0.6,0-1.2,0.1-1.7,0.1\tc-2.2,0.2-4.4,0.5-6.5,0.9c-8.6,1.6-16.7,4.7-23.8,9c-7.1,4.3-13.3,9.6-18.3,15.6c-5,6-8.9,12.7-11.6,19.6c-2.7,6.9-4.2,14.1-4.6,21\tc-0.1,1.7-0.1,3.5-0.1,5.2c0,0.4,0,0.9,0,1.3l0.1,1.4c0.1,0.8,0.1,1.7,0.2,2.5c0.3,3.5,1,6.9,1.9,10.1c1.9,6.5,4.9,12.4,8.6,17.4\tc3.7,5,8.2,9.1,12.9,12.4c4.7,3.2,9.8,5.5,14.8,7c5,1.5,10,2.1,14.7,2.1c0.6,0,1.2,0,1.7,0c0.3,0,0.6,0,0.9,0c0.3,0,0.6,0,0.9-0.1\tc0.5,0,1-0.1,1.5-0.1c0.1,0,0.3,0,0.4-0.1l0.5-0.1c0.3,0,0.6-0.1,0.9-0.1c0.6-0.1,1.1-0.2,1.7-0.3c0.6-0.1,1.1-0.2,1.6-0.4\tc1.1-0.2,2.1-0.6,3.1-0.9c2-0.7,4-1.5,5.7-2.4c1.8-0.9,3.4-2,5-3c0.4-0.3,0.9-0.6,1.3-1c1.6-1.3,1.9-3.7,0.6-5.3\tc-1.1-1.4-3.1-1.8-4.7-0.9c-0.4,0.2-0.8,0.4-1.2,0.6c-1.4,0.7-2.8,1.3-4.3,1.8c-1.5,0.5-3.1,0.9-4.7,1.2c-0.8,0.1-1.6,0.2-2.5,0.3\tc-0.4,0-0.8,0.1-1.3,0.1c-0.4,0-0.9,0-1.2,0c-0.4,0-0.8,0-1.2,0c-0.5,0-1,0-1.5-0.1c0,0-0.3,0-0.1,0l-0.2,0l-0.3,0\tc-0.2,0-0.5,0-0.7-0.1c-0.5-0.1-0.9-0.1-1.4-0.2c-3.7-0.5-7.4-1.6-10.9-3.2c-3.6-1.6-7-3.8-10.1-6.6c-3.1-2.8-5.8-6.1-7.9-9.9\tc-2.1-3.8-3.6-8-4.3-12.4c-0.3-2.2-0.5-4.5-0.4-6.7c0-0.6,0.1-1.2,0.1-1.8c0,0.2,0-0.1,0-0.1l0-0.2l0-0.5c0-0.3,0.1-0.6,0.1-0.9\tc0.1-1.2,0.3-2.4,0.5-3.6c1.7-9.6,6.5-19,13.9-26.1c1.9-1.8,3.9-3.4,6-4.9c2.1-1.5,4.4-2.8,6.8-3.9c2.4-1.1,4.8-2,7.4-2.7\tc2.5-0.7,5.1-1.1,7.8-1.4c1.3-0.1,2.6-0.2,4-0.2c0.4,0,0.6,0,0.9,0l1.1,0l0.7,0c0.3,0,0,0,0.1,0l0.3,0l1.1,0.1\tc2.9,0.2,5.7,0.6,8.5,1.3c5.6,1.2,11.1,3.3,16.2,6.1c10.2,5.7,18.9,14.5,24.2,25.1c2.7,5.3,4.6,11,5.5,16.9c0.2,1.5,0.4,3,0.5,4.5\tl0.1,1.1l0.1,1.1c0,0.4,0,0.8,0,1.1c0,0.4,0,0.8,0,1.1l0,1l0,1.1c0,0.7-0.1,1.9-0.1,2.6c-0.1,1.6-0.3,3.3-0.5,4.9\tc-0.2,1.6-0.5,3.2-0.8,4.8c-0.3,1.6-0.7,3.2-1.1,4.7c-0.8,3.1-1.8,6.2-3,9.3c-2.4,6-5.6,11.8-9.4,17.1\tc-7.7,10.6-18.2,19.2-30.2,24.7c-6,2.7-12.3,4.7-18.8,5.7c-3.2,0.6-6.5,0.9-9.8,1l-0.6,0l-0.5,0l-1.1,0l-1.6,0l-0.8,0\tc0.4,0-0.1,0-0.1,0l-0.3,0c-1.8,0-3.5-0.1-5.3-0.3c-7-0.5-13.9-1.8-20.7-3.7c-6.7-1.9-13.2-4.6-19.4-7.8\tc-12.3-6.6-23.4-15.6-32-26.5c-4.3-5.4-8.1-11.3-11.2-17.4c-3.1-6.1-5.6-12.6-7.4-19.1c-1.8-6.6-2.9-13.3-3.4-20.1l-0.1-1.3l0-0.3\tl0-0.3l0-0.6l0-1.1l0-0.3l0-0.4l0-0.8l0-1.6l0-0.3c0,0,0,0.1,0-0.1l0-0.6c0-0.8,0-1.7,0-2.5c0.1-3.3,0.4-6.8,0.8-10.2\tc0.4-3.4,1-6.9,1.7-10.3c0.7-3.4,1.5-6.8,2.5-10.2c1.9-6.7,4.3-13.2,7.1-19.3c5.7-12.2,13.1-23.1,22-31.8c2.2-2.2,4.5-4.2,6.9-6.2\tc2.4-1.9,4.9-3.7,7.5-5.4c2.5-1.7,5.2-3.2,7.9-4.6c1.3-0.7,2.7-1.4,4.1-2c0.7-0.3,1.4-0.6,2.1-0.9c0.7-0.3,1.4-0.6,2.1-0.9\tc2.8-1.2,5.7-2.2,8.7-3.1c0.7-0.2,1.5-0.4,2.2-0.7c0.7-0.2,1.5-0.4,2.2-0.6c1.5-0.4,3-0.8,4.5-1.1c0.7-0.2,1.5-0.3,2.3-0.5\tc0.8-0.2,1.5-0.3,2.3-0.5c0.8-0.1,1.5-0.3,2.3-0.4l1.1-0.2l1.2-0.2c0.8-0.1,1.5-0.2,2.3-0.3c0.9-0.1,1.7-0.2,2.6-0.3\tc0.7-0.1,1.9-0.2,2.6-0.3c0.5-0.1,1.1-0.1,1.6-0.2l1.1-0.1l0.5-0.1l0.6,0c0.9-0.1,1.7-0.1,2.6-0.2l1.3-0.1c0,0,0.5,0,0.1,0l0.3,0\tl0.6,0c0.7,0,1.5-0.1,2.2-0.1c2.9-0.1,5.9-0.1,8.8,0c5.8,0.2,11.5,0.9,17,1.9c11.1,2.1,21.5,5.6,31,10.3\tc9.5,4.6,17.9,10.3,25.3,16.5c0.5,0.4,0.9,0.8,1.4,1.2c0.4,0.4,0.9,0.8,1.3,1.2c0.9,0.8,1.7,1.6,2.6,2.4c0.9,0.8,1.7,1.6,2.5,2.4\tc0.8,0.8,1.6,1.6,2.4,2.5c3.1,3.3,6,6.6,8.6,10c5.2,6.7,9.4,13.5,12.7,19.9c0.2,0.4,0.4,0.8,0.6,1.2c0.2,0.4,0.4,0.8,0.6,1.2\tc0.4,0.8,0.8,1.6,1.1,2.4c0.4,0.8,0.7,1.5,1.1,2.3c0.3,0.8,0.7,1.5,1,2.3c1.2,3,2.4,5.9,3.3,8.6c1.5,4.4,2.6,8.3,3.5,11.7\tc0.3,1.4,1.6,2.3,3,2.1c1.5-0.1,2.6-1.3,2.6-2.8C342.6,170.4,342.5,166.1,342,161.2z\" />\n  </svg>\n);\n\nexport const jaegerIcon = (\n  <svg width=\"17px\" height=\"17px\" viewBox=\"-113.76 -96.84 64 64\">\n    <g transform=\"matrix(.38094 0 0 .38094 -67.511 -92.268)\">\n      <path d=\"M-3.73 35.388s10.563 4.275 17.354 59.354c0 0-9.8 3.772-3.772 10.312s6.8 18.863 6.8 18.863-35.713 5.28-97.833.754c0 0-24.898-50.803-15.1-68.408l16.348.503s23.64 11.82 22.635-.754c0 0 4.352-.432 5.012-.83s5.747 9.38 19.42-6.936c0 0 21.268-8.125 28.798-12.75zm-81.07 9.31S-91.233 26.2-99.367 30.054s.592 17.674 2.884 20.63c0 .001 7.024-4.58 11.683-5.988z\" fill=\"#60d0e4\" />\n      <g fillRule=\"evenodd\">\n        <path d=\"M-30.957 138.898c-.803-1.302-2.67-1.275-4.504-1.542-1.2-.177-3.928-.473-4-1.975-.1-1.64 2.314-2.246 3.95-2.592 3.913-.827 7.814-.68 12.526-.68 2.02 0 4.714.127 4.936 1.48.185 1.136-.775 1.947-1.543 2.468-.682.463-2.06.9-2.097 1.914-.068 1.798 3.24 1.47 2.468 3.948-.285.916-1.874 1.44-3.085 1.8-3.84 1.1-8.353 1.477-12.773 1.852-4 .34-9.118 1.105-13.822.432-1.347-.2-3.124-.706-3.208-1.665-.135-1.537 2.4-2.022 3.764-2.223 3.77-.55 7.307-.308 11.415-.493 2.864-.13 5.74-.1 5.984-2.714zm-40.417.556c2.778-.133 6.68.042 9.44.74.722.183 2.095.48 2.098 1.234.004.806-1.808 1.15-2.838 1.42-5.868 1.52-11.88 2-18.387 2.775-1.183.143-2.412.224-3.703.556-.752.194-2.595.742-2.53 1.666.082 1.14 3.03.83 4.258.926 1.58.122 3.58.314 3.702 1.48.208 1.987-3.717 2.075-5.677 2.22-5.4.405-10.042.203-15.056-.432-.977-.123-2.2-.354-3.208-.74-.9-.347-2.134-.868-2.16-1.728-.04-1.285 1.97-1.77 3.085-2.036 4.14-.982 8.652-1.358 12.958-2.283 2.734-.587 5.427-1.633 7.65-2.53 2.428-.98 4.726-2.248 7.22-2.962 1-.283 1.8-.243 3.146-.308zm51.214 4.32c.644-.065 2.397.007 2.406.863.015 1.252-3.937 1.248-4 .062-.047-.742.872-.85 1.604-.925zm-5.06 1.05c.84-.078 2.244-.03 2.284.864.06 1.354-4 1.296-3.95 0 .028-.585.786-.783 1.665-.864zm-6.046 1.046c.37-.028 1.26-.07 1.913.062.237.048 1.207.405 1.234.74.053.635-1.292.874-1.912.925-1.222.103-3.43-.053-3.518-.802-.086-.737 1.566-.872 2.283-.925zm-8.02.618c.97-.13 3.175-.082 3.208.986.048 1.527-4.9 1.535-4.937.062-.018-.795.977-.947 1.73-1.048zm-9.38.112c.823-.056 1.644-.054 2.53.124.43.085 1.756.392 1.8.985.063 1.135-3.138 1.34-4.7 1.235-.945-.062-2.708-.428-2.715-1.172-.006-.63.952-.843 1.234-.926.673-.195 1.048-.2 1.85-.246zm-57.694 2.47c-.295.9-3.22 1.398-4.32.74.574-1 3.033-1.254 4.32-.74zm.68 1.614c-.753 1.114-3.643 1.518-5.06.803.762-1.104 3.64-1.522 5.06-.803zm1.172 1.48c-.805 1.013-3.65 1.514-5.06.742.563-1.034 3.945-1.55 5.06-.742zm5.183.742c-.523 1.247-4.048 1.798-5.245.802.63-1.312 3.904-1.73 5.245-.802zm4.195.246c.787-.062 2.366-.127 3.208.123 1.56.463.332 1.3-.863 1.604-1.23.323-2.566.476-4.135.37-.738-.05-1.98-.247-2.036-.74-.068-.585 1.127-.873 1.542-.988.9-.252 1.47-.305 2.284-.37zm12.342.678c-.07.783-1.12 1.232-2.037 1.543a13.01 13.01 0 0 1-3.085.616c-.685.043-2.04-.004-2.16-.493-.18-.73 1.466-1.3 1.975-1.48 1.053-.378 2.083-.6 3.023-.678.917-.068 1.945-.04 2.284.493zm51.584-119.087c.07.012.034.062 0 .062-4.13 4.635-7.988 9.6-12.834 13.76-.562.483-1.583 1.47-2.345 1.42-.432-.028-.976-.633-1.418-.803-2.6-.993-5.36 1.026-5.245 3.888-.958.293-1.994.4-3.085.493-1.037.1-2.242.352-3.27.247-.76-.078-1.255-.476-2.283-.37-.473.05-.845.358-1.42.432-.9.114-2.1-.193-3.147-.432-6.56-1.5-12.15-4.92-18.573-6.293.792-.483 1.794-.756 2.715-1.1.158-.298-.32-.778-.185-1.05 15.196 1.584 28.443-2.042 39.8-5.8 3.937-1.303 7.775-2.946 11.3-4.442z\" />\n        <path d=\"M-42.496 39.185c-11.356 3.758-24.603 7.385-39.8 5.8-.134.27.342.75.185 1.05-.92.354-1.923.628-2.715 1.11 6.422 1.373 12.013 4.784 18.573 6.293 1.038.24 2.258.546 3.147.432.574-.074.946-.383 1.42-.432 1.028-.105 1.522.293 2.283.37 1.03.105 2.233-.158 3.27-.247 1.092-.093 2.127-.2 3.085-.493-.114-2.862 2.635-4.88 5.245-3.888.443.17.987.774 1.418.803.762.05 1.783-.937 2.345-1.42 4.846-4.16 8.706-9.125 12.834-13.76.034 0 .07-.05 0-.062-3.515 1.496-7.353 3.14-11.29 4.442z\" fill=\"#638b18\" />\n        <path d=\"M-36.942 141.613c-4.108.186-7.645-.056-11.415.493-1.375.2-3.9.686-3.764 2.223.084.96 1.86 1.474 3.208 1.665 4.704.674 9.822-.093 13.822-.432 4.42-.375 8.933-.74 12.773-1.852 1.2-.35 2.8-.873 3.085-1.8.773-2.48-2.536-2.15-2.468-3.948.038-1.005 1.416-1.45 2.097-1.914.768-.52 1.728-1.332 1.543-2.468-.222-1.354-2.917-1.48-4.936-1.48-4.7 0-8.613-.148-12.526.68-1.635.346-4.04.953-3.95 2.592.083 1.502 2.8 1.798 4 1.975 1.835.267 3.7.24 4.504 1.542-.243 2.613-3.12 2.584-5.984 2.714zm-37.578-1.85c-2.494.714-4.792 1.982-7.22 2.962-2.224.896-4.917 1.942-7.65 2.53-4.306.925-8.818 1.3-12.958 2.283-1.115.266-3.124.75-3.085 2.036.026.86 1.26 1.38 2.16 1.728 1 .386 2.23.617 3.208.74 5.014.635 9.654.837 15.056.432 1.96-.146 5.885-.233 5.677-2.22-.122-1.166-2.122-1.358-3.702-1.48-1.23-.095-4.176.213-4.258-.926-.065-.924 1.777-1.472 2.53-1.666 1.292-.332 2.52-.413 3.703-.556 6.508-.784 12.52-1.255 18.387-2.775 1.03-.27 2.842-.614 2.838-1.42-.003-.756-1.376-1.052-2.098-1.234-2.76-.698-6.663-.873-9.44-.74-1.346.064-2.156.024-3.146.308zm52.757 4.935c.073 1.187 4.025 1.2 4-.062-.01-.856-1.763-.93-2.406-.863-.732.076-1.65.183-1.604.925zm-5.122 1c-.062 1.296 4 1.354 3.95 0-.04-.896-1.445-.942-2.284-.864-.878.08-1.636.278-1.665.864zm-6.665 1.098c.088.75 2.295.904 3.518.802.62-.05 1.965-.3 1.912-.925-.027-.335-.997-.692-1.234-.74-.652-.133-1.542-.1-1.913-.062-.717.053-2.37.188-2.283.925zm-7.465.74c.035 1.473 4.984 1.465 4.937-.062-.034-1.068-2.24-1.116-3.208-.986-.753.1-1.748.252-1.73 1.048zm-9.503-.678c-.282.083-1.24.296-1.234.926.007.744 1.77 1.1 2.715 1.172 1.552.104 4.752-.1 4.7-1.235-.033-.594-1.36-.9-1.8-.985-.886-.178-1.707-.18-2.53-.124-.8.055-1.175.05-1.85.246zm-60.16 2.962c1.1.658 4.024.17 4.32-.74-1.286-.514-3.745-.26-4.32.74zm-.062 1.666c1.417.715 4.307.312 5.06-.803-1.42-.718-4.298-.3-5.06.803zm1.172 1.42c1.4.77 4.255.27 5.06-.742-1.115-.8-4.497-.292-5.06.742zm4.998.802c1.197.996 4.72.445 5.245-.802-1.34-.927-4.614-.5-5.245.802zm7.157-.188c-.415.115-1.6.403-1.542.988.057.493 1.297.7 2.036.74 1.57.105 2.904-.047 4.135-.37 1.195-.315 2.424-1.142.863-1.604-.842-.25-2.42-.185-3.208-.123-.813.064-1.373.117-2.284.37zm12.343-.182c-.94.068-1.97.3-3.023.678-.5.183-2.156.75-1.975 1.48.12.5 1.475.536 2.16.493a13.06 13.06 0 0 0 3.085-.616c.917-.3 1.967-.76 2.037-1.543-.34-.532-1.368-.56-2.284-.493z\" />\n      </g>\n      <path d=\"M-32.024 24.95C-69.12 41.047-87.73 34.004-87.73 34.004l4.4 8.803c26.533 3.018 53.8-11.255 53.8-11.255l1.364-3.738s-2.26.487-3.56-2.96z\" fill=\"#e1caa2\" />\n      <path d=\"M-29.32 14.167l-.472-1.603S-59.69.87-66.48 4.453-82.04 14.262-88.454 23.88l-.094 7.64s18.423 7.64 56.4-8.833c0 0-.503-4.592 2.83-8.52z\" fill=\"#638b18\" />\n      <path d=\"M-27.02 25.048c-.45.392-.874.68-1.32.593-1.348-.265-1.97-2.884-1.388-5.85s2.148-5.155 3.498-4.9c1.297.255 1.92 2.688 1.448 5.514 0 0-1.436 2.635-2.116 4.485z\" />\n      <path d=\"M-8.635 11.307s-6.79-4.464-14.02 5.47c0 0-.943-5.91-5.785-3.396 0 0 .943-8.488.314-9.683 0 0 1.887.566 2.452 1.823 0 0 4.968-4.84 4.653-11.568 0 0 1.32.188 1.32 1.886 0 0 1.85-4.092 3.663-6.298l.172-.242c7.293-6.35.566 12.576.566 12.576s-.392.94-.793 1.446 3.454-1.285 3.407-2.747.483 2.063 0 2.7c0 0 6.378-5.045 6.566-7.687 0 0 1.006 2.012.754 3.646 0 0 5.658-4.275 5.785-6.288 0 0 .69 10.877-10.06 15.027 0 0 1.76 0 3.96-.44z\" fill=\"#e1caa2\" />\n      <path d=\"M-5.05 24.322s22.698-5.03 10.626 3.08-13.833 7.734-13.833 7.734S-5.805 31.5-5.05 24.322zM-8.634 35.45l-20.875 9.117s6.477-5.533 4.276-8.552c0 .001 10.94-1.633 16.6-.565z\" fill=\"#638b18\" />\n      <path d=\"M-28 33.88c5.784-16.033 8.692-17.838 8.692-17.838 2.742-2.95 6.193-4.25 8.918-3.002 2.36 1.08 3.626 3.823 3.637 7.137l-.001.21s-.435 8.086-2.698 12.676c0 0-8.425-1.195-17.48 1.195z\" fill=\"#60d0e4\" />\n      <path d=\"M-19.74 26.715s4.526-9.008 7.288-6.926c0 0 .68.815-1.086 6.745 0 0 7.3-8.918-.27-9.145-.001.001-5.07-.18-5.93 9.326zM-90.1 44.397s-5.96-9.016-6.5-7.22.36 4.972.5 5.84-4.554-5.542-1.408-7.88 7.04 4.914 7.4 9.257z\" />\n      <path d=\"M-97.047 53.515h.235s3.454-2.513 4.475-2.905-1.256 3.768 4.788 3.768-15.7.785-20.645-3.532c0 0-.43-1.867 6.198-4.94 0 0 4 7.148 4.95 7.6z\" fill=\"#638b18\" />\n      <path d=\"M-75.77 55.948c-6.837-3.02-11.954-3.9-11.954-3.9-.05.004-.102.006-.153.006-1.074 0-1.946-.697-1.946-1.556s.872-1.556 1.946-1.556a2.61 2.61 0 0 1 .268.014s5.02.84 12.83 4.103l.85.35c7.262 2.688 10.186 2.76 10.186 2.76.98.083 1.238.083 1.368-.012s.943-.412.943-.412c1.033.027 1.862.743 1.862 1.622 0 .846-1.45 1.767-2.428 1.844 0 0-5.375.424-13.486-3.16zm48.6-18.112c.017-.085.025-.173.025-.264 0-.778-.644-1.4-1.438-1.4a1.47 1.47 0 0 0-.569.114l-5.944 6.487c-2.436 2.758-8.392 9.272-10.886 9.565l-1.408.146c-.118-.234-.754-.8-.754-.8-.4-.355-.94-.505-1.454-.348-.82.25-1.262 1.2-.984 2.098a1.85 1.85 0 0 0 .565.868s4.124 4.177 11.224-3.247l7.746-8.186s3.6-3.932 3.843-4.87z\" />\n      <path d=\"M-14.754 67.066c0 7.885-6.4 14.283-14.3 14.283S-43.35 74.952-43.35 67.066c0-7.892 6.4-14.3 14.296-14.3a14.3 14.3 0 0 1 14.3 14.29zm-44.4 10.438c0 7.886-6.4 14.283-14.3 14.283S-87.74 85.4-87.74 77.504s6.4-14.3 14.297-14.3a14.3 14.3 0 0 1 14.3 14.3zm11.916 22.896c.377 2.137 1.257 6.664 4.275 6.286s1.887-6.413 1.887-6.413-5.156-.25-5.66 0zm-1.76.594s-2.768 1.18-4.977 2.62c0 0 1.1 5.624 4.77 3.945 0 0 2.415-.532.354-6.187zm-7.028-12.496s-17.6 5.3-8.967 14.2c5.66 5.834 13.494-4.1 17.672-4.004 0 0 .958-.435 2.96-.174s15.062 1.306 16.28-3.918-.348-6.094-1.306-6.703-7.835-4.7-18.108-1.828z\" fill=\"#fff\" />\n      <circle cx=\"-74.702\" cy=\"82.41\" r=\"4.481\" />\n      <ellipse cx=\"-30.94\" cy=\"71.722\" rx=\"4.481\" ry=\"4.476\" />\n      <ellipse transform=\"matrix(.9801 -.1986 .1986 .9801 -18.308 -8.1092)\" cx=\"-49.585\" cy=\"87.228\" rx=\"7.93\" ry=\"4.328\" />\n      <path d=\"M-57.802 91.347s-11.066 5.155-5.9 9.43 7.67-2.64 15.215-3.897 16.222.754 17.982-2.516 2.4-5.9-9.935-7.042c0 0-1.13 5.408-7.67 6.036s-7.668-.504-9.68-2.012z\" fill=\"#e1caa2\" />\n      <path d=\"m29.777 115.67s18.272 3.873-8.144 7.747c0 0 6.257-2.582 8.144-7.747z\" fill=\"#231f20\" />\n      <path d=\"m17.264 117.45s-0.597-9.333-6.158-14.398 4.668-6.653 7.846-5.264 16.386 10.527 4.766 22.245c0 0-4.965 3.575-6.355-2.085z\" fill=\"#e1caa2\" />\n      <path d=\"M-99.488 115.365s-.943 4.715-.188 6.696c0 0-15.94-2.736-.755-6.508z\" />\n      <path d=\"m-91.85 104.71s-9.787 11.58-4.716 21.03c4.15 7.734 12.543-1.036 12.732-1.32z\" fill=\"#e1caa2\" />\n    </g>\n  </svg>\n);\n\nexport const linkerdLogoOnly = (\n  <svg id=\"linkerd-logo\" className=\"linkerd-logo-only\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 138.9 129.2\">\n    <linearGradient\n      id=\"SVGID_1_\"\n      gradientUnits=\"userSpaceOnUse\"\n      x1=\"130.52\"\n      y1=\"24.532\"\n      x2=\"130.52\"\n      y2=\"79.491\">\n      <stop offset=\"0\" stopColor=\"#2beda7\" />\n      <stop offset=\"1\" stopColor=\"#018afd\" />\n    </linearGradient>\n    <polygon\n      className=\"st0\"\n      points=\"125.9,24.5 125.9,74.2 135.1,79.5 135.1,29.8\" />\n    <linearGradient\n      id=\"SVGID_2_\"\n      gradientUnits=\"userSpaceOnUse\"\n      x1=\"7.78\"\n      y1=\"24.534\"\n      x2=\"7.78\"\n      y2=\"79.495\">\n      <stop offset=\"0\" stopColor=\"#2beda7\" />\n      <stop offset=\"1\" stopColor=\"#018afd\" />\n    </linearGradient>\n    <polygon\n      className=\"st1\"\n      points=\"3.2,79.5 12.4,74.2 12.4,24.5 3.2,29.8\" />\n    <g>\n      <polygon\n        className=\"st2\"\n        points=\"48.1,79 90.2,103.3 90.2,92.8 50.8,70 48.1,71.5 48.1,71.5\" />\n      <path\n        className=\"st2\"\n        d=\"M92.6,117.4L48.1,91.7v10.6l11.9,6.9l-14.3,8.3c-1,0.6-1,1.9,0,2.5l7,4l16.5-9.5l16.5,9.5l7-4 C93.6,119.4,93.6,118,92.6,117.4z\" />\n    </g>\n    <g>\n      <linearGradient\n        id=\"SVGID_3_\"\n        gradientUnits=\"userSpaceOnUse\"\n        x1=\"130.52\"\n        y1=\"48.861\"\n        x2=\"130.52\"\n        y2=\"99.634\">\n        <stop offset=\"0\" stopColor=\"#2beda7\" />\n        <stop offset=\"1\" stopColor=\"#018afd\" />\n      </linearGradient>\n      <path\n        className=\"st3\"\n        d=\"M125.9,54.1v44c0,1.1,1.2,1.8,2.2,1.3l6.3-3.6c0.4-0.3,0.7-0.7,0.7-1.3V48.9L125.9,54.1z\" />\n      <linearGradient\n        id=\"SVGID_4_\"\n        gradientUnits=\"userSpaceOnUse\"\n        x1=\"110.368\"\n        y1=\"60.496\"\n        x2=\"110.368\"\n        y2=\"111.269\">\n        <stop offset=\"0\" stopColor=\"#2beda7\" />\n        <stop offset=\"1\" stopColor=\"#018afd\" />\n      </linearGradient>\n      <path\n        className=\"st4\"\n        d=\"M108,111.1l5.9-3.4c0.7-0.4,1.1-1.1,1.1-1.9V60.5l-9.2,5.3v44C105.8,110.9,107,111.6,108,111.1z\" />\n      <linearGradient\n        id=\"SVGID_5_\"\n        gradientUnits=\"userSpaceOnUse\"\n        x1=\"90.216\"\n        y1=\"72.13\"\n        x2=\"90.216\"\n        y2=\"123.959\">\n        <stop offset=\"0\" stopColor=\"#2beda7\" />\n        <stop offset=\"1\" stopColor=\"#018afd\" />\n      </linearGradient>\n      <path\n        className=\"st5\"\n        d=\"M85.6,124l8.4-4.9c0.4-0.3,0.7-0.7,0.7-1.3V72.1l-9.2,5.3V124z\" />\n      <linearGradient\n        id=\"SVGID_6_\"\n        gradientUnits=\"userSpaceOnUse\"\n        x1=\"48.089\"\n        y1=\"72.13\"\n        x2=\"48.089\"\n        y2=\"123.958\">\n        <stop offset=\"0\" stopColor=\"#2beda7\" />\n        <stop offset=\"1\" stopColor=\"#018afd\" />\n      </linearGradient>\n      <path\n        className=\"st6\"\n        d=\"M44.2,119.1l8.4,4.9V77.4l-9.2-5.3v45.7C43.5,118.4,43.8,118.8,44.2,119.1z\" />\n      <linearGradient\n        id=\"SVGID_7_\"\n        gradientUnits=\"userSpaceOnUse\"\n        x1=\"27.937\"\n        y1=\"60.495\"\n        x2=\"27.937\"\n        y2=\"111.269\">\n        <stop offset=\"0\" stopColor=\"#2beda7\" />\n        <stop offset=\"1\" stopColor=\"#018afd\" />\n      </linearGradient>\n      <path\n        className=\"st7\"\n        d=\"M24.4,107.7l5.9,3.4c1,0.6,2.2-0.1,2.2-1.3v-44l-9.2-5.3v45.3C23.4,106.6,23.8,107.3,24.4,107.7z\" />\n      <linearGradient\n        id=\"SVGID_8_\"\n        gradientUnits=\"userSpaceOnUse\"\n        x1=\"7.785\"\n        y1=\"48.861\"\n        x2=\"7.785\"\n        y2=\"99.633\">\n        <stop offset=\"0\" stopColor=\"#2beda7\" />\n        <stop offset=\"1\" stopColor=\"#018afd\" />\n      </linearGradient>\n      <path\n        className=\"st8\"\n        d=\"M4.3,96l5.9,3.4c1,0.6,2.2-0.1,2.2-1.3v-44l-9.2-5.3v45.3C3.2,94.9,3.6,95.6,4.3,96z\" />\n    </g>\n    <g>\n      <path\n        className=\"st2\"\n        d=\"M135.1,29.8l-9.2-5.3l-16.5,9.5l-11-6.3l14.3-8.3c1-0.6,1-1.9,0-2.5l-5.9-3.4c-0.7-0.4-1.5-0.4-2.2,0 l-15.4,8.9l-11-6.3l14.3-8.3c1-0.6,1-1.9,0-2.5l-5.9-3.4c-0.7-0.4-1.5-0.4-2.2,0l-15.4,8.9L53.7,1.9c-0.7-0.4-1.5-0.4-2.2,0 l-5.9,3.4c-1,0.6-1,1.9,0,2.5l80.3,46.3l9.2-5.3l-16.5-9.5L135.1,29.8z\" />\n      <path\n        className=\"st2\"\n        d=\"M31.4,13.5l-5.9,3.4c-1,0.6-1,1.9,0,2.5l80.3,46.3l9.2-5.3l-81.4-47C32.9,13.1,32.1,13.1,31.4,13.5z\" />\n      <polygon\n        className=\"st2\"\n        points=\"3.2,29.8 19.7,39.3 3.2,48.9 12.4,54.2 28.9,44.6 39.8,51 23.4,60.5 32.5,65.8 49,56.3 60,62.6 43.5,72.1 52.7,77.4 69.1,67.9 85.6,77.4 94.8,72.1 12.4,24.5\" />\n    </g>\n  </svg>\n);\n\nexport const linkerdWordLogo = (\n  <svg className=\"linkerd-word-logo\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1092.4 233.7\">\n    <g id=\"Layer_1\">\n      <path\n        className=\"st2\"\n        d=\"M300.7,164.6v-97c0-4.6,3.9-8.5,8.7-8.5c4.6,0,8.4,3.9,8.4,8.5v91.1h44.8c4.1,0,7.7,3.2,7.7,7.3 c0,4.3-3.6,7.7-7.7,7.7h-52.7C303.9,173.6,300.7,170.4,300.7,164.6z\" />\n      <path\n        className=\"st2\"\n        d=\"M403.5,165.9V67.5c0-4.6,3.9-8.5,8.7-8.5c4.6,0,8.4,3.9,8.4,8.5v98.4c0,4.6-3.8,8.5-8.4,8.5 C407.4,174.5,403.5,170.5,403.5,165.9z\" />\n      <path\n        className=\"st2\"\n        d=\"M538.7,170l-60.4-83.7v79.6c0,4.6-3.8,8.5-8.4,8.5c-4.8,0-8.7-3.9-8.7-8.5V70.3c0-6.1,4.9-11.3,11.3-11.3 c3.6,0,7,1.7,9,4.6l59.2,81.5V67.5c0-4.6,3.8-8.5,8.4-8.5c4.8,0,8.7,3.9,8.7,8.5v96.3c0,5.8-4.8,10.6-10.6,10.6 C543.7,174.5,540.6,172.9,538.7,170z\" />\n      <path\n        className=\"st2\"\n        d=\"M683.5,166.3c0,4.4-3.4,8.2-7.8,8.2c-2.2,0-4.3-0.5-6-2.4l-43.3-47.6l-10.9,11.6v29.8c0,4.6-3.8,8.5-8.5,8.5 c-4.6,0-8.5-3.9-8.5-8.5V67.5c0-4.6,3.9-8.5,8.5-8.5c4.8,0,8.5,3.9,8.5,8.5v49.1l50.1-55.1c1.7-1.9,3.8-2.6,6-2.6 c4.3,0,8,3.4,8,7.8c0,2-0.5,3.9-2.2,5.5l-39.6,42.1l43.5,46.4C682.6,162.4,683.5,164.4,683.5,166.3z\" />\n      <path\n        className=\"st2\"\n        d=\"M717.9,164.6V68.9c0-5.8,3.2-9,9.2-9h60.4c4.1,0,7.7,3.4,7.7,7.7c0,4.1-3.6,7.3-7.7,7.3H735v33.3h51.3 c4.1,0,7.7,3.2,7.7,7.3c0,4.3-3.6,7.7-7.7,7.7H735v35.5h52.5c4.1,0,7.7,3.2,7.7,7.3c0,4.3-3.6,7.7-7.7,7.7h-60.4 C721.2,173.6,717.9,170.4,717.9,164.6z\" />\n      <path\n        className=\"st2\"\n        d=\"M915.7,166.6c0,3.6-3.2,7.8-8,7.8c-2.9,0-5.6-1.4-7.2-3.6l-28.5-41.1h-24v36.2c0,4.6-3.8,8.5-8.4,8.5 c-4.8,0-8.7-3.9-8.7-8.5v-97c0-5.8,3.2-9,9.2-9h38.7c21.7,0,36.5,14,36.5,35c0,19.1-11.9,30-25.4,32.7l24,33.8 C915,162.5,915.7,163.9,915.7,166.6z M848,114.8h28.6c12.3,0,21.1-8,21.1-20c0-11.9-8.9-20-21.1-20H848V114.8z\" />\n      <path\n        className=\"st2\"\n        d=\"M951.7,164.6V68.9c0-5.8,3.2-9,9.2-9h31.4c35.5,0,59,24.2,59,57c0,32.9-23.5,56.8-59,56.8h-31.4 C954.9,173.6,951.7,170.4,951.7,164.6z M992.3,158.6c26.3,0,41.4-18.8,41.4-41.8c0-23.4-14.7-41.9-41.4-41.9h-23.5v83.7H992.3z\" />\n      <g id=\"linkerd-hash\">\n        <linearGradient\n          id=\"SVGID_1_\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"239.749\"\n          y1=\"43.139\"\n          x2=\"239.749\"\n          y2=\"146.622\">\n          <stop offset=\"0\" stopColor=\"#2beda7\" />\n          <stop offset=\"1\" stopColor=\"#018afd\" />\n        </linearGradient>\n        <polygon\n          className=\"st3\"\n          points=\"231.1,43.1 231.1,136.7 248.4,146.6 248.4,53.1\" />\n        <linearGradient\n          id=\"SVGID_2_\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"8.642\"\n          y1=\"43.142\"\n          x2=\"8.642\"\n          y2=\"146.628\">\n          <stop offset=\"0\" stopColor=\"#2beda7\" />\n          <stop offset=\"1\" stopColor=\"#018afd\" />\n        </linearGradient>\n        <polygon\n          className=\"st4\"\n          points=\"0,146.6 17.3,136.7 17.3,43.1 0,53.1\" />\n        <g>\n          <polygon\n            className=\"st5\"\n            points=\"84.5,145.7 163.9,191.5 163.9,171.6 89.6,128.7 84.5,131.6 84.5,131.7\" />\n          <path\n            className=\"st5\"\n            d=\"M168.4,218l-83.9-48.4v19.9l22.4,12.9L80,218c-1.8,1-1.8,3.7,0,4.7l13.2,7.6l31-17.9l31,17.9l13.2-7.6 C170.2,221.7,170.2,219.1,168.4,218z\" />\n        </g>\n        <g>\n          <linearGradient\n            id=\"SVGID_3_\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"239.749\"\n            y1=\"88.949\"\n            x2=\"239.749\"\n            y2=\"184.549\">\n            <stop offset=\"0\" stopColor=\"#2beda7\" />\n            <stop offset=\"1\" stopColor=\"#018afd\" />\n          </linearGradient>\n          <path\n            className=\"st6\"\n            d=\"M231.1,98.9v82.9c0,2.1,2.3,3.4,4.1,2.4l11.8-6.8c0.8-0.5,1.4-1.4,1.4-2.4V88.9L231.1,98.9z\" />\n          <linearGradient\n            id=\"SVGID_4_\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"201.805\"\n            y1=\"110.855\"\n            x2=\"201.805\"\n            y2=\"206.455\">\n            <stop offset=\"0\" stopColor=\"#2beda7\" />\n            <stop offset=\"1\" stopColor=\"#018afd\" />\n          </linearGradient>\n          <path\n            className=\"st7\"\n            d=\"M197.3,206.1l11.1-6.4c1.3-0.7,2-2.1,2-3.5v-85.3l-17.2,10v82.9C193.2,205.8,195.4,207.1,197.3,206.1z\" />\n          <linearGradient\n            id=\"SVGID_5_\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"163.861\"\n            y1=\"132.761\"\n            x2=\"163.861\"\n            y2=\"230.35\">\n            <stop offset=\"0\" stopColor=\"#2beda7\" />\n            <stop offset=\"1\" stopColor=\"#018afd\" />\n          </linearGradient>\n          <path\n            className=\"st8\"\n            d=\"M155.2,230.3l15.9-9.2c0.8-0.5,1.4-1.4,1.4-2.4v-86.1l-17.2,10V230.3z\" />\n          <linearGradient\n            id=\"SVGID_6_\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"84.539\"\n            y1=\"132.761\"\n            x2=\"84.539\"\n            y2=\"230.348\">\n            <stop offset=\"0\" stopColor=\"#2beda7\" />\n            <stop offset=\"1\" stopColor=\"#018afd\" />\n          </linearGradient>\n          <path\n            className=\"st9\"\n            d=\"M77.3,221.2l15.9,9.2v-87.6l-17.2-10v86.1C75.9,219.8,76.4,220.7,77.3,221.2z\" />\n          <linearGradient\n            id=\"SVGID_7_\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"46.595\"\n            y1=\"110.855\"\n            x2=\"46.595\"\n            y2=\"206.455\">\n            <stop offset=\"0\" stopColor=\"#2beda7\" />\n            <stop offset=\"1\" stopColor=\"#018afd\" />\n          </linearGradient>\n          <path\n            className=\"st10\"\n            d=\"M40,199.7l11.1,6.4c1.8,1,4.1-0.3,4.1-2.4v-82.9l-17.2-10v85.3C38,197.6,38.7,198.9,40,199.7z\" />\n          <linearGradient\n            id=\"SVGID_8_\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"8.651\"\n            y1=\"88.947\"\n            x2=\"8.651\"\n            y2=\"184.548\">\n            <stop offset=\"0\" stopColor=\"#2beda7\" />\n            <stop offset=\"1\" stopColor=\"#018afd\" />\n          </linearGradient>\n          <path\n            className=\"st11\"\n            d=\"M2.1,177.8l11.1,6.4c1.8,1,4.1-0.3,4.1-2.4V98.9L0,88.9v85.3C0,175.7,0.8,177,2.1,177.8z\" />\n        </g>\n        <g>\n          <path\n            className=\"st5\"\n            d=\"M248.4,53.1l-17.3-10l-31,17.9l-20.7-11.9l27-15.6c1.8-1,1.8-3.7,0-4.7l-11.1-6.4c-1.3-0.7-2.8-0.7-4.1,0 l-29,16.7l-20.7-11.9l27-15.6c1.8-1,1.8-3.7,0-4.7l-11.1-6.4c-1.3-0.7-2.8-0.7-4.1,0l-29,16.7l-29-16.8c-1.3-0.7-2.8-0.7-4.1,0 L80,6.9c-1.8,1-1.8,3.7,0,4.7l151.1,87.3l17.2-10l-31-17.9L248.4,53.1z\" />\n          <path\n            className=\"st5\"\n            d=\"M53.2,22.4L42,28.8c-1.8,1-1.8,3.7,0,4.7l151.1,87.3l17.3-10L57.3,22.4C56,21.7,54.4,21.7,53.2,22.4z\" />\n          <polygon\n            className=\"st5\"\n            points=\"0,53.1 31.1,71 0,89 17.3,98.9 48.3,81 69,92.9 38,110.9 55.2,120.8 86.3,102.9 106.9,114.8 75.9,132.8 93.2,142.7 124.2,124.8 155.2,142.7 172.5,132.8 17.3,43.1\" />\n        </g>\n      </g>\n    </g>\n  </svg>\n);\nexport const namespaceIcon = (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    version=\"1.1\"\n    width=\"24px\"\n    height=\"24px\"\n    viewBox=\"0 0 18 18\">\n    <g fillOpacity=\"1\">\n      <g\n        stroke=\"none\"\n        strokeDasharray=\"none\"\n        strokeMiterlimit=\"4\"\n        strokeWidth=\"0\">\n        <path\n          fill=\"#757575\"\n          strokeOpacity=\"1\"\n          d=\"M-6.85 4.272a1.12 1.11 0 00-.428.109l-5.852 2.796a1.12 1.11 0 00-.606.753l-1.444 6.282a1.12 1.11 0 00.152.85 1.12 1.11 0 00.064.089l4.05 5.037a1.12 1.11 0 00.876.417l6.496-.001a1.12 1.11 0 00.875-.417l4.049-5.038a1.12 1.11 0 00.216-.939L.152 7.93a1.12 1.11 0 00-.605-.753L-6.307 4.38a1.12 1.11 0 00-.542-.109z\"\n          transform=\"matrix(1.01489 0 0 1.01489 16.902 -2.699)\" />\n        <path\n          fill=\"#fff\"\n          fillRule=\"nonzero\"\n          d=\"M-6.852 3.818a1.181 1.172 0 00-.452.115l-6.18 2.951a1.181 1.172 0 00-.638.795l-1.524 6.63a1.181 1.172 0 00.16.9 1.181 1.172 0 00.067.093l4.276 5.317a1.181 1.172 0 00.924.44h6.858a1.181 1.172 0 00.923-.44L1.837 15.3a1.181 1.172 0 00.228-.99L.54 7.677a1.181 1.172 0 00-.64-.795l-6.178-2.95a1.181 1.172 0 00-.573-.115zm.003.455a1.12 1.11 0 01.542.108l5.853 2.795a1.12 1.11 0 01.606.753l1.446 6.281a1.12 1.11 0 01-.216.94l-4.05 5.037a1.12 1.11 0 01-.875.417l-6.496.001a1.12 1.11 0 01-.875-.417l-4.05-5.037a1.12 1.11 0 01-.064-.088 1.12 1.11 0 01-.152-.851l1.444-6.281a1.12 1.11 0 01.605-.753l5.853-2.797a1.12 1.11 0 01.429-.108z\"\n          baselineShift=\"baseline\"\n          color=\"#000\"\n          direction=\"ltr\"\n          display=\"inline\"\n          enableBackground=\"accumulate\"\n          fontFamily=\"Sans\"\n          fontSize=\"medium\"\n          fontStretch=\"normal\"\n          fontStyle=\"normal\"\n          fontVariant=\"normal\"\n          fontWeight=\"normal\"\n          letterSpacing=\"normal\"\n          overflow=\"visible\"\n          textAnchor=\"start\"\n          textDecoration=\"none\"\n          transform=\"matrix(1.01489 0 0 1.01489 16.902 -2.699)\"\n          style={{\n            lineHeight: 'normal',\n            InkscapeFontSpecification: 'Sans',\n            WebkitTextIndent: '0',\n            textIndent: '0',\n            WebkitTextAlign: 'start',\n            textAlign: 'start',\n            WebkitTextDecorationLine: 'none',\n            textDecorationLine: 'none',\n            WebkitTextTransform: 'none',\n            textTransform: 'none',\n            marker: 'none',\n          }}\n          visibility=\"visible\"\n          wordSpacing=\"normal\"\n          writingMode=\"lr-tb\" />\n      </g>\n      <path\n        fill=\"none\"\n        fillRule=\"nonzero\"\n        stroke=\"#fff\"\n        strokeDasharray=\"0.80000001, 0.4\"\n        strokeDashoffset=\"3.44\"\n        strokeLinecap=\"butt\"\n        strokeLinejoin=\"round\"\n        strokeMiterlimit=\"10\"\n        strokeOpacity=\"1\"\n        strokeWidth=\"0.4\"\n        d=\"M6.173 6.579H13.847000000000001V13.269H6.173z\"\n        opacity=\"1\" />\n    </g>\n  </svg>\n);\nexport const daemonsetIcon = (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    version=\"1.1\"\n    width=\"24px\"\n    height=\"24px\"\n    viewBox=\"0 0 18 18\">\n    <g\n      fillOpacity=\"1\"\n      stroke=\"none\"\n      strokeDasharray=\"none\"\n      strokeMiterlimit=\"4\"\n      strokeWidth=\"0\">\n      <path\n        fill=\"#757575\"\n        strokeOpacity=\"1\"\n        d=\"M-6.85 4.272a1.12 1.11 0 00-.428.109l-5.852 2.796a1.12 1.11 0 00-.606.753l-1.444 6.282a1.12 1.11 0 00.152.85 1.12 1.11 0 00.064.089l4.05 5.037a1.12 1.11 0 00.876.417l6.496-.001a1.12 1.11 0 00.875-.417l4.049-5.038a1.12 1.11 0 00.216-.939L.152 7.93a1.12 1.11 0 00-.605-.753L-6.307 4.38a1.12 1.11 0 00-.542-.109z\"\n        transform=\"translate(-.993 -1.174) matrix(1.01489 0 0 1.01489 16.902 -2.699)\" />\n      <path\n        fill=\"#fff\"\n        fillRule=\"nonzero\"\n        d=\"M-6.852 3.818a1.181 1.172 0 00-.452.115l-6.18 2.951a1.181 1.172 0 00-.638.795l-1.524 6.63a1.181 1.172 0 00.16.9 1.181 1.172 0 00.067.093l4.276 5.317a1.181 1.172 0 00.924.44h6.858a1.181 1.172 0 00.923-.44L1.837 15.3a1.181 1.172 0 00.228-.99L.54 7.677a1.181 1.172 0 00-.64-.795l-6.178-2.95a1.181 1.172 0 00-.573-.115zm.003.455a1.12 1.11 0 01.542.108l5.853 2.795a1.12 1.11 0 01.606.753l1.446 6.281a1.12 1.11 0 01-.216.94l-4.05 5.037a1.12 1.11 0 01-.875.417l-6.496.001a1.12 1.11 0 01-.875-.417l-4.05-5.037a1.12 1.11 0 01-.064-.088 1.12 1.11 0 01-.152-.851l1.444-6.281a1.12 1.11 0 01.605-.753l5.853-2.797a1.12 1.11 0 01.429-.108z\"\n        baselineShift=\"baseline\"\n        color=\"#000\"\n        direction=\"ltr\"\n        display=\"inline\"\n        enableBackground=\"accumulate\"\n        fontFamily=\"Sans\"\n        fontSize=\"medium\"\n        fontStretch=\"normal\"\n        fontStyle=\"normal\"\n        fontVariant=\"normal\"\n        fontWeight=\"normal\"\n        letterSpacing=\"normal\"\n        overflow=\"visible\"\n        textAnchor=\"start\"\n        textDecoration=\"none\"\n        transform=\"translate(-.993 -1.174) matrix(1.01489 0 0 1.01489 16.902 -2.699)\"\n        style={{\n          lineHeight: 'normal',\n          InkscapeFontSpecification: 'Sans',\n          WebkitTextIndent: '0',\n          textIndent: '0',\n          WebkitTextAlign: 'start',\n          textAlign: 'start',\n          WebkitTextDecorationLine: 'none',\n          textDecorationLine: 'none',\n          WebkitTextTransform: 'none',\n          textTransform: 'none',\n          marker: 'none',\n        }}\n        visibility=\"visible\"\n        wordSpacing=\"normal\"\n        writingMode=\"lr-tb\" />\n    </g>\n    <g fillRule=\"evenodd\">\n      <path\n        fill=\"none\"\n        stroke=\"#fff\"\n        strokeDasharray=\"1.58743756, 1.58743756\"\n        strokeDashoffset=\"3.667\"\n        strokeLinecap=\"square\"\n        strokeLinejoin=\"round\"\n        strokeMiterlimit=\"10\"\n        strokeOpacity=\"1\"\n        strokeWidth=\"0.529\"\n        d=\"M7.708 5.283h6.525v4.583H7.708z\"\n        transform=\"translate(-.993 -1.174) translate(.586 .457)\" />\n      <path\n        fill=\"none\"\n        stroke=\"#fff\"\n        strokeDasharray=\"none\"\n        strokeLinecap=\"butt\"\n        strokeLinejoin=\"miter\"\n        strokeMiterlimit=\"4\"\n        strokeOpacity=\"1\"\n        strokeWidth=\"0.618\"\n        d=\"M4.35 13.607h7.075\"\n        transform=\"translate(-.993 -1.174) translate(.586 .457)\" />\n      <path\n        fill=\"#757575\"\n        fillOpacity=\"1\"\n        stroke=\"#fff\"\n        strokeDasharray=\"1.58743756, 1.58743756\"\n        strokeDashoffset=\"3.879\"\n        strokeLinecap=\"square\"\n        strokeLinejoin=\"round\"\n        strokeMiterlimit=\"10\"\n        strokeOpacity=\"1\"\n        strokeWidth=\"0.529\"\n        d=\"M6.17 6.694h6.525v4.583H6.17z\"\n        transform=\"translate(-.993 -1.174) translate(.586 .457)\" />\n      <path\n        fill=\"none\"\n        stroke=\"#fff\"\n        strokeLinecap=\"butt\"\n        strokeLinejoin=\"round\"\n        strokeMiterlimit=\"10\"\n        strokeOpacity=\"1\"\n        strokeWidth=\"0.529\"\n        d=\"M4.63 8.105h6.526v4.584H4.63z\"\n        transform=\"translate(-.993 -1.174) translate(.586 .457)\" />\n      <path\n        fill=\"#fff\"\n        stroke=\"none\"\n        strokeLinecap=\"square\"\n        strokeMiterlimit=\"10\"\n        strokeWidth=\"0.265\"\n        d=\"M4.587 8.123h6.525v4.583H4.587z\"\n        transform=\"translate(-.993 -1.174) translate(.586 .457)\" />\n    </g>\n  </svg>\n);\n\nexport const deploymentIcon = (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    version=\"1.1\"\n    width=\"24px\"\n    height=\"24px\"\n    viewBox=\"0 0 18 18\">\n    <g stroke=\"none\">\n      <g\n        fillOpacity=\"1\"\n        strokeDasharray=\"none\"\n        strokeMiterlimit=\"4\"\n        strokeWidth=\"0\">\n        <path\n          fill=\"#757575\"\n          strokeOpacity=\"1\"\n          d=\"M-6.85 4.272a1.12 1.11 0 00-.428.109l-5.852 2.796a1.12 1.11 0 00-.606.753l-1.444 6.282a1.12 1.11 0 00.152.85 1.12 1.11 0 00.064.089l4.05 5.037a1.12 1.11 0 00.876.417l6.496-.001a1.12 1.11 0 00.875-.417l4.049-5.038a1.12 1.11 0 00.216-.939L.152 7.93a1.12 1.11 0 00-.605-.753L-6.307 4.38a1.12 1.11 0 00-.542-.109z\"\n          transform=\"translate(-.993 -1.174) matrix(1.01489 0 0 1.01489 16.902 -2.699)\" />\n        <path\n          fill=\"#fff\"\n          fillRule=\"nonzero\"\n          d=\"M-6.852 3.818a1.181 1.172 0 00-.452.115l-6.18 2.951a1.181 1.172 0 00-.638.795l-1.524 6.63a1.181 1.172 0 00.16.9 1.181 1.172 0 00.067.093l4.276 5.317a1.181 1.172 0 00.924.44h6.858a1.181 1.172 0 00.923-.44L1.837 15.3a1.181 1.172 0 00.228-.99L.54 7.677a1.181 1.172 0 00-.64-.795l-6.178-2.95a1.181 1.172 0 00-.573-.115zm.003.455a1.12 1.11 0 01.542.108l5.853 2.795a1.12 1.11 0 01.606.753l1.446 6.281a1.12 1.11 0 01-.216.94l-4.05 5.037a1.12 1.11 0 01-.875.417l-6.496.001a1.12 1.11 0 01-.875-.417l-4.05-5.037a1.12 1.11 0 01-.064-.088 1.12 1.11 0 01-.152-.851l1.444-6.281a1.12 1.11 0 01.605-.753l5.853-2.797a1.12 1.11 0 01.429-.108z\"\n          baselineShift=\"baseline\"\n          color=\"#000\"\n          direction=\"ltr\"\n          display=\"inline\"\n          enableBackground=\"accumulate\"\n          fontFamily=\"Sans\"\n          fontSize=\"medium\"\n          fontStretch=\"normal\"\n          fontStyle=\"normal\"\n          fontVariant=\"normal\"\n          fontWeight=\"normal\"\n          letterSpacing=\"normal\"\n          overflow=\"visible\"\n          textAnchor=\"start\"\n          textDecoration=\"none\"\n          transform=\"translate(-.993 -1.174) matrix(1.01489 0 0 1.01489 16.902 -2.699)\"\n          style={{\n            lineHeight: 'normal',\n            InkscapeFontSpecification: 'Sans',\n            WebkitTextIndent: '0',\n            textIndent: '0',\n            WebkitTextAlign: 'start',\n            textAlign: 'start',\n            WebkitTextDecorationLine: 'none',\n            textDecorationLine: 'none',\n            WebkitTextTransform: 'none',\n            textTransform: 'none',\n            marker: 'none',\n          }}\n          visibility=\"visible\"\n          wordSpacing=\"normal\"\n          writingMode=\"lr-tb\" />\n      </g>\n      <g\n        fill=\"#fff\"\n        fillRule=\"evenodd\"\n        strokeLinecap=\"square\"\n        strokeMiterlimit=\"10\"\n        strokeWidth=\"0.265\">\n        <path\n          d=\"M10.225 13.732a4.446 4.446 0 114.227-4.728l-1.757.113a2.685 2.685 0 10-2.553 2.856z\"\n          transform=\"translate(-.993 -1.174) translate(-.654 .634)\" />\n        <path\n          d=\"M11.136 9.009l1.397 3.42 3.226-3.42z\"\n          transform=\"translate(-.993 -1.174) translate(-.654 .634)\" />\n      </g>\n    </g>\n  </svg>\n);\n\nexport const serviceIcon = (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    version=\"1.1\"\n    width=\"24px\"\n    height=\"24px\"\n    viewBox=\"0 0 18 18\">\n    <g\n      transform=\"translate(-0.99262638,-1.174181)\"\n      id=\"layer1\">\n      <g\n        transform=\"matrix(1.0148887,0,0,1.0148887,16.902146,-2.698726)\"\n        id=\"g70\">\n        <path\n          fill=\"#757575\"\n          fillOpacity=\"1\"\n          stroke=\"none\"\n          strokeWidth=\"0\"\n          strokeMiterlimit=\"4\"\n          strokeDasharray=\"none\"\n          strokeOpacity=\"1\"\n          d=\"m -6.8492015,4.2724668 a 1.1191255,1.1099671 0 0 0 -0.4288818,0.1085303 l -5.8524037,2.7963394 a 1.1191255,1.1099671 0 0 0 -0.605524,0.7529759 l -1.443828,6.2812846 a 1.1191255,1.1099671 0 0 0 0.151943,0.851028 1.1191255,1.1099671 0 0 0 0.06362,0.08832 l 4.0508,5.036555 a 1.1191255,1.1099671 0 0 0 0.874979,0.417654 l 6.4961011,-0.0015 a 1.1191255,1.1099671 0 0 0 0.8749788,-0.416906 L 1.3818872,15.149453 A 1.1191255,1.1099671 0 0 0 1.5981986,14.210104 L 0.15212657,7.9288154 A 1.1191255,1.1099671 0 0 0 -0.45339794,7.1758396 L -6.3065496,4.3809971 A 1.1191255,1.1099671 0 0 0 -6.8492015,4.2724668 Z\"\n          id=\"path3055\" />\n        <path d=\"M -6.8523435,3.8176372 A 1.1814304,1.171762 0 0 0 -7.3044284,3.932904 l -6.1787426,2.9512758 a 1.1814304,1.171762 0 0 0 -0.639206,0.794891 l -1.523915,6.6308282 a 1.1814304,1.171762 0 0 0 0.160175,0.89893 1.1814304,1.171762 0 0 0 0.06736,0.09281 l 4.276094,5.317236 a 1.1814304,1.171762 0 0 0 0.92363,0.440858 l 6.8576188,-0.0015 a 1.1814304,1.171762 0 0 0 0.9236308,-0.44011 l 4.2745966,-5.317985 a 1.1814304,1.171762 0 0 0 0.228288,-0.990993 L 0.53894439,7.6775738 A 1.1814304,1.171762 0 0 0 -0.10026101,6.8834313 L -6.2790037,3.9321555 A 1.1814304,1.171762 0 0 0 -6.8523435,3.8176372 Z m 0.00299,0.4550789 a 1.1191255,1.1099671 0 0 1 0.5426517,0.1085303 l 5.85315169,2.7948425 A 1.1191255,1.1099671 0 0 1 0.15197811,7.9290648 L 1.598051,14.21035 a 1.1191255,1.1099671 0 0 1 -0.2163123,0.939348 l -4.0493032,5.037304 a 1.1191255,1.1099671 0 0 1 -0.8749789,0.416906 l -6.4961006,0.0015 a 1.1191255,1.1099671 0 0 1 -0.874979,-0.417652 l -4.0508,-5.036554 a 1.1191255,1.1099671 0 0 1 -0.06362,-0.08832 1.1191255,1.1099671 0 0 1 -0.151942,-0.851028 l 1.443827,-6.2812853 a 1.1191255,1.1099671 0 0 1 0.605524,-0.7529758 l 5.8524036,-2.7963395 a 1.1191255,1.1099671 0 0 1 0.4288819,-0.1085303 z\" id=\"path3054-2-9\" />\n      </g>\n      <g\n        transform=\"translate(0.09238801,0.66897746)\"\n        id=\"g3345\">\n        <path\n          id=\"path964\"\n          d=\"m 4.4949896,11.260826 2.9083311,0 0,2.041667 -2.9083311,0 z\"\n          fill=\"#ffffff\"\n          fillRule=\"evenodd\"\n          stroke=\"none\"\n          strokeWidth=\"0.26458332\"\n          strokeLinecap=\"square\"\n          strokeMiterlimit=\"10\" />\n        <path\n          id=\"path966\"\n          d=\"m 8.4637407,11.260826 2.9083303,0 0,2.041667 -2.9083303,0 z\"\n          fill=\"#ffffff\"\n          fillRule=\"evenodd\"\n          stroke=\"none\"\n          strokeWidth=\"0.26458332\"\n          strokeLinecap=\"square\"\n          strokeMiterlimit=\"10\" />\n        <path\n          id=\"path968\"\n          d=\"m 12.432491,11.260826 2.90833,0 0,2.041667 -2.90833,0 z\"\n          fill=\"#ffffff\"\n          fillRule=\"evenodd\"\n          stroke=\"none\"\n          strokeWidth=\"0.26458332\"\n          strokeLinecap=\"square\"\n          strokeMiterlimit=\"10\" />\n        <path\n          id=\"path970\"\n          d=\"m 7.6137407,5.2082921 4.6083303,0 0,2.041667 -4.6083303,0 z\"\n          fill=\"#ffffff\"\n          fillRule=\"evenodd\"\n          stroke=\"none\"\n          strokeWidth=\"0.26458332\"\n          strokeLinecap=\"square\"\n          strokeMiterlimit=\"10\" />\n        <path\n          id=\"path978\"\n          d=\"m 9.9179005,7.2499601 0,2.005449 -3.966671,0 0,2.0028859\"\n          fill=\"none\"\n          fillRule=\"evenodd\"\n          stroke=\"#ffffff\"\n          strokeWidth=\"0.52916664\"\n          strokeLinecap=\"butt\"\n          strokeLinejoin=\"round\"\n          strokeMiterlimit=\"10\"\n          strokeOpacity=\"1\" />\n        <path\n          id=\"path986\"\n          d=\"m 9.9179005,7.2499601 0,2.005449 3.9666705,0 0,2.0028859\"\n          fill=\"none\"\n          fillRule=\"evenodd\"\n          stroke=\"#ffffff\"\n          strokeWidth=\"0.52899998\"\n          strokeLinecap=\"butt\"\n          strokeLinejoin=\"round\"\n          strokeMiterlimit=\"10\"\n          strokeDasharray=\"none\"\n          strokeOpacity=\"1\" />\n        <path\n          id=\"path982\"\n          d=\"m 9.9095538,7.2512251 0,2.005449 0.0167,0 0,2.0028859\"\n          fill=\"none\"\n          fillRule=\"evenodd\"\n          stroke=\"#ffffff\"\n          strokeWidth=\"0.52916664\"\n          strokeLinecap=\"butt\"\n          strokeLinejoin=\"round\"\n          strokeMiterlimit=\"10\"\n          strokeOpacity=\"1\" />\n      </g>\n    </g>\n  </svg>\n);\n\nexport const jobIcon = (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    version=\"1.1\"\n    width=\"24px\"\n    height=\"24px\"\n    viewBox=\"0 0 18 18\">\n    <g fillOpacity=\"1\" stroke=\"none\">\n      <g strokeDasharray=\"none\" strokeMiterlimit=\"4\" strokeWidth=\"0\">\n        <path\n          fill=\"#757575\"\n          strokeOpacity=\"1\"\n          d=\"M-6.85 4.272a1.12 1.11 0 00-.428.109l-5.852 2.796a1.12 1.11 0 00-.606.753l-1.444 6.282a1.12 1.11 0 00.152.85 1.12 1.11 0 00.064.089l4.05 5.037a1.12 1.11 0 00.876.417l6.496-.001a1.12 1.11 0 00.875-.417l4.049-5.038a1.12 1.11 0 00.216-.939L.152 7.93a1.12 1.11 0 00-.605-.753L-6.307 4.38a1.12 1.11 0 00-.542-.109z\"\n          transform=\"translate(-.993 -1.174) matrix(1.01489 0 0 1.01489 16.902 -2.699)\" />\n        <path\n          fill=\"#fff\"\n          fillRule=\"nonzero\"\n          d=\"M-6.852 3.818a1.181 1.172 0 00-.452.115l-6.18 2.951a1.181 1.172 0 00-.638.795l-1.524 6.63a1.181 1.172 0 00.16.9 1.181 1.172 0 00.067.093l4.276 5.317a1.181 1.172 0 00.924.44h6.858a1.181 1.172 0 00.923-.44L1.837 15.3a1.181 1.172 0 00.228-.99L.54 7.677a1.181 1.172 0 00-.64-.795l-6.178-2.95a1.181 1.172 0 00-.573-.115zm.003.455a1.12 1.11 0 01.542.108l5.853 2.795a1.12 1.11 0 01.606.753l1.446 6.281a1.12 1.11 0 01-.216.94l-4.05 5.037a1.12 1.11 0 01-.875.417l-6.496.001a1.12 1.11 0 01-.875-.417l-4.05-5.037a1.12 1.11 0 01-.064-.088 1.12 1.11 0 01-.152-.851l1.444-6.281a1.12 1.11 0 01.605-.753l5.853-2.797a1.12 1.11 0 01.429-.108z\"\n          baselineShift=\"baseline\"\n          color=\"#000\"\n          direction=\"ltr\"\n          display=\"inline\"\n          enableBackground=\"accumulate\"\n          fontFamily=\"Sans\"\n          fontSize=\"medium\"\n          fontStretch=\"normal\"\n          fontStyle=\"normal\"\n          fontVariant=\"normal\"\n          fontWeight=\"normal\"\n          letterSpacing=\"normal\"\n          overflow=\"visible\"\n          textAnchor=\"start\"\n          textDecoration=\"none\"\n          transform=\"translate(-.993 -1.174) matrix(1.01489 0 0 1.01489 16.902 -2.699)\"\n          style={{\n            lineHeight: 'normal',\n            InkscapeFontSpecification: 'Sans',\n            WebkitTextIndent: '0',\n            textIndent: '0',\n            WebkitTextAlign: 'start',\n            textAlign: 'start',\n            WebkitTextDecorationLine: 'none',\n            textDecorationLine: 'none',\n            WebkitTextTransform: 'none',\n            textTransform: 'none',\n            marker: 'none',\n          }}\n          visibility=\"visible\"\n          wordSpacing=\"normal\"\n          writingMode=\"lr-tb\" />\n      </g>\n      <g\n        fill=\"#fff\"\n        fillRule=\"nonzero\"\n        strokeDasharray=\"1.41800002, 1.41800002\"\n        strokeDashoffset=\"23.045\"\n        strokeLinecap=\"butt\"\n        strokeLinejoin=\"round\"\n        strokeMiterlimit=\"10\"\n        strokeOpacity=\"1\"\n        strokeWidth=\"0.709\"\n        transform=\"translate(-.993 -1.174) translate(.304 1.092)\">\n        <path d=\"M5.453 10.878H7.656000000000001V13.021H5.453z\" opacity=\"1\" />\n        <path d=\"M8.605 10.878H10.808V13.021H8.605z\" opacity=\"1\" />\n        <path\n          d=\"M11.757 10.878H13.959999999999999V13.021H11.757z\"\n          opacity=\"1\" />\n        <path\n          d=\"M11.757 7.728H13.959999999999999V9.870999999999999H11.757z\"\n          opacity=\"1\" />\n        <path d=\"M8.615 7.761H10.818V9.904H8.615z\" opacity=\"1\" />\n        <path d=\"M5.473 7.761H7.676V9.904H5.473z\" opacity=\"1\" />\n        <path\n          d=\"M11.757 4.644H13.959999999999999V6.787H11.757z\"\n          opacity=\"1\" />\n      </g>\n    </g>\n  </svg>\n);\n\nexport const podIcon = (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    version=\"1.1\"\n    width=\"24px\"\n    height=\"24px\"\n    viewBox=\"0 0 18 18\">\n    <g stroke=\"none\">\n      <g\n        fillOpacity=\"1\"\n        strokeDasharray=\"none\"\n        strokeMiterlimit=\"4\"\n        strokeWidth=\"0\">\n        <path\n          fill=\"#757575\"\n          strokeOpacity=\"1\"\n          d=\"M-6.85 4.272a1.12 1.11 0 00-.428.109l-5.852 2.796a1.12 1.11 0 00-.606.753l-1.444 6.282a1.12 1.11 0 00.152.85 1.12 1.11 0 00.064.089l4.05 5.037a1.12 1.11 0 00.876.417l6.496-.001a1.12 1.11 0 00.875-.417l4.049-5.038a1.12 1.11 0 00.216-.939L.152 7.93a1.12 1.11 0 00-.605-.753L-6.307 4.38a1.12 1.11 0 00-.542-.109z\"\n          transform=\"translate(-.993 -1.174) matrix(1.01489 0 0 1.01489 16.902 -2.699)\" />\n        <path\n          fill=\"#fff\"\n          fillRule=\"nonzero\"\n          d=\"M-6.852 3.818a1.181 1.172 0 00-.452.115l-6.18 2.951a1.181 1.172 0 00-.638.795l-1.524 6.63a1.181 1.172 0 00.16.9 1.181 1.172 0 00.067.093l4.276 5.317a1.181 1.172 0 00.924.44h6.858a1.181 1.172 0 00.923-.44L1.837 15.3a1.181 1.172 0 00.228-.99L.54 7.677a1.181 1.172 0 00-.64-.795l-6.178-2.95a1.181 1.172 0 00-.573-.115zm.003.455a1.12 1.11 0 01.542.108l5.853 2.795a1.12 1.11 0 01.606.753l1.446 6.281a1.12 1.11 0 01-.216.94l-4.05 5.037a1.12 1.11 0 01-.875.417l-6.496.001a1.12 1.11 0 01-.875-.417l-4.05-5.037a1.12 1.11 0 01-.064-.088 1.12 1.11 0 01-.152-.851l1.444-6.281a1.12 1.11 0 01.605-.753l5.853-2.797a1.12 1.11 0 01.429-.108z\"\n          baselineShift=\"baseline\"\n          color=\"#000\"\n          direction=\"ltr\"\n          display=\"inline\"\n          enableBackground=\"accumulate\"\n          fontFamily=\"Sans\"\n          fontSize=\"medium\"\n          fontStretch=\"normal\"\n          fontStyle=\"normal\"\n          fontVariant=\"normal\"\n          fontWeight=\"normal\"\n          letterSpacing=\"normal\"\n          overflow=\"visible\"\n          textAnchor=\"start\"\n          textDecoration=\"none\"\n          transform=\"translate(-.993 -1.174) matrix(1.01489 0 0 1.01489 16.902 -2.699)\"\n          style={{\n            lineHeight: 'normal',\n            InkscapeFontSpecification: 'Sans',\n            WebkitTextIndent: '0',\n            textIndent: '0',\n            WebkitTextAlign: 'start',\n            textAlign: 'start',\n            WebkitTextDecorationLine: 'none',\n            textDecorationLine: 'none',\n            WebkitTextTransform: 'none',\n            textTransform: 'none',\n            marker: 'none',\n          }}\n          visibility=\"visible\"\n          wordSpacing=\"normal\"\n          writingMode=\"lr-tb\" />\n      </g>\n      <g\n        fill=\"#fff\"\n        fillRule=\"evenodd\"\n        strokeLinecap=\"square\"\n        strokeMiterlimit=\"10\"\n        strokeWidth=\"0.265\">\n        <path\n          d=\"M6.262 7.036l3.62-1.05 3.621 1.05-3.62 1.05zM6.262 7.438v3.853l3.373 1.869.017-4.713zM13.503 7.438v3.853L10.13 13.16l-.017-4.713z\"\n          transform=\"translate(-.993 -1.174) translate(.128 .351)\" />\n      </g>\n    </g>\n  </svg>\n);\n\nexport const replicaSetIcon = (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    version=\"1.1\"\n    width=\"24px\"\n    height=\"24px\"\n    viewBox=\"0 0 18 18\">\n    <g\n      fillOpacity=\"1\"\n      stroke=\"none\"\n      strokeDasharray=\"none\"\n      strokeMiterlimit=\"4\"\n      strokeWidth=\"0\">\n      <path\n        fill=\"#757575\"\n        strokeOpacity=\"1\"\n        d=\"M-6.85 4.272a1.12 1.11 0 00-.428.109l-5.852 2.796a1.12 1.11 0 00-.606.753l-1.444 6.282a1.12 1.11 0 00.152.85 1.12 1.11 0 00.064.089l4.05 5.037a1.12 1.11 0 00.876.417l6.496-.001a1.12 1.11 0 00.875-.417l4.049-5.038a1.12 1.11 0 00.216-.939L.152 7.93a1.12 1.11 0 00-.605-.753L-6.307 4.38a1.12 1.11 0 00-.542-.109z\"\n        transform=\"translate(-.993 -1.174) matrix(1.01489 0 0 1.01489 16.902 -2.699)\" />\n      <path\n        fill=\"#fff\"\n        fillRule=\"nonzero\"\n        d=\"M-6.852 3.818a1.181 1.172 0 00-.452.115l-6.18 2.951a1.181 1.172 0 00-.638.795l-1.524 6.63a1.181 1.172 0 00.16.9 1.181 1.172 0 00.067.093l4.276 5.317a1.181 1.172 0 00.924.44h6.858a1.181 1.172 0 00.923-.44L1.837 15.3a1.181 1.172 0 00.228-.99L.54 7.677a1.181 1.172 0 00-.64-.795l-6.178-2.95a1.181 1.172 0 00-.573-.115zm.003.455a1.12 1.11 0 01.542.108l5.853 2.795a1.12 1.11 0 01.606.753l1.446 6.281a1.12 1.11 0 01-.216.94l-4.05 5.037a1.12 1.11 0 01-.875.417l-6.496.001a1.12 1.11 0 01-.875-.417l-4.05-5.037a1.12 1.11 0 01-.064-.088 1.12 1.11 0 01-.152-.851l1.444-6.281a1.12 1.11 0 01.605-.753l5.853-2.797a1.12 1.11 0 01.429-.108z\"\n        baselineShift=\"baseline\"\n        color=\"#000\"\n        direction=\"ltr\"\n        display=\"inline\"\n        enableBackground=\"accumulate\"\n        fontFamily=\"Sans\"\n        fontSize=\"medium\"\n        fontStretch=\"normal\"\n        fontStyle=\"normal\"\n        fontVariant=\"normal\"\n        fontWeight=\"normal\"\n        letterSpacing=\"normal\"\n        overflow=\"visible\"\n        textAnchor=\"start\"\n        textDecoration=\"none\"\n        transform=\"translate(-.993 -1.174) matrix(1.01489 0 0 1.01489 16.902 -2.699)\"\n        style={{\n          lineHeight: 'normal',\n          InkscapeFontSpecification: 'Sans',\n          WebkitTextIndent: '0',\n          textIndent: '0',\n          WebkitTextAlign: 'start',\n          textAlign: 'start',\n          WebkitTextDecorationLine: 'none',\n          textDecorationLine: 'none',\n          WebkitTextTransform: 'none',\n          textTransform: 'none',\n          marker: 'none',\n        }}\n        visibility=\"visible\"\n        wordSpacing=\"normal\"\n        writingMode=\"lr-tb\" />\n    </g>\n    <g fillRule=\"evenodd\" strokeMiterlimit=\"10\">\n      <path\n        fill=\"#757575\"\n        fillOpacity=\"1\"\n        stroke=\"#fff\"\n        strokeDasharray=\"1.58700001, 1.58700001\"\n        strokeDashoffset=\"3.666\"\n        strokeLinecap=\"square\"\n        strokeLinejoin=\"round\"\n        strokeOpacity=\"1\"\n        strokeWidth=\"0.529\"\n        d=\"M8.124 5.552h6.525v4.584H8.124z\"\n        transform=\"translate(-.993 -1.174) translate(.163 .669)\" />\n      <path\n        fill=\"#757575\"\n        fillOpacity=\"1\"\n        stroke=\"#fff\"\n        strokeDasharray=\"1.58743756, 1.58743756\"\n        strokeDashoffset=\"3.879\"\n        strokeLinecap=\"square\"\n        strokeLinejoin=\"round\"\n        strokeOpacity=\"1\"\n        strokeWidth=\"0.529\"\n        d=\"M6.585 6.964h6.525v4.583H6.585z\"\n        transform=\"translate(-.993 -1.174) translate(.163 .669)\" />\n      <path\n        fill=\"#fff\"\n        stroke=\"none\"\n        strokeLinecap=\"square\"\n        strokeWidth=\"0.265\"\n        d=\"M5.046 8.375h6.525v4.583H5.046z\"\n        transform=\"translate(-.993 -1.174) translate(.163 .669)\" />\n      <path\n        fill=\"none\"\n        stroke=\"#fff\"\n        strokeLinecap=\"butt\"\n        strokeLinejoin=\"round\"\n        strokeOpacity=\"1\"\n        strokeWidth=\"0.529\"\n        d=\"M5.046 8.375h6.525v4.583H5.046z\"\n        transform=\"translate(-.993 -1.174) translate(.163 .669)\" />\n    </g>\n  </svg>\n);\n\nexport const statefulSetIcon = (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    version=\"1.1\"\n    width=\"24px\"\n    height=\"24px\"\n    viewBox=\"0 0 18 18\">\n    <g\n      fillOpacity=\"1\"\n      stroke=\"none\"\n      strokeDasharray=\"none\"\n      strokeMiterlimit=\"4\"\n      strokeWidth=\"0\">\n      <path\n        fill=\"#757575\"\n        strokeOpacity=\"1\"\n        d=\"M-6.85 4.272a1.12 1.11 0 00-.428.109l-5.852 2.796a1.12 1.11 0 00-.606.753l-1.444 6.282a1.12 1.11 0 00.152.85 1.12 1.11 0 00.064.089l4.05 5.037a1.12 1.11 0 00.876.417l6.496-.001a1.12 1.11 0 00.875-.417l4.049-5.038a1.12 1.11 0 00.216-.939L.152 7.93a1.12 1.11 0 00-.605-.753L-6.307 4.38a1.12 1.11 0 00-.542-.109z\"\n        transform=\"translate(-.993 -1.174) matrix(1.01489 0 0 1.01489 16.937 -2.699)\" />\n      <path\n        fill=\"#fff\"\n        fillRule=\"nonzero\"\n        d=\"M-6.852 3.818a1.181 1.172 0 00-.452.115l-6.18 2.951a1.181 1.172 0 00-.638.795l-1.524 6.63a1.181 1.172 0 00.16.9 1.181 1.172 0 00.067.093l4.276 5.317a1.181 1.172 0 00.924.44h6.858a1.181 1.172 0 00.923-.44L1.837 15.3a1.181 1.172 0 00.228-.99L.54 7.677a1.181 1.172 0 00-.64-.795l-6.178-2.95a1.181 1.172 0 00-.573-.115zm.003.455a1.12 1.11 0 01.542.108l5.853 2.795a1.12 1.11 0 01.606.753l1.446 6.281a1.12 1.11 0 01-.216.94l-4.05 5.037a1.12 1.11 0 01-.875.417l-6.496.001a1.12 1.11 0 01-.875-.417l-4.05-5.037a1.12 1.11 0 01-.064-.088 1.12 1.11 0 01-.152-.851l1.444-6.281a1.12 1.11 0 01.605-.753l5.853-2.797a1.12 1.11 0 01.429-.108z\"\n        baselineShift=\"baseline\"\n        color=\"#000\"\n        direction=\"ltr\"\n        display=\"inline\"\n        enableBackground=\"accumulate\"\n        fontFamily=\"Sans\"\n        fontSize=\"medium\"\n        fontStretch=\"normal\"\n        fontStyle=\"normal\"\n        fontVariant=\"normal\"\n        fontWeight=\"normal\"\n        letterSpacing=\"normal\"\n        overflow=\"visible\"\n        textAnchor=\"start\"\n        textDecoration=\"none\"\n        transform=\"translate(-.993 -1.174) matrix(1.01489 0 0 1.01489 16.937 -2.699)\"\n        style={{\n          lineHeight: 'normal',\n          InkscapeFontSpecification: 'Sans',\n          WebkitTextIndent: '0',\n          textIndent: '0',\n          WebkitTextAlign: 'start',\n          textAlign: 'start',\n          WebkitTextDecorationLine: 'none',\n          textDecorationLine: 'none',\n          WebkitTextTransform: 'none',\n          textTransform: 'none',\n          marker: 'none',\n        }}\n        visibility=\"visible\"\n        wordSpacing=\"normal\"\n        writingMode=\"lr-tb\" />\n    </g>\n    <g fillRule=\"evenodd\" strokeMiterlimit=\"10\">\n      <path\n        fill=\"none\"\n        stroke=\"#fff\"\n        strokeDasharray=\"1.58743761, 1.58743761\"\n        strokeDashoffset=\"3.667\"\n        strokeLinecap=\"square\"\n        strokeLinejoin=\"round\"\n        strokeOpacity=\"1\"\n        strokeWidth=\"0.529\"\n        d=\"M8.053 5.13h6.525v4.582H8.053z\"\n        transform=\"translate(-.993 -1.174) translate(.269 1.092)\" />\n      <path\n        fill=\"#757575\"\n        fillOpacity=\"1\"\n        stroke=\"#fff\"\n        strokeDasharray=\"1.58743761, 1.58743761\"\n        strokeDashoffset=\"3.879\"\n        strokeLinecap=\"square\"\n        strokeLinejoin=\"round\"\n        strokeOpacity=\"1\"\n        strokeWidth=\"0.529\"\n        d=\"M6.514 6.54h6.525v4.584H6.514z\"\n        transform=\"translate(-.993 -1.174) translate(.269 1.092)\" />\n      <path\n        fill=\"#fff\"\n        stroke=\"none\"\n        strokeLinecap=\"square\"\n        strokeWidth=\"0.265\"\n        d=\"M4.976 7.952H11.5v4.583H4.976z\"\n        transform=\"translate(-.993 -1.174) translate(.269 1.092)\" />\n      <path\n        fill=\"none\"\n        stroke=\"#fff\"\n        strokeLinecap=\"butt\"\n        strokeLinejoin=\"round\"\n        strokeOpacity=\"1\"\n        strokeWidth=\"0.529\"\n        d=\"M4.976 7.952H11.5v4.583H4.976z\"\n        transform=\"translate(-.993 -1.174) translate(.269 1.092)\" />\n      <path\n        fill=\"#fff\"\n        fillOpacity=\"1\"\n        stroke=\"none\"\n        strokeLinecap=\"square\"\n        strokeWidth=\"0.265\"\n        d=\"M5.509 9.226c0-.43 1.222-.777 2.729-.777 1.507 0 2.73.348 2.73.777 0 .43-1.223.778-2.73.778s-2.73-.349-2.73-.778z\"\n        transform=\"translate(-.993 -1.174) translate(.269 1.092)\" />\n      <path\n        fill=\"#000\"\n        fillOpacity=\"0\"\n        stroke=\"none\"\n        strokeLinecap=\"square\"\n        strokeWidth=\"0.265\"\n        d=\"M10.967 9.226c0 .43-1.222.778-2.73.778-1.506 0-2.728-.349-2.728-.778 0-.43 1.222-.777 2.729-.777 1.507 0 2.73.348 2.73.777v2.103c0 .43-1.223.778-2.73.778s-2.73-.348-2.73-.778V9.226\"\n        transform=\"translate(-.993 -1.174) translate(.269 1.092)\" />\n      <path\n        fill=\"none\"\n        stroke=\"#757575\"\n        strokeLinecap=\"butt\"\n        strokeLinejoin=\"round\"\n        strokeOpacity=\"1\"\n        strokeWidth=\"0.529\"\n        d=\"M10.967 9.226h0c0 .43-1.222.778-2.73.778-1.506 0-2.728-.349-2.728-.778h0c0-.43 1.222-.777 2.729-.777 1.507 0 2.73.348 2.73.777v2.103c0 .43-1.223.778-2.73.778s-2.73-.348-2.73-.778V9.226\"\n        transform=\"translate(-.993 -1.174) translate(.269 1.092)\" />\n    </g>\n  </svg>\n);\n\nexport const cronJobIcon = (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width=\"24px\"\n    height=\"24px\"\n    version=\"1.1\"\n    viewBox=\"0 0 18.035 17.5\">\n    <g fillOpacity=\"1\">\n      <g\n        stroke=\"none\"\n        strokeDasharray=\"none\"\n        strokeMiterlimit=\"4\"\n        strokeWidth=\"0\">\n        <path\n          fill=\"#757575\"\n          strokeOpacity=\"1\"\n          d=\"M-6.85 4.272a1.12 1.11 0 00-.428.109l-5.852 2.796a1.12 1.11 0 00-.606.753l-1.444 6.282a1.12 1.11 0 00.152.85 1.12 1.11 0 00.064.089l4.05 5.037a1.12 1.11 0 00.876.417l6.496-.001a1.12 1.11 0 00.875-.417l4.049-5.038a1.12 1.11 0 00.216-.939L.152 7.93a1.12 1.11 0 00-.605-.753L-6.307 4.38a1.12 1.11 0 00-.542-.109z\"\n          transform=\"translate(-.993 -1.174) matrix(1.01489 0 0 1.01489 16.902 -2.699)\" />\n        <path\n          fill=\"#fff\"\n          fillRule=\"nonzero\"\n          d=\"M-6.852 3.818a1.181 1.172 0 00-.452.115l-6.18 2.951a1.181 1.172 0 00-.638.795l-1.524 6.63a1.181 1.172 0 00.16.9 1.181 1.172 0 00.067.093l4.276 5.317a1.181 1.172 0 00.924.44h6.858a1.181 1.172 0 00.923-.44L1.837 15.3a1.181 1.172 0 00.228-.99L.54 7.677a1.181 1.172 0 00-.64-.795l-6.178-2.95a1.181 1.172 0 00-.573-.115zm.003.455a1.12 1.11 0 01.542.108l5.853 2.795a1.12 1.11 0 01.606.753l1.446 6.281a1.12 1.11 0 01-.216.94l-4.05 5.037a1.12 1.11 0 01-.875.417l-6.496.001a1.12 1.11 0 01-.875-.417l-4.05-5.037a1.12 1.11 0 01-.064-.088 1.12 1.11 0 01-.152-.851l1.444-6.281a1.12 1.11 0 01.605-.753l5.853-2.797a1.12 1.11 0 01.429-.108z\"\n          baselineShift=\"baseline\"\n          color=\"#000\"\n          direction=\"ltr\"\n          display=\"inline\"\n          enableBackground=\"accumulate\"\n          fontFamily=\"Sans\"\n          fontSize=\"medium\"\n          fontStretch=\"normal\"\n          fontStyle=\"normal\"\n          fontVariant=\"normal\"\n          fontWeight=\"normal\"\n          letterSpacing=\"normal\"\n          overflow=\"visible\"\n          textAnchor=\"start\"\n          textDecoration=\"none\"\n          transform=\"translate(-.993 -1.174) matrix(1.01489 0 0 1.01489 16.902 -2.699)\"\n          style={{\n            lineHeight: 'normal',\n            InkscapeFontSpecification: 'Sans',\n            WebkitTextIndent: '0',\n            textIndent: '0',\n            WebkitTextAlign: 'start',\n            textAlign: 'start',\n            WebkitTextDecorationLine: 'none',\n            textDecorationLine: 'none',\n            WebkitTextTransform: 'none',\n            textTransform: 'none',\n            marker: 'none',\n          }}\n          visibility=\"visible\"\n          wordSpacing=\"normal\"\n          writingMode=\"lr-tb\" />\n      </g>\n      <g fill=\"#fff\">\n        <path\n          fillRule=\"nonzero\"\n          stroke=\"none\"\n          strokeDasharray=\"1.418, 1.418\"\n          strokeDashoffset=\"23.045\"\n          strokeLinecap=\"butt\"\n          strokeLinejoin=\"round\"\n          strokeMiterlimit=\"10\"\n          strokeOpacity=\"1\"\n          strokeWidth=\"0.709\"\n          d=\"M11.673 3.96v2.143h2.202V3.96zm0 3.084v.546c.258-.06.526-.097.803-.097.497 0 .97.106 1.4.295v-.744zm-6.284.033V9.22h2.203V7.077zm3.142 0V9.22h.928c.31-.52.75-.955 1.275-1.258v-.885zm-3.163 3.117v2.143h2.203v-2.143zm3.152 0v2.143h.707c-.17-.411-.265-.86-.265-1.33 0-.28.037-.551.1-.813z\"\n          opacity=\"1\"\n          transform=\"translate(-.993 -1.174) translate(-.578 .775)\" />\n        <path\n          strokeWidth=\"0.32\"\n          d=\"M12.608 7.94a3.21 3.21 0 00-3.2 3.2c0 1.76 1.44 3.2 3.2 3.2 1.76 0 3.2-1.44 3.2-3.2 0-1.76-1.44-3.2-3.2-3.2zm1.344 4.543l-1.664-1.024V9.54h.48v1.664l1.44.864z\"\n          clipPath=\"url(#b)\"\n          transform=\"translate(-.993 -1.174) translate(-.578 .775)\" />\n      </g>\n    </g>\n  </svg>\n);\n"
  },
  {
    "path": "web/app/js/components/util/TapUtils.jsx",
    "content": "import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport Grid from '@material-ui/core/Grid';\nimport OpenInNewIcon from '@material-ui/icons/OpenInNew';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport { Trans } from '@lingui/macro';\nimport _each from 'lodash/each';\nimport _get from 'lodash/get';\nimport _has from 'lodash/has';\nimport _isEmpty from 'lodash/isEmpty';\nimport _isNil from 'lodash/isNil';\nimport _map from 'lodash/map';\nimport _merge from 'lodash/merge';\nimport _size from 'lodash/size';\nimport _take from 'lodash/take';\nimport { faLongArrowAltRight } from '@fortawesome/free-solid-svg-icons/faLongArrowAltRight';\nimport TapLink from '../TapLink.jsx';\nimport Popover from '../Popover.jsx';\nimport BaseTable from '../BaseTable.jsx';\nimport { podOwnerLookup, toShortResourceName } from './Utils.js';\n\nexport const httpMethods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'];\n\nexport const defaultMaxRps = '100.0';\nexport const setMaxRps = query => {\n  if (!_isEmpty(query.maxRps)) {\n    query.maxRps = parseFloat(query.maxRps);\n  } else {\n    query.maxRps = 0; // golang unset value for maxRps\n  }\n};\n\n// resources you can tap/top to tap all pods in the resource\nexport const tapResourceTypes = [\n  'deployment',\n  'daemonset',\n  'pod',\n  'replicationcontroller',\n  'statefulset',\n  'job',\n  'replicaset',\n  'cronjob',\n];\n\n// use a generator to get this object, to prevent it from being overwritten\nexport const emptyTapQuery = () => ({\n  resource: '',\n  namespace: '',\n  toResource: '',\n  toNamespace: '',\n  method: '',\n  path: '',\n  scheme: '',\n  authority: '',\n  maxRps: '',\n});\n\nexport const tapQueryProps = {\n  resource: PropTypes.string,\n  namespace: PropTypes.string,\n  toResource: PropTypes.string,\n  toNamespace: PropTypes.string,\n  method: PropTypes.string,\n  path: PropTypes.string,\n  scheme: PropTypes.string,\n  authority: PropTypes.string,\n  maxRps: PropTypes.string,\n  extract: PropTypes.bool,\n};\n\nexport const tapQueryPropType = PropTypes.shape(tapQueryProps);\n\n// from https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent\nexport const wsCloseCodes = {\n  1000: 'Normal Closure',\n  1001: 'Going Away',\n  1002: 'Protocol Error',\n  1003: 'Unsupported Data',\n  1004: 'Reserved',\n  1005: 'No Status Recvd',\n  1006: 'Abnormal Closure',\n  1007: 'Invalid frame payload data',\n  1008: 'Policy Violation',\n  1009: 'Message too big',\n  1010: 'Missing Extension',\n  1011: 'Internal Error',\n  1012: 'Service Restart',\n  1013: 'Try Again Later',\n  1014: 'Bad Gateway',\n  1015: 'TLS Handshake',\n};\n\nexport const WS_NORMAL_CLOSURE = 1000;\nexport const WS_ABNORMAL_CLOSURE = 1006;\nexport const WS_POLICY_VIOLATION = 1008;\n\n/*\n  Use tap data to figure out a resource's unmeshed upstreams/downstreams\n*/\nexport const processNeighborData = (source, labels, resourceAgg, resourceType) => {\n  if (_isEmpty(labels)) {\n    return resourceAgg;\n  }\n\n  let neighbor = {};\n  if (_has(labels, resourceType)) {\n    neighbor = {\n      type: resourceType,\n      name: labels[resourceType],\n      namespace: labels.namespace,\n    };\n  } else if (_has(labels, 'pod')) {\n    neighbor = {\n      type: 'pod',\n      name: labels.pod,\n      namespace: labels.namespace,\n    };\n  } else if (_has(labels, 'node')) {\n    neighbor = {\n      type: 'node',\n      name: labels.node,\n    };\n  } else {\n    neighbor = {\n      type: 'ip',\n      name: source.str,\n    };\n  }\n\n  // keep track of pods under this resource to display the number of unmeshed source pods\n  neighbor.pods = {};\n  if (labels.pod) {\n    neighbor.pods[labels.pod] = true;\n  }\n\n  const key = `${neighbor.type}/${neighbor.name}`;\n  if (_has(labels, 'control_plane_ns')) {\n    delete resourceAgg[key];\n  } else {\n    if (_has(resourceAgg, key)) {\n      _merge(neighbor.pods, resourceAgg[key].pods);\n    }\n    resourceAgg[key] = neighbor;\n  }\n\n  return resourceAgg;\n};\n\n/*\n  Use this key to associate the corresponding response with the request\n  so that we can have one single event with reqInit, rspInit and rspEnd\n  current key: (src, dst, stream)\n*/\nconst tapEventKey = (d, eventType) => {\n  return `${d.source.str},${d.destination.str},${_get(d, ['http', eventType, 'id', 'stream'])}`;\n};\n\n/*\n  produce octets given an ip address\n*/\nconst decodeIPToOctets = ip => {\n  const ip_ = parseInt(ip, 10);\n\n  return [\n    (ip_ >> 24) & 255,\n    (ip_ >> 16) & 255,\n    (ip_ >> 8) & 255,\n    ip_ & 255,\n  ];\n};\n\n/*\n  converts an address to an ipv4 formatted host\n*/\nconst publicAddressToString = ipv4 => {\n  const octets = decodeIPToOctets(ipv4);\n  return octets.join('.');\n};\n\n/*\n  display more human-readable information about source/destination\n*/\nconst resourceShortLink = (resourceType, labels, ResourceLink) => (\n  <ResourceLink\n    key={`${labels[resourceType]}-${labels.namespace}`}\n    resource={{ type: resourceType, name: labels[resourceType], namespace: labels.namespace }}\n    linkText={`${toShortResourceName(resourceType)}/${labels[resourceType]}`} />\n);\n\nexport const extractPodOwner = labels => {\n  let podOwner = '';\n  _each(labels, (labelVal, labelName) => {\n    if (_has(podOwnerLookup, labelName)) {\n      podOwner = `${labelName}/${labelVal}`;\n    }\n  });\n  return podOwner;\n};\n\nexport const processTapEvent = jsonString => {\n  const d = JSON.parse(jsonString);\n\n  d.source.str = publicAddressToString(_get(d, 'source.ip.ipv4'));\n  d.source.pod = _get(d, 'sourceMeta.labels.pod', null);\n  d.source.owner = extractPodOwner(d.sourceMeta.labels);\n  d.source.namespace = _get(d, 'sourceMeta.labels.namespace', null);\n\n  d.destination.str = publicAddressToString(_get(d, 'destination.ip.ipv4'));\n  d.destination.pod = _get(d, 'destinationMeta.labels.pod', null);\n  d.destination.owner = extractPodOwner(d.destinationMeta.labels);\n  d.destination.namespace = _get(d, 'destinationMeta.labels.namespace', null);\n\n  if (_isNil(d.http)) {\n    this.setState({ error: 'Undefined request type' });\n  } else {\n    if (!_isNil(d.http.requestInit)) {\n      d.eventType = 'requestInit';\n    } else if (!_isNil(d.http.responseInit)) {\n      d.eventType = 'responseInit';\n    } else if (!_isNil(d.http.responseEnd)) {\n      d.eventType = 'responseEnd';\n    }\n    d.id = tapEventKey(d, d.eventType);\n  }\n\n  return d;\n};\n\nconst displayLimit = 3; // how many upstreams/downstreams to display in the popover table\nconst popoverSrcDstColumns = [\n  { title: <Trans>columnTitleSource</Trans>, dataIndex: 'source' },\n  { title: '', key: 'arrow', render: () => <FontAwesomeIcon icon={faLongArrowAltRight} /> },\n  { title: <Trans>columnTitleDestination</Trans>, dataIndex: 'destination' },\n];\n\nconst getPodOwner = (labels, ResourceLink) => {\n  const podOwner = extractPodOwner(labels);\n  if (!podOwner) {\n    return null;\n  } else {\n    const [labelName] = podOwner.split('/');\n    return (\n      <div className=\"popover-td\">\n        { resourceShortLink(labelName, labels, ResourceLink) }\n      </div>\n    );\n  }\n};\n\nconst getPodList = (endpoint, display, labels, ResourceLink) => {\n  let podList = '---';\n  if (!display) {\n    if (endpoint.pod) {\n      podList = resourceShortLink('pod', { pod: endpoint.pod, namespace: labels.namespace }, ResourceLink);\n    }\n  } else if (!_isEmpty(display.pods)) {\n    podList = (\n      <React.Fragment>\n        {\n          _map(display.pods, (namespace, pod, i) => {\n            if (i > displayLimit) {\n              return null;\n            } else {\n              return <div key={pod}>{resourceShortLink('pod', { pod, namespace }, ResourceLink)}</div>;\n            }\n          })\n        }\n        { (_size(display.pods) > displayLimit ? '...' : '') }\n      </React.Fragment>\n    );\n  }\n  return <div className=\"popover-td\">{podList}</div>;\n};\n\n// display consists of a list of ips and pods for aggregated displays (Top)\nconst getIpList = (endpoint, display) => {\n  let ipList = endpoint.str;\n  if (display) {\n    ipList = _take(Object.keys(display.ips), displayLimit).join(', ') +\n      (_size(display.ips) > displayLimit ? '...' : '');\n  }\n  return <div className=\"popover-td\">{ipList}</div>;\n};\n\nconst popoverResourceTable = (d, ResourceLink) => { // eslint-disable-line no-unused-vars\n  const tableData = [\n    {\n      source: getPodOwner(d.sourceLabels, ResourceLink),\n      destination: getPodOwner(d.destinationLabels, ResourceLink),\n      key: 'podOwner',\n    },\n    {\n      source: getPodList(d.source, d.sourceDisplay, d.sourceLabels, ResourceLink),\n      destination: getPodList(d.destination, d.destinationDisplay, d.destinationLabels, ResourceLink),\n      key: 'podList',\n    },\n    {\n      source: getIpList(d.source, d.sourceDisplay),\n      destination: getIpList(d.destination, d.destinationDisplay),\n      key: 'ipList',\n    },\n  ];\n\n  return (\n    <BaseTable\n      tableColumns={popoverSrcDstColumns}\n      tableRows={tableData}\n      tableClassName=\"metric-table\" />\n  );\n};\n\nexport const directionColumn = d => (\n  <Tooltip title={d === 'INBOUND' ? <Trans>tooltipInbound</Trans> : <Trans>tooltipOutbound</Trans>} placement=\"right\">\n    <span>{d === 'INBOUND' ? <Trans>columnTitleFrom</Trans> : <Trans>columnTitleTo</Trans>}</span>\n  </Tooltip>\n);\n\nexport const extractDisplayName = d => {\n  let display = {};\n  let labels = {};\n\n  if (d.direction === 'INBOUND') {\n    display = d.source;\n    labels = d.sourceLabels;\n  } else {\n    display = d.destination;\n    labels = d.destinationLabels;\n  }\n  return [labels, display];\n};\n\nexport const srcDstColumn = (d, resourceType, ResourceLink) => {\n  const [labels, display] = extractDisplayName(d);\n\n  const link = (\n    !_isEmpty(labels[resourceType]) ?\n      resourceShortLink(resourceType, labels, ResourceLink) :\n      display.str\n  );\n\n  const linkFn = e => {\n    e.preventDefault();\n  };\n\n  const baseContent = (\n    <OpenInNewIcon fontSize=\"small\" style={{ color: 'var(--linkblue)' }} onClick={linkFn} />\n  );\n\n  return (\n    <Grid\n      container\n      direction=\"row\"\n      alignItems=\"center\"\n      spacing={1}>\n      <Grid item>\n        {link}\n      </Grid>\n      <Grid item>\n        <Popover\n          popoverContent={(popoverResourceTable(d, ResourceLink))}\n          baseContent={baseContent} />\n      </Grid>\n    </Grid>\n  );\n};\n\nexport const tapLink = (d, resourceType, PrefixedLink) => {\n  let disabled = false;\n  const namespace = d.sourceLabels.namespace;\n  let resource = '';\n\n  if (!d.meshed) {\n    disabled = true;\n  } else if (_has(d.sourceLabels, resourceType)) {\n    resource = `${resourceType}/${d.sourceLabels[resourceType]}`;\n  } else if (_has(d.sourceLabels, 'pod')) {\n    resource = `pod/${d.sourceLabels.pod}`;\n  } else {\n    // can't tap a resource by IP from the web UI\n    disabled = true;\n  }\n\n  let toNamespace = '';\n  let toResource = '';\n\n  if (_has(d.destinationLabels, resourceType)) {\n    toNamespace = d.destinationLabels.namespace;\n    toResource = `${resourceType}/${d.destinationLabels[resourceType]}`;\n  } else if (_has(d.destinationLabels, 'pod')) {\n    toNamespace = d.destinationLabels.namespace;\n    toResource = `${resourceType}/${d.destinationLabels.pod}`;\n  }\n\n  return (\n    <TapLink\n      namespace={namespace}\n      resource={resource}\n      toNamespace={toNamespace}\n      toResource={toResource}\n      path={d.path}\n      PrefixedLink={PrefixedLink}\n      disabled={disabled} />\n  );\n};\n"
  },
  {
    "path": "web/app/js/components/util/Utils.js",
    "content": "import _has from 'lodash/has';\nimport _isNil from 'lodash/isNil';\nimport _lowerCase from 'lodash/lowerCase';\nimport _startCase from 'lodash/startCase';\nimport { format as d3Format } from 'd3-format';\n\n/*\n* Display grid constants\n*/\nexport const baseWidth = 8; // design base width of 8px\nexport const rowGutter = 3 * baseWidth;\n\n/*\n* Number formatters\n*/\nconst successRateFormatter = d3Format('.2%');\nconst commaFormatter = d3Format(',');\nconst secondsFormatter = d3Format(',.3s');\n\nexport const formatWithComma = m => {\n  if (_isNil(m)) {\n    return '---';\n  } else {\n    return commaFormatter(m);\n  }\n};\n\nconst niceLatency = l => commaFormatter(Math.round(l));\n\nexport const formatLatencySec = latency => {\n  const s = parseFloat(latency);\n  if (_isNil(s)) {\n    return '---';\n  } else if (s === parseFloat(0.0)) {\n    return '0 s';\n  } else if (s < 0.001) {\n    return `${niceLatency(s * 1000 * 1000)} µs`;\n  } else if (s < 1.0) {\n    return `${niceLatency(s * 1000)} ms`;\n  } else {\n    return `${secondsFormatter(s)} s`;\n  }\n};\n\nexport const formatLatencyMs = m => {\n  if (_isNil(m)) {\n    return '---';\n  } else {\n    return `${formatLatencySec(m / 1000)}`;\n  }\n};\n\n/*\n* Add commas to a number (converting it to a string in the process)\n*/\nexport function addCommas(nStr) {\n  let nStr_ = nStr;\n  nStr_ += '';\n  const x = nStr_.split('.');\n  let x1 = x[0];\n  const x2 = x.length > 1 ? `.${x[1]}` : '';\n  const rgx = /(\\d+)(\\d{3})/;\n  while (rgx.test(x1)) {\n    x1 = x1.replace(rgx, '$1,$2');\n  }\n  return x1 + x2;\n}\n\n/*\n* Round a number to a given number of decimals\n*/\nexport const roundNumber = (num, dec) => {\n  return Math.round(num * 10 ** dec) / 10 ** dec;\n};\n\n/*\n* Shorten and style number\n*/\nexport const styleNum = (number, unit = '', truncate = true) => {\n  let number_ = number;\n  if (Number.isNaN(number_)) {\n    return 'N/A';\n  }\n\n  if (truncate && number_ > 999999999) {\n    number_ = roundNumber(number_ / 1000000000.0, 3);\n    return `${addCommas(number_)}G${unit}`;\n  } else if (truncate && number_ > 999999) {\n    number_ = roundNumber(number_ / 1000000.0, 3);\n    return `${addCommas(number_)}M${unit}`;\n  } else if (truncate && number_ > 999) {\n    number_ = roundNumber(number_ / 1000.0, 3);\n    return `${addCommas(number_)}k${unit}`;\n  } else if (number_ > 999) {\n    number_ = roundNumber(number_, 0);\n    return addCommas(number_) + unit;\n  } else {\n    number_ = roundNumber(number_, 2);\n    return addCommas(number_) + unit;\n  }\n};\n\nexport const metricToFormatter = {\n  REQUEST_RATE: m => _isNil(m) ? '---' : styleNum(m, ' RPS', true),\n  SUCCESS_RATE: m => _isNil(m) ? '---' : successRateFormatter(m),\n  LATENCY: formatLatencyMs,\n  UNTRUNCATED: m => styleNum(m, '', false),\n  BYTES: m => _isNil(m) ? '---' : styleNum(m, 'B/s', true),\n  NO_UNIT: m => _isNil(m) ? '---' : styleNum(m, '', true),\n};\n\n/*\n* Convert a string to a valid css class name\n*/\nexport const toClassName = name => {\n  if (!name) { return ''; }\n  return _lowerCase(name).replace(/[^a-zA-Z0-9]/g, '_');\n};\n\n/*\n Create regex string from user input for a filter\n*/\nexport const regexFilterString = input => {\n  // make input lower case and strip out unwanted characters\n  const input_ = input.replace(/[^A-Z0-9/.\\-_*]/gi, '').toLowerCase();\n  // replace \"*\" in input with wildcard\n  return new RegExp(input_.replace(/[*]/g, '.+'));\n};\n\n/*\n  Get a singular resource name from a plural resource\n*/\nexport const singularResource = resource => {\n  if (resource === 'authorities') {\n    return 'authority';\n  } else { return resource.replace(/s$/, ''); }\n};\n\n/*\n  Nicely readable names for the stat resources\n*/\nexport const friendlyTitle = singularOrPluralResource => {\n  const resource = singularResource(singularOrPluralResource);\n  let titleCase = _startCase(resource);\n  if (resource === 'replicationcontroller') {\n    titleCase = _startCase('replication controller');\n  } else if (resource === 'daemonset') {\n    titleCase = _startCase('daemon set');\n  } else if (resource === 'statefulset') {\n    titleCase = _startCase('stateful set');\n  } else if (resource === 'trafficsplit') {\n    titleCase = _startCase('traffic split');\n  } else if (resource === 'cronjob') {\n    titleCase = _startCase('cron job');\n  } else if (resource === 'replicaset') {\n    titleCase = _startCase('replica set');\n  }\n\n  const titles = { singular: titleCase };\n  if (resource === 'authority') {\n    titles.plural = 'Authorities';\n  } else {\n    titles.plural = `${titles.singular}s`;\n  }\n  return titles;\n};\n\n/*\n  Get the resource type from the /pods response, whose json\n  is camelCased.\n*/\nconst camelCaseLookUp = {\n  replicaset: 'replicaSet',\n  replicationcontroller: 'replicationController',\n  statefulset: 'statefulSet',\n  trafficsplit: 'trafficSplit',\n  daemonset: 'daemonSet',\n  cronjob: 'cronJob',\n};\n\nexport const resourceTypeToCamelCase = resource => camelCaseLookUp[resource] || resource;\n\n/*\n  A simplified version of ShortNameFromCanonicalResourceName\n*/\nexport const shortNameLookup = {\n  deployment: 'deploy',\n  daemonset: 'ds',\n  namespace: 'ns',\n  pod: 'po',\n  replicationcontroller: 'rc',\n  replicaset: 'rs',\n  service: 'svc',\n  statefulset: 'sts',\n  trafficsplit: 'ts',\n  job: 'job',\n  authority: 'au',\n  cronjob: 'cj',\n};\n\nexport const podOwnerLookup = {\n  deployment: 'deploy',\n  daemonset: 'ds',\n  replicationcontroller: 'rc',\n  replicaset: 'rs',\n  statefulset: 'sts',\n  cronjob: 'cj',\n};\n\nexport const toShortResourceName = name => shortNameLookup[name] || name;\n\nexport const displayName = resource => `${toShortResourceName(resource.type)}/${resource.name}`;\n\nexport const isResource = name => {\n  const singularResourceName = singularResource(name);\n  return _has(shortNameLookup, singularResourceName);\n};\n\n/*\n  produce octets given an ip address\n*/\nconst decodeIPToOctets = ip => {\n  const ip_ = parseInt(ip, 10);\n  return [\n    (ip_ >> 24) & 255,\n    (ip_ >> 16) & 255,\n    (ip_ >> 8) & 255,\n    ip_ & 255,\n  ];\n};\n\n/*\n  converts an address to an ipv4 formatted host:port pair\n*/\nexport const publicAddressToString = (ipv4, port) => {\n  const octets = decodeIPToOctets(ipv4);\n  return `${octets.join('.')}:${port}`;\n};\n\nexport const getSrClassification = sr => {\n  if (sr < 0.9) {\n    return 'status-poor';\n  } else if (sr < 0.95) {\n    return 'status-ok';\n  } else { return 'status-good'; }\n};\n"
  },
  {
    "path": "web/app/js/components/util/Utils.test.js",
    "content": "import {\n  formatLatencySec,\n  metricToFormatter,\n  regexFilterString,\n  styleNum,\n  toClassName\n} from './Utils.js';\n\n// introduce some binary floating point rounding errors, like ya do\nfunction float(num) {\n  return num * 0.1 * 10;\n}\n\ndescribe('Utils', () => {\n  describe('styleNum', () => {\n    it('properly formats numbers', () => {\n      let compare = (f, s) => expect(styleNum(float(f))).toEqual(s);\n      compare(1, \"1\");\n      compare(2.20, \"2.2\");\n      compare(3, \"3\");\n      compare(4.4, \"4.4\");\n      compare(5.0000001, \"5\");\n      compare(7.6666667, \"7.67\");\n      compare(123.456, \"123.46\");\n      compare(1212.999999, \"1.213k\");\n      compare(5329.333333, \"5.329k\");\n      compare(16384.888, \"16.385k\");\n      compare(131042, \"131.042k\");\n      compare(1048576, \"1.049M\");\n      compare(2097152.1, \"2.097M\");\n      compare(16777216, \"16.777M\");\n      compare(536870912, \"536.871M\");\n      compare(1073741824, \"1.074G\");\n      compare(68719476736, \"68.719G\");\n    });\n\n    it('properly formats numbers with units and no truncation', () => {\n      let compare = (f, s) => expect(styleNum(float(f), \" RPS\", false)).toEqual(s);\n      compare(1, \"1 RPS\");\n      compare(2.20, \"2.2 RPS\");\n      compare(3, \"3 RPS\");\n      compare(4.4, \"4.4 RPS\");\n      compare(5.0000001, \"5 RPS\");\n      compare(7.6666667, \"7.67 RPS\");\n      compare(123.456, \"123.46 RPS\");\n      compare(1212.999999, \"1,213 RPS\");\n      compare(5329.333333, \"5,329 RPS\");\n      compare(16384.888, \"16,385 RPS\");\n      compare(131042, \"131,042 RPS\");\n      compare(1048576, \"1,048,576 RPS\");\n      compare(2097152.1, \"2,097,152 RPS\");\n      compare(16777216, \"16,777,216 RPS\");\n      compare(536870912, \"536,870,912 RPS\");\n      compare(1073741824, \"1,073,741,824 RPS\");\n      compare(68719476736, \"68,719,476,736 RPS\");\n    });\n  });\n\n  describe('Metric Formatters', () => {\n    it('formats undefined input', () => {\n      let undefinedMetric;\n      expect(metricToFormatter[\"REQUEST_RATE\"](undefinedMetric)).toEqual('---');\n      expect(metricToFormatter[\"SUCCESS_RATE\"](undefinedMetric)).toEqual('---');\n      expect(metricToFormatter[\"LATENCY\"](undefinedMetric)).toEqual('---');\n    });\n\n    it('formats requests with rounding and unit', () => {\n      expect(metricToFormatter[\"REQUEST_RATE\"](99)).toEqual('99 RPS');\n      expect(metricToFormatter[\"REQUEST_RATE\"](999)).toEqual('999 RPS');\n      expect(metricToFormatter[\"REQUEST_RATE\"](1000)).toEqual('1k RPS');\n      expect(metricToFormatter[\"REQUEST_RATE\"](4444)).toEqual('4.444k RPS');\n      expect(metricToFormatter[\"REQUEST_RATE\"](9999)).toEqual('9.999k RPS');\n      expect(metricToFormatter[\"REQUEST_RATE\"](99999)).toEqual('99.999k RPS');\n    });\n\n    it('formats subsecond latency as ms', () => {\n      expect(metricToFormatter[\"LATENCY\"](99)).toEqual('99 ms');\n      expect(metricToFormatter[\"LATENCY\"](999)).toEqual('999 ms');\n    });\n\n    it('formats latency greater than 1s as s', () => {\n      expect(metricToFormatter[\"LATENCY\"](1000)).toEqual('1.00 s');\n      expect(metricToFormatter[\"LATENCY\"](9999)).toEqual('10.0 s');\n      expect(metricToFormatter[\"LATENCY\"](99999)).toEqual('100 s');\n    });\n\n    it('formats success rate', () => {\n      expect(metricToFormatter[\"SUCCESS_RATE\"](0.012345)).toEqual('1.23%');\n      expect(metricToFormatter[\"SUCCESS_RATE\"](0.01)).toEqual('1.00%');\n      expect(metricToFormatter[\"SUCCESS_RATE\"](0.1)).toEqual('10.00%');\n      expect(metricToFormatter[\"SUCCESS_RATE\"](0.9999)).toEqual('99.99%');\n      expect(metricToFormatter[\"SUCCESS_RATE\"](4)).toEqual('400.00%');\n    });\n\n    it('formats bytes', () => {\n      expect(metricToFormatter[\"BYTES\"](123.938112312)).toEqual('123.94B/s');\n      expect(metricToFormatter[\"BYTES\"](1234.32)).toEqual('1.234kB/s');\n      expect(metricToFormatter[\"BYTES\"](12345.1831)).toEqual('12.345kB/s');\n      expect(metricToFormatter[\"BYTES\"](1234567.02384)).toEqual('1.235MB/s');\n    });\n\n    it('formats latencies expressed as seconds into a more appropriate display unit', () => {\n      expect(formatLatencySec(\"0.002837700\")).toEqual(\"3 ms\");\n      expect(formatLatencySec(\"0.000\")).toEqual(\"0 s\");\n      expect(formatLatencySec(\"0.000000797\")).toEqual(\"1 µs\");\n      expect(formatLatencySec(\"0.000231910\")).toEqual(\"232 µs\");\n      expect(formatLatencySec(\"0.000988600\")).toEqual(\"989 µs\");\n      expect(formatLatencySec(\"0.005598200\")).toEqual(\"6 ms\");\n      expect(formatLatencySec(\"3.029409200\")).toEqual(\"3.03 s\");\n      expect(formatLatencySec(\"34.395600\")).toEqual(\"34.4 s\");\n    });\n  });\n\n  describe('toClassName', () => {\n    it('converts a string to a valid class name', () => {\n      expect(toClassName('')).toEqual('');\n      expect(toClassName('---')).toEqual('');\n      expect(toClassName('foo/bar/baz')).toEqual('foo_bar_baz');\n      expect(toClassName('FOOBAR')).toEqual('foobar');\n      expect(toClassName('FooBar')).toEqual('foo_bar');\n\n      // the perhaps unexpected number of spaces here are due to the fact that\n      // _.lowerCase returns space separated words\n      expect(toClassName('potato123yam0squash')).toEqual('potato_123_yam_0_squash');\n      expect(toClassName('test/potato-e1af21-f3f3')).toEqual('test_potato_e_1_af_21_f_3_f_3');\n    });\n  });\n\n  describe('regexFilterString', () => {\n    it('converts input string to a valid regex for filtering', () => {\n      expect(regexFilterString('emojivoto')).toEqual(new RegExp(/emojivoto/));\n      expect(regexFilterString('emojivoto123')).toEqual(new RegExp(/emojivoto123/));\n      expect(regexFilterString('emojivoto*')).toEqual(new RegExp(/emojivoto.+/));\n      expect(regexFilterString('emojivoto**')).toEqual(new RegExp(/emojivoto.+.+/));\n      expect(regexFilterString('Emojivoto')).toEqual(new RegExp(/emojivoto/));\n      expect(regexFilterString('emojivoto{}')).toEqual(new RegExp(/emojivoto/));\n      expect(regexFilterString('emojivoto_.')).toEqual(new RegExp(/emojivoto_./));\n      expect(regexFilterString('emojivoto_.{')).toEqual(new RegExp(/emojivoto_./));\n      expect(regexFilterString('emojivoto//')).toEqual(new RegExp(/emojivoto\\/\\//));\n      expect(regexFilterString('emojivoto//')).toEqual(new RegExp(/emojivoto\\/\\//));\n      expect(regexFilterString('emojivoto-prod-1')).toEqual(new RegExp(/emojivoto-prod-1/));\n      expect(regexFilterString('emoji??Voto-pr##od-1')).toEqual(new RegExp(/emojivoto-prod-1/));\n    })\n  });\n});\n"
  },
  {
    "path": "web/app/js/components/util/theme.js",
    "content": "import green from '@material-ui/core/colors/green';\nimport grey from '@material-ui/core/colors/grey';\nimport orange from '@material-ui/core/colors/orange';\nimport red from '@material-ui/core/colors/red';\n\nconst status = {\n  // custom variables for success rate indicators\n  dark: {\n    danger: red[500],\n    warning: orange[500],\n    good: green[500],\n    default: grey[500],\n  },\n  // custom variables for progress bars, which need both the normal colors\n  // as well as a lighter version of them for the bar background\n  light: {\n    danger: red[200],\n    warning: orange[200],\n    good: green[200],\n    default: grey[200],\n  },\n};\n\nexport const dashboardTheme = {\n  palette: {\n    primary: {\n      main: '#001443',\n    },\n  },\n  // substituting default Material breakpoints with Bootstrap breakpoints\n  breakpoints: {\n    values: {\n      sm: 576,\n      md: 992,\n      lg: 1200,\n    },\n  },\n  status,\n};\n\nexport const statusClassNames = theme => {\n  theme.status = theme.status || status; // tests don't inject custom variables\n\n  return {\n    poor: {\n      backgroundColor: theme.status.dark.danger,\n    },\n    warning: {\n      backgroundColor: theme.status.dark.warning,\n    },\n    good: {\n      backgroundColor: theme.status.dark.good,\n    },\n    default: {\n      backgroundColor: theme.status.dark.default,\n    },\n  };\n};\n"
  },
  {
    "path": "web/app/js/components/util/withREST.jsx",
    "content": "import PropTypes from 'prop-types';\nimport React from 'react';\nimport _get from 'lodash/get';\nimport _isEmpty from 'lodash/isEmpty';\nimport _merge from 'lodash/merge';\nimport { handlePageVisibility, withPageVisibility } from './PageVisibility.jsx';\nimport { withContext } from './AppContext.jsx';\n\n/**\n * Provides components with data fetched via. polling a list of REST URLs.\n * @constructor\n * @param {React.Component} WrappedComponent - Component to add functionality to.\n * @param {List[string]} requestURLs - List of URLs to poll.\n * @param {List[string]} options - Options for withREST\n */\nconst withREST = (WrappedComponent, componentPromises, options = {}) => {\n  const localOptions = _merge({}, {\n    resetProps: [],\n    poll: true,\n  }, options);\n\n  class RESTWrapper extends React.Component {\n    constructor(props) {\n      super(props);\n      this.api = props.api;\n\n      this.state = this.getInitialState(this.props);\n    }\n\n    getInitialState() {\n      return {\n        pollingInterval: 2000, // TODO: poll based on metricsWindow size\n        data: [],\n        pendingRequests: false,\n        loading: true,\n        error: null,\n      };\n    }\n\n    componentDidMount() {\n      this.startServerPolling(this.props);\n    }\n\n    componentDidUpdate(prevProps) {\n      const { isPageVisible } = this.props;\n      handlePageVisibility({\n        prevVisibilityState: prevProps.isPageVisible,\n        currentVisibilityState: isPageVisible,\n        onVisible: () => this.startServerPolling(this.props),\n        onHidden: () => this.stopServerPolling(),\n      });\n\n      const changed = localOptions.resetProps.filter(\n        prop => _get(prevProps, prop) !== _get(this.props, prop),\n      );\n\n      if (_isEmpty(changed)) { return; }\n\n      // React won't unmount this component when switching resource pages so we need to clear state\n      this.stopServerPolling();\n      this.resetState();\n      this.startServerPolling(this.props);\n    }\n\n    resetState() {\n      this.setState(this.getInitialState());\n    }\n\n    componentWillUnmount() {\n      this.stopServerPolling();\n    }\n\n    startServerPolling = props => {\n      const { pollingInterval } = this.state;\n      this.loadFromServer(props);\n      if (localOptions.poll) {\n        this.timerId = window.setInterval(this.loadFromServer, pollingInterval, props);\n      }\n    };\n\n    stopServerPolling = () => {\n      this.api.cancelCurrentRequests();\n      this.setState({ pendingRequests: false });\n      if (localOptions.poll) {\n        window.clearInterval(this.timerId);\n      }\n    };\n\n    loadFromServer = props => {\n      const { pendingRequests } = this.state;\n\n      if (pendingRequests) {\n        return; // don't make more requests if the ones we sent haven't completed\n      }\n\n      this.setState({ pendingRequests: true });\n\n      this.api.setCurrentRequests(componentPromises(props));\n\n      Promise.all(this.api.getCurrentPromises())\n        .then(data => {\n          this.setState({\n            data,\n            loading: false,\n            pendingRequests: false,\n            error: null,\n          });\n        })\n        .catch(this.handleApiError);\n    };\n\n    handleApiError = e => {\n      if (e.isCanceled) { return; }\n\n      this.setState({\n        pendingRequests: false,\n        error: e,\n      });\n    };\n\n    render() {\n      const { data, error, loading } = this.state;\n\n      return (\n        <WrappedComponent\n          data={data}\n          error={error}\n          loading={loading}\n          {...this.props} />\n      );\n    }\n  }\n\n  RESTWrapper.propTypes = {\n    api: PropTypes.shape({\n      cancelCurrentRequests: PropTypes.func.isRequired,\n      getCurrentPromises: PropTypes.func.isRequired,\n      setCurrentRequests: PropTypes.func.isRequired,\n    }).isRequired,\n    isPageVisible: PropTypes.bool.isRequired,\n  };\n\n  return withPageVisibility(withContext(RESTWrapper));\n};\n\nexport default withREST;\n"
  },
  {
    "path": "web/app/js/index.js",
    "content": "import '../css/styles.css';\nimport '../img/favicon.png'; // needs to be referenced somewhere so webpack bundles it\n\nimport { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom';\nimport { DETECTORS, LocaleResolver, TRANSFORMERS } from 'locales-detector';\nimport { MuiThemeProvider, createTheme } from '@material-ui/core/styles';\n\nimport CssBaseline from '@material-ui/core/CssBaseline';\nimport { I18nProvider } from '@lingui/react';\nimport { i18n } from '@lingui/core';\nimport { en, es } from 'make-plural/plurals';\nimport { QueryParamProvider } from 'use-query-params';\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport _find from 'lodash/find';\nimport _isEmpty from 'lodash/isEmpty';\nimport { ReactRouter5Adapter } from 'use-query-params/adapters/react-router-5';\nimport ApiHelpers from './components/util/ApiHelpers.jsx';\nimport AppContext from './components/util/AppContext.jsx';\nimport Community from './components/Community.jsx';\nimport Extensions from './components/Extensions.jsx';\nimport Gateway from './components/Gateway.jsx';\nimport Namespace from './components/Namespace.jsx';\nimport Navigation from './components/Navigation.jsx';\nimport NoMatch from './components/NoMatch.jsx';\nimport ResourceDetail from './components/ResourceDetail.jsx';\nimport ResourceList from './components/ResourceList.jsx';\nimport ServiceMesh from './components/ServiceMesh.jsx';\nimport Tap from './components/Tap.jsx';\nimport Top from './components/Top.jsx';\nimport TopRoutes from './components/TopRoutes.jsx';\nimport catalogEn from './locales/en/messages.js';\nimport catalogEs from './locales/es/messages.js';\nimport { dashboardTheme } from './components/util/theme.js';\n\nconst appMain = document.getElementById('main');\nconst appData = !appMain ? {} : appMain.dataset;\n\nlet pathPrefix = '';\nconst proxyPathMatch = window.location.pathname.match(/\\/api\\/v1\\/namespaces\\/.*\\/proxy/g);\nif (proxyPathMatch) {\n  pathPrefix = proxyPathMatch[0];\n}\n\nlet defaultNamespace = 'default';\nconst pathArray = window.location.pathname.split('/');\n\n// if the current URL path specifies a namespace, this should become the\n// defaultNamespace\nif (pathArray[0] === '' && pathArray[1] === 'namespaces' && pathArray[2]) {\n  defaultNamespace = pathArray[2];\n// if the current URL path is a legacy path such as `/daemonsets`, the\n// defaultNamespace should be \"_all\", unless the path is `/namespaces`\n} else if (pathArray.length === 2 && pathArray[1] !== '' && pathArray[1] !== 'namespaces') {\n  defaultNamespace = '_all';\n}\n\nconst detectedLocales = new LocaleResolver(\n  [new DETECTORS.NavigatorDetector()],\n  [new TRANSFORMERS.FallbacksTransformer()],\n).getLocales();\nconst langOptions = {\n  en: {\n    catalog: catalogEn,\n    plurals: en,\n  },\n  es: {\n    catalog: catalogEs,\n    plurals: es,\n  },\n};\nconst selectedLocale =\n    _find(detectedLocales, l => !_isEmpty(langOptions[l])) || 'en';\nconst selectedLangOptions = langOptions[selectedLocale] || langOptions.en;\n\ni18n.loadLocaleData(selectedLocale, { plurals: selectedLangOptions.plurals });\ni18n.load(selectedLocale, selectedLangOptions.catalog.messages);\ni18n.activate(selectedLocale);\n\nclass App extends React.Component {\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      ...appData,\n    };\n\n    this.state.api = ApiHelpers(pathPrefix);\n    this.state.pathPrefix = pathPrefix;\n    this.state.productName = 'Linkerd';\n    this.state.selectedNamespace = defaultNamespace;\n\n    this.state.updateNamespaceInContext = name => {\n      this.setState({ selectedNamespace: name });\n    };\n\n    this.state.checkNamespaceMatch = path => {\n      const { selectedNamespace } = this.state;\n      const pathNamespace = path.split('/')[2];\n      if (pathNamespace && pathNamespace !== selectedNamespace) {\n        this.setState({ selectedNamespace: pathNamespace });\n      }\n    };\n  }\n\n  render() {\n    return (\n      <AppContext.Provider value={this.state}>\n        <I18nProvider i18n={i18n}>\n          <AppHTML />\n        </I18nProvider>\n      </AppContext.Provider>\n    );\n  }\n}\n\nconst AppHTML = function() {\n  const theme = createTheme(dashboardTheme);\n\n  return (\n    <React.Fragment>\n      <CssBaseline />\n      <MuiThemeProvider theme={theme}>\n        <BrowserRouter>\n          <QueryParamProvider adapter={ReactRouter5Adapter}>\n            <Switch>\n              <Redirect exact from={`${pathPrefix}/`} to={`${pathPrefix}/namespaces`} />\n              <Redirect exact from={`${pathPrefix}/overview`} to={`${pathPrefix}/namespaces`} />\n              <Redirect exact from={`${pathPrefix}/deployments`} to={`${pathPrefix}/namespaces/_all/deployments`} />\n              <Redirect exact from={`${pathPrefix}/services`} to={`${pathPrefix}/namespaces/_all/services`} />\n              <Redirect exact from={`${pathPrefix}/daemonsets`} to={`${pathPrefix}/namespaces/_all/daemonsets`} />\n              <Redirect exact from={`${pathPrefix}/statefulsets`} to={`${pathPrefix}/namespaces/_all/statefulsets`} />\n              <Redirect exact from={`${pathPrefix}/jobs`} to={`${pathPrefix}/namespaces/_all/jobs`} />\n              <Redirect exact from={`${pathPrefix}/replicationcontrollers`} to={`${pathPrefix}/namespaces/_all/replicationcontrollers`} />\n              <Redirect exact from={`${pathPrefix}/pods`} to={`${pathPrefix}/namespaces/_all/pods`} />\n\n              <Route\n                path={`${pathPrefix}/controlplane`}\n                render={props => <Navigation {...props} ChildComponent={ServiceMesh} />} />\n              <Route\n                path={`${pathPrefix}/gateways`}\n                render={props => <Navigation {...props} ChildComponent={Gateway} resource=\"gateway\" />} />\n              <Route\n                exact\n                path={`${pathPrefix}/namespaces/:namespace`}\n                render={props => <Navigation {...props} ChildComponent={Namespace} />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/pods/:pod`}\n                render={props => <Navigation {...props} ChildComponent={ResourceDetail} />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/pods`}\n                render={props => <Navigation {...props} ChildComponent={ResourceList} resource=\"pod\" />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/daemonsets/:daemonset`}\n                render={props => <Navigation {...props} ChildComponent={ResourceDetail} />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/daemonsets`}\n                render={props => <Navigation {...props} ChildComponent={ResourceList} resource=\"daemonset\" />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/statefulsets/:statefulset`}\n                render={props => <Navigation {...props} ChildComponent={ResourceDetail} />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/statefulsets`}\n                render={props => <Navigation {...props} ChildComponent={ResourceList} resource=\"statefulset\" />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/jobs/:job`}\n                render={props => <Navigation {...props} ChildComponent={ResourceDetail} />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/jobs`}\n                render={props => <Navigation {...props} ChildComponent={ResourceList} resource=\"job\" />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/deployments/:deployment`}\n                render={props => <Navigation {...props} ChildComponent={ResourceDetail} />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/services/:service`}\n                render={props => <Navigation {...props} ChildComponent={ResourceDetail} />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/deployments`}\n                render={props => <Navigation {...props} ChildComponent={ResourceList} resource=\"deployment\" />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/services`}\n                render={props => <Navigation {...props} ChildComponent={ResourceList} resource=\"service\" />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/replicationcontrollers/:replicationcontroller`}\n                render={props => <Navigation {...props} ChildComponent={ResourceDetail} />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/replicationcontrollers`}\n                render={props => <Navigation {...props} ChildComponent={ResourceList} resource=\"replicationcontroller\" />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/cronjobs/:cronjob`}\n                render={props => <Navigation {...props} ChildComponent={ResourceDetail} />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/cronjobs`}\n                render={props => <Navigation {...props} ChildComponent={ResourceList} resource=\"cronjob\" />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/replicasets/:replicaset`}\n                render={props => <Navigation {...props} ChildComponent={ResourceDetail} />} />\n              <Route\n                path={`${pathPrefix}/namespaces/:namespace/replicasets`}\n                render={props => <Navigation {...props} ChildComponent={ResourceList} resource=\"replicaset\" />} />\n              <Route\n                path={`${pathPrefix}/tap`}\n                render={props => <Navigation {...props} ChildComponent={Tap} />} />\n              <Route\n                path={`${pathPrefix}/top`}\n                render={props => <Navigation {...props} ChildComponent={Top} />} />\n              <Route\n                path={`${pathPrefix}/routes`}\n                render={props => <Navigation {...props} ChildComponent={TopRoutes} />} />\n              <Route\n                path={`${pathPrefix}/namespaces`}\n                render={props => <Navigation {...props} ChildComponent={ResourceList} resource=\"namespace\" />} />\n              <Route\n                path={`${pathPrefix}/community`}\n                render={props => <Navigation {...props} ChildComponent={Community} />} />\n              <Route\n                path={`${pathPrefix}/extensions`}\n                render={props => <Navigation {...props} ChildComponent={Extensions} />} />\n              <Route component={NoMatch} />\n            </Switch>\n          </QueryParamProvider>\n        </BrowserRouter>\n      </MuiThemeProvider>\n    </React.Fragment>\n  );\n};\n\nReactDOM.render(<App />, appMain);\n"
  },
  {
    "path": "web/app/js/locales/en/messages.json",
    "content": "{\n  \"404Msg\": \"Page not found.\",\n  \"A new version ({versionText}) is available.\": \"A new version ({versionText}) is available.\",\n  \"Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh\": \"Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh\",\n  \"All namespaces have a {productName} install.\": \"All namespaces have a {productName} install.\",\n  \"Control plane\": \"Control plane\",\n  \"Control plane components\": \"Control plane components\",\n  \"Current {cmdNameDisplay} query\": \"Current {cmdNameDisplay} query\",\n  \"Data plane proxies\": \"Data plane proxies\",\n  \"LinkerdIsUpToDateMsg\": \"Linkerd is up to date.\",\n  \"New service profile\": \"New service profile\",\n  \"NoDataToDisplayMsg\": \"No data to display\",\n  \"Pod status: {0}\": \"Pod status: {0}\",\n  \"Running\": \"Running\",\n  \"Service mesh details\": \"Service mesh details\",\n  \"Update Now\": \"Update Now\",\n  \"Version check failed{0}.\": \"Version check failed{0}.\",\n  \"buttonCancel\": \"Cancel\",\n  \"buttonClose\": \"Close\",\n  \"buttonCreateServiceProfile\": \"Create Service Profile\",\n  \"buttonDownload\": \"Download\",\n  \"buttonLearnMore\": \"Learn More\",\n  \"buttonReRunCheck\": \"Re-Run Check\",\n  \"buttonReset\": \"Reset\",\n  \"buttonRunLinkerdCheck\": \"Run Linkerd Check\",\n  \"buttonStart\": \"Start\",\n  \"buttonStop\": \"Stop\",\n  \"columnTitleAlive\": \"Alive\",\n  \"columnTitleApexService\": \"Apex Service\",\n  \"columnTitleBest\": \"Best\",\n  \"columnTitleClusterName\": \"Cluster Name\",\n  \"columnTitleCount\": \"Count\",\n  \"columnTitleDeployment\": \"Deployment\",\n  \"columnTitleDestination\": \"Destination\",\n  \"columnTitleDirection\": \"Direction\",\n  \"columnTitleFrom\": \"FROM\",\n  \"columnTitleGRPCStatus\": \"GRPC Status\",\n  \"columnTitleGrafana\": \"Grafana\",\n  \"columnTitleHTTPStatus\": \"HTTP Status\",\n  \"columnTitleIdentity\": \"Identity\",\n  \"columnTitleJaeger\": \"Jaeger\",\n  \"columnTitleLast\": \"Last\",\n  \"columnTitleLatency\": \"Latency\",\n  \"columnTitleLeafService\": \"Leaf Service\",\n  \"columnTitleMeshed\": \"Meshed\",\n  \"columnTitleMeshedPods\": \"Meshed Pods\",\n  \"columnTitleMeshedStatus\": \"Meshed Status\",\n  \"columnTitleMethod\": \"Method\",\n  \"columnTitleName\": \"Name\",\n  \"columnTitleNamespace\": \"Namespace\",\n  \"columnTitleNoTraffic\": \"No Traffic\",\n  \"columnTitleOpenConnections\": \"Connections\",\n  \"columnTitleP50Latency\": \"P50 Latency\",\n  \"columnTitleP95Latency\": \"P95 Latency\",\n  \"columnTitleP99Latency\": \"P99 Latency\",\n  \"columnTitlePairedServices\": \"Paired Services\",\n  \"columnTitlePath\": \"Path\",\n  \"columnTitlePodStatus\": \"Pod Status\",\n  \"columnTitlePods\": \"Pods\",\n  \"columnTitleRPS\": \"RPS\",\n  \"columnTitleReadRate\": \"Read Bytes / sec\",\n  \"columnTitleRoute\": \"Route\",\n  \"columnTitleSecured\": \"Secured\",\n  \"columnTitleService\": \"Service\",\n  \"columnTitleSource\": \"Source\",\n  \"columnTitleSuccessRate\": \"Success Rate\",\n  \"columnTitleTap\": \"Tap\",\n  \"columnTitleTo\": \"TO\",\n  \"columnTitleUnmeshed\": \"Unmeshed\",\n  \"columnTitleValue\": \"Value\",\n  \"columnTitleWeight\": \"Weight\",\n  \"columnTitleWorst\": \"Worst\",\n  \"columnTitleWriteRate\": \"Write Bytes / sec\",\n  \"componentsMsg\": \"Components\",\n  \"connectResourceMsg {resource}\": \"Connect your first {resource}\",\n  \"controllerInstalledMsg\": \"Controller successfully installed\",\n  \"createNewProfileMsg\": \"You can also create a new profile\",\n  \"extensionsPageMsg\": \"Linkerd provides a mix of built-in and third-party extensions to add additional functionality to the base installation. The following is the list of known extensions:\",\n  \"formAuthority\": \"Authority\",\n  \"formAuthorityHelpText\": \"Display requests with this :authority\",\n  \"formCreateServiceProfileHelpText\": \"To create a service profile, download a profile and then apply it with `kubectl apply`.\",\n  \"formGRPCStatus\": \"GRPC Status\",\n  \"formHTTPMethod\": \"HTTP Method\",\n  \"formHTTPMethodHelpText\": \"Display requests with this HTTP method\",\n  \"formHTTPStatus\": \"HTTP Status\",\n  \"formHeaders\": \"Headers\",\n  \"formHideFilters\": \"Hide filters\",\n  \"formLatency\": \"Latency\",\n  \"formMaxRPS\": \"Max RPS\",\n  \"formMaxRPSHelpText {defaultMaxRps}\": \"Maximum requests per second to tap. Default {defaultMaxRps}\",\n  \"formMethod\": \"Method\",\n  \"formNamespace\": \"Namespace\",\n  \"formNamespaceErrorText\": \"Namespace must consist of lower case alphanumeric characters or '-' and must start and end with an alphanumeric character\",\n  \"formNamespaceHelpText\": \"Namespace to query\",\n  \"formNoNamedRouteTrafficFound\": \"No named route traffic found. This could be because the service is not receiving any traffic, or because there is no service profile configured. Does the service have a service profile?\",\n  \"formPath\": \"Path\",\n  \"formPathHelpText\": \"Display requests with paths that start with this prefix\",\n  \"formResource\": \"Resource\",\n  \"formResourceHelpText\": \"Resource to query\",\n  \"formResponseLengthB\": \"Response Length (B)\",\n  \"formScheme\": \"Scheme\",\n  \"formSchemeHelpText\": \"Display requests with this scheme\",\n  \"formServiceNameErrorText\": \"Service name must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character\",\n  \"formShowFilters\": \"Show more filters\",\n  \"formToNamespace\": \"To Namespace\",\n  \"formToNamespaceHelpText\": \"Namespace of target resource\",\n  \"formToResource\": \"To Resource\",\n  \"formToResourceHelpText\": \"Target resource\",\n  \"installMulticlusterMsg\": \"To view gateway stats for your mesh, install the linkerd multicluster extension by running\",\n  \"labelError\": \"Error\",\n  \"labelSuccess\": \"Success\",\n  \"menuItemCommunity\": \"Community\",\n  \"menuItemControlPlane\": \"Control Plane\",\n  \"menuItemCronJobs\": \"Cron Jobs\",\n  \"menuItemDaemonSets\": \"Daemon Sets\",\n  \"menuItemDeployments\": \"Deployments\",\n  \"menuItemServices\": \"Services\",\n  \"menuItemDocumentation\": \"Documentation\",\n  \"menuItemExtension\": \"Extensions\",\n  \"menuItemGateway\": \"Gateway\",\n  \"menuItemGitHub\": \"GitHub\",\n  \"menuItemJobs\": \"Jobs\",\n  \"menuItemMailingList\": \"Mailing List\",\n  \"menuItemNamespaces\": \"Namespaces\",\n  \"menuItemPods\": \"Pods\",\n  \"menuItemReplicaSets\": \"Replica Sets\",\n  \"menuItemReplicationControllers\": \"Replication Controllers\",\n  \"menuItemRoutes\": \"Routes\",\n  \"menuItemSlack\": \"Slack\",\n  \"menuItemStatefulSets\": \"Stateful Sets\",\n  \"menuItemTap\": \"Tap\",\n  \"menuItemTop\": \"Top\",\n  \"noNamespacesDetectedMsg\": \"No namespaces detected.\",\n  \"noResourcesDetectedMsg\": \"No resources detected.\",\n  \"podsAreInitializingMsg\": \"Pods are initializing\",\n  \"serviceMeshInstalledMsg\": \"The service mesh was successfully installed!\",\n  \"sidebarHeadingCluster\": \"Cluster\",\n  \"sidebarHeadingTools\": \"Tools\",\n  \"sidebarHeadingWorkloads\": \"Workloads\",\n  \"statusExplanationGood\": \"is up and running\",\n  \"statusExplanationInMesh\": \"Added to mesh\",\n  \"statusExplanationNotInMesh\": \"Not in mesh\",\n  \"statusExplanationNotStarted\": \"has not been started\",\n  \"tabLiveCalls\": \"Live Calls\",\n  \"tabRouteMetrics\": \"Route Metrics\",\n  \"tableTitleEdgesEmpty\": \"Edges\",\n  \"tableTitleEdgesWithIdentity {identity}\": \"Edges (Identity: {identity})\",\n  \"tableTitleGateways\": \"Gateways\",\n  \"tableTitleHTTPMetrics\": \"HTTP Metrics\",\n  \"tableTitleInbound\": \"Inbound\",\n  \"tableTitleLeafServices\": \"Leaf Services\",\n  \"tableTitleOutbound\": \"Outbound\",\n  \"tableTitlePods\": \"Pods\",\n  \"tableTitleRequestDetails\": \"Request Details\",\n  \"tableTitleRequestInit\": \"Request Init\",\n  \"tableTitleResponseEnd\": \"Response End\",\n  \"tableTitleResponseInit\": \"Response Init\",\n  \"tableTitleTCP\": \"TCP\",\n  \"tableTitleTCPMetrics\": \"TCP Metrics\",\n  \"Pods under the resource {resource} in the {namespace} namespace are missing tap configurations (restart these pods to enable tap)\": \"Pods under the resource {resource} in the {namespace} namespace are missing tap configurations (restart these pods to enable tap)\",\n  \"tooltipInbound\": \"INBOUND\",\n  \"tooltipOutbound\": \"OUTBOUND\",\n  \"unspecifiedResourcesMsg\": \"one or more resources\",\n  \"{numResources, plural, zero {{resource}} one {{resource}} other {{resource}}}\": \"{numResources, plural, zero {No {resource}s detected} one {# {resource} detected} other {# {resource}s detected}}\",\n  \"{numUnadded, plural, one {# namespace has no meshed resources} other {# namespaces have no meshed resources}}\": \"{numUnadded, plural, one {# namespace has no meshed resources.} other {# namespaces have no meshed resources.}}\",\n  \"{productName} namespace\": \"{productName} namespace\",\n  \"{productName} version\": \"{productName} version\"\n}\n"
  },
  {
    "path": "web/app/js/locales/es/messages.json",
    "content": "{\n  \"404Msg\": \"Página no encontrada.\",\n  \"A new version ({versionText}) is available.\": \"Una nueva version ({versionText}) está disponible\",\n  \"Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh\": \"Agrega {0} al archivo k8s.yml<0/><1/>Luego ejecuta {inject} para inyectarlo en la malla de servicios\",\n  \"All namespaces have a {productName} install.\": \"Todos los namespaces tienen una instalación de {productName}.\",\n  \"Control plane\": \"Plano de control\",\n  \"Control plane components\": \"Componentes del plano de control\",\n  \"Current {cmdNameDisplay} query\": \"Consulta {cmdNameDisplay} actual\",\n  \"Data plane proxies\": \"Proxies del plano de datos\",\n  \"LinkerdIsUpToDateMsg\": \"Linkerd está actualizado.\",\n  \"New service profile\": \"Nuevo service profile\",\n  \"NoDataToDisplayMsg\": \"No hay datos que mostrar\",\n  \"Pod status: {0}\": \"Estado pod: {0}\",\n  \"Running\": \"Ejecutando\",\n  \"Service mesh details\": \"Detalles de la malla de servicios\",\n  \"Update Now\": \"Actualizar Ahora\",\n  \"Version check failed{0}.\": \"Error al comprobar la versión{0}\",\n  \"buttonCancel\": \"Cancelar\",\n  \"buttonClose\": \"Cerrar\",\n  \"buttonCreateServiceProfile\": \"Crear Service Profile\",\n  \"buttonDownload\": \"Descargar\",\n  \"buttonLearnMore\": \"Aprende Más\",\n  \"buttonReRunCheck\": \"Volver a Check\",\n  \"buttonReset\": \"Restablecer\",\n  \"buttonRunLinkerdCheck\": \"Ejecutar Linkerd Check\",\n  \"buttonStart\": \"Empezar\",\n  \"buttonStop\": \"Terminar\",\n  \"columnTitleAlive\": \"Vivo\",\n  \"columnTitleApexService\": \"Servicio Apice\",\n  \"columnTitleBest\": \"Mejor\",\n  \"columnTitleClusterName\": \"Nombre del Clúster\",\n  \"columnTitleCount\": \"Total\",\n  \"columnTitleDeployment\": \"Deployment\",\n  \"columnTitleDestination\": \"Destino\",\n  \"columnTitleDirection\": \"Dirección\",\n  \"columnTitleFrom\": \"DESDE\",\n  \"columnTitleGRPCStatus\": \"Estado GRPC\",\n  \"columnTitleGrafana\": \"Grafana\",\n  \"columnTitleHTTPStatus\": \"Estado HTTP\",\n  \"columnTitleIdentity\": \"Identidad\",\n  \"columnTitleJaeger\": \"Jaeger\",\n  \"columnTitleLast\": \"Último\",\n  \"columnTitleLatency\": \"Latencia\",\n  \"columnTitleLeafService\": \"Servicio Hoja\",\n  \"columnTitleMeshed\": \"En la malla de servicios\",\n  \"columnTitleMeshedPods\": \"Pods en la malla de servicios\",\n  \"columnTitleMeshedStatus\": \"Estado de la malla de servicios\",\n  \"columnTitleMethod\": \"Método\",\n  \"columnTitleName\": \"Nombre\",\n  \"columnTitleNamespace\": \"Namespace\",\n  \"columnTitleNoTraffic\": \"No Hay Tráfico\",\n  \"columnTitleOpenConnections\": \"Conexiones\",\n  \"columnTitleP50Latency\": \"Latencia P50\",\n  \"columnTitleP95Latency\": \"Latencia P95\",\n  \"columnTitleP99Latency\": \"Latencia P99\",\n  \"columnTitlePairedServices\": \"Servicios Emparejados\",\n  \"columnTitlePath\": \"Ruta\",\n  \"columnTitlePodStatus\": \"Estado del Pod\",\n  \"columnTitlePods\": \"Pods\",\n  \"columnTitleRPS\": \"PPS\",\n  \"columnTitleReadRate\": \"Lectura Bytes / seg\",\n  \"columnTitleRoute\": \"Ruta\",\n  \"columnTitleSecured\": \"Asegurado\",\n  \"columnTitleService\": \"Servicio\",\n  \"columnTitleSource\": \"Fuente\",\n  \"columnTitleSuccessRate\": \"Tasa de éxito\",\n  \"columnTitleTap\": \"Tap\",\n  \"columnTitleTo\": \"HACIA\",\n  \"columnTitleUnmeshed\": \"Ausente en la malla de servicios\",\n  \"columnTitleValue\": \"Valor\",\n  \"columnTitleWeight\": \"Peso\",\n  \"columnTitleWorst\": \"Peor\",\n  \"columnTitleWriteRate\": \"Escritura Bytes / seg\",\n  \"componentsMsg\": \"Componentes\",\n  \"connectResourceMsg {resource}\": \"Conecta tu primer {resource}\",\n  \"controllerInstalledMsg\": \"Controller instalado correctamente\",\n  \"createNewProfileMsg\": \"También puede crear un nuevo perfil\",\n  \"formAuthority\": \"Authority\",\n  \"formAuthorityHelpText\": \"Mostrar solicitudes con este :authority\",\n  \"formCreateServiceProfileHelpText\": \"Para crear un service profile, descargue un perfil y luego aplíquelo con `kubectl apply`.\",\n  \"formGRPCStatus\": \"Estado GRPC\",\n  \"formHTTPMethod\": \"Método HTTP\",\n  \"formHTTPMethodHelpText\": \"Mostrar solicitudes con este método HTTP\",\n  \"formHTTPStatus\": \"Estado HTTP\",\n  \"formHeaders\": \"Encabezados\",\n  \"formHideFilters\": \"Ocultar filtros\",\n  \"formLatency\": \"Latencia\",\n  \"formMaxRPS\": \"Max PPS\",\n  \"formMaxRPSHelpText {defaultMaxRps}\": \"Máximo de solicitudes por segundo para tap. Por defecto {defaultMaxRps}\",\n  \"formMethod\": \"Método\",\n  \"formNamespace\": \"Namespace\",\n  \"formNamespaceErrorText\": \"El namespace debe constar de caracteres alfanuméricos en minúscula o '-' y debe comenzar y terminar con un carácter alfanumérico\",\n  \"formNamespaceHelpText\": \"Namespace para consultar\",\n  \"formNoNamedRouteTrafficFound\": \"No se encontró tráfico de ruta con nombre. Esto podría deberse a que el servicio no está recibiendo tráfico o porque no hay ningún service profile configurado. ¿El servicio tiene un service profile?\",\n  \"formPath\": \"Ruta\",\n  \"formPathHelpText\": \"Mostrar solicitudes con rutas que comienzan con este prefijo\",\n  \"formResource\": \"Recurso\",\n  \"formResourceHelpText\": \"Recurso para consultar\",\n  \"formResponseLengthB\": \"Longitud De Respuesta (B)\",\n  \"formScheme\": \"Esquema\",\n  \"formSchemeHelpText\": \"Mostrar solicitudes con este esquema\",\n  \"formServiceNameErrorText\": \"El nombre del servicio debe constar de caracteres alfanuméricos en minúscula o '-' comenzar con un carácter alfabético y terminar con un carácter alfanumérico\",\n  \"formShowFilters\": \"Mostrar más filtros\",\n  \"formToNamespace\": \"Al Namespace\",\n  \"formToNamespaceHelpText\": \"Namespace del recurso de destino\",\n  \"formToResource\": \"Al Recurso\",\n  \"formToResourceHelpText\": \"Recurso destino\",\n  \"installMulticlusterMsg\": \"Para ver las estadísticas de la puerta de enlace para su malla, instale la extensión de multicluster linkerd ejecutando\",\n  \"labelError\": \"Error\",\n  \"labelSuccess\": \"Éxito\",\n  \"menuItemCommunity\": \"Comunidad\",\n  \"menuItemControlPlane\": \"Plano de Control\",\n  \"menuItemCronJobs\": \"Cron Jobs\",\n  \"menuItemDaemonSets\": \"Daemon Sets\",\n  \"menuItemDeployments\": \"Deployments\",\n  \"menuItemDocumentation\": \"Documentación\",\n  \"menuItemExtension\": \"Extensions\",\n  \"menuItemGateway\": \"Gateway\",\n  \"menuItemGitHub\": \"GitHub\",\n  \"menuItemJobs\": \"Jobs\",\n  \"menuItemMailingList\": \"Lista de Correo\",\n  \"menuItemNamespaces\": \"Namespaces\",\n  \"menuItemPods\": \"Pods\",\n  \"menuItemReplicaSets\": \"Replica Sets\",\n  \"menuItemReplicationControllers\": \"Replication Controllers\",\n  \"menuItemRoutes\": \"Rutas\",\n  \"menuItemServices\": \"Services\",\n  \"menuItemSlack\": \"Slack\",\n  \"menuItemStatefulSets\": \"Stateful Sets\",\n  \"menuItemTap\": \"Tap\",\n  \"menuItemTop\": \"Top\",\n  \"noNamespacesDetectedMsg\": \"No se han encontrado namespaces.\",\n  \"noResourcesDetectedMsg\": \"No se han encontrado recursos.\",\n  \"podsAreInitializingMsg\": \"Los pods se están inicializando\",\n  \"serviceMeshInstalledMsg\": \"¡La malla de servicios se instaló correctamente!\",\n  \"sidebarHeadingCluster\": \"Clúster\",\n  \"sidebarHeadingTools\": \"Herramientas\",\n  \"sidebarHeadingWorkloads\": \"Cargas de trabajo\",\n  \"statusExplanationGood\": \"está en funcionamiento\",\n  \"statusExplanationInMesh\": \"Añadido a la malla\",\n  \"statusExplanationNotInMesh\": \"No en malla\",\n  \"statusExplanationNotStarted\": \"no se ha iniciado\",\n  \"tabLiveCalls\": \"Llamadas En Vivo\",\n  \"tabRouteMetrics\": \"Métricas De Ruta\",\n  \"tableTitleEdgesEmpty\": \"Bordes\",\n  \"tableTitleEdgesWithIdentity {identity}\": \"Bordes (Identidad: {identity})\",\n  \"tableTitleGateways\": \"Gateways\",\n  \"tableTitleHTTPMetrics\": \"Métricas HTTP\",\n  \"tableTitleInbound\": \"Entrada\",\n  \"tableTitleLeafServices\": \"Servicios Hoja\",\n  \"tableTitleOutbound\": \"Salida\",\n  \"tableTitlePods\": \"Pods\",\n  \"tableTitleRequestDetails\": \"Detalle Solicitudes\",\n  \"tableTitleRequestInit\": \"Solicitud Inicial\",\n  \"tableTitleResponseEnd\": \"Fin De Respuesta\",\n  \"tableTitleResponseInit\": \"Respuesta Inicial\",\n  \"tableTitleTCP\": \"TCP\",\n  \"tableTitleTCPMetrics\": \"Métricas TCP\",\n  \"Pods under the resource {resource} in the {namespace} namespace are missing tap configurations (restart these pods to enable tap)\":\"A los pods debajo del recurso {resource} en el namespace {namespace} les hace falta la configuración de tap (reinicie estos pods para habilitar tap)\",\n  \"tooltipInbound\": \"ENTRADA\",\n  \"tooltipOutbound\": \"SALIDA\",\n  \"unspecifiedResourcesMsg\": \"uno o más recursos\",\n  \"{numResources, plural, zero {{resource}} one {{resource}} other {{resource}}}\": \"{numResources, plural, zero {No {resource}s detectados} one {# {resource} detectado} other {# {resource}s detectados}}\",\n  \"{numUnadded, plural, one {# namespace has no meshed resources} other {# namespaces have no meshed resources}}\": \"{numUnadded, plural, one {# namespace no tiene recursos en la malla de servicios.} other {# namespaces no tienen recursos en la malla de servicios.}}\",\n  \"{productName} namespace\": \"Namespace de {productName}\",\n  \"{productName} version\": \"Versión de {productName}\"\n}\n"
  },
  {
    "path": "web/app/package.json",
    "content": "{\n  \"name\": \"web\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@babel/eslint-plugin\": \"7.27.1\",\n    \"@fortawesome/fontawesome-svg-core\": \"7.2.0\",\n    \"@fortawesome/free-regular-svg-icons\": \"7.2.0\",\n    \"@fortawesome/free-solid-svg-icons\": \"7.2.0\",\n    \"@fortawesome/react-fontawesome\": \"0.2.6\",\n    \"@lingui/core\": \"^3.17.1\",\n    \"@lingui/macro\": \"3.17.2\",\n    \"@lingui/react\": \"3.17.2\",\n    \"@material-ui/core\": \"4.12.4\",\n    \"@material-ui/icons\": \"4.11.3\",\n    \"@material-ui/lab\": \"^4.0.0-alpha.61\",\n    \"classnames\": \"2.5.1\",\n    \"core-js\": \"^3.48.0\",\n    \"css-mediaquery\": \"^0.1.2\",\n    \"d3-drag\": \"3.0.0\",\n    \"d3-force\": \"3.0.0\",\n    \"d3-format\": \"3.1.2\",\n    \"d3-selection\": \"3.0.0\",\n    \"date-fns\": \"4.1.0\",\n    \"jest-progress-bar-reporter\": \"^1.0.25\",\n    \"locales-detector\": \"3.0.2\",\n    \"lodash\": \"4.17.23\",\n    \"make-plural\": \"^7.4.0\",\n    \"path\": \"0.12.7\",\n    \"prop-types\": \"15.8.1\",\n    \"query-string\": \"^9.3.1\",\n    \"react\": \"16.14.0\",\n    \"react-dom\": \"16.14.0\",\n    \"react-linkify\": \"1.0.0-alpha\",\n    \"react-page-visibility\": \"^7.0.0\",\n    \"react-router-dom\": \"5.3.4\",\n    \"react-router-prop-types\": \"1.0.5\",\n    \"regenerator-runtime\": \"^0.14.1\",\n    \"use-query-params\": \"2.2.2\",\n    \"whatwg-fetch\": \"3.6.20\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.29.0\",\n    \"@babel/eslint-parser\": \"^7.28.6\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.17.12\",\n    \"@babel/preset-env\": \"^7.29.0\",\n    \"@babel/preset-react\": \"^7.28.5\",\n    \"@babel/runtime\": \"^7.28.6\",\n    \"@lingui/cli\": \"3.17.2\",\n    \"babel-core\": \"^7.0.0-bridge.0\",\n    \"babel-jest\": \"^30.3.0\",\n    \"babel-loader\": \"^10.1.1\",\n    \"babel-plugin-import\": \"^1.13.8\",\n    \"babel-plugin-macros\": \"^3.1.0\",\n    \"babel-plugin-transform-react-remove-prop-types\": \"0.4.24\",\n    \"chai\": \"4.5.0\",\n    \"clean-webpack-plugin\": \"4.0.0\",\n    \"css-loader\": \"^7.1.4\",\n    \"enzyme\": \"3.11.0\",\n    \"enzyme-adapter-react-16\": \"^1.15.8\",\n    \"eslint\": \"^8.57.1\",\n    \"eslint-config-airbnb\": \"^19.0.4\",\n    \"eslint-plugin-import\": \"^2.32.0\",\n    \"eslint-plugin-jsx-a11y\": \"^6.10.2\",\n    \"eslint-plugin-promise\": \"^7.2.1\",\n    \"eslint-plugin-react\": \"^7.37.5\",\n    \"eslint-plugin-react-hooks\": \"^4.6.2\",\n    \"eslint-webpack-plugin\": \"^4.2.0\",\n    \"file-loader\": \"^6.2.0\",\n    \"history\": \"5.3.0\",\n    \"html-webpack-plugin\": \"^5.6.6\",\n    \"jest\": \"^30.3.0\",\n    \"jest-environment-jsdom\": \"^30.2.0\",\n    \"lodash-webpack-plugin\": \"^0.11.6\",\n    \"react-test-renderer\": \"16.14.0\",\n    \"sinon\": \"21.0.2\",\n    \"sinon-stub-promise\": \"4.0.0\",\n    \"style-loader\": \"^4.0.0\",\n    \"url-loader\": \"^4.1.1\",\n    \"webpack\": \"^5.105.4\",\n    \"webpack-bundle-analyzer\": \"5.2.0\",\n    \"webpack-cli\": \"6.0.1\",\n    \"webpack-dev-server\": \"5.2.3\"\n  },\n  \"resolutions\": {\n    \"@lingui/**/**/minimist\": \">=1.2.5\",\n    \"moment\": \"2.29.4\",\n    \"multicast-dns\": \"7.2.3\",\n    \"webpack-dev-server/selfsigned/node-forge\": \">=0.10.0\"\n  },\n  \"jest\": {\n    \"testEnvironment\": \"jsdom\",\n    \"setupFilesAfterEnv\": [\n      \"<rootDir>/setupTests.js\"\n    ],\n    \"transformIgnorePatterns\": [\n      \"/node_modules/(?!(d3-.*|sinon))\"\n    ],\n    \"moduleNameMapper\": {\n      \"\\\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$\": \"<rootDir>/__mocks__/fileMock.js\",\n      \"\\\\.(css|less)$\": \"<rootDir>/__mocks__/styleMock.js\"\n    }\n  },\n  \"lingui\": {\n    \"catalogs\": [\n      {\n        \"path\": \"<rootDir>/js/locales/{locale}/messages\"\n      }\n    ],\n    \"locales\": [\n      \"en\",\n      \"es\"\n    ],\n    \"format\": \"minimal\"\n  }\n}\n"
  },
  {
    "path": "web/app/setupTests.js",
    "content": "import 'core-js/stable';\nimport 'regenerator-runtime/runtime';\nimport Enzyme from 'enzyme';\nimport Adapter from 'enzyme-adapter-react-16';\n\nEnzyme.configure({ adapter: new Adapter() });\n"
  },
  {
    "path": "web/app/test/fixtures/allRollup.json",
    "content": "{\n  \"ok\": {\n    \"statTables\": [\n      {\n        \"podGroup\": {\n          \"rows\": [\n            {\n              \"meshedPodCount\": \"1\",\n              \"resource\": {\n                \"name\": \"voting\",\n                \"namespace\": \"emojivoto\",\n                \"type\": \"deployment\"\n              },\n              \"stats\": {\n                \"failureCount\": \"15\",\n                \"latencyMsP50\": \"1\",\n                \"latencyMsP95\": \"2\",\n                \"latencyMsP99\": \"7\",\n                \"successCount\": \"135\"\n              },\n              \"timeWindow\": \"1m\",\n              \"runningPodCount\": \"1\",\n              \"failedPodCount\": null\n            }\n          ]\n        }\n      },\n      {\n        \"podGroup\": {\n          \"rows\": []\n        }\n      },\n      {\n        \"podGroup\": {\n          \"rows\": [\n            {\n              \"resource\": {\n                \"namespace\": \"shiny-product\",\n                \"type\": \"pod\",\n                \"name\": \"web-5f97578cf6-597sr\"\n              },\n              \"timeWindow\": \"1m\",\n              \"meshedPodCount\": \"4\",\n              \"runningPodCount\": \"4\",\n              \"stats\": {\n                \"successCount\": \"6\",\n                \"failureCount\": \"0\",\n                \"latencyMsP50\": \"5\",\n                \"latencyMsP95\": \"9\",\n                \"latencyMsP99\": \"10\"\n              }\n            },\n            {\n              \"resource\": {\n                \"namespace\": \"shiny-product-other\",\n                \"type\": \"pod\",\n                \"name\": \"controller-795dd8df6-tjdt6\"\n              },\n              \"timeWindow\": \"1m\",\n              \"meshedPodCount\": \"4\",\n              \"runningPodCount\": \"4\",\n              \"stats\": {\n                \"successCount\": \"24\",\n                \"failureCount\": \"0\",\n                \"latencyMsP50\": \"7\",\n                \"latencyMsP95\": \"18\",\n                \"latencyMsP99\": \"20\"\n              }\n            },\n            {\n              \"resource\": {\n                \"namespace\": \"not-shiny-product\",\n                \"type\": \"pod\",\n                \"name\": \"prometheus-595785446-r7rlq\"\n              },\n              \"timeWindow\": \"1m\",\n              \"meshedPodCount\": \"4\",\n              \"runningPodCount\": \"4\",\n              \"stats\": null\n            },\n            {\n              \"resource\": {\n                \"namespace\": \"shiny-product-other\",\n                \"type\": \"pod\",\n                \"name\": \"grafana-78cdb656bf-v8lsj\"\n              },\n              \"timeWindow\": \"1m\",\n              \"meshedPodCount\": \"4\",\n              \"runningPodCount\": \"4\",\n              \"stats\": null\n            }\n          ]\n        }\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "web/app/test/fixtures/deployRollup.json",
    "content": "{\n  \"ok\": {\n    \"statTables\": [\n      {\n        \"podGroup\": {\n          \"rows\": [\n            {\n              \"meshedPodCount\": \"1\",\n              \"resource\": {\n                \"name\": \"voting\",\n                \"namespace\": \"emojivoto\",\n                \"type\": \"deployment\"\n              },\n              \"stats\": {\n                \"failureCount\": \"15\",\n                \"latencyMsP50\": \"1\",\n                \"latencyMsP95\": \"2\",\n                \"latencyMsP99\": \"7\",\n                \"successCount\": \"135\"\n              },\n              \"tcpStats\": {\n                \"openConnections\": \"221\",\n                \"readBytesTotal\": \"4421\",\n                \"writeBytesTotal\": \"4421\"\n              },\n              \"timeWindow\": \"1m\",\n              \"runningPodCount\": \"1\",\n              \"failedPodCount\": null,\n              \"errorsByPod\": {}\n            }\n          ]\n        }\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "web/app/test/fixtures/emojivotoPods.json",
    "content": "[\n  {\n    \"ok\": {\n      \"statTables\": [\n        {\n          \"podGroup\": {\n            \"rows\": [\n              {\n                \"resource\": {\n                  \"namespace\": \"emojivoto\",\n                  \"type\": \"deployment\",\n                  \"name\": \"web\"\n                },\n                \"timeWindow\": \"1m\",\n                \"meshedPodCount\": \"1\",\n                \"runningPodCount\": \"1\",\n                \"failedPodCount\": \"0\",\n                \"stats\": {\n                  \"successCount\": \"118\",\n                  \"failureCount\": \"0\",\n                  \"latencyMsP50\": \"1\",\n                  \"latencyMsP95\": \"1\",\n                  \"latencyMsP99\": \"2\"\n                },\n                \"errorsByPod\": {}\n              }\n            ]\n          }\n        }\n      ]\n    }\n  },\n  {\n    \"ok\": {\n      \"statTables\": [\n        {\n          \"podGroup\": {\n            \"rows\": []\n          }\n        }\n      ]\n    }\n  },\n  {\n    \"ok\": {\n      \"statTables\": [\n        {\n          \"podGroup\": {\n            \"rows\": [\n              {\n                \"resource\": {\n                  \"namespace\": \"emojivoto\",\n                  \"type\": \"deployment\",\n                  \"name\": \"web\"\n                },\n                \"timeWindow\": \"1m\",\n                \"meshedPodCount\": \"1\",\n                \"runningPodCount\": \"1\",\n                \"failedPodCount\": \"0\",\n                \"stats\": {\n                  \"successCount\": \"47\",\n                  \"failureCount\": \"12\",\n                  \"latencyMsP50\": \"1\",\n                  \"latencyMsP95\": \"9\",\n                  \"latencyMsP99\": \"10\"\n                },\n                \"errorsByPod\": {}\n              }\n            ]\n          }\n        }\n      ]\n    }\n  },\n  {\n    \"ok\": {\n      \"statTables\": [\n        {\n          \"podGroup\": {\n            \"rows\": [\n              {\n                \"resource\": {\n                  \"namespace\": \"emojivoto\",\n                  \"type\": \"deployment\",\n                  \"name\": \"vote-bot\"\n                },\n                \"timeWindow\": \"1m\",\n                \"meshedPodCount\": \"1\",\n                \"runningPodCount\": \"1\",\n                \"failedPodCount\": \"0\",\n                \"stats\": {\n                  \"successCount\": \"108\",\n                  \"failureCount\": \"12\",\n                  \"latencyMsP50\": \"2\",\n                  \"latencyMsP95\": \"3\",\n                  \"latencyMsP99\": \"4\"\n                },\n                \"errorsByPod\": {}\n              }\n            ]\n          }\n        }\n      ]\n    }\n  }\n]\n"
  },
  {
    "path": "web/app/test/fixtures/gateway.json",
    "content": "{\n    \"ok\": {\n        \"gatewaysTable\": {\n            \"rows\": [\n                {\n                    \"namespace\": \"test_namespace\",\n                    \"name\": \"test_gateway\",\n                    \"clusterName\": \"multi_cluster\",\n                    \"pairedServices\": \"0\",\n                    \"alive\": true,\n                    \"latencyMsP50\": \"0\",\n                    \"latencyMsP95\": \"0\",\n                    \"latencyMsP99\": \"0\"\n                },\n                {\n                    \"namespace\": \"test_namespace\",\n                    \"name\": \"default_gateway\",\n                    \"clusterName\": \"multi_cluster\",\n                    \"pairedServices\": \"0\",\n                    \"alive\": true,\n                    \"latencyMsP50\": \"0\",\n                    \"latencyMsP95\": \"0\",\n                    \"latencyMsP99\": \"0\"\n                }\n            ]\n        }\n    }\n}"
  },
  {
    "path": "web/app/test/fixtures/multiDeployRollup.json",
    "content": "{\n  \"ok\": {\n    \"statTables\": [\n      {\n        \"podGroup\": {\n          \"rows\": [\n            {\n              \"meshedPodCount\": \"1\",\n              \"resource\": {\n                \"name\": \"voting\",\n                \"namespace\": \"emojivoto\",\n                \"type\": \"deployment\"\n              },\n              \"stats\": {\n                \"failureCount\": \"10\",\n                \"latencyMsP50\": \"1\",\n                \"latencyMsP95\": \"1\",\n                \"latencyMsP99\": \"2\",\n                \"successCount\": \"56\"\n              },\n              \"timeWindow\": \"1m\",\n              \"runningPodCount\": \"1\"\n            },\n            {\n              \"meshedPodCount\": \"1\",\n              \"resource\": {\n                \"name\": \"emoji\",\n                \"namespace\": \"emojivoto\",\n                \"type\": \"deployment\"\n              },\n              \"stats\": {\n                \"failureCount\": \"0\",\n                \"latencyMsP50\": \"1\",\n                \"latencyMsP95\": \"1\",\n                \"latencyMsP99\": \"2\",\n                \"successCount\": \"124\"\n              },\n              \"timeWindow\": \"1m\",\n              \"runningPodCount\": \"1\"\n            },\n            {\n              \"meshedPodCount\": \"1\",\n              \"resource\": {\n                \"name\": \"vote-bot\",\n                \"namespace\": \"emojivoto\",\n                \"type\": \"deployment\"\n              },\n              \"stats\": {\n                \"failureCount\": \"0\",\n                \"latencyMsP50\": \"1\",\n                \"latencyMsP95\": \"1\",\n                \"latencyMsP99\": \"1\",\n                \"successCount\": \"6\"\n              },\n              \"timeWindow\": \"1m\",\n              \"runningPodCount\": \"1\"\n            },\n            {\n              \"meshedPodCount\": \"1\",\n              \"resource\": {\n                \"name\": \"web\",\n                \"namespace\": \"emojivoto\",\n                \"type\": \"deployment\"\n              },\n              \"stats\": {\n                \"failureCount\": \"11\",\n                \"latencyMsP50\": \"3\",\n                \"latencyMsP95\": \"10\",\n                \"latencyMsP99\": \"18\",\n                \"successCount\": \"113\"\n              },\n              \"timeWindow\": \"1m\",\n              \"runningPodCount\": \"1\"\n            }\n          ]\n        }\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "web/app/test/fixtures/namespaces.json",
    "content": "{\n  \"ok\": {\n    \"statTables\": [\n      {\n        \"podGroup\": {\n          \"rows\": [\n            {\n              \"resource\": {\n                \"namespace\": \"\",\n                \"type\": \"namespace\",\n                \"name\": \"shiny-product\"\n              },\n              \"timeWindow\": \"1m\",\n              \"meshedPodCount\": \"4\",\n              \"runningPodCount\": \"4\",\n              \"stats\": {\n                \"successCount\": \"30\",\n                \"failureCount\": \"0\",\n                \"latencyMsP50\": \"5\",\n                \"latencyMsP95\": \"10\",\n                \"latencyMsP99\": \"10\"\n              }\n            },\n            {\n              \"resource\": {\n                \"namespace\": \"\",\n                \"type\": \"namespace\",\n                \"name\": \"shiny-product-other\"\n              },\n              \"timeWindow\": \"1m\",\n              \"meshedPodCount\": \"4\",\n              \"runningPodCount\": \"4\",\n              \"stats\": {\n                \"successCount\": \"30\",\n                \"failureCount\": \"0\",\n                \"latencyMsP50\": \"5\",\n                \"latencyMsP95\": \"10\",\n                \"latencyMsP99\": \"10\"\n              }\n            },\n            {\n              \"resource\": {\n                \"namespace\": \"\",\n                \"type\": \"namespace\",\n                \"name\": \"shiny-product-unmeshed\"\n              },\n              \"timeWindow\": \"1m\",\n              \"meshedPodCount\": \"0\",\n              \"runningPodCount\": \"4\",\n              \"stats\": {\n                \"successCount\": \"30\",\n                \"failureCount\": \"0\",\n                \"latencyMsP50\": \"5\",\n                \"latencyMsP95\": \"10\",\n                \"latencyMsP99\": \"10\"\n              }\n            },\n            {\n              \"resource\": {\n                \"namespace\": \"\",\n                \"type\": \"namespace\",\n                \"name\": \"kube-system\"\n              },\n              \"timeWindow\": \"1m\",\n              \"meshedPodCount\": \"0\",\n              \"runningPodCount\": \"9\",\n              \"stats\": null\n            },\n            {\n              \"resource\": {\n                \"namespace\": \"\",\n                \"type\": \"namespace\",\n                \"name\": \"default\"\n              },\n              \"timeWindow\": \"1m\",\n              \"meshedPodCount\": \"0\",\n              \"runningPodCount\": \"0\",\n              \"stats\": null\n            },\n            {\n              \"resource\": {\n                \"namespace\": \"\",\n                \"type\": \"namespace\",\n                \"name\": \"kube-public\"\n              },\n              \"timeWindow\": \"1m\",\n              \"meshedPodCount\": \"0\",\n              \"runningPodCount\": \"0\",\n              \"stats\": null\n            }\n          ]\n        }\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "web/app/test/fixtures/podRollup.json",
    "content": "{\n  \"ok\": {\n    \"statTables\": [\n      {\n        \"podGroup\": {\n          \"rows\": [\n            {\n              \"resource\": {\n                \"namespace\": \"shiny-product\",\n                \"type\": \"pod\",\n                \"name\": \"web-5f97578cf6-597sr\"\n              },\n              \"timeWindow\": \"1m\",\n              \"meshedPodCount\": \"4\",\n              \"runningPodCount\": \"4\",\n              \"stats\": {\n                \"successCount\": \"6\",\n                \"failureCount\": \"0\",\n                \"latencyMsP50\": \"5\",\n                \"latencyMsP95\": \"9\",\n                \"latencyMsP99\": \"10\"\n              }\n            },\n            {\n              \"resource\": {\n                \"namespace\": \"shiny-product\",\n                \"type\": \"pod\",\n                \"name\": \"controller-795dd8df6-tjdt6\"\n              },\n              \"timeWindow\": \"1m\",\n              \"meshedPodCount\": \"4\",\n              \"runningPodCount\": \"4\",\n              \"stats\": {\n                \"successCount\": \"24\",\n                \"failureCount\": \"0\",\n                \"latencyMsP50\": \"7\",\n                \"latencyMsP95\": \"18\",\n                \"latencyMsP99\": \"20\"\n              }\n            },\n            {\n              \"resource\": {\n                \"namespace\": \"shiny-product\",\n                \"type\": \"pod\",\n                \"name\": \"prometheus-595785446-r7rlq\"\n              },\n              \"timeWindow\": \"1m\",\n              \"meshedPodCount\": \"4\",\n              \"runningPodCount\": \"4\",\n              \"stats\": null\n            },\n            {\n              \"resource\": {\n                \"namespace\": \"shiny-product\",\n                \"type\": \"pod\",\n                \"name\": \"grafana-78cdb656bf-v8lsj\"\n              },\n              \"timeWindow\": \"1m\",\n              \"meshedPodCount\": \"4\",\n              \"runningPodCount\": \"4\",\n              \"stats\": null\n            }\n          ]\n        }\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "web/app/test/testHelpers.jsx",
    "content": "import _merge from 'lodash/merge';\nimport ApiHelpers from '../js/components/util/ApiHelpers.jsx';\nimport { createMemoryHistory } from 'history';\nimport React from 'react';\nimport { Route, Router } from 'react-router-dom';\nimport { I18nProvider } from '@lingui/react';\nimport { i18n } from '@lingui/core';\nimport { en } from 'make-plural/plurals'\nimport catalogEn from './../js/locales/en/messages.js';\n\nconst componentDefaultProps = {\n  api: ApiHelpers(''),\n  controllerNamespace: 'shiny-controller-ns',\n  productName: 'ShinyProductName',\n  releaseVersion: ''\n};\n\ni18n.loadLocaleData('en', { plurals: en })\ni18n.loadLocaleData('en');\ni18n.load('en', catalogEn.messages);\ni18n.activate('en');\n\nexport function routerWrap(Component, extraProps={}, route=\"/\", currentLoc=\"/\") {\n  const createElement = (ComponentToWrap, props) => (\n    <ComponentToWrap {...(_merge({}, componentDefaultProps, props, extraProps))} />\n  );\n  return (\n    <Router history={createMemoryHistory(currentLoc)} createElement={createElement}>\n      <Route path={route} render={props => createElement(Component, props)} />\n    </Router>\n  );\n}\n\nexport function i18nWrap(Component) {\n  return (\n    <I18nProvider i18n={i18n}>\n      {Component}\n    </I18nProvider>\n  );\n}\n\nexport function i18nAndRouterWrap(component, props) { return i18nWrap(routerWrap(component, props))};\n"
  },
  {
    "path": "web/app/webpack.config.js",
    "content": "/* global require, module, __dirname */\n\nconst path = require('path');\nconst { CleanWebpackPlugin } = require('clean-webpack-plugin');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst LodashModuleReplacementPlugin = require('lodash-webpack-plugin');\nconst ESLintPlugin = require('eslint-webpack-plugin');\n\n// uncomment here and in plugins to analyze webpack bundle size\n// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;\n\nmodule.exports = {\n  mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',\n  entry: './js/index.js',\n  devServer: {\n    devMiddleware: {\n      writeToDisk: true\n    }\n  },\n  output: {\n    path: path.resolve(__dirname, 'dist'),\n    publicPath: 'dist/',\n    filename: '[name].[contenthash].js'\n  },\n  devtool: 'cheap-module-source-map',\n  externals: {\n    cheerio: 'window',\n    'react/addons': 'react',\n    'react/lib/ExecutionEnvironment': 'react',\n    'react/lib/ReactContext': 'react',\n    'react-addons-test-utils': 'react-dom',\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.jsx?$/,\n        exclude: /node_modules/,\n        use: ['babel-loader']\n      },\n      {\n        test: /\\.css$/,\n        use: [\n          'style-loader',\n          { loader: 'css-loader', options: { importLoaders: 1 } },\n        ]\n      },\n      {\n        test: /\\.(png|jpg|gif|eot|svg|ttf|woff|woff2)$/,\n        use: [\n          {\n            loader: 'file-loader',\n            options: {\n              name: 'img/[name].[ext]'\n            }\n          }\n        ]\n      }\n    ]\n  },\n  plugins: [\n    // new BundleAnalyzerPlugin(), // uncomment to analyze bundle size\n    new ESLintPlugin({\n      extensions: ['.jsx', '.js'],\n      fix: process.env.NODE_ENV === 'development',\n      emitWarning: process.env.NODE_ENV === 'development',\n      files: ['js', 'test']\n    }),\n    new CleanWebpackPlugin({\n      protectWebpackAssets: false\n    }),\n    new LodashModuleReplacementPlugin({\n      // 'chain': true,\n      'collections': true,\n      'paths': true,\n      'shorthands': true\n    }),\n    // compile the bundle with hashed filename into index_bundle.js\n    new HtmlWebpackPlugin({\n      cache: false,\n      inject: false,\n      filename: 'index_bundle.js',\n      template: 'index_bundle.js.lodash.tmpl',\n      // this is important! Production builds minimize the template, causing\n      // the comment on the first line to be for the entire file and then\n      // nothing loads.\n      minify: false\n    })\n  ],\n};\n"
  },
  {
    "path": "web/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"regexp\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/linkerd/linkerd2/pkg/config\"\n\n\t\"github.com/linkerd/linkerd2/pkg/admin\"\n\t\"github.com/linkerd/linkerd2/pkg/charts/linkerd2\"\n\t\"github.com/linkerd/linkerd2/pkg/flags\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/trace\"\n\t\"github.com/linkerd/linkerd2/viz/metrics-api/client\"\n\t\"github.com/linkerd/linkerd2/web/srv\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nfunc main() {\n\tcmd := flag.NewFlagSet(\"public-api\", flag.ExitOnError)\n\n\taddr := cmd.String(\"addr\", \":8084\", \"address to serve on\")\n\tmetricsAddr := cmd.String(\"metrics-addr\", \":9994\", \"address to serve scrapable metrics on\")\n\tvizAPIAddr := cmd.String(\"linkerd-metrics-api-addr\", \"127.0.0.1:8085\", \"address of the linkerd-metrics-api service\")\n\tgrafanaAddr := cmd.String(\"grafana-addr\", \"\", \"address of the linkerd-grafana service\")\n\tgrafanaExternalAddr := cmd.String(\"grafana-external-addr\", \"\", \"address of the external grafana service\")\n\tgrafanaPrefix := cmd.String(\"grafana-prefix\", \"\", \"prefix for Grafana dashboard UID's\")\n\tjaegerAddr := cmd.String(\"jaeger-addr\", \"\", \"address of the jaeger service\")\n\ttemplateDir := cmd.String(\"template-dir\", \"templates\", \"directory to search for template files\")\n\tstaticDir := cmd.String(\"static-dir\", \"app/dist\", \"directory to search for static files\")\n\treload := cmd.Bool(\"reload\", true, \"reloading set to true or false\")\n\tcontrollerNamespace := cmd.String(\"controller-namespace\", \"linkerd\", \"namespace in which Linkerd is installed\")\n\tenforcedHost := cmd.String(\"enforced-host\", \"\", \"regexp describing the allowed values for the Host header; protects from DNS-rebinding attacks\")\n\tkubeConfigPath := cmd.String(\"kubeconfig\", \"\", \"path to kube config\")\n\tclusterDomain := cmd.String(\"cluster-domain\", \"\", \"kubernetes cluster domain\")\n\tenablePprof := cmd.Bool(\"enable-pprof\", false, \"Enable pprof endpoints on the admin server\")\n\n\ttraceCollector := flags.AddTraceFlags(cmd)\n\n\tflags.ConfigureAndParse(cmd, os.Args[1:])\n\n\tready := false\n\tadminServer := admin.NewServer(*metricsAddr, *enablePprof, &ready)\n\n\tgo func() {\n\t\tlog.Infof(\"starting admin server on %s\", *metricsAddr)\n\t\tif err := adminServer.ListenAndServe(); err != nil {\n\t\t\tlog.Errorf(\"failed to start web admin server: %s\", err)\n\t\t}\n\t}()\n\n\tctx := context.Background()\n\n\t_, _, err := net.SplitHostPort(*vizAPIAddr) // Verify vizAPIAddr is of the form host:port.\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to parse metrics API server address: %s\", *vizAPIAddr)\n\t}\n\tclient, err := client.NewInternalClient(*vizAPIAddr)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to construct client for viz API server URL %s\", *vizAPIAddr)\n\t}\n\n\tif *clusterDomain == \"\" {\n\t\t*clusterDomain = \"cluster.local\"\n\t\tlog.Warnf(\"expected cluster domain through args (falling back to %s)\", *clusterDomain)\n\t}\n\n\tk8sAPI, err := k8s.NewAPI(*kubeConfigPath, \"\", \"\", []string{}, 0)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to construct Kubernetes API client: [%s]\", err)\n\t}\n\n\t// Setup health checker\n\tchecks := []healthcheck.CategoryID{\n\t\thealthcheck.KubernetesAPIChecks,\n\t\thealthcheck.KubernetesVersionChecks,\n\t\thealthcheck.LinkerdConfigChecks,\n\t\thealthcheck.LinkerdControlPlaneExistenceChecks,\n\t\thealthcheck.LinkerdVersionChecks,\n\t\thealthcheck.LinkerdControlPlaneVersionChecks,\n\t}\n\thc := healthcheck.NewHealthChecker(checks, &healthcheck.Options{\n\t\tControlPlaneNamespace: *controllerNamespace,\n\t\tKubeConfig:            *kubeConfigPath,\n\t})\n\n\tuuid, version := getUUIDAndVersion(ctx, k8sAPI, *controllerNamespace)\n\n\tstop := make(chan os.Signal, 1)\n\tsignal.Notify(stop, os.Interrupt, syscall.SIGTERM)\n\n\tif *traceCollector != \"\" {\n\t\tif err := trace.InitializeTracing(\"web\", *traceCollector); err != nil {\n\t\t\tlog.Warnf(\"failed to initialize tracing: %s\", err)\n\t\t}\n\t}\n\n\treHost, err := regexp.Compile(*enforcedHost)\n\tif err != nil {\n\t\tlog.Fatalf(\"invalid --enforced-host parameter: %s\", err)\n\t}\n\n\tserver := srv.NewServer(*addr, *grafanaAddr, *grafanaExternalAddr, *grafanaPrefix, *jaegerAddr, *templateDir, *staticDir, uuid, version,\n\t\t*controllerNamespace, *clusterDomain, *reload, reHost, client, k8sAPI, hc)\n\n\tgo func() {\n\t\tlog.Infof(\"starting HTTP server on %+v\", *addr)\n\t\tif err := server.ListenAndServe(); err != nil {\n\t\t\tlog.Errorf(\"failed to start web HTTP server: %s\", err)\n\t\t}\n\t}()\n\n\tready = true\n\n\t<-stop\n\n\tlog.Infof(\"shutting down HTTP server on %+v\", *addr)\n\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\tdefer cancel()\n\tserver.Shutdown(ctx)\n\tadminServer.Shutdown(ctx)\n}\n\nfunc getUUIDAndVersion(ctx context.Context, k8sAPI *k8s.KubernetesAPI, controllerNamespace string) (string, string) {\n\tvar uuid string\n\tvar version string\n\n\tcm, err := config.FetchLinkerdConfigMap(ctx, k8sAPI, controllerNamespace)\n\tif err != nil {\n\t\tlog.Errorf(\"Failed to fetch linkerd-config: %s\", err)\n\t} else {\n\t\tuuid = string(cm.GetUID())\n\n\t\tvalues, err := linkerd2.ValuesFromConfigMap(cm)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"failed to load values from linkerd-config: %s\", err)\n\t\t} else {\n\t\t\tversion = values.LinkerdVersion\n\t\t}\n\t}\n\n\treturn uuid, version\n}\n"
  },
  {
    "path": "web/srv/api_handlers.go",
    "content": "package srv\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/protohttp\"\n\tmetricsPb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\tvizUtil \"github.com/linkerd/linkerd2/viz/metrics-api/util\"\n\ttapPb \"github.com/linkerd/linkerd2/viz/tap/gen/tap\"\n\ttappkg \"github.com/linkerd/linkerd2/viz/tap/pkg\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\"google.golang.org/protobuf/encoding/protojson\"\n\t\"google.golang.org/protobuf/proto\"\n\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"sigs.k8s.io/yaml\"\n)\n\n// Control Frame payload size can be no bigger than 125 bytes. 2 bytes are\n// reserved for the status code when formatting the message.\nconst maxControlFrameMsgSize = 123\n\ntype (\n\tjsonError struct {\n\t\tError string `json:\"error\"`\n\t}\n)\n\nvar (\n\tdefaultResourceType = k8s.Deployment\n\tpbMarshaler         = protojson.MarshalOptions{EmitUnpopulated: true}\n\tmaxMessageSize      = 2048\n\twebsocketUpgrader   = websocket.Upgrader{\n\t\tReadBufferSize:  maxMessageSize,\n\t\tWriteBufferSize: maxMessageSize,\n\t\t// Only allows requests from the same host, to prevent CSRF attacks.\n\t\t// This is the default behavior in gorilla/websocket even if the\n\t\t// CheckOrigin field is not set, but we make it explicit here as a good\n\t\t// practice, avoiding relying on default behavior.\n\t\tCheckOrigin: checkSameOrigin,\n\t}\n\n\t// Checks whose description matches the following regexp won't be included\n\t// in the handleApiCheck output. In the context of the dashboard, some\n\t// checks like cli or kubectl versions ones may not be relevant.\n\t//\n\t// TODO(tegioz): use more reliable way to identify the checks that should\n\t// not be displayed in the dashboard (hint anchor is not unique).\n\texcludedChecksRE = regexp.MustCompile(`(?i)cli|(?i)kubectl`)\n)\n\nfunc renderJSONError(w http.ResponseWriter, err error, status int) {\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tlog.Error(err.Error())\n\trsp, _ := json.Marshal(jsonError{Error: err.Error()})\n\tw.WriteHeader(status)\n\tw.Write(rsp)\n}\n\nfunc renderJSON(w http.ResponseWriter, resp interface{}) {\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjsonResp, err := json.Marshal(resp)\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\tw.Write(jsonResp)\n}\n\nfunc renderJSONPb(w http.ResponseWriter, msg proto.Message) {\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson, err := pbMarshaler.Marshal(msg)\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusBadRequest)\n\t}\n\tw.Write(json)\n}\n\nfunc renderJSONBytes(w http.ResponseWriter, b []byte) {\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.Write(b)\n}\n\nfunc (h *handler) handleAPIVersion(w http.ResponseWriter, req *http.Request, p httprouter.Params) {\n\tresp := map[string]interface{}{\n\t\t\"version\": h.version,\n\t}\n\trenderJSON(w, resp)\n}\n\nfunc (h *handler) handleAPIPods(w http.ResponseWriter, req *http.Request, p httprouter.Params) {\n\tpods, err := h.apiClient.ListPods(req.Context(), &metricsPb.ListPodsRequest{\n\t\tSelector: &metricsPb.ResourceSelection{\n\t\t\tResource: &metricsPb.Resource{\n\t\t\t\tNamespace: req.FormValue(\"namespace\"),\n\t\t\t},\n\t\t},\n\t})\n\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\trenderJSONPb(w, pods)\n}\n\nfunc (h *handler) handleAPIServices(w http.ResponseWriter, req *http.Request, p httprouter.Params) {\n\tservices, err := h.apiClient.ListServices(req.Context(), &metricsPb.ListServicesRequest{\n\t\tNamespace: req.FormValue(\"namespace\"),\n\t})\n\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\trenderJSONPb(w, services)\n}\n\nfunc (h *handler) handleAPIStat(w http.ResponseWriter, req *http.Request, p httprouter.Params) {\n\t// Try to get stat summary from cache using the query as key\n\tcachedResultJSON, ok := h.statCache.Get(req.URL.RawQuery)\n\tif ok {\n\t\t// Cache hit, render cached json result\n\t\trenderJSONBytes(w, cachedResultJSON.([]byte))\n\t\treturn\n\t}\n\n\ttrueStr := fmt.Sprintf(\"%t\", true)\n\n\trequestParams := vizUtil.StatsSummaryRequestParams{\n\t\tStatsBaseRequestParams: vizUtil.StatsBaseRequestParams{\n\t\t\tTimeWindow:    req.FormValue(\"window\"),\n\t\t\tResourceName:  req.FormValue(\"resource_name\"),\n\t\t\tResourceType:  req.FormValue(\"resource_type\"),\n\t\t\tNamespace:     req.FormValue(\"namespace\"),\n\t\t\tAllNamespaces: req.FormValue(\"all_namespaces\") == trueStr,\n\t\t},\n\t\tToName:        req.FormValue(\"to_name\"),\n\t\tToType:        req.FormValue(\"to_type\"),\n\t\tToNamespace:   req.FormValue(\"to_namespace\"),\n\t\tFromName:      req.FormValue(\"from_name\"),\n\t\tFromType:      req.FormValue(\"from_type\"),\n\t\tFromNamespace: req.FormValue(\"from_namespace\"),\n\t\tSkipStats:     req.FormValue(\"skip_stats\") == trueStr,\n\t\tTCPStats:      req.FormValue(\"tcp_stats\") == trueStr,\n\t}\n\n\t// default to returning deployment stats\n\tif requestParams.ResourceType == \"\" {\n\t\trequestParams.ResourceType = defaultResourceType\n\t}\n\n\tstatRequest, err := vizUtil.BuildStatSummaryRequest(requestParams)\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tresult, err := h.apiClient.StatSummary(req.Context(), statRequest)\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Marshal result into json and cache it\n\tjson, err := pbMarshaler.Marshal(result)\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\tvar resultJSON bytes.Buffer\n\tresultJSON.Write(json)\n\n\th.statCache.SetDefault(req.URL.RawQuery, resultJSON.Bytes())\n\n\trenderJSONBytes(w, resultJSON.Bytes())\n}\n\nfunc (h *handler) handleAPITopRoutes(w http.ResponseWriter, req *http.Request, p httprouter.Params) {\n\trequestParams := vizUtil.TopRoutesRequestParams{\n\t\tStatsBaseRequestParams: vizUtil.StatsBaseRequestParams{\n\t\t\tTimeWindow:   req.FormValue(\"window\"),\n\t\t\tResourceName: req.FormValue(\"resource_name\"),\n\t\t\tResourceType: req.FormValue(\"resource_type\"),\n\t\t\tNamespace:    req.FormValue(\"namespace\"),\n\t\t},\n\t\tToName:      req.FormValue(\"to_name\"),\n\t\tToType:      req.FormValue(\"to_type\"),\n\t\tToNamespace: req.FormValue(\"to_namespace\"),\n\t}\n\n\ttopReq, err := vizUtil.BuildTopRoutesRequest(requestParams)\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tresult, err := h.apiClient.TopRoutes(req.Context(), topReq)\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\trenderJSONPb(w, result)\n}\n\n// Control frame payload size must be no longer than `maxControlFrameMsgSize`\n// bytes. In the case of an unexpected HTTP status code or unexpected error,\n// truncate the message after `maxControlFrameMsgSize` bytes so that the web\n// socket message is properly written.\nfunc validateControlFrameMsg(err error) string {\n\tlog.Debugf(\"tap error: %s\", err.Error())\n\n\tmsg := err.Error()\n\tif len(msg) > maxControlFrameMsgSize {\n\t\treturn msg[:maxControlFrameMsgSize]\n\t}\n\n\treturn msg\n}\n\nfunc websocketError(ws *websocket.Conn, wsError int, err error) {\n\tmsg := validateControlFrameMsg(err)\n\n\terr = ws.WriteControl(websocket.CloseMessage,\n\t\twebsocket.FormatCloseMessage(wsError, msg),\n\t\ttime.Time{})\n\tif err != nil {\n\t\tlog.Errorf(\"Unexpected websocket error: %s\", err)\n\t}\n}\n\nfunc (h *handler) handleAPITap(w http.ResponseWriter, req *http.Request, p httprouter.Params) {\n\tws, err := websocketUpgrader.Upgrade(w, req, nil)\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\tdefer ws.Close()\n\n\tmessageType, message, err := ws.ReadMessage()\n\tif err != nil {\n\t\twebsocketError(ws, websocket.CloseInternalServerErr, err)\n\t\treturn\n\t}\n\n\tif messageType != websocket.TextMessage {\n\t\twebsocketError(ws, websocket.CloseUnsupportedData, errors.New(\"messageType not supported\"))\n\t\treturn\n\t}\n\n\tvar requestParams tappkg.TapRequestParams\n\terr = json.Unmarshal(message, &requestParams)\n\tif err != nil {\n\t\twebsocketError(ws, websocket.CloseInternalServerErr, err)\n\t\treturn\n\t}\n\n\ttapReq, err := tappkg.BuildTapByResourceRequest(requestParams)\n\tif err != nil {\n\t\twebsocketError(ws, websocket.CloseInternalServerErr, err)\n\t\treturn\n\t}\n\n\tgo func() {\n\t\treader, body, err := tappkg.Reader(req.Context(), h.k8sAPI, tapReq)\n\t\tif err != nil {\n\t\t\t// If there was a [403] error when initiating a tap, close the\n\t\t\t// socket with `ClosePolicyViolation` status code so that the error\n\t\t\t// renders without the error prefix in the banner\n\t\t\tvar he protohttp.HTTPError\n\t\t\tif errors.Is(err, &he) && he.Code == http.StatusForbidden {\n\t\t\t\terr := fmt.Errorf(\"missing authorization, visit %s to remedy\", tappkg.TapRbacURL)\n\t\t\t\twebsocketError(ws, websocket.ClosePolicyViolation, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// All other errors from initiating a tap should close with\n\t\t\t// `CloseInternalServerErr` status code\n\t\t\twebsocketError(ws, websocket.CloseInternalServerErr, err)\n\t\t\treturn\n\t\t}\n\t\tdefer body.Close()\n\n\t\tfor {\n\t\t\tevent := tapPb.TapEvent{}\n\t\t\terr := protohttp.FromByteStreamToProtocolBuffers(reader, &event)\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\twebsocketError(ws, websocket.CloseInternalServerErr, err)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tjson, err := pbMarshaler.Marshal(&event)\n\t\t\tif err != nil {\n\t\t\t\twebsocketError(ws, websocket.CloseUnsupportedData, err)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tbuf := new(bytes.Buffer)\n\t\t\tbuf.Write(json)\n\n\t\t\tif err := ws.WriteMessage(websocket.TextMessage, buf.Bytes()); err != nil {\n\t\t\t\tif websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) {\n\t\t\t\t\tlog.Error(err)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}()\n\n\tfor {\n\t\t_, _, err := ws.ReadMessage()\n\t\tif err != nil {\n\t\t\tlog.Debugf(\"Received close frame: %v\", err)\n\t\t\tif websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) {\n\t\t\t\tlog.Errorf(\"Unexpected close error: %s\", err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (h *handler) handleAPIEdges(w http.ResponseWriter, req *http.Request, p httprouter.Params) {\n\trequestParams := vizUtil.EdgesRequestParams{\n\t\tNamespace:    req.FormValue(\"namespace\"),\n\t\tResourceType: req.FormValue(\"resource_type\"),\n\t}\n\n\tedgesRequest, err := vizUtil.BuildEdgesRequest(requestParams)\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tresult, err := h.apiClient.Edges(req.Context(), edgesRequest)\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\trenderJSONPb(w, result)\n}\n\nfunc (h *handler) handleAPICheck(w http.ResponseWriter, req *http.Request, p httprouter.Params) {\n\ttype CheckResult struct {\n\t\t*healthcheck.CheckResult\n\t\tErrMsg  string `json:\",omitempty\"`\n\t\tHintURL string `json:\",omitempty\"`\n\t}\n\n\tsuccess := true\n\tresults := make(map[healthcheck.CategoryID][]*CheckResult)\n\n\tcollectResults := func(result *healthcheck.CheckResult) {\n\t\tif result.Retry || excludedChecksRE.MatchString(result.Description) {\n\t\t\treturn\n\t\t}\n\t\tvar errMsg, hintURL string\n\t\tif result.Err != nil {\n\t\t\tif !result.Warning {\n\t\t\t\tsuccess = false\n\t\t\t}\n\t\t\terrMsg = result.Err.Error()\n\t\t\thintURL = result.HintURL\n\t\t}\n\t\tresults[result.Category] = append(results[result.Category], &CheckResult{\n\t\t\tCheckResult: result,\n\t\t\tErrMsg:      errMsg,\n\t\t\tHintURL:     hintURL,\n\t\t})\n\t}\n\t// TODO (tegioz): ignore runchecks results until we stop filtering checks\n\t// in this method (see #3670 for more details)\n\t_, _ = h.hc.RunChecks(collectResults)\n\n\trenderJSON(w, map[string]interface{}{\n\t\t\"success\": success,\n\t\t\"results\": results,\n\t})\n}\n\nfunc (h *handler) handleAPIResourceDefinition(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {\n\tvar missingParams []string\n\trequiredParams := []string{\"namespace\", \"resource_type\", \"resource_name\"}\n\tfor _, param := range requiredParams {\n\t\tif req.FormValue(param) == \"\" {\n\t\t\tmissingParams = append(missingParams, param)\n\t\t}\n\t}\n\tif len(missingParams) != 0 {\n\t\trenderJSONError(w, fmt.Errorf(\"required params not provided: %s\", strings.Join(missingParams, \", \")), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tnamespace := req.FormValue(\"namespace\")\n\tresourceType := req.FormValue(\"resource_type\")\n\tresourceName := req.FormValue(\"resource_name\")\n\n\tvar resource interface{}\n\tvar err error\n\toptions := metav1.GetOptions{}\n\tswitch resourceType {\n\tcase k8s.CronJob:\n\t\tresource, err = h.k8sAPI.BatchV1beta1().CronJobs(namespace).Get(req.Context(), resourceName, options)\n\tcase k8s.DaemonSet:\n\t\tresource, err = h.k8sAPI.AppsV1().DaemonSets(namespace).Get(req.Context(), resourceName, options)\n\tcase k8s.Deployment:\n\t\tresource, err = h.k8sAPI.AppsV1().Deployments(namespace).Get(req.Context(), resourceName, options)\n\tcase k8s.Service:\n\t\tresource, err = h.k8sAPI.CoreV1().Services(namespace).Get(req.Context(), resourceName, options)\n\tcase k8s.Job:\n\t\tresource, err = h.k8sAPI.BatchV1().Jobs(namespace).Get(req.Context(), resourceName, options)\n\tcase k8s.Pod:\n\t\tresource, err = h.k8sAPI.CoreV1().Pods(namespace).Get(req.Context(), resourceName, options)\n\tcase k8s.ReplicationController:\n\t\tresource, err = h.k8sAPI.CoreV1().ReplicationControllers(namespace).Get(req.Context(), resourceName, options)\n\tcase k8s.ReplicaSet:\n\t\tresource, err = h.k8sAPI.AppsV1().ReplicaSets(namespace).Get(req.Context(), resourceName, options)\n\tdefault:\n\t\trenderJSONError(w, errors.New(\"Invalid resource type: \"+resourceType), http.StatusBadRequest)\n\t\treturn\n\t}\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tresourceDefinition, err := yaml.Marshal(resource)\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\tw.Header().Set(\"Content-Type\", \"text/yaml\")\n\tw.Write(resourceDefinition)\n}\n\nfunc (h *handler) handleGetExtensions(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {\n\tctx := req.Context()\n\textensionName := req.FormValue(\"extension_name\")\n\n\ttype Extension struct {\n\t\tName      string `json:\"name\"`\n\t\tUID       string `json:\"uid\"`\n\t\tNamespace string `json:\"namespace\"`\n\t}\n\n\tresp := map[string]interface{}{}\n\tif extensionName != \"\" {\n\t\tns, err := h.k8sAPI.GetNamespaceWithExtensionLabel(ctx, extensionName)\n\t\tif err != nil && kerrors.IsNotFound(err) {\n\t\t\trenderJSON(w, resp)\n\t\t\treturn\n\t\t} else if err != nil {\n\t\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\tresp[\"data\"] = Extension{\n\t\t\tUID:       string(ns.UID),\n\t\t\tName:      ns.GetLabels()[k8s.LinkerdExtensionLabel],\n\t\t\tNamespace: ns.Name,\n\t\t}\n\n\t\trenderJSON(w, resp)\n\t\treturn\n\t}\n\n\tinstalledExtensions, err := h.k8sAPI.GetAllNamespacesWithExtensionLabel(ctx)\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\textensionList := make([]Extension, len(installedExtensions))\n\n\tfor i, installedExtension := range installedExtensions {\n\t\textensionList[i] = Extension{\n\t\t\tUID:       string(installedExtension.GetObjectMeta().GetUID()),\n\t\t\tName:      installedExtension.GetLabels()[k8s.LinkerdExtensionLabel],\n\t\t\tNamespace: installedExtension.GetName(),\n\t\t}\n\t}\n\n\tresp[\"extensions\"] = extensionList\n\trenderJSON(w, resp)\n}\n\nfunc (h *handler) handleAPIGateways(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {\n\twindow := req.FormValue(\"window\")\n\tif window == \"\" {\n\t\twindow = \"1m\"\n\t}\n\t_, err := time.ParseDuration(window)\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\tgatewayRequest := &metricsPb.GatewaysRequest{\n\t\tTimeWindow:        window,\n\t\tGatewayNamespace:  req.FormValue(\"gatewayNamespace\"),\n\t\tRemoteClusterName: req.FormValue(\"remoteClusterName\"),\n\t}\n\tresult, err := h.apiClient.Gateways(req.Context(), gatewayRequest)\n\tif err != nil {\n\t\trenderJSONError(w, err, http.StatusInternalServerError)\n\t\treturn\n\t}\n\trenderJSONPb(w, result)\n}\n"
  },
  {
    "path": "web/srv/api_handlers_test.go",
    "content": "package srv\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\tvizApi \"github.com/linkerd/linkerd2/viz/metrics-api\"\n\tpb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n)\n\ntype mockHealthChecker struct {\n\tresults []*healthcheck.CheckResult\n}\n\nfunc (c *mockHealthChecker) RunChecks(observer healthcheck.CheckObserver) (bool, bool) {\n\tfor _, result := range c.results {\n\t\tobserver(result)\n\t}\n\treturn true, false\n}\n\nfunc TestHandleApiCheck(t *testing.T) {\n\t// Setup handler using a mock health checker\n\tmockResults := []*healthcheck.CheckResult{\n\t\t{\n\t\t\tCategory:    healthcheck.LinkerdConfigChecks,\n\t\t\tDescription: \"check3-description\",\n\t\t\tHintURL:     healthcheck.DefaultHintBaseURL + \"check3-hint-anchor\",\n\t\t\tWarning:     false,\n\t\t\tErr:         nil,\n\t\t},\n\t\t{\n\t\t\tCategory:    healthcheck.LinkerdConfigChecks,\n\t\t\tDescription: \"check4-description-kubectl\",\n\t\t\tHintURL:     healthcheck.DefaultHintBaseURL + \"check4-hint-anchor\",\n\t\t\tWarning:     true,\n\t\t\tErr:         nil,\n\t\t},\n\t\t{\n\t\t\tCategory:    healthcheck.KubernetesAPIChecks,\n\t\t\tDescription: \"check1-description\",\n\t\t\tHintURL:     healthcheck.DefaultHintBaseURL + \"check1-hint-anchor\",\n\t\t\tWarning:     false,\n\t\t\tErr:         nil,\n\t\t},\n\t\t{\n\t\t\tCategory:    healthcheck.KubernetesAPIChecks,\n\t\t\tDescription: \"check2-description\",\n\t\t\tHintURL:     healthcheck.DefaultHintBaseURL + \"check2-hint-anchor\",\n\t\t\tWarning:     true,\n\t\t\tErr:         errors.New(\"check2-error\"),\n\t\t},\n\t}\n\th := &handler{\n\t\thc: &mockHealthChecker{\n\t\t\tresults: mockResults,\n\t\t},\n\t}\n\n\t// Handle request recording the response\n\treq := httptest.NewRequest(\"GET\", \"/api/check\", nil)\n\tw := httptest.NewRecorder()\n\th.handleAPICheck(w, req, httprouter.Params{})\n\tresp := w.Result()\n\tdefer resp.Body.Close()\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatalf(\"not expecting error reading response body but got: %v\", err)\n\t}\n\n\t// Check we receive the headers and body expected\n\texpectedHeaders := http.Header{\n\t\t\"Content-Type\": []string{\"application/json\"},\n\t}\n\tif diff := deep.Equal(resp.Header, expectedHeaders); diff != nil {\n\t\tt.Errorf(\"Unexpected header: %v\", diff)\n\t}\n\tapiCheckOutputGolden, err := os.ReadFile(\"testdata/api_check_output.json\")\n\tif err != nil {\n\t\tt.Fatalf(\"not expecting error reading api check output golden file but got: %v\", err)\n\t}\n\tapiCheckOutputGoldenCompact := &bytes.Buffer{}\n\terr = json.Compact(apiCheckOutputGoldenCompact, apiCheckOutputGolden)\n\tif err != nil {\n\t\tt.Fatalf(\"not expecting error compacting api check output golden file but got: %v\", err)\n\t}\n\tif !bytes.Equal(body, apiCheckOutputGoldenCompact.Bytes()) {\n\t\tt.Errorf(\"expecting response body to be\\n %s\\n but got\\n %s\", apiCheckOutputGoldenCompact.Bytes(), body)\n\t}\n}\n\nfunc TestHandleApiGateway(t *testing.T) {\n\tmockAPIClient := &vizApi.MockAPIClient{\n\t\tGatewaysResponseToReturn: &pb.GatewaysResponse{\n\t\t\tResponse: &pb.GatewaysResponse_Ok_{\n\t\t\t\tOk: &pb.GatewaysResponse_Ok{\n\t\t\t\t\tGatewaysTable: &pb.GatewaysTable{\n\t\t\t\t\t\tRows: []*pb.GatewaysTable_Row{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tNamespace:   \"test_namespace\",\n\t\t\t\t\t\t\t\tName:        \"test_gateway\",\n\t\t\t\t\t\t\t\tClusterName: \"multi_cluster\",\n\t\t\t\t\t\t\t\tAlive:       true,\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\tserver := FakeServer()\n\n\thandler := &handler{\n\t\trender:    server.RenderTemplate,\n\t\tapiClient: mockAPIClient,\n\t}\n\n\tt.Run(\"Returns expected gateway response\", func(t *testing.T) {\n\t\trecorder := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/api/gateways\", nil)\n\t\thandler.handleAPIGateways(recorder, req, httprouter.Params{})\n\t\tresp := recorder.Result()\n\t\tdefer resp.Body.Close()\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"not expecting error reading response body but got: %v\", err)\n\t\t}\n\n\t\tif recorder.Code != http.StatusOK {\n\t\t\tt.Errorf(\"Incorrect StatusCode: %+v\", recorder.Code)\n\t\t\tt.Errorf(\"Expected              %+v\", http.StatusOK)\n\t\t}\n\n\t\theader := http.Header{\n\t\t\t\"Content-Type\": []string{\"application/json\"},\n\t\t}\n\t\tif diff := deep.Equal(recorder.Header(), header); diff != nil {\n\t\t\tt.Errorf(\"Unexpected header: %v\", diff)\n\t\t}\n\n\t\tapiGatewayOutputGolden, err := os.ReadFile(\"testdata/api_gateway_output.json\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"not expecting error reading api check output golden file but got: %v\", err)\n\t\t}\n\t\tapiGatewayOutputGoldenCompact := &bytes.Buffer{}\n\t\terr = json.Compact(apiGatewayOutputGoldenCompact, apiGatewayOutputGolden)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"not expecting error compacting api check output golden file but got: %v\", err)\n\t\t}\n\t\tbodyCompact := &bytes.Buffer{}\n\t\terr = json.Compact(bodyCompact, body)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to compact response body: %s\", err)\n\t\t}\n\t\tif !bytes.Equal(bodyCompact.Bytes(), apiGatewayOutputGoldenCompact.Bytes()) {\n\t\t\tt.Errorf(\"expecting response body to be\\n %s\\n but got\\n %s\", apiGatewayOutputGoldenCompact.Bytes(), bodyCompact.Bytes())\n\t\t}\n\t})\n\n\tt.Run(\"Returns error when invalid timeWindow is passed\", func(t *testing.T) {\n\t\trecorder := httptest.NewRecorder()\n\t\treq := httptest.NewRequest(\"GET\", \"/api/gateways?window=1t\", nil)\n\t\thandler.handleAPIGateways(recorder, req, httprouter.Params{})\n\t\tresp := recorder.Result()\n\t\tdefer resp.Body.Close()\n\t\t_, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"not expecting error reading response body but got: %v\", err)\n\t\t}\n\t\tif recorder.Code == http.StatusOK {\n\t\t\tt.Errorf(\"Incorrect StatusCode: %+v\", recorder.Code)\n\t\t\tt.Errorf(\"Expected              %+v\", http.StatusInternalServerError)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "web/srv/check_same_origin.go",
    "content": "package srv\n\nimport (\n\t\"net/http\"\n\t\"net/url\"\n\t\"unicode/utf8\"\n)\n\n// checkSameOrigin returns true if the origin is not set or is equal to the request host.\n// Copied from gorilla/websocket.\nfunc checkSameOrigin(r *http.Request) bool {\n\torigin := r.Header[\"Origin\"]\n\tif len(origin) == 0 {\n\t\treturn true\n\t}\n\tu, err := url.Parse(origin[0])\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn equalASCIIFold(u.Host, r.Host)\n}\n\n// equalASCIIFold returns true if s is equal to t with ASCII case folding as\n// defined in RFC 4790.\n// Copied from gorilla/websocket.\nfunc equalASCIIFold(s, t string) bool {\n\tfor s != \"\" && t != \"\" {\n\t\tsr, size := utf8.DecodeRuneInString(s)\n\t\ts = s[size:]\n\t\ttr, size := utf8.DecodeRuneInString(t)\n\t\tt = t[size:]\n\t\tif sr == tr {\n\t\t\tcontinue\n\t\t}\n\t\tif 'A' <= sr && sr <= 'Z' {\n\t\t\tsr = sr + 'a' - 'A'\n\t\t}\n\t\tif 'A' <= tr && tr <= 'Z' {\n\t\t\ttr = tr + 'a' - 'A'\n\t\t}\n\t\tif sr != tr {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn s == t\n}\n"
  },
  {
    "path": "web/srv/handlers.go",
    "content": "package srv\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"regexp\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\tprofiles \"github.com/linkerd/linkerd2/pkg/profiles\"\n\tvizPb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/patrickmn/go-cache\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nvar proxyPathRegexp = regexp.MustCompile(\"/api/v1/namespaces/.*/proxy/\")\n\ntype (\n\trenderTemplate func(http.ResponseWriter, string, string, interface{}) error\n\n\thandler struct {\n\t\trender              renderTemplate\n\t\tapiClient           vizPb.ApiClient\n\t\tk8sAPI              *k8s.KubernetesAPI\n\t\tuuid                string\n\t\tversion             string\n\t\tcontrollerNamespace string\n\t\tclusterDomain       string\n\t\tgrafana             string\n\t\tgrafanaExternalURL  string\n\t\tgrafanaPrefix       string\n\t\tjaeger              string\n\t\tgrafanaProxy        *reverseProxy\n\t\tjaegerProxy         *reverseProxy\n\t\thc                  healthChecker\n\t\tstatCache           *cache.Cache\n\t}\n)\n\nfunc (h *handler) handleIndex(w http.ResponseWriter, req *http.Request, p httprouter.Params) {\n\t// when running the dashboard via `linkerd dashboard`, serve the index bundle at the right path\n\tpathPfx := proxyPathRegexp.FindString(req.URL.Path)\n\tif pathPfx == \"\" {\n\t\tpathPfx = \"/\"\n\t}\n\n\tparams := appParams{\n\t\tUUID:                h.uuid,\n\t\tReleaseVersion:      h.version,\n\t\tControllerNamespace: h.controllerNamespace,\n\t\tPathPrefix:          pathPfx,\n\t\tGrafana:             h.grafana,\n\t\tGrafanaExternalURL:  h.grafanaExternalURL,\n\t\tGrafanaPrefix:       h.grafanaPrefix,\n\t\tJaeger:              h.jaeger,\n\t}\n\n\terr := h.render(w, \"app.tmpl.html\", \"base\", params)\n\tif err != nil {\n\t\tlog.Error(err)\n\t}\n}\n\nfunc (h *handler) handleProfileDownload(w http.ResponseWriter, req *http.Request, params httprouter.Params) {\n\tservice := req.FormValue(\"service\")\n\tnamespace := req.FormValue(\"namespace\")\n\n\tif service == \"\" || namespace == \"\" {\n\t\terr := fmt.Errorf(\"Service and namespace must be provided to create a new profile\")\n\t\tlog.Error(err)\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tprofileYaml := &bytes.Buffer{}\n\terr := profiles.RenderProfileTemplate(namespace, service, h.clusterDomain, profileYaml, \"yaml\")\n\n\tif err != nil {\n\t\tlog.Error(err)\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tdispositionHeaderVal := fmt.Sprintf(\"attachment; filename=%s-profile.yml\", service)\n\n\tw.Header().Set(\"Content-Type\", \"text/yaml\")\n\tw.Header().Set(\"Content-Disposition\", dispositionHeaderVal)\n\n\tw.Write(profileYaml.Bytes())\n}\n\nfunc (h *handler) handleGrafana(w http.ResponseWriter, req *http.Request, p httprouter.Params) {\n\th.grafanaProxy.ServeHTTP(w, req)\n}\n\nfunc (h *handler) handleJaeger(w http.ResponseWriter, req *http.Request, p httprouter.Params) {\n\th.jaegerProxy.ServeHTTP(w, req)\n}\n"
  },
  {
    "path": "web/srv/handlers_test.go",
    "content": "package srv\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/go-test/deep\"\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2\"\n\thelpers \"github.com/linkerd/linkerd2/pkg/profiles\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nconst releaseVersion = \"0.3.3\"\n\nfunc TestHandleIndex(t *testing.T) {\n\tserver := FakeServer()\n\n\thandler := &handler{\n\t\trender:  server.RenderTemplate,\n\t\tversion: releaseVersion,\n\t}\n\n\trecorder := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\thandler.handleIndex(recorder, req, httprouter.Params{})\n\n\tif recorder.Code != http.StatusOK {\n\t\tt.Errorf(\"Incorrect StatusCode: %+v\", recorder.Code)\n\t\tt.Errorf(\"Expected              %+v\", http.StatusOK)\n\t}\n\n\theader := http.Header{\n\t\t\"Content-Type\": []string{\"text/html\"},\n\t}\n\tif diff := deep.Equal(recorder.Header(), header); diff != nil {\n\t\tt.Errorf(\"Unexpected header: %v\", diff)\n\t}\n\n\tactualBody := recorder.Body.String()\n\n\texpectedSubstrings := []string{\n\t\t\"<div class=\\\"main\\\" id=\\\"main\\\"\",\n\t\t\"data-release-version=\\\"\" + releaseVersion + \"\\\"\",\n\t\t\"data-controller-namespace=\\\"\\\"\",\n\t\t\"data-uuid=\\\"\\\"\",\n\t}\n\tfor _, expectedSubstring := range expectedSubstrings {\n\t\tif !strings.Contains(actualBody, expectedSubstring) {\n\t\t\tt.Fatalf(\"Expected string [%s] to be present in [%s]\", expectedSubstring, actualBody)\n\t\t}\n\t}\n}\n\nfunc TestHandleConfigDownload(t *testing.T) {\n\tserver := FakeServer()\n\n\thandler := &handler{\n\t\trender:              server.RenderTemplate,\n\t\tcontrollerNamespace: \"linkerd\",\n\t\tclusterDomain:       \"mycluster.local\",\n\t}\n\n\trecorder := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", \"/profiles/new?service=authors&namespace=booksns\", nil)\n\n\thandler.handleProfileDownload(recorder, req, httprouter.Params{})\n\n\tif recorder.Code != http.StatusOK {\n\t\tt.Errorf(\"Incorrect StatusCode: %+v\", recorder.Code)\n\t\tt.Errorf(\"Expected              %+v\", http.StatusOK)\n\t}\n\n\theader := http.Header{\n\t\t\"Content-Type\": []string{\n\t\t\t\"text/yaml\",\n\t\t},\n\t\t\"Content-Disposition\": []string{\n\t\t\t\"attachment; filename=authors-profile.yml\",\n\t\t},\n\t}\n\tif diff := deep.Equal(recorder.Header(), header); diff != nil {\n\t\tt.Errorf(\"Unexpected header: %v\", diff)\n\t}\n\n\tvar serviceProfile v1alpha2.ServiceProfile\n\terr := yaml.Unmarshal(recorder.Body.Bytes(), &serviceProfile)\n\tif err != nil {\n\t\tt.Fatalf(\"Error parsing service profile: %v\", err)\n\t}\n\n\texpectedServiceProfile := helpers.GenServiceProfile(\"authors\", \"booksns\", \"mycluster.local\")\n\n\terr = helpers.ServiceProfileYamlEquals(serviceProfile, expectedServiceProfile)\n\tif err != nil {\n\t\tt.Fatalf(\"ServiceProfiles are not equal: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "web/srv/reverse_proxy.go",
    "content": "package srv\n\nimport (\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"strings\"\n)\n\n// reverseProxy is an HTTP reverse proxy that forwards all web requests\n// containing paths prefixed  to the corresponding service. The proxy\n// strips the prefix and rewrites the Host header before sending.\ntype reverseProxy struct {\n\t*httputil.ReverseProxy\n}\n\nfunc newReverseProxy(addr string, prefix string) *reverseProxy {\n\tdirector := func(req *http.Request) {\n\t\treq.URL.Host = addr\n\t\treq.URL.Scheme = \"http\"\n\t\treq.URL.Path = strings.TrimPrefix(req.URL.Path, prefix)\n\n\t\t// the default director implementation does this, so we will too\n\t\tif _, ok := req.Header[\"User-Agent\"]; !ok {\n\t\t\t// explicitly disable User-Agent so it's not set to default value\n\t\t\treq.Header.Set(\"User-Agent\", \"\")\n\t\t}\n\t}\n\n\treturn &reverseProxy{\n\t\tReverseProxy: &httputil.ReverseProxy{Director: director},\n\t}\n}\n"
  },
  {
    "path": "web/srv/server.go",
    "content": "package srv\n\nimport (\n\t\"fmt\"\n\t\"html\"\n\t\"html/template\"\n\t\"net/http\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/linkerd/linkerd2/pkg/filesonly\"\n\t\"github.com/linkerd/linkerd2/pkg/healthcheck\"\n\t\"github.com/linkerd/linkerd2/pkg/k8s\"\n\t\"github.com/linkerd/linkerd2/pkg/prometheus\"\n\tvizPb \"github.com/linkerd/linkerd2/viz/metrics-api/gen/viz\"\n\t\"github.com/patrickmn/go-cache\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nconst (\n\ttimeout = 15 * time.Second\n\n\t// statExpiration indicates when items in the stat cache expire.\n\tstatExpiration = 1500 * time.Millisecond\n\n\t// statCleanupInterval indicates how often expired items in the stat cache\n\t// are cleaned up.\n\tstatCleanupInterval = 5 * time.Minute\n)\n\ntype (\n\t// Server encapsulates the Linkerd control plane's web dashboard server.\n\tServer struct {\n\t\ttemplateDir string\n\t\treload      bool\n\t\ttemplates   map[string]*template.Template\n\t\trouter      *httprouter.Router\n\t\treHost      *regexp.Regexp\n\t}\n\n\ttemplatePayload struct {\n\t\tContents interface{}\n\t}\n\tappParams struct {\n\t\tUUID                string\n\t\tReleaseVersion      string\n\t\tControllerNamespace string\n\t\tError               bool\n\t\tErrorMessage        string\n\t\tPathPrefix          string\n\t\tJaeger              string\n\t\tGrafana             string\n\t\tGrafanaExternalURL  string\n\t\tGrafanaPrefix       string\n\t}\n\n\thealthChecker interface {\n\t\tRunChecks(observer healthcheck.CheckObserver) (bool, bool)\n\t}\n)\n\n// this is called by the HTTP server to actually respond to a request\nfunc (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {\n\tif !s.reHost.MatchString(req.Host) {\n\t\terr := fmt.Sprintf(`It appears that you are trying to reach this service with a host of '%s'.\nThis does not match /%s/ and has been denied for security reasons.\nPlease see https://linkerd.io/dns-rebinding for an explanation of what is happening and how to fix it.`,\n\t\t\thtml.EscapeString(req.Host),\n\t\t\thtml.EscapeString(s.reHost.String()))\n\t\thttp.Error(w, err, http.StatusBadRequest)\n\t\treturn\n\t}\n\tw.Header().Set(\"X-Content-Type-Options\", \"nosniff\")\n\tw.Header().Set(\"X-Frame-Options\", \"SAMEORIGIN\")\n\tw.Header().Set(\"X-XSS-Protection\", \"1; mode=block\")\n\ts.router.ServeHTTP(w, req)\n}\n\n// NewServer returns an initialized `http.Server`, configured to listen on an\n// address, render templates, and serve static assets, for a given Linkerd\n// control plane.\nfunc NewServer(\n\taddr string,\n\tgrafanaAddr string,\n\tgrafanaExternalAddr string,\n\tgrafanaPrefix string,\n\tjaegerAddr string,\n\ttemplateDir string,\n\tstaticDir string,\n\tuuid string,\n\tversion string,\n\tcontrollerNamespace string,\n\tclusterDomain string,\n\treload bool,\n\treHost *regexp.Regexp,\n\tapiClient vizPb.ApiClient,\n\tk8sAPI *k8s.KubernetesAPI,\n\thc healthChecker,\n) *http.Server {\n\tserver := &Server{\n\t\ttemplateDir: templateDir,\n\t\treload:      reload,\n\t\treHost:      reHost,\n\t}\n\n\tserver.router = &httprouter.Router{\n\t\tRedirectTrailingSlash:  true,\n\t\tRedirectFixedPath:      true,\n\t\tHandleMethodNotAllowed: false, // disable 405s\n\t}\n\n\twrappedServer := prometheus.WithTelemetry(server)\n\thandler := &handler{\n\t\tapiClient:           apiClient,\n\t\tk8sAPI:              k8sAPI,\n\t\trender:              server.RenderTemplate,\n\t\tuuid:                uuid,\n\t\tversion:             version,\n\t\tcontrollerNamespace: controllerNamespace,\n\t\tclusterDomain:       clusterDomain,\n\t\tjaegerProxy:         newReverseProxy(jaegerAddr, \"\"),\n\t\tgrafana:             grafanaAddr,\n\t\tgrafanaExternalURL:  grafanaExternalAddr,\n\t\tgrafanaPrefix:       grafanaPrefix,\n\t\tjaeger:              jaegerAddr,\n\t\thc:                  hc,\n\t\tstatCache:           cache.New(statExpiration, statCleanupInterval),\n\t}\n\n\t// Only create the grafana reverse proxy if we aren't using external grafana\n\tif grafanaExternalAddr == \"\" {\n\t\thandler.grafanaProxy = newReverseProxy(grafanaAddr, \"/grafana\")\n\t}\n\n\thttpServer := &http.Server{\n\t\tAddr:              addr,\n\t\tReadTimeout:       timeout,\n\t\tReadHeaderTimeout: timeout,\n\t\tWriteTimeout:      timeout,\n\t\tHandler:           wrappedServer,\n\t}\n\n\t// webapp routes\n\tserver.router.GET(\"/\", handler.handleIndex)\n\tserver.router.GET(\"/controlplane\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces\", handler.handleIndex)\n\tserver.router.GET(\"/gateways\", handler.handleIndex)\n\n\t// paths for a list of resources by namespace\n\tserver.router.GET(\"/namespaces/:namespace/daemonsets\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/statefulsets\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/jobs\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/deployments\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/services\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/replicationcontrollers\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/pods\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/cronjobs\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/replicasets\", handler.handleIndex)\n\n\t// legacy paths that are deprecated but should not 404\n\tserver.router.GET(\"/overview\", handler.handleIndex)\n\tserver.router.GET(\"/daemonsets\", handler.handleIndex)\n\tserver.router.GET(\"/statefulsets\", handler.handleIndex)\n\tserver.router.GET(\"/jobs\", handler.handleIndex)\n\tserver.router.GET(\"/deployments\", handler.handleIndex)\n\tserver.router.GET(\"/services\", handler.handleIndex)\n\tserver.router.GET(\"/replicationcontrollers\", handler.handleIndex)\n\tserver.router.GET(\"/pods\", handler.handleIndex)\n\n\t// paths for individual resource view\n\tserver.router.GET(\"/namespaces/:namespace\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/pods/:pod\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/daemonsets/:daemonset\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/statefulsets/:statefulset\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/deployments/:deployment\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/services/:deployment\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/jobs/:job\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/replicationcontrollers/:replicationcontroller\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/cronjobs/:cronjob\", handler.handleIndex)\n\tserver.router.GET(\"/namespaces/:namespace/replicasets/:replicaset\", handler.handleIndex)\n\n\t// tools and community paths\n\tserver.router.GET(\"/tap\", handler.handleIndex)\n\tserver.router.GET(\"/top\", handler.handleIndex)\n\tserver.router.GET(\"/community\", handler.handleIndex)\n\tserver.router.GET(\"/routes\", handler.handleIndex)\n\tserver.router.GET(\"/extensions\", handler.handleIndex)\n\tserver.router.GET(\"/profiles/new\", handler.handleProfileDownload)\n\n\t// add catch-all parameter to match all files in dir\n\tserver.router.GET(\"/dist/*filepath\", mkStaticHandler(staticDir))\n\n\t// webapp api routes\n\tserver.router.GET(\"/api/version\", handler.handleAPIVersion)\n\t// Traffic Performance Summary.  This route used to be called /api/stat\n\t// but was renamed to avoid triggering ad blockers.\n\t// See: https://github.com/linkerd/linkerd2/issues/970\n\tserver.router.GET(\"/api/tps-reports\", handler.handleAPIStat)\n\tserver.router.GET(\"/api/pods\", handler.handleAPIPods)\n\tserver.router.GET(\"/api/services\", handler.handleAPIServices)\n\tserver.router.GET(\"/api/tap\", handler.handleAPITap)\n\tserver.router.GET(\"/api/routes\", handler.handleAPITopRoutes)\n\tserver.router.GET(\"/api/edges\", handler.handleAPIEdges)\n\tserver.router.GET(\"/api/check\", handler.handleAPICheck)\n\tserver.router.GET(\"/api/resource-definition\", handler.handleAPIResourceDefinition)\n\tserver.router.GET(\"/api/gateways\", handler.handleAPIGateways)\n\tserver.router.GET(\"/api/extensions\", handler.handleGetExtensions)\n\n\t// grafana proxy, only used if external grafana is not in use\n\tif grafanaExternalAddr == \"\" {\n\t\tserver.handleAllOperationsForPath(\"/grafana/*grafanapath\", handler.handleGrafana)\n\t}\n\n\t// jaeger proxy\n\tserver.handleAllOperationsForPath(\"/jaeger/*jaegerpath\", handler.handleJaeger)\n\n\treturn httpServer\n}\n\n// RenderTemplate writes a rendered template into a buffer, given an HTTP\n// request and template information.\nfunc (s *Server) RenderTemplate(w http.ResponseWriter, templateFile, templateName string, args interface{}) error {\n\tlog.Debugf(\"emitting template %s\", templateFile)\n\ttemplate, err := s.loadTemplate(templateFile)\n\n\tif err != nil {\n\t\tlog.Error(err.Error())\n\t\thttp.Error(w, \"internal server error\", http.StatusInternalServerError)\n\t\treturn nil\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"text/html\")\n\tif templateName == \"\" {\n\t\treturn template.Execute(w, args)\n\t}\n\n\treturn template.ExecuteTemplate(w, templateName, templatePayload{Contents: args})\n}\n\nfunc (s *Server) loadTemplate(templateFile string) (template *template.Template, err error) {\n\t// load template from disk if necessary\n\ttemplate = s.templates[templateFile]\n\n\tif template == nil || s.reload {\n\t\ttemplatePath := safelyJoinPath(s.templateDir, templateFile)\n\t\tincludes, err := filepath.Glob(filepath.Join(s.templateDir, \"includes\", \"*.tmpl.html\"))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// for cases where you're not calling a named template, the passed-in path needs to be first\n\t\ttemplateFiles := append([]string{templatePath}, includes...)\n\t\tlog.Debugf(\"loading templates from %v\", templateFiles)\n\t\ttemplate, err = template.ParseFiles(templateFiles...)\n\t\tif err == nil && !s.reload {\n\t\t\ts.templates[templateFile] = template\n\t\t}\n\t}\n\treturn template, err\n}\n\nfunc (s *Server) handleAllOperationsForPath(path string, handle httprouter.Handle) {\n\ts.router.DELETE(path, handle)\n\ts.router.GET(path, handle)\n\ts.router.HEAD(path, handle)\n\ts.router.OPTIONS(path, handle)\n\ts.router.PATCH(path, handle)\n\ts.router.POST(path, handle)\n\ts.router.PUT(path, handle)\n}\n\nfunc safelyJoinPath(rootPath, userPath string) string {\n\treturn filepath.Join(rootPath, path.Clean(\"/\"+userPath))\n}\n\nfunc mkStaticHandler(staticDir string) httprouter.Handle {\n\tfileServer := http.FileServer(filesonly.FileSystem(staticDir))\n\n\treturn func(w http.ResponseWriter, req *http.Request, p httprouter.Params) {\n\t\tfilepath := p.ByName(\"filepath\")\n\t\tif filepath == \"/index_bundle.js\" {\n\t\t\t// don't cache the bundle because it references a hashed js file\n\t\t\tw.Header().Set(\"Cache-Control\", \"no-store, must-revalidate\")\n\t\t}\n\n\t\treq.URL.Path = filepath\n\t\tfileServer.ServeHTTP(w, req)\n\t}\n}\n"
  },
  {
    "path": "web/srv/test_helpers.go",
    "content": "package srv\n\n// FakeServer provides a mock of a Server in `/web/srv`.\nfunc FakeServer() Server {\n\treturn Server{\n\t\ttemplateDir: \"../templates\",\n\t\treload:      true,\n\t}\n}\n"
  },
  {
    "path": "web/srv/testdata/api_check_output.json",
    "content": "{\n    \"results\": {\n        \"kubernetes-api\": [{\n            \"Category\": \"kubernetes-api\",\n            \"Description\": \"check1-description\",\n            \"Retry\": false,\n            \"Warning\": false,\n            \"Err\": null\n        }, {\n            \"Category\": \"kubernetes-api\",\n            \"Description\": \"check2-description\",\n            \"Retry\": false,\n            \"Warning\": true,\n            \"Err\": {},\n            \"ErrMsg\": \"check2-error\",\n            \"HintURL\": \"https://linkerd.io/2/checks/#check2-hint-anchor\"\n        }],\n        \"linkerd-config\": [{\n            \"Category\": \"linkerd-config\",\n            \"Description\": \"check3-description\",\n            \"Retry\": false,\n            \"Warning\": false,\n            \"Err\": null\n        }]\n    },\n    \"success\": true\n}\n"
  },
  {
    "path": "web/srv/testdata/api_gateway_output.json",
    "content": "{\n    \"ok\": {\n        \"gatewaysTable\": {\n            \"rows\": [\n                {\n                    \"namespace\": \"test_namespace\",\n                    \"name\": \"test_gateway\",\n                    \"clusterName\": \"multi_cluster\",\n                    \"pairedServices\": \"0\",\n                    \"alive\": true,\n                    \"latencyMsP50\": \"0\",\n                    \"latencyMsP95\": \"0\",\n                    \"latencyMsP99\": \"0\"\n                }\n            ]\n        }\n    }\n}"
  },
  {
    "path": "web/templates/app.tmpl.html",
    "content": "{{ define \"content\" }}\n  <div class=\"main\" id=\"main\"\n    data-release-version=\"{{.ReleaseVersion}}\"\n    data-controller-namespace=\"{{.ControllerNamespace}}\"\n    data-uuid=\"{{.UUID}}\"\n    data-grafana=\"{{.Grafana}}\"\n    data-grafana-external-url=\"{{.GrafanaExternalURL}}\"\n    data-grafana-prefix=\"{{.GrafanaPrefix}}\"\n    data-jaeger=\"{{.Jaeger}}\">\n    {{ if .Error }}\n      <p>Failed to call public API: {{ .ErrorMessage }}</p>\n    {{ end }}\n  </div>\n{{ end }}\n\n{{ define \"script-tags\" }}\n  {{ if not .Contents.Error }}\n    <script id=\"bundle\" type=\"text/javascript\" src=\"{{.Contents.PathPrefix}}dist/index_bundle.js\" async></script>\n  {{end}}\n{{end}}\n"
  },
  {
    "path": "web/templates/includes/base.tmpl.html",
    "content": "{{ define \"base\" }}\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width\">\n    <title>Linkerd</title>\n    <meta name=\"description\" content=\"Linkerd\">\n    <meta name=\"keywords\" content=\"Linkerd\">\n    <link rel=\"icon\" type=\"image/png\" href=\"{{.Contents.PathPrefix}}dist/img/favicon.png\">\n    <!-- material-ui uses Roboto -->\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:100,300,400|Roboto+Mono\" rel=\"stylesheet\">\n    {{ template \"script-tags\" . }}\n  </head>\n  <body>\n    {{ template \"content\" .Contents }}\n  </body>\n</html>\n{{ end }}\n"
  }
]